5
<title>Example: Creating a Hierarchical ListBox Widget</title>
6
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Maven+Pro:400,700">
7
<link rel="stylesheet" href="../../build/cssgrids/grids-min.css">
8
<link rel="stylesheet" href="../assets/css/main.css">
9
<link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
10
<script src="../../build/yui/yui-min.js"></script>
15
<h1>Example: Creating a Hierarchical ListBox Widget</h1>
20
<div class="yui3-u-3-4">
22
<div class="content"><style type="text/css" scoped>
29
border:1px solid #aaa;
37
border: solid 1px #000;
38
background-color:#fff;
42
.yui3-listbox .yui3-listbox {
48
.yui3-listbox .yui3-option,
49
.yui3-listbox .yui3-listbox-option {
53
list-style-image:none;
54
list-style-position:outside;
64
.yui3-listbox-content {
70
.yui3-listbox .yui3-listbox .yui3-option-content {
78
.yui3-option-selected {
79
background-color: #cccccc;
82
.yui3-option-focused {
84
background-color: blue;
90
<p>This is an advanced example, in which we create a ListBox widget with nested Option widgets, by extending the base <code>Widget</code> class, and adding <code>WidgetParent</code> and <code>WidgetChild</code> extensions, through <code>Base.build</code>.</p>
91
<p>The <a href="../tabview">TabView</a> component that is included in the YUI 3 library, is also built using the WidgetParent and WidgetChild extensions.</p>
95
<p id="selected">Selected: <span id="selection">None</span></p>
97
<div id="exampleContainer"></div>
99
<script type="text/javascript">
103
fullpath: "../assets/widget/listbox.js",
104
requires: ["substitute", "widget", "widget-parent", "widget-child", "node-focusmanager"]
107
}).use("listbox", function (Y) {
109
var listbox = new Y.ListBox({
114
{ label: "Item One" },
115
{ label: "Item Two" }
123
{ label: "Item Three - One" },
124
{ label: "Item Three - Two" }
128
listbox.add({ label: "Item Four" });
131
new Y.Option({ label: "Item Five" })
138
{ label: "Item Six - One" },
139
{ label: "Item Six - Two" }
143
listbox.after("selectionChange", function(e) {
145
var selection = this.get("selection");
146
if (selection instanceof Y.ListBox) {
147
selection = selection.get("selection");
151
Y.one("#selection").setContent(selection.get("label"));
155
listbox.render("#exampleContainer");
160
<h3>The WidgetParent and WidgetChild Extensions</h3>
162
<p><a href="http://yuilibrary.com/yui/docs/api/WidgetParent.html">WidgetParent</a> is an extension, designed to be used with <code>Base.build</code> to create a class of Widget which is designed to contain child Widgets (for example a Tree widget, which contains TreeNode children).
163
WidgetParent itself augments <a href="http://yuilibrary.com/yui/docs/api/ArrayList.html">ArrayList</a> providing a convenient set of array iteration and convenience methods, allowing users of your class to easily work with parent's list of children.</p>
165
<p><a href="http://yuilibrary.com/yui/docs/api/WidgetChild.html">WidgetChild</a> is also an extension, designed to be used with <code>Base.build</code> to create a class of Widget which is designed to nested inside parent Widgets (for example a TreeNode widget, which sits inside a Tree widget).</p>
167
<p>A Widget can be built with both the WidgetParent and WidgetChild extensions (it can be both a Parent and a Child), in cases where we want to support multi-level hierarchies, such as the ListBox example below.</p>
169
<p>In addition to providing the basic support to manage (add/remove/iterate/render) children the Widget Parent/Child implementations also provides support for both single and multiple selection models.</p>
171
<h3>Using WidgetParent and WidgetChild to Create the ListBox Class</h3>
173
<p>For ListBox, since we're creating a new class from scratch, we use the sugar version of <code>Base.build</code>, called <code>Base.create</code>, which allows us to easily create a new class and define it's prototype and static properties/methods, in a single call, as shown below:</p>
175
<pre class="code prettyprint">// Create a new class, ListBox, which extends Widget, and mixes in both the WidgetParent and WidgetChild
176
// extensions since we want to be able to nest one ListBox inside another, to create heirarchical listboxes
178
Y.ListBox = Y.Base.create("listbox", Y.Widget, [Y.WidgetParent, Y.WidgetChild], {
179
// Prototype Properties for ListBox
181
// Static Properties for ListBox
185
<p>We can then go ahead and fill out the prototype and static properties we want to override in our ListBox implementation, while Widget, WidgetParent and WidgetChild provide the basic Widget rendering and parent-child relationship support. Comments inline below provide the background:</p>
187
<h4>Prototype Method and Properties</h4>
189
<pre class="code prettyprint">Y.ListBox = Y.Base.create("listbox", Y.Widget, [Y.WidgetParent, Y.WidgetChild], {
191
// The default content box for ListBoxes will be a UL (Widget uses a DIV by default)
192
CONTENT_TEMPLATE : "<ul></ul>",
194
// Setup Custom Listeners
199
// Setup custom focus handling, using the NodeFocusManager plugin
200
// This will help us easily crete next/previous item handling using the arrow keys
202
this.get("boundingBox").plug(Y.Plugin.NodeFocusManager, {
203
descendants: ".yui3-option",
205
next: "down:40", // Down arrow
206
previous: "down:38" // Up arrow
212
this.get("boundingBox").on("contextmenu", function (event) {
213
event.preventDefault();
216
// Setup listener to control keyboard based single/multiple item selection
217
this.on("option:keydown", function (event) {
219
var item = event.target,
220
domEvent = event.domEvent,
221
keyCode = domEvent.keyCode,
222
direction = (keyCode == 40);
224
if (this.get("multiple")) {
225
if (keyCode == 40 || keyCode == 38) {
226
if (domEvent.shiftKey) {
227
this._selectNextSibling(item, direction);
230
this._selectNextSibling(item, direction);
234
if (keyCode == 13 || keyCode == 32) {
235
domEvent.preventDefault();
236
item.set("selected", 1);
241
// Setup listener to control mouse based single/multiple item selection
242
this.on("option:mousedown", function (event) {
244
var item = event.target,
245
domEvent = event.domEvent,
248
if (this.get("multiple")) {
249
if (domEvent.metaKey) {
250
item.set("selected", 1);
253
item.set("selected", 1);
256
item.set("selected", 1);
262
// Helper Method, to find the correct next sibling, taking into account nested ListBoxes
263
_selectNextSibling : function(item, direction) {
265
var parent = item.get("parent"),
266
method = (direction) ? "next" : "previous",
268
// Only go circular for the root listbox
269
circular = (parent === this),
270
sibling = item[method](circular);
273
// If we found a sibling, it's either an Option or a ListBox
274
if (sibling instanceof Y.ListBox) {
275
// If it's a ListBox, select it's first child (in the direction we're headed)
276
sibling.selectChild((direction) ? 0 : sibling.size() - 1);
278
// If it's an Option, select it
279
sibling.set("selected", 1);
282
// If we didn't find a sibling, we're at the last leaf in a nested ListBox
283
parent[method](true).set("selected", 1);
287
// The markup template we use internally to render nested ListBox children
288
NESTED_TEMPLATE : '<li class="{nestedOptionClassName}"><em class="{labelClassName}">{label}</em></li>',
290
renderUI: function () {
292
// Handling Nested Child Element Rendering
293
if (this.get("depth") > -1) {
296
labelClassName : this.getClassName("label"),
297
nestedOptionClassName : this.getClassName("option"),
298
label : this.get("label")
300
liHtml = Y.substitute(this.NESTED_TEMPLATE, tokens),
301
li = Y.Node.create(liHtml),
303
boundingBox = this.get("boundingBox"),
304
parent = boundingBox.get("parentNode");
306
li.appendChild(boundingBox);
307
parent.appendChild(li);
311
} { /* static properties */ });</pre>
314
<h4>Static Properties</h4>
316
<p>The only static property we're interested in defining for the ListBox class is the <code>ATTRS</code> property. Comments inline below provide the background:</p>
318
<pre class="code prettyprint">{
319
// Define any new attributes, or override existing ones
322
// We need to define the default child class to use,
323
// when we need to create children from the configuration
324
// object passed to add or to the "children" attribute (which is provided by WidgetParent)
326
// In this case, when a configuration object (e.g. { label:"My Option" }),
327
// is passed into the add method,or as the value of the "children"
328
// attribute, we want to create instances of Y.Option
330
value: "Option"
333
// Setup Label Attribute
335
validator: Y.Lang.isString
341
<h3>Using WidgetChild to Create the Option (leaf) Class</h3>
343
<p>The Option class is pretty simple, and largely just needs the attribute and API provided by WidgetChild. We only need to over-ride the default templates and tabIndex handling:</p>
345
<pre class="code prettyprint">Y.Option = Y.Base.create("option", Y.Widget, [Y.WidgetChild], {
347
// Override the default DIVs used for rendering the bounding box and content box.
348
CONTENT_TEMPLATE : "<em></em>",
349
BOUNDING_TEMPLATE : "<li></li>",
351
// Handle rendering the label attribute
352
renderUI: function () {
353
this.get("contentBox").setContent(this.get("label"));
360
// Setup Label Attribute
362
validator: Y.Lang.isString
365
// Override the default tabIndex for an Option,
366
// since we want FocusManager to control keboard
367
// based focus
376
<h3>Adding The Code As A "listbox" Custom Module</h3>
378
<p>This example also shows how you can package code for re-use as a module, by registering it through the <code>YUI.add</code> method, specifying any requirements it has (the packaged code is available in ./assets/listbox.js).</p>
380
<pre class="code prettyprint">YUI.add('listbox', function(Y) {
386
}, '3.1.0' ,{requires:['substitute', 'widget', 'widget-parent', 'widget-child', 'node-focusmanager']});</pre>
389
<h3>Using the Custom "listbox" Module</h3>
391
<p>To create an instance of a ListBox, we ask for the "listbox" module we packaged in the previous step, through <code>YUI().use("listbox")</code>:</p>
393
<pre class="code prettyprint">YUI({
395
"listbox": {
396
fullpath: "listbox.js",
397
requires: ["substitute", "widget", "widget-parent", "widget-child", "node-focusmanager"]
400
}).use("listbox", function (Y) {
402
// Create the top level ListBox instance, and start it off with
403
// 2 children (the defaultChildType will be used to create instances of Y.Option with the
404
// children configuration passed in below).
406
var listbox = new Y.ListBox({
407
id:"mylistbox",
408
width:"13em",
409
height:"15em",
411
{ label: "Item One" },
412
{ label: "Item Two" }
420
<p>We can also use the <code>add</code> method provided by WidgetParent, to add children after contruction, and then render to the DOM:</p>
422
<pre class="code prettyprint">// Then we add a nested ListBox which itself has 2 children, using
423
// the add API provided by WidgetParent
426
type: "ListBox",
427
label: "Item Three",
429
{ label: "Item Three - One" },
430
{ label: "Item Three - Two" }
434
// One more Option child
436
listbox.add({ label: "Item Four" });
438
// One more Option child, using providing an actual
439
// instance, as opposed to just the configuration
442
new Y.Option({ label: "Item Five" })
445
// And finally, a last nested ListBox, again with
446
// 2 children
449
type: "ListBox",
450
label: "Item Six",
452
{ label: "Item Six - One" },
453
{ label: "Item Six - Two" }
457
// Render it, using Widget's render method,
458
// to the "#exampleContainer" element.
459
listbox.render("#exampleContainer");</pre>
462
<p>The ListBox fires selectionChange events, every time it's selection state changes (provided by WidgetParent), which we can listen and respond to:</p>
464
<pre class="code prettyprint">listbox.after("selectionChange", function(e) {
466
var selection = this.get("selection");
467
if (selection instanceof Y.ListBox) {
468
selection = selection.get("selection");
472
Y.one("#selection").setContent(selection.get("label"));
480
<pre class="code prettyprint">.yui3-listbox {
483
border: solid 1px #000;
484
background-color:#fff;
488
.yui3-listbox .yui3-listbox {
490
margin-bottom: .25em;
494
.yui3-listbox .yui3-option,
495
.yui3-listbox .yui3-listbox-option {
499
list-style-image:none;
500
list-style-position:outside;
501
list-style-type:none;
504
.yui3-option-content,
505
.yui3-listbox-label {
510
.yui3-listbox-content {
516
.yui3-listbox .yui3-listbox .yui3-option-content {
520
.yui3-listbox-label {
524
.yui3-option-selected {
525
background-color: #cccccc;
528
.yui3-option-focused {
530
background-color: blue;
535
<h2>Complete Example Source</h2>
536
<pre class="code prettyprint"><p id="selected">Selected: <span id="selection">None</span></p>
538
<div id="exampleContainer"></div>
540
<script type="text/javascript">
543
"listbox": {
544
fullpath: "../assets/widget/listbox.js",
545
requires: ["substitute", "widget", "widget-parent", "widget-child", "node-focusmanager"]
548
}).use("listbox", function (Y) {
550
var listbox = new Y.ListBox({
551
id:"mylistbox",
552
width:"13em",
553
height:"15em",
555
{ label: "Item One" },
556
{ label: "Item Two" }
561
type: "ListBox",
562
label: "Item Three",
564
{ label: "Item Three - One" },
565
{ label: "Item Three - Two" }
569
listbox.add({ label: "Item Four" });
572
new Y.Option({ label: "Item Five" })
576
type: "ListBox",
577
label: "Item Six",
579
{ label: "Item Six - One" },
580
{ label: "Item Six - Two" }
584
listbox.after("selectionChange", function(e) {
586
var selection = this.get("selection");
587
if (selection instanceof Y.ListBox) {
588
selection = selection.get("selection");
592
Y.one("#selection").setContent(selection.get("label"));
596
listbox.render("#exampleContainer");
598
</script></pre>
604
<div class="yui3-u-1-4">
605
<div class="sidebar">
609
<div class="sidebox">
611
<h2 class="no-toc">Examples</h2>
615
<ul class="examples">
618
<li data-description="Shows how to extend the base widget class, to create your own Widgets.">
619
<a href="widget-extend.html">Extending the Base Widget Class</a>
624
<li data-description="Shows how to use Base.create and mix/match extensions to create custom Widget classes.">
625
<a href="widget-build.html">Creating Custom Widget Classes With Extensions</a>
630
<li data-description="Shows how to create an IO plugin for Widget.">
631
<a href="widget-plugin.html">Creating a Widget Plugin</a>
636
<li data-description="Shows how to extend the Widget class, and add WidgetPosition and WidgetStack to create a Tooltip widget class.">
637
<a href="widget-tooltip.html">Creating a Simple Tooltip Widget With Extensions</a>
642
<li data-description="Shows how to extend the Widget class, and add WidgetParent and WidgetChild to create a simple ListBox widget.">
643
<a href="widget-parentchild-listbox.html">Creating a Hierarchical ListBox Widget</a>
658
<script src="../assets/vendor/prettify/prettify-min.js"></script>
659
<script>prettyPrint();</script>