/*!
* Copyright (c) 2009 Francesco Mele jsbeans@francescomele.com
*
* This Software is licenced under the LGPL Licence (GNU Lesser General
* Public License).
* In addition to the LGPL Licence the Software is subject to the
* following conditions:
*
* i every modification must be public and comunicated to the Author
* ii every "jsbean" added to this library must be self consistent
* except for the dependence from jsbeans-x.x.x.js
* iii copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* IntSpinner is an UI (User Interface) element representing an input that
* allows only Integers and let the user go up and down by a defined step.
* @namespace jsbeans
* @class IntSpinner
* @constructor
* @param object {DOM | String}
* @param [options] {JSON}
* <pre>
* arrowColor: <String>, // the color of arrows
* min: <Integer>, // the minimum value allowed, default jsbeans.IntSpinner.MIN_VALUE
* max: <Integer>, // the maximum value allowed, default jsbeans.IntSpinner.MAX_VALUE
* step: <Integer>, // amount of increment/decrement, default 1
* roll: <boolean>, // if one of bound is reached, it starts again, default true
* inputClassName: <String>, // the input style class
* arrowUpClassName: <String>, // the up arrow style class
* arrowDownClassName: <String>, // the down arrow style class
* readonly: <boolean>, // if the input can be edit by user, default true (not editable)
* label: <String>, // a string inside a label tag. Optional
* startsWith: <Integer>, // the starting number, default 1
* onBeforeClick: <Function>, // function fired on both arrows just before value's update, it may be used to store previous value. It takes the input and the event fired as arguments.
* onClick: <Function>, // function fired onclick on both arrows. It takes the input and the event fired as arguments.
* onMouseOver: <Function>, // function fired on mouseover the input, may be used to store previous value. It takes the input and the event fired as arguments.
* onMouseOut: <Function> // function fired onmouseout the input, may be used for AJAX calls or for validation. It takes the input and the event fired as arguments.
* </pre>
* @author Francesco Mele
* @credits Andrea Previati for 'onClick', 'onMouseOver' and 'onMouseOut' suggestion
* */
jsbeans.IntSpinner = function(o) {
this._obj = null;
if (typeof o === "string") {
this._obj = document.getElementById(o);
}
if (typeof this._obj == "undefined" || this._obj == null) {
return;
}
this.options = arguments[1] || {};
this.options.arrowColor = this.options.arrowColor || null;
this.options.min = this.options.min || jsbeans.IntSpinner.MIN_VALUE;
this.options.max = this.options.max || jsbeans.IntSpinner.MAX_VALUE;
this.options.step = this.options.step || 1;
this.options.roll = (typeof(this.options.roll) == "undefined") ? true : this.options.roll;
this.options.inputClassName = this.options.inputClassName || null;
this.options.arrowUpClassName = this.options.arrowUpClassName || null;
this.options.arrowDownClassName = this.options.arrowDownClassName || null;
this.options.readonly = (typeof(this.options.readonly) == "undefined") ? true : this.options.readonly;
this.options.label = this.options.label || null;
this.options.startsWith = this.options.startsWith || 1;
this.options.onBeforeClick = this.options.onBeforeClick || null;
this.options.onClick = this.options.onClick || null;
this.options.onMouseOver = this.options.onMouseOver || null;
this.options.onMouseOut = this.options.onMouseOut || null;
};
/**
* Renders the UI element.
* @method dispose
* */
jsbeans.IntSpinner.prototype.dispose = function() {
if (this._obj == null) {
return;
}
var table = document.createElement("table");
var tr = document.createElement("tr");
var td = document.createElement("td");
var input = document.createElement("input");
input.setAttribute("type", "text");
var inputId = this._obj.getAttribute("id") + "_input";
input.setAttribute("id", inputId);
input.setAttribute("name", inputId);
input.value = this.options.startsWith;
if (this.options.readonly === true) {
input.setAttribute("readonly", "readonly");
}
if (this.options.inputClassName != null) {
input.className = this.options.inputClassName;
}
if (this.options.label != null) {
var label = document.createElement("label");
label.setAttribute("for", inputId);
label.innerHTML = this.options.label;
td.appendChild(label);
}
td.appendChild(input);
var td2 = document.createElement("td");
tr.appendChild(td);
tr.appendChild(td2);
table.appendChild(tr);
var table2 = document.createElement("table");
var tr21 = document.createElement("tr");
var tdUp = document.createElement("td");
var divUp = document.createElement("div");
if (this.options.arrowUpClassName != null) {
tdUp.className = this.options.arrowUpClassName;
}
tdUp.appendChild(divUp);
var styleUp = "border-color: transparent transparent " + ((this.options.arrowColor != null) ? this.options.arrowColor : "#333") + ";"
+ "border-style: solid;"
+ "border-width: 0 6px 6px;"
+ "height: 0;"
+ "margin: 0 3px;"
+ "width: 0;"
+ "cursor:hand;cursor:pointer";
divUp.setAttribute("style", styleUp);
var ops = this.options;
tdUp.onclick = function(e) {
if (ops.onBeforeClick != null) {
try {
e = e || event;
ops.onBeforeClick(input, e);
}
catch(eup) {}
}
jsbeans.IntSpinner._roll(input, ops.step, ops);
if (ops.onClick != null) {
try {
e = e || event;
ops.onClick(input, e);
}
catch(eup) {}
}
};
tr21.appendChild(tdUp);
table2.appendChild(tr21);
var tr22 = document.createElement("tr");
var tdDown = document.createElement("td");
var divDown = document.createElement("div");
if (this.options.arrowDownClassName != null) {
tdDown.className = this.options.arrowDownClassName;
}
tdDown.appendChild(divDown);
var styleDown = "border-color: " + ((this.options.arrowColor != null) ? this.options.arrowColor : "#333") + " transparent transparent;"
+ "border-style: solid;"
+ "border-width: 6px 6px 0;"
+ "height: 0;"
+ "margin: 0 3px;"
+ "width: 0;"
+ "cursor:hand;cursor:pointer";
divDown.setAttribute("style", styleDown);
tdDown.onclick = function(e) {
if (ops.onBeforeClick != null) {
try {
e = e || event;
ops.onBeforeClick(input, e);
}
catch(edown) {}
}
jsbeans.IntSpinner._roll(input, -ops.step, ops);
if (ops.onClick != null) {
try {
e = e || event;
ops.onClick(input, e);
}
catch(edown) {}
}
};
tr22.appendChild(tdDown);
table2.appendChild(tr22);
td2.appendChild(table2);
input.onmouseover = function(e) {
jsbeans.IntSpinner._addHandler(document, 'mousewheel', jsbeans.IntSpinner._wheel);
jsbeans.IntSpinner._addHandler(document, 'DOMMouseScroll', jsbeans.IntSpinner._wheel);
if (ops.onMouseOver != null) {
try {
e = e || event;
ops.onMouseOver(input, e);
}
catch(emover) {}
}
};
input.onmouseout = function(e) {
jsbeans.IntSpinner._removeHandler(document, 'mousewheel', jsbeans.IntSpinner._wheel);
jsbeans.IntSpinner._removeHandler(document, 'DOMMouseScroll', jsbeans.IntSpinner._wheel);
if (ops.onMouseOut != null) {
try {
e = e || event;
ops.onMouseOut(input, e);
}
catch(emover) {}
}
};
input.options = ops;
this._input = input;
this._obj.appendChild(table);
};
/**
* @config jsbeans.IntSpinner.MIN_VALUE
* @type Integer
* @static
* @final
* @default -999999999999999
* */
jsbeans.IntSpinner.MIN_VALUE = -999999999999999;
/**
* @config jsbeans.IntSpinner.MAX_VALUE
* @type Integer
* @static
* @final
* @default 999999999999999
* */
jsbeans.IntSpinner.MAX_VALUE = 999999999999999;
/**
* @method _roll
* @param object {DOM}
* @param step {Integer}
* @param options {JSON}
* @private
* @static
* */
jsbeans.IntSpinner._roll = function(o, step, options) {
var ops = options || {};
var v = parseInt(o.value);
if (isNaN(v)) {
return;
}
var min = ops.min || jsbeans.IntSpinner.MIN_VALUE;
var max = ops.max || jsbeans.IntSpinner.MAX_VALUE;
var roll = (typeof(ops.roll) == "undefined") ? true : ops.roll;
var nv;
if (step > 0) {
if (v >=max) {
if (roll) {
nv = min;
}
else {
nv = max;
}
}
else if ((v + step) > max) {
nv = max;
}
else if (v < min) {
nv = step;
}
else {
nv = v + step;
}
}
else {
if (v <= min) {
if (roll) {
nv = max;
}
else {
nv = min;
}
}
else if ((v + step) < min) {
nv = min;
}
else if (v > max) {
nv = min;
}
else {
nv = v + step;
}
}
o.value = nv;
};
/**
* @method _wheel
* @param event {event}
* @private
* @static
* */
jsbeans.IntSpinner._wheel = function(event) {
event = event ? event : window.event;
var delta = null;
if (event.wheelDelta){
delta = event.wheelDelta;
} else {
delta = -event.detail * 40;
}
var target = event.target || event.srcElement;
if (delta > 0) {
jsbeans.IntSpinner._roll(target, target.options.step, target.options);
}
else {
jsbeans.IntSpinner._roll(target, -target.options.step, target.options);
}
if (event.preventDefault){
event.preventDefault();
}
else {
event.returnValue = false;
}
};
/**
* @method _addHandler
* @param element {DOM}
* @param type {String}
* @param handler {Function}
* @private
* @static
* */
jsbeans.IntSpinner._addHandler = function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
}
else if (element.attachEvent){
element.attachEvent("on" + type, handler);
}
else {
element["on" + type] = handler;
}
};
/**
* @method _removeHandler
* @param element {DOM}
* @param type {String}
* @param handler {Function}
* @private
* @static
* */
jsbeans.IntSpinner._removeHandler = function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
}
else if (element.detachEvent){
element.detachEvent("on" + type, handler);
}
else {
element["on" + type] = null;
}
};