app/scripts/ui-lib/widgets/list.coffee

List

The List class provides a list of options.

author: Julien Ramboz version: 1.0 references: WAI-ARIA listbox role, AOL's Listbox style guide usage: this widget should not be used directly

AMD loader

Try loading as AMD module or fall back to default loading

((widget) -> if typeof define is "function" and define.amd define ["jslib", "core", "widget"], widget else widget @$, _, _.AbstractWidget ) list = ($, _, AbstractWidget) -> "use strict"

Widget

The actual widget class

class List extends AbstractWidget

Default options for the widget

@defaultOptions:
  • event: the event to react to
event: "click"
  • panels: a selector pointing to the panels
panels: ""
  • items: a selector pointing to the items (from within the panels)
items: ""
  • cycleItems: whether to cycle though the items or not (true or false)
cycleItems: false

Initialisation

Initializer function.

initialize: (options) -> super options @panels = @findPanels() @items = @findItems()

Accessibility markup

Add aria attributes

aria: () -> @items.attr("role", "option") @items.each () -> this.id = _.getGUID("listbox-option-") if not this.id if @items.length > 1 $parents = @items.first().parents() @wrapper = $parents.filter( (i) => $parents.eq(i).find(@items.last()).length ).first() else @wrapper = @items.parent() @wrapper.attr("role", "listbox") @panels.attr("role", "group")

Event handling

Attach evenets to the widget

bindEvents: () -> @handleKeys(@element)

select list items on click

@element.on @options.event, "[role='option']", (e) => if e.target.getAttribute("aria-disabled") is "true" return e.preventDefault() $option = $(e.target) if e.target.getAttribute('role') isnt 'option' $option = $(e.target).closest('[role="option"]') @selectItem($option).focus() @selectItem()

Select the specified item

selectItem: ($item) -> return if @element.get(0).getAttribute("aria-disabled") is "true" if @currentItem and @currentItem.attr('data-target') $target = $(@currentItem.attr('data-target')) if $target.attr('src') isnt undefined $target.attr('src', '') @currentItem = $item or @items.first() @currentPanel = @panels.filter( (i) => @panels.eq(i).find(@currentItem).length ) @element.trigger("beforeSelect", {item: @currentItem, panel: @currentPanel}) @items.removeAttr("tabindex") @panels.filter( (i) => @panels.get(i).getAttribute("aria-hidden") isnt "true") .attr("aria-hidden", "true") @currentPanel.attr("aria-hidden", "false") @currentItem.attr("tabindex", 0) @element.trigger("afterSelect", {item: @currentItem, panel: @currentPanel}) @wrapper.attr("aria-activedescendant", @currentItem.attr("id")) if @currentItem.attr('data-target') $target = $(@currentItem.attr('data-target')) if $target.attr('src') isnt undefined $target.attr('src', @currentItem.attr('data-url')) @currentItem

Select the previous item

selectPreviousItem: () -> index = @items.index(@currentItem) if @options.cycleItems is "true" or @options.cycleItems is true or index > 0 @selectItem(@items.eq(index - 1)) else @items.first()

Select the next item

selectNextItem: () -> index = @items.index(@currentItem) if @options.cycleItems is "true" or @options.cycleItems is true or index < @items.length - 1 @selectItem(@items.eq((index + 1) % @items.length)) else @items.last()

Structure discovery

Find the widget structure using the specified configuration

Panels

Find the panels using:

  • children with role="group"
  • panels option (selector relative to the element)
  • children of the first child if controls are positioned after
  • children of the second child otherwise
findPanels: () -> $controls = @controls $groups = $("[role='group']", @element).filter () -> $controls and not $controls.find($(this)).length return $groups if $groups.length if @options.panels $(@options.panels, @element) else if @options.controls is "before" @element.children().eq(1).children() else @element.children().eq(0).children()

Items

Find the items using:

  • children with role="option" (relative to the panels)
  • items option (selector relative to the element)
  • children of the panels
findItems: ($panel) -> $items = $("[role='option']", $panel or @panels) return $items if $items.length if @options.items $(@options.items, $panel or @element) else ($panel or @panels).children()

Key handling

Handle left key press

keyLeft: (e) -> $item = @selectPreviousItem() if e.target.getAttribute("role") isnt "button" $item.focus() $item

Handle up key press

keyUp: (e) -> @keyLeft(e)

Handle right key press

keyRight: (e) -> $item = @selectNextItem() if e.target.getAttribute("role") isnt "button" $item.focus() $item

Handle down key press

keyDown: (e) -> @keyRight(e)

Handle page up key press

keyPageUp: (e) -> e.preventDefault() return if @currentItem[0] is @items.get(0) index = @panels.index(@currentPanel) - 1 if index < 0 @selectItem(@items.first()).focus() else @selectItem(@findItems(@panels.eq(index)).first()).focus()

Handle page down key press

keyPageDown: (e) -> e.preventDefault() return if @currentItem[0] is @items.get(-1) index = @panels.index(@currentPanel) + 1 if index > @panels.length - 1 @selectItem(@items.last()).focus() else @selectItem(@findItems(@panels.eq(index)).first()).focus()

Handle home key press

keyHome: (e) -> e.preventDefault() return if @currentItem[0] is @items.get(0) @selectItem(@items.first()).focus()

Handle end key press

keyEnd: (e) -> e.preventDefault() return if @currentItem[0] is @items.get(-1) @selectItem(@items.last()).focus()

Cleanup

Cleanup the widget and remove remaining references

destructor: () -> super

Installation

Install the widget into the JS library

List.install("List", () -> $("[data-widget='list']")["#{_.namespace}list"]() )