diff --git a/src/attribute/docs/attribute-basic-speeddate.mustache b/src/attribute/docs/attribute-basic-speeddate.mustache new file mode 100644 index 00000000000..98278d261f8 --- /dev/null +++ b/src/attribute/docs/attribute-basic-speeddate.mustache @@ -0,0 +1,345 @@ + + +
+ This example builds on the "Basic Configuration" example, + showing how you can use attribute to model objects in your application. +
+ ++ As with the basic example, it is geared towards users who want to create their own classes from + scratch and add attribute support. In most cases you should consider extending the + `Base` class when you need attribute support, instead + of augmenting Attribute directly. `Base` does the work described + in this example for you, in addition to making it easier for users to extend you class. +
+In this example, we'll create a custom `SpeedDater` class, and show how you can use attributes to manage the state for a Speed Dater. +In the "Attribute Event Based Speed Dating" example we'll modify this example to leverage attribute change events.
+ +As with the other attribute examples, we'll setup our own instance of the YUI object and request the modules we require using the code pattern shown below:
+ +``` + +``` + +The first step in the example is to create the constructor function for our new class, to which we want to add attribute support. In our example, this class is called `SpeedDater`. +We then augment `SpeedDater` with `Y.Attribute`, so that it receives all of `Attribute's` methods, in addition to any it may defined itself:
+ +``` +// Setup custom class which we want to add attribute support to +function SpeedDater(cfg) { + ... +} + +// Augment custom class with Attribute +Y.augment(SpeedDater, Y.Attribute); +``` + ++We can now set up any attributes we need for `SpeedDater` using Attribute's `addAttrs()` method. + +For this example we add 3 attributes - `name`, `personality`, and `available`. + +We provide an default initial `value` for `personality` and `available`, but don't have anything for `name`. + +As mentioned in the basic example, the same object literal we use to provide the initial value for the attribute can also be used to configure attribute properties such as `readOnly` or +`writeOnce`, and to define `getter`, `setter` and `validator` methods for the attribute. For `name`, we configure it to be `writeOnce`, +meaning that it's value can be set once by the user, but not modified after that single set. +
+ ++The default set of attributes which `SpeedDater` will support is passed to `addAttrs` to set up the attributes for each instance during construction. +
+ ++As mentioned previously, if you expect your class to be extended, Base provides a more convenient way for you to define the same attribute configuration statically for your class, so that it can be modified by extended classes. +Base will take care of isolating the static configuration, so that it isn't modified across instances. +
+ +The complete definition for `SpeedDater` is shown below: + +``` +// Setup custom class which we want to +// add managed attribute support to + +function SpeedDater(cfg) { + + // When constructed, setup the initial attributes for the + // instance, by calling the addAttrs method. + + var attrs = { + // Add 3 attributes: name, personality, available + name : { + writeOnce:true + }, + + personality : { + value:50 + }, + + available : { + value:true + } + }; + + this.addAttrs(attrs, cfg); +} + +SpeedDater.prototype.applyNameTag = function(where) { + // Method used to render the visual representation of a + // SpeedDater object's state (in this case as a name tag) +}; + +SpeedDater.prototype.updateNameTag = function() { + // Method used to update the rendered state of SpeedDater in the DOM. +} + +// Template to use form the markup +SpeedDater.NAMETAG = " "; + +// Augment custom class with Attribute +Y.augment(SpeedDater, Y.Attribute); +``` + ++The `addAttrs()` method, in addition to the default attribute configuration, also accepts an object literal (associative array) of name/value pairs which can be +used to over-ride the default initial values of the attributes. This is useful for classes which wish to allow the user the set the value of attributes as part of object +construction, as shown by the use of the `cfg` argument above. +
+ +Now that we have `SpeedDater` defined with the set of attributes it supports, we can create multiple instances of `SpeedDater` defining the initial +attribute state for each instance through the constructor. We can also update the instance's attribute state after construction, using the `get` and +`set` methods defined by Attribute.
+ +We create a first instance, `john`, setting up the intial state using Attribute's constructor configuration object support:
+ +``` +// Set both name and personality during construction +john = new SpeedDater({ + name: "John", + personality: 76.43 +}); +``` + +For the second instance that we create, `jane`, we set the value of the personality attribute, after construction:
+ +``` +// Set name during construction +jane = new SpeedDater({ + name: "Jane" +}); + +// Set personality after construction. The initial value for personality +// in this case, will be the value defined when the attribute was added +// using addAttrs (above) +jane.set("personality", 82); +``` + +We render the current attribute state of each instance to the DOM, using the `applyNameTag()` method defined on SpeedDater's prototype:
+ +``` +// Render the sticker with john's state information to the DOM +john.applyNameTag("#john .shirt"); + +// Render the sticker with jane's state information to the DOM +jane.applySicker("#jane .shirt"); +``` + +Although not directly related to working with Attributes, it's worth taking a look at the `applyNameTag()` and `updateNameTag()` implementations, since they establish +a commonly used pattern.
+ +The `applyNameTag()` method handles rendering the initial visual representation for a speed dater object's state (in this case a name tag). It uses tokens in an HTML "template" string, which it replaces with the values +of attributes using the `substitute()` utility method:
+ +``` +// A template for the markup representing the SpeedDater object.. +SpeedDater.NAMETAG = ' '; +``` + +``` +// A rendering method, used to create the initial markup for the SpeedDater. +SpeedDater.prototype.applyNameTag = function(where) { + + // This example uses an HTML template string containing placeholder + // tokens (SpeedDater.NAMETAG above), and Y.substitute to replace the + // tokens with the current attribute values. + + var tokens = { + // Get attribute values and map them to the tokens in the HTML template string + name: this.get("name"), + available: (this.get("available")) ? "I'm still looking " : "Sorry, I'm taken", + personality: this.get("personality") + }; + + // Create a new Node element, from the token substituted string... + this.nameTag = Y.Node.create(Y.substitute(SpeedDater.NAMETAG, tokens)); + + // ... and append it to the DOM + Y.one(where).appendChild(this.nameTag); +}; +``` + +The `updateNameTag()` method handles updating this visual representation with the current state, when requested by the user
+ +``` +// An update method, used to refresh the rendered content, after +// an attribute value is changed. +SpeedDater.prototype.updateNameTag = function() { + + // Get current attribute values... + var taken = (this.get("available")) ? "I'm still looking " : "Sorry, I'm taken"; + var name = this.get("name"); + var personality = this.get("personality"); + + // Find the corresponding element, and replace the innerHTML with the new value + this.nameTag.one(".sd-name").set("innerHTML", name); + this.nameTag.one(".sd-availability").set("innerHTML", taken); + + var personalityEl = this.nameTag.one(".sd-personality"); + personalityEl.set("innerHTML", personality); + + if (personality > 90) { + personalityEl.addClass("sd-max"); + } +} +``` + +Each instance's state can be now be updated using Attribute's `set` method, and the subsequent call to SpeedDater's `updateNameTag()` method will update the visual representation (the rendered DOM) of the object:
+ +``` +Y.on("click", function() { + + john.set("available", false); + john.updateNameTag(); + +}, "#john .taken"); + +Y.on("click", function() { + + jane.set("available", false); + jane.updateNameTag(); + +}, "#jane .taken"); + +Y.on("click", function() { + + jane.set("personality", 98); + jane.updateNameTag(); + +}, "#jane .upgrade"); +``` + +In the "Attribute Event Based Speed Dating" example, we'll see how we can use Attribute change events to eliminate the need for users to call `updateNameTag()` each time they set an attribute, and have the two instances communicate with one another.
+ +This example provides an introduction to the Attribute utility, showing how you can use it to add attribute support to your own custom classes.
++ It is geared towards users who want to create their own classes from scratch and add Attribute support. In most cases you should consider extending the `Base` class when you need managed attribute support, + instead of augmenting Attribute directly, especially if you expect your class to be extended. `Base` does the work described in this example for you, in addition to making it easier for users to extend you class. +
+In this example, we'll show how you can use the Attribute utility to add managed attributes to your own object classes. Later examples will show how you can configure more advanced attribute properties, and work with attribute change events.
+ +Before we get into attribute, a quick note on how we set up the instance of YUI we'll use for the examples. For all of the attribute examples, we'll setup our own instance of the YUI object and download the files we require on demand using the code pattern shown below:
+ +``` + +``` + +The call to `YUI()` will create and return a new instance of the global YUI object for us to use. However this instance does not yet have all the modules we need for the examples.
+ +To load the modules, we invoke `use()` and pass it the list of modules we'd like populated on our new YUI instance - in this case, `attribute` and `node`. + +The YUI instance will pull down the source files for modules if they don't already exist on the page, plus any or their dependencies. +When the source files are done downloading, the callback function which we pass in as the 3rd argument to `use()`, is invoked. Our custom YUI instance, `Y`, is passed to the callback, populated with the classes which make up the requested modules.
+ +This callback function is where we'll write all our example code. By working inside the callback function, we don't pollute the global namespace and we're also able to download the files we need on demand, rather than have them be on the page up front.
+ +The configuration object passed to `YUI()` when creating the instance is used to specify how (combined, separate, debug, min etc.) we want the files downloaded, and from where. The API documentation for the YUI object, provides more information about the configuration options available.
+ +The first step in the example is to create the constructor function for our new class, to which we want to add attribute support. In our example, this class is called `MyClass`. + +We then augment `MyClass` with `Y.Attribute`, so that it receives all of `Attribute's` methods:
+ +``` +function MyClass(cfg) { + ... +} + +Y.augment(MyClass, Y.Attribute); +``` + +We can now set up any attributes we need for `MyClass` using the `addAttrs` method. For the basic example we add 3 attributes - `foo`,`bar`, and `foobar`, and provide an initial `value` for each. + +The same object literal we use to provide the initial value for the attribute will also be used in the other examples to configure attribute properties such as `readOnly` or `writeOnce`, and define `getter`, `setter` and `validator` methods for the attribute.
+ +In this example, the default set of attributes which `MyClass` will support gets passed to `addAttrs` to set up the attributes for each instance during construction.
+ +The complete definition for `MyClass` is shown below: + +``` +// Setup custom class which we want to add managed attribute support to +function MyClass(cfg) { + + // When constructed, setup the initial attributes for the + // instance, by calling the addAttrs method. + var attrs = { + // Add 3 attributes, foo, bar and foobar + "foo" : { + value:5 + }, + + "bar" : { + value:"Hello World!" + }, + + "foobar" : { + value:true + } + }; + + this.addAttrs(attrs, cfg); +} + +// Augment custom class with Attribute +Y.augment(MyClass, Y.Attribute); +``` + +The `addAttrs` method, in addition to the default attribute configuration, also accepts an object literal (associative array) of name/value pairs which can be used to over-ride the default initial values of the attributes. This is useful for classes which wish to allow the user to set the value of attributes as part of object construction, as shown by the use of the `cfg` argument above.
+ ++As mentioned previously, if you expect your class to be extended, Base provides a more convenient way for you to define the same attribute configuration statically for your class, so that it can be easily modified by extended classes. Base will take care of isolating the static configuration, so that it isn't modified across instances. +
+ +Now that we have `MyClass` defined with a set of attributes it supports, users can get and set attribute values on instances of `MyClass`:
+ +We construct the first instance, `o1`, without setting any initial attribute values in the constructor, but use Attribute's `set()` method to set values after construction:
+ +``` +// Create a new instance, but don't provide any initial attribute values. +var o1 = new MyClass(); + +// Display current values +displayValues(o1, "o1 with default values, set during construction", + "#createo1 .example-out"); + +... + +// Update values, using the "set" method +o1.set("foo", 10); +o1.set("bar", "Hello New World!"); +o1.set("foobar", false); + +displayValues(o1, "o1 values updated using set, after construction", + "#updateo1 .example-out"); +``` + +For the second instance that, `o2` we set the initial values of the attributes, using the constructor configuration argument:
+ +``` +var o2 = new MyClass({ + foo: 7, + bar: "Aloha World!", + foobar: false +}); +``` + +The `displayValues()` method uses Attribute's `get()` method to retrieve the current values of the attributes, to display:
+ +``` +function displayValues(o, title, node) { + var str = + 'Attribute change events are one of the key benefits of using attributes to maintain state for your objects, instead of regular object properties.
+This example refactors the basic "Attribute Based Speed Dating" example 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.
+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, +using the `SpeedDater` class, introduced in the "Attribute Based Speed Dating" example.
+ +We'll create two SpeedDater instances, `jane` and `john`, and use the attribute events they generate both internally (within the class code), to wire up UI refreshes, +and externally, to have `jane` react to changes in the `john`'s state.
+ +We start by setting up the same basic class we created for the "Attribute Based Speed Dating" example, with an additional attribute, `interests`, using the code below:
+ +``` +// Setup custom class which we want to add managed attribute support to +function SpeedDater(cfg) { + // When constructed, setup the initial attributes for the instance, + // by calling the addAttrs method. + var attrs = { + name : { + writeOnce:true + }, + + personality : { + value:50 + }, + + available : { + value:true + }, + + interests : { + value : [] + } + }; + + this.addAttrs(attrs, cfg); +} + +// Augment custom class with Attribute +Y.augment(SpeedDater, Y.Attribute); +``` + +We then create two instances of SpeedDaters, `jane` and `john`:
+ +``` +// Create a john instance... +john = new SpeedDater({ + name: "John", + personality: 78 +}); +// ... and render to the page +john.applyNameTag("#john .shirt"); + +// Create a jane instance... +jane = new SpeedDater({ + name: "Jane", + personality: 82, + interests: ["Popcorn", "Saving Whales"] +}); +jane.applyNameTag("#jane .shirt"); +``` + +For this event based example, we no longer have an `updateNameTag()` 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. +Instead the `SpeedDater` class sets up some internal attribute change event listeners in its `listenForChanges()` method, which will refresh the UI for a particular attribute, each time its value is modified:
+ +``` +// Method used to attach attribute change event listeners, so that +// the SpeedDater instance will react to changes in attribute state, +// and update what's rendered on the page +SpeedDater.prototype.listenForChanges = function() { + + // Sync up the UI for "available", after the value of the "available" + // attribute has changed: + this.after("availableChange", function(e) { + var taken = (e.newVal) ? "" : "Oh, is that the time?"; + this.nameTag.one(".sd-availability").set("innerHTML", taken); + }); + + // Sync up the UI for "name", after the value of the "name" + // attribute has changed: + this.after("nameChange", function(e) { + var name = e.newVal; + this.nameTag.one(".sd-name").set("innerHTML", name); + }); + + // Sync up the UI for "personality", after the value of the "personality" + // attribute has changed: + this.after("personalityChange", function(e) { + var personality = e.newVal; + + var personalityEl = this.nameTag.one(".sd-personality"); + personalityEl.set("innerHTML", personality); + + if (personality > 90) { + personalityEl.addClass("sd-max"); + } + }); + + // Sync up the UI for "interests", after the value of the "interests" + // attribute has changed: + this.after("interestsChange", function(e) { + var interests = (e.newVal.length == 0) ? + "absolutely nothing" : this.get("interests").join(", "); + this.nameTag.one(".sd-interests").set("innerHTML", interests); + }); +}; +``` + ++As seen in the above code, the event type for attribute change events is created by concatenating the attribute name with `"Change"` (e.g. `"availableChange"`). Whenever an attribute value is changed through Attribute's `set()` method, both "on" and "after" subscribers are notified. +
++In the code snippet above, all the subscribers are listening for the "after" moment using the `after()` subscription method, since they're only interested in being notified after the value has actually changed. +However, as we'll see below, the example also shows you how to use an "on" listener, to prevent the attribute state change from occuring under certain conditions. +
+ +A single attribute change event has two moments which can be subscribed to, depending on what the subscriber wants to do when notified.
+ +on : Subscribers to the "on" moment, will be notified before any actual state change has occurred. This provides the opportunity to prevent the state change from occurring, +using the `preventDefault()` method of the event facade object passed to the subscriber. If you use `get()` to retrieve the value of the attribute in an "on" subscriber, you will receive the current, unchanged value. +However the event facade provides access to the value which the attribute is being set to, through it's `newVal` property.
+ +after : Subscribers to the "after" moment, will be notified after the attribute's state has been updated. +This provides the opportunity to update state in other parts of your application, in response to a change in the attribute's state.
+ +Based on the definition above, `after` listeners are not invoked if state change is prevented; for example, due to one of the "on" listeners calling `preventDefault()` on the event object passed to the subscriber.
+ +Aside from the internal listeners set up by the class, in this example `jane` also sets up two more subscribers. The first is a subscriber, which allows `jane` to "reconsider" changing the state of her `available` attribute, +under certain conditions. Since she may want to prevent the `available` attribute from being modified in this case, we use Attribute's `on()` method to listen for the "on" moment, so that the default behavior can be prevented:
+ +``` +// We can also listen before an attribute changes its value, and +// decide if we want to allow the state change to occur or not. + +// Invoking e.preventDefault() stops the state from being updated. + +// In this case, Jane can change her mind about making herself +// unavailable, if John likes saving whales, as long as he doesn't +// dig knitting too. + +jane.on("availableChange", function(e) { + var johnsInterests = john.get("interests"); + var janeAvailable = e.newVal; + + if (janeAvailable === false && Y.Array.indexOf(johnsInterests, "Saving Whales") !== -1 + && Y.Array.indexOf(johnsInterests, "Knitting") == -1 ) { + // Reconsider.. + e.preventDefault(); + }; +}); +``` + +We also set up an "after" listener on the `john` instance, which allows `jane` to update her interests, so she can admit to enjoying "Reading Specifications", if `john` admits it first:
+ +``` +// Consider updating Jane's interests state, after John's interests +// state changes... +john.after("interestsChange", function(e) { + + var janesInterests = jane.get("interests"), + + // Get john's new interests from the attribute change event... + johnsInterests = e.newVal, + + readingSpecs = "Reading Specifications"; + + // If it turns out that John enjoys reading specs, then Jane can admit it too... + if (Y.Array.indexOf(johnsInterests, readingSpecs) !== -1) { + if(Y.Array.indexOf(janesInterests, readingSpecs) == -1) { + janesInterests.push(readingSpecs); + } + } else { + // Otherwise, we use Y.Array.reject, provided by the "collection" module, + // to remove "Reading Specifications" from jane's interests.. + janesInterests = Y.Array.reject(janesInterests, + function(item){return (item == readingSpecs);}); + } + + jane.set("interests", janesInterests); + jane.set("available", true); + + ... +}); +``` + +The event object (an instance of EventFacade) passed to attribute change event subscribers, has the following interesting properties and methods related to attribute management:
+ +Attribute change events are one of the key benefits of using attributes to maintain state for your objects, instead of regular object properties. This example shows how you can listen for attribute change events and work with the event payload they receive.
+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.
+ +We start by setting up the same custom class we created for the basic example with 3 attributes `foo`, `bar` and `foobar`, using the code below:
+ +``` +YUI().use("attribute", "node", function(Y) { + + // Setup a custom class with attribute support + function MyClass(cfg) { + + // Setup attribute configuration + var attrs = { + "foo" : { + value:5 + }, + + "bar" : { + value:"Hello World!" + }, + + "foobar" : { + value:true + } + }; + + this.addAttrs(attrs, cfg); + } + + Y.augment(MyClass, Y.Attribute); + +}); +``` + +Once we have an instance of the custom class, we can use the `on` and `after` methods provided by Attribute, to listen for changes in the value of each of the attributes:
+ +``` +var o1 = new MyClass(); + +... + +// Event Listners +o1.after("fooChange", function(e) { + displayEvent(e, "After fooChange"); + currentValSpan.set("innerHTML", Y.Escape.html(e.newVal+"")); +}); + +o1.after("barChange", function(e) { + displayEvent(e, "After barChange"); + currentValSpan.set("innerHTML", Y.Escape.html(e.newVal+"")); +}); + +o1.on("foobarChange", function(e) { + + if (preventFoobarChk.get("checked")) { + + // Calling preventDefault, in an "on" listener + // will prevent the attribute change from occuring + // and the after listener being called. + + e.preventDefault(); + displayEvent(null, "On foobarChange (prevented)"); + } + +}); + +o1.after("foobarChange", function(e) { + + // This foobar after listener will not get called, + // if we end up preventing default in the "on" + // listener above. + + displayEvent(e, "After foobarChange"); + currentValSpan.set("innerHTML", Y.Escape.html(e.newVal+"")); +}); +``` + +As seen in the above code, the event type for attribute change events is created by concatenating the attribute name with `"Change"` (e.g. `"fooChange"`), and this event type is used for both the `on` and `after` subscription methods. Whenever an attribute value is changed through Attribute's `set` method, both "on" and "after" subscribers are notified.
+ +on : Subscribers to the "on" moment, will be notified before any actual state change has occurred. This provides the opportunity to prevent the state change from occurring, using the `preventDefault` method of the event facade object passed to the subscriber. If you use `get` to retrieve the value of the attribute in an "on" subscriber, you will receive the current, unchanged value. However the event facade provides access to the value which the attribute is being set to, through it's `newVal` property.
+ +after : Subscribers to the "after" moment, will be notified after the attribute's state has been updated. This provides the opportunity to update state in other parts of your application, in response to a change in the attribute's state.
+ +Based on the definition above, `after` listeners are not invoked if state change is prevented, for example, due to one of the `on` listeners calling `preventDefault` on the event object, as is done in the `on` listener for the `foobar` attribute:
+ +``` +o1.on("foobarChange", function(event) { + + // Calling preventDefault, in an "on" listener + // will prevent the attribute change from occurring + // and prevent the after listeners from being called + displayEvent(event, "on foobarChange (change prevented)"); + + event.preventDefault(); +}); +``` + +For primitive values (non-Object values), the `after` listeners will also not be invoked if there is no change in the actual value of the attribute. That is, if the new value of the attribute is the same as the current value (based on the identity operator, `===`), the `after` listeners will not be notified because there is no change in state. You can see this, by setting an attribute to the same value twice in a row.
+ +The event object (an instance of EventFacade) passed to attribute change event subscribers, has the following interesting properties and methods related to attribute management:
+ +The "Attribute Event Based Speed Dating" example provides a look at how you can leverage attribute change events in your applications, to decouple logic both within your class, and when interacting with other objects.
+ +The "Basic Attribute Configuration" example 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.
+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.
+ +You can also define a `validator` function for each attribute, which is used to validate the final value before it gets stored.
+ +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.
+ +In this example, we'll set up a custom `Box` class representing a positionable element, with `x`, `y` and `xy` attributes.
+ +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. +However we don't want to store the actual values in the `x` and `y` attributes, to avoid having to constantly synchronize all three. + +The `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:
+ +``` +// Setup a custom class with attribute support +function Box(cfg) { + + ... + + // Attribute configuration + var attrs = { + + "parent" : { + value: null + }, + + "x" : { + setter: function(val, name) { + // Pass through x value to xy + this.set("xy", [parseInt(val), this.get("y")]); + }, + + getter: function(val, name) { + // Get x value from xy + return this.get("xy")[0]; + } + }, + + "y" : { + setter: function(val, name) { + // Pass through y value to xy + this.set("xy", [this.get("x"), parseInt(val)]); + }, + + getter: function() { + // Get y value from xy + return this.get("xy")[1]; + } + }, + + "xy" : { + // Actual stored xy co-ordinates + value: [0, 0], + + setter: function(val, name) { + // Constrain XY value to the parent element. + + // Returns the constrained xy value, which will + // be the final value stored. + return this.constrain(val); + }, + + validator: function(val, name) { + // Ensure we only store a valid data value + return (Y.Lang.isArray(val) && + val.length == 2 && + Y.Lang.isNumber(val[0]) && Y.Lang.isNumber(val[1])); + } + }, + + ... + + this.addAttrs(attrs, cfg); + + ... +} +``` + +The `validator` function for `xy` ensures that only valid values finally end up being stored.
+ +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:
+ +``` +// Get min, max unconstrained values for X. +// Using Math.round to handle FF3's sub-pixel region values +Box.prototype.getXConstraints = function() { + var parentRegion = this.get("parent").get("region"); + return [Math.round(parentRegion.left + Box.BUFFER), + Math.round(parentRegion.right - this._node.get("offsetWidth") - Box.BUFFER)]; +}; + +// Get min, max unconstrained values for Y. +// Using Math.round to handle FF3's sub-pixel region values +Box.prototype.getYConstraints = function() { + var parentRegion = this.get("parent").get("region"); + return [Math.round(parentRegion.top + Box.BUFFER), + Math.round(parentRegion.bottom - this._node.get("offsetHeight") - Box.BUFFER)]; +}; + +// Constrains given x,y values +Box.prototype.constrain = function(val) { + + // If the X value places the box outside it's parent, + // modify it's value to place the box inside it's parent. + + var xConstraints = this.getXConstraints(); + + if (val[0] < xConstraints[0]) { + val[0] = xConstraints[0]; + } else { + if (val[0] > xConstraints[1]) { + val[0] = xConstraints[1]; + } + } + + // If the Y value places the box outside it's parent, + // modify it's value to place the box inside it's parent. + + var yConstraints = this.getYConstraints(); + + if (val[1] < yConstraints[0]) { + val[1] = yConstraints[0]; + } else { + if (val[1] > yConstraints[1]) { + val[1] = yConstraints[1]; + } + } + + return val; +}; +``` + +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`.
+ +The `Box` class also has a `color` attribute which also has a `getter` and `validator` functions defined:
+ +``` +... +"color" : { + value: "olive", + + getter: function(val, name) { + if (val) { + return Y.Color.toHex(val); + } else { + return null; + } + }, + + validator: function(val, name) { + return (Y.Color.re_RGB.test(val) || Y.Color.re_hex.test(val) + || Y.Color.KEYWORDS[val]); + } +} +... +``` + +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.
+ +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:
+ +``` +// Bind listeners for attribute change events +Box.prototype._bind = function() { + + // Reflect any changes in xy, to the rendered Node + this.after("xyChange", this._syncXY); + + // Reflect any changes in color, to the rendered Node + // and output the color value received from get + this.after("colorChange", this._syncColor); + + // Append the rendered node to the parent provided + this.after("parentChange", this._syncParent); + +}; +``` + +Since only `xy` stores the final co-ordinates, we don't need to monitor the `x` and `y` attributes individually for changes.
+ +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:
+ +``` +// Set references to form controls +var xTxt = Y.one("#x"); +var yTxt = Y.one("#y"); +var colorTxt = Y.one("#color"); + +// Use event delegation for the action button clicks +Y.delegate("click", function(e) { + + // Get Node target from the event object. + + // We already know it's a button which has an action because + // of our selector (button.action), so all we need to do is + // route it based on the id. + var id = e.currentTarget.get("id"); + + switch (id) { + case "setXY": + box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]); + break; + case "setX": + box.set("x", parseInt(xTxt.get("value"))); + break; + case "setY": + box.set("y", parseInt(yTxt.get("value"))); + break; + case "setColor": + box.set("color", Y.Lang.trim(colorTxt.get("value"))); + break; + case "setAll": + box.set("xy", [parseInt(xTxt.get("value")), parseInt(yTxt.get("value"))]); + box.set("color", Y.Lang.trim(colorTxt.get("value"))); + break; + case "getAll": + getAll(); + break; + default: + break; + } + +}, "#attrs", "click", "button.action"); +``` + +Rather than attach individual listeners to each button, the above code uses YUI 3's `delegate` support, to listen for clicks from `buttons` with an `action` class which bubble up to the `attrs` element.
+The delegate listener uses the Event Facade 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 Node facade when dealing with references to HTML elements (e.g. `xTxt, yTxt, colorTxt`).
+ +Attributes can be configured to be `readOnly`, stopping them from being modified by the end user, or `writeOnce` allowing them to be set by the end user, but only once. This example demonstrates how to setup attributes for your class as `readOnly` or `writeOnce` attributes, and shows how their behavior differs when the end user attempts to set their values.
+Attribute supports the ability to configure attributes to be `readOnly` or `writeOnce`. `readOnly` attributes cannot be set by the end user, either through initial values passed to the constructor, or by invoking the `set` method. `writeOnce` attributes on the other hand, can be set by the user, but only once, either during initialization or through a call to `set`. Once a value is established for a `writeOnce` attribute, it cannot be reset to another value by the user.
+ +This example sets up a custom class, `MyClass`, with two attributes, `foo` and `bar`. `foo` is configured to be a `readOnly` attribute, and `bar` is configured to be a `writeOnce` attribute:
+ +``` +// Setup a custom class with attribute support +function MyClass(cfg) { + + // Attribute configuration + var attrs = { + "foo" : { + value: "Default Foo", + readOnly: true + }, + + "bar" : { + value: "Default Bar", + writeOnce: true + } + } + + this.addAttrs(attrs, cfg); +} +``` + +We first attempt to set values for both attributes in the constructor (used to intialize the attributes) and see that only `bar`, the `writeOnce` attribute, gets set to the user provided value:
+ +``` +var fooVal = Y.one("#writeInitial .fooVal").get("value"); +var barVal = Y.one("#writeInitial .barVal").get("value"); + +o1 = new MyClass({ + foo: fooVal, + bar: barVal +}); + +displayValues(o1, "Attempt to set initial values in constructor", + "#writeInitial .example-out"); +``` + +We then attempt to set values for both attributes again, using `set`, and see that niether of the values are modified:
+ +``` +var fooVal = Y.one("#writeAgain .fooVal").get("value"); +var barVal = Y.one("#writeAgain .barVal").get("value"); + +// Attempt to reset values: +o1.set("foo", fooVal); +o1.set("bar", barVal); + +displayValues(o1, "Attempt to set values again, using set", + "#writeAgain .example-out"); +``` + +Although the user cannot update the value of `readOnly` attributes, it maybe neccessary for the host object to update it's value internally. The example shows how this can be done, using the private `_set` property on the host:
+ +``` +MyClass.prototype.doSomething = function(val) { + // ... Do something which requires + // MyClass to change the value + // of foo ... + + // Host code can reset value of + // readOnly attributes interally, + // by working with the private _set + // property + + this._set("foo", val); +}; + +... + +var val = Y.one("#writeInternally .fooVal").get("value"); + +// Call method, which sets foo internally... +o1.doSomething(val); + +displayValues(o1, "Set value of foo (readOnly) interally", + "#writeInternally .example-out"); +``` + + ++ : + + +
+:
+:
+rgb(255,0,0)
, #ff0000
, red
].+ + + + +
++ + + + +
++ + + +
+Construct o1, setting initial values for both foo and bar in the constructor:
+ + + + +Try setting values again, after they've been set once:
+ + + + +Call a MyClass method, which sets foo internally (using _set):
+ + + +-The Plugin landing page discusses plugin development in detail. The Widget IO Plugin, Overlay IO Plugin and -Overlay Animation Plugin examples also provide a concrete look at plugin development. +The Plugin landing page discusses plugin development in detail. The Widget IO Plugin, Overlay IO Plugin and +Overlay Animation Plugin examples also provide a concrete look at plugin development.
@@ -377,4 +377,4 @@ wp.lock();
See Base's `API documentation` for more details on the `build`, `create` and `mix` methods.
-The "MyExtension" template file provides a starting point for you to create your own extensions.
\ No newline at end of file +The "MyExtension" template file provides a starting point for you to create your own extensions.
diff --git a/src/intl/docs/assets/translator/lang/translator.js b/src/intl/docs/assets/translator/lang/translator.js new file mode 100644 index 00000000000..55241aed858 --- /dev/null +++ b/src/intl/docs/assets/translator/lang/translator.js @@ -0,0 +1,15 @@ +YUI.add("lang/translator", function(Y) { + + Y.Intl.add( + + "translator", // Associated Module + "", // Root + + { // Translated String Key/Value Pairs + hello:"Hello", + goodbye: "Goodbye" + } + + ); + +}, "3.1.0"); diff --git a/src/intl/docs/assets/translator/lang/translator_en.js b/src/intl/docs/assets/translator/lang/translator_en.js new file mode 100644 index 00000000000..74f38c4fbd1 --- /dev/null +++ b/src/intl/docs/assets/translator/lang/translator_en.js @@ -0,0 +1,15 @@ +YUI.add("lang/translator_en", function(Y) { + + Y.Intl.add( + + "translator", // Associated Module + "en", // BCP 47 Language Tag + + { // Translated String Key/Value Pairs + hello:"Hello", + goodbye: "Goodbye" + } + + ); + +}, "3.1.0"); diff --git a/src/intl/docs/assets/translator/lang/translator_es.js b/src/intl/docs/assets/translator/lang/translator_es.js new file mode 100644 index 00000000000..3f63e879e44 --- /dev/null +++ b/src/intl/docs/assets/translator/lang/translator_es.js @@ -0,0 +1,15 @@ +YUI.add("lang/translator_es", function(Y) { + + Y.Intl.add( + + "translator", // Associated Module + "es", // BCP 47 Language Tag + + { // Translated String Key/Value Pairs + hello:"Hola", + goodbye: "Adiós" + } + + ); + +}, "3.1.0"); diff --git a/src/intl/docs/assets/translator/lang/translator_fr.js b/src/intl/docs/assets/translator/lang/translator_fr.js new file mode 100644 index 00000000000..cd159de33ea --- /dev/null +++ b/src/intl/docs/assets/translator/lang/translator_fr.js @@ -0,0 +1,15 @@ +YUI.add("lang/translator_fr", function(Y) { + + Y.Intl.add( + + "translator", // Associated Module + "fr", // BCP 47 Language Tag + + { // Translated String Key/Value Pairs + hello:"Bonjour", + goodbye: "Au revoir" + } + + ); + +}, "3.1.0"); diff --git a/src/intl/docs/assets/translator/translator.js b/src/intl/docs/assets/translator/translator.js new file mode 100644 index 00000000000..0f0fc56d705 --- /dev/null +++ b/src/intl/docs/assets/translator/translator.js @@ -0,0 +1,22 @@ +YUI.add("translator", function(Y) { + + function Translator() { + this._strs = Y.Intl.get("translator"); + } + + Translator.prototype = { + constructor : Translator, + + hi : function() { + return this._strs.hello; + }, + + bye : function() { + return this._strs.goodbye; + } + } + + Y.Translator = Translator; + +}, "3.1.0", {lang: ["en", "fr", "es"]}); + diff --git a/src/intl/docs/component.json b/src/intl/docs/component.json index 50d7bab9911..267c2f12060 100644 --- a/src/intl/docs/component.json +++ b/src/intl/docs/component.json @@ -5,5 +5,17 @@ "author" : "sdesai", "tags": ["intl", "utility"], - "use" : ["intl"] + "use" : ["intl"], + + "examples": [ + { + "name" : "intl-basic", + "displayName": "Language Resource Bundles", + "description": "How to create components which use language resource bundles.", + "modules" : ["intl"], + "tags" : ["intl", "loader"], + + "hideTableOfContents": true + } + ] } diff --git a/src/intl/docs/index.mustache b/src/intl/docs/index.mustache index 55b337239d2..4bc17d9daab 100644 --- a/src/intl/docs/index.mustache +++ b/src/intl/docs/index.mustache @@ -191,7 +191,7 @@ The Internationalization utility offers some low-level support for this: `intl:langChange` events to find out about language changes. -The Formatting
+ The Formatting
Dates Using Language Resource Bundles example shows how to use these interfaces. This example shows how you can define language resource bundles for your custom module implementations; it also illustrates how YUI Loader can load the correct bundle based on the language you've chosen for your YUI instance. We use Loader's groups support to add a custom module called "translator" under the group "myapp". We set this configuration using YUI_config, so it's applied to all our YUI instances. The "lang" property in the module's metadata specifies
+which set of languages it supports. The language resource bundles for any module follows the pattern below: The `"lang/[for-module]_[lang]"` passed to `YUI.add` is the default module name used for language resource bundles, and the `Y.Intl.add` method is used to register the string name/value pair hash for a given module and language combination.
+
+ YUI Builder will handle the creation of the boiler plate code shown above, from the raw language files found in the module's `src/[module]/lang` subdirectory. The raw files under the `lang` directory contain just the string name/value pairs for each language. As the component developer, the only thing you need to do is specify the `component.lang` property in the build properties file: Provide the raw string name/value pairs in the `src/[component]/lang` subdirectory in your component's source area: And whenever you build your component code, the language resource bundles will be built and deployed too. You can checkout the YUI 3 Source Code and see the source code and build configuration files for the "console" and "datatype-date-format" modules to see a concrete example of this. The Translator class implementation gets access to the localized strings by using `Y.Intl.get`, passing in the module name whose strings we need access to: We specify the language to use for each instance, using the "lang" configuration property for the instance. As mentioned above, the `datatype` module (specifically the `datatype-date-format` module) and `console` are shipped with language resource bundles. Datatype ships with over 50 different languages supported, and Console ships with en and es language resource bundles, mainly as a demonstration of how language resource bundles are defined and used for Widget development. Overlay is a positionable and stackable widget, which also provides support for the standard module format layout, with a header, body and footer section.
The overlay is built by extending the `Widget` class and adding the `WidgetPosition`, `WidgetStack`, `WidgetPositionAlign`, `WidgetPositionConstrain` and `WidgetStdMod` extensions,
- and doesn't actually need to add any implementation code of its own. The "Creating Custom Widget Classes" example shows how you can use these extensions to build classes which mix and match some of the above features.Internationalizing Modules
diff --git a/src/intl/docs/intl-basic.mustache b/src/intl/docs/intl-basic.mustache
new file mode 100644
index 00000000000..50a5ee8a425
--- /dev/null
+++ b/src/intl/docs/intl-basic.mustache
@@ -0,0 +1,163 @@
+
+
+Defining your Custom Module
+
+What Language Resource Bundles Look Like
+
+Generating Language Resource Bundles
+
+Accessing Localized Resources In Your Class
+
+Specifying the Language for an Instance
+
+ An English instance
+
+```
+YUI({
+ lang:"en"
+}).use("node-base", "translator", function(Y) {
+ var translator = new Y.Translator(),
+ out = Y.one("#out");
+
+ say("Speaking in: " + Y.Intl.getLang("translator"), out);
+ say(translator.hi(), out);
+ say(translator.bye(), out);
+});
+```
+
+A French YUI Instance
+
+```
+YUI({
+ lang:"fr"
+}).use("node-base", "translator", function(Y) {
+ ...
+});
+```
+
+A Spanish YUI Instance
+
+```
+YUI({
+ lang:"es"
+}).use("node-base", "translator", function(Y) {
+ ...
+});
+```
+
+Modules Shipping With Language Resource Bundles
+
+Complete Example Source
+```
+{{>intl-basic-source}}
+```
diff --git a/src/intl/docs/partials/intl-basic-source.mustache b/src/intl/docs/partials/intl-basic-source.mustache
new file mode 100644
index 00000000000..59f319ca7f7
--- /dev/null
+++ b/src/intl/docs/partials/intl-basic-source.mustache
@@ -0,0 +1,58 @@
+
+
+
\ No newline at end of file
diff --git a/src/overlay/docs/assets/img/ajax-loader.gif b/src/overlay/docs/assets/img/ajax-loader.gif
new file mode 100644
index 00000000000..fe2cd23b3a3
Binary files /dev/null and b/src/overlay/docs/assets/img/ajax-loader.gif differ
diff --git a/src/overlay/docs/component.json b/src/overlay/docs/component.json
index e418e42e705..397369d0cf7 100644
--- a/src/overlay/docs/component.json
+++ b/src/overlay/docs/component.json
@@ -5,5 +5,71 @@
"author" : "sdesai",
"tags": ["overlay", "widget"],
- "use" : ["overlay"]
+ "use" : ["overlay"],
+
+ "examples" : [
+ {
+ "name" : "overlay-xy",
+ "displayName": "Basic XY Positioning",
+ "description": "Shows how to instantiate a basic Overlay instance, and use the Overlay's basic XY positioning support.",
+ "modules" : ["overlay"],
+ "tags" : ["overlay"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-align",
+ "displayName": "Alignment Support",
+ "description": "Shows how to use the Overlay's XY alignment support, to align the Overlay relative to another element, or to the viewport.",
+ "modules" : ["overlay"],
+ "tags" : ["overlay"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-stack",
+ "displayName": "Stack Support",
+ "description": "Shows how to use the Overlay's zindex and shim support when positioning Overlays above other elements on the page.",
+ "modules" : ["overlay"],
+ "tags" : ["overlay"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-stdmod",
+ "displayName": "Standard Module Support",
+ "description": "Shows how to modify content in the Overlay's header, body and footer sections.",
+ "modules" : ["overlay"],
+ "tags" : ["overlay"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-constrain",
+ "displayName": "Constrain Support",
+ "description": "Shows how to use Overlay's constrainment support, to limit the XY value which can be set for an Overlay.",
+ "modules" : ["overlay", "slider"],
+ "tags" : ["overlay", "slider"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-io-plugin",
+ "displayName": "IO Plugin",
+ "description": "Shows how to create a simple plugin to retrieve content for the Overlay using the io utility.",
+ "modules" : ["overlay", "io", "plugin"],
+ "tags" : ["overlay", "io", "plugin"],
+
+ "hideTableOfContents": true
+ },
+ {
+ "name" : "overlay-anim-plugin",
+ "displayName": "Animation Plugin",
+ "description": "Shows how to create a simple plugin to animate the Overlay's movement and visibility.",
+ "modules" : ["overlay", "anim", "plugin"],
+ "tags" : ["overlay", "anim", "plugin"],
+
+ "hideTableOfContents": true
+ }
+ ]
}
diff --git a/src/overlay/docs/index.mustache b/src/overlay/docs/index.mustache
index 8aba825de1d..82210927928 100644
--- a/src/overlay/docs/index.mustache
+++ b/src/overlay/docs/index.mustache
@@ -2,7 +2,7 @@
The Basic XY Positioning example shows basic positioning in action.
+The Basic XY Positioning example shows basic positioning in action.
The "Alignment Support" example shows aligned positioning support in action.
+The "Alignment Support" example shows aligned positioning support in action.
Note that currently alignment/centering is not maintained when the viewport is scrolled, window resized etc. Support to re-align the overlay on a default and custom set of trigger events will be provided in a future release.
@@ -261,7 +261,7 @@ overlay.set("constrain", "#constrainingNode"); overlay.set("constrain", true); ``` -The "Constrain Support" example shows constrained positioning in action.
+The "Constrain Support" example shows constrained positioning in action.
The "Stack Support" example creates a simple "bringToTop" implementation based on the `zIndex` attribute. +
The "Stack Support" example creates a simple "bringToTop" implementation based on the `zIndex` attribute. This support will be provided as part of Overlay itself (or more precisely, as part of `WidgetStack`) in a future release.
The content change events mentioned above, will be fired when content is set through the `setStdModContent` method just as they would when setting the content using the attribute.
-The Standard Module example provides a way to exercise the above content attributes and methods.
+The Standard Module example provides a way to exercise the above content attributes and methods.
This example shows how you can use Overlay's extended positioning support to align or center the overlay either in the viewport or relative to another node on the page. You can specify any one of 9 points (top-left, top-right, bottom-left, bottom-right, top-center, bottom-center, left-center, right-center, center) to align on both the Overlay and the node/viewport it is being aligned to.
+