2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
10 * @requires yahoo, dom, event, element
12 * @title ProgressBar Widget
16 var Dom = YAHOO.util.Dom,
19 CLASS_PROGBAR = 'yui-pb',
20 CLASS_MASK = CLASS_PROGBAR + '-mask',
21 CLASS_BAR = CLASS_PROGBAR + '-bar',
22 CLASS_ANIM = CLASS_PROGBAR + '-anim',
23 CLASS_TL = CLASS_PROGBAR + '-tl',
24 CLASS_TR = CLASS_PROGBAR + '-tr',
25 CLASS_BL = CLASS_PROGBAR + '-bl',
26 CLASS_BR = CLASS_PROGBAR + '-br',
28 // Configuration attributes
31 MIN_VALUE = 'minValue',
32 MAX_VALUE = 'maxValue',
35 DIRECTION = 'direction',
36 DIRECTION_LTR = 'ltr',
37 DIRECTION_RTL = 'rtl',
38 DIRECTION_TTB = 'ttb',
39 DIRECTION_BTT = 'btt',
42 ARIA_TEXT_TEMPLATE = 'ariaTextTemplate',
46 PROGRESS = 'progress',
47 COMPLETE = 'complete';
50 * The ProgressBar widget provides an easy way to draw a bar depicting progress of an operation,
51 * a level meter, rating or any such simple linear measure.
52 * It allows for highly customized styles including animation, vertical or horizontal and forward or reverse.
53 * @namespace YAHOO.widget
55 * @extends YAHOO.util.Element
56 * @param oConfigs {object} An object containing any configuration attributes to be set
59 var Prog = function(oConfigs) {
60 YAHOO.log('Creating ProgressBar instance','info','ProgressBar');
62 Prog.superclass.constructor.call(this, document.createElement('div') , oConfigs);
67 YAHOO.widget.ProgressBar = Prog;
70 * String containing the HTML string which is the basis for the Progress Bar.
72 * @property ProgressBar.MARKUP
81 '"></div><div class="',
85 '"></div><div class="',
87 '"></div><div class="',
89 '"></div><div class="',
95 Lang.extend(Prog, YAHOO.util.Element, {
97 * Initialization code for the widget, separate from the constructor to allow for overriding/patching.
98 * It is called after <a href="#method_initAttributes">initAttributes</a>
101 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
104 _init: function (oConfigs) {
106 * Fires when the value is about to change. It reports the starting value
109 * @param value {Number} the current (initial) value
111 // No actual creation required, event will be created when subscribed to
112 //this.createEvent(START);
114 * If animation is active, it will trigger several times during the animation providing intermediate values
115 * If animation is not active, it will fire only once providing the end value
118 * @param value{Number} the current, changing value
120 // No actual creation required, event will be created when subscribed to
121 //this.createEvent(PROGRESS);
123 * Fires at the end of the animation or immediately upon changing values if animation is not loaded
126 * @param value {Number} the current (final) value
128 // No actual creation required, event will be created when listened to
129 //this.createEvent(COMPLETE);
134 * Implementation of Element's abstract method. Sets up config values.
136 * @method initAttributes
137 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
140 initAttributes: function (oConfigs) {
141 YAHOO.log('Initializing configuration attributes','info','ProgressBar');
143 Prog.superclass.initAttributes.call(this, oConfigs);
144 this.set('innerHTML',Prog.MARKUP);
145 this.addClass(CLASS_PROGBAR);
147 // I need to apply at least the following styles, if present in oConfigs,
148 // to the ProgressBar so when it later reads the width and height,
149 // they are already set to the correct values.
150 // id is important because it can be used as a CSS selector.
151 var key, presets = ['id',WIDTH,HEIGHT,'class','style'];
152 while((key = presets.pop())) {
153 if (key in oConfigs) {
154 this.set(key,oConfigs[key]);
161 * @description Reference to the HTML object that makes the moving bar (read-only)
162 * @type HTMLElement (div)
165 this.setAttributeConfig(BAR_EL, {
167 value: this.getElementsByClassName(CLASS_BAR)[0]
171 * @description Reference to the HTML object that overlays the bar providing the mask. (read-only)
172 * @type HTMLElement (table)
175 this.setAttributeConfig(MASK_EL, {
177 value: this.getElementsByClassName(CLASS_MASK)[0]
182 * @attribute direction
183 * @description Direction of movement of the bar.
184 * It can be any of 'ltr' (left to right), 'rtl' (the reverse) , 'ttb' (top to bottom) or 'btt'.
185 * Can only be set once and only before rendering.
187 * @type String (any of "ltr", "rtl", "ttb" or "btt")
189 this.setAttributeConfig(DIRECTION, {
191 validator:function(value) {
192 if (this._rendered) { return false; }
203 method: function(value) {
204 this._barSizeFunction = this._barSizeFunctions[this.get(ANIM)?1:0][value];
209 * @attribute maxValue
210 * @description Represents the top value for the bar.
211 * The bar will be fully extended when reaching this value.
212 * Values higher than this will be ignored.
216 this.setAttributeConfig(MAX_VALUE, {
218 validator: Lang.isNumber,
219 method: function (value) {
220 this.get('element').setAttribute('aria-valuemax',value);
221 if (this.get(VALUE) > value) { this.set(VALUE,value); }
226 * @attribute minValue
227 * @description Represents the lowest value for the bar.
228 * The bar will be totally collapsed when reaching this value.
229 * Values lower than this will be ignored.
234 this.setAttributeConfig(MIN_VALUE, {
236 validator: Lang.isNumber,
237 method: function (value) {
238 this.get('element').setAttribute('aria-valuemin',value);
239 if (this.get(VALUE) < value) { this.set(VALUE,value); }
244 * @description Width of the ProgressBar.
245 * If a number, it will be assumed to be in pixels.
246 * If a string it should be a valid setting for the CSS width attribute.
247 * It will always be returned as a string including units.
249 * @type Number or String
252 this.setAttributeConfig(WIDTH, {
254 return this.getStyle(WIDTH);
256 method: this._widthChange
262 * @description Height of the ProgressBar.
263 * If a number, it will be assumed to be in pixels.
264 * If a string it should be a valid setting for the CSS height attribute.
265 * It will always be returned as a string including units.
267 * @type Number or String
269 this.setAttributeConfig(HEIGHT, {
271 return this.getStyle(HEIGHT);
273 method: this._heightChange
279 * @attribute ariaTextTemplate
280 * @description Text to be voiced by screen readers.
281 * The text is processed by <a href="YAHOO.lang.html#method_substitute">YAHOO.lang.substitute</a>.
282 * It can use the placeholders {value}, {minValue} and {maxValue}
286 this.setAttributeConfig(ARIA_TEXT_TEMPLATE, {
292 * @description The value for the bar.
293 * Valid values are in between the minValue and maxValue attributes.
297 this.setAttributeConfig(VALUE, {
299 validator: function(value) {
300 return Lang.isNumber(value) && value >= this.get(MIN_VALUE) && value <= this.get(MAX_VALUE);
302 method: this._valueChange
307 * @description it accepts either a boolean (recommended) or an instance of <a href="YAHOO.util.Anim.html">YAHOO.util.Anim</a>.
308 * If a boolean, it will enable/disable animation creating its own instance of the animation utility.
309 * If given an instance of <a href="YAHOO.util.Anim.html">YAHOO.util.Anim</a> it will use that instance.
310 * The <a href="YAHOO.util.Anim.html">animation</a> utility needs to be loaded.
311 * When read, it returns the instance of the animation utility in use or null if none.
312 * It can be used to set the animation parameters such as easing methods or duration.
314 * @type {boolean} or {instance of YAHOO.util.Anim}
316 this.setAttributeConfig(ANIM, {
317 validator: function(value) {
318 return !!YAHOO.util.Anim;
320 setter: this._animSetter
326 * Renders the ProgressBar into the given container.
327 * If the container has other content, the ProgressBar will be appended to it.
328 * If the second argument is provided, the ProgressBar will be inserted before the given child.
329 * The method is chainable since it returns a reference to this instance.
331 * @param el {HTML Element} HTML element that will contain the ProgressBar
332 * @param before {HTML Element} (optional) If present, the ProgressBar will be inserted before this element.
333 * @return {YAHOO.widget.ProgressBar}
336 render: function(parent,before) {
338 YAHOO.log('start render','info','ProgressBar');
339 if (this._rendered) { return; }
340 this._rendered = true;
342 var direction = this.get(DIRECTION);
344 // If the developer set a className attribute on initialization,
345 // Element would have wiped out my own classNames
346 // So I need to insist on them, plus add the one for direction.
347 this.addClass(CLASS_PROGBAR);
348 this.addClass(CLASS_PROGBAR + '-' + direction);
350 var container = this.get('element');
351 container.tabIndex = 0;
352 container.setAttribute('role','progressbar');
353 container.setAttribute('aria-valuemin',this.get(MIN_VALUE));
354 container.setAttribute('aria-valuemax',this.get(MAX_VALUE));
356 this.appendTo(parent,before);
359 // I need to use the non-animated bar resizing function for initial redraw
360 this._barSizeFunction = this._barSizeFunctions[0][direction];
362 this._previousValue = this.get(VALUE);
364 // I can now set the correct bar resizer
365 if (this.get(ANIM)) {
366 this._barSizeFunction = this._barSizeFunctions[1][direction];
369 this.on('minValueChange',this.redraw);
370 this.on('maxValueChange',this.redraw);
376 * Recalculates the bar size and position and redraws it
380 redraw: function () {
381 YAHOO.log('Redraw','info','ProgressBar');
382 this._recalculateConstants();
383 this._valueChange(this.get(VALUE));
387 * Destroys the ProgressBar, related objects and unsubscribes from all events
391 destroy: function() {
392 YAHOO.log('destroy','info','ProgressBar');
393 this.set(ANIM,false);
394 this.unsubscribeAll();
395 var el = this.get('element');
396 if (el.parentNode) { el.parentNode.removeChild(el); }
399 * The previous value setting for the bar. Used mostly as information to event listeners
400 * @property _previousValue
407 * The actual space (in pixels) available for the bar within the mask (excludes margins)
408 * @property _barSpace
415 * The factor to convert the actual value of the bar into pixels
416 * @property _barSpace
424 * A flag to signal that rendering has already happened
425 * @property _rendered
433 * Function to be used to calculate bar size.
434 * It is picked from <a href="#property_barSizeFunctions">_barSizeFunctions</a>
435 * depending on direction and whether animation is active.
436 * @property _barSizeFunction
441 _barSizeFunction: null,
444 * Method called when the height attribute is changed
445 * @method _heightChange
446 * @param {int or string} value New height, in pixels if int or string including units
450 _heightChange: function(value) {
451 if (Lang.isNumber(value)) {
454 this.setStyle(HEIGHT,value);
460 * Method called when the height attribute is changed
461 * @method _widthChange
462 * @param {int or string} value New width, in pixels if int or string including units
466 _widthChange: function(value) {
467 if (Lang.isNumber(value)) {
470 this.setStyle(WIDTH,value);
476 * Due to rounding differences, some browsers fail to cover the whole area
477 * with the mask quadrants when the width or height is odd. This method
478 * stretches the lower and/or right quadrants to make the difference.
483 _fixEdges:function() {
484 if (!this._rendered || YAHOO.env.ua.ie || YAHOO.env.ua.gecko ) { return; }
485 var maskEl = this.get(MASK_EL),
486 tlEl = Dom.getElementsByClassName(CLASS_TL,undefined,maskEl)[0],
487 trEl = Dom.getElementsByClassName(CLASS_TR,undefined,maskEl)[0],
488 blEl = Dom.getElementsByClassName(CLASS_BL,undefined,maskEl)[0],
489 brEl = Dom.getElementsByClassName(CLASS_BR,undefined,maskEl)[0],
490 newSize = (parseInt(Dom.getStyle(maskEl,HEIGHT),10) -
491 parseInt(Dom.getStyle(tlEl,HEIGHT),10)) + 'px';
493 Dom.setStyle(blEl,HEIGHT,newSize);
494 Dom.setStyle(brEl,HEIGHT,newSize);
495 newSize = (parseInt(Dom.getStyle(maskEl,WIDTH),10) -
496 parseInt(Dom.getStyle(tlEl,WIDTH),10)) + 'px';
497 Dom.setStyle(trEl,WIDTH,newSize);
498 Dom.setStyle(brEl,WIDTH,newSize);
504 * Calculates some auxiliary values to make the rendering faster
505 * @method _recalculateConstants
509 _recalculateConstants: function() {
510 YAHOO.log('Recalculating auxiliary factors','info','ProgressBar');
511 var barEl = this.get(BAR_EL);
513 switch (this.get(DIRECTION)) {
516 this._barSpace = parseInt(this.get(WIDTH),10) -
517 (parseInt(Dom.getStyle(barEl,'marginLeft'),10) || 0) -
518 (parseInt(Dom.getStyle(barEl,'marginRight'),10) || 0);
522 this._barSpace = parseInt(this.get(HEIGHT),10) -
523 (parseInt(Dom.getStyle(barEl,'marginTop'),10) || 0)-
524 (parseInt(Dom.getStyle(barEl,'marginBottom'),10) || 0);
527 this._barFactor = this._barSpace / (this.get(MAX_VALUE) - (this.get(MIN_VALUE) || 0)) || 1;
531 * Called in response to a change in the <a href="#config_anim">anim</a> attribute.
532 * It creates and sets up or destroys the instance of the animation utility that will move the bar
533 * @method _animSetter
537 _animSetter: function (value) {
538 var anim, barEl = this.get(BAR_EL);
540 YAHOO.log('Turning animation on','info','ProgressBar');
541 if (value instanceof YAHOO.util.Anim) {
544 anim = new YAHOO.util.Anim(barEl);
546 anim.onTween.subscribe(this._animOnTween,this,true);
547 anim.onComplete.subscribe(this._animComplete,this,true);
549 // Temporary solution until http://yuilibrary.com/projects/yui2/ticket/2528222 gets solved:
550 var oldSetAttribute = anim.setAttribute,
552 switch(this.get(DIRECTION)) {
554 anim.setAttribute = function(attr , val , unit) {
555 val = Math.round(val);
556 oldSetAttribute.call(this,attr,val,unit);
557 Dom.setStyle(barEl,'top',(pb._barSpace - val) + 'px');
561 anim.setAttribute = function(attr , val , unit) {
562 val = Math.round(val);
563 oldSetAttribute.call(this,attr,val,unit);
564 Dom.setStyle(barEl,'left',(pb._barSpace - val) + 'px');
571 YAHOO.log('Turning animation off','info','ProgressBar');
572 anim = this.get(ANIM);
574 anim.onTween.unsubscribeAll();
575 anim.onComplete.unsubscribeAll();
579 this._barSizeFunction = this._barSizeFunctions[anim?1:0][this.get(DIRECTION)];
583 _animComplete: function() {
584 YAHOO.log('Animation completed','info','ProgressBar');
585 var value = this.get(VALUE);
586 this._previousValue = value;
587 this.fireEvent(PROGRESS,value);
588 this.fireEvent(COMPLETE, value);
589 Dom.removeClass(this.get(BAR_EL),CLASS_ANIM);
591 _animOnTween:function (name,oArgs) {
592 var value = Math.floor(this._tweenFactor * oArgs[0].currentFrame + this._previousValue);
593 // The following fills the logger window too fast
594 //YAHOO.log('Animation onTween at: ' + value,'info','ProgressBar');
595 this.fireEvent(PROGRESS,value);
599 * Called in response to a change in the <a href="#config_value">value</a> attribute.
600 * Moves the bar to reflect the new value
601 * @method _valueChange
605 _valueChange: function (value) {
606 YAHOO.log('set value: ' + value,'info','ProgressBar');
607 var anim = this.get(ANIM),
608 pixelValue = Math.floor((value - this.get(MIN_VALUE)) * this._barFactor),
609 barEl = this.get(BAR_EL);
611 this._setAriaText(value);
612 if (this._rendered) {
615 if (anim.isAnimated()) { anim._onComplete.fire(); } // see: http://yuilibrary.com/projects/yui2/ticket/2528217
617 this.fireEvent(START,this._previousValue);
618 this._barSizeFunction(value, pixelValue, barEl, anim);
623 * Utility method to set the ARIA value attributes
624 * @method _setAriaText
628 _setAriaText: function(value) {
629 // When animated, this fills the logger window too fast
630 //YAHOO.log('Show template','info','ProgressBar');
632 var container = this.get('element'),
633 text = Lang.substitute(this.get(ARIA_TEXT_TEMPLATE),{
635 minValue:this.get(MIN_VALUE),
636 maxValue:this.get(MAX_VALUE)
638 container.setAttribute('aria-valuenow',value);
639 container.setAttribute('aria-valuetext',text);
643 * Collection of functions used by to calculate the size of the bar.
644 * One of this will be used depending on direction and whether animation is active.
645 * @property _barSizeFunctions
646 * @type {collection of functions}
650 Prog.prototype._barSizeFunctions = b;
652 b[0][DIRECTION_LTR] = function(value, pixelValue, barEl, anim) {
653 Dom.setStyle(barEl,WIDTH, pixelValue + 'px');
654 this.fireEvent(PROGRESS,value);
655 this.fireEvent(COMPLETE,value);
657 b[0][DIRECTION_RTL] = function(value, pixelValue, barEl, anim) {
658 Dom.setStyle(barEl,WIDTH, pixelValue + 'px');
659 Dom.setStyle(barEl,'left',(this._barSpace - pixelValue) + 'px');
660 this.fireEvent(PROGRESS,value);
661 this.fireEvent(COMPLETE,value);
663 b[0][DIRECTION_TTB] = function(value, pixelValue, barEl, anim) {
664 Dom.setStyle(barEl,HEIGHT, pixelValue + 'px');
665 this.fireEvent(PROGRESS,value);
666 this.fireEvent(COMPLETE,value);
668 b[0][DIRECTION_BTT] = function(value, pixelValue, barEl, anim) {
669 Dom.setStyle(barEl,HEIGHT, pixelValue + 'px');
670 Dom.setStyle(barEl,'top', (this._barSpace - pixelValue) + 'px');
671 this.fireEvent(PROGRESS,value);
672 this.fireEvent(COMPLETE,value);
674 b[1][DIRECTION_LTR] = function(value, pixelValue, barEl, anim) {
675 Dom.addClass(barEl,CLASS_ANIM);
676 this._tweenFactor = (value - this._previousValue) / anim.totalFrames / anim.duration;
677 anim.attributes = {width:{ to: pixelValue }};
680 b[1][DIRECTION_RTL] = b[1][DIRECTION_LTR];
681 b[1][DIRECTION_TTB] = function(value, pixelValue, barEl, anim) {
682 Dom.addClass(barEl,CLASS_ANIM);
683 this._tweenFactor = (value - this._previousValue) / anim.totalFrames / anim.duration;
684 anim.attributes = {height:{to: pixelValue}};
687 b[1][DIRECTION_BTT] = b[1][DIRECTION_TTB];
691 YAHOO.register("progressbar", YAHOO.widget.ProgressBar, {version: "2.8.0r4", build: "2449"});