YUI 3.x Home -

YUI Library Examples: Widget: Extending the base widget class

Widget: Extending the base widget class

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

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 of the initialization chain (line 2)
  2. 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)
  3. Defining the static ATTRS property for the class, which defines the set of attribute which the class will introduce, in addition to the superclass attributes (line 18-57)
  4. Extending the Widget class, and adding/over-riding any prototype properties/methods (line 59)
  1. /* Spinner class constructor */
  2. function Spinner(config) {
  3. Spinner.superclass.constructor.apply(this, arguments);
  4. }
  5.  
  6. /*
  7.  * Required NAME static field, to identify the Widget class and
  8.  * used as an event prefix, to generate class names etc. (set to the
  9.  * class name in camel case).
  10.  */
  11. Spinner.NAME = "spinner";
  12.  
  13. /*
  14.  * The attribute configuration for the Spinner widget. Attributes can be
  15.  * defined with default values, get/set functions and validator functions
  16.  * as with any other class extending Base.
  17.  */
  18. Spinner.ATTRS = {
  19. // The minimum value for the spinner.
  20. min : {
  21. value:0
  22. },
  23.  
  24. // The maximum value for the spinner.
  25. max : {
  26. value:100
  27. },
  28.  
  29. // The current value of the spinner.
  30. value : {
  31. value:0,
  32. validator: function(val) {
  33. return this._validateValue(val);
  34. }
  35. },
  36.  
  37. // Amount to increment/decrement the spinner when the buttons,
  38. // or arrow up/down keys are pressed.
  39. minorStep : {
  40. value:1
  41. },
  42.  
  43. // Amount to increment/decrement the spinner when the page up/down keys are pressed.
  44. majorStep : {
  45. value:10
  46. },
  47.  
  48. // The localizable strings for the spinner. This attribute is
  49. // defined by the base Widget class but has an empty value. The
  50. // spinner is simply providing a default value for the attribute.
  51. strings: {
  52. value: {
  53. tooltip: "Press the arrow up/down keys for minor increments, \
  54. page up/down for major increments.",
  55. increment: "Increment",
  56. decrement: "Decrement"
  57. }
  58. }
  59. };
  60.  
  61. Y.extend(Spinner, Widget, {
  62. // Methods/properties to add to the prototype of the new class
  63. ...
  64. });
/* 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.

  1. /*
  2.  * The HTML_PARSER static constant is used by the Widget base class to populate
  3.  * the configuration for the spinner instance from markup already on the page.
  4.  *
  5.  * The Spinner class attempts to set the value of the spinner widget if it
  6.  * finds the appropriate input element on the page.
  7.  */
  8. Spinner.HTML_PARSER = {
  9. value: function (contentBox) {
  10. var node = contentBox.one("." + Spinner.INPUT_CLASS);
  11. return (node) ? parseInt(node.get("value")) : null;
  12. }
  13. };
/* 
 * 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 setup initial state during construction, and cleanup 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).

  1. /*
  2.  * initializer is part of the lifecycle introduced by
  3.  * the Widget class. It is invoked during construction,
  4.  * and can be used to setup instance specific state.
  5.  *
  6.  * The Spinner class does not need to perform anything
  7.  * specific in this method, but it is left in as an example.
  8.  */
  9. initializer: function(config) {
  10. // Not doing anything special during initialization
  11. },
  12.  
  13. /*
  14.  * destructor is part of the lifecycle introduced by
  15.  * the Widget class. It is invoked during destruction,
  16.  * and can be used to cleanup instance specific state.
  17.  *
  18.  * The spinner cleans up any node references it's holding
  19.  * onto. The Widget classes destructor will purge the
  20.  * widget's bounding box of event listeners, so spinner
  21.  * only needs to clean up listeners it attaches outside of
  22.  * the bounding box.
  23.  */
  24. destructor : function() {
  25. this._documentMouseUpHandle.detach();
  26.  
  27. this.inputNode = null;
  28. this.incrementNode = null;
  29. this.decrementNode = null;
  30. }
/*
 * initializer is part of the lifecycle introduced by 
 * the Widget class. It is invoked during construction,
 * and can be used to setup 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 cleanup 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. bindUI() : responsible for binding event listeners (both attribute change and DOM event listeners) to 'activate' the rendered UI.
  3. syncUI() : responsible for updating the rendered UI based on the current state of the widget.

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:

  1. /*
  2.  * For spinner the method adds the input (if it's not already
  3.  * present in the markup), and creates the increment/decrement buttons
  4.  */
  5. renderUI : function() {
  6. this._renderInput();
  7. this._renderButtons();
  8. },
  9.  
  10. /*
  11.  * For spinner, the method:
  12.  *
  13.  * - Sets up the attribute change listener for the "value" attribute
  14.  *
  15.  * - Binds key listeners for the arrow/page keys
  16.  * - Binds mouseup/down listeners on the boundingBox, document respectively.
  17.  * - Binds a simple change listener on the input box.
  18.  */
  19. bindUI : function() {
  20. this.after("valueChange", this._afterValueChange);
  21.  
  22. var boundingBox = this.get("boundingBox");
  23.  
  24. // Looking for a key event which will fire continuously across browsers
  25. // while the key is held down. 38, 40 = arrow up/down, 33, 34 = page up/down
  26. var keyEventSpec = (!Y.UA.opera) ? "down:" : "press:";
  27. keyEventSpec += "38, 40, 33, 34";
  28.  
  29.  
  30. Y.on("change", Y.bind(this._onInputChange, this), this.inputNode);
  31. Y.on("key", Y.bind(this._onDirectionKey, this), boundingBox, keyEventSpec);
  32. Y.on("mousedown", Y.bind(this._onMouseDown, this), boundingBox);
  33. this._documentMouseUpHandle = Y.on("mouseup", Y.bind(this._onDocMouseUp, this),
  34. boundingBox.get("ownerDocument"));
  35. },
  36.  
  37. /*
  38.  * For spinner, the method sets the value of the input field,
  39.  * to match the current state of the value attribute.
  40.  */
  41. syncUI : function() {
  42. this._uiSetValue(this.get("value"));
  43. }
/*
 * 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 30).

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:

  1. /*
  2.  * value attribute change listener. Updates the
  3.  * value in the rendered input box, whenever the
  4.  * attribute value changes.
  5.  */
  6. _afterValueChange : function(e) {
  7. this._uiSetValue(e.newVal);
  8. },
  9.  
  10. /*
  11.  * Updates the value of the input box to reflect
  12.  * the value passed in
  13.  */
  14. _uiSetValue : function(val) {
  15. this.inputNode.set("value", val);
  16. },
  17.  
  18. /*
  19.  * value attribute default validator. Verifies that
  20.  * the value being set lies between the min/max value
  21.  */
  22. _validateValue: function(val) {
  23. var min = this.get("min"),
  24. max = this.get("max");
  25.  
  26. return (Lang.isNumber(val) && val >= min && val <= max);
  27. }
/*
 * 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:

  1. /*
  2.  * Creates the input field for the spinner and adds it to
  3.  * the widget's content box, if not already in the markup.
  4.  */
  5. _renderInput : function() {
  6. var contentBox = this.get("contentBox"),
  7. input = contentBox.one("." + Spinner.INPUT_CLASS),
  8. strings = this.get("strings");
  9.  
  10. if (!input) {
  11. input = Node.create(Spinner.INPUT_TEMPLATE);
  12. contentBox.appendChild(input);
  13. }
  14.  
  15. input.set("title", strings.tooltip);
  16. this.inputNode = input;
  17. },
  18.  
  19. /*
  20.  * Creates the button controls for the spinner and add them to
  21.  * the widget's content box, if not already in the markup.
  22.  */
  23. _renderButtons : function() {
  24. var contentBox = this.get("contentBox"),
  25. strings = this.get("strings");
  26.  
  27. var inc = this._createButton(strings.increment, this.getClassName("increment"));
  28. var dec = this._createButton(strings.decrement, this.getClassName("decrement"));
  29.  
  30. this.incrementNode = contentBox.appendChild(inc);
  31. this.decrementNode = contentBox.appendChild(dec);
  32. },
  33.  
  34. /*
  35.  * Utility method, to create a spinner button
  36.  */
  37. _createButton : function(text, className) {
  38.  
  39. var btn = Y.Node.create(Spinner.BTN_TEMPLATE);
  40. btn.set("innerHTML", text);
  41. btn.set("title", text);
  42. btn.addClass(className);
  43.  
  44. return btn;
  45. }
