/*
|
* Fuel UX Spinbox
|
* https://github.com/ExactTarget/fuelux
|
*
|
* Copyright (c) 2014 ExactTarget
|
* Licensed under the BSD New license.
|
*/
|
|
// -- BEGIN UMD WRAPPER PREFACE --
|
|
// For more information on UMD visit:
|
// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
|
|
(function (factory) {
|
if (typeof define === 'function' && define.amd) {
|
// if AMD loader is available, register as an anonymous module.
|
define(['jquery'], factory);
|
} else if (typeof exports === 'object') {
|
// Node/CommonJS
|
module.exports = factory(require('jquery'));
|
} else {
|
// OR use browser globals if AMD is not present
|
factory(jQuery);
|
}
|
}(function ($) {
|
// -- END UMD WRAPPER PREFACE --
|
|
// -- BEGIN MODULE CODE HERE --
|
|
var old = $.fn.spinbox;
|
|
// SPINBOX CONSTRUCTOR AND PROTOTYPE
|
|
var Spinbox = function Spinbox(element, options) {
|
this.$element = $(element);
|
this.$element.find('.btn').on('click', function (e) {
|
//keep spinbox from submitting if they forgot to say type="button" on their spinner buttons
|
e.preventDefault();
|
});
|
this.options = $.extend({}, $.fn.spinbox.defaults, options);
|
this.options.step = this.$element.data('step') || this.options.step;
|
|
if (this.options.value < this.options.min) {
|
this.options.value = this.options.min;
|
} else if (this.options.max < this.options.value) {
|
this.options.value = this.options.max;
|
}
|
|
this.$input = this.$element.find('.spinbox-input');
|
this.$input.on('focusout.fu.spinbox', this.$input, $.proxy(this.change, this));
|
this.$element.on('keydown.fu.spinbox', this.$input, $.proxy(this.keydown, this));
|
this.$element.on('keyup.fu.spinbox', this.$input, $.proxy(this.keyup, this));
|
|
this.bindMousewheelListeners();
|
this.mousewheelTimeout = {};
|
|
if (this.options.hold) {
|
this.$element.on('mousedown.fu.spinbox', '.spinbox-up', $.proxy(function () {
|
this.startSpin(true);
|
}, this));
|
this.$element.on('mouseup.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
|
this.$element.on('mouseout.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
|
this.$element.on('mousedown.fu.spinbox', '.spinbox-down', $.proxy(function () {
|
this.startSpin(false);
|
}, this));
|
} else {
|
this.$element.on('click.fu.spinbox', '.spinbox-up', $.proxy(function () {
|
this.step(true);
|
}, this));
|
this.$element.on('click.fu.spinbox', '.spinbox-down', $.proxy(function () {
|
this.step(false);
|
}, this));
|
}
|
|
this.switches = {
|
count: 1,
|
enabled: true
|
};
|
|
if (this.options.speed === 'medium') {
|
this.switches.speed = 300;
|
} else if (this.options.speed === 'fast') {
|
this.switches.speed = 100;
|
} else {
|
this.switches.speed = 500;
|
}
|
|
this.options.defaultUnit = _isUnitLegal(this.options.defaultUnit, this.options.units) ? this.options.defaultUnit : '';
|
this.unit = this.options.defaultUnit;
|
|
this.lastValue = this.options.value;
|
|
this.render();
|
|
if (this.options.disabled) {
|
this.disable();
|
}
|
};
|
|
// Truly private methods
|
var _limitToStep = function _limitToStep(number, step) {
|
return Math.round(number / step) * step;
|
};
|
|
var _isUnitLegal = function _isUnitLegal(unit, validUnits) {
|
var legalUnit = false;
|
var suspectUnit = unit.toLowerCase();
|
|
$.each(validUnits, function (i, validUnit) {
|
validUnit = validUnit.toLowerCase();
|
if (suspectUnit === validUnit) {
|
legalUnit = true;
|
return false;//break out of the loop
|
}
|
});
|
|
return legalUnit;
|
};
|
|
var _applyLimits = function _applyLimits(value) {
|
// if unreadable
|
if (isNaN(parseFloat(value))) {
|
return value;
|
}
|
|
// if not within range return the limit
|
if (value > this.options.max) {
|
if (this.options.cycle) {
|
value = this.options.min;
|
} else {
|
value = this.options.max;
|
}
|
} else if (value < this.options.min) {
|
if (this.options.cycle) {
|
value = this.options.max;
|
} else {
|
value = this.options.min;
|
}
|
}
|
|
if (this.options.limitToStep && this.options.step) {
|
value = _limitToStep(value, this.options.step);
|
|
//force round direction so that it stays within bounds
|
if(value > this.options.max){
|
value = value - this.options.step;
|
} else if(value < this.options.min) {
|
value = value + this.options.step;
|
}
|
}
|
|
return value;
|
};
|
|
Spinbox.prototype = {
|
constructor: Spinbox,
|
|
destroy: function destroy() {
|
this.$element.remove();
|
// any external bindings
|
// [none]
|
// set input value attrbute
|
this.$element.find('input').each(function () {
|
$(this).attr('value', $(this).val());
|
});
|
// empty elements to return to original markup
|
// [none]
|
// returns string of markup
|
return this.$element[0].outerHTML;
|
},
|
|
render: function render() {
|
this.setValue(this.getDisplayValue());
|
},
|
|
change: function change() {
|
this.setValue(this.getDisplayValue());
|
|
this.triggerChangedEvent();
|
},
|
|
stopSpin: function stopSpin() {
|
if (this.switches.timeout !== undefined) {
|
clearTimeout(this.switches.timeout);
|
this.switches.count = 1;
|
this.triggerChangedEvent();
|
}
|
},
|
|
triggerChangedEvent: function triggerChangedEvent() {
|
var currentValue = this.getValue();
|
if (currentValue === this.lastValue) return;
|
this.lastValue = currentValue;
|
|
// Primary changed event
|
this.$element.trigger('changed.fu.spinbox', currentValue);
|
},
|
|
startSpin: function startSpin(type) {
|
if (!this.options.disabled) {
|
var divisor = this.switches.count;
|
|
if (divisor === 1) {
|
this.step(type);
|
divisor = 1;
|
} else if (divisor < 3) {
|
divisor = 1.5;
|
} else if (divisor < 8) {
|
divisor = 2.5;
|
} else {
|
divisor = 4;
|
}
|
|
this.switches.timeout = setTimeout($.proxy(function () {
|
this.iterate(type);
|
}, this), this.switches.speed / divisor);
|
this.switches.count++;
|
}
|
},
|
|
iterate: function iterate(type) {
|
this.step(type);
|
this.startSpin(type);
|
},
|
|
step: function step(isIncrease) {
|
//refresh value from display before trying to increment in case they have just been typing before clicking the nubbins
|
this.setValue(this.getDisplayValue());
|
var newVal;
|
|
if (isIncrease) {
|
newVal = this.options.value + this.options.step;
|
} else {
|
newVal = this.options.value - this.options.step;
|
}
|
|
newVal = newVal.toFixed(5);
|
|
this.setValue(newVal + this.unit);
|
},
|
|
getDisplayValue: function getDisplayValue() {
|
var inputValue = this.parseInput(this.$input.val());
|
var value = (!!inputValue) ? inputValue : this.options.value;
|
return value;
|
},
|
|
setDisplayValue: function setDisplayValue(value) {
|
this.$input.val(value);
|
},
|
|
getValue: function getValue() {
|
var val = this.options.value;
|
if (this.options.decimalMark !== '.'){
|
val = (val + '').split('.').join(this.options.decimalMark);
|
}
|
return val + this.unit;
|
},
|
|
setValue: function setValue(val) {
|
//remove any i18n on the number
|
if (this.options.decimalMark !== '.') {
|
val = this.parseInput(val);
|
}
|
|
//are we dealing with united numbers?
|
if(typeof val !== "number"){
|
var potentialUnit = val.replace(/[0-9.-]/g, '');
|
//make sure unit is valid, or else drop it in favor of current unit, or default unit (potentially nothing)
|
this.unit = _isUnitLegal(potentialUnit, this.options.units) ? potentialUnit : this.options.defaultUnit;
|
}
|
|
var intVal = this.getIntValue(val);
|
|
//make sure we are dealing with a number
|
if (isNaN(intVal) && !isFinite(intVal)) {
|
return this.setValue(this.options.value);
|
}
|
|
//conform
|
intVal = _applyLimits.call(this, intVal);
|
|
//cache the pure int value
|
this.options.value = intVal;
|
|
//prepare number for display
|
val = intVal + this.unit;
|
|
if (this.options.decimalMark !== '.'){
|
val = (val + '').split('.').join(this.options.decimalMark);
|
}
|
|
//display number
|
this.setDisplayValue(val);
|
|
return this;
|
},
|
|
value: function value(val) {
|
if (val || val === 0) {
|
return this.setValue(val);
|
} else {
|
return this.getValue();
|
}
|
},
|
|
parseInput: function parseInput(value) {
|
value = (value + '').split(this.options.decimalMark).join('.');
|
|
return value;
|
},
|
|
getIntValue: function getIntValue(value) {
|
//if they didn't pass in a number, try and get the number
|
value = (typeof value === "undefined") ? this.getValue() : value;
|
// if there still isn't a number, abort
|
if(typeof value === "undefined"){return;}
|
|
if (typeof value === 'string'){
|
value = this.parseInput(value);
|
}
|
|
value = parseFloat(value, 10);
|
|
return value;
|
},
|
|
disable: function disable() {
|
this.options.disabled = true;
|
this.$element.addClass('disabled');
|
this.$input.attr('disabled', '');
|
this.$element.find('button').addClass('disabled');
|
},
|
|
enable: function enable() {
|
this.options.disabled = false;
|
this.$element.removeClass('disabled');
|
this.$input.removeAttr('disabled');
|
this.$element.find('button').removeClass('disabled');
|
},
|
|
keydown: function keydown(event) {
|
var keyCode = event.keyCode;
|
if (keyCode === 38) {
|
this.step(true);
|
} else if (keyCode === 40) {
|
this.step(false);
|
} else if (keyCode === 13) {
|
this.change();
|
}
|
},
|
|
keyup: function keyup(event) {
|
var keyCode = event.keyCode;
|
|
if (keyCode === 38 || keyCode === 40) {
|
this.triggerChangedEvent();
|
}
|
},
|
|
bindMousewheelListeners: function bindMousewheelListeners() {
|
var inputEl = this.$input.get(0);
|
if (inputEl.addEventListener) {
|
//IE 9, Chrome, Safari, Opera
|
inputEl.addEventListener('mousewheel', $.proxy(this.mousewheelHandler, this), false);
|
// Firefox
|
inputEl.addEventListener('DOMMouseScroll', $.proxy(this.mousewheelHandler, this), false);
|
} else {
|
// IE <9
|
inputEl.attachEvent('onmousewheel', $.proxy(this.mousewheelHandler, this));
|
}
|
},
|
|
mousewheelHandler: function mousewheelHandler(event) {
|
if (!this.options.disabled) {
|
var e = window.event || event;// old IE support
|
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
|
var self = this;
|
|
clearTimeout(this.mousewheelTimeout);
|
this.mousewheelTimeout = setTimeout(function () {
|
self.triggerChangedEvent();
|
}, 300);
|
|
if (delta > 0) {//ACE
|
this.step(true);
|
} else {
|
this.step(false);
|
}
|
|
if (e.preventDefault) {
|
e.preventDefault();
|
} else {
|
e.returnValue = false;
|
}
|
|
return false;
|
}
|
}
|
};
|
|
|
// SPINBOX PLUGIN DEFINITION
|
|
$.fn.spinbox = function spinbox(option) {
|
var args = Array.prototype.slice.call(arguments, 1);
|
var methodReturn;
|
|
var $set = this.each(function () {
|
var $this = $(this);
|
var data = $this.data('fu.spinbox');
|
var options = typeof option === 'object' && option;
|
|
if (!data) {
|
$this.data('fu.spinbox', (data = new Spinbox(this, options)));
|
}
|
|
if (typeof option === 'string') {
|
methodReturn = data[option].apply(data, args);
|
}
|
});
|
|
return (methodReturn === undefined) ? $set : methodReturn;
|
};
|
|
// value needs to be 0 for this.render();
|
$.fn.spinbox.defaults = {
|
value: 0,
|
min: 0,
|
max: 999,
|
step: 1,
|
hold: true,
|
speed: 'medium',
|
disabled: false,
|
cycle: false,
|
units: [],
|
decimalMark: '.',
|
defaultUnit: '',
|
limitToStep: false
|
};
|
|
$.fn.spinbox.Constructor = Spinbox;
|
|
$.fn.spinbox.noConflict = function noConflict() {
|
$.fn.spinbox = old;
|
return this;
|
};
|
|
|
// DATA-API
|
|
$(document).on('mousedown.fu.spinbox.data-api', '[data-initialize=spinbox]', function (e) {
|
var $control = $(e.target).closest('.spinbox');
|
if (!$control.data('fu.spinbox')) {
|
$control.spinbox($control.data());
|
}
|
});
|
|
// Must be domReady for AMD compatibility
|
$(function () {
|
$('[data-initialize=spinbox]').each(function () {
|
var $this = $(this);
|
if (!$this.data('fu.spinbox')) {
|
$this.spinbox($this.data());
|
}
|
});
|
});
|
|
// -- BEGIN UMD WRAPPER AFTERWORD --
|
}));
|
// -- END UMD WRAPPER AFTERWORD --
|