Cross Reference: /yui3/src/widget/docs/widget-extend.mustache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<style type="text/css" scoped>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-js-enabled .yui3-spinner-loading {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:none;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-hidden {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:none;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:-moz-inline-stack;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:inline-block;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai zoom:1;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *display:inline;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai vertical-align:middle;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-content {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai position:relative;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-value {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai width:2em;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai height:1.5em;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai text-align:right;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai margin-right:22px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai vertical-align:top;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai border:1px solid #000;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:2px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-increment, .yui3-spinner-decrement {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai position:absolute;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai height:1em;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai width:22px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai overflow:hidden;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai text-indent:-10em;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai border:1px solid #999;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai margin:0;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:0px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-increment {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai top:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *top:2px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai right:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai background:#ddd url({{componentAssets}}/arrows.png) no-repeat 50% 0px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-spinner-decrement {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai bottom:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *bottom:2px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai right:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai background:#ddd url({{componentAssets}}/arrows.png) no-repeat 50% -20px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai #widget-extend-example {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:5px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai #widget-extend-example .hint {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai margin-top:10px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai font-size:85%;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai color:#00a;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</style>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<div class="intro">
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <p>This example shows how to extend the base `Widget` class to create a simple, re-usable spinner control. The `Spinner` class created in the example is not intended to be a fully featured spinner. It is used here as a concrete example, to convey some of the key concepts to keep in mind when extending the `Widget` class.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</div>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<div class="example">
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai {{>widget-extend-source}}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</div>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h2>Extending The `Widget` Class</h2>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Basic Class Structure</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Widgets classes follow the general pattern implemented by the `Spinner` class, shown in the code snippet below. The basic pattern for setting up a new widget class involves:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<ol>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>Defining the constructor function for the new widget class, which invokes the superclass constructor to kick off the initialization chain <em>(line 2)</em></li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>Defining the static `NAME` property for the class, which is normally the class name in camel case, and is used to prefix events and CSS classes fired/created by the class <em>(line 11)</em></li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>Defining the static `ATTRS` property for the class, which defines the set of attributes which the class will introduce, in addition to the superclass attributes <em>(line 18-57)</em></li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>Extending the `Widget` class, and adding/overriding any prototype properties/methods <em>(line 61)</em></li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</ol>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Spinner class constructor */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaifunction Spinner(config) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Spinner.superclass.constructor.apply(this, arguments);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Required NAME static field, to identify the Widget class and
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * used as an event prefix, to generate class names etc. (set to the
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * class name in camel case).
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiSpinner.NAME = "spinner";
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The attribute configuration for the Spinner widget. Attributes can be
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * defined with default values, get/set functions and validator functions
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * as with any other class extending Base.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiSpinner.ATTRS = {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // The minimum value for the spinner.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai min : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value:0
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai },
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // The maximum value for the spinner.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai max : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value:100
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai },
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // The current value of the spinner.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value:0,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai validator: function(val) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return this._validateValue(val);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai },
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Amount to increment/decrement the spinner when the buttons,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // or arrow up/down keys are pressed.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai minorStep : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value:1
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai },
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Amount to increment/decrement the spinner when the page up/down keys are pressed.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai majorStep : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value:10
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai },
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // The localizable strings for the spinner. This attribute is
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // defined by the base Widget class but has an empty value. The
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // spinner is simply providing a default value for the attribute.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai strings: {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value: {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai tooltip: "Press the arrow up/down keys for minor increments, \
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai page up/down for major increments.",
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai increment: "Increment",
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai decrement: "Decrement"
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai};
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiY.extend(Spinner, Widget, {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Methods/properties to add to the prototype of the new class
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai});
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Note that these steps are the same for any class which is derived from <a href="../base/index.html">`Base`</a>, nothing Widget-specific is involved yet.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiWidget adds the concept of a rendered UI to the existing Base lifecycle (viz. init, destroy and attribute state configuration), which we'll see show up in Widget-specific areas below.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai<h4>A Note On Externalizing Strings and Internationalization</h4>
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai<p>For the scope of this example we won't get into packaging your custom widget code as a YUI module, but when you do, you can leverage the <a href="../intl/index.html">Internationalization</a> infrastructure
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desaito bundle the strings for your widget separately from the code, and avoid hard-coding them into the widget's code base as we're doing above.</p>
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai<p>When packaged separately from the code, the `strings` attribute can be changed to be:</p>
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai```
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen DesaiSpinner.ATTRS = {
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai ...
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai strings : {
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai valueFn : function() {
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai return Y.Intl.get("myspinner"); // Assuming "myspinner" is the name of your widget's module.
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai }
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai }
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai ...
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai}
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai```
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai<p>Loader will deliver the language specific bundle for your widget along with the code when someone uses your `myspinner` module. The language specific strings can be retrieved through the `Y.Intl.get(modulename)` call.</p>
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai<p>The <a href="../intl/intl-basic.html">Language Resource Bundles</a> example goes into more detail about the structure of the langauge bundles, how they are built and how to configure your YUI instance to deliver them. <a href="https://github.com/yui/yui3/tree/master/build/calendar-base">Calendar's source code</a> is also a good example of how this infrastructure is used.</p>
31c505368ffbf78b2d9a8b7dba5d792023f3d7e2Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>The HTML_PARSER Property</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiThe first Widget-specific property `Spinner` implements is the static <a href="{{apiDocs}}/Widget.html#property_Widget.HTML_PARSER">`HTML_PARSER`</a> property. It is used to set the initial widget configuration based on markup, providing basic progressive enhancement support.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiThe value of the `HTML_PARSER` property is an object literal, where each property is a widget attribute name, and the value is either a selector string (if the attribute is a node reference) or a function which is executed to provide
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaia value for the attribute from the markup on the page. Markup is essentially thought of as an additional data source for the user to set initial attribute values, outside of the configuration object passed to the constructor
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<em>(values passed to the constructor will take precedence over values picked up from markup)</em>.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>For `Spinner`, `HTML_PARSER` defines a function for the `value` attribute, which sets the initial value of the spinner based on an input field if present in the markup.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The HTML_PARSER static constant is used by the Widget base class to populate
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the configuration for the spinner instance from markup already on the page.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The Spinner class attempts to set the value of the spinner widget if it
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * finds the appropriate input element on the page.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiSpinner.HTML_PARSER = {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value: function (contentBox) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var node = contentBox.one("." + Spinner.INPUT_CLASS);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return (node) ? parseInt(node.get("value")) : null;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai};
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Lifecycle Methods: initializer, destructor</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `initializer` and `destructor` lifecycle methods are carried over from `Base`, and can be used to set up initial state during construction, and clean up state during destruction respectively.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>For `Spinner`, there is nothing special we need to do in the `initializer` (attribute setup is already taken care of), but it's left in the example to round out the lifecycle discussion.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `destructor` takes care of detaching any event listeners `Spinner` adds outside of the bounding box (event listeners on/inside the bounding box are purged by `Widget`'s `destructor`).</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * initializer is part of the lifecycle introduced by
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the Widget class. It is invoked during construction,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * and can be used to set up instance specific state.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The Spinner class does not need to perform anything
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * specific in this method, but it is left in as an example.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaiinitializer: function(config) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Not doing anything special during initialization
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * destructor is part of the lifecycle introduced by
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the Widget class. It is invoked during destruction,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * and can be used to clean up instance specific state.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The spinner cleans up any node references it's holding
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * onto. The Widget classes destructor will purge the
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * widget's bounding box of event listeners, so spinner
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * only needs to clean up listeners it attaches outside of
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the bounding box.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaidestructor : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._documentMouseUpHandle.detach();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.inputNode = null;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.incrementNode = null;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.decrementNode = null;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Rendering Lifecycle Methods: renderer, renderUI, bindUI, syncUI</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Widget adds a `render` method to the `init` and `destroy` lifecycle methods provided by Base. The `init` and `destroy` methods invoke the corresponding `initializer` and `destructor` implementations for the widget. Similarly, the `render` method invokes the `renderer` implementation for the widget. Note that the `renderer` method is not chained automatically, unlike the `initializer` and `destructor` methods.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `Widget` class already provides a default `renderer` implementation, which invokes the following abstract methods in the order shown <em>(with their respective responsibilities)</em>:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<ol>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>`renderUI()` : responsible for creating/adding elements to the DOM to render the widget.</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>`bindUI()` : responsible for binding event listeners (both attribute change and DOM event listeners) to 'activate' the rendered UI.</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>`syncUI()` : responsible for updating the rendered UI based on the current state of the widget.</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</ol>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Since the `Spinner` class has no need to modify the `Widget` `renderer` implementation, it simply implements the above 3 methods to handle the render phase:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * For spinner the method adds the input (if it's not already
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * present in the markup), and creates the increment/decrement buttons
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesairenderUI : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._renderInput();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._renderButtons();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * For spinner, the method:
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * - Sets up the attribute change listener for the "value" attribute
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * - Binds key listeners for the arrow/page keys
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * - Binds mouseup/down listeners on the boundingBox, document respectively.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * - Binds a simple change listener on the input box.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaibindUI : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.after("valueChange", this._afterValueChange);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var boundingBox = this.get("boundingBox");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Looking for a key event which will fire continuously across browsers
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // while the key is held down. 38, 40 = arrow up/down, 33, 34 = page up/down
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var keyEventSpec = (!Y.UA.opera) ? "down:" : "press:";
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai keyEventSpec += "38, 40, 33, 34";
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Y.on("change", Y.bind(this._onInputChange, this), this.inputNode);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Y.on("key", Y.bind(this._onDirectionKey, this), boundingBox, keyEventSpec);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Y.on("mousedown", Y.bind(this._onMouseDown, this), boundingBox);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._documentMouseUpHandle = Y.on("mouseup", Y.bind(this._onDocMouseUp, this),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai boundingBox.get("ownerDocument"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * For spinner, the method sets the value of the input field,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * to match the current state of the value attribute.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaisyncUI : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._uiSetValue(this.get("value"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h4>A Note On Key Event Listeners</h4>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `Spinner` uses Event's `"key"` support, to set up a listener for arrow up/down and page up/down keys on the spinner's bounding box (line 31).</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Event's `"key"` support allows `Spinner` to define a single listener, which is only invoked for the key specification provided. The key specification in the above use case is `"down:38, 40, 33, 34"` for most browsers, indicating that
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaithe `_onDirectionKey` method should only be called if the bounding box receives a keydown event with a character code which is either 38, 40, 33 or 34. `"key"` specifications can also contain more <a href="{{apiDocs}}/YUI.html#event_key">advanced filter criteria</a>, involving modifiers such as CTRL and SHIFT.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>For the Spinner widget, we're looking for a key event which fires repeatedly while the key is held down. This differs for Opera, so we need to fork for the key event we're interested in. Future versions of `"key"` support will aim to provide this type of higher level cross-browser abstraction also.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Attribute Supporting Methods</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Since all widgets are attribute-driven, they all follow a pretty similar pattern when it comes to how those attributes are used. For a given attribute, widgets will generally have:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<ul>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>A prototype method to listen for changes in the attribute</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>A prototype method to update the state of the rendered UI, to reflect the value of an attribute.</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <li>A prototype method used to set/get/validate the attribute.</li>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</ul>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>These methods are kept on the prototype to facilitate customization at any of the levels - event handling, ui updates, set/get/validation logic.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>For `Spinner`, these corresponding methods for the `value` attribute are: `_afterValueChange`, `_uiSetValue` and `_validateValue`:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * value attribute change listener. Updates the
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * value in the rendered input box, whenever the
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * attribute value changes.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_afterValueChange : function(e) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._uiSetValue(e.newVal);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Updates the value of the input box to reflect
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the value passed in
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_uiSetValue : function(val) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.inputNode.set("value", val);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * value attribute default validator. Verifies that
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the value being set lies between the min/max value
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_validateValue: function(val) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var min = this.get("min"),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai max = this.get("max");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return (Lang.isNumber(val) && val >= min && val <= max);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Since this example focuses on general patterns for widget development, validator/set/get functions are not defined for attributes such as min/max in the interests of keeping the example simple, but could be, in a production ready spinner.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Rendering Support Methods</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>`Spinner`'s `renderUI` method hands off creation of the input field and buttons to the following helpers which use markup templates to generate node instances:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Creates the input field for the spinner and adds it to
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the widget's content box, if not already in the markup.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_renderInput : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var contentBox = this.get("contentBox"),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai input = contentBox.one("." + Spinner.INPUT_CLASS),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai strings = this.get("strings");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (!input) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai input = Node.create(Spinner.INPUT_TEMPLATE);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai contentBox.appendChild(input);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai input.set("title", strings.tooltip);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.inputNode = input;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Creates the button controls for the spinner and adds them to
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the widget's content box, if not already in the markup.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_renderButtons : function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var contentBox = this.get("contentBox"),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai strings = this.get("strings");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var inc = this._createButton(strings.increment, this.getClassName("increment"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var dec = this._createButton(strings.decrement, this.getClassName("decrement"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.incrementNode = contentBox.appendChild(inc);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.decrementNode = contentBox.appendChild(dec);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Utility method, to create a spinner button
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_createButton : function(text, className) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var btn = Y.Node.create(Spinner.BTN_TEMPLATE);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai btn.set("innerHTML", text);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai btn.set("title", text);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai btn.addClass(className);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return btn;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>DOM Event Listeners</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The DOM event listeners attached during `bindUI` are straightforward event listeners, which receive the event facade for the DOM event, and update the spinner state accordingly.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>A couple of interesting points worth noting: In the `"key"` listener we set up, we can call `e.preventDefault()` without having to check the character code, since the `"key"` event specifier will only invoke the listener
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaiif one of the specified keys is pressed (arrow/page up/down)</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Also, to allow the spinner to update its value while the mouse button is held down, we set up a timer, which gets cleared out when we receive a mouseup event on the document.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Bounding box Arrow up/down, Page up/down key listener.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Increments/Decrements the spinner value, based on the key pressed.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_onDirectionKey : function(e) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai e.preventDefault();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai switch (e.charCode) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai case 38:
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal += minorStep;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai break;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai case 40:
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal -= minorStep;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai break;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai case 33:
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal += majorStep;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal = Math.min(newVal, this.get("max"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai break;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai case 34:
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal -= majorStep;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai newVal = Math.max(newVal, this.get("min"));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai break;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (newVal !== currVal) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.set("value", newVal);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Bounding box mouse down handler. Will determine if the mouse down
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * is on one of the spinner buttons, and increment/decrement the value
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * accordingly.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * The method also sets up a timer, to support the user holding the mouse
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * down on the spinner buttons. The timer is cleared when a mouse up event
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * is detected.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_onMouseDown : function(e) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var node = e.target
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (node.hasClass(this.getClassName("increment"))) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.set("value", currVal + minorStep);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai } else if (node.hasClass(this.getClassName("decrement"))) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.set("value", currVal - minorStep);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (handled) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._setMouseDownTimers(dir);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Document mouse up handler. Clears the timers supporting
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * the "mouse held down" behavior.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_onDocMouseUp : function(e) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this._clearMouseDownTimers();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai},
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/*
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai * Simple change handler, to make sure user does not input an invalid value
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai_onInputChange : function(e) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (!this._validateValue(this.inputNode.get("value"))) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // If the entered value is not valid, re-display the stored value
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.syncUI();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai }
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>ClassName Support Methods</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>A key part of developing widgets which work with the DOM is defining class names which it will use to mark the nodes it renders. These class names could be used to mark a node for later retrieval/lookup, for CSS application (both functional as well as cosmetic) or to indicate the current state of the widget.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The widget infrastructure uses the `ClassNameManager` utility, to generate consistently named classes to apply to the nodes it adds to the page:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiY.ClassNameManager.getClassName(Spinner.NAME, "value");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaithis.getClassName("increment");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiClass names generated by the Widget's `getClassName` prototype method use the NAME field of the widget, to generate a prefixed classname through `ClassNameManager` - e.g. for spinner the `this.getClassName("increment")` above will generate the class name `yui3-spinner-increment` ("yui" being the system level prefix, "spinner" being the widget name).
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiWhen you need to generate standard class names in static code (where you don't have a reference to `this.getClassName()`), you can use the ClassNameManager directly, as shown in line 1 above, to achieve the same results.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>CSS Considerations</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Since widget uses the `getClassName` method to generate state-related class names and to mark the bounding box/content box of the widget (e.g. "yui3-[widgetname]-content", "yui3-[widgetname]-hidden", "yui3-[widgetname]-disabled"), we need to provide the default CSS handling for states we're interested in handling for the new Spinner widget. The "yui3-[widgetname]-hidden" class is probably one state class, which all widgets will provide implementations for.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Progressive enhancement support, to hide the text box, if JavaScript is enabled, while we instantiate the rich control */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-js-enabled .yui3-spinner-loading {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:none;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Controlling show/hide state using display (since this control is inline-block) */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-spinner-hidden {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:none;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Bounding Box - Set the bounding box to be "inline block" for spinner */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-spinner {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:inline-block;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai zoom:1;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai *display:inline;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Content Box - Start adding visual treatment for the spinner */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-spinner-content {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:1px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Input Text Box, generated through getClassName("value") */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-spinner-value {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai/* Button controls, generated through getClassName("increment") */
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai.yui3-spinner-increment, .yui3-spinner-decrement {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai ...
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Using The Spinner Widget</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>For the example, we have an input field already on the page, which we'd like to enhance to create a Spinner instance. We mark it with a yui3-spinner-loading class, so that if JavaScript is enabled, we can hide it while we're instantiating the rich control:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<input type="text" id="numberField" class="yui3-spinner-loading" value="20">
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>We provide the constructor for the Spinner with the `srcNode` which contains the input field with our initial value. The `HTML_PARSER` code we saw earlier will extract the value from the input field, and use it as the initial value for the Spinner instance:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Create a new Spinner instance, drawing its
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// starting value from an input field already on the
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// page (the #numberField text input box)
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaivar spinner = new Spinner({
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai srcNode: "#numberField",
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai max:100,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai min:0
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai});
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaispinner.render();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaispinner.focus();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The custom widget class structure discussed above is captured in this <a href="{{componentAssets}}/mywidget.js.txt">"MyWidget" template file</a>, which you can use as a starting point to develop your own widgets.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h2>Complete Example Source</h2>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai{{>widget-extend-source}}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai```