/** * 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)); };