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. +

+
+ +
+ {{>attribute-basic-speeddate-source}} +
+ +

Setting Up a SpeedDater 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.

+ +

Creating A YUI Instance

+ +

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:

+ +``` + +``` + +

Defining The SpeedDater Class

+ +

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); +``` + +

Adding Attributes

+ +

+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 = "
Hello!
...
"; + +// 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. +

+ +

Using Attributes

+ +

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 = '
\ +
Hello!
\ +
I\'m {name} \ + and my PersonalityQuotientIndex is \ + {personality} \ +
\ +
{available}
\ +
'; +``` + +``` +// 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.

+ +

Complete Example Source

+ +``` +{{>attribute-basic-speeddate-source}} +``` diff --git a/src/attribute/docs/attribute-basic.mustache b/src/attribute/docs/attribute-basic.mustache new file mode 100644 index 00000000000..0270348ef33 --- /dev/null +++ b/src/attribute/docs/attribute-basic.mustache @@ -0,0 +1,191 @@ + + +
+

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. +

+
+ +
+ {{>attribute-basic-source}} +
+ +

Setting Up Your Own Class To Use Attribute

+ +

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.

+ +

Creating A YUI Instance

+ +

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.

+ +

Defining Your Custom Class

+ +

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); +``` + +

Adding Attributes

+ +

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. +

+ +

Using Attributes

+ +

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 = + '
' + + title + + ':
'; + + // Use the Y.one() method to get the first element which + // matches the selector passed in, to output the string to... + Y.one(node).set("innerHTML", str); +} +``` + +

Complete Example Source

+ +``` +{{>attribute-basic-source}} +``` diff --git a/src/attribute/docs/attribute-event-speeddate.mustache b/src/attribute/docs/attribute-event-speeddate.mustache new file mode 100644 index 00000000000..85ee165f3fb --- /dev/null +++ b/src/attribute/docs/attribute-event-speeddate.mustache @@ -0,0 +1,305 @@ + + +
+

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.

+
+ +
+ {{>attribute-event-speeddate-source}} +
+ +

Listening For Attribute Change Events

+ +

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.

+ +

Setting Up The SpeedDater Class With Attribute

+ +

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"); +``` + +

Registering Event Listeners

+ +

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. +

+ +

On vs. After

+ +

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.

+ +

Having Jane React To John

+ +

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); + + ... +}); +``` + +

Event Facade

+ +

The event object (an instance of EventFacade) passed to attribute change event subscribers, has the following interesting properties and methods related to attribute management:

+ +
+
newVal
+
The value which the attribute will be set to (in the case of "on" subscribers), or has been set to (in the case of "after" subscribers
+
prevVal
+
The value which the attribute is currently set to (in the case of "on" subscribers), or was previously set to (in the case of "after" subscribers
+
attrName
+
The name of the attribute which is being set
+
subAttrName
+
Attribute also allows you to set nested properties of attributes which have values which are objects through the + `set` method (e.g. `o1.set("x.y.z")`). This property will contain the path to the property which was changed.
+
preventDefault()
+
This method can be called in an "on" subscriber to prevent the attribute's value from being updated (the default behavior). Calling this method in an "after" listener has no impact, since the default behavior has already been invoked.
+
stopImmediatePropagation()
+
This method can be called in "on" or "after" subscribers, and will prevent the rest of the subscriber stack from + being invoked, but will not prevent the attribute's value from being updated.
+
+ +

Complete Example Source

+ +``` +{{>attribute-event-speeddate-source}} +``` diff --git a/src/attribute/docs/attribute-event.mustache b/src/attribute/docs/attribute-event.mustache new file mode 100644 index 00000000000..73c5d6230f1 --- /dev/null +++ b/src/attribute/docs/attribute-event.mustache @@ -0,0 +1,209 @@ + + +
+

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.

+
+ +
+ {{>attribute-event-source}} +
+ +

Listening For Attribute Change Events

+ +

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.

+ +

Setting Up A Custom Class With Attribute

+ +

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); + +}); +``` + +

Registering Event Listeners

+ +

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 vs. After

+ +

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.

+ +

Event Facade

+ +

The event object (an instance of EventFacade) passed to attribute change event subscribers, has the following interesting properties and methods related to attribute management:

+ +
+
newVal
+
The value which the attribute will be set to (in the case of "on" subscribers), or has been set to (in the case of "after" subscribers
+
prevVal
+
The value which the attribute is currently set to (in the case of "on" subscribers), or was previously set to (in the case of "after" subscribers
+
attrName
+
The name of the attribute which is being set
+
subAttrName
+
Attribute also allows you to set nested properties of attributes which have values which are objects through the + `set` method (e.g. `o1.set("x.y.z")`). This property will contain the path to the property which was changed.
+
preventDefault()
+
This method can be called in an "on" subscriber to prevent the attribute's value from being updated (the default behavior). Calling this method in an "after" listener has no impact, since the default behavior has already been invoked.
+
stopImmediatePropagation()
+
This method can be called in "on" or "after" subscribers, and will prevent the rest of the subscriber stack from + being invoked, but will not prevent the attribute's value from being updated.
+
+ +

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.

+ +

Complete Example Source

+ +``` +{{>attribute-event-source}} +``` diff --git a/src/attribute/docs/attribute-getset.mustache b/src/attribute/docs/attribute-getset.mustache new file mode 100644 index 00000000000..c3b9c4f947a --- /dev/null +++ b/src/attribute/docs/attribute-getset.mustache @@ -0,0 +1,321 @@ + + +
+

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-getset-source}} +
+ +

Getter, Setter And Validator Functions

+ +

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.

+ +

Creating The Box Class - The X, Y And XY 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 Color Attribute - Normalizing Stored Values Through Get

+ +

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.

+ +

Syncing Changes Using Attribute Change Events

+ +

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.

+ +

DOM Event Listeners And Delegation

+ +

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`).

+ +

Complete Example Source

+ +``` +{{>attribute-getset-source}} +``` diff --git a/src/attribute/docs/attribute-rw.mustache b/src/attribute/docs/attribute-rw.mustache new file mode 100644 index 00000000000..0231d67a956 --- /dev/null +++ b/src/attribute/docs/attribute-rw.mustache @@ -0,0 +1,148 @@ + + +
+

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-rw-source}} +
+ +

ReadOnly And WriteOnce

+ +

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.

+ +

Configuring ReadOnly And WriteOnce Attributes

+ +

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); +} +``` + +

Attempting To Set Values

+ +

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"); +``` + +

Setting The State Of ReadOnly Values Internally

+ +

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"); +``` + + +

Complete Example Source

+ +``` +{{>attribute-rw-source}} +``` diff --git a/src/attribute/docs/component.json b/src/attribute/docs/component.json index a3fa4cecd8c..25bd1ba7215 100644 --- a/src/attribute/docs/component.json +++ b/src/attribute/docs/component.json @@ -5,5 +5,68 @@ "author" : "sdesai", "tags": ["attribute", "infrastructure"], - "use" : ["attribute"] + "use" : ["attribute"], + + "examples": [ + { + "name" : "attribute-basic", + "displayName": "Basic Attribute Configuration", + "description": "Use the Attribute API to define, set and get attribute values.", + "modules" : ["attribute"], + "tags" : ["attribute", "base"], + + "hideTableOfContents": true + }, + + { + "name" : "attribute-rw", + "displayName": "Read-Only and Write-Once Attributes", + "description": "Configure attributes to be readOnly or writeOnce.", + "modules" : ["attribute"], + "tags" : ["attribute", "base"], + + "hideTableOfContents": true + }, + + { + "name" : "attribute-event", + "displayName": "Attribute Change Events", + "description": "How to listen for changes in attribute values.", + "modules" : ["attribute"], + "tags" : ["attribute", "base", "event"], + + "hideTableOfContents": true + }, + + { + "name" : "attribute-basic-speeddate", + "displayName": "Attribute Based Speed Dating", + "description": "Create a basic SpeedDater class, with Attribute support.", + "modules" : ["attribute"], + "tags" : ["attribute", "base"], + + "hideTableOfContents": true + }, + + { + "name" : "attribute-event-speeddate", + "displayName": "Attribute Event Based Speed Dating", + "description": "Refactors the basic Speed Dating example, to use attribute change events to update rendered elements, and have two instances react to another.", + "modules" : ["attribute"], + "tags" : ["attribute", "base", "event"], + + "hideTableOfContents": true + }, + + { + "name" : "attribute-getset", + "displayName": "Attribute Getters, Setters and Validators", + "description": "Add custom methods to get and set attribute values and provide validation support.", + "modules" : ["attribute"], + "tags" : ["attribute", "base"], + + "hideTableOfContents": true + } + ] + } diff --git a/src/attribute/docs/partials/attribute-basic-source.mustache b/src/attribute/docs/partials/attribute-basic-source.mustache new file mode 100644 index 00000000000..649150e4e10 --- /dev/null +++ b/src/attribute/docs/partials/attribute-basic-source.mustache @@ -0,0 +1,96 @@ +
+ Construct o1, with default attribute values +
+
+
+ Update the first instance, using set +
+
+
+ Create the second instance, passing initial values to the constructor +
+
+ + diff --git a/src/attribute/docs/partials/attribute-basic-speeddate-source.mustache b/src/attribute/docs/partials/attribute-basic-speeddate-source.mustache new file mode 100644 index 00000000000..e8d0678fe6b --- /dev/null +++ b/src/attribute/docs/partials/attribute-basic-speeddate-source.mustache @@ -0,0 +1,149 @@ +
+ +

Speed Dating With Attributes

+ +
+ + +
+
+ +
+ + + +
+
+
+ + diff --git a/src/attribute/docs/partials/attribute-event-source.mustache b/src/attribute/docs/partials/attribute-event-source.mustache new file mode 100644 index 00000000000..5ccf02ef842 --- /dev/null +++ b/src/attribute/docs/partials/attribute-event-source.mustache @@ -0,0 +1,144 @@ +
+
Enter a new value and click the "Change Value" button:
+
+

+ : + + +

+

:

+

:

+
+ +
+ +
+ + diff --git a/src/attribute/docs/partials/attribute-event-speeddate-source.mustache b/src/attribute/docs/partials/attribute-event-speeddate-source.mustache new file mode 100644 index 00000000000..9c030ac5cc4 --- /dev/null +++ b/src/attribute/docs/partials/attribute-event-speeddate-source.mustache @@ -0,0 +1,228 @@ +
+ +

Communicating With Attribute Events On Speed Dates

+ +
+ + + + I enjoy: + + + + + +
+
+ +
+ + (unless he likes whales, and avoids knitting ) +
+
+
+ + diff --git a/src/attribute/docs/partials/attribute-getset-source.mustache b/src/attribute/docs/partials/attribute-getset-source.mustache new file mode 100644 index 00000000000..e3fe3e91453 --- /dev/null +++ b/src/attribute/docs/partials/attribute-getset-source.mustache @@ -0,0 +1,294 @@ +
+
Enter new values and click the "Set" buttons
+
+ +
+

+ + + + +

+

+ + + + +

+

+ + + +

+
+
+ +
+ +
+ + diff --git a/src/attribute/docs/partials/attribute-rw-source.mustache b/src/attribute/docs/partials/attribute-rw-source.mustache new file mode 100644 index 00000000000..ec7b43fbc22 --- /dev/null +++ b/src/attribute/docs/partials/attribute-rw-source.mustache @@ -0,0 +1,114 @@ +
+

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):

+ + +
+
+ + diff --git a/src/base/docs/index.mustache b/src/base/docs/index.mustache index e8432c6fbd8..ef093defc55 100644 --- a/src/base/docs/index.mustache +++ b/src/base/docs/index.mustache @@ -265,8 +265,8 @@ methods are provided, allowing the developer to define the list of plugins to be

-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.

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 @@ + + +
+

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.

+
+ +
+ {{>intl-basic-source}} +
+ +

Defining your Custom Module

+ +

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.

+ +``` +YUI_config = { + filter:"raw", + groups: { + myapp: { + base: '{{componentAssets}}', + modules : { + "translator" : { + lang: ["en", "fr", "es"] + } + } + } + } +}; +``` + +

What Language Resource Bundles Look Like

+ +

The language resource bundles for any module follows the pattern below:

+ +``` +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"); +``` + +

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. + +

Generating Language Resource Bundles

+ +

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:

+ +``` +# In your component's build properties file (src/[module]/build.properties for example): +component.lang=en,fr,es +``` + +

Provide the raw string name/value pairs in the `src/[component]/lang` subdirectory in your component's source area:

+ +``` +// Contents of the raw src/[component]/lang/[component]_fr.js file +{ + hello:"Bonjour", + goodbye: "Au revoir" +} +``` + +

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.

+ +

Accessing Localized Resources In Your Class

+ +

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:

+ +``` +function Translator() { + // Get localized strings in the current language + this._strs = Y.Intl.get("translator"); +} + +Translator.prototype = { + + hi : function() { + return this._strs.hello; + }, + + bye : function() { + return this._strs.goodbye; + } + + ... +} +``` + + +

Specifying the Language for an Instance

+ +

We specify the language to use for each instance, using the "lang" configuration property for the 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

+ +

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.

+ +

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 @@

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.

+ 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.

{{>getting-started}} @@ -185,7 +185,7 @@ overlay.move([100,200]); ``` -

The Basic XY Positioning example shows basic positioning in action.

+

The Basic XY Positioning example shows basic positioning in action.

Extended XY Positioning

@@ -246,7 +246,7 @@ overlay.set("centered", true); ``` -

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.

Stacking

