diff -r 000000000000 -r 40c8f766c9b8 src/cm/media/js/lib/yui/yui3.0.0/examples/node-focusmanager/node-focusmanager-2.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/media/js/lib/yui/yui3.0.0/examples/node-focusmanager/node-focusmanager-2.html Mon Nov 23 15:14:29 2009 +0100 @@ -0,0 +1,1216 @@ + + + + + YUI Library Examples: Focus Manager Node Plugin: Accessible TabView + + + + + + + + + + + + +
+
+
+

+ + YUI 3.x Home - + +

+
+ + +
+ + + +
+
+
+
+

YUI Library Examples: Focus Manager Node Plugin: Accessible TabView

+
+
+ + +
+
+
+
+ +

Focus Manager Node Plugin: Accessible TabView

+ +
+
+

+This example illustrates how to create an accessible tabview widget using the +Focus Manager Node Plugin, +Event's delegation support, and +Node's support for the +WAI-ARIA Roles and States. +

+ +
+
+ + + + +

Today's News

+
+ +
+ + + + + +
+
+ + + + + +
+
+
+ +

Setting Up the HTML

+

+The tabs in the tabview widget will be represented by a list of +<a> elements whose href attribute is set to +the id of an <div> element that contains its content. +Therefore, without JavaScript and CSS, the tabs function as in-page links. +

+
  1. <h3 id="tabview-heading">Today's News</h3>
  2. <div id="tabview-1">
  3. <ul>
  4. <li class="yui-tab yui-tab-selected"><a href="#top-stories"><em>Top Stories</em></a></li>
  5. <li class="yui-tab"><a href="#world-news"><em>World</em></a></li>
  6. <li class="yui-tab"><a href="#entertainment-news"><em>Entertainment</em></a></li>
  7. <li class="yui-tab"><a href="#sports-news"><em>Sports</em></a></li>
  8. <li class="yui-tab"><a href="#technology-news"><em>Technology</em></a></li>
  9. </ul>
  10. <div>
  11. <div class="yui-tabpanel yui-tabpanel-selected" id="top-stories">
  12. <!-- Tab Panel Content Here -->
  13. </div>
  14. <div class="yui-tabpanel" id="world-news">
  15. <!-- Tab Panel Content Here -->
  16. </div>
  17. <div class="yui-tabpanel" id="entertainment-news">
  18. <!-- Tab Panel Content Here -->
  19. </div>
  20. <div class="yui-tabpanel" id="sports-news">
  21. <!-- Tab Panel Content Here -->
  22. </div>
  23. <div class="yui-tabpanel" id="technology-news">
  24. <!-- Tab Panel Content Here -->
  25. </div>
  26. </div>
  27. </div>
<h3 id="tabview-heading">Today's News</h3>
+<div id="tabview-1">
+    <ul>
+        <li class="yui-tab yui-tab-selected"><a href="#top-stories"><em>Top Stories</em></a></li>
+        <li class="yui-tab"><a href="#world-news"><em>World</em></a></li>
+        <li class="yui-tab"><a href="#entertainment-news"><em>Entertainment</em></a></li>
+        <li class="yui-tab"><a href="#sports-news"><em>Sports</em></a></li>
+        <li class="yui-tab"><a href="#technology-news"><em>Technology</em></a></li>
+    </ul>
+    <div>
+        <div class="yui-tabpanel yui-tabpanel-selected" id="top-stories">
+            <!-- Tab Panel Content Here  -->
+        </div>
+        <div class="yui-tabpanel" id="world-news">
+            <!-- Tab Panel Content Here  -->
+        </div>
+        <div class="yui-tabpanel" id="entertainment-news">
+            <!-- Tab Panel Content Here  -->
+        </div>
+        <div class="yui-tabpanel" id="sports-news">
+            <!-- Tab Panel Content Here  -->
+        </div>
+        <div class="yui-tabpanel" id="technology-news">
+            <!-- Tab Panel Content Here  -->
+        </div>
+    </div>
+</div>

+For this example the content of each tab panel is created on the server using +the YQL API to fetch the title and +URL for news stories made available from the various +Yahoo! News RSS feeds. +Here's the PHP: +

