// Copyright 2006 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Implementation of a progress bar. * * @author arv@google.com (Erik Arvidsson) * @see ../demos/progressbar.html */ goog.provide('goog.ui.ProgressBar'); goog.provide('goog.ui.ProgressBar.Orientation'); goog.require('goog.a11y.aria'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.dom.classlist'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.ui.Component'); goog.require('goog.ui.RangeModel'); goog.require('goog.userAgent'); /** * This creates a progress bar object. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. * @constructor * @extends {goog.ui.Component} */ goog.ui.ProgressBar = function(opt_domHelper) { goog.ui.Component.call(this, opt_domHelper); /** @type {?HTMLDivElement} */ this.thumbElement_; /** * The underlying data model for the progress bar. * @type {goog.ui.RangeModel} * @private */ this.rangeModel_ = new goog.ui.RangeModel; goog.events.listen( this.rangeModel_, goog.ui.Component.EventType.CHANGE, this.handleChange_, false, this); }; goog.inherits(goog.ui.ProgressBar, goog.ui.Component); goog.tagUnsealableClass(goog.ui.ProgressBar); /** * Enum for representing the orientation of the progress bar. * * @enum {string} */ goog.ui.ProgressBar.Orientation = { VERTICAL: 'vertical', HORIZONTAL: 'horizontal' }; /** * Map from progress bar orientation to CSS class names. * @type {!Object} * @private */ goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_ = {}; goog.ui.ProgressBar .ORIENTATION_TO_CSS_NAME_[goog.ui.ProgressBar.Orientation.VERTICAL] = goog.getCssName('progress-bar-vertical'); goog.ui.ProgressBar .ORIENTATION_TO_CSS_NAME_[goog.ui.ProgressBar.Orientation.HORIZONTAL] = goog.getCssName('progress-bar-horizontal'); /** * Creates the DOM nodes needed for the progress bar * @override */ goog.ui.ProgressBar.prototype.createDom = function() { this.thumbElement_ = this.createThumb_(); this.setElementInternal(this.getDomHelper().createDom( goog.dom.TagName.DIV, goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_], this.thumbElement_)); this.setValueState_(); this.setMinimumState_(); this.setMaximumState_(); }; /** @override */ goog.ui.ProgressBar.prototype.enterDocument = function() { goog.ui.ProgressBar.superClass_.enterDocument.call(this); this.attachEvents_(); this.updateUi_(); var element = this.getElement(); goog.asserts.assert(element, 'The progress bar DOM element cannot be null.'); // state live = polite will notify the user of updates, // but will not interrupt ongoing feedback goog.a11y.aria.setRole(element, 'progressbar'); goog.a11y.aria.setState(element, 'live', 'polite'); }; /** @override */ goog.ui.ProgressBar.prototype.exitDocument = function() { goog.ui.ProgressBar.superClass_.exitDocument.call(this); this.detachEvents_(); }; /** * This creates the thumb element. * @private * @return {HTMLDivElement} The created thumb element. */ goog.ui.ProgressBar.prototype.createThumb_ = function() { return this.getDomHelper().createDom( goog.dom.TagName.DIV, goog.getCssName('progress-bar-thumb')); }; /** * Adds the initial event listeners to the element. * @private */ goog.ui.ProgressBar.prototype.attachEvents_ = function() { if (goog.userAgent.IE && goog.userAgent.VERSION < 7) { goog.events.listen( this.getElement(), goog.events.EventType.RESIZE, this.updateUi_, false, this); } }; /** * Removes the event listeners added by attachEvents_. * @private */ goog.ui.ProgressBar.prototype.detachEvents_ = function() { if (goog.userAgent.IE && goog.userAgent.VERSION < 7) { goog.events.unlisten( this.getElement(), goog.events.EventType.RESIZE, this.updateUi_, false, this); } }; /** * Decorates an existing HTML DIV element as a progress bar input. If the * element contains a child with a class name of 'progress-bar-thumb' that will * be used as the thumb. * @param {Element} element The HTML element to decorate. * @override */ goog.ui.ProgressBar.prototype.decorateInternal = function(element) { goog.ui.ProgressBar.superClass_.decorateInternal.call(this, element); goog.dom.classlist.add( goog.asserts.assert(this.getElement()), goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_]); // find thumb var thumb = goog.dom.getElementsByTagNameAndClass( null, goog.getCssName('progress-bar-thumb'), this.getElement())[0]; if (!thumb) { thumb = this.createThumb_(); this.getElement().appendChild(thumb); } this.thumbElement_ = /** @type {!HTMLDivElement} */ (thumb); }; /** * @return {number} The value. */ goog.ui.ProgressBar.prototype.getValue = function() { return this.rangeModel_.getValue(); }; /** * Sets the value * @param {number} v The value. */ goog.ui.ProgressBar.prototype.setValue = function(v) { this.rangeModel_.setValue(v); if (this.getElement()) { this.setValueState_(); } }; /** * Sets the state for a11y of the current value. * @private */ goog.ui.ProgressBar.prototype.setValueState_ = function() { var element = this.getElement(); goog.asserts.assert(element, 'The progress bar DOM element cannot be null.'); goog.a11y.aria.setState(element, 'valuenow', this.getValue()); }; /** * @return {number} The minimum value. */ goog.ui.ProgressBar.prototype.getMinimum = function() { return this.rangeModel_.getMinimum(); }; /** * Sets the minimum number * @param {number} v The minimum value. */ goog.ui.ProgressBar.prototype.setMinimum = function(v) { this.rangeModel_.setMinimum(v); if (this.getElement()) { this.setMinimumState_(); } }; /** * Sets the state for a11y of the minimum value. * @private */ goog.ui.ProgressBar.prototype.setMinimumState_ = function() { var element = this.getElement(); goog.asserts.assert(element, 'The progress bar DOM element cannot be null.'); goog.a11y.aria.setState(element, 'valuemin', this.getMinimum()); }; /** * @return {number} The maximum value. */ goog.ui.ProgressBar.prototype.getMaximum = function() { return this.rangeModel_.getMaximum(); }; /** * Sets the maximum number * @param {number} v The maximum value. */ goog.ui.ProgressBar.prototype.setMaximum = function(v) { this.rangeModel_.setMaximum(v); if (this.getElement()) { this.setMaximumState_(); } }; /** * Sets the state for a11y of the maximum valiue. * @private */ goog.ui.ProgressBar.prototype.setMaximumState_ = function() { var element = this.getElement(); goog.asserts.assert(element, 'The progress bar DOM element cannot be null.'); goog.a11y.aria.setState(element, 'valuemax', this.getMaximum()); }; /** * * @type {goog.ui.ProgressBar.Orientation} * @private */ goog.ui.ProgressBar.prototype.orientation_ = goog.ui.ProgressBar.Orientation.HORIZONTAL; /** * Call back when the internal range model changes * @param {goog.events.Event} e The event object. * @private */ goog.ui.ProgressBar.prototype.handleChange_ = function(e) { this.updateUi_(); this.dispatchEvent(goog.ui.Component.EventType.CHANGE); }; /** * This is called when we need to update the size of the thumb. This happens * when first created as well as when the value and the orientation changes. * @private */ goog.ui.ProgressBar.prototype.updateUi_ = function() { if (this.thumbElement_) { var min = this.getMinimum(); var max = this.getMaximum(); var val = this.getValue(); var ratio = (val - min) / (max - min); var size = Math.round(ratio * 100); if (this.orientation_ == goog.ui.ProgressBar.Orientation.VERTICAL) { // Note(arv): IE up to version 6 has some serious computation bugs when // using percentages or bottom. We therefore first set the height to // 100% and measure that and base the top and height on that size instead. if (goog.userAgent.IE && goog.userAgent.VERSION < 7) { this.thumbElement_.style.top = '0'; this.thumbElement_.style.height = '100%'; var h = this.thumbElement_.offsetHeight; var bottom = Math.round(ratio * h); this.thumbElement_.style.top = h - bottom + 'px'; this.thumbElement_.style.height = bottom + 'px'; } else { this.thumbElement_.style.top = (100 - size) + '%'; this.thumbElement_.style.height = size + '%'; } } else { this.thumbElement_.style.width = size + '%'; } } }; /** * This is called when we need to setup the UI sizes and positions. This * happens when we create the element and when we change the orientation. * @private */ goog.ui.ProgressBar.prototype.initializeUi_ = function() { var tStyle = this.thumbElement_.style; if (this.orientation_ == goog.ui.ProgressBar.Orientation.VERTICAL) { tStyle.left = '0'; tStyle.width = '100%'; } else { tStyle.top = tStyle.left = '0'; tStyle.height = '100%'; } }; /** * Changes the orientation * @param {goog.ui.ProgressBar.Orientation} orient The orientation. */ goog.ui.ProgressBar.prototype.setOrientation = function(orient) { if (this.orientation_ != orient) { var oldCss = goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_]; var newCss = goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[orient]; this.orientation_ = orient; // Update the DOM var element = this.getElement(); if (element) { goog.dom.classlist.swap(element, oldCss, newCss); this.initializeUi_(); this.updateUi_(); } } }; /** * @return {goog.ui.ProgressBar.Orientation} The orientation of the * progress bar. */ goog.ui.ProgressBar.prototype.getOrientation = function() { return this.orientation_; }; /** @override */ goog.ui.ProgressBar.prototype.disposeInternal = function() { this.detachEvents_(); goog.ui.ProgressBar.superClass_.disposeInternal.call(this); this.thumbElement_ = null; this.rangeModel_.dispose(); }; /** * @return {?number} The step value used to determine how to round the value. */ goog.ui.ProgressBar.prototype.getStep = function() { return this.rangeModel_.getStep(); }; /** * Sets the step value. The step value is used to determine how to round the * value. * @param {?number} step The step size. */ goog.ui.ProgressBar.prototype.setStep = function(step) { this.rangeModel_.setStep(step); };