/**
* Initialization is trying to parse a number from input value.
* If there is no any sign of number, value 1 will be set by default.
*
* @param {!jQuery} root Html input[type=text] element.
* @param {number=} opt_max Defaults to Number.POSITIVE_INFINITY (unlimited).
* @param {number=} opt_min Defaults to 1.
* @param {boolean=} opt_processKeyboardInputWithDelay Defaults to false.
* @param {boolean=} opt_controls Defaults to false.
* @constructor
*/
jt.block.IntegerInput = function(
root, opt_max, opt_min, opt_processKeyboardInputWithDelay, opt_controls) {
/**
* @type {!jQuery}
* @private
*/
this.root_ = root;
/**
* @type {number}
* @private
*/
this.min_ = typeof opt_min === 'number' ? opt_min : 1;
/**
* @type {number}
* @private
*/
this.max_ = typeof opt_max === 'number' ? opt_max : Number.POSITIVE_INFINITY;
/**
* @type {number}
* @private
*/
this.value_;
/**
* @type {!jQuery}
* @private
*/
this.eventsDispatcher_ = jQuery({});
/**
* @type {number}
* @private
*/
this.step_ = root.data('step') || 1;
/**
* @type {boolean}
* @private
*/
this.keyboardInputWithDelay_ = opt_processKeyboardInputWithDelay || false;
/**
* @type {number}
* @private
*/
this.keyboardInputTimeout_ = 0;
this.attachEvents_();
if (opt_controls) {
this.attachControls_();
}
this.initInputMaxLength_();
this.initValue_();
};
/**
* Milliseconds to wait after keyup event and then process user input
* @const
* @type {number}
*/
jt.block.IntegerInput.KEYBOARD_INPUT_DELAY = 900;
/**
* @const
* @type {string}
*/
jt.block.IntegerInput.DECREMENTER_TEMPLATE =
'';
/**
* @const
* @type {string}
*/
jt.block.IntegerInput.INCREMENTER_TEMPLATE =
'';
/**
* @enum {string}
*/
jt.block.IntegerInput.EventType = {
CHANGE: 'change'
};
/**
* @return {!jQuery}
*/
jt.block.IntegerInput.prototype.getRoot = function() {
return this.root_;
};
/**
* @return {number}
*/
jt.block.IntegerInput.prototype.getValue = function() {
return this.value_;
};
/**
* @param {number} value
*/
jt.block.IntegerInput.prototype.setValue = function(value) {
this.setValue_(value, false);
};
/**
* @param {jt.block.IntegerInput.EventType} eventType
* @param {function(!jQuery.Event)} callback
*/
jt.block.IntegerInput.prototype.addEventListener = function(eventType, callback) {
this.eventsDispatcher_.bind(eventType, callback);
};
/**
* @param {jt.block.IntegerInput.EventType} eventType
* @param {function(!jQuery.Event)} callback
*/
jt.block.IntegerInput.prototype.removeEventListener = function(eventType, callback) {
this.eventsDispatcher_.unbind(eventType, callback);
};
/**
* @private
*/
jt.block.IntegerInput.prototype.initInputMaxLength_ = function() {
if (this.max_ !== Number.POSITIVE_INFINITY) {
this.root_.attr('maxlength', this.max_.toString().length);
}
};
/**
* @private
*/
jt.block.IntegerInput.prototype.initValue_ = function() {
/** @type {number} */
var value = this.getInputValueNumber_();
this.setValue_(isNaN(value) ? 1 : value, true);
};
/**
* @param {number} value
* @param {boolean} initial
* @private
*/
jt.block.IntegerInput.prototype.setValue_ = function(value, initial) {
if (!isFinite(value)) {
return;
}
/** @type {number} */
var newValue = this.normalizeValue_(value);
/** disable unavailable controls */
if (typeof(this.decrementer) !== 'undefined') {
this.decrementer.toggleClass('input__decrementer_disabled', (newValue <= this.min_));
}
if (typeof(this.incrementer) !== 'undefined') {
this.incrementer.toggleClass('input__incrementer_disabled', (newValue >= this.max_));
}
/*
We change input value unconditionally, because it's possible that
input currently have invalid string value,
but 'newValue' could be equal 'this.value_'.
If we put this line under condition,
nothing will happen in described situation.
*/
this.root_.val(newValue.toString());
if (newValue !== this.value_) {
this.value_ = newValue;
if (!initial) {
this.eventsDispatcher_.trigger(jt.block.IntegerInput.EventType.CHANGE);
}
}
};
/**
* @private
*/
jt.block.IntegerInput.prototype.attachEvents_ = function() {
var that = this;
this.root_
.keyup(function(event) {
//return;
/** @type {number} */
var delay = that.keyboardInputWithDelay_ ?
jt.block.IntegerInput.KEYBOARD_INPUT_DELAY :
0;
clearTimeout(that.keyboardInputTimeout_);
that.keyboardInputTimeout_ = setTimeout(
function() { that.onInputChange_(event); }, delay);
})
.blur(function(event) {
that.onInputChange_(event);
})
.keydown(
/**
* @param {!jQuery.Event} event
*/
function(event) {
if (event.which === jt.utils.Keyboard.UP) {
that.increment_();
return false;
}
if (event.which === jt.utils.Keyboard.DOWN) {
that.decrement_();
return false;
}
})
.mousewheel(
/**
* @param {!jQuery.Event} event
* @param {number} delta
*/
function(event, delta) {
if (delta > 0) {
that.increment_();
} else {
that.decrement_();
}
return false;
});
/* prevent annoying text selecting */
this.root_.parent().on('selectstart', function() {return false;});
};
/**
* @param {!jQuery.Event} event
* @private
*/
jt.block.IntegerInput.prototype.onInputChange_ = function(event) {
/** @type {number} */
var value = this.getInputValueNumber_();
if (isNaN(value)) {
if (event['type'] === 'keyup') {
this.root_.val('');
}
if (event['type'] === 'blur') {
this.setValue(this.value_);
}
} else {
var val =
this.setValue(Math.ceil(jt.utils.parseInt(value)/this.step_)*this.step_);
}
};
/**
* @return {number}
* @private
*/
jt.block.IntegerInput.prototype.getInputValueNumber_ = function() {
return jt.utils.clean_to_number(
(/** @type {string} */ this.root_.val()));
};
/**
* @private
*/
jt.block.IntegerInput.prototype.increment_ = function() {
this.setValue(this.value_ + this.step_);
};
/**
* @private
*/
jt.block.IntegerInput.prototype.decrement_ = function() {
this.setValue(this.value_ - this.step_);
};
/**
* @private
*/
jt.block.IntegerInput.prototype.attachControls_ = function() {
var root;
root = this.root_;
this.decrementer = root.before(jt.block.IntegerInput.DECREMENTER_TEMPLATE)
.prev();
this.incrementer = root.after(jt.block.IntegerInput.INCREMENTER_TEMPLATE)
.next();
this.incrementer.on('click', jQuery.proxy(this.increment_, this));
this.decrementer.on('click', jQuery.proxy(this.decrement_, this));
}
/**
* @param {number} value
* @return {number}
* @private
*/
jt.block.IntegerInput.prototype.normalizeValue_ = function(value) {
return Math.max(this.min_, Math.min(this.max_, value));
};