Source: accessibility/AccessibilityMenu.js

/**
 * Copyright (C) 2005-2016 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * This module creates, when configured, an accessibility menu which provides a selection of hotkey links such as 'skip to content'.
 * 
 * @module alfresco/accessibility/AccessibilityMenu
 * @extends external:dijit/_WidgetBase
 * @mixes external:dojo/_TemplatedMixin
 * @mixes module:alfresco/core/Core
 * @example <caption>Sample configuration</caption>
 * {
 *    name: "alfresco/accessibility/AccessibilityMenu",
 *    config: {
 *       id: "AccessibilityMenu",
 *       titleMsgKey: "access.key.links.message",
 *       menu: [
 *          {url: "#accesskey-skip", key: "s", msgKey: "skip.to.content.message"},
 *          {url: "/share/page/accessibility-help", key: "0", msgKey: "access.keys.message"},
 *          {url: "/share/page/user/admin/dashboard", key: "1", msgKey: "home.page.message"},
 *          {url: "/share/page/advsearch", key: "4", msgKey: "search.this.site.message"},
 *          {url: "/share/page/site-help", key: "6", msgKey: "accessibility.help.message"},
 *          {url: "/share/page/terms", key: "8", msgKey: "terms.and.conditions.message"},
 *          {url: "/share", key: "9", msg: "This is just a test message"},
 *          {url: "#accesskey-foot", key: "b", msgKey: "skip.to.foot.message"}
 *       ],
 *       targets: [
 *          {domid: "AccessibilityMenu", targetid: "accesskey-skip", after: false},
 *          {domid: "AccessibilityMenu", targetid: "accesskey-foot", after: true}
 *       ]
 *    }
 * }
 * @author Richard Smith
 */
 define(["dojo/_base/declare",
         "dijit/_WidgetBase", 
         "dijit/_TemplatedMixin",
         "alfresco/core/Core",
         "alfresco/core/topics",
         "dojo/text!./templates/AccessibilityMenu.html",
         "dojo/_base/lang",
         "dojo/_base/array",
         "dojo/dom",
         "dojo/dom-construct",
         "dojo/on",
         "dijit/registry"], 
         function(declare, _WidgetBase, _TemplatedMixin, AlfCore, topics, template, lang, array, dom, domConstruct, on, registry) {
   
   return declare([_WidgetBase, _TemplatedMixin, AlfCore], {

      /**
       * An array of the i18n files to use with this widget.
       * 
       * @instance
       * @type {object[]}
       * @default [{i18nFile: "./i18n/AccessibilityMenu.properties"}]
       */
      i18nRequirements: [{i18nFile: "./i18n/AccessibilityMenu.properties"}],

      /**
       * An array of the CSS files to use with this widget.
       * 
       * @instance
       * @type {object[]}
       * @default [{cssFile:"./css/AccessibilityMenu.css"}]
       */
      cssRequirements: [{cssFile:"./css/AccessibilityMenu.css"}],

      /**
       * The HTML template to use for the widget.
       * @instance
       * @type {String}
       */
      templateString: template,

      /**
       * The message key to display as title if no titleMsg provided
       * @instance
       * @type {String}
       */
      titleMsgKey: "access.key.links.message",

      /**
       * The message to display as title
       * @instance
       * @type {String}
       */
      titleMsg: null,
      
      /**
       * The urls, access keys and message identifiers used to render the accessibility links
       * @instance
       * @type {object[]}
       */
      menu: [],

      /**
       * The domid, targetid and positioning used to render the accessibility link targets
       * @instance
       * @type {object[]}
       */
      targets: [],

      /**
       * A storage array for the menu links we generate
       * @private
       * @type {object[]}
       */
      _createdLinks: [],

      /**
       * @instance
       */
      postCreate: function alfresco_accessibility_AccessibilityMenu__postCreate() {
         // Inject access key header
         domConstruct.create("p", {
            className: "alfresco-accessibility-AccessibilityMenu__header",
            innerHTML: this.titleMsg ? this.titleMsg : this.message(this.titleMsgKey)
         }, this.accessKeys, "first");

         // Create unordered list for access key items - injected at the end of the accessKeys object
         var ul = domConstruct.create("ul", {
            className: "alfresco-accessibility-AccessibilityMenu__access-key-list"
         }, this.accessKeys);

         // Then iterate menu items creating li and a tags accordingly
         array.forEach(this.menu, function(item) {
            this.writeMenuItem(ul, item);
         }, this);
         
         // Subscribe generateTargets and addEvents to ALF_WIDGETS_READY
         this.alfSubscribe(topics.PAGE_WIDGETS_READY, lang.hitch(this, this.generateTargets));
         this.alfSubscribe(topics.PAGE_WIDGETS_READY, lang.hitch(this, this.addEvents));
      },

      /**
       * This function writes a list item for the accessibility menu.
       * @instance
       * @param {object} list The list into which menu items are added
       * @param {object} item The object containing details for the menu item
       */
      writeMenuItem: function alfresco_accessibility_AccessibilityMenu__writeMenuItem(list, item) {
         // If the menu item has at least a msg or msgKey
         if(item.msg || item.msgKey){
         
            // If the list container exists
            if(list)
            {
               // Create the list item
               var li = domConstruct.create("li", {
                  className: "alfresco-accessibility-AccessibilityMenu__access-key-item"
               }, list, "last");
   
               // Insert an a tag into the list item
               this._createdLinks.push(
                  domConstruct.create("a", {
                     className: "alfresco-accessibility-AccessibilityMenu__access-key-item__link",
                     href: item.url,
                     accessKey: item.key,
                     innerHTML: item.msg ? item.msg : this.message(item.msgKey)
                  }, li)
               );

               this.alfLog("log", "Created accessibility menu item '" + item.url + "'", this);
            }
            else
            {
               this.alfLog("error", "List specified as container for 'writeMenuItem' does not exist", this);
            }
         }
         else
         {
            this.alfLog("error", "An accessibility menu item was provided with neither a 'msgKey' nor a 'msg'", this);
         }
      },

      /**
       * This function generates HTML anchors from this.targets. It is called via a subscription to the ALF_WIDGETS_READY 
       * channel to make sure that the destination DOM elements have already been created.
       * @instance
       * @listens ALF_WIDGETS_READY
       */
      generateTargets: function alfresco_accessibility_AccessibilityMenu__generateTargets() {
         // Iterate target items creating anchors accordingly
         array.forEach(this.targets, function(item) {
            if(dom.byId(item.domid))
            {
               var pos = item.after ? "after" : "before";
               domConstruct.create("a", {
                  className: "alfresco-accessibility-AccessibilityMenu__access-key-item__link",
                  id: item.targetid,
                  innerHTML: " "
               }, item.domid, pos);
               
               this.alfLog("log", "Created accessibility anchor '" + item.targetid + "'", this);
            }
            else
            {
               this.alfLog("error", "Dom element '" + item.domid + "', specified as location for accessibility anchor, does not exist", this);
            }
         }, this);
      },

      /**
       * This function iterates over the generated menu links. If they contain a '#' value and the target item (by id) can 
       * be found then the link is given an onClick that sets and then unsets the tabindex of the target to take it out of 
       * regular page flow, and then focuses it.
       * 
       * @instance
       * @listens ALF_WIDGETS_READY
       */
      addEvents: function alfresco_accessibility_AccessibilityMenu__addEvents() {
         // Iterate over the _createdLinks and attach click events to them
         for(var i=0; i < this._createdLinks.length; i++)
         {
            var item = this._createdLinks[i];
            if(item.href.indexOf("#") !== -1)
            {
               var target = dom.byId(item.href.split("#")[1]);
               if(target)
               {
                  on(item, "click", lang.hitch(this, this.onLinkClick, item, target));
               }
            }
         }
      },

      /**
       * This function iterates over the generated menu links. If they contain a '#' value and the target item (by id) can 
       * be found then the link is given an onClick that sets and then unsets the tabindex of the target to take it out of 
       * regular page flow, and then focuses it.
       * 
       * @instance
       * @param {object} item The clicked item
       * @param {object} target The target configured for the clicked item
       * @param {event} evt The click event
       */
      onLinkClick: function alfresco_accessibility_AccessibilityMenu__onLinkClick(item, target, evt) {
         var widget = registry.getEnclosingWidget(target);
         if (widget && typeof widget.focus === "function")
         {
            widget.focus();
         }
         else
         {
            target.focus();
         }
         evt.stopPropagation();
         evt.preventDefault();
      }
   });
});