~bac/juju-gui/trunkcopy

« back to all changes in this revision

Viewing changes to lib/yui/docs/attribute/attribute-getset.html

  • Committer: kapil.foss at gmail
  • Date: 2012-07-13 18:45:59 UTC
  • Revision ID: kapil.foss@gmail.com-20120713184559-2xl7be17egsrz0c9
reshape

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
<!DOCTYPE html>
2
 
<html lang="en">
3
 
<head>
4
 
    <meta charset="utf-8">
5
 
    <title>Example: Attribute Getters, Setters and Validators</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>
11
 
</head>
12
 
<body>
13
 
 
14
 
<div id="doc">
15
 
    <h1>Example: Attribute Getters, Setters and Validators</h1>
16
 
 
17
 
    
18
 
 
19
 
    <div class="yui3-g">
20
 
        <div class="yui3-u-3-4">
21
 
            <div id="main">
22
 
                <div class="content"><style type="text/css" scoped>
23
 
 
24
 
    #boxParent {
25
 
        overflow:hidden;
26
 
        background-color:#004c6d;
27
 
        height:25em;
28
 
        padding:10px;
29
 
        margin:5px;
30
 
    }
31
 
 
32
 
    #boxParent .yui3-box p, #attrs p {
33
 
        margin:2px;
34
 
    }
35
 
 
36
 
    .attrs {
37
 
        border:1px solid #000;
38
 
        background-color:#cdcdcd;
39
 
        margin:5px;
40
 
    }
41
 
 
42
 
    .attrs .header {
43
 
        font-weight:bold;
44
 
        background-color:#aaa;
45
 
        padding:5px;
46
 
    }
47
 
 
48
 
    .attrs .body {
49
 
        padding:10px;
50
 
    }
51
 
 
52
 
    .attrs .body .hints li {
53
 
        padding-bottom:10px;
54
 
    }
55
 
 
56
 
    .attrs .footer {
57
 
        padding:0px 20px 10px 20px;
58
 
    }
59
 
 
60
 
    .attrs label {
61
 
        font-weight:bold;
62
 
        display:block;
63
 
        float:left;
64
 
        width:4em;
65
 
    }
66
 
 
67
 
    .attrs .hint {
68
 
        font-size:85%;
69
 
        color: #004c6d;
70
 
    }
71
 
 
72
 
    .attrs .fields {
73
 
        border-top:1px solid #aaa;
74
 
        padding:10px;
75
 
    }
76
 
 
77
 
    .yui3-box {
78
 
        padding:5px;
79
 
        border:1px solid #000;
80
 
        width:8em;
81
 
        height:8em;
82
 
        text-align:center;
83
 
        color:#000;
84
 
    }
85
 
 
86
 
    .yui3-box .color, .yui3-box .coord {
87
 
        font-family:courier;
88
 
    }
89
 
 
90
 
</style>
91
 
 
92
 
<div class="intro">
93
 
    <p>The <a href="attribute-basic.html">"Basic Attribute Configuration" example</a> shows how you can add attributes to a host class, and set up default values for them using the attribute configuration object. This example explores how you can configure <code>setter</code>, <code>getter</code> and <code>validator</code> functions for individual attributes, which can be used to modify or normalize attribute values during get and set invocations, and prevent invalid values from being stored.</p> 
94
 
</div>
95
 
 
96
 
<div class="example">
97
 
    <div id="attrs" class="attrs">
98
 
    <div class="header">Enter new values and click the "Set" buttons</div>
99
 
    <div class="body">
100
 
        <ul class="hints">
101
 
            <li>Try entering valid and invalid values for x, y; or values which attempt to position the box outside it's parent (parent box co-ordinates are displayed next to the text box).</li>
