1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 /** 24 * Creates a spinner control. 25 * @constructor 26 * @class 27 * Represents a entry field for entering numeric values. Has 2 arrow buttons 28 * that can be used to increment or decrement the current value with a step 29 * that can be specified. 30 * 31 * <h4>CSS</h4> 32 * <ul> 33 * <li><code>DwtSpinner</code> -- a table that contains the spinner elements 34 * <li><code>DwtSpinner-inputCell</code> -- the TD that holds the input field 35 * <li><code>DwtSpinner-btnCell</code> -- a DIV holding the 2 arrow buttons 36 * <li><code>DwtSpinner-upBtn</code> -- the DIV button for increment operation 37 * <li><code>DwtSpinner-downBtn</code> -- the DIV button for decrement operation 38 * <li><code>DwtSpinner-up-pressed</code> -- upBtn while pressed 39 * <li><code>DwtSpinner-down-pressed</code> -- downBtn while pressed 40 * <li><code>DwtSpinner-disabled</code> -- the table gets this class added when the widget is disabled 41 * </ul> 42 * 43 * @param {hash} params a hash of parameters 44 * @param {DwtComposite} params.parent the parent widget 45 * @param {string} params.className the class name for the containing DIV (see {@link DwtControl}) 46 * @param {string} params.posStyle the positioning style (see {@link DwtControl}) 47 * @param {number} params.max the maximum value 48 * @param {number} params.min the minimum value 49 * @param {number} params.size size of the input field, as in <code><input size="X"></code> 50 * @param {number} params.value the original value of the input field 51 * @param {number} params.maxLen the maximum length of the text in the input field 52 * @param {number} params.step the amount to add or substract when the arrow buttons are pressed 53 * @param {number} [params.decimals=0] Number of decimal digits. Specify 0 to allow only 54 * integers (default). Pass <code>null</code> to allow float numbers but 55 * not enforce decimals. 56 * @param {string} [params.align="right"] the align of the input field text (see <code>dwt.css</code>) 57 * 58 * @author Mihai Bazon 59 * 60 * @extends DwtControl 61 */ 62 DwtSpinner = function(params) { 63 if (arguments.length == 0) return; 64 DwtControl.call(this, { parent : params.parent, 65 className : params.className, 66 posStyle : params.posStyle, 67 parentElement : params.parentElement 68 }); 69 70 // setup arguments 71 this._maxValue = params.max != null ? params.max : null; 72 this._minValue = params.min != null ? params.min : null; 73 this._fieldSize = params.size != null ? params.size : 3; 74 this._origValue = params.value || 0; 75 this._maxLen = params.maxLen || null; 76 this._step = params.step || 1; 77 this._decimals = 'decimals' in params ? params.decimals : 0; 78 this._align = params.align || null; 79 80 // timerFunc is a closure that gets called upon timeout when the user 81 // presses and holds the mouse button 82 this._timerFunc = AjxCallback.simpleClosure(this._timerFunc, this); 83 84 // upon click and hold we capture mouse events 85 this._btnPressCapture = new DwtMouseEventCapture({ 86 targetObj:this, 87 id:"DwtSpinner", 88 mouseUpHdlr:AjxCallback.simpleClosure(this._stopCapture, this) 89 }); 90 91 this._createElements(); 92 }; 93 94 DwtSpinner.prototype = new DwtControl; 95 DwtSpinner.prototype.constructor = DwtSpinner; 96 97 DwtSpinner.INIT_TIMER = 250; 98 DwtSpinner.SLOW_TIMER = 125; 99 DwtSpinner.FAST_TIMER = 33; 100 101 DwtSpinner.prototype._createElements = function() { 102 var div = this.getHtmlElement(); 103 var id = Dwt.getNextId(); 104 this._idField = id; 105 this._idUpButton = id + "-up"; 106 this._idDownButton = id + "-down"; 107 var html = [ "<table class='DwtSpinner' cellspacing='0' cellpadding='0'>", 108 "<tr><td rowspan='2' class='DwtSpinner-inputCell'>", "<input id='", id, "' autocomplete='off' />", "</td>", 109 "<td unselectable id='", this._idUpButton, "' class='DwtSpinner-upBtn'><div class='ImgUpArrowSmall'> </div></td>", 110 "</tr><tr>", 111 "<td unselectable id='", this._idDownButton, "' class='DwtSpinner-downBtn'><div class='ImgDownArrowSmall'> </div></td>", 112 "</tr></table>" ]; 113 114 115 // "<td><div class='DwtSpinner-btnCell'>", 116 // "<div unselectable class='DwtSpinner-upBtn' id='", this._idUpButton, "'><div class='ImgUpArrowSmall'> </div></div>", 117 // "<div unselectable class='DwtSpinner-downBtn' id='", this._idDownButton, "'><div class='ImgDownArrowSmall'> </div></div>", 118 // "</div></td></tr></table>" ]; 119 div.innerHTML = html.join(""); 120 121 var b1 = this._getUpButton(); 122 b1.onmousedown = AjxCallback.simpleClosure(this._btnPressed, this, "Up"); 123 var b2 = this._getDownButton(); 124 b2.onmousedown = AjxCallback.simpleClosure(this._btnPressed, this, "Down"); 125 // if (AjxEnv.isIE) { 126 // b1.ondblclick = b1.onmousedown; 127 // b2.ondblclick = b2.onmousedown; 128 // } 129 // if (AjxEnv.isIE && b1.offsetHeight == 1) { 130 // // we must correct button heights for IE 131 // div = b1.parentNode; 132 // var td = div.parentNode; 133 // div.style.height = td.offsetHeight + "px"; 134 // // b1.style.height = b2.style.height = td.offsetHeight / 2 + "px"; 135 // // b2.style.top = ""; 136 // // b2.style.bottom = "0px"; 137 // } 138 var input = this.getInputElement(); 139 if (this._maxLen) 140 input.maxLength = this._maxLen; 141 if (this._fieldSize) 142 input.size = this._fieldSize; 143 if (this._align) 144 input.style.textAlign = this._align; 145 if (this._origValue != null) 146 this.setValue(this._origValue); 147 148 input.onblur = AjxCallback.simpleClosure(this.setValue, this, null); 149 input[(AjxEnv.isIE || AjxEnv.isOpera) ? "onkeydown" : "onkeypress"] 150 = AjxCallback.simpleClosure(this.__onKeyPress, this); 151 }; 152 153 DwtSpinner.prototype._getValidValue = function(val) { 154 var n = parseFloat(val); 155 if (isNaN(n) || n == null) 156 n = this._lastValidValue; // note that this may be string 157 if (n == null) 158 n = this._minValue || 0; 159 if (this._minValue != null && n < this._minValue) 160 n = this._minValue; 161 if (this._maxValue != null && n > this._maxValue) 162 n = this._maxValue; 163 // make sure it's a number 164 n = parseFloat(n); 165 if (this._decimals != null) 166 n = n.toFixed(this._decimals); 167 this._lastValidValue = n; 168 return n; 169 }; 170 171 /** 172 * Gets the input element. 173 * 174 * @return {Element} the element 175 */ 176 DwtSpinner.prototype.getInputElement = function() { 177 return document.getElementById(this._idField); 178 }; 179 180 DwtSpinner.prototype._getUpButton = function() { 181 return document.getElementById(this._idUpButton); 182 }; 183 184 DwtSpinner.prototype._getDownButton = function() { 185 return document.getElementById(this._idDownButton); 186 }; 187 188 DwtSpinner.prototype._getButton = function(direction) { 189 switch (direction) { 190 case "Up" : return this._getUpButton(); 191 case "Down" : return this._getDownButton(); 192 } 193 }; 194 195 DwtSpinner.prototype._setBtnState = function(dir, disabled) { 196 var btn = this._getButton(dir); 197 if (disabled) { 198 Dwt.addClass(btn, "DwtSpinner-" + dir + "-disabled"); 199 btn.firstChild.className = "Img" + dir + "ArrowSmallDis"; 200 } else { 201 Dwt.delClass(btn, "DwtSpinner-" + dir + "-disabled"); 202 btn.firstChild.className = "Img" + dir + "ArrowSmall"; 203 } 204 }; 205 206 /** 207 * Gets the value. 208 * 209 * @return {number} the value 210 */ 211 DwtSpinner.prototype.getValue = function() { 212 return parseFloat(this._getValidValue(this.getInputElement().value)); 213 }; 214 215 /** 216 * Sets the value. 217 * 218 * @param {number} val the value 219 */ 220 DwtSpinner.prototype.setValue = function(val) { 221 if (val == null) 222 val = this.getInputElement().value; 223 val = this._getValidValue(val); 224 this.getInputElement().value = val; 225 val = parseFloat(val); 226 this._setBtnState("Down", this._minValue != null && this._minValue == val); 227 this._setBtnState("Up", this._maxValue != null && this._maxValue == val); 228 }; 229 230 DwtSpinner.prototype.setEnabled = function(enabled) { 231 DwtControl.prototype.setEnabled.call(this, enabled); 232 this.getInputElement().disabled = !enabled; 233 var table = this.getHtmlElement().firstChild; 234 if (!enabled) 235 Dwt.addClass(table, "DwtSpinner-disabled"); 236 else 237 Dwt.delClass(table, "DwtSpinner-disabled"); 238 }; 239 240 DwtSpinner.prototype._rotateVal = function(direction) { 241 var val = this.getValue(); 242 switch (direction) { 243 case "Up" : val += this._step; break; 244 case "Down" : val -= this._step; break; 245 } 246 this.setValue(val); 247 }; 248 249 DwtSpinner.prototype._btnPressed = function(direction) { 250 if (!this.getEnabled()) 251 return; 252 Dwt.addClass(this._getButton(direction), "DwtSpinner-" + direction + "-pressed"); 253 this._direction = direction; 254 this._rotateVal(direction); 255 this._btnPressCapture.capture(); 256 this._timerSteps = 0; 257 this._timer = setTimeout(this._timerFunc, DwtSpinner.INIT_TIMER); 258 }; 259 260 DwtSpinner.prototype._timerFunc = function() { 261 var v1 = this.getValue(); 262 this._rotateVal(this._direction); 263 var v2 = this.getValue(); 264 this._timerSteps++; 265 var timeout = this._timerSteps > 4 ? DwtSpinner.FAST_TIMER : DwtSpinner.SLOW_TIMER; 266 if (v1 != v2) 267 this._timer = setTimeout(this._timerFunc, timeout); 268 else 269 this._stopCapture(); 270 }; 271 272 DwtSpinner.prototype._stopCapture = function() { 273 if (this._timer) 274 clearTimeout(this._timer); 275 this._timer = null; 276 this._timerSteps = null; 277 var direction = this._direction; 278 Dwt.delClass(this._getButton(direction), "DwtSpinner-" + direction + "-pressed"); 279 this._direction = null; 280 this._btnPressCapture.release(); 281 var input = this.getInputElement(); 282 input.focus(); 283 Dwt.setSelectionRange(input, 0, input.value.length); 284 }; 285 286 DwtSpinner.prototype.__onKeyPress = function(ev) { 287 if (AjxEnv.isIE) 288 ev = window.event; 289 var dir = null; 290 switch (ev.keyCode) { 291 case 38: 292 dir = "Up"; 293 break; 294 case 40: 295 dir = "Down"; 296 break; 297 } 298 if (dir) { 299 this._rotateVal(dir); 300 var input = this.getInputElement(); 301 Dwt.setSelectionRange(input, 0, input.value.length); 302 } 303 }; 304 305 DwtSpinner.prototype.focus = function() { 306 this.getInputElement().focus(); 307 }; 308 309 DwtSpinner.prototype.select = function() { 310 var input = this.getInputElement(); 311 input.focus(); 312 Dwt.setSelectionRange(input, 0, input.value.length); 313 }; 314