+ +
  1. function getFeed($sFeed) {
  2.  
  3. $params = array(
  4. "q" => ('select title,link from rss where url="http://rss.news.yahoo.com/rss/$sFeed"'),
  5. "format" => "json"
  6. );
  7.  
  8. $encoded_params = array();
  9.  
  10. foreach ($params as $k => $v) {
  11. $encoded_params[] = urlencode($k)."=".urlencode($v);
  12. }
  13.  
  14. $url = "http://query.yahooapis.com/v1/public/yql?".implode("&", $encoded_params);
  15.  
  16. $ch = curl_init();
  17. curl_setopt($ch, CURLOPT_URL, $url);
  18. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  19. $rsp = curl_exec($ch);
  20. curl_close($ch);
  21.  
  22. if ($rsp !== false) {
  23.  
  24. $rsp_obj = json_decode($rsp, true);
  25.  
  26. $results = $rsp_obj["query"]["results"]["item"];
  27.  
  28. $list = ""; // HTML output
  29.  
  30. $nResults = count($results);
  31.  
  32. if ($nResults > 10) {
  33. $nResults = 9;
  34. }
  35.  
  36. for ($i = 0; $i<= $nResults; $i++) {
  37.  
  38. $result = $results[$i];
  39.  
  40. $list.= <<< END_OF_HTML
  41.   <li>
  42.   <a href="${result["link"]}"><q>${result["title"]}</q></a>
  43.   </li>
  44. END_OF_HTML;
  45.  
  46. }
  47.  
  48. return ("<ul>" . $list . "</ul>");
  49.  
  50. }
  51.  
  52. }
  53.  
function getFeed($sFeed) {
+ 
+    $params = array(
+        "q" => ('select title,link from rss where url="http://rss.news.yahoo.com/rss/$sFeed"'),
+        "format" => "json"
+    );
+ 
+    $encoded_params = array();
+ 
+    foreach ($params as $k => $v) {
+        $encoded_params[] = urlencode($k)."=".urlencode($v);
+    }
+ 
+    $url = "http://query.yahooapis.com/v1/public/yql?".implode("&", $encoded_params);
+ 
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_URL, $url);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+    $rsp = curl_exec($ch);
+    curl_close($ch);
+ 
+    if ($rsp !== false) {
+ 
+        $rsp_obj = json_decode($rsp, true);
+ 
+        $results = $rsp_obj["query"]["results"]["item"];
+ 
+        $list = ""; // HTML output
+ 
+        $nResults = count($results);
+ 
+        if ($nResults > 10) {
+            $nResults = 9;
+        }
+ 
+        for ($i = 0; $i<= $nResults; $i++) {
+ 
+            $result = $results[$i];
+ 
+            $list.= <<< END_OF_HTML
+            <li>
+                <a href="${result["link"]}"><q>${result["title"]}</q></a>
+            </li>
+END_OF_HTML;
+ 
+        }
+ 
+        return ("<ul>" . $list . "</ul>");
+ 
+    }
+ 
+}
+

Progressive Enhancement

+

+The markup above will be transformed using both CSS and JavaScript. To account +for the scenario where the user has CSS enabled in their browser but JavaScript +is disabled, the CSS used to style the tabview will be loaded via JavaScript +using the YUI instance's built-in Loader. +Additionally, a small block of JavaScript will be placed in the +<head> — used to temporarily hide the markup +while the JavaScript and CSS are in the process of loading to prevent the user +from seeing a flash unstyled content. +

+
  1. YUI({
  2.  
  3. base: "../../build/",
  4. modules: {
  5. "tabviewcss": {
  6. type: "css",
  7. fullpath: "assets/tabview.css"
  8. },
  9. "tabviewjs": {
  10. type: "js",
  11. fullpath: "assets/tabview.js",
  12. requires: ["node-focusmanager", "tabviewcss"]
  13. }
  14.  
  15. },
  16. timeout: 10000
  17.  
  18. }).use("tabviewjs", function(Y, result) {
  19.  
  20. // The callback supplied to use() will be executed regardless of
  21. // whether the operation was successful or not. The second parameter
  22. // is a result object that has the status of the operation. We can
  23. // use this to try to recover from failures or timeouts.
  24.  
  25. if (!result.success) {
  26.  
  27. Y.log("Load failure: " + result.msg, "warn", "Example");
  28.  
  29. // Show the tabview HTML if the loader failed that way the
  30. // original unskinned tabview will be visible so that the
  31. // user can interact with it either way.
  32.  
  33. document.documentElement.className = "";
  34.  
  35. }
  36.  
  37. });
