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
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<style type="text/css" scoped>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai #boxParent {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai overflow:hidden;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai background-color:#004c6d;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai height:25em;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:10px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai #boxParent .yui3-box p, #attrs p {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai border:1px solid #000;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai background-color:#cdcdcd;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .header {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai font-weight:bold;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai background-color:#aaa;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:5px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .body {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:10px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .body .hints li {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding-bottom:10px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .footer {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:0px 20px 10px 20px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs label {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai font-weight:bold;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai display:block;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .hint {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai font-size:85%;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai color: #004c6d;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .attrs .fields {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai border-top:1px solid #aaa;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:10px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai padding:5px;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai border:1px solid #000;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai text-align:center;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai .yui3-box .color, .yui3-box .coord {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai font-family:courier;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<div class="intro">
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai <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 `setter`, `getter` and `validator` 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>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<div class="example">
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai {{>attribute-getset-source}}
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h2>Getter, Setter And Validator Functions</h2>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Attribute lets you configure `getter` and `setter` functions for each attribute. These functions are invoked when the user calls Attribute's `get` and `set` methods, and provide a way to modify the value returned or the value stored respectively.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>You can also define a `validator` function for each attribute, which is used to validate the final value before it gets stored.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<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>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Creating The Box Class - The X, Y And XY Attributes</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>In this example, we'll set up a custom `Box` class representing a positionable element, with `x`, `y` and `xy` attributes.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Only the `xy` attribute will actually store the page co-ordinate position of the box. The `x` and `y` attributes provide the user a convenient way to set only one of the co-ordinates.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiHowever we don't want to store the actual values in the `x` and `y` attributes, to avoid having to constantly synchronize all three.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiThe `getter` and `setter` functions provide us with an easy way to achieve this. We'll define `getter` and `setter` functions for both the `x` and `y` attributes, which simply pass through to the `xy` attribute to store and retrieve values:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Setup a custom class with attribute support
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaifunction Box(cfg) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Attribute configuration
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var attrs = {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai "parent" : {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai setter: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Pass through x value to xy
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.set("xy", [parseInt(val), this.get("y")]);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai getter: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Get x value from xy
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return this.get("xy")[0];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai setter: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Pass through y value to xy
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.set("xy", [this.get("x"), parseInt(val)]);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai getter: function() {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Get y value from xy
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return this.get("xy")[1];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Actual stored xy co-ordinates
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value: [0, 0],
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai setter: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Constrain XY value to the parent element.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Returns the constrained xy value, which will
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // be the final value stored.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai validator: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Ensure we only store a valid data value
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return (Y.Lang.isArray(val) &&
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Y.Lang.isNumber(val[0]) && Y.Lang.isNumber(val[1]));
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `validator` function for `xy` ensures that only valid values finally end up being stored.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `xy` attribute also has a `setter` function configured, which makes sure that the box is always constrained to it's parent element. The `constrain` 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 `setter` is the value which is ultimately stored for the `xy` attribute:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Get min, max unconstrained values for X.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Using Math.round to handle FF3's sub-pixel region values
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var parentRegion = this.get("parent").get("region");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return [Math.round(parentRegion.left + Box.BUFFER),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Math.round(parentRegion.right - this._node.get("offsetWidth") - Box.BUFFER)];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Get min, max unconstrained values for Y.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Using Math.round to handle FF3's sub-pixel region values
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var parentRegion = this.get("parent").get("region");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return [Math.round(parentRegion.top + Box.BUFFER),
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai Math.round(parentRegion.bottom - this._node.get("offsetHeight") - Box.BUFFER)];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Constrains given x,y values
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // If the X value places the box outside it's parent,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // modify it's value to place the box inside it's parent.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var xConstraints = this.getXConstraints();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (val[0] < xConstraints[0]) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai val[0] = xConstraints[0];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (val[0] > xConstraints[1]) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai val[0] = xConstraints[1];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // If the Y value places the box outside it's parent,
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // modify it's value to place the box inside it's parent.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai var yConstraints = this.getYConstraints();
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (val[1] < yConstraints[0]) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai val[1] = yConstraints[0];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai if (val[1] > yConstraints[1]) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai val[1] = yConstraints[1];
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `setter`, `getter` and `validator` functions are invoked with the host object as the context, so that they can refer to the host object using "`this`", as we see in the `setter` function for `xy`.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>The Color Attribute - Normalizing Stored Values Through Get</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The `Box` class also has a `color` attribute which also has a `getter` and `validator` functions defined:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai value: "olive",
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai getter: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return null;
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai validator: function(val, name) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val)
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The role of the `getter` handler in this case is to normalize the actual stored value of the `color` attribute, so that users always receive the hex value, regardless of the actual value stored, which maybe a color keyword (e.g. `"red"`), an rgb value (e.g.`rbg(255,0,0)`), or a hex value (`#ff0000`). The `validator` ensures the the stored value is one of these three formats.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>Syncing Changes Using Attribute Change Events</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Another interesting aspect of this example, is it's use of attribute change events to listen for changes to the attribute values. `Box`'s `_bind` method configures a set of attribute change event listeners which monitor changes to the `xy`, `color` and `parent` attributes and update the rendered DOM for the Box in response:</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Bind listeners for attribute change events
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Reflect any changes in xy, to the rendered Node
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.after("xyChange", this._syncXY);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Reflect any changes in color, to the rendered Node
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // and output the color value received from get
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.after("colorChange", this._syncColor);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // Append the rendered node to the parent provided
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai this.after("parentChange", this._syncParent);
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>Since only `xy` stores the final co-ordinates, we don't need to monitor the `x` and `y` attributes individually for changes.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h3>DOM Event Listeners And Delegation</h3>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<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>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai// Set references to form controls
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaivar xTxt = Y.one("#x");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaivar yTxt = Y.one("#y");
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desaivar colorTxt = Y.one("#color");
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai// Use event delegation for the action button clicks, and form submissions
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen DesaiY.delegate("click", function(e) {
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai // Get Node target from the event object
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // We already know it's a button which has an action because
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // of our selector (button.action), so all we need to do is
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai // route it based on the id.
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai switch (id) {
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai case "setXY":
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai case "setAll":
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]);
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai box.set("color", Y.Lang.trim(colorTxt.get("value")));
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai case "getAll":
d670ed5457ff2a28c995fe21bd0fe0eb24abe723Satyen Desai}, "#attrs", "button.action");
ed5f686644f6fc425d5ff82cf23d8caf76894a41Satyen Desai<p>Rather than attach individual listeners to each button, the above code uses YUI 3's `delegate` support, to listen for `click` from buttons, with an `action` class which bubble up to the `attrs` element.</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<p>The delegate listener uses the <a href="{{apiDocs}}/DOMEventFacade.html">Event Facade</a> which normalizes cross-browser access to DOM event properties, such as `currentTarget`, to route to the appropriate button handler. Note the use of selector syntax when we specify the elements for the listener (e.g. `#attrs`, `button.actions`) and the use of the <a href="{{apiDocs}}/Node.html">Node</a> facade when dealing with references to HTML elements (e.g. `xTxt, yTxt, colorTxt`).</p>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai<h2>Complete Example Source</h2>
e808b8824ca1091c8efb5669db9129e68e5e1c14Satyen Desai{{>attribute-getset-source}}