102
 
            <li>Try entering rgb, hex or keyword color values [ <code>rgb(255,0,0)</code>, <code>#ff0000</code>, <code>red</code> ].</li>
103
 
        </ul>
104
 
        <div class="fields">
105
 
            <p>
106
 
                <form action="#"  id="setX" class="action">
107
 
                    <label for="x">x:</label>
108
 
                    <input type="text" name="x" id="x" />
109
 
                    <button type="submit">Set</button>
110
 
                    <span id="xhint" class="hint"></span>
111
 
                </form>
112
 
            </p>
113
 
            <p>
114
 
                <form action="#" id="setY" class="action">
115
 
                    <label for="y">y:</label>
116
 
                    <input type="text" name="y" id="y" />
117
 
                    <button type="submit">Set</button>
118
 
                    <span id="yhint" class="hint"></span>
119
 
                </form>
120
 
            </p>
121
 
            <p>
122
 
                <form action="#" id="setColor" class="action">
123
 
                    <label for="color">color:</label>
124
 
                    <input type="text" name="color" id="color" />
125
 
                    <button type="submit">Set</button>
126
 
                </form>
127
 
            </p>
128
 
        </div>
129
 
    </div>
130
 
    <div class="footer">
131
 
        <button type="button" class="action" id="setXY">Set XY</button>
132
 
        <button type="button" class="action" id="setAll">Set All</button>
133
 
        <button type="button" class="action" id="getAll">Get All</button>
134
 
    </div>
135
 
</div>
136
 
 
137
 
<div id="boxParent"></div>
138
 
 
139
 
<script type="text/javascript">
140
 
// Get a new YUI instance 
141
 
YUI().use("node", "attribute", function(Y) {
142
 
 
143
 
    var boxParent = Y.one("#boxParent");
144
 
 
145
 
    // Setup a custom class with attribute support
146
 
    function Box(cfg) {
147
 
        this._createNode(cfg);
148
 
        
149
 
        // Attribute configuration
150
 
        var attrs = {
151
 
    
152
 
            "parent" : {
153
 
                value: null
154
 
            },
155
 
 
156
 
            "x" : {
157
 
                setter: function(val, name) {
158
 
                    // Pass through x value to xy
159
 
                    this.set("xy", [parseInt(val), this.get("y")]);
160
 
                },
161
 
    
162
 
                getter: function(val, name) {
163
 
                    // Get x value from xy
164
 
                    return this.get("xy")[0];
165
 
                }
166
 
            },
167
 
    
168
 
            "y" : {
169
 
                setter: function(val, name) {
170
 
                    // Pass through y value to xy
171
 
                    this.set("xy", [this.get("x"), parseInt(val)]);
172
 
                },
173
 
    
174
 
                getter: function() {
175
 
                    // Get y value from xy
176
 
                    return this.get("xy")[1];
177
 
                }
178
 
            },
179
 
    
180
 
            "xy" : {
181
 
                // Actual stored xy co-ordinates
182
 
                value: [0, 0],
183
 
    
184
 
                setter: function(val, name) {
185
 
                    // Constrain XY value to the parent element.
186
 
    
187
 
                    // Returns the constrained xy value, which will
188
 
                    // be the final value stored.
189
 
                    return this.constrain(val);
190
 
                },
191
 
    
192
 
                validator: function(val, name) {
193
 
                    // Ensure we only store a valid data value
194
 
                    return (Y.Lang.isArray(val) && val.length == 2 && Y.Lang.isNumber(val[0]) && Y.Lang.isNumber(val[1]));
195
 
                }
196
 
            },
197
 
    
198
 
            "color" : {
199
 
                value: "olive",
200
 
    
201
 
                getter: function(val, name) {
202
 
                    // Always normalize the returned value to 
203
 
                    // a hex color value,  even if the stored 
204
 
                    // value is a keyword, or an rgb value.
205
 
                    if (val) {
206
 
                        return Y.Color.toHex(val);
207
 
                    } else {
208
 
                        return null;
209
 
                    }
210
 
                },
211
 
    
212
 
                validator: function(val, name) {
213
 
                    // Ensure we only store rgb, hex or keyword values.
214
 
                    return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val) || Y.Color.KEYWORDS[val]); 
215
 
                }
216
 
            }
217
 
        };
218
 
 
219
 
        this.addAttrs(attrs, cfg);
220
 
 
221
 
        this._sync();
222
 
        this._bind();
223
 
    }
224
 
 
225
 
    Box.BUFFER = 5;
226
 
 
227
 
    // Create the box node
228
 
    Box.prototype._createNode = function() {
229
 
        this._node = Y.Node.create('<div class="yui3-box"><p>Positioned Box</p><p class="coord"></p><p class="color">None</p></div>');
230
 
    };
231
 
 
232
 
    // Update rendered state to match the attribute state
233
 
    Box.prototype._sync = function() {
234
 
        this._syncParent();
235
 
        this._syncXY();
236
 
        this._syncColor();
237
 
    };
238
 
 
239
 
    Box.prototype._syncParent = function() {
240
 
        this.get("parent").appendChild(this._node);
241
 
    };
242
 
 
243
 
    Box.prototype._syncXY = function() {
244
 
        this._node.setXY(this.get("xy"));
245
 
        this._node.one("p.coord").set("innerHTML", "[" + this.get("x") + "," + this.get("y") + "]");
246
 
    };
247
 
 
248
 
    Box.prototype._syncColor = function() {
249
 
        this._node.setStyle("backgroundColor", this.get("color"));
250
 
        this._node.one("p.color").set("innerHTML", this.get("color"));
251
 
    };
252
 
 
253
 
    // Bind listeners for attribute change events
254
 
    Box.prototype._bind = function() {
255
 
 
256
 
        // Reflect any changes in xy, to the rendered Node
257
 
        this.after("xyChange", this._syncXY);
258
 
 
259
 
        // Reflect any changes in color, to the rendered Node
260
 
        // and output the color value received from get
261
 
        this.after("colorChange", this._syncColor);
262
 
 
263
 
        // Append the rendered node to the parent provided
264
 
        this.after("parentChange", this._syncParent);
265
 
 
266
 
    };
267
 
 
268
 
    // Get min, max unconstrained values for X. Using Math.round to handle FF3's sub-pixel region values
269
 
    Box.prototype.getXConstraints = function() {
270
 
        var parentRegion = this.get("parent").get("region");
271
 
        return [Math.round(parentRegion.left + Box.BUFFER), Math.round(parentRegion.right - this._node.get("offsetWidth") - Box.BUFFER)];
272
 
    };
273
 
 
274
 
    // Get min, max unconstrained values for Y.  Using Math.round to handle FF3's sub-pixel region values
275
 
    Box.prototype.getYConstraints = function() {
276
 
        var parentRegion = this.get("parent").get("region");
277
 
        return [Math.round(parentRegion.top + Box.BUFFER), Math.round(parentRegion.bottom - this._node.get("offsetHeight") - Box.BUFFER)];
278
 
    };
279
 
 
280
 
    // Constrain the x,y value provided
281
 
    Box.prototype.constrain = function(val) {
282
 
 
283
 
        // If the X value places the box outside it's parent,
284
 
        // modify it's value to place the box inside it's parent.
285
 
 
286
 
        var xConstraints = this.getXConstraints();
287
 
 
288
 
        if (val[0] < xConstraints[0]) {
289
 
            val[0] = xConstraints[0];
290
 
        } else {
291
 
            if (val[0] > xConstraints[1]) {
292
 
                val[0] = xConstraints[1];
293
 
            }
294
 
        }
295
 
 
296
 
        // If the Y value places the box outside it's parent,
297
 
        // modify it's value to place the box inside it's parent.
298
 
 
299
 
        var yConstraints = this.getYConstraints();
300
 
 
301
 
        if (val[1] < yConstraints[0]) {
302
 
            val[1] = yConstraints[0];
303
 
        } else {
304
 
            if (val[1] > yConstraints[1]) {
305
 
                val[1] = yConstraints[1];
306
 
            }
307
 
        }
308
 
 
309
 
        return val;
310
 
    };
311
 
 
312
 
 
313
 
    Y.augment(Box, Y.Attribute);
314
 
 
315
 
    // ------
316
 
 
317
 
    // Create a new instance of Box
318
 
    var box = new Box({
319
 
        parent : boxParent 
320
 
    });
321
 
 
322
 
    // Set references to form controls
323
 
    var xTxt = Y.one("#x");
324
 
    var yTxt = Y.one("#y");
325
 
    var colorTxt = Y.one("#color");
326
 
 
327
 
    var xHint = Y.one("#xhint");
328
 
    var yHint = Y.one("#yhint");
329
 
 
330
 
    function getAll() {
331
 
        xTxt.set("value", box.get("x"));
332
 
        yTxt.set("value", box.get("y"));
333
 
        colorTxt.set("value", box.get("color"));
334
 
    }
335
 
 
336
 
    // Use event delegation for the action button clicks, and form submissions
337
 
    Y.delegate("click", function(e) {
338
 
 
339
 
        // Get Node target from the event object
340
 
 
341
 
        // We already know it's a button which has an action because
342
 
        // of our selector (button.action), so all we need to do is 
343
 
        // route it based on the id.
344
 
        var id = e.currentTarget.get("id");
345
 
 
346
 
        switch (id) {
347
 
            case "setXY":
348
 
                box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
349
 
                break;
350
 
            case "setAll":
351
 
                box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
352
 
                box.set("color", Y.Lang.trim(colorTxt.get("value")));
353
 
                break;
354
 
            case "getAll":
355
 
                getAll();
356
 
                break;
357
 
            default:
358
 
                break;
359
 
        }
360
 
 
361
 
    }, "#attrs", "button.action");
362
 
 
363
 
    Y.all("#attrs form.action").on("submit", function(e) {
364
 
 
365
 
        e.preventDefault();
366
 
 
367
 
        // Get Node target from the event object
368
 
 
369
 
        // We already know it's a button which has an action because
370
 
        // of our selector (button.action), so all we need to do is 
371
 
        // route it based on the id.
372
 
        var id = e.currentTarget.get("id");
373
 
 
374
 
        switch (id) {
375
 
            case "setX":
376
 
                box.set("x", parseInt(xTxt.get("value")));
377
 
                break;
378
 
            case "setY":
379
 
                box.set("y", parseInt(yTxt.get("value")));
380
 
                break;
381
 
            case "setColor":
382
 
                box.set("color", Y.Lang.trim(colorTxt.get("value")));
383
 
                break;
384
 
            default:
385
 
                break;
386
 
        }
387
 
    });
388
 
 
389
 
    // Bind listeners to provide min, max unconstrained value hints for x, y
390
 
    // (focus/blur doesn't bubble, so bind individual listeners)
391
 
    Y.on("focus", function() {
392
 
        var minmax = box.getXConstraints();
393
 
        xHint.set("innerHTML", "Valid values: " + minmax[0] + " to " + minmax[1]);
394
 
    }, xTxt);
395
 
 
396
 
    Y.on("focus", function() {
397
 
        var minmax = box.getYConstraints();
398
 
        yHint.set("innerHTML", "Valid values: " + minmax[0] + " to " + minmax[1]);
399
 
    }, yTxt);
400
 
 
401
 
    Y.on("blur", function() {
402
 
        xHint.set("innerHTML", "");
403
 
    }, xTxt);
404
 
 
405
 
    Y.on("blur", function() {
406
 
        yHint.set("innerHTML", "");
407
 
    }, yTxt);
408
 
 
409
 
    getAll();
410
 
});
411
 