YUI({
+ 
+    base: "../../build/",
+    modules: {
+        "tabviewcss": {
+            type: "css",
+            fullpath: "assets/tabview.css"
+        },
+        "tabviewjs": {
+            type: "js",
+            fullpath: "assets/tabview.js",
+            requires: ["node-focusmanager", "tabviewcss"]
+        }
+ 
+    },
+    timeout: 10000
+ 
+}).use("tabviewjs", function(Y, result) {
+ 
+    //  The callback supplied to use() will be executed regardless of
+    //  whether the operation was successful or not.  The second parameter
+    //  is a result object that has the status of the operation.  We can
+    //  use this to try to recover from failures or timeouts.
+ 
+    if (!result.success) {
+ 
+        Y.log("Load failure: " + result.msg, "warn", "Example");
+ 
+        //  Show the tabview HTML if the loader failed that way the
+        //  original unskinned tabview will be visible so that the
+        //  user can interact with it either way.
+ 
+        document.documentElement.className = "";
+ 
+    }
+ 
+});
+ +

ARIA Support

+

+Through the use of CSS and JavaScript the HTML for the tabview can be +transformed into something that looks and behaves like a desktop tab control, +but users of screen readers won't perceive it as an atomic widget, but rather +simply as a set of HTML elements. However, through the application +of the +WAI-ARIA Roles and States, it is +possible to improve the semantics of the markup such that users of screen +readers perceive it as a tab control. +

+ + +

Keyboard Functionality

+

