blob: f3a53b8a9e138b5334056c268e8177bcbdaa5d8d [file] [log] [blame]
// 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.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_();
var cs = goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_];
this.setElementInternal(
this.getDomHelper().createDom('div', cs, 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 /** @type {!HTMLDivElement} */ (this.getDomHelper().createDom('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_ = 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);
};