</script>
412
 
 
413
 
</div>
414
 
 
415
 
<h2>Getter, Setter And Validator Functions</h2>
416
 
 
417
 
<p>Attribute lets you configure <code>getter</code> and <code>setter</code> functions for each attribute. These functions are invoked when the user calls Attribute's <code>get</code> and <code>set</code> methods, and provide a way to modify the value returned or the value stored respectively.</p>
418
 
 
419
 
<p>You can also define a <code>validator</code> function for each attribute, which is used to validate the final value before it gets stored.</p>
420
 
 
421
 
<p>All these functions receive the value and name of the attribute being set or retrieved, as shown in the example code below. The name is not used in this example, but is provided to support use cases where you may wish to share the same function between different attributes.</p>
422
 
 
423
 
<h3>Creating The Box Class - The X, Y And XY Attributes</h3>
424
 
 
425
 
<p>In this example, we'll set up a custom <code>Box</code> class representing a positionable element, with <code>x</code>, <code>y</code> and <code>xy</code> attributes.</p>
426
 
 
427
 
<p>Only the <code>xy</code> attribute will actually store the page co-ordinate position of the box. The <code>x</code> and <code>y</code> attributes provide the user a convenient way to set only one of the co-ordinates. 
428
 
However we don't want to store the actual values in the <code>x</code> and <code>y</code> attributes, to avoid having to constantly synchronize all three. 
429
 
 
430
 
The <code>getter</code> and <code>setter</code> functions provide us with an easy way to achieve this. We'll define <code>getter</code> and <code>setter</code> functions for both the <code>x</code> and <code>y</code> attributes, which simply pass through to the <code>xy</code> attribute to store and retrieve values:</p>
431
 
 
432
 
<pre class="code prettyprint">&#x2F;&#x2F; Setup a custom class with attribute support
433
 
