5
<title>Example: Attribute Event Based Speed Dating</title>
6
<link rel="stylesheet" href="http://yui.yahooapis.com/3.4.0pr3/build/cssgrids/grids-min.css">
7
<link rel="stylesheet" href="../assets/css/main.css">
8
<link rel="stylesheet" href="../assets/vendor/prettify/prettify-min.css">
9
<script src="../../build/yui/yui-min.js"></script>
14
<h1>Example: Attribute Event Based Speed Dating</h1>
19
<div id="main" class="yui3-u">
20
<div class="content"><style type="text/css" scoped>
31
.interests.disabled, .reconsider.disabled {
40
border:1px solid #000;
45
background-color:#00f;
48
-webkit-border-radius: 10px;
49
-moz-border-radius: 10px;
51
box-shadow: 3px 3px 3px #888;
52
-moz-box-shadow: 3px 3px 3px #888;
53
-webkit-box-shadow: 3px 3px 3px #888;
66
border-top-right-radius: 10px;
67
border-top-left-radius: 10px;
68
-moz-border-radius-topright: 10px;
69
-moz-border-radius-topleft: 10px;
70
-webkit-border-top-right-radius: 10px;
71
-webkit-border-top-left-radius: 10px;
75
border-bottom-right-radius: 10px;
76
border-bottom-left-radius: 10px;
77
-moz-border-radius-bottomright: 10px;
78
-moz-border-radius-bottomleft: 10px;
79
-webkit-border-bottom-right-radius: 10px;
80
-webkit-border-bottom-left-radius: 10px;
84
background-color:#fff;
88
.sd-nametag .sd-bd .sd-name,
89
.sd-nametag .sd-bd .sd-personality,
90
.sd-nametag .sd-bd .sd-interests {
93
font-family:monospace;
94
text-decoration:underline;
100
<p>Attribute change events are one of the key benefits of using attributes to maintain state for your objects, instead of regular object properties.</p>
101
<p>This example refactors the basic <a href="attribute-basic-speeddate.html">"Attribute Based Speed Dating" example</a> to shows how you can listen for attribute change events to tie together your object's internal logic (such as updating the visual presentation of the object), and also to communicate with other objects.</p>
104
<div class="example">
107
<h1>Communicating With Attribute Events On Speed Dates</h1>
110
<button type="button" class="hi">Hi, I'm John</button>
112
<span class="interests disabled">
114
<label><input type="checkbox" class="interest" value="Sunsets" disabled="disabled"> Sunsets</label>
115
<label><input type="checkbox" class="interest" value="Reading Specifications" disabled="disabled"> Reading Specifications</label>
116
<label><input type="checkbox" class="interest" value="Saving Whales" disabled="disabled"> Saving Whales</label>
117
<label><input type="checkbox" class="interest" value="Knitting" disabled="disabled"> Knitting</label>
119
<div class="shirt"></div>
123
<button type="button" class="hi" disabled="disabled">Hey, I'm Jane</button>
124
<button type="button" class="movingOn" disabled="disabled">I'm Moving On...</button> <span class="reconsider disabled">(unless he likes whales, and avoids knitting <em class="message"></em>)</span>
125
<div class="shirt"></div>
129
<script type="text/javascript">
131
// Get a new instance of YUI and
132
// load it with the required set of modules
134
YUI().use("collection", "event", "node", "attribute", "substitute", function(Y) {
136
// Setup custom class which we want to add managed attribute support to
138
function SpeedDater(cfg) {
139
// When constructed, setup the initial attributes for the instance, by calling the addAttrs method.
158
this.addAttrs(attrs, cfg);
161
// The HTML template representing the SpeedDater name tag.
162
SpeedDater.NAMETAG = '<div class="sd-nametag"> \
163
<div class="sd-hd">Hello!</div> \
164
<div class="sd-bd"> \
165
<p>I\'m <span class="sd-name">{name}</span> and my PersonalityQuotientIndex is <span class="sd-personality">{personality}</span></p> \
166
<p>I enjoy <span class="sd-interests">{interests}</span>.</p> \
168
<div class="sd-ft sd-availability">{available}</div> \
171
// Method used to render the visual representation of a SpeedDater object's state (in this case as a name tag)
172
SpeedDater.prototype.applyNameTag = function(where) {
175
name: this.get("name"),
176
available: (this.get("available")) ? "" : "Sorry, moving on",
177
personality: this.get("personality"),
178
interests: (this.get("interests").length == 0) ? "absolutely nothing" : this.get("interests").join(", ")
181
this.nameTag = Y.Node.create(Y.substitute(SpeedDater.NAMETAG, tokens));
182
Y.one(where).appendChild(this.nameTag);
184
this.listenForChanges();
187
// Method used to attach attribute change event listeners, so that the SpeedDater instance
188
// will react to changes in attribute state, and update what's rendered on the page
189
SpeedDater.prototype.listenForChanges = function() {
191
// Sync up the UI for "available", after the value of the "available" attribute has changed:
192
this.after("availableChange", function(e) {
193
var taken = (e.newVal) ? "" : "Oh, is that the time?";
194
this.nameTag.one(".sd-availability").set("innerHTML", taken);
197
// Sync up the UI for "name", after the value of the "name" attribute has changed:
198
this.after("nameChange", function(e) {
200
this.nameTag.one(".sd-name").set("innerHTML", name);
203
// Sync up the UI for "personality", after the value of the "personality" attribute has changed:
204
this.after("personalityChange", function(e) {
205
var personality = e.newVal;
207
var personalityEl = this.nameTag.one(".sd-personality");
208
personalityEl.set("innerHTML", personality);
210
if (personality > 90) {
211
personalityEl.addClass("sd-max");
215
// Sync up the UI for "interests", after the value of the "interests" attribute has changed:
216
this.after("interestsChange", function(e) {
217
var interests = (e.newVal.length == 0) ? "absolutely nothing" : this.get("interests").join(", ");
218
this.nameTag.one(".sd-interests").set("innerHTML", interests);
222
// Augment custom class with Attribute
223
Y.augment(SpeedDater, Y.Attribute);
227
Y.on("click", function() {
231
john = new SpeedDater({
235
john.applyNameTag("#john .shirt");
237
Y.one("#jane .hi").set("disabled", false);
242
Y.on("click", function() {
246
jane = new SpeedDater({
249
interests: ["Popcorn", "Saving Whales"]
251
jane.applyNameTag("#jane .shirt");
253
// Update Jane's interests state, after John's interests state changes...
254
john.after("interestsChange", function(e) {
256
var janesInterests = jane.get("interests"),
257
johnsInterests = e.newVal,
259
readingSpecs = "Reading Specifications";
261
// If it turns out that John enjoys reading specs, then Jane can admit it too...
262
if (Y.Array.indexOf(johnsInterests, readingSpecs) !== -1) {
263
if(Y.Array.indexOf(janesInterests, readingSpecs) == -1) {
264
janesInterests.push(readingSpecs);
267
janesInterests = Y.Array.reject(janesInterests, function(item){return (item == readingSpecs);});
270
jane.set("interests", janesInterests);
271
jane.set("available", true);
276
// We can also listen before an attribute changes its value, and decide if we want to
277
// allow the state change to occur or not. Invoking e.preventDefault() stops the state from
280
// In this case, Jane can change her mind about making herself unavailable, if John likes
281
// saving whales, as long as he doesn't dig knitting too.
283
jane.on("availableChange", function(e) {
284
var johnsInterests = john.get("interests");
285
var janeAvailable = e.newVal;
286
if (janeAvailable === false && Y.Array.indexOf(johnsInterests, "Saving Whales") !== -1 && Y.Array.indexOf(johnsInterests, "Knitting") == -1 ) {
290
setMessage("... which he does");
299
Y.on("click", function() {
300
jane.set("available", false);
301
}, "#jane .movingOn");
303
// A delegate DOM event listener which will update John's interests, based on the checkbox state, whenever
304
// a checkbox is clicked.
305
Y.delegate("click", function() {
308
Y.Node.all("#john input[type=checkbox].interest").each(function(checkbox) {
309
if (checkbox.get("checked")) {
310
interests.push(checkbox.get("value"));
313
john.set("interests", interests);
315
}, "#john", "input[type=checkbox].interest");
318
// Example helpers...
319
function enableExampleUI() {
320
Y.all("#jane button").set("disabled", false);
321
Y.all("#john button").set("disabled", false);
322
Y.all("#john input").set("disabled", false);
323
Y.one("#john .interests").removeClass("disabled");
324
Y.one("#jane .reconsider").removeClass("disabled");
327
function setMessage(msg) {
328
Y.one("#jane .message").set("innerHTML", msg);
336
<h2>Listening For Attribute Change Events</h2>
338
<p>In this example, we'll look at how you can setup listeners for attribute change events, and work with the event payload which the listeners receive,
339
using the <code>SpeedDater</code> class, introduced in the <a href="attribute-basic-speeddate.html">"Attribute Based Speed Dating" example</a>.</p>
341
<p>We'll create two SpeedDater instances, <code>jane</code> and <code>john</code>, and use the attribute events they generate both internally (within the class code), to wire up UI refreshes,
342
and externally, to have <code>jane</code> react to changes in the <code>john</code>'s state.</p>
344
<h3>Setting Up The SpeedDater Class With Attribute</h3>
346
<p>We start by setting up the same basic class we created for the <a href="attribute-basic-speeddate.html">"Attribute Based Speed Dating" example</a>, with an additional attribute, <code>interests</code>, using the code below:</p>
348
<pre class="code prettyprint">// Setup custom class which we want to add managed attribute support to
349
function SpeedDater(cfg) {
350
// When constructed, setup the initial attributes for the instance,
351
// by calling the addAttrs method.
370
this.addAttrs(attrs, cfg);
373
// Augment custom class with Attribute
374
Y.augment(SpeedDater, Y.Attribute);</pre>
377
<p>We then create two instances of SpeedDaters, <code>jane</code> and <code>john</code>:</p>
379
<pre class="code prettyprint">// Create a john instance...
380
john = new SpeedDater({
381
name: "John",
384
// ... and render to the page
385
john.applyNameTag("#john .shirt");
387
// Create a jane instance...
388
jane = new SpeedDater({
389
name: "Jane",
391
interests: ["Popcorn", "Saving Whales"]
393
jane.applyNameTag("#jane .shirt");</pre>
396
<h3>Registering Event Listeners</h3>
398
<p>For this event based example, we no longer have an <code>updateNameTag()</code> method which the user is responsible for calling when they want to refresh the name tag rendered on the page, as we did in the basic example.
399
Instead the <code>SpeedDater</code> class sets up some internal attribute change event listeners in its <code>listenForChanges()</code> method, which will refresh the UI for a particular attribute, each time its value is modified:</p>
401
<pre class="code prettyprint">// Method used to attach attribute change event listeners, so that
402
// the SpeedDater instance will react to changes in attribute state,
403
// and update what's rendered on the page
404
SpeedDater.prototype.listenForChanges = function() {
406
// Sync up the UI for "available", after the value of the "available"
407
// attribute has changed:
408
this.after("availableChange", function(e) {
409
var taken = (e.newVal) ? "" : "Oh, is that the time?";
410
this.nameTag.one(".sd-availability").set("innerHTML", taken);
413
// Sync up the UI for "name", after the value of the "name"
414
// attribute has changed:
415
this.after("nameChange", function(e) {
417
this.nameTag.one(".sd-name").set("innerHTML", name);
420
// Sync up the UI for "personality", after the value of the "personality"
421
// attribute has changed:
422
this.after("personalityChange", function(e) {
423
var personality = e.newVal;
425
var personalityEl = this.nameTag.one(".sd-personality");
426
personalityEl.set("innerHTML", personality);
428
if (personality > 90) {
429
personalityEl.addClass("sd-max");
433
// Sync up the UI for "interests", after the value of the "interests"
434
// attribute has changed:
435
this.after("interestsChange", function(e) {
436
var interests = (e.newVal.length == 0) ?
437
"absolutely nothing" : this.get("interests").join(", ");
438
this.nameTag.one(".sd-interests").set("innerHTML", interests);
444
As seen in the above code, the event type for attribute change events is created by concatenating the attribute name with <code>"Change"</code> (e.g. <code>"availableChange"</code>). Whenever an attribute value is changed through Attribute's <code>set()</code> method, both <em>"on"</em> and <em>"after"</em> subscribers are notified.
447
In the code snippet above, all the subscribers are listening for the <em>"after"</em> moment using the <code>after()</code> subscription method, since they're only interested in being notified after the value has actually changed.
448
However, as we'll see below, the example also shows you how to use an <em>"on"</em> listener, to prevent the attribute state change from occuring under certain conditions.
451
<h3>On vs. After</h3>
453
<p>A single attribute change event has two moments which can be subscribed to, depending on what the subscriber wants to do when notified.</p>
455
<p><strong>on :</strong> Subscribers to the <em>"on"</em> moment, will be notified <em>before</em> any actual state change has occurred. This provides the opportunity to prevent the state change from occurring,
456
using the <code>preventDefault()</code> method of the event facade object passed to the subscriber. If you use <code>get()</code> to retrieve the value of the attribute in an <em>"on"</em> subscriber, you will receive the current, unchanged value.
457
However the event facade provides access to the value which the attribute is being set to, through it's <code>newVal</code> property.</p>
459
<p><strong>after :</strong> Subscribers to the <em>"after"</em> moment, will be notified <em>after</em> the attribute's state has been updated.
460
This provides the opportunity to update state in other parts of your application, in response to a change in the attribute's state.</p>
462
<p>Based on the definition above, <code>after</code> listeners are not invoked if state change is prevented; for example, due to one of the <em>"on"</em> listeners calling <code>preventDefault()</code> on the event object passed to the subscriber.</p>
464
<h3>Having Jane React To John</h3>
466
<p>Aside from the internal listeners set up by the class, in this example <code>jane</code> also sets up two more subscribers. The first is a subscriber, which allows <code>jane</code> to "reconsider" changing the state of her <code>available</code> attribute,
467
under certain conditions. Since she may want to prevent the <code>available</code> attribute from being modified in this case, we use Attribute's <code>on()</code> method to listen for the <em>"on"</em> moment, so that the default behavior can be prevented:</p>
469
<pre class="code prettyprint">// We can also listen before an attribute changes its value, and
470
// decide if we want to allow the state change to occur or not.
472
// Invoking e.preventDefault() stops the state from being updated.
474
// In this case, Jane can change her mind about making herself
475
// unavailable, if John likes saving whales, as long as he doesn't
476
// dig knitting too.
478
jane.on("availableChange", function(e) {
479
var johnsInterests = john.get("interests");
480
var janeAvailable = e.newVal;
482
if (janeAvailable === false && Y.Array.indexOf(johnsInterests, "Saving Whales") !== -1
483
&& Y.Array.indexOf(johnsInterests, "Knitting") == -1 ) {
484
// Reconsider..
490
<p>We also set up an <em>"after"</em> listener on the <code>john</code> instance, which allows <code>jane</code> to update her interests, so she can admit to enjoying "Reading Specifications", if <code>john</code> admits it first:</p>
492
<pre class="code prettyprint">// Consider updating Jane's interests state, after John's interests
493
// state changes...
494
john.after("interestsChange", function(e) {
496
var janesInterests = jane.get("interests"),
498
// Get john's new interests from the attribute change event...
499
johnsInterests = e.newVal,
501
readingSpecs = "Reading Specifications";
503
// If it turns out that John enjoys reading specs, then Jane can admit it too...
504
if (Y.Array.indexOf(johnsInterests, readingSpecs) !== -1) {
505
if(Y.Array.indexOf(janesInterests, readingSpecs) == -1) {
506
janesInterests.push(readingSpecs);
509
// Otherwise, we use Y.Array.reject, provided by the "collection" module,
510
// to remove "Reading Specifications" from jane's interests..
511
janesInterests = Y.Array.reject(janesInterests,
512
function(item){return (item == readingSpecs);});
515
jane.set("interests", janesInterests);
516
jane.set("available", true);
522
<h3>Event Facade</h3>
524
<p>The event object (an instance of <a href="http://yuilibrary.com/yui/docs/api/EventFacade.html">EventFacade</a>) passed to attribute change event subscribers, has the following interesting properties and methods related to attribute management:</p>
528
<dd>The value which the attribute will be set to (in the case of <em>"on"</em> subscribers), or has been set to (in the case of <em>"after"</em> subscribers</dd>
530
<dd>The value which the attribute is currently set to (in the case of <em>"on"</em> subscribers), or was previously set to (in the case of <em>"after"</em> subscribers</dd>
532
<dd>The name of the attribute which is being set</dd>
534
<dd>Attribute also allows you to set nested properties of attributes which have values which are objects through the
535
<code>set</code> method (e.g. <code>o1.set("x.y.z")</code>). This property will contain the path to the property which was changed.</dd>
536
<dt>preventDefault()<dt>
537
<dd>This method can be called in an <em>"on"</em> subscriber to prevent the attribute's value from being updated (the default behavior). Calling this method in an <em>"after"</em> listener has no impact, since the default behavior has already been invoked.</dd>
538
<dt>stopImmediatePropagation()</dt>
539
<dd>This method can be called in <em>"on"</em> or <em>"after"</em> subscribers, and will prevent the rest of the subscriber stack from
540
being invoked, but will not prevent the attribute's value from being updated.</dd>
543
<h2>Complete Example Source</h2>
545
<pre class="code prettyprint"><div id="speeddate">
547
<h1>Communicating With Attribute Events On Speed Dates</h1>
549
<div id="john">
550
<button type="button" class="hi">Hi, I'm John</button>
552
<span class="interests disabled">
554
<label><input type="checkbox" class="interest" value="Sunsets" disabled="disabled"> Sunsets</label>
555
<label><input type="checkbox" class="interest" value="Reading Specifications" disabled="disabled"> Reading Specifications</label>
556
<label><input type="checkbox" class="interest" value="Saving Whales" disabled="disabled"> Saving Whales</label>
557
<label><input type="checkbox" class="interest" value="Knitting" disabled="disabled"> Knitting</label>
559
<div class="shirt"></div>
562
<div id="jane">
563
<button type="button" class="hi" disabled="disabled">Hey, I'm Jane</button>
564
<button type="button" class="movingOn" disabled="disabled">I'm Moving On...</button> <span class="reconsider disabled">(unless he likes whales, and avoids knitting <em class="message"></em>)</span>
565
<div class="shirt"></div>
569
<script type="text/javascript">
571
// Get a new instance of YUI and
572
// load it with the required set of modules
574
YUI().use("collection", "event", "node", "attribute", "substitute", function(Y) {
576
// Setup custom class which we want to add managed attribute support to
578
function SpeedDater(cfg) {
579
// When constructed, setup the initial attributes for the instance, by calling the addAttrs method.
598
this.addAttrs(attrs, cfg);
601
// The HTML template representing the SpeedDater name tag.
602
SpeedDater.NAMETAG = '<div class="sd-nametag"> \
603
<div class="sd-hd">Hello!</div> \
604
<div class="sd-bd"> \
605
<p>I\'m <span class="sd-name">{name}</span> and my PersonalityQuotientIndex is <span class="sd-personality">{personality}</span></p> \
606
<p>I enjoy <span class="sd-interests">{interests}</span>.</p> \
608
<div class="sd-ft sd-availability">{available}</div> \
609
</div>';
611
// Method used to render the visual representation of a SpeedDater object's state (in this case as a name tag)
612
SpeedDater.prototype.applyNameTag = function(where) {
615
name: this.get("name"),
616
available: (this.get("available")) ? "" : "Sorry, moving on",
617
personality: this.get("personality"),
618
interests: (this.get("interests").length == 0) ? "absolutely nothing" : this.get("interests").join(", ")
621
this.nameTag = Y.Node.create(Y.substitute(SpeedDater.NAMETAG, tokens));
622
Y.one(where).appendChild(this.nameTag);
624
this.listenForChanges();
627
// Method used to attach attribute change event listeners, so that the SpeedDater instance
628
// will react to changes in attribute state, and update what's rendered on the page
629
SpeedDater.prototype.listenForChanges = function() {
631
// Sync up the UI for "available", after the value of the "available" attribute has changed:
632
this.after("availableChange", function(e) {
633
var taken = (e.newVal) ? "" : "Oh, is that the time?";
634
this.nameTag.one(".sd-availability").set("innerHTML", taken);
637
// Sync up the UI for "name", after the value of the "name" attribute has changed:
638
this.after("nameChange", function(e) {
640
this.nameTag.one(".sd-name").set("innerHTML", name);
643
// Sync up the UI for "personality", after the value of the "personality" attribute has changed:
644
this.after("personalityChange", function(e) {
645
var personality = e.newVal;
647
var personalityEl = this.nameTag.one(".sd-personality");
648
personalityEl.set("innerHTML", personality);
650
if (personality > 90) {
651
personalityEl.addClass("sd-max");
655
// Sync up the UI for "interests", after the value of the "interests" attribute has changed:
656
this.after("interestsChange", function(e) {
657
var interests = (e.newVal.length == 0) ? "absolutely nothing" : this.get("interests").join(", ");
658
this.nameTag.one(".sd-interests").set("innerHTML", interests);
662
// Augment custom class with Attribute
663
Y.augment(SpeedDater, Y.Attribute);
667
Y.on("click", function() {
671
john = new SpeedDater({
672
name: "John",
675
john.applyNameTag("#john .shirt");
677
Y.one("#jane .hi").set("disabled", false);
680
}, "#john .hi");
682
Y.on("click", function() {
686
jane = new SpeedDater({
687
name: "Jane",
689
interests: ["Popcorn", "Saving Whales"]
691
jane.applyNameTag("#jane .shirt");
693
// Update Jane's interests state, after John's interests state changes...
694
john.after("interestsChange", function(e) {
696
var janesInterests = jane.get("interests"),
697
johnsInterests = e.newVal,
699
readingSpecs = "Reading Specifications";
701
// If it turns out that John enjoys reading specs, then Jane can admit it too...
702
if (Y.Array.indexOf(johnsInterests, readingSpecs) !== -1) {
703
if(Y.Array.indexOf(janesInterests, readingSpecs) == -1) {
704
janesInterests.push(readingSpecs);
707
janesInterests = Y.Array.reject(janesInterests, function(item){return (item == readingSpecs);});
710
jane.set("interests", janesInterests);
711
jane.set("available", true);
713
setMessage("");
716
// We can also listen before an attribute changes its value, and decide if we want to
717
// allow the state change to occur or not. Invoking e.preventDefault() stops the state from
718
// being updated.
720
// In this case, Jane can change her mind about making herself unavailable, if John likes
721
// saving whales, as long as he doesn't dig knitting too.
723
jane.on("availableChange", function(e) {
724
var johnsInterests = john.get("interests");
725
var janeAvailable = e.newVal;
726
if (janeAvailable === false && Y.Array.indexOf(johnsInterests, "Saving Whales") !== -1 && Y.Array.indexOf(johnsInterests, "Knitting") == -1 ) {
727
// Reconsider..
730
setMessage("... which he does");
737
}, "#jane .hi");
739
Y.on("click", function() {
740
jane.set("available", false);
741
}, "#jane .movingOn");
743
// A delegate DOM event listener which will update John's interests, based on the checkbox state, whenever
744
// a checkbox is clicked.
745
Y.delegate("click", function() {
748
Y.Node.all("#john input[type=checkbox].interest").each(function(checkbox) {
749
if (checkbox.get("checked")) {
750
interests.push(checkbox.get("value"));
753
john.set("interests", interests);
755
}, "#john", "input[type=checkbox].interest");
758
// Example helpers...
759
function enableExampleUI() {
760
Y.all("#jane button").set("disabled", false);
761
Y.all("#john button").set("disabled", false);
762
Y.all("#john input").set("disabled", false);
763
Y.one("#john .interests").removeClass("disabled");
764
Y.one("#jane .reconsider").removeClass("disabled");
767
function setMessage(msg) {
768
Y.one("#jane .message").set("innerHTML", msg);
772
</script></pre>
777
<div id="sidebar" class="yui3-u">
781
<div class="sidebox">
783
<h2 class="no-toc">Examples</h2>
787
<ul class="examples">
790
<li data-description="Use the Attribute API to define, set and get attribute values.">
791
<a href="attribute-basic.html">Basic Attribute Configuration</a>
796
<li data-description="Configure attributes to be readOnly or writeOnce.">
797
<a href="attribute-rw.html">Read-Only and Write-Once Attributes</a>
802
<li data-description="How to listen for changes in attribute values.">
803
<a href="attribute-event.html">Attribute Change Events</a>
808
<li data-description="Create a basic SpeedDater class, with Attribute support.">
809
<a href="attribute-basic-speeddate.html">Attribute Based Speed Dating</a>
814
<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.">
815
<a href="attribute-event-speeddate.html">Attribute Event Based Speed Dating</a>
820
<li data-description="Add custom methods to get and set attribute values and provide validation support.">
821
<a href="attribute-getset.html">Attribute Getters, Setters and Validators</a>
835
<script src="../assets/vendor/prettify/prettify-min.js"></script>
836
<script>prettyPrint();</script>