+The keyboard functionality for the tabview widget will be provided by the +Focus Manager Node Plugin. The Focus Manager's +descendants +attribute is set to a value of ".yui-tab>a", so that only one tab in the tabview +is in the browser's default tab flow. This allows users navigating via the +keyboard to use the tab key to quickly move into and out of the tabview. Once +the tabview has focus, the user can move focus among each tab using the left +and right arrows keys, as defined by the value of the +keys +attribute. Lastly, the +focusClass +attribute is used to apply a class of yui-tab-focus to the parent +<li> of each <a> when it is focused, +making it easy to style the tab's focused state in each of the +A-Grade browsers. + +

  1. YUI().use("*", function (Y) {
  2.  
  3. var tabView = Y.one("#tabview-1"),
  4. tabList = tabView.one("ul"),
  5. tabHeading = Y.one("#tabview-heading"),
  6. sInstructionalText = tabHeading.get("innerHTML");
  7. selectedTabAnchor = tabView.one(".yui-tab-selected>a"),
  8. bGeckoIEWin = ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1),
  9. panelMap = {};
  10.  
  11.  
  12. tabView.addClass("yui-tabview");
  13.  
  14. // Remove the "yui-loading" class from the documentElement
  15. // now that the necessary YUI dependencies are loaded and the
  16. // tabview has been skinned.
  17.  
  18. tabView.get("ownerDocument").get("documentElement").removeClass("yui-loading");
  19.  
  20. // Apply the ARIA roles, states and properties.
  21.  
  22. // Add some instructional text to the heading that will be read by
  23. // the screen reader when the first tab in the tabview is focused.
  24.  
  25. tabHeading.set("innerHTML", (sInstructionalText + " <em>Press the enter key to load the content of each tab.</em>"));
  26.  
  27. tabList.setAttrs({
  28. "aria-labelledby": "tabview-heading",
  29. role: "tablist"
  30. });
  31.  
  32. tabView.one("div").set("role", "presentation");
  33.  
  34.  
  35. tabView.plug(Y.Plugin.NodeFocusManager, {
  36. descendants: ".yui-tab>a",
  37. keys: { next: "down:39", // Right arrow
  38. previous: "down:37" }, // Left arrow
  39. focusClass: {
  40. className: "yui-tab-focus",
  41. fn: function (node) {
  42. return node.get("parentNode");
  43. }
  44. },
  45. circular: true
  46. });
  47.  
  48.  
  49. // If the list of tabs loses focus, set the activeDescendant
  50. // attribute to the currently selected tab.
  51.  
  52. tabView.focusManager.after("focusedChange", function (event) {
  53.  
  54. if (!event.newVal) { // The list of tabs has lost focus
  55. this.set("activeDescendant", selectedTabAnchor);
  56. }
  57.  
  58. });
  59.  
  60.  
  61. tabView.all(".yui-tab>a").each(function (anchor) {
  62.  
  63. var sHref = anchor.getAttribute("href", 2),
  64. sPanelID = sHref.substring(1, sHref.length),
  65. panel;
  66.  
  67. // Apply the ARIA roles, states and properties to each tab
  68.  
  69. anchor.set("role", "tab");
  70. anchor.get("parentNode").set("role", "presentation");
  71.  
  72.  
  73. // Remove the "href" attribute from the anchor element to
  74. // prevent JAWS and NVDA from reading the value of the "href"
  75. // attribute when the anchor is focused
  76.  
  77. if (bGeckoIEWin) {
  78. anchor.removeAttribute("href");
  79. }
  80.  
  81. // Cache a reference to id of the tab's corresponding panel
  82. // element so that it can be made visible when the tab
  83. // is clicked.
  84. panelMap[anchor.get("id")] = sPanelID;
  85.  
  86.  
  87. // Apply the ARIA roles, states and properties to each panel
  88.  
  89. panel = Y.one(("#" + sPanelID));
  90.  
  91. panel.setAttrs({
  92. role: "tabpanel",
  93. "aria-labelledby": anchor.get("id")
  94. });
  95.  
  96. });
  97.  
  98.  
  99. // Use the "delegate" custom event to listen for the "click" event
  100. // of each tab's <A> element.
  101.  
  102. tabView.delegate("click", function (event) {
  103.  
  104. var selectedPanel,
  105. sID = this.get("id");
  106.  
  107. // Deselect the currently selected tab and hide its
  108. // corresponding panel.
  109.  
  110. if (selectedTabAnchor) {
  111. selectedTabAnchor.get("parentNode").removeClass("yui-tab-selected");
  112. Y.one(("#" + panelMap[selectedTabAnchor.get("id")])).removeClass("yui-tabpanel-selected");
  113. }
  114.  
  115. selectedTabAnchor = this;
  116. selectedTabAnchor.get("parentNode").addClass("yui-tab-selected");
  117.  
  118. selectedPanel = Y.one(("#" + panelMap[sID]));
  119. selectedPanel.addClass("yui-tabpanel-selected");
  120.  
  121. creatingPaging(selectedPanel);
  122.  
  123. // Prevent the browser from following the URL specified by the
  124. // anchor's "href" attribute when clicked.
  125.  
  126. event.preventDefault();
  127.  
  128. }, ".yui-tab>a");
  129.  
  130.  
  131. // Since the anchor's "href" attribute has been removed, the
  132. // element will not fire the click event in Firefox when the
  133. // user presses the enter key. To fix this, dispatch the
  134. // "click" event to the anchor when the user presses the
  135. // enter key.
  136.  
  137. if (bGeckoIEWin) {
  138.  
  139. tabView.delegate("keydown", function (event) {
  140.  
  141. if (event.charCode === 13) {
  142. this.simulate("click");
  143. }
  144.  
  145. }, ">ul>li>a");
  146.  
  147. }
  148.  
  149. });
