| /* |
| Copyright (c) 2004-2006, The Dojo Foundation |
| All Rights Reserved. |
| |
| Licensed under the Academic Free License version 2.1 or above OR the |
| modified BSD license. For more information on Dojo licensing, see: |
| |
| http://dojotoolkit.org/community/licensing.shtml |
| */ |
| |
| dojo.provide("dojo.widget.Spinner"); |
| |
| dojo.require("dojo.io.*"); |
| dojo.require("dojo.lfx.*"); |
| dojo.require("dojo.html.*"); |
| dojo.require("dojo.html.layout"); |
| dojo.require("dojo.string"); |
| dojo.require("dojo.widget.*"); |
| dojo.require("dojo.widget.IntegerTextbox"); |
| dojo.require("dojo.widget.RealNumberTextbox"); |
| dojo.require("dojo.widget.DateTextbox"); |
| |
| dojo.require("dojo.experimental"); |
| |
| dojo.declare( |
| "dojo.widget.Spinner", |
| null, |
| { |
| // summary: Mixin for validation widgets with a spinner |
| // description: This class basically (conceptually) extends dojo.widget.ValidationTextbox. |
| // It modifies the template to have up/down arrows, and provides related handling code. |
| |
| _typamaticTimer: null, |
| _typamaticFunction: null, |
| _currentTimeout: this.defaultTimeout, |
| _eventCount: 0, |
| |
| // defaultTimeout: Number |
| // number of milliseconds before a held key or button becomes typematic |
| defaultTimeout: 500, |
| |
| // timeoutChangeRate: Number |
| // fraction of time used to change the typematic timer between events |
| // 1.0 means that each typematic event fires at defaultTimeout intervals |
| // < 1.0 means that each typematic event fires at an increasing faster rate |
| timeoutChangeRate: 0.90, |
| |
| templatePath: dojo.uri.dojoUri("src/widget/templates/Spinner.html"), |
| templateCssPath: dojo.uri.dojoUri("src/widget/templates/Spinner.css"), |
| |
| // incrementSrc: String |
| // up arrow graphic URL |
| incrementSrc: dojo.uri.dojoUri("src/widget/templates/images/spinnerIncrement.gif"), |
| |
| // decrementSrc: String |
| // down arrow graphic URL |
| decrementSrc: dojo.uri.dojoUri("src/widget/templates/images/spinnerDecrement.gif"), |
| |
| // does the keyboard related stuff |
| _handleKeyEvents: function(/*Event*/ evt){ |
| if(!evt.key){ return; } |
| |
| if(!evt.ctrlKey && !evt.altKey){ |
| switch(evt.key){ |
| case evt.KEY_DOWN_ARROW: |
| dojo.event.browser.stopEvent(evt); |
| this._downArrowPressed(evt); |
| return; |
| case evt.KEY_UP_ARROW: |
| dojo.event.browser.stopEvent(evt); |
| this._upArrowPressed(evt); |
| return; |
| } |
| } |
| this._eventCount++; |
| }, |
| |
| _onSpinnerKeyUp: function(/*Event*/ evt){ |
| this._arrowReleased(evt); |
| this.onkeyup(evt); |
| }, |
| |
| // reset button size; this function is called when the input area has changed size |
| _resize: function(){ |
| var inputSize = dojo.html.getBorderBox(this.textbox); |
| this.buttonSize = { width: inputSize.height / 2, height: inputSize.height / 2 }; |
| if(this.upArrowNode){ |
| dojo.html.setMarginBox(this.upArrowNode, this.buttonSize); |
| dojo.html.setMarginBox(this.downArrowNode, this.buttonSize); |
| } |
| }, |
| |
| _pressButton: function(/*DomNode*/ node){ |
| node.style.borderWidth = "1px 0px 0px 1px"; |
| node.style.borderStyle = "inset"; |
| }, |
| |
| _releaseButton: function(/*DomNode*/ node){ |
| node.style.borderWidth = "0px 1px 1px 0px"; |
| node.style.borderStyle = "outset"; |
| }, |
| |
| _arrowPressed: function(/*Event*/ evt, /*Number*/ direction){ |
| var nodePressed = (direction == -1) ? this.downArrowNode : this.upArrowNode; |
| var nodeReleased = (direction == +1) ? this.downArrowNode : this.upArrowNode; |
| if(typeof evt != "number"){ |
| if(this._typamaticTimer != null){ |
| if(this._typamaticNode == nodePressed){ |
| return; |
| } |
| dojo.lang.clearTimeout(this._typamaticTimer); |
| } |
| this._releaseButton(nodeReleased); |
| this._eventCount++; |
| this._typamaticTimer = null; |
| this._currentTimeout = this.defaultTimeout; |
| |
| }else if (evt != this._eventCount){ |
| this._releaseButton(nodePressed); |
| return; |
| } |
| this._pressButton(nodePressed); |
| this._setCursorX(this.adjustValue(direction,this._getCursorX())); |
| this._typamaticNode = nodePressed; |
| this._typamaticTimer = dojo.lang.setTimeout(this, "_arrowPressed", this._currentTimeout, this._eventCount, direction); |
| this._currentTimeout = Math.round(this._currentTimeout * this.timeoutChangeRate); |
| }, |
| |
| _downArrowPressed: function(/*Event*/ evt){ |
| return this._arrowPressed(evt,-1); |
| }, |
| |
| // IE sends these events when rapid clicking, mimic an extra single click |
| _downArrowDoubleClicked: function(/*Event*/ evt){ |
| var rc = this._downArrowPressed(evt); |
| dojo.lang.setTimeout(this, "_arrowReleased", 50, null); |
| return rc; |
| }, |
| |
| _upArrowPressed: function(/*Event*/ evt){ |
| return this._arrowPressed(evt,+1); |
| }, |
| |
| // IE sends these events when rapid clicking, mimic an extra single click |
| _upArrowDoubleClicked: function(/*Event*/ evt){ |
| var rc = this._upArrowPressed(evt); |
| dojo.lang.setTimeout(this, "_arrowReleased", 50, null); |
| return rc; |
| }, |
| |
| _arrowReleased: function(/*Event*/ evt){ |
| this.textbox.focus(); |
| if(evt != null && typeof evt == "object" && evt.keyCode && evt.keyCode != null){ |
| var keyCode = evt.keyCode; |
| var k = dojo.event.browser.keys; |
| |
| switch(keyCode){ |
| case k.KEY_DOWN_ARROW: |
| case k.KEY_UP_ARROW: |
| dojo.event.browser.stopEvent(evt); |
| break; |
| } |
| } |
| this._releaseButton(this.upArrowNode); |
| this._releaseButton(this.downArrowNode); |
| this._eventCount++; |
| if(this._typamaticTimer != null){ |
| dojo.lang.clearTimeout(this._typamaticTimer); |
| } |
| this._typamaticTimer = null; |
| this._currentTimeout = this.defaultTimeout; |
| }, |
| |
| _mouseWheeled: function(/*Event*/ evt){ |
| var scrollAmount = 0; |
| if(typeof evt.wheelDelta == 'number'){ // IE |
| scrollAmount = evt.wheelDelta; |
| }else if (typeof evt.detail == 'number'){ // Mozilla+Firefox |
| scrollAmount = -evt.detail; |
| } |
| if(scrollAmount > 0){ |
| this._upArrowPressed(evt); |
| this._arrowReleased(evt); |
| }else if (scrollAmount < 0){ |
| this._downArrowPressed(evt); |
| this._arrowReleased(evt); |
| } |
| }, |
| |
| _discardEvent: function(/*Event*/ evt){ |
| dojo.event.browser.stopEvent(evt); |
| }, |
| |
| _getCursorX: function(){ |
| var x = -1; |
| try{ |
| this.textbox.focus(); |
| if (typeof this.textbox.selectionEnd == "number"){ |
| x = this.textbox.selectionEnd; |
| }else if (document.selection && document.selection.createRange){ |
| var range = document.selection.createRange().duplicate(); |
| if(range.parentElement() == this.textbox){ |
| range.moveStart('textedit', -1); |
| x = range.text.length; |
| } |
| } |
| }catch(e){ /* squelch! */ } |
| return x; |
| }, |
| |
| _setCursorX: function(/*Number*/ x){ |
| try{ |
| this.textbox.focus(); |
| if(!x){ x = 0; } |
| if(typeof this.textbox.selectionEnd == "number"){ |
| this.textbox.selectionEnd = x; |
| }else if(this.textbox.createTextRange){ |
| var range = this.textbox.createTextRange(); |
| range.collapse(true); |
| range.moveEnd('character', x); |
| range.moveStart('character', x); |
| range.select(); |
| } |
| }catch(e){ /* squelch! */ } |
| }, |
| |
| _spinnerPostMixInProperties: function(/*Object*/ args, /*Object*/ frag){ |
| // summary: the widget's postMixInProperties() method should call this method |
| |
| // set image size before instantiating template; |
| // changing it aftwards doesn't work on FF |
| var inputNode = this.getFragNodeRef(frag); |
| var inputSize = dojo.html.getBorderBox(inputNode); |
| this.buttonSize = { width: inputSize.height / 2 - 1, height: inputSize.height / 2 - 1}; |
| }, |
| |
| _spinnerPostCreate: function(/*Object*/ args, /*Object*/ frag){ |
| // summary: the widget's postCreate() method should call this method |
| |
| // extra listeners |
| if(this.textbox.addEventListener){ |
| // dojo.event.connect() doesn't seem to work with DOMMouseScroll |
| this.textbox.addEventListener('DOMMouseScroll', dojo.lang.hitch(this, "_mouseWheeled"), false); // Mozilla + Firefox + Netscape |
| }else{ |
| dojo.event.connect(this.textbox, "onmousewheel", this, "_mouseWheeled"); // IE + Safari |
| } |
| //dojo.event.connect(window, "onchange", this, "_resize"); |
| } |
| } |
| ); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.IntegerSpinner", |
| [dojo.widget.IntegerTextbox, dojo.widget.Spinner], |
| { |
| // summary: an IntegerTextbox with +/- buttons |
| |
| // delta: Number |
| // increment amount |
| delta: "1", |
| |
| postMixInProperties: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.IntegerSpinner.superclass.postMixInProperties.apply(this, arguments); |
| this._spinnerPostMixInProperties(args, frag); |
| }, |
| |
| postCreate: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.IntegerSpinner.superclass.postCreate.apply(this, arguments); |
| this._spinnerPostCreate(args, frag); |
| }, |
| |
| adjustValue: function(/*Number*/ direction, /*Number*/ x){ |
| // sumary |
| // spin the input field |
| // direction < 0: spin down |
| // direction > 0: spin up |
| // direction = 0: revalidate existing value |
| |
| var val = this.getValue().replace(/[^\-+\d]/g, ""); |
| if(val.length == 0){ return; } |
| |
| var num = Math.min(Math.max((parseInt(val)+(parseInt(this.delta) * direction)), (this.flags.min?this.flags.min:-Infinity)), (this.flags.max?this.flags.max:+Infinity)); |
| val = num.toString(); |
| |
| if(num >= 0){ |
| val = ((this.flags.signed == true)?'+':' ')+val; // make sure first char is a nondigit |
| } |
| |
| if(this.flags.separator.length > 0){ |
| for (var i=val.length-3; i > 1; i-=3){ |
| val = val.substr(0,i)+this.flags.separator+val.substr(i); |
| } |
| } |
| |
| if(val.substr(0,1) == ' '){ val = val.substr(1); } // remove space |
| |
| this.setValue(val); |
| |
| return val.length; |
| } |
| }); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.RealNumberSpinner", |
| [dojo.widget.RealNumberTextbox, dojo.widget.Spinner], |
| function(){ dojo.experimental("dojo.widget.RealNumberSpinner"); }, |
| { |
| // summary |
| // A RealNumberTextbox with +/- buttons |
| |
| // delta: Number |
| // amount that pushing a button changes the value? |
| delta: "1e1", |
| |
| postMixInProperties: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.RealNumberSpinner.superclass.postMixInProperties.apply(this, arguments); |
| this._spinnerPostMixInProperties(args, frag); |
| }, |
| |
| postCreate: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.RealNumberSpinner.superclass.postCreate.apply(this, arguments); |
| this._spinnerPostCreate(args, frag); |
| }, |
| |
| adjustValue: function(/*Number*/ direction, /*Number*/ x){ |
| var val = this.getValue().replace(/[^\-+\.eE\d]/g, ""); |
| if(!val.length){ return; } |
| |
| var num = parseFloat(val); |
| if(isNaN(num)){ return; } |
| var delta = this.delta.split(/[eE]/); |
| if(!delta.length){ |
| delta = [1, 1]; |
| }else{ |
| delta[0] = parseFloat(delta[0].replace(/[^\-+\.\d]/g, "")); |
| if(isNaN(delta[0])){ delta[0] = 1; } |
| if(delta.length > 1){ |
| delta[1] = parseInt(delta[1]); |
| } |
| if(isNaN(delta[1])){ delta[1] = 1; } |
| } |
| val = this.getValue().split(/[eE]/); |
| if(!val.length){ return; } |
| var numBase = parseFloat(val[0].replace(/[^\-+\.\d]/g, "")); |
| if(val.length == 1){ |
| var numExp = 0; |
| }else{ |
| var numExp = parseInt(val[1].replace(/[^\-+\d]/g, "")); |
| } |
| if(x <= val[0].length){ |
| x = 0; |
| numBase += delta[0] * direction; |
| }else{ |
| x = Number.MAX_VALUE; |
| numExp += delta[1] * direction; |
| if(this.flags.eSigned == false && numExp < 0){ |
| numExp = 0; |
| } |
| } |
| num = Math.min(Math.max((numBase * Math.pow(10,numExp)), (this.flags.min?this.flags.min:-Infinity)), (this.flags.max?this.flags.max:+Infinity)); |
| if((this.flags.exponent == true || (this.flags.exponent != false && x != 0)) && num.toExponential){ |
| if (isNaN(this.flags.places) || this.flags.places == Infinity){ |
| val = num.toExponential(); |
| }else{ |
| val = num.toExponential(this.flags.places); |
| } |
| }else if(num.toFixed && num.toPrecision){ |
| if(isNaN(this.flags.places) || this.flags.places == Infinity){ |
| val = num.toPrecision((1/3).toString().length-1); |
| }else{ |
| val = num.toFixed(this.flags.places); |
| } |
| }else{ |
| val = num.toString(); |
| } |
| |
| if(num >= 0){ |
| if(this.flags.signed == true){ |
| val = '+' + val; |
| } |
| } |
| val = val.split(/[eE]/); |
| if(this.flags.separator.length > 0){ |
| if(num >= 0 && val[0].substr(0,1) != '+'){ |
| val[0] = ' ' + val[0]; // make sure first char is nondigit for easy algorithm |
| } |
| var i = val[0].lastIndexOf('.'); |
| if(i >= 0){ |
| i -= 3; |
| }else{ |
| i = val[0].length-3; |
| } |
| for (; i > 1; i-=3){ |
| val[0] = val[0].substr(0,i)+this.flags.separator+val[0].substr(i); |
| } |
| if(val[0].substr(0,1) == ' '){ val[0] = val[0].substr(1); } // remove space |
| } |
| if(val.length > 1){ |
| if((this.flags.eSigned == true)&&(val[1].substr(0,1) != '+')){ |
| val[1] = '+' + val[1]; |
| }else if((!this.flags.eSigned)&&(val[1].substr(0,1) == '+')){ |
| val[1] = val[1].substr(1); |
| }else if((!this.flags.eSigned)&&(val[1].substr(0,1) == '-')&&(num.toFixed && num.toPrecision)){ |
| if(isNaN(this.flags.places)){ |
| val[0] = num.toPrecision((1/3).toString().length-1); |
| }else{ |
| val[0] = num.toFixed(this.flags.places).toString(); |
| } |
| val[1] = "0"; |
| } |
| val[0] += 'e' + val[1]; |
| } |
| this.setValue(val[0]); |
| if(x > val[0].length){ x = val[0].length; } |
| return x; |
| } |
| }); |
| |
| dojo.widget.defineWidget( |
| "dojo.widget.TimeSpinner", |
| [dojo.widget.TimeTextbox, dojo.widget.Spinner], |
| function(){ dojo.experimental("dojo.widget.TimeSpinner"); }, |
| { |
| postMixInProperties: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.TimeSpinner.superclass.postMixInProperties.apply(this, arguments); |
| this._spinnerPostMixInProperties(args, frag); |
| }, |
| |
| postCreate: function(/*Object*/ args, /*Object*/ frag){ |
| dojo.widget.TimeSpinner.superclass.postCreate.apply(this, arguments); |
| this._spinnerPostCreate(args, frag); |
| }, |
| |
| adjustValue: function(/*Number*/ direction, /*Number*/ x){ |
| //FIXME: formatting should make use of dojo.date.format? |
| var val = this.getValue(); |
| var format = (this.flags.format && this.flags.format.search(/[Hhmst]/) >= 0) ? this.flags.format : "hh:mm:ss t"; |
| if(direction == 0 || !val.length || !this.isValid()){ return; } |
| if (!this.flags.amSymbol){ |
| this.flags.amSymbol = "AM"; |
| } |
| if (!this.flags.pmSymbol){ |
| this.flags.pmSymbol = "PM"; |
| } |
| var re = dojo.regexp.time(this.flags); |
| var qualifiers = format.replace(/H/g,"h").replace(/[^hmst]/g,"").replace(/([hmst])\1/g,"$1"); |
| var hourPos = qualifiers.indexOf('h') + 1; |
| var minPos = qualifiers.indexOf('m') + 1; |
| var secPos = qualifiers.indexOf('s') + 1; |
| var ampmPos = qualifiers.indexOf('t') + 1; |
| // tweak format to match the incoming data exactly to help find where the cursor is |
| var cursorFormat = format; |
| var ampm = ""; |
| if (ampmPos > 0){ |
| ampm = val.replace(new RegExp(re),"$"+ampmPos); |
| cursorFormat = cursorFormat.replace(/t+/, ampm.replace(/./g,"t")); |
| } |
| var hour = 0; |
| var deltaHour = 1; |
| if (hourPos > 0){ |
| hour = val.replace(new RegExp(re),"$"+hourPos); |
| if (dojo.lang.isString(this.delta)){ |
| deltaHour = this.delta.replace(new RegExp(re),"$"+hourPos); |
| } |
| if (isNaN(deltaHour)){ |
| deltaHour = 1; |
| } else { |
| deltaHour = parseInt(deltaHour); |
| } |
| if (hour.length == 2){ |
| cursorFormat = cursorFormat.replace(/([Hh])+/, "$1$1"); |
| } else { |
| cursorFormat = cursorFormat.replace(/([Hh])+/, "$1"); |
| } |
| if (isNaN(hour)){ |
| hour = 0; |
| } else { |
| hour = parseInt(hour.replace(/^0(\d)/,"$1")); |
| } |
| } |
| var min = 0; |
| var deltaMin = 1; |
| if (minPos > 0){ |
| min = val.replace(new RegExp(re),"$"+minPos); |
| if (dojo.lang.isString(this.delta)){ |
| deltaMin = this.delta.replace(new RegExp(re),"$"+minPos); |
| } |
| if (isNaN(deltaMin)){ |
| deltaMin = 1; |
| } else { |
| deltaMin = parseInt(deltaMin); |
| } |
| cursorFormat = cursorFormat.replace(/m+/, min.replace(/./g,"m")); |
| if (isNaN(min)){ |
| min = 0; |
| } else { |
| min = parseInt(min.replace(/^0(\d)/,"$1")); |
| } |
| } |
| var sec = 0; |
| var deltaSec = 1; |
| if (secPos > 0){ |
| sec = val.replace(new RegExp(re),"$"+secPos); |
| if (dojo.lang.isString(this.delta)){ |
| deltaSec = this.delta.replace(new RegExp(re),"$"+secPos); |
| } |
| if (isNaN(deltaSec)){ |
| deltaSec = 1; |
| } else { |
| deltaSec = parseInt(deltaSec); |
| } |
| cursorFormat = cursorFormat.replace(/s+/, sec.replace(/./g,"s")); |
| if (isNaN(sec)){ |
| sec = 0; |
| } else { |
| sec = parseInt(sec.replace(/^0(\d)/,"$1")); |
| } |
| } |
| if (isNaN(x) || x >= cursorFormat.length){ |
| x = cursorFormat.length-1; |
| } |
| var cursorToken = cursorFormat.charAt(x); |
| |
| switch(cursorToken){ |
| case 't': |
| if (ampm == this.flags.amSymbol){ |
| ampm = this.flags.pmSymbol; |
| } |
| else if (ampm == this.flags.pmSymbol){ |
| ampm = this.flags.amSymbol; |
| } |
| break; |
| default: |
| if (hour >= 1 && hour < 12 && ampm == this.flags.pmSymbol){ |
| hour += 12; |
| } |
| if (hour == 12 && ampm == this.flags.amSymbol){ |
| hour = 0; |
| } |
| switch(cursorToken){ |
| case 's': |
| sec += deltaSec * direction; |
| while (sec < 0){ |
| min--; |
| sec += 60; |
| } |
| while (sec >= 60){ |
| min++; |
| sec -= 60; |
| } |
| case 'm': |
| if (cursorToken == 'm'){ |
| min += deltaMin * direction; |
| } |
| while (min < 0){ |
| hour--; |
| min += 60; |
| } |
| while (min >= 60){ |
| hour++; |
| min -= 60; |
| } |
| case 'h': |
| case 'H': |
| if (cursorToken == 'h' || cursorToken == 'H'){ |
| hour += deltaHour * direction; |
| } |
| while (hour < 0){ |
| hour += 24; |
| } |
| while (hour >= 24){ |
| hour -= 24; |
| } |
| break; |
| default: // should never get here |
| return; |
| } |
| if (hour >= 12){ |
| ampm = this.flags.pmSymbol; |
| if (format.indexOf('h') >= 0 && hour >= 13){ |
| hour -= 12; |
| } |
| } else { |
| ampm = this.flags.amSymbol; |
| if (format.indexOf('h') >= 0 && hour == 0){ |
| hour = 12; |
| } |
| } |
| } |
| |
| cursorFormat = format; |
| if (hour >= 0 && hour < 10 && format.search(/[hH]{2}/) >= 0){ |
| hour = "0" + hour.toString(); |
| } |
| if (hour >= 10 && cursorFormat.search(/[hH]{2}/) < 0 ){ |
| cursorFormat = cursorFormat.replace(/(h|H)/, "$1$1"); |
| } |
| if (min >= 0 && min < 10 && cursorFormat.search(/mm/) >= 0){ |
| min = "0" + min.toString(); |
| } |
| if (min >= 10 && cursorFormat.search(/mm/) < 0 ){ |
| cursorFormat = cursorFormat.replace(/m/, "$1$1"); |
| } |
| if (sec >= 0 && sec < 10 && cursorFormat.search(/ss/) >= 0){ |
| sec = "0" + sec.toString(); |
| } |
| if (sec >= 10 && cursorFormat.search(/ss/) < 0 ){ |
| cursorFormat = cursorFormat.replace(/s/, "$1$1"); |
| } |
| x = cursorFormat.indexOf(cursorToken); |
| if (x == -1){ |
| x = format.length; |
| } |
| format = format.replace(/[hH]+/, hour); |
| format = format.replace(/m+/, min); |
| format = format.replace(/s+/, sec); |
| format = format.replace(/t/, ampm); |
| this.setValue(format); |
| if(x > format.length){ x = format.length; } |
| return x; |
| } |
| }); |