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

Dialog

The Dialog class provides a simple dialog widget.

author: Julien Ramboz version: 1.0 usage: Dialog examples

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 ) dialog = ($, _, AbstractWidget) -> "use strict"

Widget

The actual widget class

class Dialog extends AbstractWidget

Default options for the widget

@defaultOptions:
  • overlay: whether or not the popup is an overlay
overlay: false

Initialisation

Initializer function.

initialize: (options) -> super options if @options.overlay is "true" or @options.overlay is true @element.attr("data-modal", "true") @element.wrapInner(@createDialog().attr("data-role", "popup")) @dialog = @element.find("[data-role='popup']") @element.remove().appendTo("body") @dialogHeader = @findHeader() @dialogContent = @findContent() @dialogFooter = @findFooter() @closeButton = @findClose().attr("data-role", "close") @closeButton.prependTo(@dialog) @endElement = @createEnd().appendTo(@element)

Accessibility markup

Add aria attributes

aria: () -> if not @element.attr("id") @element.attr "id", _.getGUID("dialog-") @element.attr "role", "dialog" @closeButton.attr "role": "button" "tabindex": 0 if @dialogHeader if not @dialogHeader.attr('id') @dialogHeader.attr 'id', _.getGUID('dialog-header-') @element.attr 'aria-labelledby', @dialogHeader.attr('id') if not @dialogContent.attr('id') @dialogContent.attr 'id', _.getGUID('dialog-content-') @element.attr 'aria-describedby', @dialogContent.attr('id')

Event handling

Attach evenets to the widget

bindEvents: () -> @handleKeys(@element) @element.on "click", (e) => if e.target is @closeButton.get(0) @close(e) else if @element.find(e.target).length @closeButton.focus() @element.on "afterToggle", (e, o) => if o.status and $(o.targets).index(@element) > -1 @centerOnPage() @closeButton.focus() @endElement.on "focus", (e) => @closeButton.focus() if @options.overlay

Close the dialog

close: (e) -> $toggles = $("[aria-controls*='#{@element.attr("id")}']") if $toggles.length $toggles.each () -> $toggle = $(this) _.getInstance(this, ["toggle","collapse"]).hide(e) $toggle.focus() else @element.remove() @destroy()

Center the dialog on the page

centerOnPage: () -> @dialogContent.css 'display': '' 'height': '' if @options.overlay @element.css "height", $(document).height() + "px" @dialog.css "top", Math.round(($(window).height() - @dialog.height())/ 2 + $(window).scrollTop()) + "px" else @element.css left: Math.round(($(window).width() - @dialog.outerWidth(true)) / 2) + "px" top: Math.round(($(window).height() - @dialog.height()) / 2 + $(window).scrollTop()) + "px" @dialogContent.css 'max-height', Math.round(@dialog.height() - @dialogHeader.outerHeight(true) - @dialogFooter.outerHeight(true)) maxHeight = parseInt(@dialogContent.css('max-height'), 10) if @dialogContent.height() >= maxHeight @dialogContent.css 'display': 'inline-block' 'height': maxHeight 'max-height': ''

Force redraw

@dialogContent.hide() @dialogContent.offset() @dialogContent.show()

Structure discovery

Find the widget structure using the specified configuration

Close button

Find the close button, specified using the data-role="close" attribute, or auto-generated

findClose: () -> $close = @element.find("[data-role='close']") return $close if $close.length @createClose().prependTo(@element)

Dialog header

Find the dialog header, specified using the aria-labelledby attribute or falling back to the header child or falling back to the 1st child when there are 2 or more

findHeader: () -> label = @element.attr('aria-labelledby') return $("##{label}}") if label $header = @dialog.children('header') return $header if $header.length if @dialog.children().length >= 2 return @dialog.children(':not([data-role="close"])').first() $()

Dialog content

Find the dialog content, specified using the aria-describedby attribute or falling back to the 2nd child when there are 2 or more or falling back to the 1st child when there is only 1

findContent: () -> description = @element.attr('aria-describedby') return $("##{description}}") if description if @dialog.children().length >= 2 return @dialog.children(':not([data-role="close"])').eq(1) @dialog.children(':not([data-role="close"])')

Find the dialog header, specified using the footer child or falling back to the last child when there are 3 or more

findFooter: () -> $footer = @dialog.children('footer') return $footer if $footer.length if @dialog.children().length >= 3 return @dialog.children(':not([data-role="close"])').last() $()

Structure creation

Create elements required by the structure if they are not present

Dialog element

createDialog: () -> $(document.createElement("div"))

Close button

createClose: () -> $(document.createElement("span"))

End element

createEnd: () -> $(document.createElement("span")).attr("tabindex", 0).text(" ").css height: "1px" width: "1px"

Key handling

Handle escape key press

keyEscape: (e) -> e.preventDefault() @close(e)

Handle enter key press

keyEnter: (e) -> @close(e) if e.target is @closeButton.get(0)

Handle tab key press

keyTab: (e) -> if @options.overlay and e.target is @closeButton.get(0) and e.shiftKey e.preventDefault()

Cleanup

Cleanup the widget and remove remaining references

destructor: () -> @element.removeAttr("role") super() @closeButton = null @overlay = null @endElement = null

Installation

Install the widget into the JS library

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