YUI().use("*", function (Y) {
+ 
+    var tabView = Y.one("#tabview-1"),
+        tabList = tabView.one("ul"),
+        tabHeading = Y.one("#tabview-heading"),
+        sInstructionalText = tabHeading.get("innerHTML");
+        selectedTabAnchor = tabView.one(".yui-tab-selected>a"),
+        bGeckoIEWin = ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1),
+        panelMap = {};
+ 
+ 
+    tabView.addClass("yui-tabview");
+ 
+    //  Remove the "yui-loading" class from the documentElement
+    //  now that the necessary YUI dependencies are loaded and the
+    //  tabview has been skinned.
+ 
+    tabView.get("ownerDocument").get("documentElement").removeClass("yui-loading");
+ 
+    //  Apply the ARIA roles, states and properties.
+ 
+    //  Add some instructional text to the heading that will be read by
+    //  the screen reader when the first tab in the tabview is focused.
+ 
+    tabHeading.set("innerHTML", (sInstructionalText + " <em>Press the enter key to load the content of each tab.</em>"));
+ 
+    tabList.setAttrs({
+        "aria-labelledby": "tabview-heading",
+        role: "tablist"
+    });
+ 
+    tabView.one("div").set("role", "presentation");
+ 
+ 
+    tabView.plug(Y.Plugin.NodeFocusManager, {
+            descendants: ".yui-tab>a",
+            keys: { next: "down:39", // Right arrow
+                    previous: "down:37" },  // Left arrow
+            focusClass: {
+                className: "yui-tab-focus",
+                fn: function (node) {
+                    return node.get("parentNode");
+                }
+            },
+            circular: true
+        });
+ 
+ 
+    //  If the list of tabs loses focus, set the activeDescendant
+    //  attribute to the currently selected tab.
+ 
+    tabView.focusManager.after("focusedChange", function (event) {
+ 
+        if (!event.newVal) {    //  The list of tabs has lost focus
+            this.set("activeDescendant", selectedTabAnchor);
+        }
+ 
+    });
+ 
+ 
+    tabView.all(".yui-tab>a").each(function (anchor) {
+ 
+        var sHref = anchor.getAttribute("href", 2),
+            sPanelID = sHref.substring(1, sHref.length),
+            panel;
+ 
+        //  Apply the ARIA roles, states and properties to each tab
+ 
+        anchor.set("role", "tab");
+        anchor.get("parentNode").set("role", "presentation");
+ 
+ 
+        //  Remove the "href" attribute from the anchor element to
+        //  prevent JAWS and NVDA from reading the value of the "href"
+        //  attribute when the anchor is focused
+ 
+        if (bGeckoIEWin) {
+            anchor.removeAttribute("href");
+        }
+ 
+        //  Cache a reference to id of the tab's corresponding panel
+        //  element so that it can be made visible when the tab
+        //  is clicked.
+        panelMap[anchor.get("id")] = sPanelID;
+ 
+ 
+        //  Apply the ARIA roles, states and properties to each panel
+ 
+        panel = Y.one(("#" + sPanelID));
+ 
+        panel.setAttrs({
+            role: "tabpanel",
+            "aria-labelledby": anchor.get("id")
+        });
+ 
+    });
+ 
+ 
+    //  Use the "delegate" custom event to listen for the "click" event
+    //  of each tab's <A> element.
+ 
+    tabView.delegate("click", function (event) {
+ 
+        var selectedPanel,
+            sID = this.get("id");
+ 
+        //  Deselect the currently selected tab and hide its
+        //  corresponding panel.
+ 
+        if (selectedTabAnchor) {
+            selectedTabAnchor.get("parentNode").removeClass("yui-tab-selected");
+            Y.one(("#" + panelMap[selectedTabAnchor.get("id")])).removeClass("yui-tabpanel-selected");
+        }
+ 
+        selectedTabAnchor = this;
+        selectedTabAnchor.get("parentNode").addClass("yui-tab-selected");
+ 
+        selectedPanel = Y.one(("#" + panelMap[sID]));
+        selectedPanel.addClass("yui-tabpanel-selected");
+ 
+        creatingPaging(selectedPanel);
+ 
+        //  Prevent the browser from following the URL specified by the
+        //  anchor's "href" attribute when clicked.
+ 
+        event.preventDefault();
+ 
+    }, ".yui-tab>a");
+ 
+ 
+    //  Since the anchor's "href" attribute has been removed, the
+    //  element will not fire the click event in Firefox when the
+    //  user presses the enter key.  To fix this, dispatch the
+    //  "click" event to the anchor when the user presses the
+    //  enter key.
+ 
+    if (bGeckoIEWin) {
+ 
+        tabView.delegate("keydown", function (event) {
+ 
+            if (event.charCode === 13) {
+                this.simulate("click");
+            }
+ 
+        }, ">ul>li>a");
+ 
+    }
+ 
+});
+