@@ -286,7 +286,7 @@ var overlay = new Y.Overlay({ ``` -

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.

Setting Section Content

@@ -378,7 +378,7 @@ overlay.setStdModContent(

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.

Markup Structure

diff --git a/src/overlay/docs/overlay-align.mustache b/src/overlay/docs/overlay-align.mustache new file mode 100644 index 00000000000..3549d51e355 --- /dev/null +++ b/src/overlay/docs/overlay-align.mustache @@ -0,0 +1,222 @@ + + +
+

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.

+
+ +
"Basic XY Positioning" example, to create an instance of an Overlay on your page, the only module you need to request is the `overlay` module. The `overlay` module will pull in the `widget`, `widget-stack`, `widget-position`, `widget-position-align`, `widget-position-constrain` and `widget-stdmod` extensions it uses.

+ +``` +YUI({...}).use("overlay", function(Y) { + // We'll write example code here, where we have a Y.Overlay class available. +}); +``` + +

Using the `overlay` module, will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

Instantiating The Overlay

+ +

For this example, we'll instantiate an Overlay, as we did for the "Basic XY Positioning" example, however we'll set the content for the Overlay sections using the `headerContent` and `bodyContent` attributes. We won't create a footer section for this overlay:

+ +``` +/* Create Overlay from script, this time. With no footer */ +var overlay = new Y.Overlay({ + width:"10em", + height:"10em", + headerContent: "Aligned Overlay", + bodyContent: "Click the 'Align Next' button to try a new alignment", + zIndex:2 +}); + +/* Render it as a child of the #overlay-align element */ +overlay.render("#overlay-align"); +``` + +

Since the Overlay is created from script, and doesn't currently exist in the document, we pass the `overlay.render()` method a selector query (`"#overlay-align"`) for the node under which we want the Overlay to be rendered in the DOM. If we leave out this argument to render (or if the selector query doesn't bring back a node), the Overlay will be rendered to the current document's body element.

+ +

Aligning the overlay

+ +

The `WidgetPositionAlign` extension used to create the overlay class adds the `align` and `centered` attributes to the Overlay, which can be used to align or center the Overlay relative to another element on the page (or the viewport).

+ +

The `align` attribute accepts as a value an object literal with the following properties:

+ +
+
node
+
+ The node to which the Widget is to be aligned. If set to null, or not provided, the Overlay is aligned to the viewport +
+
points
+
+

+ A two element array, defining the two points on the Overlay and node which are to be aligned. The first element is the point on the Overlay, and the second element is the point on the node (or viewport). + Supported alignment points are defined as static properties on the `WidgetPositionAlign` extension. +

+

+ e.g. `[Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.TL]` aligns the Top-Right corner of the Overlay with the + Top-Left corner of the node/viewport, and `[Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.TC]` aligns the Center of the + Overlay with the Top-Center edge of the node/viewport. +

+
+
+ +

The `centered` property can either by set to `true` to center the Overlay in the viewport, or set to a selector string or node reference to center the Overlay in a particular node.

+ +

The example loops around a list of 6 alignment configurations, as the "Align Next" button is clicked:

+ +``` +/* Setup local variable for Y.WidgetPositionAlign, since we use it multiple times */ +var WidgetPositionAlign = Y.WidgetPositionAlign; + +... + +/* Center in #overlay-align (example box) */ +overlay.set("align", {node:"#overlay-align", + points:[WidgetPositionAlign.CC, WidgetPositionAlign.CC]}); + +/* Align top-left corner of overlay, with top-right corner of #align1 */ +overlay.set("align", {node:"#align1", + points:[WidgetPositionAlign.TL, WidgetPositionAlign.TR]}); + +/* Center overlay in #align2 */ +overlay.set("centered", "#align2"); + +/* Align right-center edge of overlay with right-center edge of viewport */ +overlay.set("align", {points:[WidgetPositionAlign.RC, WidgetPositionAlign.RC]}); + +/* Center overlay in viewport */ +overlay.set("centered", true); + +/* Align top-center edge of overlay with bottom-center edge of #align3 */ +overlay.set("align", {node:"#align3", + points:[WidgetPositionAlign.TC, WidgetPositionAlign.BC]}); +``` + +

NOTE: Future verions will add support to re-align the Overlay in response to trigger events (e.g. window resize, scroll etc.) and support for constrained positioning.

+ +

CSS: Overlay Look/Feel

+ +

As mentioned in the "Basic XY Positioning" example, the overlay.css Sam Skin file (build/overlay/assets/skins/sam/overlay.css) provides the default functional CSS for the overlay. Namely the CSS rules to hide the overlay, and position it absolutely. However there's no default out-of-the-box look/feel applied to the Overlay widget.

+ +

The example provides it's own look and feel for the Overlay, by defining rules for the content box, header and body sections:

+ +``` +.yui3-overlay-content { + padding:2px; + border:1px solid #000; + background-color:#aaa; + font-size:93%; +} + +.yui3-overlay-content .yui3-widget-hd { + font-weight:bold; + text-align:center; + padding:2px; + border:2px solid #aa0000; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-bd { + text-align:left; + padding:2px; + border:2px solid #0000aa; + background-color:#fff; +} +``` + +

Complete Example Source

+``` +{{>overlay-align-source}} +``` diff --git a/src/overlay/docs/overlay-anim-plugin.mustache b/src/overlay/docs/overlay-anim-plugin.mustache new file mode 100644 index 00000000000..5ccf3619dd6 --- /dev/null +++ b/src/overlay/docs/overlay-anim-plugin.mustache @@ -0,0 +1,334 @@ + + +
+

This example shows how you can use Widget's plugin infrastructure to customize the existing behavior of a widget.

+ +

We create an Animation plugin class for Overlay called `AnimPlugin` which changes the way Overlay instances are shown/hidden, by fading them in and out. The Overlay is initially constructed with the `AnimPlugin` plugged in (with the duration set to 2 seconds). + Clicking the "Unplug AnimPlugin" button, will restore the original non-Animated Overlay show/hide behavior. Clicking on the "Plug AnimPlugin" button will plug in the `AnimPlugin` again, but with a shorter duration.

+ +

NOTE: This example serves as a tutorial for how to build your own plugins. A packaged animation plugin based on this example is available by using the `widget-anim` module, which sets up a `Y.Plugin.WidgetAnim` class, similar to the one discussed in this example.

+
+ +
Using the `overlay` module will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

AnimPlugin Class Structure

+ +

The `AnimPlugin` class will extend the `Plugin` base class. Since `Plugin` derives from `Base`, we follow the same pattern we use for widgets and other utilities which extend Base to setup our new class.

+ +

Namely:

+ + + +

Additionally, since this is a plugin, we provide a `NS` property for the class, which defines the property which will refer to the `AnimPlugin` instance on the host class (e.g. `overlay.fx` will be an instance of `AnimPlugin`)

. + +

This basic structure is shown below:

+ +``` +/* Animation Plugin Constructor */ +function AnimPlugin(config) { + AnimPlugin.superclass.constructor.apply(this, arguments); +} + +/* + * The namespace for the plugin. This will be the property on the widget, which will + * reference the plugin instance, when it's plugged in + */ +AnimPlugin.NS = "fx"; + +/* + * The NAME of the AnimPlugin class. Used to prefix events generated + * by the plugin class. + */ +AnimPlugin.NAME = "animPlugin"; + +/* + * The default set of attributes for the AnimPlugin class. + */ +AnimPlugin.ATTRS = { + + /* + * Default duration. Used by the default animation implementations + */ + duration : { + value: 0.2 + }, + + /* + * Default animation instance used for showing the widget (opacity fade-in) + */ + animVisible : { + valueFn : function() { + ... + } + }, + + /* + * Default animation instance used for hiding the widget (opacity fade-out) + */ + animHidden : { + valueFn : function() { + ... + } + } +} + +/* + * Extend the base plugin class + */ +Y.extend(AnimPlugin, Y.Plugin.Base, { + + // Lifecycle methods + initializer : function(config) { ... }, + destructor : function() { ... }, + + // Other plugin specific methods + _uiAnimSetVisible : function(val) { ... }, + _uiSetVisible : function(val) { ... }, + ... +}); +``` + +

Attributes: animVisible, animHidden

+ +

The `animVisible` and `animHidden` attributes use Attribute's `valueFn` support to set up instance based default values for the attributes.

+ +

The `animHidden` attribute is pretty straight forward and simply returns the Animation instance bound to the bounding box of the Overlay to provide a fade-out animation:

+ +``` +animHidden : { + valueFn : function() { + return new Y.Anim({ + node: this.get("host").get("boundingBox"), + to: { opacity: 0 }, + duration: this.get("duration") + }); + } +} +``` + +

The `animVisible` attribute is a little more interesting:

+ +``` +animVisible : { + valueFn : function() { + + var host = this.get("host"), + boundingBox = host.get("boundingBox"); + + var anim = new Y.Anim({ + node: boundingBox, + to: { opacity: 1 }, + duration: this.get("duration") + }); + + // Set initial opacity, to avoid initial flicker + if (!host.get("visible")) { + boundingBox.setStyle("opacity", 0); + } + + // Clean up, on destroy. Where supported, remove + // opacity set using style. Else make 100% opaque + anim.on("destroy", function() { + if (Y.UA.ie) { + this.get("node").setStyle("opacity", 1); + } else { + this.get("node").setStyle("opacity", ""); + } + }); + + return anim; + } +} +``` + +

It essentially does the same thing as `animHidden`; setting up an Animation instance to provide an opacity based fade-in. However it also sets up a listener which will attempt to cleanup the opacity state of the Overlay when the plugin is unplugged from the Overlay. When a plugin is unplugged, it is destroyed.

+ +

Lifecycle Methods: initializer, destructor

+ +

In the `initializer`, we set up listeners on the animation instances (using `_bindAnimVisible, _bindAnimHidden`), which invoke the original visibility handling to make the Overlay visible before starting the `animVisible` animation and hide it after the `animHidden` animation is complete.

+ +``` +initializer : function(config) { + this._bindAnimVisible(); + this._bindAnimHidden(); + + this.after("animVisibleChange", this._bindAnimVisible); + this.after("animHiddenChange", this._bindAnimHidden); + + // Override default _uiSetVisible method, with custom animated method + this.doBefore("_uiSetVisible", this._uiAnimSetVisible); +} + +... + +_bindAnimVisible : function() { + var animVisible = this.get("animVisible"); + + animVisible.on("start", Y.bind(function() { + // Setup original visibility handling (for show) before starting to animate + this._uiSetVisible(true); + }, this)); +}, + +_bindAnimHidden : function() { + var animHidden = this.get("animHidden"); + + animHidden.after("end", Y.bind(function() { + // Setup original visibility handling (for hide) after completing animation + this._uiSetVisible(false); + }, this)); +} +``` + +

+However the key part of the `initializer` method is the call to `this.doBefore("_uiSetVisible", this._uiAnimSetVisible)` (line 9). `Plugin`'s `doBefore` and `doAfter` methods, will let you set up both before/after event listeners, as well as inject code to be executed before or after a given method on the host object (in this case the Overlay) is invoked. +

+

+For the animation plugin, we want to change how the Overlay updates its UI in response to changes to the `visible` attribute. Instead of simply flipping visibility (through the application of the `yui-overlay-hidden` class), we want to fade the Overlay in and out. Therefore, we inject our custom animation method, `_uiSetAnimVisible`, before the Overlay's `_uiSetVisible`. +

+ +

Using `Plugin`'s `doBefore/doAfter` methods to setup any event listeners and method injection code on the host object (Overlay), ensures that the custom behavior is removed when the plugin is unplugged from the host, restoring it's original behavior.

+ +

The `destructor` simply calls `destroy` on the animation instances used, and lets them perform their own cleanup (as defined in the discussion on attributes):

+ +``` +destructor : function() { + this.get("animVisible").destroy(); + this.get("animHidden").destroy(); +}, +``` + +

The Animated Visibility Method

+ +

The `_uiAnimSetVisible` method is the method we use to over-ride the default visibility handling for the Overlay. Instead of simply adding or removing the `yui-overlay-hidden` class, it starts the appropriate animation depending on whether or not visible is being set to true or false:

+ +``` +_uiAnimSetVisible : function(val) { + if (this.get("host").get("rendered")) { + if (val) { + this.get("animHidden").stop(); + this.get("animVisible").run(); + } else { + this.get("animVisible").stop(); + this.get("animHidden").run(); + } + return new Y.Do.Halt(); + } +} +``` + +

Since this method is injected before the default method which handles visibility changes for Overlay (`_uiSetVisibility`), we invoke `Y.Do.Halt()` to prevent the original method from being invoked, since we'd like to invoke it in response to the animation starting or completing. +`Y.Do` is YUI's aop infrastructure and is used under the hood by Plugin's `doBefore` and `doAfter` methods when injecting code

. + +

The Original Visibility Method

+ +

The original visiblity handling for Overlay is replicated in the `AnimPlugin`'s `_uiSetVisible` method and is invoked before starting the `animVisible` animation and after completing the `animHidden` animation as described above.

+ +``` +_uiSetVisible : function(val) { + var host = this.get("host"); + if (!val) { + host.get("boundingBox").addClass(host.getClassName("hidden")); + } else { + host.get("boundingBox").removeClass(host.getClassName("hidden")); + } +} +``` + +

NOTE: We're evaluating whether or not `Y.Do` may provide access to the original method for a future release, which would make this replicated code unnecessary.

+ +

Using The Plugin

+ +

All objects which derive from Base are Plugin Hosts. They provide `plug` and `unplug` methods to allow users to add/remove plugins to/from existing instances. They also allow the user to specify the set of plugins to be applied to a new instance, along with their configurations, as part of the constructor arguments:

+ +``` +var overlay = new Y.Overlay({ + contentBox: "#overlay", + width:"10em", + height:"10em", + visible:false, + shim:false, + align: { + node: "#show", + points: ["tl", "bl"] + }, + plugins : [{fn:AnimPlugin, cfg:{duration:2}}] +}); +overlay.render(); +``` + +

We use the constructor support to setup the `AnimPlugin` for the instance with a custom value for its `duration` attribute as shown on line 11 above.

+ +

NOTE: In the interests of keeping the example focused on the plugin infrastructure, we turn off shimming for the overlay. If we needed to enable shimming, In IE6, we'd need to remove the alpha opacity filter set on the shim while animating, to have IE animate the contents of the Overlay correctly.

+ +

The example also uses the `plug` and `unplug` methods, to add and remove the custom animation behavior after the instance is created:

+ +``` +// Listener for the "Unplug AnimPlugin" button, +// removes the AnimPlugin from the overlay instance +Y.on("click", function() { + overlay.unplug("fx"); +}, "#unplug"); + +// Listener for the "Plug AnimPlugin" button, +// removes the AnimPlugin from the overlay instance, +// and re-adds it with a new, shorter duration value. +Y.on("click", function() { + overlay.unplug("fx"); + overlay.plug(AnimPlugin, {duration:0.5}); +}, "#plug"); +``` + +

Complete Example Source

+``` +{{>overlay-anim-plugin-source}} +``` diff --git a/src/overlay/docs/overlay-constrain.mustache b/src/overlay/docs/overlay-constrain.mustache new file mode 100644 index 00000000000..0c1b0d89513 --- /dev/null +++ b/src/overlay/docs/overlay-constrain.mustache @@ -0,0 +1,211 @@ + + +
+

This example shows how you can use Overlay's constrained positioning support to constrain the XY position of the overlay so that it remains within another node on the page (or within the viewport).

+
+ +
"Basic XY Positioning" example, to create an instance of an Overlay on your page, the only module you need to request is the `overlay` module. The `overlay` module will pull in the `widget`, `widget-stack`, `widget-position`, `widget-position-align`, `widget-position-constrain` and `widget-stdmod` extensions it uses.

+ +``` +YUI({...}).use("overlay", function(Y) { + // We'll write example code here, where we have a Y.Overlay class + // available, which is bundled with XY positioning, align and + // constrain support. +}); +``` + +

Using the `overlay` module, will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

Instantiating The Overlay

+ +

For this example, we'll instantiate an Overlay from script, as we did for the "Alignment Support" example, but use the `render` attribute to specify the node under which we want the Overlay to be rendered in the DOM, and to indicate that we want it rendered on construction. The `render` attribute is a sugar attribute for the `render()` method:

+ +``` + /* Create Overlay from script */ + var overlay = new Y.Overlay({ + id:"overlay", + + width:"140px", + height:"120px", + + headerContent: "Constrained", + bodyContent: "Use the sliders to move the overlay", + footerContent: '', + + align:{node:"#constrain-box", points:["cc", "cc"]}, + constrain:"#constrain-box", + + render: "#overlay-example" + }); +``` + +

We align the overlay to the center of the `#constrain-box` node, which we're also using as the constraining node for the overlay. The `constrain` attribute accepts a node reference (either an actual Node instance, or a string selector reference), or it can simply be set to `true` to constrain the overlay to the Viewport.

+ +

Demonstrating Constrained Support

+ +

For this example, we set up a couple of Slider instances which can be used to set the overlay's `x` and `y` attribute values. + +``` + + // Get the current XY position of the overlay + // (after it's been center aligned) to set the + // initial value for the sliders + var overlayXY = Y.one("#overlay").getXY(); + + // Get the region for the constraing box + var constrainRegion = Y.one("#constrain-box").get("region"); + + // X-Axis + var sx = new Y.Slider({ + id:"x", + length: "450px", + min: constrainRegion.left - 50, + max: constrainRegion.right + 50, + value: overlayXY[0], + render:"#overlay-example" + }); + + // Y Axis + var sy = new Y.Slider({ + id:"y", + axis: 'y', + length: "400px", + min: constrainRegion.top - 50, + max:constrainRegion.bottom + 50, + value: overlayXY[1], + render: "#overlay-example" + }); + +``` + +

We set the `min` and `max` values of the slider instances to allow the overlay to be moved beyond the edge of the constraining region, and set the initial `value` of the sliders to reflect the current centered position of the overlay.

+ +

Finally, we set up `valueChange` listeners for the sliders, when attempt to set the corresponding axis position of the overlay:

+ +``` + // Set the overlay's x attribute value + sx.after("valueChange", function(e) { + overlay.set("x", e.newVal); + }); + + // Set the overlay's y attribute value + sy.after("valueChange", function(e) { + overlay.set("y", e.newVal); + }); +``` + +

CSS: Overlay Look/Feel

+ +

As mentioned in the "Basic XY Positioning" example, the overlay.css Sam Skin file (build/overlay/assets/skins/sam/overlay.css) provides the default functional CSS for the overlay. Namely the CSS rules to hide the overlay, and position it absolutely. However there's no default out-of-the-box look/feel applied to the Overlay widget.

+ +

The example provides it's own look and feel for the Overlay, by defining rules for the content box, header and body sections:

+ +``` +/* Overlay Look/Feel */ +.yui3-overlay-content { + text-align:center; + padding:2px; + border:1px solid #000; + background-color:#aaa; + font-size:93%; +} + +.yui3-overlay-content .yui3-widget-hd { + font-weight:bold; + padding:5px; + border:2px solid #aa0000; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-ft { + padding:5px; + border:2px solid #000; + background-color:#ccc; +} + +.yui3-overlay-content .yui3-widget-bd { + padding:5px; + border:2px solid #0000aa; + background-color:#fff; +} +``` + +

Complete Example Source

+``` +{{>overlay-constrain-source}} +``` diff --git a/src/overlay/docs/overlay-io-plugin.mustache b/src/overlay/docs/overlay-io-plugin.mustache new file mode 100644 index 00000000000..1fe4a088d0e --- /dev/null +++ b/src/overlay/docs/overlay-io-plugin.mustache @@ -0,0 +1,290 @@ + + +
+

This example shows how you can use Widget's plugin infrastructure to add additional features to an existing widget.

+

We create an IO plugin class for `Overlay` called `StdModIOPlugin`. The plugin adds IO capabilities to the Overlay, bound to one of its standard module sections (header, body or footer).

+
+ +
widget plugin example, pull in `overlay`; `json` and `substitute` utility modules +and the `plugin` module. The Widget IO plugin will pull in the dependencies it needs, the main one being `io` to provide the XHR support. + +The `json` and `substitute` modules provide the support we need to parse/transform JSON responses into HTML.The code to set up our sandbox instance is shown below:

+ +``` +YUI({...}).use("overlay", "substitute", "json", "gallery-io-plugin", function(Y) { + // We'll write our code here, after pulling in the default Overlay widget, + // the IO utility, the Plugin.WidgetIO base class along with the + // Substitute and JSON utilities +}); +``` + +

Using the `overlay` module will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

StdModIO Class Structure

+ +

The `StdModIO` class will extend the `Plugin.WidgetIO` base class. +Since `WidgetIO` derives from `Pluing.Base` and hence `Base`, we follow the same +pattern we use for widgets and other utilities which extend Base to setup our new class.

+ +

Namely:

+ +
    +
  • Setting up the default attributes, using the `ATTRS` property
  • +
  • Calling the superclass constructor
  • +
  • Setting up the the `NAME` property
  • +
  • Providing prototype implementations for anything we want executed during initialization and destruction using the `initializer` and `destructor` lifecycle methods
  • +
+ +

Additionally, since this is a plugin, we provide a `NS` property for the class, which defines the property which will refer to the `StdModIO` instance on the host class (e.g. `overlay.io` will be an instance of `StdModIO`)

. + +``` +StdModIO = function(config) { + StdModIO.superclass.constructor.apply(this, arguments); +}; + +Y.extend(StdModIO, Y.Plugin.WidgetIO, { + initializer: function() {...} + +}, { + ATTRS: { + section: {...} + } + +}, { + NAME: 'StdModIO', + NS: 'io' +}); +``` +

Plugin Attributes

+ +

The `StdModIO` is a fairly simple plugin class. It provides +incremental functionality. It does not need to modify the behavior of any +methods on the host Overlay instance, or monitor any Overlay events +(unlike the AnimPlugin example).

+ +

In terms of code, the attributes for the plugin are set up using the standard +`ATTRS` property. For this example, we will add an attribute called +`section` that represents which part of the module (e.g. "header", +"body", or "footer") will be updated with the returned content.

+ +``` + /* + * The Standard Module section to which the io plugin instance is bound. + * Response data will be used to populate this section, after passing through + * the configured formatter. + */ + ATTRS: { + section: { + value:StdMod.BODY, + validator: function(val) { + return (!val || val == StdMod.BODY + || val == StdMod.HEADER + || val == StdMod.FOOTER); + } + } + } +}; +``` + +

Lifecycle Methods: initializer, destructor

+ +

The base `WidgetIO` plugin implements the `initializer` +and `destructor` lifecycle methods. For the purposes of this example, +we will extend the `StdModIO` plugin's `initializer` so that it +activates the Flash based XDR transport so that the +plugin is able to dispatch both in-domain and cross-domain requests +(the transport used for any particular uri is controlled through the plugin's +`cfg` attribute).

+ +``` +initializer: function() { + // We setup a flag, so that we know if + // flash is available to make the + // XDR request. + Y.on('io:xdrReady', function() { + transportAvailable = true; + }); + + Y.io.transport({ + id:'flash', + yid: Y.id, + src:'../../build/io/io.swf?stamp=' + (new Date()).getTime() + }); +} +``` + +

Using the Plugin

+ +

All objects derived from Base are Plugin Hosts. They provide `plug` and `unplug` methods to allow users to add/remove plugins to/from existing instances. +They also allow the user to specify the set of plugins to be applied to a new instance, along with their configurations, as part of the constructor arguments.

+ +

In this example, we'll create a new instance of an Overlay:

+ +``` +/* Create a new Overlay instance, with content generated from script */ +var overlay = new Y.Overlay({ + width:"40em", + visible:false, + align: { + node:"#show", + points: [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL] + }, + zIndex:10, + headerContent: generateHeaderMarkup(), + bodyContent: "Feed data will be displayed here" +}); +``` + +

And then use the `plug` method to add the `StdModIO`, providing it with a configuration to use when sending out io transactions (The Animation Plugin example shows how you could do the same thing during construction):

+ +``` +/* + * Add the Standard Module IO Plugin, and configure it to use flash, + * and a formatter specific to the pipes response we're expecting + * from the uri request we'll send out. + */ +overlay.plug(StdModIO, { + uri : pipes.baseUri + pipes.feeds["ynews"].uri, + cfg:{ + xdr: { + use:'flash' + } + }, + formatter: pipes.formatter, + loading: '' +}); +``` + +

For this example, the io plugin is configured to use the `flash` cross-domain transport, to send out requests to the pipes API for the feed the user selects from the dropdown.

+ +

Getting Feed Data Through Pipes

+ +

We setup an object (`pipes`) to hold the feed URIs, which can be looked up and passed to the plugin to dispatch requests for new data:

+ +``` +/* The Pipes feed URIs to be used to dispatch io transactions */ + +var pipes = { + baseUri : 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run? \ + _id=6b7b2c6a32f5a12e7259c36967052387& \ + _render=json&url=http:/'+'/', + feeds : { + ynews : { + title: 'Yahoo! US News', + uri: 'rss.news.yahoo.com/rss/us' + }, + yui : { + title: 'YUI Blog', + uri: 'feeds.yuiblog.com/YahooUserInterfaceBlog' + }, + slashdot : { + title: 'Slashdot', + uri: 'rss.slashdot.org/Slashdot/slashdot' + }, + ... + }, + + ... +``` + +

The data structure also holds the default formatter (`pipes.formatter`) required to convert the JSON responses from the above URIs to HTML. The JSON utility is first used to parse the json response string. The resulting object is iterated around, using Y.each, and substitute is used to generate the list markup:

+ +``` +... + +// The default formatter, responsible for converting the JSON responses received, +// into HTML. JSON is used for the parsing step, and substitute for some basic +// templating functionality + +formatter : function (val) { + var formatted = "Error parsing feed data"; + try { + var json = Y.JSON.parse(val); + if (json && json.count) { + var html = ['
    ']; + var linkTemplate = + '
  • {title}
  • '; + + // Loop around all the items returned, and feed + // them into the template above, using substitute. + Y.each(json.value.items, function(v, i) { + if (i < 10) { + html.push(Y.substitute(linkTemplate, v)); + } + }); + html.push("
"); + formatted = html.join(""); + } else { + formatted = "No Data Available"; + } + } catch(e) { + formatted = "Error parsing feed data"; + } + return formatted; +} +``` + +

The `change` handler for the select dropdown binds everything together, taking the currently selected feed, constructing the URI for the feed, setting it on the plugin, and sending out the request:

+ +``` +/* Handle select change */ +Y.on("change", function(e) { + var val = this.get("value"); + if (transportAvailable) { + if (val != -1) { + overlay.io.set("uri", pipes.baseUri + pipes.feeds[val].uri); + overlay.io.refresh(); + } + } else { + overlay.io.setContent("Flash doesn't appear to be available. Cross-domain requests to pipes cannot be made without it."); + } +}, "#feedSelector"); +``` + +

Complete Example Source

+``` +{{>overlay-io-plugin-source}} +``` diff --git a/src/overlay/docs/overlay-stack.mustache b/src/overlay/docs/overlay-stack.mustache new file mode 100644 index 00000000000..3671e11e8c6 --- /dev/null +++ b/src/overlay/docs/overlay-stack.mustache @@ -0,0 +1,248 @@ + + +
+

This example shows how you can work with the `zIndex` attribute which the Overlay supports, to implement a simple `bringToTop` function. The example code also listens for changes in the `zIndex` attribute, which it uses to update the content of the overlay, when it is brought to the top of the stack.

+
+ +
"Basic XY Positioning" example, to create an instance of an Overlay on your page, the only module you need to request is the `overlay` module. The `overlay` module will pull in the `widget`, `widget-stack`, `widget-position`, `widget-position-align`, `widget-position-constrain` and `widget-stdmod` extensions it uses.

+ +``` +YUI({...}).use("overlay", function(Y) { + // We'll write example code here, where we have a Y.Overlay class available. +}); +``` + +

Using the `overlay` module will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

Instantiating The Overlay

+ +

For this example we'll instantiate Overlays from script, as we did for the "Alignment Support" example. We'll create 6 Overlay instances and give them increasing zIndex and xy attribute values:

+ +``` +function getOverlayXY(xy, i) { + return [xy[0] + i * 60, xy[1] + i * 40]; +} + +for (var i = 0; i < n; ++i) { + + ovrXY = getOverlayXY(xy, i); + ovrZIndex = i+1; + + // Setup n Overlays, with increasing zIndex values and xy positions + overlay = new Y.Overlay({ + zIndex:ovrZIndex, + xy:ovrXY, + + width:"8em", + height:"8em", + headerContent: 'Overlay ' + i + '', + bodyContent: "zIndex = " + ovrZIndex + }); + + overlay.render("#overlay-stack"); + + ... + +} +``` + +

We store the Overlay instances in an `overlays` array, which we'll later use to sort the Overlays by their zIndex values. We also setup a listener for the `zIndex` attribute change event, which will update the body section of the Overlay to display its new zIndex value.

+ +``` +overlays.push(overlay); + +// Update body whenever zIndex changes +overlay.after("zIndexChange", function(e) { + this.set("bodyContent", "zIndex = " + e.newVal); +}); +``` + +

Handling MouseDown Using Widget.getByNode

+ +

The `Widget` class has a static `getByNode` method which can be used to retrieve Widget instances based on a node reference. The method will return the closest Widget which contains the given node.

+ +

+We'll use this method in a click listener bound to the container of the example ("#overlay-stack"). Target nodes of click events bubbled up to this example container, will be passed to the `Widget.getByNode` method, to see if an Overlay was clicked on. +

+ +``` +function onStackMouseDown(e) { + var widget = Y.Widget.getByNode(e.target); + + // If user clicked on an Overlay, bring it to the top of the stack + if (widget && widget instanceof Y.Overlay) { + bringToTop(widget); + } +} + +Y.on("mousedown", onStackMouseDown, "#overlay-stack"); +``` + +

If an Overlay was clicked on, we invoke the simple bringToTop method which will set the zIndex of the clicked Overlay to the highest current Overlay zIndex value.

+ +

The `bringToTop` Implementation

+ +

We use a basic comparator function to sort the array of Overlays we have. The comparator makes sure the array element we're dealing with has a `WidgetStack` implementation (which Overlays do) and if so, sorts them in descending `zIndex` attribute value order:

+ +``` +// zIndex comparator +function byZIndexDesc(a, b) { + if (!a || !b || !a.hasImpl(Y.WidgetStack) || !b.hasImpl(Y.WidgetStack)) { + return 0; + } else { + var aZ = a.get("zIndex"); + var bZ = b.get("zIndex"); + + if (aZ > bZ) { + return -1; + } else if (aZ < bZ) { + return 1; + } else { + return 0; + } + } +} +``` + +

Once sorted, for the purposes of the example, we simply switch the `zIndex` of the "highest" Overlay, with the Overlay which was clicked on, giving the selected Overlay the highest zIndex value:

+ +``` +function bringToTop(overlay) { + + // Sort overlays by their numerical zIndex values + overlays.sort(byZIndexDesc); + + // Get the highest one + var highest = overlays[0]; + + // If the overlay is not the highest one, switch zIndices + if (highest !== overlay) { + var highestZ = highest.get("zIndex"); + var overlayZ = overlay.get("zIndex"); + + overlay.set("zIndex", highestZ); + highest.set("zIndex", overlayZ); + } +} +``` + +

CSS: Overlay Look/Feel

+ +

As mentioned in the "Basic XY Positioning" example, the overlay.css Sam Skin file (build/overlay/assets/skins/sam/overlay.css) provides the default functional CSS for the overlay. Namely the CSS rules to hide the overlay, and position it absolutely. However there's no default out-of-the-box look/feel applied to the Overlay widget.

+ +

The example provides it's own look and feel for the Overlay, by defining rules for the content box, header and body sections:

+ +``` +/* Overlay Look/Feel */ +.yui3-overlay-content { + padding:2px; + border:1px solid #000; + background-color:#aaa; + font-size:93%; +} + +.yui3-overlay-content .yui3-widget-hd { + font-weight:bold; + text-align:center; + padding:2px; + border:2px solid #aa0000; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-bd { + text-align:left; + padding:2px; + border:2px solid #0000aa; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-hd .yui3-ovr-number { + color:#aa0000; +} +``` + +

Complete Example Source

+``` +{{>overlay-stack-source}} +``` diff --git a/src/overlay/docs/overlay-stdmod.mustache b/src/overlay/docs/overlay-stdmod.mustache new file mode 100644 index 00000000000..feac6cad970 --- /dev/null +++ b/src/overlay/docs/overlay-stdmod.mustache @@ -0,0 +1,200 @@ + + +
+

This example shows how you can work either the `headerContent, bodyContent, footerContent` attributes, to replace content in the Overlay's standard module sections, or use the `setStdModContent(section, content, where)` method to insert content before, or append it after existing content in the section.

+
+ +

Overlay's Standard Module Support

+ +

Setting Up The YUI Instance

+ +

To create an instance of an Overlay on you page, the only module you need to request is the `overlay` module. The `overlay` module will pull in the `widget`, `widget-stack`, `widget-position`, `widget-position-align`, `widget-position-constrain` and `widget-stdmod` extensions it uses.

+ +``` +YUI({...}).use("overlay", function(Y) { + // We'll write example code here, where we have a Y.Overlay class available. +}); +``` + +

Note, using the `overlay` module, will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

Creating The Overlay From Markup

+ +

For this example, we'll create the overlay instance from markup which already exists on the page, and is shown below. We mark the existing markup with the `yui3-overlay-loading` class, so that we can hide it while the rich control is being instantiated:

+ +``` +
+
Overlay Header
+
Overlay Body
+
Overlay Footer
+
+``` + +

Instantiating The Overlay

+ +

To create an overlay instance, we use the overlay constructor `Y.Overlay`, and pass it the `srcNode` reference for the existing markup on the page:

+ +``` +var overlay = new Y.Overlay({ + srcNode:"#overlay", + width:"20em", + align: { + node:"#overlay-stdmod > .filler", + points:["tl", "tl"] + } +}); +overlay.render(); +``` + +

We also set it's width and align it to the filler paragraph in the example box ("#overlay-stdmod > .filler"). We don't pass any node references to the `render` method, so the Overlay is rendered in the location of the contentBox provided.

+ +

Setting Content

+ +

+The example provides a set of input fields, allowing the user to set content in either of the 3 standard module sections which Overlay supports using Overlay's `setStdModContent` method. +The content can either be inserted before, appended after, or replace existing content in the specified section.

+ +

+Additionally the new content can be converted to a node instance before being added to the specified section. Although it has no impact on the example, if the new content is added as a string, innerHTML is used to insert before or append after the existing section content, removing any event listeners which may have been attached to elements inside the section. The ability to convert the content to a node instance is provided in the example to illustrate Overlay's ability to handle both content formats. +

+ +``` +// Hold onto input field references. +var content = Y.one("#content"); +var where = Y.one("#where"); +var section = Y.one("#section"); +var asString = Y.one("#asString"); + +Y.on("click", function() { + + // New content to be added. For this example, we escape the HTML since it's coming from an unknown source, + // however Overlay accepts HTML strings as content (you should ensure it's coming from a trusted source). + var newContent = Y.Escape.html(content.get("value")); + + // If the "Set content as string" checkbox is checked, we pass new content into the + // setStdModContent method as string (innerHTML will be used to set the new content). + + // If it's not checked, we create a node reference from the string, + // and pass the new content into the setStdModContent as a node reference. + + if (! asString.get("checked") ) { + newContent = Y.Node.create(newContent); + } + + overlay.setStdModContent(section.get("value"), newContent, where.get("value")); + +}, "#setContent"); +``` + +

CSS: Overlay Look/Feel

+ +

As with the other basic overlay examples, the overlay.css Sam Skin file (build/overlay/assets/skins/sam/overlay.css) provides the default functional CSS for the overlay. Namely the CSS rules to hide the overlay, and position it absolutely. However there's no default out-of-the-box look/feel applied to the Overlay widget.

+ +

The example provides it's own look and feel for the Overlay, by defining rules for the content box, header, body and footer sections:

+ +``` +/* Hide overlay markup while loading, if js is enabled */ +.yui3-js-enabled .yui3-overlay-loading { + top:-1000em; + left:-1000em; + position:absolute; +} + +/* Overlay Look/Feel */ +.yui3-overlay-content { + padding:3px; + border:1px solid #000; + background-color:#aaa; +} + +.yui3-overlay-content .yui3-widget-hd { + padding:5px; + border:2px solid #aa0000; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-bd { + padding:5px; + border:2px solid #0000aa; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-ft { + padding:5px; + border:2px solid #00aa00; + background-color:#fff; +} +``` diff --git a/src/overlay/docs/overlay-xy.mustache b/src/overlay/docs/overlay-xy.mustache new file mode 100644 index 00000000000..ef55ee8044d --- /dev/null +++ b/src/overlay/docs/overlay-xy.mustache @@ -0,0 +1,184 @@ + + +
+

This example walks you through basics of creating and positioning an Overlay. It walks you through setting up the sandbox environment for the Overlay, including the required modules, and instantiating the Overlay based on markup already on the page.

+

It also provides a couple of input fields, allowing you to invoke the Overlay's `move()` method, to move the Overlay to a specific XY position on the page.

+
+ +
Note, using the `overlay` module, will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

+ +

Creating The Overlay From Markup

+ +

For this example, we'll create the overlay instance from markup which already exists on the page, and is shown below. We mark the existing markup with the `yui3-overlay-loading` class, so that we can hide it while the rich control is being instantiated:

+ +``` +
+
Overlay Header
+
Overlay Body
+
Overlay Footer
+
+``` + +

The container DIV with id="overlay" is specified as the contentBox for the Overlay instance, and during instantiation, the overlay will look for DIV's marked with the `yui3-widget-hd, yui3-widget-bd, yui3-widget-ft` classes to setup the Overlay's header, body and footer content attributes.

+ +

Instantiating The Overlay

+ +

To create an overlay instance, we use the overlay constructor `Y.Overlay`, and pass it the `contentBox` node reference for the existing markup on the page. We also set a height/width for the overlay and the initial position for the Overlay (which otherwise defaults to 0,0):

+ +``` +// Create an overlay from markup, using an existing contentBox. +var overlay = new Y.Overlay({ + srcNode:"#overlay", + width:"10em", + height:"10em", + xy:[xy[0] + 10, xy[1] + 35] +}); +overlay.render(); +``` + +

After creating the overlay instance, we invoke `overlay.render()` to update the DOM to reflect the current state of the overlay. Before render is called, the state of the Overlay should not be reflected in the DOM (for example, we can change the height, without it being reflected in the DOM. When we finally render, the current height value will be applied to the DOM). We could also pass an optional node reference to the render method, to have the overlay rendered under a different parent node, from where the content box currently lies.

+ +

Moving the Overlay

+ +

Overlays have support for basic page based XY positioning. This example provides a couple of input controls which can be used to move the overlay to a specific XY page co-ordinate. Later examples will show how Overlay's extended positioning support to align/center the Overlay relative to other elements on the page.

+ +``` +var xInput = Y.one("#x"); +var yInput = Y.one("#y"); + +Y.on("click", function(e) { + + var x = parseInt(xInput.get("value")); + var y = parseInt(yInput.get("value")); + + overlay.move(x,y); +}, "#move"); +``` + +

Overlay can be moved by invoking the `move` method, with either seperate x and y arguments (`move(100,200)`), or as an array (`move([100,200])`). The `x, y and xy` attributes can also be used to move the overlay, and are equivalent to the move method (`overlay.set("x", 200);overlay.set("xy", [100,200])`)

+ +

A select dropdown is added to the example page, along with additional content, to demonstrate the Overlay's ability to provide stacking and shimming support (to block select dropdown bleed through in IE6).

+ +

CSS: Overlay Look/Feel

+ +

The overlay.css Sam Skin file (build/overlay/assets/skins/sam/overlay.css) provides the default functional CSS for the overlay. Namely the CSS rules to hide the overlay, and position it absolutely. However there's no default out-of-the-box look/feel applied to the Overlay widget.

+ +

The example provides it's own look and feel for the Overlay, by defining rules for the content box, header, body and footer sections, and also specifies how markup should be hidden while the overlay is loading.:

+ +``` +/* Hide overlay markup while loading, if js is enabled */ +.yui3-js-enabled .yui3-overlay-loading { + top:-1000em; + left:-1000em; + position:absolute; +} + +/* Overlay Look/Feel */ +.yui3-overlay-content { + padding:3px; + border:1px solid #000; + background-color:#aaa; +} + +.yui3-overlay-content .yui3-widget-hd { + padding:5px; + border:2px solid #aa0000; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-bd { + padding:5px; + border:2px solid #0000aa; + background-color:#fff; +} + +.yui3-overlay-content .yui3-widget-ft { + padding:5px; + border:2px solid #00aa00; + background-color:#fff; +} +``` + +

NOTE: As discussed on the Widget landing page, all widgets are enclosed in 2 containing elements - the boundingBox is the outer(most) element, and the contentBox is the inner element into which the widget's content is added. It is advised to apply any look/feel CSS for the widget to the content box and it's children. This leaves the bounding box without padding/borders, allowing for consistent positioning/sizing across box models.

+ +

Complete Example Source

+``` +{{>overlay-xy-source}} +``` diff --git a/src/overlay/docs/partials/overlay-align-source.mustache b/src/overlay/docs/partials/overlay-align-source.mustache new file mode 100644 index 00000000000..683152552d4 --- /dev/null +++ b/src/overlay/docs/partials/overlay-align-source.mustache @@ -0,0 +1,77 @@ +
+

+

+

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est.

+
id = #align1
+
id = #align2
+
id = #align3
+
+ + diff --git a/src/overlay/docs/partials/overlay-anim-plugin-source.mustache b/src/overlay/docs/partials/overlay-anim-plugin-source.mustache new file mode 100644 index 00000000000..26d0ff5a1cf --- /dev/null +++ b/src/overlay/docs/partials/overlay-anim-plugin-source.mustache @@ -0,0 +1,210 @@ +
+
Overlay Header
+
Overlay Body
+
Overlay Footer
+
+ + + + + + + diff --git a/src/overlay/docs/partials/overlay-constrain-source.mustache b/src/overlay/docs/partials/overlay-constrain-source.mustache new file mode 100644 index 00000000000..cd220dbd5ca --- /dev/null +++ b/src/overlay/docs/partials/overlay-constrain-source.mustache @@ -0,0 +1,74 @@ +
+
+
+ + diff --git a/src/overlay/docs/partials/overlay-io-plugin-source.mustache b/src/overlay/docs/partials/overlay-io-plugin-source.mustache new file mode 100644 index 00000000000..2caca7bd5f0 --- /dev/null +++ b/src/overlay/docs/partials/overlay-io-plugin-source.mustache @@ -0,0 +1,172 @@ + + + + diff --git a/src/overlay/docs/partials/overlay-stack-source.mustache b/src/overlay/docs/partials/overlay-stack-source.mustache new file mode 100644 index 00000000000..5775b1a282f --- /dev/null +++ b/src/overlay/docs/partials/overlay-stack-source.mustache @@ -0,0 +1,95 @@ +

Click on an Overlay, to switch it's zIndex with the highest zIndex in the stack, bringing it to the top of the stack:

+
+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est. + + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est. +

+
+ + diff --git a/src/overlay/docs/partials/overlay-xy-source.mustache b/src/overlay/docs/partials/overlay-xy-source.mustache new file mode 100644 index 00000000000..92eb045aef3 --- /dev/null +++ b/src/overlay/docs/partials/overlay-xy-source.mustache @@ -0,0 +1,54 @@ +
+ + + + + + + +
+
Overlay Header
+
Overlay Body
+
Overlay Footer
+
+ +

+ + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est. +

+
+ + diff --git a/src/scrollview/docs/assets/horizontal-smallscreen.css b/src/scrollview/docs/assets/horizontal-smallscreen.css new file mode 100644 index 00000000000..d02456f1242 --- /dev/null +++ b/src/scrollview/docs/assets/horizontal-smallscreen.css @@ -0,0 +1,18 @@ +body { + min-height: 420px; + overflow:hidden; +} + +#additional-content { + display:none; +} + +#scrollview-container { + float:none; + width:100%; + margin:0; +} + +#scrollview { + border:0; +} diff --git a/src/scrollview/docs/assets/horizontal.css b/src/scrollview/docs/assets/horizontal.css new file mode 100644 index 00000000000..30fcdfba799 --- /dev/null +++ b/src/scrollview/docs/assets/horizontal.css @@ -0,0 +1,90 @@ +html, body { + margin:0; + padding:0; + font-family: arial,helvetica,clean,sans-serif; +} + +#additional-content { + display:block; + margin:10px; +} + +#additional-content p { + margin-bottom:5px; +} + +/* === scrollview styles === */ + +#scrollview { + border:1px solid #000; + border-top:0; +} + +#scrollview-content img { + border-width:2px; + border-style:solid; + width: 300px; + margin: 10px; + -webkit-transform: translate3d(0, 0, 0); +} + +/* To layout horizontal LIs */ +#scrollview-content { + white-space:nowrap; +} + +#scrollview-content li { + display:inline-block; + background-color:#fff; +} + +/* For IE 6/7 - needs inline block hack */ +#scrollview-content li { + *display:inline; + *zoom:1; +} + +/* === scrollview container and header styles === */ + +#scrollview-container { + float:left; + margin:0 10px; +} + +#scrollview-header { + background-color:#6d83a1; + border:1px solid #000; + border-bottom:#2d3033; + + height:44px; + *width:320px; + + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#d8dee6), + color-stop(0.01, #b0bccc), + color-stop(0.49, #889bb3), + color-stop(0.50, #8094ae), + to(#6d83a1) + ); + +} + +#scrollview-header h1 { + color: #fff; + + margin:0; + padding:10px 0; + + text-align:center; + + font-size:150%; + font-weight:bold; + text-shadow: 0 -1px 0 rgba(0,0,0,0.7); +} + +#scrollview-pager { + padding:5px; +} \ No newline at end of file diff --git a/src/scrollview/docs/assets/vertical-smallscreen.css b/src/scrollview/docs/assets/vertical-smallscreen.css new file mode 100644 index 00000000000..dd232f24258 --- /dev/null +++ b/src/scrollview/docs/assets/vertical-smallscreen.css @@ -0,0 +1,18 @@ +body { + min-height: 420px; + overflow:hidden; +} + +#additional-content { + display:none; +} + +#scrollview-container { + float:none; + width:100%; + margin:0; +} + +#scrollview { + border:0; +} \ No newline at end of file diff --git a/src/scrollview/docs/assets/vertical.css b/src/scrollview/docs/assets/vertical.css new file mode 100644 index 00000000000..6fef607d919 --- /dev/null +++ b/src/scrollview/docs/assets/vertical.css @@ -0,0 +1,79 @@ +html, body { + margin:0; + padding:0; + font-family: arial,helvetica,clean,sans-serif; +} + +#additional-content { + display:block; + margin:10px; +} + +#additional-content p { + margin-bottom:5px; +} + +/* === scrollview styles === */ + +#scrollview { + border:1px solid #000; + border-top:0; +} + +#scrollview-content li { + border-bottom: 1px solid #ccc; + padding: 8px; + font-size: 140%; + font-weight: bold; + background-color:#fff; +} + +/* For IE 6/7 - needs a background color (above) to pick up events, and zoom, to fill the UL */ +#scrollview-content li { + *zoom:1; +} + +/* For IE7 - needs zoom, otherwise clipped content is not rendered */ +#scrollview-content ul { + *zoom:1; +} + +/* === scrollview container and header styles === */ + +#scrollview-container { + width:400px; + float:left; + margin:0 10px; +} + +#scrollview-header { + background-color:#6d83a1; + border:1px solid #000; + border-bottom:#2d3033; + + height:44px; + + background: -webkit-gradient( + linear, + left top, + left bottom, + from(#d8dee6), + color-stop(0.01, #b0bccc), + color-stop(0.49, #889bb3), + color-stop(0.50, #8094ae), + to(#6d83a1) + ); +} + +#scrollview-header h1 { + color: #fff; + + margin:0; + padding:10px 0; + + text-align:center; + + font-size:150%; + font-weight:bold; + text-shadow: 0 -1px 0 rgba(0,0,0,0.7); +} \ No newline at end of file diff --git a/src/scrollview/docs/component.json b/src/scrollview/docs/component.json index 6f5236b2ca6..8fe819c5680 100644 --- a/src/scrollview/docs/component.json +++ b/src/scrollview/docs/component.json @@ -5,5 +5,63 @@ "author" : "sdesai", "tags": ["scrollview", "widget"], - "use" : ["scrollview"] + "use" : ["scrollview"], + + "examples": [ + { + "name" : "scrollview-base", + "displayName": "Basic ScrollView Without a Scroll Indicator", + "description": "This example creates a basic ScrollView which doesn't include a scrollbar indicator.", + "modules" : ["scrollview-base"], + "tags" : ["scrollview", "widget"], + + "hideTableOfContents": true + }, + { + "name" : "scrollview", + "displayName": "ScrollView with Scroll Indicator and Link Suppression Behavior", + "description": "This example shows the classic Scrollview implementation, including scroll indicators (bars) and including code to suppress link navigation while scrolling.", + "modules" : ["scrollview"], + "tags" : ["scrollview", "widget"], + + "hideTableOfContents": true + }, + { + "name" : "scrollview-horiz", + "displayName": "Horizontal ScrollView", + "description": "This example creates a horizontal ScrollView.", + "modules" : ["scrollview"], + "tags" : ["scrollview", "widget"], + + "hideTableOfContents": true + }, + { + "name" : "scrollview-paging", + "displayName": "ScrollView With Pagination", + "description": "This example creates a horizontal ScrollView with pagination support.", + "modules" : ["scrollview", "scrollview-paginator"], + "tags" : ["scrollview", "widget"], + + "hideTableOfContents": true + } + ], + + "pages": { + "scrollview-base-example": { + "displayName": "ScrollView: Basic ScrollView Without a Scroll Indicator", + "layout" : "scrollview-vertical-example" + }, + "scrollview-example": { + "displayName": "ScrollView: ScrollView with Scroll Indicator and Link Suppression Behavior", + "layout" : "scrollview-vertical-example" + }, + "scrollview-horiz-example": { + "displayName": "ScrollView: Horizontal ScrollView", + "layout" : "scrollview-horizontal-example" + }, + "scrollview-paging-example": { + "displayName": "ScrollView: ScrollView with Pagination", + "layout" : "scrollview-horizontal-example" + } + } } diff --git a/src/scrollview/docs/index.mustache b/src/scrollview/docs/index.mustache index acb5b20d25d..7d931c6f9a7 100644 --- a/src/scrollview/docs/index.mustache +++ b/src/scrollview/docs/index.mustache @@ -26,7 +26,7 @@
``` -

The `yui3-scrollview-loading` class is applied by the developer to progressively enhanced markup, and can be used along with the `yui3-js-enabled` class applied to the document element, to hide the scrollview markup from view, while the JS is being loaded. The examples show how to set up a `yui3-scrollview-loading` rule to hide progressively enhanced content.

+

The `yui3-scrollview-loading` class is applied by the developer to progressively enhanced markup, and can be used along with the `yui3-js-enabled` class applied to the document element, to hide the scrollview markup from view, while the JS is being loaded. The examples show how to set up a `yui3-scrollview-loading` rule to hide progressively enhanced content.

When instantiating from markup, a reference to the `srcNode` is provided to the constructor as part of the configuration object. This reference can be a node reference, or a selector string which can be used to identify the node. If the selector string is too broad (returns multiple nodes), the first node found will be used. The following code references the markup shown above, while constructing the scrollview:

@@ -213,4 +213,4 @@ YUI({...}).use("scrollview-base", "scrollview-paginator", function(Y) { ```

The paginator plugin accepts a `selector` attribute as part of its configuration, which selects the list of elements to be used to establish page boundaries. In the example above, each `LI` within the ScrollView's content box, defines a page in the ScrollView.

-

When plugged in, the plugin API is available on the `pages` property of the scrollview widget instance, and can be used to set the current page, or retrieve paging information.

\ No newline at end of file +

When plugged in, the plugin API is available on the `pages` property of the scrollview widget instance, and can be used to set the current page, or retrieve paging information.

diff --git a/src/scrollview/docs/layouts/scrollview-horizontal-example.mustache b/src/scrollview/docs/layouts/scrollview-horizontal-example.mustache new file mode 100644 index 00000000000..520d324e1e1 --- /dev/null +++ b/src/scrollview/docs/layouts/scrollview-horizontal-example.mustache @@ -0,0 +1,29 @@ + + + + ScrollView With Scrollbar Indicator + + + + + + + + + + + + + + {{>layout_content}} + + \ No newline at end of file diff --git a/src/scrollview/docs/layouts/scrollview-vertical-example.mustache b/src/scrollview/docs/layouts/scrollview-vertical-example.mustache new file mode 100644 index 00000000000..daaa65133a7 --- /dev/null +++ b/src/scrollview/docs/layouts/scrollview-vertical-example.mustache @@ -0,0 +1,28 @@ + + + + ScrollView With Scrollbar Indicator + + + + + + + + + + + + + {{>layout_content}} + + \ No newline at end of file diff --git a/src/scrollview/docs/partials/scrollview-base-source.mustache b/src/scrollview/docs/partials/scrollview-base-source.mustache new file mode 100644 index 00000000000..6b180c16274 --- /dev/null +++ b/src/scrollview/docs/partials/scrollview-base-source.mustache @@ -0,0 +1,77 @@ +
+
+

Basic ScrollView

+
+
+
    +
  • AC/DC
  • +
  • Aerosmith
  • +
  • Billy Joel
  • +
  • Bob Dylan
  • +
  • Bob Seger
  • +
  • Bon Jovi
  • +
  • Bruce Springsteen
  • +
  • Creed
  • +
  • Creedence Clearwater Revival
  • +
  • Dave Matthews Band
  • +
  • Def Leppard
  • +
  • Eagles
  • +
  • Eminem
  • +
  • Fleetwood Mac
  • +
  • Green Day
  • +
  • Guns N' Roses
  • +
  • James Taylor
  • +
  • Jay-Z
  • +
  • Jimi Hendrix
  • +
  • Led Zeppelin
  • +
  • Lynyrd Skynyrd
  • +
  • Metallica
  • +
  • Motley Crue
  • +
  • Neil Diamond
  • +
  • Nirvana
  • +
  • Ozzy Osbourne
  • +
  • Pearl Jam
  • +
  • Pink Floyd
  • +
  • Queen
  • +
  • Rod Stewart
  • +
  • Rush
  • +
  • Santana
  • +
  • Simon and Garfunkel
  • +
  • Steve Miller Band
  • +
  • The Beatles
  • +
  • The Doors
  • +
  • The Police
  • +
  • The Rolling Stones
  • +
  • Tom Petty
  • +
  • U2
  • +
  • Van Halen
  • +
  • Willie Nelson
  • +
  • ZZ Top
  • +
+
+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+
+ + \ No newline at end of file diff --git a/src/scrollview/docs/partials/scrollview-horiz-source.mustache b/src/scrollview/docs/partials/scrollview-horiz-source.mustache new file mode 100644 index 00000000000..d48a375762f --- /dev/null +++ b/src/scrollview/docs/partials/scrollview-horiz-source.mustache @@ -0,0 +1,43 @@ +
+
+

Horizontal ScrollView

+
+ +
+
    +
  • Above The City II
  • +
  • Walls and Canyon
  • +
  • Stairs Using In Situ Stone
  • +
  • Tree Silhouette
  • +
+
+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+
+ + diff --git a/src/scrollview/docs/partials/scrollview-paging-source.mustache b/src/scrollview/docs/partials/scrollview-paging-source.mustache new file mode 100644 index 00000000000..4bece6c0f49 --- /dev/null +++ b/src/scrollview/docs/partials/scrollview-paging-source.mustache @@ -0,0 +1,65 @@ +
+
+

Paged ScrollView

+
+ +
+
    +
  • Above The City II
  • +
  • Walls and Canyon
  • +
  • Stairs Using In Situ Stone
  • +
  • Tree Silhouette
  • +
+
+ +
+ + +
+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+
+ + diff --git a/src/scrollview/docs/partials/scrollview-source.mustache b/src/scrollview/docs/partials/scrollview-source.mustache new file mode 100644 index 00000000000..d911e7e22cc --- /dev/null +++ b/src/scrollview/docs/partials/scrollview-source.mustache @@ -0,0 +1,93 @@ + + +
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras aliquam hendrerit elit id vulputate. Pellentesque pellentesque erat rutrum velit facilisis sodales convallis tellus lacinia. Curabitur gravida mi sit amet nulla suscipit sed congue dolor volutpat. Aenean sem tortor, pretium et euismod in, imperdiet sit amet urna. Ut ante nisi, auctor mattis suscipit a, ullamcorper eget leo. Phasellus sagittis ante at lectus rutrum ut sollicitudin sem malesuada. Duis ultrices sapien et nulla tincidunt malesuada. Mauris ante turpis, dignissim eu tincidunt vitae, placerat quis diam. In augue nisl, cursus at rutrum ut, scelerisque et erat. Suspendisse potenti. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris orci dui, aliquam ut convallis ut, dapibus et erat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Mauris placerat elit id lectus rhoncus in dignissim justo mollis. Donec nec odio sapien. In iaculis euismod felis non laoreet. Mauris ornare varius neque, et congue erat porta a. Aliquam nec auctor lectus. Etiam ut ipsum a nibh iaculis fringilla.

+
+ + \ No newline at end of file diff --git a/src/scrollview/docs/scrollview-base-example.mustache b/src/scrollview/docs/scrollview-base-example.mustache new file mode 100644 index 00000000000..9f5150fc793 --- /dev/null +++ b/src/scrollview/docs/scrollview-base-example.mustache @@ -0,0 +1 @@ +{{>scrollview-base-source}} diff --git a/src/scrollview/docs/scrollview-base.mustache b/src/scrollview/docs/scrollview-base.mustache new file mode 100644 index 00000000000..e2e1546da19 --- /dev/null +++ b/src/scrollview/docs/scrollview-base.mustache @@ -0,0 +1,120 @@ +
+

This example shows how to create a basic ScrollView widget. The base ScrollView widget doesn't have a scrollbar indicator or pagination support.

+
+ + + +

The Basic ScrollView Widget

+ +

In this example, we'll create a basic ScrollView instance, without any additional feature plugins applied. This is the lightest version of the ScrollView widget. In later examples, we'll see how we can pull in different modules and plugins to provide additional features.

+ +

Modules Used

+ +

Since we only need the basic scrollview for this example, we pull in the `scrollview-base` module, the lightest version of ScrollView:

+ +``` +YUI().use('scrollview-base', function(Y) { + ... +}); + +``` + +

The `scrollview-base` module provides a ScrollView without any plugins applied. We'll see in the Scrollview With Scroll Indicators example, that the `scrollview` module provides a base ScrollView bundled with scroll indicator support.

+ +

Instantiating The ScrollView Widget

+ +

The ScrollView provides support for scrollable content. In general this content can be anything, but most often it is in the form of a list, to be scrolled through. For this example, we'll provide the content for the scrollview in the form of a list, as shown below:

+ +``` +
+
    +
  • AC/DC
  • +
  • Aerosmith
  • +
  • Billy Joel
  • +
  • Bob Dylan
  • + ... +
+
+``` +

We add the `yui3-scrollview-loading` class as described in the Widget Progressive Enhancement section, and provide a custom rule to hide this progressively enhanced content while the scrollview is being rendered:

+ +``` +.yui3-js-enabled .yui3-scrollview-loading { + visibility:hidden; +} +``` + +

To instantiate the ScrollView instance, we provide it with the `srcNode` attribute during construction, so it uses the markup above for it's content, as shown below. We could also add the content dynamically, however providing the markup on the page, allows users without JavaScript enabled to still see the content.

+ +``` +YUI().use('scrollview-base', function(Y) { + + var scrollView = new Y.ScrollView({ + id:"scrollview", + srcNode: '#scrollview-content', + height: 310, + flick: { + minDistance:10, + minVelocity:0.3, + axis: "y" + } + }); + + scrollView.render(); +}); +``` + +

For this example, since we want a vertically scrolling ScrollView widget, we also give it a height during construction. Without the height, the ScrollView widget would be as tall as it's content list, and there would be no need to scroll. We also give the ScrollView widget bounding box an id ("scrollview") which we can target in the example CSS. Finally, we constrain flicks so that only flicks along the "y" axis are picked up.

+ +

As the last step, to see the functional ScrollView on the page, we call `scrollView.render()`.

+ +

Controlling Sensitivity

+ +

The scroll dynamics for the ScrollView widget can be controlled by tweaking the following attributes, either during construction or after:

+ +
+
flick
+
Defines the minimum distance and/or minimum velocity which define a flick. It can be set to 0 to disable flick support completely.
+ +
bounce
+
Defines how quickly the velocity of the scrollview content decreases during a bounce (when the scrollview hits the edge of it's scroll limits). It can be set to 0 to disable bounce completely.
+ +
deceleration
+
Defines how quickly the velocity of the scrollview content decreases in response to a flick.
+
+ +

Additional details about these parameters and a few other static properties which can be used to modify scroll dynamics are discussed in the ScrollView documentation.

+ +

Modifying Layout For Small Screen Devices

+ +

This example also shows how you can modify the look and feel for your page/application, based on the size of the device you're delivering it to. For this example, when the maximum width of the device is 480px or less, we provide additional CSS rules which hide additional content and make the scrollview a full screen Widget, using media queries:

+ +``` + +``` + +

The CSS in the above file, which is only served to devices matching the criteria in the `media` attribute, hides additional content and makes the ScrollView fill the width of the browser:

+ +``` +#additional-content { + display:none; +} + +.yui3-scrollview { + border:0; + margin:0; + width:100%; + float:none; +} +``` +

Complete Example Source

+``` +{{>scrollview-base-source}} +``` diff --git a/src/scrollview/docs/scrollview-example.mustache b/src/scrollview/docs/scrollview-example.mustache new file mode 100644 index 00000000000..b616d3b751f --- /dev/null +++ b/src/scrollview/docs/scrollview-example.mustache @@ -0,0 +1 @@ +{{>scrollview-source}} diff --git a/src/scrollview/docs/scrollview-horiz-example.mustache b/src/scrollview/docs/scrollview-horiz-example.mustache new file mode 100644 index 00000000000..224548fbe21 --- /dev/null +++ b/src/scrollview/docs/scrollview-horiz-example.mustache @@ -0,0 +1 @@ +{{>scrollview-horiz-source}} diff --git a/src/scrollview/docs/scrollview-horiz.mustache b/src/scrollview/docs/scrollview-horiz.mustache new file mode 100644 index 00000000000..dabc22cb47d --- /dev/null +++ b/src/scrollview/docs/scrollview-horiz.mustache @@ -0,0 +1,100 @@ +
+

This example shows how to create a ScrollView widget which scrolls horizontally.

+
+ + + +

A Horizontal ScrollView

+ +

In this example, we'll create a ScrollView instance, which scrolls horizontally, as opposed to the vertically scrolling ScrollView we created in the ScrollView With Scroll Indicator example.

+ +

The code for the example is pretty much the same as the code for the ScrollView With Scroll Indicator example. The only difference is that instead of having a ScrollView with a fixed height (and content which overflows that height), we set up a ScrollView with a fixed width (and content which overflows that width).

+ +

Modules Used

+ +

Since we want to use the base ScrollView, along with the ScrollViewScrollbars plugin, we use the `scrollview` module as we did for the vertical ScrollView example:

+ +``` +YUI().use('scrollview', function(Y) { + ... +}); + +``` + +

Instantiating The ScrollView Widget

+ +

As with the vertical ScrollView example, we provide the markup for the ScrollView content on the page, as shown below:

+ +``` + +
+
    +
  • +
  • +
  • +
  • +
+
+``` + +

But this time, when we instantiate the ScrollView instance, we provide a fixed width, as opposed to a fixed height, for the widget.

+ +``` +// Constraining the width, instead of the height for horizontal scrolling +var scrollView = new Y.ScrollView({ + id: 'scrollview', + srcNode: '#scrollview-content', + width: 320, + flick: { + minDistance:10, + minVelocity:0.3, + axis: "x" + } +}); + +scrollView.render(); +``` + +

This causes the list content (which has inline-block applied to each LI by the scrollview CSS) to be wider than the ScrollView, forcing horizontal scrolling. The height of the ScrollView in this case is driven by the height of it's content. As with the ScrollView Base example, we constrain flick handling to a specific axis, in this case the "x" axis.

+ +

The important CSS for the example, which switches the LIs to layout horizontally, is shown below, along with some tweaks requires for IE 6 and 7 support:

+ +``` +/* To layout horizontal LIs */ +#scrollview-content { + white-space:nowrap; +} + +#scrollview-content li { + display:inline-block; + background-color:#fff; +} + +/* For IE 6/7 - needs inline block hack (and the background color mentioned above) */ +#scrollview-content li { + *display:inline; + *zoom:1; +} +``` + +

NOTE: In the initial ScrollView release (3.2.0), the above CSS to layout LIs horizontally was bundled with the ScrollView CSS out-of-the-box. It has been removed as of the 3.3.0 release, since it makes it harder for developers to nest lists inside the ScrollView content, and in general, ScrollView is content agnostic.

+ +

Preventing Native Behavior For Content

+ +

In this example, since we have images which act as drag/flick targets, we need to stop the default image drag/drop behavior in certain browsers (for example gecko and IE), by preventing default on the underlying mousedown event. If we don't prevent default, the image will be natively draggable by default, and interfere with scrolling:

+ +``` +// Prevent the image from being natively dragged +content.delegate("mousedown", function(e) { + e.preventDefault(); +}, "img"); +``` + +

Complete Example Source

+``` +{{>scrollview-horiz-source}} +``` diff --git a/src/scrollview/docs/scrollview-paging-example.mustache b/src/scrollview/docs/scrollview-paging-example.mustache new file mode 100644 index 00000000000..02ece2c25f6 --- /dev/null +++ b/src/scrollview/docs/scrollview-paging-example.mustache @@ -0,0 +1 @@ +{{>scrollview-paging-source}} diff --git a/src/scrollview/docs/scrollview-paging.mustache b/src/scrollview/docs/scrollview-paging.mustache new file mode 100644 index 00000000000..76b8986ac81 --- /dev/null +++ b/src/scrollview/docs/scrollview-paging.mustache @@ -0,0 +1,138 @@ +
+

This example shows how to create a ScrollView widget with pagination support, using the ScrollViewPaginator plugin.

+
+ + + +

ScrollView with Pagination Support

+ +

In this example, we'll create a ScrollView instance which has pagination support enabled. This allows the ScrollView to scroll between discrete page boundaries in the content, as opposed to continuous scrolling. Pagination is only supported for horizontal ScrollViews currently.

+ +

Modules Used

+ +

For this example, since we want both pagination and scrollbar indicator support enabled, we use the `scrollview` module as we did for the Horizontal ScrollView example to get the base ScrollView with scrollbar support.

+ +

Additionally we pull down the `scrollview-paginator` module, which provides the `ScrollViewPaginator` plugin:

+ +``` + +// Pull in the scrollview widget, and the paginator plugin + +YUI().use('scrollview', 'scrollview-paginator', function(Y) { + ... +}); + +``` + +

Instantiating The ScrollView Widget

+ +

As with the Horizontal ScrollView example, we provide the markup for the ScrollView content on the page, as shown below:

+ +``` + +
+
    +
  • +
  • +
  • +
  • +
+
+``` + +

And we instantiate the ScrollView instance in the same way, by providing a fixed width for the widget, and constraining flicks to the "x" axis:

+ +``` +// Constraining the width, instead of the height for horizontal scrolling +var scrollView = new Y.ScrollView({ + id: '#scrollview', + srcNode: '#scrollview-content', + width : 320, + flick: { + minDistance:10, + minVelocity:0.3, + axis: "x" + } +}); +``` + +

As we did in the Horizontal ScrollView example, we add CSS which switches the LIs to layout horizontally:

+ +``` +/* To layout horizontal LIs */ +#scrollview-content { + white-space:nowrap; +} + +#scrollview-content li { + display:inline-block; + background-color:#fff; +} + +/* For IE 6/7 - needs inline block hack (and the background color mentioned above) */ +#scrollview-content li { + *display:inline; + *zoom:1; +} +``` + +

This gives us a ScrollView instance with scrollbar indicator support. However it doesn't have pagination support available yet.

+ +

Plugging In Pagination Support

+ +

To add pagination support to the ScrollView instance, we plug in the `ScrollViewPaginator` plugin, providing the `selector` attribute configuration argument to it. The `selector` tells +the paginator which list of elements inside the ScrollView content box define page boundaries at which the ScrollView should stop when scrolling. For this example, each LI defines a ScrollView page:

+ +``` +scrollView.plug(Y.Plugin.ScrollViewPaginator, { + selector: 'li' +}); +``` + +

The ScrollView now has pagination support activated, and will stop at page boundaries when the user flicks the content, or drags the content past the halfway point of the current page.

+ +

Accessing The Paginator Plugin API

+ +

Similar to the ScrollBar indicator plugin, the ScrollBarPaginator API is now available on the ScrollView instance, on the namespace defined by the plugin, which is `scrollView.pages`. +The `pages` property can be used to manage the page state of the ScrollView, as shown below:

+ +``` +Y.one('#scrollview-next').on('click', Y.bind(scrollView.pages.next, scrollView.pages)); +Y.one('#scrollview-prev').on('click', Y.bind(scrollView.pages.prev, scrollView.pages)); +``` + +

The above code calls the plugin's `next()` and `prev()` methods when the respective button is clicked. The ScrollView Paginator documentation provides additional examples of the API available through the `pages` property.

+ +

Setting Up A Click Listener On the Content

+ +

For this example, we also set up a click listener on the images. For touch devices, the click event does not fire when dragging or flicking, so there's no special handling required when setting up a click listener. However to also support mouse based devices, we need to distinguish between a click and drag or flick. In order to do this we set up a check in our click listener, to make sure we only respond to the click if the ScrollView wasn't dragged, by checking the `lastScrolledAmt` property, which is reset whenever a drag/flick gesture ends:

+ +``` +Y.one("#scrollview-content").delegate("click", function(e) { + // For mouse based devices, we need to make sure the click isn't fired + // and the end of a drag/scroll. We use 2 as an arbitrary threshold. + if (Math.abs(scrollView.lastScrolledAmt) < 2) { + alert(e.currentTarget.getAttribute("alt")); + } +}, "img"); +``` + +

Preventing Native Behavior For Content

+ +

As with the Horizontal ScrollView example, since we have images which act as drag/flick targets, we need to stop the default image drag/drop behavior in certain browsers (for example gecko and IE), by preventing default on the underlying mousedown event. If we don't prevent default, the image will be natively draggable by default, and interfere with scrolling:

+ +``` +// Prevent the image from being natively dragged +content.delegate("mousedown", function(e) { + e.preventDefault(); +}, "img"); +``` + +

Complete Example Source

+``` +{{>scrollview-paging-source}} +``` diff --git a/src/scrollview/docs/scrollview.mustache b/src/scrollview/docs/scrollview.mustache new file mode 100644 index 00000000000..cd40e255227 --- /dev/null +++ b/src/scrollview/docs/scrollview.mustache @@ -0,0 +1,119 @@ +
+

This example shows how to create a ScrollView widget with a scrollbar indicator. It also illustrates a technique for suppressing link behavior during a scroll — a technique you may require if you have a ScrollView instance heavily populated by links, as in this example.

+
+ + + +

The ScrollView Widget With A Scroll Indicator

+ +

In this example, we'll create a ScrollView instance, which also has a scrollbar indicator.

+ +

Modules Used

+ +

Since we want to use the base ScrollView, along with the ScrollViewScrollbars plugin, which provides the scrollbar indicator we use the `scrollview` module, instead of the `scrollview-base` module we used for the basic ScrollView example:

+ +``` +// Pulls in scrollview-base and scrollview-scrollbars plugin +// and plugs it in (at the class level) + +YUI().use('scrollview', function(Y) { + ... +}); +``` + +

The `scrollview` module pulls in the basic ScrollView and also the ScrollViewScrollbars plugin. It has code which plugs the scrollbar plugin into the ScrollView base class:

+ +``` +Y.Base.plug(Y.ScrollView, Y.Plugin.ScrollViewScrollbars); +``` + +

Instantiating The ScrollView Widget

+ +

As with the Base ScrollView example, we provide the markup for the ScrollView content on the page, as shown below:

+ +``` +
+
    +
  • AC/DC
  • +
  • Aerosmith
  • +
  • Billy Joel
  • +
  • Bob Dylan
  • + ... +
+
+``` + +

And we instantiate the ScrollView instance in the same way, providing the `srcNode` attribute during construction, so it uses the markup above for it's content:

+ +``` +YUI().use('scrollview', function(Y) { + + var scrollView = new Y.ScrollView({ + id: "scrollview", + srcNode: '#scrollview-content', + height: 310, + flick: { + minDistance:10, + minVelocity:0.3, + axis: "y" + } + }); + + scrollView.render(); +}); +``` + +

Again, for this example, since we want a vertically scrolling ScrollView widget, we also give it a height during construction and constrain flicks to the "y" axis.

+ +

As the last step, to see the functional ScrollView on the page, we call `scrollView.render()`.

+ +

The only difference, compared to the Base ScrollView example, is that the ScrollViewScrollbars plugin has been pulled down and plugged in by the `scrollview` module code shown above, so the ScrollView now has a scroll indicator. The scroll indicator is styled with rounded corners in browsers which support CSS rounded corners natively.

+ +

Accessing The Scrollbars Plugin API

+ +

As discussed in the ScrollBar Plugin documentation, the API to control scrollbars is available on the scrollview instance, through the `scrollView.scrollbars` property. The ScrollBar plugin doesn't have too complex of an api, just a few methods to hide and show the scrollbars:

+ +``` + /* + scrollView.scrollbars is an instance of the ScrollViewScrollbars plugin + */ + scrollView.scrollbars.hide(); + scrollView.scrollbars.show(); + scrollView.scrollbars.flash(); +}); +``` + +

Suppressing Default Link Behavior

+ +

In this example, the scrolling surface is populated with links. To prevent links' default action (page navigation) from taking place after a scroll, we look at the `lastScrolledAmt` property of our ScrollView instance; on a simple click, that number will be very close to zero, whereas after scroll that number will be much higher. In this case, we're using a 2px threshold.

+ +``` +var content = scrollView.get("contentBox"); + +content.delegate("click", function(e) { + // Prevent links from navigating as part of a scroll gesture + if (Math.abs(scrollView.lastScrolledAmt) > 2) { + e.preventDefault(); + Y.log("Link behavior suppressed.") + } +}, "a"); +``` + +

We also prevent default on mousedown, to prevent the native "drag link to desktop" behavior on certain browsers.

+ +``` +content.delegate("mousedown", function(e) { + // Prevent default anchor drag behavior, on browsers + // which let you drag anchors to the desktop + e.preventDefault(); +}, "a"); +``` + +

Complete Example Source

+``` +{{>scrollview-source}} +``` diff --git a/src/widget/docs/assets/arrows.png b/src/widget/docs/assets/arrows.png new file mode 100644 index 00000000000..6e3e5999231 Binary files /dev/null and b/src/widget/docs/assets/arrows.png differ diff --git a/src/widget/docs/assets/img/ajax-loader.gif b/src/widget/docs/assets/img/ajax-loader.gif new file mode 100644 index 00000000000..fe2cd23b3a3 Binary files /dev/null and b/src/widget/docs/assets/img/ajax-loader.gif differ diff --git a/src/widget/docs/assets/listbox.js b/src/widget/docs/assets/listbox.js new file mode 100644 index 00000000000..8414bef3cbb --- /dev/null +++ b/src/widget/docs/assets/listbox.js @@ -0,0 +1,149 @@ +YUI.add('listbox', function(Y) { + +Y.ListBox = Y.Base.create("listbox", Y.Widget, [Y.WidgetParent, Y.WidgetChild], { + + CONTENT_TEMPLATE : "
    ", + + bindUI: function() { + + if (this.isRoot()) { + this.get("boundingBox").plug(Y.Plugin.NodeFocusManager, { + descendants: ".yui3-option", + keys: { + next: "down:40", // Down arrow + previous: "down:38" // Up arrow + }, + circular: true + }); + } + + this.get("boundingBox").on("contextmenu", function (event) { + event.preventDefault(); + }); + + // Setup listener to control keyboard based single/multiple item selection + this.on("option:keydown", function (event) { + + var item = event.target, + domEvent = event.domEvent, + keyCode = domEvent.keyCode, + direction = (keyCode == 40); + + if (this.get("multiple")) { + if (keyCode == 40 || keyCode == 38) { + if (domEvent.shiftKey) { + this._selectNextSibling(item, direction); + } else { + this.deselectAll(); + this._selectNextSibling(item, direction); + } + } + } else { + if (keyCode == 13 || keyCode == 32) { + domEvent.preventDefault(); + item.set("selected", 1); + } + } + }); + + // Setup listener to control mouse based single/multiple item selection + this.on("option:mousedown", function (event) { + + var item = event.target, + domEvent = event.domEvent, + selection; + + if (this.get("multiple")) { + if (domEvent.metaKey) { + item.set("selected", 1); + } else { + this.deselectAll(); + item.set("selected", 1); + } + } else { + item.set("selected", 1); + } + + }); + }, + + // Helper Method, to find the correct next sibling, taking into account nested ListBoxes + _selectNextSibling : function(item, direction) { + + var parent = item.get("parent"), + method = (direction) ? "next" : "previous", + + // Only go circular for the root listbox + circular = (parent === this), + sibling = item[method](circular); + + if (sibling) { + // If we found a sibling, it's either an Option or a ListBox + if (sibling instanceof Y.ListBox) { + // If it's a ListBox, select it's first child (in the direction we're headed) + sibling.selectChild((direction) ? 0 : sibling.size() - 1); + } else { + // If it's an Option, select it + sibling.set("selected", 1); + } + } else { + // If we didn't find a sibling, we're at the last leaf in a nested ListBox + parent[method](true).set("selected", 1); + } + }, + + NESTED_TEMPLATE : '
  • {label}
  • ', + + renderUI: function () { + + if (this.get("depth") > -1) { + + var tokens = { + labelClassName : this.getClassName("label"), + nestedOptionClassName : this.getClassName("option"), + label : this.get("label") + }, + liHtml = Y.substitute(this.NESTED_TEMPLATE, tokens), + li = Y.Node.create(liHtml), + + boundingBox = this.get("boundingBox"), + parent = boundingBox.get("parentNode"); + + li.appendChild(boundingBox); + parent.appendChild(li); + } + } + +}, { + ATTRS : { + defaultChildType: { + value: "Option" + }, + label : { + validator: Y.Lang.isString + } + } +}); + +Y.Option = Y.Base.create("option", Y.Widget, [Y.WidgetChild], { + + CONTENT_TEMPLATE : "", + BOUNDING_TEMPLATE : "
  • ", + + renderUI: function () { + this.get("contentBox").setContent(this.get("label")); + } + +}, { + + ATTRS : { + label : { + validator: Y.Lang.isString + }, + tabIndex: { + value: -1 + } + } +}); + +}, '3.1.0' ,{requires:['substitute', 'widget', 'widget-parent', 'widget-child', 'node-focusmanager']}); diff --git a/src/widget/docs/assets/myplugin.js.txt b/src/widget/docs/assets/myplugin.js.txt new file mode 100644 index 00000000000..6180258932b --- /dev/null +++ b/src/widget/docs/assets/myplugin.js.txt @@ -0,0 +1,123 @@ +// START WRAPPER: The YUI.add wrapper is added by the build system, when you use YUI Builder to build your component from the raw source in this file +// YUI.add("myplugin", function(Y) { + + /* Any frequently used shortcuts, strings and constants */ + var Lang = Y.Lang; + + /* MyPlugin class constructor */ + function MyPlugin(config) { + MyPlugin.superclass.constructor.apply(this, arguments); + } + + /* + * Required NAME static field, to identify the class and + * used as an event prefix, to generate class names etc. (set to the + * class name in camel case). + */ + MyPlugin.NAME = "myPlugin"; + + /* + * Required NS static field, to identify the property on the host which will + * be used to refer to the plugin instance ( e.g. host.feature.doSomething() ). + */ + MyPlugin.NS = "feature"; + + /* + * The attribute configuration for the plugin. This defines the core user-facing state of the plugin + */ + MyPlugin.ATTRS = { + + attrA : { + value: "A" // The default value for attrA, used if the user does not set a value during construction. + + /* + , valueFn: "_defAttrAVal" // Can be used as a substitute for "value", when you need access to "this" to set the default value. + + , setter: "_setAttrA" // Used to normalize attrA's value while during set. Refers to a prototype method, to make customization easier + , getter: "_getAttrA" // Used to normalize attrA's value while during get. Refers to a prototype method, to make customization easier + , validator: "_validateAttrA" // Used to validate attrA's value before updating it. Refers to a prototype method, to make customization easier + + , readOnly: true // Cannot be set by the end user. Can be set by the component developer at any time, using _set + , writeOnce: true // Can only be set once by the end user (usually during construction). Can be set by the component developer at any time, using _set + + , lazyAdd: false // Add (configure) the attribute during initialization. + + // You only need to set lazyAdd to false if your attribute is + // setting some other state in your setter which needs to be set during initialization + // (not generally recommended - the setter should be used for normalization. + // You should use listeners to update alternate state). + + , broadcast: 1 // Whether the attribute change event should be broadcast or not. + */ + } + + // ... attrB, attrC, attrD ... attribute configurations. + + // Can also include attributes for the super class if you want to override or add configuration parameters + }; + + /* MyPlugin extends the base Plugin.Base class */ + Y.extend(MyPlugin, Y.Plugin.Base, { + + initializer: function() { + /* + * initializer is part of the lifecycle introduced by + * the Base class. It is invoked during construction, when + * the plugin is plugged into the host, and can be used to + * register listeners or to inject logic before or after methods + * on the host. + * + * It does not need to invoke the superclass initializer. + * init() will call initializer() for all classes in the hierarchy. + */ + + // See Y.Do.before, Y.Do.after + this.beforeHostMethod("show", this._beforeHostShowMethod); + this.afterHostMethod("show", this._afterHostShowMethod); + + // See Y.EventTarget.on, Y.EventTarget.after + this.onHostEvent("render", this._onHostRenderEvent); + this.afterHostEvent("render", this._afterHostRenderEvent); + + }, + + destructor : function() { + /* + * destructor is part of the lifecycle introduced by + * the Base class. It is invoked when the plugin is unplugged. + * + * Any listeners registered using Plugin.Base's onHostEvent/afterHostEvent methods, + * or any methods displaced using it's beforeHostMethod/afterHostMethod methods + * will be detached/restored by Plugin.Base's destructor. + * + * We only need to clean up anything we change on the host + * + * It does not need to invoke the superclass destructor. + * destroy() will call initializer() for all classes in the hierarchy. + */ + }, + + /* Supporting Methods */ + + _onHostRenderEvent : function(e) { + /* React on the host render event */ + }, + + _afterHostRenderEvent : function(e) { + /* React after the host render event */ + }, + + _beforeHostShowMethod : function() { + /* Inject logic before the host's show method is called. */ + }, + + _afterHostShowMethod : function() { + /* Inject logic after the host's show method is called. */ + } + + }); + + Y.namespace("Plugin.MyApp").MyPlugin = MyPlugin; + +// }, "3.2.0", {requires:["plugin"]}); +// END WRAPPER diff --git a/src/widget/docs/assets/mywidget.js.txt b/src/widget/docs/assets/mywidget.js.txt index cc6127bc063..6c65809ecea 100644 --- a/src/widget/docs/assets/mywidget.js.txt +++ b/src/widget/docs/assets/mywidget.js.txt @@ -182,5 +182,5 @@ Y.namespace("MyApp").MyWidget = MyWidget; -// }, "3.1.0", {requires:["widget", "substitute"]}); +// }, "3.2.0", {requires:["widget", "substitute"]}); // END WRAPPER diff --git a/src/widget/docs/assets/news.php b/src/widget/docs/assets/news.php new file mode 100644 index 00000000000..9e4bca7c62e --- /dev/null +++ b/src/widget/docs/assets/news.php @@ -0,0 +1,45 @@ + + {$result['Title']} + {$result['NewsSource']} + +END_OF_HTML; + } // end if +} // end foreach + +?> +
    \ No newline at end of file diff --git a/src/widget/docs/component.json b/src/widget/docs/component.json index 1ed9bd3ea89..166c538ee83 100644 --- a/src/widget/docs/component.json +++ b/src/widget/docs/component.json @@ -4,6 +4,54 @@ "description": "Widget is the foundation class from which all YUI 3 widgets are derived.", "author" : "sdesai", - "tags": ["widget", "widget"], - "use" : ["widget"] + "tags": ["widget", "infrastructure"], + "use" : ["widget"], + + "examples" : [ + { + "name" : "widget-extend", + "displayName": "Extending the Base Widget Class", + "description": "Shows how to extend the base widget class, to create your own Widgets.", + "modules" : ["widget", "event"], + "tags" : ["widget", "infrastructure"], + + "hideTableOfContents": true + }, + { + "name" : "widget-build", + "displayName": "Creating Custom Widget Classes With Extensions", + "description": "Shows how to use Base.create and mix/match extensions to create custom Widget classes.", + "modules" : ["widget", "base", "widget-postion", "widget-position", "widget-position-align", "widget-stdmod"], + "tags" : ["widget", "infrastructure", "extentions"], + + "hideTableOfContents": true + }, + { + "name" : "widget-plugin", + "displayName": "Creating a Widget Plugin", + "description": "Shows how to create an IO plugin for Widget.", + "modules" : ["widget", "plugin"], + "tags" : ["widget", "infrastructure", "plugins"], + + "hideTableOfContents": true + }, + { + "name" : "widget-tooltip", + "displayName": "Creating a Simple Tooltip Widget With Extensions", + "description": "Shows how to extend the Widget class, and add WidgetPosition and WidgetStack to create a Tooltip widget class.", + "modules" : ["widget", "base", "widget-stack", "widget-positon"], + "tags" : ["widget", "infrastructure", "tooltip", "extensions"], + + "hideTableOfContents": true + }, + { + "name" : "widget-parentchild-listbox", + "displayName": "Creating a Hierarchical ListBox Widget", + "description": "Shows how to extend the Widget class, and add WidgetParent and WidgetChild to create a simple ListBox widget.", + "modules" : ["widget", "widget-parent", "widget-child", "base"], + "tags" : ["widget", "infrastructure"], + + "hideTableOfContents": true + } + ] } diff --git a/src/widget/docs/index.mustache b/src/widget/docs/index.mustache index 502a7360c58..0328625adb2 100644 --- a/src/widget/docs/index.mustache +++ b/src/widget/docs/index.mustache @@ -487,7 +487,7 @@ You can then implement the `initializer`, `destructor`, `renderUI`, `bindUI` and along with the methods which support the attribute state handling and API.

    -The "Extending The Widget Class" example walks you through the step-by-step process involved in implementing a Spinner widget using this template structure. The example along with the template file should provide a good jumping off point for developing your own widgets. +The "Extending The Widget Class" example walks you through the step-by-step process involved in implementing a Spinner widget using this template structure. The example along with the template file should provide a good jumping off point for developing your own widgets.

    Additionally, the template structure shown above is captured in this "MyWidget" template file, which you can use as a starting point to develop your own widgets.

    @@ -522,7 +522,7 @@ These code reuse mechanisms are known as Plugins<

    Something baked into the class, but implemented so that it can also be re-used to build other classes.

  • -

    WidgetParent, WidgetChild and WidgetPosition, WidgetStack are good examples of extensions.

    +

    WidgetParent, WidgetChild and WidgetPosition, WidgetStack are good examples of extensions.

    A `Tree` widget class always needs Parent/Child support. However, so does a `Menu` widget class. We want to reuse the Parent/Child support across both classes, without forcing them to extend a shared base class. @@ -544,7 +544,7 @@ These code reuse mechanisms are known as Plugins<

    A feature which you may only want to apply to one instance, out of the ten instances of your widget on a page, is a plugin.

  • -

    The Animation and IO plugin examples are good use cases.

    +

    The Animation and IO plugin examples are good use cases.

    You don't want to have to bake Animation or IO support into a class (potentially resulting in the need to ship `MyAnimatedWidget`, `MyIOEnabledWidget`, `MyAnimatedAndIOEnabledWidget` and `MyWidget` classes). @@ -657,16 +657,16 @@ These code reuse mechanisms are known as Plugins<

    Below are some examples showing how you can use some of these extensions:

    You can also look at some of the bundled widgets which are built using extensions:

      -
    • Overlay

      Uses widget-position, widget-position-align, widget-position-constrain, widget-stack, widget-stdmod

    • -
    • TabView

      Uses widget-parent, widget-child

    • +
    • Overlay

      Uses widget-position, widget-position-align, widget-position-constrain, widget-stack, widget-stdmod

    • +
    • TabView

      Uses widget-parent, widget-child

    The "MyExtension" template file provides a starting point for you to create your own extensions.

    @@ -679,9 +679,9 @@ These code reuse mechanisms are known as Plugins<
  • Widget Animation Plugin (api documentation)
  • Widget (and Node) Drag/Drop Plugin (api documentation)
  • -
  • Creating Widget Plugins (example)
  • -
  • Creating An Overlay IO Plugin (example)
  • -
  • Creating An Overlay Animation Plugin (example)
  • +
  • Creating Widget Plugins (example)
  • +
  • Creating An Overlay IO Plugin (example)
  • +
  • Creating An Overlay Animation Plugin (example)
  • Additionally, the YUI Gallery is another source for plugins which provide additional functionality for YUI Widgets, such as the Modal Overlay Plugin and the Widget IO Plugin.

    diff --git a/src/widget/docs/partials/widget-build-source.mustache b/src/widget/docs/partials/widget-build-source.mustache new file mode 100644 index 00000000000..ff6490edd03 --- /dev/null +++ b/src/widget/docs/partials/widget-build-source.mustache @@ -0,0 +1,186 @@ +
      +
    1. Widget with WidgetStdMod + +
      + + + + + +
      +
      Module Header
      +
      Module Body
      +
      Module Footer
      +
      + +

      Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam.

      +
      +
    2. + +
    3. Widget with WidgetPosition, WidgetStack + +
      + + + + + +
      Positionable Widget
      + +

      + + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est. +

      +
      +
    4. + +
    5. Widget with WidgetPosition, WidgetStack, WidgetPositionAlign + +
      + +

      + + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc pretium quam eu mi varius pulvinar. Duis orci arcu, ullamcorper sit amet, luctus ut, interdum ac, quam. Pellentesque euismod. Nam tincidunt, purus in ultrices congue, urna neque posuere arcu, aliquam tristique purus sapien id nulla. Etiam rhoncus nulla at leo. Cras scelerisque nisl in nibh. Sed eget odio. Morbi elit elit, porta a, convallis sit amet, rhoncus non, felis. Mauris nulla pede, pretium eleifend, porttitor at, rutrum id, orci. Quisque non urna. Nulla aliquam rhoncus est. +

      +
      +
    6. +
    + + diff --git a/src/widget/docs/partials/widget-extend-source.mustache b/src/widget/docs/partials/widget-extend-source.mustache new file mode 100644 index 00000000000..add43e7275f --- /dev/null +++ b/src/widget/docs/partials/widget-extend-source.mustache @@ -0,0 +1,398 @@ +
    + A basic spinner widget: +

    Click the buttons, or the arrow up/down and page up/down keys on your keyboard to change the spinner's value

    +
    + + diff --git a/src/widget/docs/partials/widget-parentchild-listbox-source.mustache b/src/widget/docs/partials/widget-parentchild-listbox-source.mustache new file mode 100644 index 00000000000..1c9cddc225c --- /dev/null +++ b/src/widget/docs/partials/widget-parentchild-listbox-source.mustache @@ -0,0 +1,63 @@ +

    Selected: None

    + +
    + + \ No newline at end of file diff --git a/src/widget/docs/partials/widget-plugin-source.mustache b/src/widget/docs/partials/widget-plugin-source.mustache new file mode 100644 index 00000000000..4b913f3c570 --- /dev/null +++ b/src/widget/docs/partials/widget-plugin-source.mustache @@ -0,0 +1,14 @@ +
    + diff --git a/src/widget/docs/partials/widget-tooltip-source.mustache b/src/widget/docs/partials/widget-tooltip-source.mustache new file mode 100644 index 00000000000..31c5f7f2453 --- /dev/null +++ b/src/widget/docs/partials/widget-tooltip-source.mustache @@ -0,0 +1,467 @@ +
    +
    Tooltip One (content from title)
    +
    Tooltip Two (content set in event listener)
    +
    Tooltip Three (content from lookup)
    +
    Tooltip Four (content from title)
    + +
    + + diff --git a/src/widget/docs/widget-build.mustache b/src/widget/docs/widget-build.mustache new file mode 100644 index 00000000000..2913ae8612c --- /dev/null +++ b/src/widget/docs/widget-build.mustache @@ -0,0 +1,355 @@ + + +
    +

    This example shows how you can mix and match the `WidgetPosition`, `WidgetPositionAlign`, `WidgetStack` and `WidgetStdMod` extensions to build custom versions of the `Widget` class, using `Base.create`.

    +
    + +
    + {{>widget-build-source}} +
    + +

    Creating Custom Widget Classes

    + +

    The `Base` class provides a `create` method which can be used to create custom versions of classes which derive from `Base` by adding extension classes to them.

    + +

    Widget currently ships with four such extensions: `WidgetPosition`, `WidgetStack`, `WidgetPositionAlign` and `WidgetStdMod`. +These extensions are used to create the basic `Overlay` widget, but can also be used individually, to create custom versions of the base `Widget` class.

    + +

    Widget with WidgetStdMod support

    + +

    Adding the `WidgetStdMod` extension to Widget, creates a statically positioned Widget, with support for standard module format sections - header, body and footer, which maybe useful in portal type use cases, where the positioning/stacking capabilities which come bundled with Overlay are not required.

    + +

    To create a custom class, we use `Base.create`, which is described in detail on the documention page for Base.

    + +

    We pass in `Widget` as the main class we want to add extensions to, and `WidgetStdMod` as the extension we'd like added to the main class:

    + +``` +var StandardModule = Y.Base.create("standardModule", Y.Widget, [Y.WidgetStdMod]); + +// Render from Markup +var stdmod = new StandardModule({ + contentBox: "#widget1", + width:"12em", + height:"12em" +}); +stdmod.render(); +``` + +

    `Base.create` will:

    +
      +
    1. Create a new class which extends `Widget`
    2. +
    3. Aggregate known `Base` and `Widget` fields, such as `ATTRS` and `HTML_PARSER` from `WidgetStdMod` on the new class
    4. +
    5. Augment prototype methods from `WidgetStdMod` onto the new class prototype
    6. +
    + +

    The first argument to create is the `NAME` of the new class we are creating, just like the `NAME` we define when extending the Widget class directly.

    + +

    Note that the `Widget` class is unchanged, allowing you to still create `Widget` instances without any standard module support, along with `StandardModule` instances which have standard module support.

    + +

    Testing It Out

    + +

    The example attempts to set content on an instance of the newly created `StandardModule` class, using the `setStdModContent` method which is added by the extension (which would otherwise not exist on the Widget instance).

    + +``` +var contentInput = Y.one("#content"); +var sectionInput = Y.one("#section"); + +// This should work, since the StandardModule widget has settable +// header/body/footer sections +Y.on("click", function(e) { + + var content = Y.Escape.html(contentInput.get("value")); + var section = sectionInput.get("value"); + + stdmod.setStdModContent(section, content); + +}, "#setContent"); +``` + +

    To verify that no unrequested features are added, we also attempt to move the instance using the `move` method, which is not part of the base Widget class, and would be added by the `WidgetPosition` extension. This verifies that the other example classes we'll create, which do create new classes which use `WidgetPosition`, have not modified the base Widget class.

    + +``` +// This shoud fail, since the StandardModule widget is not positionable +Y.on("click", function(e) { + try { + stdmod.move([0,0]); + } catch (e) { + alert("move() is " + typeof stdmod.move + ", stdmod.hasImpl(Y.WidgetPosition) : " + + stdmod.hasImpl(Y.WidgetPosition)); + } +}, "#tryMove"); +``` + +

    Note that `Base.create` adds a `hasImpl` method to the built class, which allows you to query whether or not it has a particular extension applied.

    + +

    CSS Considerations

    + +

    We need to define the CSS which goes with this new `StandardModule` class we have created. The only rule really required out of the box is the rule which handles visibility (`yui-standardmodule-hidden`). The "standardmodule" used in the class name comes from the `NAME` property we set up for the new class, and is used to prefix all state related classes added to the widgets bounding box. +Since the `StandardModule` class is not positionable, we use `display:none` to define the `hidden` state.

    + +``` + +/* Visibility - How to handle visibility for this new widget */ +.yui3-standardmodule-hidden { + display:none; +} +``` + +

    The other "yui-standardmodule" rules are only used to create the required look/feel for this particular example, and do not impact the StandardModule widget's functionality.

    + +

    Widget with WidgetPosition and WidgetStack support

    + +

    As with `StandardModule`, we use `Base.create` to create the new `Positionable` widget class. This time we add `WidgetPosition` and `WidgetStack` support to the base `Widget` class to create a basic XY positionable widget, with shimming and z-index support.

    + +``` +var Positionable = Y.Base.create("positionable", Y.Widget, + [Y.WidgetPosition, Y.WidgetStack]); + +// Render from markup +var positionable = new Positionable({ + contentBox: "#widget2", + width:"10em", + height:"10em", + zIndex:1 +}); +positionable.render("#widget2-example"); + +var xy = Y.one("#widget2-example > p").getXY(); + +positionable.move(xy[0], xy[1]); +``` + +

    We don't add `WidgetPositionAlign` or `WidgetStdMod` support, so the widget doesn't have extended positioning support (align, center) or standard module support. Hence we position it manually using the `move` method which the `WidgetPosition` extension provides.

    + +

    Testing It Out

    + +

    We should now be able to invoke the `move` method on an instance of the newly created `Positionable` class:

    + +``` +// This should work, since Positionable has basic XY Positioning support +Y.on("click", function(e) { + var x = parseInt(xInput.get("value")); + var y = parseInt(yInput.get("value")); + + positionable.move(x,y); + +}, "#move"); +``` + +

    And, as with the `StandardModule` class, we should not be allowed to invoke any methods from an extension which we didn't request:

    + +``` +// This should fail, since Positionable does not have Standard Module sections +Y.on("click", function(e) { + try { + positionable.setStdModContent("header", "new content"); + } catch (e) { + alert("setStdModContent() is " + typeof positionable.setStdModContent + + ", positionable.hasImpl(Y.WidgetStdMod) : " + positionable.hasImpl(Y.WidgetStdMod)); + } +}, "#tryContent"); +``` + +

    CSS Considerations

    + +

    Since now we have a positionable widget, with z-index support, we set the widget to be absolutely positioned by default, and control it's hidden state using `visibility` as opposed to `display`

    + +``` +/* Define absolute positioning as the default for positionable widgets */ +.yui3-positionable { + position:absolute; +} + +/* + In order to be able to position the widget when hidden, we define hidden + to use visibility, as opposed to display +*/ +.yui3-positionable-hidden { + visibility:hidden; +} +``` + +

    Widget with WidgetPosition, WidgetStack and WidgetPositionAlign support

    + +

    Lastly, we'll attempt to create a new widget class, which, in addition to basic positioning and stacking support, also has extended positioning support, allowing us to align it with other elements on the page.

    + +

    Again, we use `Base.create` to create our new `Alignable` widget class, by combining `WidgetPosition`, `WidgetStack` and `WidgetPositionAlign` with the base widget class:

    + +``` +var Alignable = Y.Base.create("alignable", Y.Widget, + [Y.WidgetPosition, Y.WidgetPositionAlign, Y.WidgetStack]); + +var alignable = new Alignable({ + width:"13em", + align : { + node: "#widget3-example", + points: ["cc", "cc"] + }, + zIndex:1 +}); +alignable.get("contentBox").set("innerHTML", + 'Alignable Widget

    #widget3-example

    \ +

    [center, center]

    '); +alignable.render("#widget3-example"); +``` + +

    Testing It Out

    + +

    We'll attempt to align an instance of the `Alignable` class, using some of the additional attributes which `WidgetPositionAlign` adds to the base `Widget` class: `align` and `centered`:

    + +``` +// Align left-center egde of widget to +// right-center edge of the node with id "widget3-example" +alignable.set("align", {node:"#widget3-example", points:["lc", "rc"]}); + +// Align top-right corner of widget to +// bottom-right corner of the node with id "widget3-example" +alignable.set("align", {node:"#widget3-example", points:["tr", "br"]}); + +// Center the widget in the node with id "widget3-example" +alignable.set("centered", "widget3-example"); + +// Align the right-center edge of the widget to +// the right center edge of the viewport (since a node is not provided to 'align') +alignable.set("align", {points:["rc", "rc"]}); + +// Center the widget in the viewport (wince a node is not provided to 'centered') +alignable.set("centered", true); + +// Return the node to it's original alignment +// (centered in the node with id "widget3-example") +// NOTE: centered is a shortcut for align : { points:["cc", "cc"] } +alignable.set("align", {node:"#widget3-example", points:["cc", "cc"]}); +``` + +

    CSS Considerations

    + +

    The `Alignable` widget class, has the same core CSS rules as the `Positionable` class, to define how it is positioned and how it is hidden:

    + +``` +/* Define absolute positioning as the default for alignable widgets */ +.yui3-alignable { + position:absolute; +} + +/* + In order to be able to position the widget when hidden, we define hidden + to use visibility, as opposed to display +*/ +.yui3-alignable-hidden { + visibility:hidden; +} +``` + +

    Complete Example Source

    +``` +{{>widget-build-source}} +``` diff --git a/src/widget/docs/widget-extend.mustache b/src/widget/docs/widget-extend.mustache new file mode 100644 index 00000000000..b2ae94e88a6 --- /dev/null +++ b/src/widget/docs/widget-extend.mustache @@ -0,0 +1,564 @@ + + +
    +

    This example shows how to extend the base `Widget` class to create a simple, re-usable spinner control. The `Spinner` class created in the example is not intended to be a fully featured spinner. It is used here as a concrete example, to convey some of the key concepts to keep in mind when extending the `Widget` class.

    +
    + +
    + {{>widget-extend-source}} +
    + +

    Extending The `Widget` Class

    + +

    Basic Class Structure

    + +

    Widgets classes follow the general pattern implemented by the `Spinner` class, shown in the code snippet below. The basic pattern for setting up a new widget class involves:

    + +
      +
    1. Defining the constructor function for the new widget class, which invokes the superclass constructor to kick off the initialization chain (line 2)
    2. +
    3. Defining the static `NAME` property for the class, which is normally the class name in camel case, and is used to prefix events and CSS classes fired/created by the class (line 11)
    4. +
    5. Defining the static `ATTRS` property for the class, which defines the set of attributes which the class will introduce, in addition to the superclass attributes (line 18-57)
    6. +
    7. Extending the `Widget` class, and adding/overriding any prototype properties/methods (line 61)
    8. +
    + +``` +/* Spinner class constructor */ +function Spinner(config) { + Spinner.superclass.constructor.apply(this, arguments); +} + +/* + * Required NAME static field, to identify the Widget class and + * used as an event prefix, to generate class names etc. (set to the + * class name in camel case). + */ +Spinner.NAME = "spinner"; + +/* + * The attribute configuration for the Spinner widget. Attributes can be + * defined with default values, get/set functions and validator functions + * as with any other class extending Base. + */ +Spinner.ATTRS = { + // The minimum value for the spinner. + min : { + value:0 + }, + + // The maximum value for the spinner. + max : { + value:100 + }, + + // The current value of the spinner. + value : { + value:0, + validator: function(val) { + return this._validateValue(val); + } + }, + + // Amount to increment/decrement the spinner when the buttons, + // or arrow up/down keys are pressed. + minorStep : { + value:1 + }, + + // Amount to increment/decrement the spinner when the page up/down keys are pressed. + majorStep : { + value:10 + }, + + // The localizable strings for the spinner. This attribute is + // defined by the base Widget class but has an empty value. The + // spinner is simply providing a default value for the attribute. + strings: { + value: { + tooltip: "Press the arrow up/down keys for minor increments, \ + page up/down for major increments.", + increment: "Increment", + decrement: "Decrement" + } + } +}; + +Y.extend(Spinner, Widget, { + // Methods/properties to add to the prototype of the new class + ... +}); +``` + +

    Note that these steps are the same for any class which is derived from `Base`, nothing Widget-specific is involved yet. +Widget adds the concept of a rendered UI to the existing Base lifecycle (viz. init, destroy and attribute state configuration), which we'll see show up in Widget-specific areas below.

    + +

    The HTML_PARSER Property

    + +

    +The first Widget-specific property `Spinner` implements is the static `HTML_PARSER` property. It is used to set the initial widget configuration based on markup, providing basic progressive enhancement support. +

    +

    +The value of the `HTML_PARSER` property is an object literal, where each property is a widget attribute name, and the value is either a selector string (if the attribute is a node reference) or a function which is executed to provide +a value for the attribute from the markup on the page. Markup is essentially thought of as an additional data source for the user to set initial attribute values, outside of the configuration object passed to the constructor +(values passed to the constructor will take precedence over values picked up from markup). +

    + +

    For `Spinner`, `HTML_PARSER` defines a function for the `value` attribute, which sets the initial value of the spinner based on an input field if present in the markup.

    + +``` +/* + * The HTML_PARSER static constant is used by the Widget base class to populate + * the configuration for the spinner instance from markup already on the page. + * + * The Spinner class attempts to set the value of the spinner widget if it + * finds the appropriate input element on the page. + */ +Spinner.HTML_PARSER = { + value: function (contentBox) { + var node = contentBox.one("." + Spinner.INPUT_CLASS); + return (node) ? parseInt(node.get("value")) : null; + } +}; +``` + +

    Lifecycle Methods: initializer, destructor

    + +

    The `initializer` and `destructor` lifecycle methods are carried over from `Base`, and can be used to set up initial state during construction, and clean up state during destruction respectively.

    + +

    For `Spinner`, there is nothing special we need to do in the `initializer` (attribute setup is already taken care of), but it's left in the example to round out the lifecycle discussion.

    + +

    The `destructor` takes care of detaching any event listeners `Spinner` adds outside of the bounding box (event listeners on/inside the bounding box are purged by `Widget`'s `destructor`).

    + +``` +/* + * initializer is part of the lifecycle introduced by + * the Widget class. It is invoked during construction, + * and can be used to set up instance specific state. + * + * The Spinner class does not need to perform anything + * specific in this method, but it is left in as an example. + */ +initializer: function(config) { + // Not doing anything special during initialization +}, + +/* + * destructor is part of the lifecycle introduced by + * the Widget class. It is invoked during destruction, + * and can be used to clean up instance specific state. + * + * The spinner cleans up any node references it's holding + * onto. The Widget classes destructor will purge the + * widget's bounding box of event listeners, so spinner + * only needs to clean up listeners it attaches outside of + * the bounding box. + */ +destructor : function() { + this._documentMouseUpHandle.detach(); + + this.inputNode = null; + this.incrementNode = null; + this.decrementNode = null; +} +``` + +

    Rendering Lifecycle Methods: renderer, renderUI, bindUI, syncUI

    + +

    Widget adds a `render` method to the `init` and `destroy` lifecycle methods provided by Base. The `init` and `destroy` methods invoke the corresponding `initializer` and `destructor` implementations for the widget. Similarly, the `render` method invokes the `renderer` implementation for the widget. Note that the `renderer` method is not chained automatically, unlike the `initializer` and `destructor` methods.

    + +

    The `Widget` class already provides a default `renderer` implementation, which invokes the following abstract methods in the order shown (with their respective responsibilities):

    + +
      +
    1. `renderUI()` : responsible for creating/adding elements to the DOM to render the widget.
    2. +
    3. `bindUI()` : responsible for binding event listeners (both attribute change and DOM event listeners) to 'activate' the rendered UI.
    4. +
    5. `syncUI()` : responsible for updating the rendered UI based on the current state of the widget.
    6. +
    + +

    Since the `Spinner` class has no need to modify the `Widget` `renderer` implementation, it simply implements the above 3 methods to handle the render phase:

    + +``` +/* + * For spinner the method adds the input (if it's not already + * present in the markup), and creates the increment/decrement buttons + */ +renderUI : function() { + this._renderInput(); + this._renderButtons(); +}, + +/* + * For spinner, the method: + * + * - Sets up the attribute change listener for the "value" attribute + * + * - Binds key listeners for the arrow/page keys + * - Binds mouseup/down listeners on the boundingBox, document respectively. + * - Binds a simple change listener on the input box. + */ +bindUI : function() { + this.after("valueChange", this._afterValueChange); + + var boundingBox = this.get("boundingBox"); + + // Looking for a key event which will fire continuously across browsers + // while the key is held down. 38, 40 = arrow up/down, 33, 34 = page up/down + var keyEventSpec = (!Y.UA.opera) ? "down:" : "press:"; + keyEventSpec += "38, 40, 33, 34"; + + + Y.on("change", Y.bind(this._onInputChange, this), this.inputNode); + Y.on("key", Y.bind(this._onDirectionKey, this), boundingBox, keyEventSpec); + Y.on("mousedown", Y.bind(this._onMouseDown, this), boundingBox); + this._documentMouseUpHandle = Y.on("mouseup", Y.bind(this._onDocMouseUp, this), + boundingBox.get("ownerDocument")); +}, + +/* + * For spinner, the method sets the value of the input field, + * to match the current state of the value attribute. + */ +syncUI : function() { + this._uiSetValue(this.get("value")); +} +``` + +

    A Note On Key Event Listeners

    + +

    The `Spinner` uses Event's `"key"` support, to set up a listener for arrow up/down and page up/down keys on the spinner's bounding box (line 31).

    + +

    Event's `"key"` support allows `Spinner` to define a single listener, which is only invoked for the key specification provided. The key specification in the above use case is `"down:38, 40, 33, 34"` for most browsers, indicating that +the `_onDirectionKey` method should only be called if the bounding box receives a keydown event with a character code which is either 38, 40, 33 or 34. `"key"` specifications can also contain more advanced filter criteria, involving modifiers such as CTRL and SHIFT.

    + +

    For the Spinner widget, we're looking for a key event which fires repeatedly while the key is held down. This differs for Opera, so we need to fork for the key event we're interested in. Future versions of `"key"` support will aim to provide this type of higher level cross-browser abstraction also.

    + +

    Attribute Supporting Methods

    + +

    Since all widgets are attribute-driven, they all follow a pretty similar pattern when it comes to how those attributes are used. For a given attribute, widgets will generally have:

    +
      +
    • A prototype method to listen for changes in the attribute
    • +
    • A prototype method to update the state of the rendered UI, to reflect the value of an attribute.
    • +
    • A prototype method used to set/get/validate the attribute.
    • +
    + +

    These methods are kept on the prototype to facilitate customization at any of the levels - event handling, ui updates, set/get/validation logic.

    + +

    For `Spinner`, these corresponding methods for the `value` attribute are: `_afterValueChange`, `_uiSetValue` and `_validateValue`:

    + +``` +/* + * value attribute change listener. Updates the + * value in the rendered input box, whenever the + * attribute value changes. + */ +_afterValueChange : function(e) { + this._uiSetValue(e.newVal); +}, + +/* + * Updates the value of the input box to reflect + * the value passed in + */ +_uiSetValue : function(val) { + this.inputNode.set("value", val); +}, + +/* + * value attribute default validator. Verifies that + * the value being set lies between the min/max value + */ +_validateValue: function(val) { + var min = this.get("min"), + max = this.get("max"); + + return (Lang.isNumber(val) && val >= min && val <= max); +} +``` + +

    Since this example focuses on general patterns for widget development, validator/set/get functions are not defined for attributes such as min/max in the interests of keeping the example simple, but could be, in a production ready spinner.

    + +

    Rendering Support Methods

    + +

    `Spinner`'s `renderUI` method hands off creation of the input field and buttons to the following helpers which use markup templates to generate node instances:

    + +``` +/* + * Creates the input field for the spinner and adds it to + * the widget's content box, if not already in the markup. + */ +_renderInput : function() { + var contentBox = this.get("contentBox"), + input = contentBox.one("." + Spinner.INPUT_CLASS), + strings = this.get("strings"); + + if (!input) { + input = Node.create(Spinner.INPUT_TEMPLATE); + contentBox.appendChild(input); + } + + input.set("title", strings.tooltip); + this.inputNode = input; +}, + +/* + * Creates the button controls for the spinner and adds them to + * the widget's content box, if not already in the markup. + */ +_renderButtons : function() { + var contentBox = this.get("contentBox"), + strings = this.get("strings"); + + var inc = this._createButton(strings.increment, this.getClassName("increment")); + var dec = this._createButton(strings.decrement, this.getClassName("decrement")); + + this.incrementNode = contentBox.appendChild(inc); + this.decrementNode = contentBox.appendChild(dec); +}, + +/* + * Utility method, to create a spinner button + */ +_createButton : function(text, className) { + + var btn = Y.Node.create(Spinner.BTN_TEMPLATE); + btn.set("innerHTML", text); + btn.set("title", text); + btn.addClass(className); + + return btn; +} +``` + +

    DOM Event Listeners

    + +

    The DOM event listeners attached during `bindUI` are straightforward event listeners, which receive the event facade for the DOM event, and update the spinner state accordingly.

    + +

    A couple of interesting points worth noting: In the `"key"` listener we set up, we can call `e.preventDefault()` without having to check the character code, since the `"key"` event specifier will only invoke the listener +if one of the specified keys is pressed (arrow/page up/down)

    + +

    Also, to allow the spinner to update its value while the mouse button is held down, we set up a timer, which gets cleared out when we receive a mouseup event on the document.

    + +``` +/* + * Bounding box Arrow up/down, Page up/down key listener. + * + * Increments/Decrements the spinner value, based on the key pressed. + */ +_onDirectionKey : function(e) { + e.preventDefault(); + ... + switch (e.charCode) { + case 38: + newVal += minorStep; + break; + case 40: + newVal -= minorStep; + break; + case 33: + newVal += majorStep; + newVal = Math.min(newVal, this.get("max")); + break; + case 34: + newVal -= majorStep; + newVal = Math.max(newVal, this.get("min")); + break; + } + + if (newVal !== currVal) { + this.set("value", newVal); + } +}, + +/* + * Bounding box mouse down handler. Will determine if the mouse down + * is on one of the spinner buttons, and increment/decrement the value + * accordingly. + * + * The method also sets up a timer, to support the user holding the mouse + * down on the spinner buttons. The timer is cleared when a mouse up event + * is detected. + */ +_onMouseDown : function(e) { + var node = e.target + ... + if (node.hasClass(this.getClassName("increment"))) { + this.set("value", currVal + minorStep); + ... + } else if (node.hasClass(this.getClassName("decrement"))) { + this.set("value", currVal - minorStep); + ... + } + + if (handled) { + this._setMouseDownTimers(dir); + } +}, + +/* + * Document mouse up handler. Clears the timers supporting + * the "mouse held down" behavior. + */ +_onDocMouseUp : function(e) { + this._clearMouseDownTimers(); +}, + +/* + * Simple change handler, to make sure user does not input an invalid value + */ +_onInputChange : function(e) { + if (!this._validateValue(this.inputNode.get("value"))) { + // If the entered value is not valid, re-display the stored value + this.syncUI(); + } +} +``` + +

    ClassName Support Methods

    + +

    A key part of developing widgets which work with the DOM is defining class names which it will use to mark the nodes it renders. These class names could be used to mark a node for later retrieval/lookup, for CSS application (both functional as well as cosmetic) or to indicate the current state of the widget.

    + +

    The widget infrastructure uses the `ClassNameManager` utility, to generate consistently named classes to apply to the nodes it adds to the page:

    + +``` +Y.ClassNameManager.getClassName(Spinner.NAME, "value"); +... +this.getClassName("increment"); +``` + +

    +Class names generated by the Widget's `getClassName` prototype method use the NAME field of the widget, to generate a prefixed classname through `ClassNameManager` - e.g. for spinner the `this.getClassName("increment")` above will generate the class name `yui3-spinner-increment` ("yui" being the system level prefix, "spinner" being the widget name). +When you need to generate standard class names in static code (where you don't have a reference to `this.getClassName()`), you can use the ClassNameManager directly, as shown in line 1 above, to achieve the same results. +

    + +

    CSS Considerations

    + +

    Since widget uses the `getClassName` method to generate state-related class names and to mark the bounding box/content box of the widget (e.g. "yui3-[widgetname]-content", "yui3-[widgetname]-hidden", "yui3-[widgetname]-disabled"), we need to provide the default CSS handling for states we're interested in handling for the new Spinner widget. The "yui3-[widgetname]-hidden" class is probably one state class, which all widgets will provide implementations for.

    + +``` +/* Progressive enhancement support, to hide the text box, if JavaScript is enabled, while we instantiate the rich control */ +.yui3-js-enabled .yui3-spinner-loading { + display:none; +} + +/* Controlling show/hide state using display (since this control is inline-block) */ +.yui3-spinner-hidden { + display:none; +} + +/* Bounding Box - Set the bounding box to be "inline block" for spinner */ +.yui3-spinner { + display:inline-block; + zoom:1; + *display:inline; +} + +/* Content Box - Start adding visual treatment for the spinner */ +.yui3-spinner-content { + padding:1px; +} + +/* Input Text Box, generated through getClassName("value") */ +.yui3-spinner-value { + ... +} + +/* Button controls, generated through getClassName("increment") */ +.yui3-spinner-increment, .yui3-spinner-decrement { + ... +} +``` + +

    Using The Spinner Widget

    + +

    For the example, we have an input field already on the page, which we'd like to enhance to create a Spinner instance. We mark it with a yui3-spinner-loading class, so that if JavaScript is enabled, we can hide it while we're instantiating the rich control:

    + +``` + +``` + +

    We provide the constructor for the Spinner with the `srcNode` which contains the input field with our initial value. The `HTML_PARSER` code we saw earlier will extract the value from the input field, and use it as the initial value for the Spinner instance:

    + +``` +// Create a new Spinner instance, drawing its +// starting value from an input field already on the +// page (the #numberField text input box) +var spinner = new Spinner({ + srcNode: "#numberField", + max:100, + min:0 +}); +spinner.render(); +spinner.focus(); +``` + +

    The custom widget class structure discussed above is captured in this "MyWidget" template file, which you can use as a starting point to develop your own widgets.

    + +

    Complete Example Source

    +``` +{{>widget-extend-source}} +``` diff --git a/src/widget/docs/widget-parentchild-listbox.mustache b/src/widget/docs/widget-parentchild-listbox.mustache new file mode 100644 index 00000000000..1767c4dd575 --- /dev/null +++ b/src/widget/docs/widget-parentchild-listbox.mustache @@ -0,0 +1,468 @@ + + +
    +

    This is an advanced example, in which we create a ListBox widget with nested Option widgets, by extending the base `Widget` class, and adding `WidgetParent` and `WidgetChild` extensions, through `Base.build`.

    +

    The TabView component that is included in the YUI 3 library, is also built using the WidgetParent and WidgetChild extensions.

    +
    + +
    + {{>widget-parentchild-listbox-source}} +
    + +

    The WidgetParent and WidgetChild Extensions

    + +

    WidgetParent is an extension, designed to be used with `Base.build` to create a class of Widget which is designed to contain child Widgets (for example a Tree widget, which contains TreeNode children). +WidgetParent itself augments ArrayList providing a convenient set of array iteration and convenience methods, allowing users of your class to easily work with parent's list of children.

    + +

    WidgetChild is also an extension, designed to be used with `Base.build` to create a class of Widget which is designed to nested inside parent Widgets (for example a TreeNode widget, which sits inside a Tree widget).

    + +

    A Widget can be built with both the WidgetParent and WidgetChild extensions (it can be both a Parent and a Child), in cases where we want to support multi-level hierarchies, such as the ListBox example below.

    + +

    In addition to providing the basic support to manage (add/remove/iterate/render) children the Widget Parent/Child implementations also provides support for both single and multiple selection models.

    + +

    Using WidgetParent and WidgetChild to Create the ListBox Class

    + +

    For ListBox, since we're creating a new class from scratch, we use the sugar version of `Base.build`, called `Base.create`, which allows us to easily create a new class and define it's prototype and static properties/methods, in a single call, as shown below:

    + +``` + +// Create a new class, ListBox, which extends Widget, and mixes in both the WidgetParent and WidgetChild +// extensions since we want to be able to nest one ListBox inside another, to create heirarchical listboxes + +Y.ListBox = Y.Base.create("listbox", Y.Widget, [Y.WidgetParent, Y.WidgetChild], { + // Prototype Properties for ListBox + }, { + // Static Properties for ListBox + }); +``` + +

    We can then go ahead and fill out the prototype and static properties we want to override in our ListBox implementation, while Widget, WidgetParent and WidgetChild provide the basic Widget rendering and parent-child relationship support. Comments inline below provide the background:

    + +

    Prototype Method and Properties

    + +``` +Y.ListBox = Y.Base.create("listbox", Y.Widget, [Y.WidgetParent, Y.WidgetChild], { + + // The default content box for ListBoxes will be a UL (Widget uses a DIV by default) + CONTENT_TEMPLATE : "
      ", + + // Setup Custom Listeners + bindUI: function() { + + if (this.isRoot()) { + + // Setup custom focus handling, using the NodeFocusManager plugin + // This will help us easily crete next/previous item handling using the arrow keys + + this.get("boundingBox").plug(Y.Plugin.NodeFocusManager, { + descendants: ".yui3-option", + keys: { + next: "down:40", // Down arrow + previous: "down:38" // Up arrow + }, + circular: true + }); + } + + this.get("boundingBox").on("contextmenu", function (event) { + event.preventDefault(); + }); + + // Setup listener to control keyboard based single/multiple item selection + this.on("option:keydown", function (event) { + + var item = event.target, + domEvent = event.domEvent, + keyCode = domEvent.keyCode, + direction = (keyCode == 40); + + if (this.get("multiple")) { + if (keyCode == 40 || keyCode == 38) { + if (domEvent.shiftKey) { + this._selectNextSibling(item, direction); + } else { + this.deselectAll(); + this._selectNextSibling(item, direction); + } + } + } else { + if (keyCode == 13 || keyCode == 32) { + domEvent.preventDefault(); + item.set("selected", 1); + } + } + }); + + // Setup listener to control mouse based single/multiple item selection + this.on("option:mousedown", function (event) { + + var item = event.target, + domEvent = event.domEvent, + selection; + + if (this.get("multiple")) { + if (domEvent.metaKey) { + item.set("selected", 1); + } else { + this.deselectAll(); + item.set("selected", 1); + } + } else { + item.set("selected", 1); + } + + }); + }, + + // Helper Method, to find the correct next sibling, taking into account nested ListBoxes + _selectNextSibling : function(item, direction) { + + var parent = item.get("parent"), + method = (direction) ? "next" : "previous", + + // Only go circular for the root listbox + circular = (parent === this), + sibling = item[method](circular); + + if (sibling) { + // If we found a sibling, it's either an Option or a ListBox + if (sibling instanceof Y.ListBox) { + // If it's a ListBox, select it's first child (in the direction we're headed) + sibling.selectChild((direction) ? 0 : sibling.size() - 1); + } else { + // If it's an Option, select it + sibling.set("selected", 1); + } + } else { + // If we didn't find a sibling, we're at the last leaf in a nested ListBox + parent[method](true).set("selected", 1); + } + }, + + // The markup template we use internally to render nested ListBox children + NESTED_TEMPLATE : '
    • {label}
    • ', + + renderUI: function () { + + // Handling Nested Child Element Rendering + if (this.get("depth") > -1) { + + var tokens = { + labelClassName : this.getClassName("label"), + nestedOptionClassName : this.getClassName("option"), + label : this.get("label") + }, + liHtml = Y.substitute(this.NESTED_TEMPLATE, tokens), + li = Y.Node.create(liHtml), + + boundingBox = this.get("boundingBox"), + parent = boundingBox.get("parentNode"); + + li.appendChild(boundingBox); + parent.appendChild(li); + } + } + +} { /* static properties */ }); + +``` + +

      Static Properties

      + +

      The only static property we're interested in defining for the ListBox class is the `ATTRS` property. Comments inline below provide the background:

      + +``` +{ + // Define any new attributes, or override existing ones + ATTRS : { + + // We need to define the default child class to use, + // when we need to create children from the configuration + // object passed to add or to the "children" attribute (which is provided by WidgetParent) + + // In this case, when a configuration object (e.g. { label:"My Option" }), + // is passed into the add method,or as the value of the "children" + // attribute, we want to create instances of Y.Option + defaultChildType: { + value: "Option" + }, + + // Setup Label Attribute + label : { + validator: Y.Lang.isString + } + } +} +``` + +

      Using WidgetChild to Create the Option (leaf) Class

      + +

      The Option class is pretty simple, and largely just needs the attribute and API provided by WidgetChild. We only need to over-ride the default templates and tabIndex handling:

      + +``` + +Y.Option = Y.Base.create("option", Y.Widget, [Y.WidgetChild], { + + // Override the default DIVs used for rendering the bounding box and content box. + CONTENT_TEMPLATE : "", + BOUNDING_TEMPLATE : "
    • ", + + // Handle rendering the label attribute + renderUI: function () { + this.get("contentBox").setContent(this.get("label")); + } + +}, { + + ATTRS : { + + // Setup Label Attribute + label : { + validator: Y.Lang.isString + }, + + // Override the default tabIndex for an Option, + // since we want FocusManager to control keboard + // based focus + tabIndex: { + value: -1 + } + } + +}); + +``` + +

      Adding The Code As A "listbox" Custom Module

      + +

      This example also shows how you can package code for re-use as a module, by registering it through the `YUI.add` method, specifying any requirements it has (the packaged code is available in ./assets/listbox.js).

      + +``` +YUI.add('listbox', function(Y) { + + Y.ListBox = ... + + Y.Option = ... + +}, '3.1.0' ,{requires:['substitute', 'widget', 'widget-parent', 'widget-child', 'node-focusmanager']}); +``` + +

      Using the Custom "listbox" Module

      + +

      To create an instance of a ListBox, we ask for the "listbox" module we packaged in the previous step, through `YUI().use("listbox")`:

      + +``` + YUI({ + modules: { + "listbox": { + fullpath: "listbox.js", + requires: ["substitute", "widget", "widget-parent", "widget-child", "node-focusmanager"] + } + } + }).use("listbox", function (Y) { + + // Create the top level ListBox instance, and start it off with + // 2 children (the defaultChildType will be used to create instances of Y.Option with the + // children configuration passed in below). + + var listbox = new Y.ListBox({ + id:"mylistbox", + width:"13em", + height:"15em", + children: [ + { label: "Item One" }, + { label: "Item Two" } + ] + }); + + ... + }); +``` + +

      We can also use the `add` method provided by WidgetParent, to add children after contruction, and then render to the DOM:

      + +``` + // Then we add a nested ListBox which itself has 2 children, using + // the add API provided by WidgetParent + + listbox.add({ + type: "ListBox", + label: "Item Three", + children: [ + { label: "Item Three - One" }, + { label: "Item Three - Two" } + ] + }); + + // One more Option child + + listbox.add({ label: "Item Four" }); + + // One more Option child, using providing an actual + // instance, as opposed to just the configuration + + listbox.add( + new Y.Option({ label: "Item Five" }) + ); + + // And finally, a last nested ListBox, again with + // 2 children + + listbox.add({ + type: "ListBox", + label: "Item Six", + children: [ + { label: "Item Six - One" }, + { label: "Item Six - Two" } + ] + }); + + // Render it, using Widget's render method, + // to the "#exampleContainer" element. + listbox.render("#exampleContainer"); +``` + +

      The ListBox fires selectionChange events, every time it's selection state changes (provided by WidgetParent), which we can listen and respond to:

      + +``` + listbox.after("selectionChange", function(e) { + + var selection = this.get("selection"); + if (selection instanceof Y.ListBox) { + selection = selection.get("selection"); + } + + if (selection) { + Y.one("#selection").setContent(selection.get("label")); + } + + }); +``` + +

      The CSS

      + +``` + .yui3-listbox { + padding:0; + margin: .25em; + border: solid 1px #000; + background-color:#fff; + white-space:nowrap; + } + + .yui3-listbox .yui3-listbox { + margin-top: .25em; + margin-bottom: .25em; + border: none; + } + + .yui3-listbox .yui3-option, + .yui3-listbox .yui3-listbox-option { + margin:0; + padding:0; + cursor:default; + list-style-image:none; + list-style-position:outside; + list-style-type:none; + } + + .yui3-option-content, + .yui3-listbox-label { + display: block; + padding: .25em .5em; + } + + .yui3-listbox-content { + margin:0; + padding:0; + overflow:auto; + } + + .yui3-listbox .yui3-listbox .yui3-option-content { + margin-left:.5em; + } + + .yui3-listbox-label { + font-weight: bold; + } + + .yui3-option-selected { + background-color: #cccccc; + } + + .yui3-option-focused { + outline: none; + background-color: blue; + color: #fff; + } +``` + +

      Complete Example Source

      +``` +{{>widget-parentchild-listbox-source}} +``` diff --git a/src/widget/docs/widget-plugin.mustache b/src/widget/docs/widget-plugin.mustache new file mode 100644 index 00000000000..90f7bd4a376 --- /dev/null +++ b/src/widget/docs/widget-plugin.mustache @@ -0,0 +1,316 @@ + + +
      +

      This example shows how you can use Widget's plugin infrastructure to add additional features to an existing widget.

      +

      We create an IO plugin class for `Widget` called `WidgetIO`. The plugin adds IO capabilities to the Widget, which, by default, outputs to the `Widget`'s `contentBox`.

      +
      + +
      + {{>widget-plugin-source}} +
      + +

      Creating an IO Plugin For Widget

      + +

      Setting Up The YUI Instance

      + +

      For this example, we'll pull in `widget`; the `io`, and +the `plugin` module. The `io` module provides the XHR +support we need for the IO plugin. The `Plugin` base class is the class we'll +extend to create our io plugin class for `Widget`. +The code to set up our sandbox instance is shown below:

      + +``` +YUI({...}).use("widget", "io", "plugin", function(Y) { + // We'll write our code here, after pulling in the default Widget module, + // the IO utility, and the Plugin base class. +} +``` + +

      Using the `widget` module will also pull down the default CSS required for widget, on top of which we only need to add our required look/feel CSS for the example.

      + +

      WidgetIO Class Structure

      + +

      The `WidgetIO` class will extend the `Plugin` base class. Since `Plugin` derives from `Base`, we follow the same pattern we use for widgets and other utilities which extend Base to setup our new class.

      + +

      Namely:

      + +
        +
      • Setting up the constructor to invoke the superclass constructor
      • +
      • Setting up a `NAME` property, to identify the class
      • +
      • Setting up the default attributes, using the `ATTRS` property
      • +
      • Providing prototype implementations for anything we want executed during initialization and destruction using the `initializer` and `destructor` lifecycle methods
      • +
      + +

      Additionally, since this is a plugin, we provide a `NS` property for the class, which defines the property which will refer to the `WidgetIO` instance on the host class (e.g. `widget.io` will be an instance of `WidgetIO`)

      . + +``` +/* Widget IO Plugin Constructor */ +function WidgetIO(config) { + WidgetIO.superclass.constructor.apply(this, arguments); +} + +/* + * The namespace for the plugin. This will be the property on the widget, + * which will reference the plugin instance, when it's plugged in + */ +WidgetIO.NS = "io"; + +/* + * The NAME of the WidgetIO class. Used to prefix events generated + * by the plugin class. + */ +WidgetIO.NAME = "ioPlugin"; + +/* + * The default set of attributes for the WidgetIO class. + */ +WidgetIO.ATTRS = { + uri : {...}, + cfg : {...}, + formatter : {...}, + loading: {...} +}; + +/* Extend the base plugin class */ +Y.extend(WidgetIO, Y.Plugin.Base, { + + // Lifecycle methods. + initializer: function() {...}, + + // IO Plugin specific methods + refresh : function() {...}, + + // Default IO transaction handlers + _defSuccessHandler : function(id, o) {...}, + _defFailureHandler : function(id, o) {...}, + _defStartHandler : function(id, o) {...}, + _defCompleteHandler : function(id, o) {...}, + _defFormatter : function(val) {...} +}); +``` + +

      Plugin Attributes

      + +

      The `WidgetIO` is a fairly simple plugin class. It provides incremental functionality. It does not need to modify the behavior of any methods on the host Widget instance, or monitor any Widget events (unlike the AnimPlugin example).

      + +

      It sets up the following attributes, which are used to control how the IO plugin's `refresh` method behaves:

      + +
      +
      uri
      +
      The uri to use for the io request
      +
      cfg
      +
      The io configuration object, to pass to io when initiating a transaction
      +
      formatter
      +
      The formatter to use to formatting response data. The default implementation simply passes back the response data passed in, unchanged.
      +
      loading
      +
      The default content to display while an io transaction is in progress
      +
      + +

      In terms of code, the attributes for the plugin are set up using the standard `ATTRS` property:

      + +``` +/* Setup local variable for Y.WidgetStdMod, since we use it + multiple times to reference static properties */ +var WidgetIO = Y.WidgetIO; + +... + +/* Attribute definition */ +WidgetIO.ATTRS = { + /* + * The node that will contain the IO content + */ + contentNode: { + value: '.yui3-widget-content', + setter: '_defContentNodeSetter' + }, + + /* + * The uri to use for the io request + */ + uri : { + value:null + }, + + /* + * The io configuration object, to pass to io when initiating + * a transaction + */ + cfg : { + value:null + }, + + /* + * The default formatter to use when formatting response data. The default + * implementation simply passes back the response data passed in. + */ + formatter : { + valueFn: function() { + return this._defFormatter; + } + }, + + /* + * The default loading indicator to use, when an io transaction is in progress. + */ + loading: { + value: '' + } +}; +``` + +

      The `formatter` attribute uses `valueFn` to define an instance based default value; pointing to the `_defFormatter` method on the `WidgetIO` instance.

      + +

      Lifecycle Methods: initializer, destructor

      + +

      Since no special initialization is required, there is no need to implement an `initializer` method.

      + +

      The `destructor` terminates any existing transaction, if active when the plugin is destroyed (unplugged).

      + +``` +initializer: function() {} + +/* + * Destruction code. Terminates the activeIO transaction if it exists + */ +destructor : function() { + if (this._activeIO) { + Y.io.abort(this._activeIO); + this._activeIO = null; + } +}, +``` + +

      The refresh Method

      + +

      The `refresh` method is the main public method which the plugin provides. It's responsible for dispatching the IO request, using the current state of the attributes defined of the plugin. Users will end up invoking the method from the plugin instance attached to the Widget (`widget.io.refresh()`).

      + +``` +refresh : function() { + if (!this._activeIO) { + var uri = this.get("uri"); + + if (uri) { + cfg = this.get("cfg") || {}; + cfg.on = cfg.on || {}; + + cfg.on.start = cfg.on.start || Y.bind(this._defStartHandler, this); + cfg.on.complete = cfg.on.complete || Y.bind(this._defCompleteHandler, this); + + cfg.on.success = cfg.on.success || Y.bind(this._defSuccessHandler, this); + cfg.on.failure = cfg.on.failure || Y.bind(this._defFailureHandler, this); + + cfg.method = cfg.method; // io defaults to "GET" if not defined + + Y.io(uri, cfg); + } + } +} +``` + +

      The `refresh` method, as implemented for the scope of this example, sets up the io configuration object for the transaction it is about to dispatch, filling in the default handlers for io's `start`, `complete`, `success` and `failure` events, if the user does not provide custom implementations.

      + +

      Node Content Helper Method

      + +

      To simplify implementation and extension, a `setContent` method is used to +wrap the content setter, which by default updates the `contentBox` of the widget.

      + +``` +/* + * Helper method for setting host content + */ +setContent: function(content) { + this.get('contentNode').setContent(content); +} +``` +

      The Default IO Event Handlers

      + +

      The default success listener, pulls the response data from the response object, and uses it to update the content +defined by the `contentNode` attribute, which, by default, is the `contentBox`. The response data is passed through the `formatter` configured for the plugin, converting it to the desired output format:

      + +``` +_defSuccessHandler : function(id, o) { + var response = o.responseXML || o.responseText; + var formatter = this.get('formatter'); + + this.setContent(formatter(response)); +} +``` + +

      The default failure listener, displays an error message in the currently configured `section`, when io communication fails:

      + +``` +_defFailureHandler : function(id, o) { + this.setContent('Failed to retrieve content'); +} +``` + +

      The default start event listener renders the `loading` content, which remains in place while the transaction is in process, and also stores a reference to the "inprogress" io transaction:

      + +``` +_defStartHandler : function(id, o) { + this._activeIO = o; + this.setContent(this.get('loading')); +}, +``` + +

      The default complete event listener clears out the "inprogress" io transaction object:

      + +``` +_defCompleteHandler : function(id, o) { + this._activeIO = null; +} +``` + +

      Using the Plugin

      + +

      All objects derived from Base are Plugin Hosts. They provide `plug` and `unplug` methods to allow users to add/remove plugins to/from existing instances. +They also allow the user to specify the set of plugins to be applied to a new instance, along with their configurations, as part of the constructor arguments.

      + +

      In this example, we'll create a new instance of a Widget:

      + +``` +/* Create a new Widget instance, with content generated from script */ +var widget = new Y.Widget(); +``` + +

      And then use the `plug` method to add the `WidgetIO`, +providing it with a configuration to use when sending out io transactions +(The Animation Plugin example shows how +you could do the same thing during construction), render the widget, and refresh +the plugin to fetch the content.

      + +``` +/* + * Add the Plugin, and configure it to use a news feed uri + */ +widget.plug(WidgetIO, { + uri : '{{componentAssets}}/news.php?query=web+browser', + loading: '' +}); + +widget.render('#demo'); + +/* fetch the content */ +widget.io.refresh(); +``` + +

      The plugin class structure described above is captured in this "MyPlugin" Template File, which you can use as a starting point to create your own plugins derived from `Plugin.Base`.

      + +

      Complete Example Source

      +``` +{{>widget-plugin-source}} +``` diff --git a/src/widget/docs/widget-tooltip.mustache b/src/widget/docs/widget-tooltip.mustache new file mode 100644 index 00000000000..48915cb04f8 --- /dev/null +++ b/src/widget/docs/widget-tooltip.mustache @@ -0,0 +1,459 @@ + + +
      +

      This is an advanced example, in which we create a Tooltip widget, by extending the base `Widget` class, and adding `WidgetStack` and `WidgetPosition` extensions, through `Base.build`.

      +
      + +
      + {{>widget-tooltip-source}} +
      + +

      Creating A Tooltip Widget Class

      + +

      Basic Class Structure

      + +

      As with the basic "Extending Widget" example, the `Tooltip` class will extend the `Widget` base class and follows the same pattern we use for other classes which extend Base.

      + +

      Namely:

      + +
        +
      • Set up the constructor to invoke the superclass constructor
      • +
      • Define a `NAME` property, to identify the class
      • +
      • Define the default attribute configuration, using the `ATTRS` property
      • +
      • Implement prototype methods
      • +
      + +

      This basic structure is shown below:

      + +``` +/* + * Required NAME static field, used to identify the Widget class and + * used as an event prefix, to generate class names etc. (set to the + * class name in camel case). + */ +Tooltip.NAME = "tooltip"; + +/* Default Tooltip Attributes */ +Tooltip.ATTRS = { + + /* + * The tooltip content. This can either be a fixed content value, + * or a map of id-to-values, designed to be used when a single + * tooltip is mapped to multiple trigger elements. + */ + content : { + value: null + }, + + /* + * The set of nodes to bind to the tooltip instance. Can be a string, + * or a node instance. + */ + triggerNodes : { + value: null, + setter: function(val) { + if (val && Lang.isString(val)) { + val = Node.all(val); + } + return val; + } + }, + + /* + * The delegate node to which event listeners should be attached. + * This node should be an ancestor of all trigger nodes bound + * to the instance. By default the document is used. + */ + delegate : { + value: null, + setter: function(val) { + return Y.one(val) || Y.one("document"); + } + }, + + /* + * The time to wait, after the mouse enters the trigger node, + * to display the tooltip + */ + showDelay : { + value:250 + }, + + /* + * The time to wait, after the mouse leaves the trigger node, + * to hide the tooltip + */ + hideDelay : { + value:10 + }, + + /* + * The time to wait, after the tooltip is first displayed for + * a trigger node, to hide it, if the mouse has not left the + * trigger node + */ + autoHideDelay : { + value:2000 + }, + + /* + * Override the default visibility set by the widget base class + */ + visible : { + value:false + }, + + /* + * Override the default XY value set by the widget base class, + * to position the tooltip offscreen + */ + xy: { + value:[Tooltip.OFFSCREEN_X, Tooltip.OFFSCREEN_Y] + } +}; + +Y.extend(Tooltip, Y.Widget, { + // Prototype methods/properties +}); +``` + +

      Adding WidgetPosition and WidgetStack Extension Support

      + +

      The Tooltip class also needs basic positioning and stacking (z-index, shimming) support. As with the Custom Widget Classes example, we use +`Base.create` to create a new `Tooltip` class with this support:

      + +``` + var Tooltip = Y.Base.create("tooltip", Y.Widget, [Y.WidgetPosition, Y.WidgetStack], + { ... prototype properties ... }, + { ... static properties ... }, +``` + +

      Lifecycle Methods: initializer, destructor

      + +

      The `initializer` method is invoked during the `init` lifecycle phase, after the attributes are configured for each class. `Tooltip` uses it +to setup the private state variables it will use to store the trigger node currently being serviced by the tooltip instance, event handles and show/hide timers.

      + +``` +initializer : function(config) { + + this._triggerClassName = this.getClassName("trigger"); + + // Currently bound trigger node information + this._currTrigger = { + node: null, + title: null, + mouseX: Tooltip.OFFSCREEN_X, + mouseY: Tooltip.OFFSCREEN_Y + }; + + // Event handles - mouse over is set on the delegate + // element, mousemove and mouseleave are set on the trigger node + this._eventHandles = { + delegate: null, + trigger: { + mouseMove : null, + mouseOut: null + } + }; + + // Show/hide timers + this._timers = { + show: null, + hide: null + }; + + // Publish events introduced by Tooltip. Note the triggerEnter event is preventable, + // with the default behavior defined in the _defTriggerEnterFn method + this.publish("triggerEnter", {defaultFn: this._defTriggerEnterFn, preventable:true}); + this.publish("triggerLeave", {preventable:false}); +} +``` + +

      The `destructor` is used to clear out stored state, detach any event handles and clear out the show/hide timers:

      + +``` +destructor : function() { + this._clearCurrentTrigger(); + this._clearTimers(); + this._clearHandles(); +} +``` + +

      Lifecycle Methods: bindUI, syncUI

      + +

      The `bindUI` and `syncUI` are invoked by the base Widget class' `renderer` method.

      + +

      `bindUI` is used to bind the attribute change listeners used to update the rendered UI from the current state of the widget and also to bind +the DOM listeners required to enable the UI for interaction.

      + +

      `syncUI` is used to sync the UI state from the current widget state, when initially rendered.

      + +

      NOTE: Widget's `renderer` method also invokes the `renderUI` method, which is responsible for laying down any additional content elements a widget requires. However +tooltip does not have any additional elements in needs to add to the DOM, outside of the default Widget boundingBox and contentBox.

      + +``` +bindUI : function() { + this.after("delegateChange", this._afterSetDelegate); + this.after("nodesChange", this._afterSetNodes); + + this._bindDelegate(); +}, + +syncUI : function() { + this._uiSetNodes(this.get("triggerNodes")); +} +``` + +

      Attribute Supporting Methods

      + +

      Tooltip's `triggerNodes`, which defines the set of nodes which should trigger this tooltip instance, +has a couple of supporting methods associated with it.

      + +

      The `_afterSetNodes` method is the default attribute change event handler for the `triggerNodes` +attribute. It invokes the `_uiSetNodes` method, which marks all trigger nodes with a trigger class name (`yui-tooltip-trigger`) when set.

      + +``` +_afterSetNodes : function(e) { + this._uiSetNodes(e.newVal); +}, + +_uiSetNodes : function(nodes) { + if (this._triggerNodes) { + this._triggerNodes.removeClass(this._triggerClassName); + } + + if (nodes) { + this._triggerNodes = nodes; + this._triggerNodes.addClass(this._triggerClassName); + } +}, +``` + +

      Similarly the `_afterSetDelegate` method is the default attribute change listener for the `delegate` attribute, +and invokes `_bindDelegate` to set up the listeners when a new delegate node is set. We use `Y.delegate` support, along with Event's `mouseenter` support, +which means the only thing we need to do is tell delegate which node we want to act as the delegate, and which elements we want to target using the `"." + this._triggerClassName` selector.

      + +``` +_afterSetDelegate : function(e) { + this._bindDelegate(e.newVal); +}, + +_bindDelegate : function() { + var eventHandles = this._eventHandles; + + if (eventHandles.delegate) { + eventHandles.delegate.detach(); + eventHandles.delegate = null; + } + eventHandles.delegate = Y.delegate("mouseenter", Y.bind(this._onNodeMouseEnter, this), this.get("delegate"), "." + this._triggerClassName); +}, +``` + +

      DOM Event Handlers

      + +

      Tooltips interaction revolves around the `mouseenter`, `mousemove` and `mouseleave` DOM events. The mousenter listener is the only listener set up initially, on the `delegate` node:

      + +``` +_onNodeMouseEnter : function(e) { + var node = e.currentTarget; + if (node && (!this._currTrigger.node || !node.compareTo(this._currTrigger.node))) { + this._enterTrigger(node, e.pageX, e.pageY); + } +} +``` + +

      Since the `mouseenter` implementation doesn't invoke it's listeners for `mouseover` events generated from elements nested +inside the targeted node (for example when mousing out of a child element of a trigger node), there are no additional checks we need to perform other than to see if the node is the current trigger, before handing off to +the `_enterTrigger` method to setup the current trigger state and attach mousemove and mouseleave listeners on the current trigger node.

      + +

      The mouseleave listener delegates to the `_leaveTrigger` method, and again, since the `mouseleave` implementation deals with nested elements, we don't need to perform any additional target checks:

      + +``` +_onNodeMouseLeave : function(e) { + this._leaveTrigger(e.currentTarget); +} +``` + +

      The mouse move listener delegates to the `_overTrigger` method to store the current mouse XY co-ordinates (used to position the Tooltip when it is displayed after the `showDelay`):

      + +``` +_onNodeMouseMove : function(e) { + this._overTrigger(e.pageX, e.pageY); +} +``` + +

      Trigger Event Delegates: _enterTrigger, _leaveTrigger, _overTrigger

      + +

      As seen above, the DOM event handlers delegate to the `_enterTrigger, _leaveTrigger and _overTrigger` methods to update the +Tooltip state based on the currently active trigger node.

      + +

      The `_enterTrigger` method sets the current trigger state (which node is the current tooltip trigger, +what the current mouse XY position is, etc.). The method also fires the `triggerEnter` event, whose default function actually handles +showing the tooltip after the configured `showDelay` period. The `triggerEnter` event can be prevented by listeners, allowing +users to prevent the tooltip from being shown if required. (`triggerEnter` listeners are passed the current trigger node and pageX, pageY mouse co-ordinates as event facade properties):

      + +``` +_enterTrigger : function(node, x, y) { + this._setCurrentTrigger(node, x, y); + this.fire("triggerEnter", null, node, x, y); +}, + +_defTriggerEnterFn : function(e) { + var node = e.node; + if (!this.get("disabled")) { + this._clearTimers(); + var delay = (this.get("visible")) ? 0 : this.get("showDelay"); + this._timers.show = Y.later(delay, this, this._showTooltip, [node]); + } +}, +``` + +

      Similarly the `_leaveTrigger` method is invoked when the mouse leaves a trigger node, and clears any stored state, timers and listeners before setting up +the `hideDelay` timer. It fires a `triggerLeave` event, but cannot be prevented, and has no default behavior to prevent:

      + +``` +_leaveTrigger : function(node) { + this.fire("triggerLeave"); + + this._clearCurrentTrigger(); + this._clearTimers(); + + this._timers.hide = Y.later(this.get("hideDelay"), this, this._hideTooltip); +}, +``` + +

      As mentioned previously, the `_overTrigger` method simply stores the current mouse XY co-ordinates for use when the tooltip is shown:

      + +``` +_overTrigger : function(x, y) { + this._currTrigger.mouseX = x; + this._currTrigger.mouseY = y; +} +``` + +

      Setting Tooltip Content

      + +

      Since the content for a tooltip is usually a function of the trigger node and not constant, `Tooltip` provides a number of ways to set the content.

      + +
        +
      1. Setting the `content` attribute to a string or node. In this case, the value of the `content` attribute is used + for all triggerNodes
      2. +
      3. Setting the `content` attribute to an object literal, containing a map of triggerNode id to content. The content for a trigger node + will be set using the map, when the tooltip is triggered for the node.
      4. +
      5. Setting the title attribute on the trigger node. The value of the title attribute is used to set the tooltip content, + when triggered for the node.
      6. +
      7. By calling the `setTriggerContent` method to set content for a specific trigger node, in a `triggerEnter` event listener.
      8. +
      + +

      The precedence of these methods is handled in the `_setTriggerContent` method, invoked when the mouse enters a trigger:

      + +``` +_setTriggerContent : function(node) { + var content = this.get("content"); + if (content && !(content instanceof Node || Lang.isString(content))) { + content = content[node.get("id")] || node.getAttribute("title"); + } + this.setTriggerContent(content); +}, + +setTriggerContent : function(content) { + var contentBox = this.get("contentBox"); + contentBox.set("innerHTML", ""); + + if (content) { + if (content instanceof Node) { + for (var i = 0, l = content.size(); i < l; ++i) { + contentBox.appendChild(content.item(i)); + } + } else if (Lang.isString(content)) { + contentBox.set("innerHTML", content); + } + } +} +``` + +

      Calling the public `setTriggerContent` in a `triggerEvent` listener will over-ride content set using the `content` attribute or the trigger node's title value.

      + +

      Using Tooltip

      + +

      For this example, we set up 4 DIV elements which will act as tooltip triggers. They are all marked using a `yui-hastooltip` class, so that they can be queried using a simple selector, passed as the value for the `triggerNodes` attribute in the tooltip's constructor Also all 4 trigger nodes are contained in a wrapper DIV with `id="delegate"` which will act as the `delegate` node.

      + +``` +var tt = new Tooltip({ + triggerNodes:".yui3-hastooltip", + delegate: "#delegate", + content: { + tt3: "Tooltip 3 (from lookup)" + }, + shim:false, + zIndex:2 +}); +tt.render(); +``` + +

      The tooltip content for each of the trigger nodes is setup differently. The first trigger node uses the title attribute to set it's content. The third trigger node's content is set using the content map set in the constructor above. The second trigger node's content is set using a `triggerEnter` event listener and the `setTriggerContent` method as shown below:

      + +``` +tt.on("triggerEnter", function(e) { + var node = e.node; + if (node && node.get("id") == "tt2") { + this.setTriggerContent("Tooltip 2 (from triggerEvent)"); + } +}); +``` + +

      The fourth trigger node's content is set using it's title attribute, however it also has a `triggerEvent` listener which prevents the tooltip from being displayed for it, if the checkbox is checked.

      + +``` +var prevent = Y.one("#prevent"); +tt.on("triggerEnter", function(e) { + var node = e.node; + if (prevent.get("checked")) { + if (node && node.get("id") == "tt4") { + e.preventDefault(); + } + } +}); +``` + +

      Complete Example Source

      +``` +{{>widget-tooltip-source}} +```