function Box(cfg) {
434
 
 
435
 
    ...
436
 
          
437
 
    &#x2F;&#x2F; Attribute configuration
438
 
    var attrs = {
439
 
 
440
 
        &quot;parent&quot; : {
441
 
            value: null
442
 
        },
443
 
 
444
 
        &quot;x&quot; : {
445
 
            setter: function(val, name) {
446
 
                &#x2F;&#x2F; Pass through x value to xy
447
 
                this.set(&quot;xy&quot;, [parseInt(val), this.get(&quot;y&quot;)]);
448
 
            },
449
 
 
450
 
            getter: function(val, name) {
451
 
                &#x2F;&#x2F; Get x value from xy
452
 
                return this.get(&quot;xy&quot;)[0];
453
 
            }
454
 
        },
455
 
 
456
 
        &quot;y&quot; : {
457
 
            setter: function(val, name) {
458
 
                &#x2F;&#x2F; Pass through y value to xy
459
 
                this.set(&quot;xy&quot;, [this.get(&quot;x&quot;), parseInt(val)]);
460
 
            },
461
 
 
462
 
            getter: function() {
463
 
                &#x2F;&#x2F; Get y value from xy
464
 
                return this.get(&quot;xy&quot;)[1];
465
 
            }
466
 
        },
467
 
 
468
 
        &quot;xy&quot; : {
469
 
            &#x2F;&#x2F; Actual stored xy co-ordinates
470
 
            value: [0, 0],
471
 
 
472
 
            setter: function(val, name) {
473
 
                &#x2F;&#x2F; Constrain XY value to the parent element.
474
 
 
475
 
                &#x2F;&#x2F; Returns the constrained xy value, which will
476
 
                &#x2F;&#x2F; be the final value stored.
477
 
                return this.constrain(val);
478
 
            },
479
 
 
480
 
            validator: function(val, name) {
481
 
                &#x2F;&#x2F; Ensure we only store a valid data value
482
 
                return (Y.Lang.isArray(val) &amp;&amp; 
483
 
                        val.length == 2 &amp;&amp; 
484
 
                        Y.Lang.isNumber(val[0]) &amp;&amp; Y.Lang.isNumber(val[1]));
485
 
            }
486
 
        },
487
 
 
488
 
    ...
489
 
 
490
 
    this.addAttrs(attrs, cfg);
491
 
 
492
 
    ...
493
 
}</pre>
494
 
 
495
 
 
496
 
<p>The <code>validator</code> function for <code>xy</code> ensures that only valid values finally end up being stored.</p>
497
 
 
498
 
<p>The <code>xy</code> attribute also has a <code>setter</code> function configured, which makes sure that the box is always constrained to it's parent element. The <code>constrain</code> method which it delegates to, takes the xy value the user is trying to set and returns a constrained value if the x or y values fall outside the parent box. The value which is returned by the <code>setter</code> is the value which is ultimately stored for the <code>xy</code> attribute:</p>
499
 
 
500
 
<pre class="code prettyprint">&#x2F;&#x2F; Get min, max unconstrained values for X. 
501
 
&#x2F;&#x2F; Using Math.round to handle FF3&#x27;s sub-pixel region values
502
 
Box.prototype.getXConstraints = function() {
503
 
    var parentRegion = this.get(&quot;parent&quot;).get(&quot;region&quot;);
504
 
    return [Math.round(parentRegion.left + Box.BUFFER), 
505
 
            Math.round(parentRegion.right - this._node.get(&quot;offsetWidth&quot;) - Box.BUFFER)];
506
 
};
507
 
 
508
 
&#x2F;&#x2F; Get min, max unconstrained values for Y.  
509
 
&#x2F;&#x2F; Using Math.round to handle FF3&#x27;s sub-pixel region values
510
 
Box.prototype.getYConstraints = function() {
511
 
    var parentRegion = this.get(&quot;parent&quot;).get(&quot;region&quot;);
512
 
    return [Math.round(parentRegion.top + Box.BUFFER), 
513
 
            Math.round(parentRegion.bottom - this._node.get(&quot;offsetHeight&quot;) - Box.BUFFER)];
514
 
};
515
 
 
516
 
&#x2F;&#x2F; Constrains given x,y values
517
 
Box.prototype.constrain = function(val) {
518
 
 
519
 
    &#x2F;&#x2F; If the X value places the box outside it&#x27;s parent,
520
 
    &#x2F;&#x2F; modify it&#x27;s value to place the box inside it&#x27;s parent.
521
 
 
522
 
    var xConstraints = this.getXConstraints();
523
 
 
524
 
    if (val[0] &lt; xConstraints[0]) {
525
 
        val[0] = xConstraints[0];
526
 
    } else {
527
 
        if (val[0] &gt; xConstraints[1]) {
528
 
            val[0] = xConstraints[1];
529
 
        }
530
 
    }
531
 
 
532
 
    &#x2F;&#x2F; If the Y value places the box outside it&#x27;s parent,
533
 
    &#x2F;&#x2F; modify it&#x27;s value to place the box inside it&#x27;s parent.
534
 
 
535
 
    var yConstraints = this.getYConstraints();
536
 
 
537
 
    if (val[1] &lt; yConstraints[0]) {
538
 
        val[1] = yConstraints[0];
539
 
    } else {
540
 
        if (val[1] &gt; yConstraints[1]) {
541
 
            val[1] = yConstraints[1];
542
 
        }
543
 
    }
544
 
 
545
 
    return val;
546
 
};</pre>
547
 
 
548
 
 
549
 
<p>The <code>setter</code>, <code>getter</code> and <code>validator</code> functions are invoked with the host object as the context, so that they can refer to the host object using "<code>this</code>", as we see in the <code>setter</code> function for <code>xy</code>.</p>
550
 
 
551
 
<h3>The Color Attribute - Normalizing Stored Values Through Get</h3>
552
 
 
553
 
<p>The <code>Box</code> class also has a <code>color</code> attribute which also has a <code>getter</code> and <code>validator</code> functions defined:</p>
554
 
 
555
 
<pre class="code prettyprint">...
556
 
&quot;color&quot; : {
557
 
    value: &quot;olive&quot;,
558
 
 
559
 
    getter: function(val, name) {
560
 
        if (val) {
561
 
            return Y.Color.toHex(val);
562
 
        } else {
563
 
            return null;
564
 
        }
565
 
    },
566
 
 
567
 
    validator: function(val, name) {
568
 
        return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val) 
569
 
                    || Y.Color.KEYWORDS[val]);
570
 
    }
571
 
}
572
 
...</pre>
573
 
 
574
 
 
575
 
