(function($) { var Alpaca = $.alpaca; Alpaca.Fields.NumberField = Alpaca.Fields.TextField.extend( /** * @lends Alpaca.Fields.NumberField.prototype */ { /** * @see Alpaca.Fields.TextField#setup */ setup: function() { // default html5 input type = "number"; //this.inputType = "number"; // TODO: we can't do this because Chrome screws up it's handling of number type // and prevents us from validating properly // @see http://stackoverflow.com/questions/16420828/jquery-val-refuses-to-return-non-numeric-input-from-a-number-field-under-chrome this.base(); if (typeof(this.options.numericEntry) === "undefined") { this.options.numericEntry = false; } }, /** * @see Alpaca.Fields.TextField#getFieldType */ getFieldType: function() { return "number"; }, /** * @see Alpaca.ControlField#postRender */ postRender: function(callback) { var self = this; this.base(function() { if (self.control) { self.on("keypress", function(e) { var key = e.charCode || e.keyCode || 0; var valid = true; if (self.options.numericEntry) { valid = valid && (key >= 48 && key <= 57); } if(!valid) { // don't even allow entry of invalid characters e.preventDefault(); e.stopImmediatePropagation(); } return valid; }); } callback(); }); }, /** * @see Alpaca.Fields.ControlField#getControlValue */ getControlValue: function() { var val = this._getControlVal(true); if (typeof(val) == "undefined" || "" == val) { return val; } return parseFloat(val); }, /** * @see Alpaca.Fields.TextField#handleValidate */ handleValidate: function() { var baseStatus = this.base(); var valInfo = this.validation; var status = this._validateNumber(); valInfo["stringNotANumber"] = { "message": status ? "" : this.getMessage("stringNotANumber"), "status": status }; status = this._validateDivisibleBy(); valInfo["stringDivisibleBy"] = { "message": status ? "" : Alpaca.substituteTokens(this.getMessage("stringDivisibleBy"), [this.schema.divisibleBy]), "status": status }; status = this._validateMaximum(); valInfo["stringValueTooLarge"] = { "message": "", "status": status }; if (!status) { if (this.schema.exclusiveMaximum) { valInfo["stringValueTooLarge"]["message"] = Alpaca.substituteTokens(this.getMessage("stringValueTooLargeExclusive"), [this.schema.maximum]); } else { valInfo["stringValueTooLarge"]["message"] = Alpaca.substituteTokens(this.getMessage("stringValueTooLarge"), [this.schema.maximum]); } } status = this._validateMinimum(); valInfo["stringValueTooSmall"] = { "message": "", "status": status }; if (!status) { if (this.schema.exclusiveMinimum) { valInfo["stringValueTooSmall"]["message"] = Alpaca.substituteTokens(this.getMessage("stringValueTooSmallExclusive"), [this.schema.minimum]); } else { valInfo["stringValueTooSmall"]["message"] = Alpaca.substituteTokens(this.getMessage("stringValueTooSmall"), [this.schema.minimum]); } } status = this._validateMultipleOf(); valInfo["stringValueNotMultipleOf"] = { "message": "", "status": status }; if (!status) { valInfo["stringValueNotMultipleOf"]["message"] = Alpaca.substituteTokens(this.getMessage("stringValueNotMultipleOf"), [this.schema.multipleOf]); } // hand back a true/false return baseStatus && valInfo["stringNotANumber"]["status"] && valInfo["stringDivisibleBy"]["status"] && valInfo["stringValueTooLarge"]["status"] && valInfo["stringValueTooSmall"]["status"] && valInfo["stringValueNotMultipleOf"]["status"] && valInfo["invalidPattern"]["status"] && valInfo["stringTooLong"]["status"] && valInfo["stringTooShort"]["status"]; }, /** * Validates if it is a float number. * @returns {Boolean} true if it is a float number */ _validateNumber: function() { // get value as text var textValue = this._getControlVal(); if (typeof(textValue) === "number") { textValue = "" + textValue; } // allow empty if (Alpaca.isValEmpty(textValue)) { return true; } // check if valid number format var validNumber = Alpaca.testRegex(Alpaca.regexps.number, textValue); if (!validNumber) { return false; } // quick check to see if what they entered was a number var floatValue = this.getValue(); if (isNaN(floatValue)) { return false; } return true; }, /** * Validates divisibleBy constraint. * @returns {Boolean} true if it passes the divisibleBy constraint. */ _validateDivisibleBy: function() { var floatValue = this.getValue(); if (!Alpaca.isEmpty(this.schema.divisibleBy)) { // mod if (floatValue % this.schema.divisibleBy !== 0) { return false; } } return true; }, /** * Validates maximum constraint. * @returns {Boolean} true if it passes the maximum constraint. */ _validateMaximum: function() { var floatValue = this.getValue(); if (!Alpaca.isEmpty(this.schema.maximum)) { if (floatValue > this.schema.maximum) { return false; } if (!Alpaca.isEmpty(this.schema.exclusiveMaximum)) { if (floatValue == this.schema.maximum && this.schema.exclusiveMaximum) { // jshint ignore:line return false; } } } return true; }, /** * Validates maximum constraint. * @returns {Boolean} true if it passes the minimum constraint. */ _validateMinimum: function() { var floatValue = this.getValue(); if (!Alpaca.isEmpty(this.schema.minimum)) { if (floatValue < this.schema.minimum) { return false; } if (!Alpaca.isEmpty(this.schema.exclusiveMinimum)) { if (floatValue == this.schema.minimum && this.schema.exclusiveMinimum) { // jshint ignore:line return false; } } } return true; }, /** * Validates multipleOf constraint. * @returns {Boolean} true if it passes the multipleOf constraint. */ _validateMultipleOf: function() { var floatValue = this.getValue(); if (!Alpaca.isEmpty(this.schema.multipleOf)) { if (floatValue && this.schema.multipleOf !== 0) { return false; } } return true; }, /** * @see Alpaca.Fields.TextField#getType */ getType: function() { return "number"; }, /** * @see Alpaca.ControlField#onKeyPress */ onKeyPress: function(e) { var self = this; // ignore tab and arrow keys if (e.keyCode === 9 || e.keyCode === 37 || e.keyCode === 38 || e.keyCode === 39 || e.keyCode === 40 ) { return; } if (e.keyCode === 8) // backspace { if (!Alpaca.isEmpty(self.schema.minLength) && (self.options.constrainLengths || self.options.constrainMinLength)) { var newValue = self.getValue() || ""; if(Alpaca.isNumber(newValue)) { newValue = newValue.toString(); } if (newValue.length <= self.schema.minLength) { // kill event e.preventDefault(); e.stopImmediatePropagation(); } } } else { if (!Alpaca.isEmpty(self.schema.maxLength) && (self.options.constrainLengths || self.options.constrainMaxLength)) { var newValue = self.getValue() || ""; if(Alpaca.isNumber(newValue)) { newValue = newValue.toString(); } if (newValue.length >= self.schema.maxLength) { // kill event e.preventDefault(); e.stopImmediatePropagation(); } } } if (e.keyCode === 32) // space { if (self.options.disallowEmptySpaces) { // kill event e.preventDefault(); e.stopImmediatePropagation(); } } }, onKeyUp: function(e) { var self = this; // if applicable, update the max length indicator self.updateMaxLengthIndicator(); // trigger "fieldkeyup" $(this.field).trigger("fieldkeyup"); }, /* builder_helpers */ /** * @private * @see Alpaca.Fields.TextField#getSchemaOfSchema */ getSchemaOfSchema: function() { return Alpaca.merge(this.base(), { "properties": { "multipleOf": { "title": "Multiple Of", "description": "Property value must be a multiple of the multipleOf schema property such that division by this value yields an interger (mod zero).", "type": "number" }, "minimum": { "title": "Minimum", "description": "Minimum value of the property.", "type": "number" }, "maximum": { "title": "Maximum", "description": "Maximum value of the property.", "type": "number" }, "exclusiveMinimum": { "title": "Exclusive Minimum", "description": "Property value can not equal the number defined by the minimum schema property.", "type": "boolean", "default": false }, "exclusiveMaximum": { "title": "Exclusive Maximum", "description": "Property value can not equal the number defined by the maximum schema property.", "type": "boolean", "default": false } } }); }, /** * @private * @see Alpaca.Fields.TextField#getOptionsSchema */ getOptionsForSchema: function() { return Alpaca.merge(this.base(), { "fields": { "multipleOf": { "title": "Multiple Of", "description": "The value must be a integral multiple of the property", "type": "number" }, "minimum": { "title": "Minimum", "description": "Minimum value of the property", "type": "number" }, "maximum": { "title": "Maximum", "description": "Maximum value of the property", "type": "number" }, "exclusiveMinimum": { "rightLabel": "Exclusive minimum ?", "helper": "Field value must be greater than but not equal to this number if checked", "type": "checkbox" }, "exclusiveMaximum": { "rightLabel": "Exclusive Maximum ?", "helper": "Field value must be less than but not equal to this number if checked", "type": "checkbox" } } }); }, /** * @private * @see Alpaca.Fields.NumberField#getSchemaOfOptions */ getSchemaOfOptions: function() { return Alpaca.merge(this.base(), { "properties": { "numericEntry": { "title": "Numeric Entry", "description": "Whether to constrain data entry key presses to numeric values (0-9)", "type": "boolean", "default": false } } }); }, /** * @see Alpaca.Fields.TextField#getTitle */ getTitle: function() { return "Number Field"; }, /** * @see Alpaca.Fields.TextField#getDescription */ getDescription: function() { return "Field for float numbers."; } /* end_builder_helpers */ }); // Additional Registrations Alpaca.registerMessages({ "stringValueTooSmall": "The minimum value for this field is {0}", "stringValueTooLarge": "The maximum value for this field is {0}", "stringValueTooSmallExclusive": "Value of this field must be greater than {0}", "stringValueTooLargeExclusive": "Value of this field must be less than {0}", "stringDivisibleBy": "The value must be divisible by {0}", "stringNotANumber": "This value is not a number.", "stringValueNotMultipleOf": "This value is not a multiple of {0}" }); Alpaca.registerFieldClass("number", Alpaca.Fields.NumberField); Alpaca.registerDefaultSchemaFieldMapping("number", "number"); })(jQuery);