| // 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 range model. This is an implementation of |
| * the BoundedRangeModel as described by Java at |
| * http://java.sun.com/javase/6/docs/api/javax/swing/BoundedRangeModel.html. |
| * |
| * One good way to understand the range model is to think of a scroll bar for |
| * a scrollable element. In that case minimum is 0, maximum is scrollHeight, |
| * value is scrollTop and extent is clientHeight. |
| * |
| * Based on http://webfx.eae.net/dhtml/slider/js/range.js |
| * |
| * @author arv@google.com (Erik Arvidsson) |
| */ |
| |
| goog.provide('goog.ui.RangeModel'); |
| |
| goog.require('goog.events.EventTarget'); |
| goog.require('goog.ui.Component'); |
| |
| |
| |
| /** |
| * Creates a range model |
| * @extends {goog.events.EventTarget} |
| * @constructor |
| */ |
| goog.ui.RangeModel = function() { |
| goog.events.EventTarget.call(this); |
| }; |
| goog.inherits(goog.ui.RangeModel, goog.events.EventTarget); |
| goog.tagUnsealableClass(goog.ui.RangeModel); |
| |
| |
| /** |
| * @type {number} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.value_ = 0; |
| |
| |
| /** |
| * @type {number} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.minimum_ = 0; |
| |
| |
| /** |
| * @type {number} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.maximum_ = 100; |
| |
| |
| /** |
| * @type {number} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.extent_ = 0; |
| |
| |
| /** |
| * @type {?number} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.step_ = 1; |
| |
| |
| /** |
| * This is true if something is changed as a side effect. This happens when for |
| * example we set the maximum below the current value. |
| * @type {boolean} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.isChanging_ = false; |
| |
| |
| /** |
| * If set to true, we do not fire any change events. |
| * @type {boolean} |
| * @private |
| */ |
| goog.ui.RangeModel.prototype.mute_ = false; |
| |
| |
| /** |
| * Sets the model to mute / unmute. |
| * @param {boolean} muteValue Whether or not to mute the range, i.e., |
| * suppress any CHANGE events. |
| */ |
| goog.ui.RangeModel.prototype.setMute = function(muteValue) { |
| this.mute_ = muteValue; |
| }; |
| |
| |
| /** |
| * Sets the value. |
| * @param {number} value The new value. |
| */ |
| goog.ui.RangeModel.prototype.setValue = function(value) { |
| value = this.roundToStepWithMin(value); |
| if (this.value_ != value) { |
| if (value + this.extent_ > this.maximum_) { |
| this.value_ = this.maximum_ - this.extent_; |
| } else if (value < this.minimum_) { |
| this.value_ = this.minimum_; |
| } else { |
| this.value_ = value; |
| } |
| if (!this.isChanging_ && !this.mute_) { |
| this.dispatchEvent(goog.ui.Component.EventType.CHANGE); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @return {number} the current value. |
| */ |
| goog.ui.RangeModel.prototype.getValue = function() { |
| return this.roundToStepWithMin(this.value_); |
| }; |
| |
| |
| /** |
| * Sets the extent. The extent is the 'size' of the value. |
| * @param {number} extent The new extent. |
| */ |
| goog.ui.RangeModel.prototype.setExtent = function(extent) { |
| extent = this.roundToStepWithMin(extent); |
| if (this.extent_ != extent) { |
| if (extent < 0) { |
| this.extent_ = 0; |
| } else if (this.value_ + extent > this.maximum_) { |
| this.extent_ = this.maximum_ - this.value_; |
| } else { |
| this.extent_ = extent; |
| } |
| if (!this.isChanging_ && !this.mute_) { |
| this.dispatchEvent(goog.ui.Component.EventType.CHANGE); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @return {number} The extent for the range model. |
| */ |
| goog.ui.RangeModel.prototype.getExtent = function() { |
| return this.roundToStep(this.extent_); |
| }; |
| |
| |
| /** |
| * Sets the minimum |
| * @param {number} minimum The new minimum. |
| */ |
| goog.ui.RangeModel.prototype.setMinimum = function(minimum) { |
| // Don't round minimum because it is the base |
| if (this.minimum_ != minimum) { |
| var oldIsChanging = this.isChanging_; |
| this.isChanging_ = true; |
| |
| this.minimum_ = minimum; |
| |
| if (minimum + this.extent_ > this.maximum_) { |
| this.extent_ = this.maximum_ - this.minimum_; |
| } |
| if (minimum > this.value_) { |
| this.setValue(minimum); |
| } |
| if (minimum > this.maximum_) { |
| this.extent_ = 0; |
| this.setMaximum(minimum); |
| this.setValue(minimum); |
| } |
| |
| |
| this.isChanging_ = oldIsChanging; |
| if (!this.isChanging_ && !this.mute_) { |
| this.dispatchEvent(goog.ui.Component.EventType.CHANGE); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @return {number} The minimum value for the range model. |
| */ |
| goog.ui.RangeModel.prototype.getMinimum = function() { |
| return this.roundToStepWithMin(this.minimum_); |
| }; |
| |
| |
| /** |
| * Sets the maximum |
| * @param {number} maximum The new maximum. |
| */ |
| goog.ui.RangeModel.prototype.setMaximum = function(maximum) { |
| maximum = this.roundToStepWithMin(maximum); |
| if (this.maximum_ != maximum) { |
| var oldIsChanging = this.isChanging_; |
| this.isChanging_ = true; |
| |
| this.maximum_ = maximum; |
| |
| if (maximum < this.value_ + this.extent_) { |
| this.setValue(maximum - this.extent_); |
| } |
| if (maximum < this.minimum_) { |
| this.extent_ = 0; |
| this.setMinimum(maximum); |
| this.setValue(this.maximum_); |
| } |
| if (maximum < this.minimum_ + this.extent_) { |
| this.extent_ = this.maximum_ - this.minimum_; |
| } |
| |
| this.isChanging_ = oldIsChanging; |
| if (!this.isChanging_ && !this.mute_) { |
| this.dispatchEvent(goog.ui.Component.EventType.CHANGE); |
| } |
| } |
| }; |
| |
| |
| /** |
| * @return {number} The maximimum value for the range model. |
| */ |
| goog.ui.RangeModel.prototype.getMaximum = function() { |
| return this.roundToStepWithMin(this.maximum_); |
| }; |
| |
| |
| /** |
| * Returns the step value. The step value is used to determine how to round the |
| * value. |
| * @return {?number} The maximimum value for the range model. |
| */ |
| goog.ui.RangeModel.prototype.getStep = function() { |
| return this.step_; |
| }; |
| |
| |
| /** |
| * Sets the step. The step value is used to determine how to round the value. |
| * @param {?number} step The step size. |
| */ |
| goog.ui.RangeModel.prototype.setStep = function(step) { |
| if (this.step_ != step) { |
| this.step_ = step; |
| |
| // adjust value, extent and maximum |
| var oldIsChanging = this.isChanging_; |
| this.isChanging_ = true; |
| |
| this.setMaximum(this.getMaximum()); |
| this.setExtent(this.getExtent()); |
| this.setValue(this.getValue()); |
| |
| this.isChanging_ = oldIsChanging; |
| if (!this.isChanging_ && !this.mute_) { |
| this.dispatchEvent(goog.ui.Component.EventType.CHANGE); |
| } |
| } |
| }; |
| |
| |
| /** |
| * Rounds to the closest step using the minimum value as the base. |
| * @param {number} value The number to round. |
| * @return {number} The number rounded to the closest step. |
| */ |
| goog.ui.RangeModel.prototype.roundToStepWithMin = function(value) { |
| if (this.step_ == null) return value; |
| return this.minimum_ + |
| Math.round((value - this.minimum_) / this.step_) * this.step_; |
| }; |
| |
| |
| /** |
| * Rounds to the closest step. |
| * @param {number} value The number to round. |
| * @return {number} The number rounded to the closest step. |
| */ |
| goog.ui.RangeModel.prototype.roundToStep = function(value) { |
| if (this.step_ == null) return value; |
| return Math.round(value / this.step_) * this.step_; |
| }; |