Skip to content

Commit

Permalink
[FEATURE]: enable keyboard and screen reader accessiblity on bootstra…
Browse files Browse the repository at this point in the history
…p carousel (#324)
  • Loading branch information
Anna Färber authored and dmh committed Oct 30, 2017
1 parent 1fc8c04 commit f79ec81
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 35 deletions.
2 changes: 1 addition & 1 deletion Configuration/TCA/Overrides/tt_content.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@
$GLOBALS['TCA']['tt_content']['columns']['pi_flexform']['config']['ds']['*,contentElementBootstrapSlider'] = $flexformPath . 'flexform_bootstrapSlider.xml';

$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['appearance']['collapseAll'] = 0;
$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['0']['showitem'] = '--div--;tx_themet3kit_slide_btn_txt, --palette--;;imageoverlayPalette, --palette--;;filePalette';
$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['0']['showitem'] = '--div--;,tx_themet3kit_slide_btn_txt, --palette--;;imageoverlayPalette, --palette--;;filePalette';
$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['1']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['0']['showitem'];
$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['2']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['0']['showitem'];
$GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['3']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['contentElementBootstrapSlider']['columnsOverrides']['image']['config']['foreign_types']['0']['showitem'];
Expand Down
18 changes: 18 additions & 0 deletions Resources/Private/Language/de.locallang.xlf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.0">
<file source-language="en" target-language="de" datatype="plaintext" original="messages" date="2017-10-17T14:30:39Z" product-name="theme_t3kit">
<header/>
<body>
<!--BootstrapCarousel Element-->
<trans-unit id="bootstrapCarousel.screenReader.title">
<target>Bilderkarussell mit %s Bildern.</target>
</trans-unit>
<trans-unit id="bootstrapCarousel.screenReader.slide">
<target>Bild</target>
</trans-unit>
<trans-unit id="bootstrapCarousel.ariaLabel.control">
<target>Zeige Bild %1$s von %2$s</target>
</trans-unit>
</body>
</file>
</xliff>
11 changes: 11 additions & 0 deletions Resources/Private/Language/locallang.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@
<trans-unit id="iconFontSelector.exception.iconFileMissing">
<source>Missing font file included from the css</source>
</trans-unit>

<!--BootstrapCarousel Element-->
<trans-unit id="bootstrapCarousel.screenReader.title">
<source>Image carousel with %s slides.</source>
</trans-unit>
<trans-unit id="bootstrapCarousel.screenReader.slide">
<source>Slide</source>
</trans-unit>
<trans-unit id="bootstrapCarousel.ariaLabel.control">
<source>Show iamge %1$s of %2$s</source>
</trans-unit>
</body>
</file>
</xliff>
77 changes: 46 additions & 31 deletions Resources/Private/Templates/ContentElements/BootstrapSlider.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,45 @@
<f:if condition="{slides}">
<div id="carousel-{data.uid}" class="carousel image-slider slide"
data-interval="{f:if(condition:'{settings.interval}==0', then:'false', else:'{settings.interval}000')}"
data-ride="carousel">
<div class="carousel-inner" role="listbox">
<f:for each="{slides}" as="image" iteration="iteration">
<div class="item{f:if(condition: iteration.isFirst, then: ' active')}">
data-ride="carousel" role="complementary">
<f:comment>If one of the fixed settings is enabled show the fixed caption</f:comment>
<f:if condition="{settings.fixedTitle} || {settings.fixedDescription} || {settings.fixedLink}">
<f:render section="caption"
arguments="{
title: settings.fixedTitle,
description: settings.fixedDescription,
linkText: settings.fixedLinkText,
link: settings.fixedLink,
settings: settings,
firstImage: 'true'}" />
</f:if>

<f:comment>Quicklinks</f:comment>
<f:if condition="{settings.quickselect}">
<f:if condition="{slides->f:count()} > 1">
<ol class="carousel-indicators" role="tablist">
<f:for each="{slides}" as="image" iteration="i">
<li data-target="#carousel-{data.uid}" data-slide-to="{i.index}"
class="{f:if(condition: i.isFirst, then: 'active')}"
id="carousel-{data.uid}__tab-{i.index}"
role="tab" aria-controls="carousel-{data.uid}__tabpanel-{i.index}"
aria-selected="{f:if(condition: i.isFirst, then: 'true', else: 'false')}"
tabindex="{f:if(condition: i.isFirst, then: '0', else: '-1')}">
<span class="sr-only">
<f:translate key="bootstrapCarousel.screenReader.slide" extensionName="theme_t3kit" />
{i.cycle} {image.title} {image.description}
</span>
</li>
</f:for>
</ol>
</f:if>
</f:if>

<div class="carousel-inner">
<f:for each="{slides}" as="image" iteration="i">
<div class="item{f:if(condition: i.isFirst, then: ' active')}" id="carousel-{data.uid}__tabpanel-{i.index}"
role="tabpanel" aria-labelledby="carousel-{data.uid}__tab-{i.index}"
data-controllabel="{f:translate(key:'bootstrapCarousel.ariaLabel.control', arguments:{0: '{i.cycle}', 1: '{f:count(subject: slides)}'}, extensionName:'theme_t3kit')}">
<f:media
file="{image}"
height="{data.imageheight}c"
Expand All @@ -27,42 +62,22 @@
linkText: image.referenceProperties.tx_themet3kit_slide_btn_txt,
link: image.link,
settings: settings,
firstImage: iteration.isFirst}" />
firstImage: i.isFirst}" />
</f:else>
</f:if>
</div>
</f:for>
</div>