/*
 * 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 add 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 it's value while the mouse button is held down, we setup a timer, which gets cleared out when we receive a mouseup event on the document.

  1. /*
  2.  * Bounding box Arrow up/down, Page up/down key listener.
  3.  *
  4.  * Increments/Decrement the spinner value, based on the key pressed.
  5.  */
  6. _onDirectionKey : function(e) {
  7. e.preventDefault();
  8. ...
  9. switch (e.charCode) {
  10. case 38:
  11. newVal += minorStep;
  12. break;
  13. case 40:
  14. newVal -= minorStep;
  15. break;
  16. case 33:
  17. newVal += majorStep;
  18. newVal = Math.min(newVal, this.get("max"));
  19. break;
  20. case 34:
  21. newVal -= majorStep;
  22. newVal = Math.max(newVal, this.get("min"));
  23. break;
  24. }
  25.  
  26. if (newVal !== currVal) {
  27. this.set("value", newVal);
  28. }
  29. },
  30.  
  31. /*
  32.  * Bounding box mouse down handler. Will determine if the mouse down
  33.  * is on one of the spinner buttons, and increment/decrement the value
  34.  * accordingly.
  35.  *
  36.  * The method also sets up a timer, to support the user holding the mouse
  37.  * down on the spinner buttons. The timer is cleared when a mouse up event
  38.  * is detected.
  39.  */
  40. _onMouseDown : function(e) {
  41. var node = e.target
  42. ...
  43. if (node.hasClass(this.getClassName("increment"))) {
  44. this.set("value", currVal + minorStep);
  45. ...
  46. } else if (node.hasClass(this.getClassName("decrement"))) {
  47. this.set("value", currVal - minorStep);
  48. ...
  49. }
  50.  
  51. if (handled) {
  52. this._setMouseDownTimers(dir);
  53. }
  54. },
  55.  
  56. /*
  57.  * Document mouse up handler. Clears the timers supporting
  58.  * the "mouse held down" behavior.
  59.  */
  60. _onDocMouseUp : function(e) {
  61. this._clearMouseDownTimers();
  62. },
  63.  
  64. /*
  65.  * Simple change handler, to make sure user does not input an invalid value
  66.  */
  67. _onInputChange : function(e) {
  68. if (!this._validateValue(this.inputNode.get("value"))) {
  69. // If the entered value is not valid, re-display the stored value
  70. this.syncUI();
  71. }
  72. }
/*
 * Bounding box Arrow up/down, Page up/down key listener.
 *
 * Increments/Decrement 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:

  1. Y.ClassNameManager.getClassName(Spinner.NAME, "value");
  2. ...
  3. this.getClassName("increment");
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 yui-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. "yui-[widgetname]-content", "yui-[widgetname]-hidden", "yui-[widgetname]-disabled"), we need to provide the default CSS handling for states we're interested in handling for the new Spinner widget. The "yui-[widgetname]-hidden" class is probably one state class, which all widgets will provide implementations for.

  1. /* Controlling show/hide state using display (since this control is inline-block) */
  2. .yui-spinner-hidden {
  3. display:none;
  4. }
  5.  
  6. /* Bounding Box - Set the bounding box to be "inline block" for spinner */
  7. .yui-spinner {
  8. display:-moz-inline-stack;
  9. display:inline-block;
  10. zoom:1;
  11. *display:inline;
  12. }
  13.  
  14. /* Content Box - Start adding visual treatment for the spinner */
  15. .yui-spinner-content {
  16. padding:1px;
  17. }
  18.  
  19. /* Input Text Box, generated through getClassName("value") */
  20. .yui-spinner-value {
  21. ...
  22. }
  23.  
  24. /* Button controls, generated through getClassName("increment") */
  25. .yui-spinner-increment, .yui-spinner-decrement {
  26. ...
  27. }
/* Controlling show/hide state using display (since this control is inline-block) */
.yui-spinner-hidden {
    display:none;
}
 
/* Bounding Box - Set the bounding box to be "inline block" for spinner */
.yui-spinner {
    display:-moz-inline-stack;
    display:inline-block;
    zoom:1;
    *display:inline;
}
 
/* Content Box - Start adding visual treatment for the spinner */
.yui-spinner-content {
    padding:1px;
}
 
/* Input Text Box, generated through getClassName("value") */
.yui-spinner-value {
    ...
}
 
/* Button controls, generated through getClassName("increment") */
.yui-spinner-increment, .yui-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:

  1. <div id="numberField">
  2. <input type="text" class="yui-spinner-value" value="20" />
  3. </div>
<div id="numberField">
    <input type="text" class="yui-spinner-value" value="20" />
</div>

We provide the constructor for the Spinner with the contentBox 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:

  1. // Create a new Spinner instance, drawing it's
  2. // starting value from an input field already on the
  3. // page (contained in the #numberField content box)
  4. var spinner = new Spinner({
  5. contentBox: "#numberField",
  6. max:100,
  7. min:0
  8. });
  9. spinner.render();
  10. spinner.focus();
// Create a new Spinner instance, drawing it's 
// starting value from an input field already on the 
// page (contained in the #numberField content box)
var spinner = new Spinner({
    contentBox: "#numberField",
    max:100,
    min:0
});
spinner.render();
spinner.focus();

Copyright © 2009 Yahoo! Inc. All rights reserved.

Privacy Policy - Terms of Service - Copyright Policy - Job Openings