Accessibility Sugar

+

+One of the challenges faced by users of screen readers is knowing when you've +left the context of a given control. In the case of this tabview, if it +were adjacent to another ARIA-enabled widget, the user would know they've +left the tabview when the screen reader announces the role of the adjacent +widget. However, if the tabview is sitting alongside standard HTML content, it +would be really difficult for the user to know when they've left the context of +the active panel. +

+

+One solution to this problem is to add some additional navigation as the last +child of each tab panel that allows the user to move to the previous and next +panel in the tabview. This will not only help alert users of screen readers +that they've reached the end of the tab's panel, but allow all +keyboard users to move more quickly to the next/previous panel. Without this +additionally navigation, keyboard users would typically have to press +shift+tab to navigate back up to the list of tabs to move to the next/previous +tab. +

+
  1. var creatingPaging = function (panel) {
  2.  
  3. var listitem,
  4. sHTML,
  5. paging;
  6.  
  7. if (!panel.one(".paging")) {
  8.  
  9. listitem = selectedTabAnchor.get("parentNode");
  10. sHTML = "";
  11.  
  12. if (listitem.previous()) {
  13. sHTML += '<button type="button" class="yui-tabview-prevbtn">Previous Tab Panel</button>';
  14. }
  15.  
  16. if (listitem.next()) {
  17. sHTML += '<button type="button" class="yui-tabview-nextbtn">Next Tab Panel</button>';
  18. }
  19.  
  20. paging = Y.Node.create('<div class="paging">' + sHTML + '</div>');
  21.  
  22. panel.appendChild(paging);
  23.  
  24. }
  25.  
  26. };
  27.  
  28. creatingPaging(Y.one(".yui-tabpanel-selected"));
  29.  
  30.  
  31. tabView.delegate("click", function (event) {
  32.  
  33. var node = selectedTabAnchor.get("parentNode").previous().one("a");
  34.  
  35. tabView.focusManager.focus(node);
  36. node.simulate("click");
  37.  
  38. }, ".yui-tabview-prevbtn");
  39.  
  40.  
  41. tabView.delegate("click", function (event) {
  42.  
  43. var node = selectedTabAnchor.get("parentNode").next().one("a");
  44.  
  45. tabView.focusManager.focus(node);
  46. node.simulate("click");
  47.  
  48. }, ".yui-tabview-nextbtn");
var creatingPaging = function (panel) {
+ 
+    var listitem,
+        sHTML,
+        paging;
+ 
+    if (!panel.one(".paging")) {
+ 
+        listitem = selectedTabAnchor.get("parentNode");
+        sHTML = "";
+ 
+        if (listitem.previous()) {
+            sHTML += '<button type="button" class="yui-tabview-prevbtn">Previous Tab Panel</button>';
+        }
+ 
+        if (listitem.next()) {
+            sHTML += '<button type="button" class="yui-tabview-nextbtn">Next Tab Panel</button>';
+        }
+ 
+        paging = Y.Node.create('<div class="paging">' + sHTML + '</div>');
+ 
+        panel.appendChild(paging);
+ 
+    }
+ 
+};
+ 
+creatingPaging(Y.one(".yui-tabpanel-selected"));
+ 
+ 
+tabView.delegate("click", function (event) {
+ 
+    var node = selectedTabAnchor.get("parentNode").previous().one("a");
+ 
+    tabView.focusManager.focus(node);
+    node.simulate("click");
+ 
+}, ".yui-tabview-prevbtn");
+ 
+ 
+tabView.delegate("click", function (event) {
+ 
+    var node = selectedTabAnchor.get("parentNode").next().one("a");
+ 
+    tabView.focusManager.focus(node);
+    node.simulate("click");
+ 
+}, ".yui-tabview-nextbtn");
+ +
+ +
+
+ + + +
+ +
+

Copyright © 2009 Yahoo! Inc. All rights reserved.

+

Privacy Policy - + Terms of Service - + Copyright Policy - + Job Openings

+
+
+ + + + + +