<f:comment>If one of the fixed settings is enabled show the fixed caption</f:comment>
<f:if condition="{settings.fixedTitle} || {settings.fixedDescription} || {settings.fixedLink}">
<f:render section="caption"
arguments="{
title: settings.fixedTitle,
description: settings.fixedDescription,
linkText: settings.fixedLinkText,
link: settings.fixedLink,
settings: settings,
firstImage: 'true'}" />
</f:if>

<f:if condition="{settings.quickselect}">
<f:if condition="{slides->f:count()} > 1">
<ol class="carousel-indicators">
<f:for each="{slides}" as="image" iteration="iteration">
<li data-target="#carousel-{data.uid}" data-slide-to="{iteration.index}"
class="{f:if(condition: iteration.isFirst, then: 'active')}"></li>
</f:for>
</ol>
</f:if>
</f:if>

<f:comment>Prev, Next Arrows</f:comment>
<f:if condition="{slides->f:count()} > 1">
<div class="container carousel-control-wrapper">
<a class="carousel-control left" href="#carousel-{data.uid}" role="button" data-slide="prev">
<a class="carousel-control left" href="#carousel-{data.uid}" role="button" data-slide="prev"
aria-label="{f:translate(key:'bootstrapCarousel.ariaLabel.prev', extensionName:'theme_t3kit')}">
<span class="icons icon-chevron-left"></span>
</a>
<a class="carousel-control right" href="#carousel-{data.uid}" role="button" data-slide="next">
<a class="carousel-control right" href="#carousel-{data.uid}" role="button" data-slide="next"
aria-label="{f:translate(key:'bootstrapCarousel.ariaLabel.next', extensionName:'theme_t3kit')}">
<span class="icons icon-chevron-right"></span>
</a>
</div>
Expand All @@ -84,7 +99,7 @@
</f:if>

<f:if condition="{link}">
<f:link.typolink parameter="{link}" class="btn carousel__btn">
<f:link.typolink parameter="{link}" class="btn carousel__btn" additionalAttributes="{role:'button'}">
<f:if condition="{linkText}">
<f:then>{linkText}</f:then>
<f:else><f:translate key="contentElement.slider.linkText" extensionName="theme_t3kit"/></f:else>
Expand Down
89 changes: 86 additions & 3 deletions felayout_t3kit/dev/js/main/contentElements/bootstrapSlider.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,100 @@
// document load event
$(document).ready(function () {
$('.carousel').each(function () {
$(this).swipe({
var $this = $(this)
var $quickLinks = $this.find('.carousel-indicators li')
var $control = $this.find('.carousel-control')
var $btn = $this.find('.carousel__btn')

// Enable swipe for each carousel element
$this.swipe({
swipe: function (event, direction) {
if (direction === 'left') {
$(this).carousel('next')
$this.carousel('next')
}
if (direction === 'right') {
$(this).carousel('prev')
$this.carousel('prev')
}
},
allowPageScroll: 'vertical'
})

// Pause carousel if it has focus
if ($this.data('interval') !== false) {
pauseCarouselOnFocus()
}

// After carousel slide update aria-selected and tab index
$this.on('slid.bs.carousel', function (event) {
setAriaOnQuickLinks()
updateControlAriaLabel($(event.relatedTarget))
})

// Update quick link focus on keyboard use
// This is needed because we navigate with left(37) and right(39) key
$this.on('keydown.bs.carousel', function (event) {
if (event.which === 37 || event.which === 39) {
updateFocusOnQuickLinks()
}
})

// Set aria-selected and tab index to true only for active item
function setAriaOnQuickLinks () {
$quickLinks.each(function () {
var link = $(this)
if (link.hasClass('active')) {
link.attr({'aria-selected': 'true', 'tabindex': '0'})
} else {
link.attr({'aria-selected': 'false', 'tabindex': '-1'})
}
})
}

// Set focus on active quick link item
function updateFocusOnQuickLinks () {
$quickLinks.each(function () {
if ($(this).hasClass('active')) {
$(this).focus()
} else {
$(this).blur()
}
})
}

// Detect carousel focus elements and pause when one is focused
function pauseCarouselOnFocus () {
$quickLinks.add($control).add($btn).each(function () {
$(this).focus(function () {
$this.carousel('pause')
})
$(this).blur(function () {
$this.carousel('cycle')
})
})
}

// Update aria-label on prev and next button depending on active slide
function updateControlAriaLabel (element) {
var $slideLeft = $('.carousel-control.left')
var $slideRight = $('.carousel-control.right')
var nextLabel
var prevLabel

if (element.next().length > 0) {
nextLabel = element.next().attr('data-controllabel')
} else {
nextLabel = element.parent().children('.item').first().attr('data-controllabel')
}

if (element.prev().length > 0) {
prevLabel = element.prev().attr('data-controllabel')
} else {
prevLabel = element.parent().children('.item').last().attr('data-controllabel')
}

$slideLeft.attr('aria-label', nextLabel)
$slideRight.attr('aria-label', prevLabel)
}
})
})
})(jQuery)

0 comments on commit f79ec81

Please sign in to comment.