<p>The role of the <code>getter</code> handler in this case is to normalize the actual stored value of the <code>color</code> attribute, so that users always receive the hex value, regardless of the actual value stored, which maybe a color keyword (e.g. <code>&quot;red&quot;</code>), an rgb value (e.g.<code>rbg(255,0,0)</code>), or a hex value (<code>#ff0000</code>). The <code>validator</code> ensures the the stored value is one of these three formats.</p>
576
 
 
577
 
<h3>Syncing Changes Using Attribute Change Events</h3>
578
 
 
579
 
<p>Another interesting aspect of this example, is it's use of attribute change events to listen for changes to the attribute values. <code>Box</code>'s <code>_bind</code> method configures a set of attribute change event listeners which monitor changes to the <code>xy</code>, <code>color</code> and <code>parent</code> attributes and update the rendered DOM for the Box in response:</p>
580
 
 
581
 
<pre class="code prettyprint">&#x2F;&#x2F; Bind listeners for attribute change events
582
 
Box.prototype._bind = function() {
583
 
 
584
 
    &#x2F;&#x2F; Reflect any changes in xy, to the rendered Node
585
 
    this.after(&quot;xyChange&quot;, this._syncXY);
586
 
 
587
 
    &#x2F;&#x2F; Reflect any changes in color, to the rendered Node
588
 
    &#x2F;&#x2F; and output the color value received from get
589
 
    this.after(&quot;colorChange&quot;, this._syncColor);
590
 
 
591
 
    &#x2F;&#x2F; Append the rendered node to the parent provided
592
 
    this.after(&quot;parentChange&quot;, this._syncParent);
593
 
 
594
 
};</pre>
595
 
 
596
 
 
597
 
<p>Since only <code>xy</code> stores the final co-ordinates, we don't need to monitor the <code>x</code> and <code>y</code> attributes individually for changes.</p>
598
 
 
599
 
<h3>DOM Event Listeners And Delegation</h3>
600
 
 
601
 
<p>Although not an integral part of the example, it's worth highlighting the code which is used to setup the DOM event listeners for the form elements used by the example:</p>
602
 
 
603
 
<pre class="code prettyprint">&#x2F;&#x2F; Set references to form controls
604
 
var xTxt = Y.one(&quot;#x&quot;);
605
 
var yTxt = Y.one(&quot;#y&quot;);
606
 
var colorTxt = Y.one(&quot;#color&quot;);
607
 
 
608
 
&#x2F;&#x2F; Use event delegation for the action button clicks, and form submissions
609
 
Y.delegate(&quot;click&quot;, function(e) {
610
 
 
611
 
    &#x2F;&#x2F; Get Node target from the event object
612
 
 
613
 
    &#x2F;&#x2F; We already know it&#x27;s a button which has an action because
614
 
    &#x2F;&#x2F; of our selector (button.action), so all we need to do is 
615
 
    &#x2F;&#x2F; route it based on the id.
616
 
    var id = e.currentTarget.get(&quot;id&quot;);
617
 
 
618
 
    switch (id) {
619
 
        case &quot;setXY&quot;:
620
 
            box.set(&quot;xy&quot;, [parseInt(xTxt.get(&quot;value&quot;)), parseInt(yTxt.get(&quot;value&quot;))]);
621
 
            break;
622
 
        case &quot;setAll&quot;:
623
 
            box.set(&quot;xy&quot;, [parseInt(xTxt.get(&quot;value&quot;)), parseInt(yTxt.get(&quot;value&quot;))]);
624
 
            box.set(&quot;color&quot;, Y.Lang.trim(colorTxt.get(&quot;value&quot;)));
625
 
            break;
626
 
        case &quot;getAll&quot;:
627
 
            getAll();
628
 
            break;
629
 
        default:
630
 
            break;
631
 
    }
632
 
 
633
 
}, &quot;#attrs&quot;, &quot;button.action&quot;);</pre>
634
 
 
635
 
<p>Rather than attach individual listeners to each button, the above code uses YUI 3's <code>delegate</code> support, to listen for <code>click</code> from buttons, with an <code>action</code> class which bubble up to the <code>attrs</code> element.</p>
636
 
<p>The delegate listener uses the <a href="http://yuilibrary.com/yui/docs/api/DOMEventFacade.html">Event Facade</a> which normalizes cross-browser access to DOM event properties, such as <code>currentTarget</code>, to route to the appropriate button handler. Note the use of selector syntax when we specify the elements for the listener (e.g. <code>#attrs</code>, <code>button.actions</code>) and the use of the <a href="http://yuilibrary.com/yui/docs/api/Node.html">Node</a> facade when dealing with references to HTML elements (e.g. <code>xTxt, yTxt, colorTxt</code>).</p>
637
 
 
638
 
<h2>Complete Example Source</h2>
639
 
 
640
 
<pre class="code prettyprint">&lt;div id=&quot;attrs&quot; class=&quot;attrs&quot;&gt;
641
 
    &lt;div class=&quot;header&quot;&gt;Enter new values and click the &quot;Set&quot; buttons&lt;&#x2F;div&gt;
642
 
    &lt;div class=&quot;body&quot;&gt;
643
 
        &lt;ul class=&quot;hints&quot;&gt;
644
 
            &lt;li&gt;Try entering valid and invalid values for x, y; or values which attempt to position the box outside it&#x27;s parent (parent box co-ordinates are displayed next to the text box).&lt;&#x2F;li&gt;
645
 
            &lt;li&gt;Try entering rgb, hex or keyword color values [ &lt;code&gt;rgb(255,0,0)&lt;&#x2F;code&gt;, &lt;code&gt;#ff0000&lt;&#x2F;code&gt;, &lt;code&gt;red&lt;&#x2F;code&gt; ].&lt;&#x2F;li&gt;
646
 
        &lt;&#x2F;ul&gt;
647
 
        &lt;div class=&quot;fields&quot;&gt;
648
 
            &lt;p&gt;
649
 
                &lt;form action=&quot;#&quot;  id=&quot;setX&quot; class=&quot;action&quot;&gt;
650
 
                    &lt;label for=&quot;x&quot;&gt;x:&lt;&#x2F;label&gt;
651
 
                    &lt;input type=&quot;text&quot; name=&quot;x&quot; id=&quot;x&quot; &#x2F;&gt;
652
 
                    &lt;button type=&quot;submit&quot;&gt;Set&lt;&#x2F;button&gt;
653
 
                    &lt;span id=&quot;xhint&quot; class=&quot;hint&quot;&gt;&lt;&#x2F;span&gt;
654
 
                &lt;&#x2F;form&gt;
655
 
            &lt;&#x2F;p&gt;
656
 
            &lt;p&gt;
657
 
                &lt;form action=&quot;#&quot; id=&quot;setY&quot; class=&quot;action&quot;&gt;
658
 
                    &lt;label for=&quot;y&quot;&gt;y:&lt;&#x2F;label&gt;
659
 
                    &lt;input type=&quot;text&quot; name=&quot;y&quot; id=&quot;y&quot; &#x2F;&gt;
660
 
                    &lt;button type=&quot;submit&quot;&gt;Set&lt;&#x2F;button&gt;
661
 
                    &lt;span id=&quot;yhint&quot; class=&quot;hint&quot;&gt;&lt;&#x2F;span&gt;
662
 
                &lt;&#x2F;form&gt;
663
 
            &lt;&#x2F;p&gt;
664
 
            &lt;p&gt;
665
 
                &lt;form action=&quot;#&quot; id=&quot;setColor&quot; class=&quot;action&quot;&gt;
666
 
                    &lt;label for=&quot;color&quot;&gt;color:&lt;&#x2F;label&gt;
667
 
                    &lt;input type=&quot;text&quot; name=&quot;color&quot; id=&quot;color&quot; &#x2F;&gt;
668
 
                    &lt;button type=&quot;submit&quot;&gt;Set&lt;&#x2F;button&gt;
669
 
                &lt;&#x2F;form&gt;
670
 
            &lt;&#x2F;p&gt;
671
 
        &lt;&#x2F;div&gt;
672
 
    &lt;&#x2F;div&gt;
673
 
    &lt;div class=&quot;footer&quot;&gt;
674
 
        &lt;button type=&quot;button&quot; class=&quot;action&quot; id=&quot;setXY&quot;&gt;Set XY&lt;&#x2F;button&gt;
675
 
        &lt;button type=&quot;button&quot; class=&quot;action&quot; id=&quot;setAll&quot;&gt;Set All&lt;&#x2F;button&gt;
676
 
        &lt;button type=&quot;button&quot; class=&quot;action&quot; id=&quot;getAll&quot;&gt;Get All&lt;&#x2F;button&gt;
677
 
    &lt;&#x2F;div&gt;
678
 
&lt;&#x2F;div&gt;
679
 
 
680
 
&lt;div id=&quot;boxParent&quot;&gt;&lt;&#x2F;div&gt;
681
 
 
682
 
&lt;script type=&quot;text&#x2F;javascript&quot;&gt;
683
 
&#x2F;&#x2F; Get a new YUI instance 
684
 
YUI().use(&quot;node&quot;, &quot;attribute&quot;, function(Y) {
685
 
 
686
 
    var boxParent = Y.one(&quot;#boxParent&quot;);
687
 
 
688
 
    &#x2F;&#x2F; Setup a custom class with attribute support
689
 
    function Box(cfg) {
690
 
        this._createNode(cfg);
691
 
        
692
 
        &#x2F;&#x2F; Attribute configuration
693
 
        var attrs = {
694
 
    
695
 
            &quot;parent&quot; : {
696
 
                value: null
697
 
            },
698
 
 
699
 
            &quot;x&quot; : {
700
 
                setter: function(val, name) {
701
 
                    &#x2F;&#x2F; Pass through x value to xy
702
 
                    this.set(&quot;xy&quot;, [parseInt(val), this.get(&quot;y&quot;)]);
703
 
                },
704
 
    
705
 
                getter: function(val, name) {
706
 
                    &#x2F;&#x2F; Get x value from xy
707
 
                    return this.get(&quot;xy&quot;)[0];
708
 
                }
709
 
            },
710
 
    
711
 
            &quot;y&quot; : {
712
 
                setter: function(val, name) {
713
 
                    &#x2F;&#x2F; Pass through y value to xy
714
 
                    this.set(&quot;xy&quot;, [this.get(&quot;x&quot;), parseInt(val)]);
715
 
                },
716
 
    
717
 
                getter: function() {
718
 
                    &#x2F;&#x2F; Get y value from xy
719
 
                    return this.get(&quot;xy&quot;)[1];
720
 
                }
721
 
            },
722
 
    
723
 
            &quot;xy&quot; : {
724
 
                &#x2F;&#x2F; Actual stored xy co-ordinates
725
 
                value: [0, 0],
726
 
    
727
 
                setter: function(val, name) {
728
 
                    &#x2F;&#x2F; Constrain XY value to the parent element.
729
 
    
730
 
                    &#x2F;&#x2F; Returns the constrained xy value, which will
731
 
                    &#x2F;&#x2F; be the final value stored.
732
 
                    return this.constrain(val);
733
 
                },
734
 
    
735
 
                validator: function(val, name) {
736
 
                    &#x2F;&#x2F; Ensure we only store a valid data value
737
 
                    return (Y.Lang.isArray(val) &amp;&amp; val.length == 2 &amp;&amp; Y.Lang.isNumber(val[0]) &amp;&amp; Y.Lang.isNumber(val[1]));
738
 
                }
739
 
            },
740
 
    
741
 
            &quot;color&quot; : {
742
 
                value: &quot;olive&quot;,
743
 
    
744
 
                getter: function(val, name) {
745
 
                    &#x2F;&#x2F; Always normalize the returned value to 
746
 
                    &#x2F;&#x2F; a hex color value,  even if the stored 
747
 
                    &#x2F;&#x2F; value is a keyword, or an rgb value.
748
 
                    if (val) {
749
 
                        return Y.Color.toHex(val);
750
 
                    } else {
751
 
                        return null;
752
 
                    }
753
 
                },
754
 
    
755
 
                validator: function(val, name) {
756
 
                    &#x2F;&#x2F; Ensure we only store rgb, hex or keyword values.
757
 
                    return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val) || Y.Color.KEYWORDS[val]); 
758
 
                }
759
 
            }
760
 
        };
761
 
 
762
 
        this.addAttrs(attrs, cfg);
763
 
 
764
 
        this._sync();
765
 
        this._bind();
766
 
    }
767
 
 
768
 
    Box.BUFFER = 5;
769
 
 
770
 
    &#x2F;&#x2F; Create the box node
771
 
    Box.prototype._createNode = function() {
772
 
        this._node = Y.Node.create(&#x27;&lt;div class=&quot;yui3-box&quot;&gt;&lt;p&gt;Positioned Box&lt;&#x2F;p&gt;&lt;p class=&quot;coord&quot;&gt;&lt;&#x2F;p&gt;&lt;p class=&quot;color&quot;&gt;None&lt;&#x2F;p&gt;&lt;&#x2F;div&gt;&#x27;);
773
 
    };
774
 
 
775
 
    &#x2F;&#x2F; Update rendered state to match the attribute state
776
 
    Box.prototype._sync = function() {
777
 
        this._syncParent();
778
 
        this._syncXY();
779
 
        this._syncColor();
780
 
    };
781
 
 
782
 
    Box.prototype._syncParent = function() {
783
 
        this.get(&quot;parent&quot;).appendChild(this._node);
784
 
    };
785
 
 
786
 
    Box.prototype._syncXY = function() {
787
 
        this._node.setXY(this.get(&quot;xy&quot;));
788
 
        this._node.one(&quot;p.coord&quot;).set(&quot;innerHTML&quot;, &quot;[&quot; + this.get(&quot;x&quot;) + &quot;,&quot; + this.get(&quot;y&quot;) + &quot;]&quot;);
789
 
    };
790
 
 
791
 
    Box.prototype._syncColor = function() {
792
 
        this._node.setStyle(&quot;backgroundColor&quot;, this.get(&quot;color&quot;));
793
 
        this._node.one(&quot;p.color&quot;).set(&quot;innerHTML&quot;, this.get(&quot;color&quot;));
794
 
    };
795
 
 
796
 
    &#x2F;&#x2F; Bind listeners for attribute change events
797
 
    Box.prototype._bind = function() {
798
 
 
799
 
        &#x2F;&#x2F; Reflect any changes in xy, to the rendered Node
800
 
        this.after(&quot;xyChange&quot;, this._syncXY);
801
 
 
802
 
        &#x2F;&#x2F; Reflect any changes in color, to the rendered Node
803
 
        &#x2F;&#x2F; and output the color value received from get
804
 
        this.after(&quot;colorChange&quot;, this._syncColor);
805
 
 
806
 
        &#x2F;&#x2F; Append the rendered node to the parent provided
807
 
        this.after(&quot;parentChange&quot;, this._syncParent);
808
 
 
809
 
    };
810
 
 
811
 
    &#x2F;&#x2F; Get min, max unconstrained values for X. Using Math.round to handle FF3&#x27;s sub-pixel region values
812
 
    Box.prototype.getXConstraints = function() {
813
 
        var parentRegion = this.get(&quot;parent&quot;).get(&quot;region&quot;);
814
 
        return [Math.round(parentRegion.left + Box.BUFFER), Math.round(parentRegion.right - this._node.get(&quot;offsetWidth&quot;) - Box.BUFFER)];
815
 
    };
816
 
 
817
 
    &#x2F;&#x2F; Get min, max unconstrained values for Y.  Using Math.round to handle FF3&#x27;s sub-pixel region values
818
 
    Box.prototype.getYConstraints = function() {
819
 
        var parentRegion = this.get(&quot;parent&quot;).get(&quot;region&quot;);
820
 
        return [Math.round(parentRegion.top + Box.BUFFER), Math.round(parentRegion.bottom - this._node.get(&quot;offsetHeight&quot;) - Box.BUFFER)];
821
 
    };
822
 
 
823
 
    &#x2F;&#x2F; Constrain the x,y value provided
824
 
    Box.prototype.constrain = function(val) {
825
 
 
826
 
        &#x2F;&#x2F; If the X value places the box outside it&#x27;s parent,
827
 
        &#x2F;&#x2F; modify it&#x27;s value to place the box inside it&#x27;s parent.
828
 
 
829
 
        var xConstraints = this.getXConstraints();
830
 
 
831
 
        if (val[0] &lt; xConstraints[0]) {
832
 
            val[0] = xConstraints[0];
833
 
        } else {
834
 
            if (val[0] &gt; xConstraints[1]) {
835
 
                val[0] = xConstraints[1];
836
 
            }
837
 
        }
838
 
 
839
 
        &#x2F;&#x2F; If the Y value places the box outside it&#x27;s parent,
840
 
        &#x2F;&#x2F; modify it&#x27;s value to place the box inside it&#x27;s parent.
841
 
 
842
 
        var yConstraints = this.getYConstraints();
843
 
 
844
 
        if (val[1] &lt; yConstraints[0]) {
845
 
            val[1] = yConstraints[0];
846
 
        } else {
847
 
            if (val[1] &gt; yConstraints[1]) {
848
 
                val[1] = yConstraints[1];
849
 
            }
850
 
        }
851
 
 
852
 
        return val;
853
 
    };
854
 
 
855
 
 
856
 
    Y.augment(Box, Y.Attribute);
857
 
 
858
 
    &#x2F;&#x2F; ------
859
 
 
860
 
    &#x2F;&#x2F; Create a new instance of Box
861
 
    var box = new Box({
862
 
        parent : boxParent 
863
 
    });
864
 
 
865
 
    &#x2F;&#x2F; Set references to form controls
866
 
    var xTxt = Y.one(&quot;#x&quot;);
867
 
    var yTxt = Y.one(&quot;#y&quot;);
868
 
    var colorTxt = Y.one(&quot;#color&quot;);
869
 
 
870
 
    var xHint = Y.one(&quot;#xhint&quot;);
871
 
    var yHint = Y.one(&quot;#yhint&quot;);
872
 
 
873
 
    function getAll() {
874
 
        xTxt.set(&quot;value&quot;, box.get(&quot;x&quot;));
875
 
        yTxt.set(&quot;value&quot;, box.get(&quot;y&quot;));
876
 
        colorTxt.set(&quot;value&quot;, box.get(&quot;color&quot;));
877
 
    }
878
 
 
879
 
    &#x2F;&#x2F; Use event delegation for the action button clicks, and form submissions
880
 
    Y.delegate(&quot;click&quot;, function(e) {
881
 
 
882
 
        &#x2F;&#x2F; Get Node target from the event object
883
 
 
884
 
        &#x2F;&#x2F; We already know it&#x27;s a button which has an action because
885
 
        &#x2F;&#x2F; of our selector (button.action), so all we need to do is 
886
 
        &#x2F;&#x2F; route it based on the id.
887
 
        var id = e.currentTarget.get(&quot;id&quot;);
888
 
 
889
 
        switch (id) {
890
 
            case &quot;setXY&quot;:
891
 
                box.set(&quot;xy&quot;, [parseInt(xTxt.get(&quot;value&quot;)), parseInt(yTxt.get(&quot;value&quot;))]);
892
 
                break;
893
 
            case &quot;setAll&quot;:
894
 
                box.set(&quot;xy&quot;, [parseInt(xTxt.get(&quot;value&quot;)), parseInt(yTxt.get(&quot;value&quot;))]);
895
 
                box.set(&quot;color&quot;, Y.Lang.trim(colorTxt.get(&quot;value&quot;)));
896
 
                break;
897
 
            case &quot;getAll&quot;:
898
 
                getAll();
899
 
                break;
900
 
            default:
901
 
                break;
902
 
        }
903
 
 
904
 
    }, &quot;#attrs&quot;, &quot;button.action&quot;);
905
 
 
906
 
    Y.all(&quot;#attrs form.action&quot;).on(&quot;submit&quot;, function(e) {
907
 
 
908
 
        e.preventDefault();
909
 
 
910
 
        &#x2F;&#x2F; Get Node target from the event object
911
 
 
912
 
        &#x2F;&#x2F; We already know it&#x27;s a button which has an action because
913
 
        &#x2F;&#x2F; of our selector (button.action), so all we need to do is 
914
 
        &#x2F;&#x2F; route it based on the id.
915
 
        var id = e.currentTarget.get(&quot;id&quot;);
916
 
 
917
 
        switch (id) {
918
 
            case &quot;setX&quot;:
919
 
                box.set(&quot;x&quot;, parseInt(xTxt.get(&quot;value&quot;)));
920
 
                break;
921
 
            case &quot;setY&quot;:
922
 
                box.set(&quot;y&quot;, parseInt(yTxt.get(&quot;value&quot;)));
923
 
                break;
924
 
            case &quot;setColor&quot;:
925
 
                box.set(&quot;color&quot;, Y.Lang.trim(colorTxt.get(&quot;value&quot;)));
926
 
                break;
927
 
            default:
928
 
                break;
929
 
        }
930
 
    });
931
 
 
932
 
    &#x2F;&#x2F; Bind listeners to provide min, max unconstrained value hints for x, y
933
 
    &#x2F;&#x2F; (focus&#x2F;blur doesn&#x27;t bubble, so bind individual listeners)
934
 
    Y.on(&quot;focus&quot;, function() {
935
 
        var minmax = box.getXConstraints();
936
 
        xHint.set(&quot;innerHTML&quot;, &quot;Valid values: &quot; + minmax[0] + &quot; to &quot; + minmax[1]);
937
 
    }, xTxt);
938
 
 
939
 
    Y.on(&quot;focus&quot;, function() {
940
 
        var minmax = box.getYConstraints();
941
 
        yHint.set(&quot;innerHTML&quot;, &quot;Valid values: &quot; + minmax[0] + &quot; to &quot; + minmax[1]);
942
 
    }, yTxt);
943
 
 
944
 
    Y.on(&quot;blur&quot;, function() {
945
 
        xHint.set(&quot;innerHTML&quot;, &quot;&quot;);
946
 
    }, xTxt);
947
 
 
948
 
    Y.on(&quot;blur&quot;, function() {
949
 
        yHint.set(&quot;innerHTML&quot;, &quot;&quot;);
950
 
    }, yTxt);
951
 
 
952
 
    getAll();
953
 
});
954
 
&lt;&#x2F;script&gt;</pre>
955
 
 
956
 
</div>
957
 
            </div>
958
 
        </div>
959
 
 
960
 
        <div class="yui3-u-1-4">
961
 
            <div class="sidebar">
962
 
                
963
 
 
964
 
                
965
 
                    <div class="sidebox">
966
 
                        <div class="hd">
967
 
                            <h2 class="no-toc">Examples</h2>
968
 
                        </div>
969
 
 
970
 
                        <div class="bd">
971
 
                            <ul class="examples">
972
 
                                
973
 
                                    
974
 
                                        <li data-description="Use the Attribute API to define, set and get attribute values.">
975
 
                                            <a href="attribute-basic.html">Basic Attribute Configuration</a>
976
 
                                        </li>
977
 
                                    
978
 
                                
979
 
                                    
980
 
                                        <li data-description="Configure attributes to be readOnly or writeOnce.">
981
 
                                            <a href="attribute-rw.html">Read-Only and Write-Once Attributes</a>
982
 
                                        </li>
983
 
                                    
984
 
                                
985
 
                                    
986
 
                                        <li data-description="How to listen for changes in attribute values.">
987
 
                                            <a href="attribute-event.html">Attribute Change Events</a>
988
 
                                        </li>
989
 
                                    
990
 
                                
991
 
                                    
992
 
                                        <li data-description="Create a basic SpeedDater class, with Attribute support.">
993
 
                                            <a href="attribute-basic-speeddate.html">Attribute Based Speed Dating</a>
994
 
                                        </li>
995
 
                                    
996
 
                                
997
 
                                    
998
 
                                        <li data-description="Refactors the basic Speed Dating example, to use attribute change events to update rendered elements, and have two instances react to another.">
999
 
                                            <a href="attribute-event-speeddate.html">Attribute Event Based Speed Dating</a>
1000
 
                                        </li>
1001
 
                                    
1002
 
                                
1003
 
                                    
1004
 
                                        <li data-description="Add custom methods to get and set attribute values and provide validation support.">
1005
 
                                            <a href="attribute-getset.html">Attribute Getters, Setters and Validators</a>
1006
 
                                        </li>
1007
 
                                    
1008
 
                                
1009
 
                            </ul>
1010
 
                        </div>
1011
 
                    </div>
1012
 
                
1013
 
 
1014
 
                
1015
 
            </div>
1016
 
        </div>
1017
 
    </div>
1018
 
</div>
1019
 
 
1020
 
<script src="../assets/vendor/prettify/prettify-min.js"></script>
1021
 
<script>prettyPrint();</script>
1022
 
 
1023
 
</body>
1024
 
</html>