diff --git a/core/field_number.js b/core/field_number.js index def30ed74..7f7de4afb 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -41,37 +41,50 @@ goog.require('Blockly.utils.object'); * @param {Function=} opt_validator A function that is called to validate * changes to the field's value. Takes in a number & returns a validated * number, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/number#creation} + * for a list of properties this parameter supports. * @extends {Blockly.FieldTextInput} * @constructor */ Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision, - opt_validator) { - + opt_validator, opt_config) { + /** - * The minimum value constraint. + * The minimum value this number field can contain. * @type {number} * @protected */ this.min_ = -Infinity; /** - * The maximum value constraint. + * The maximum value this number field can contain. * @type {number} * @protected */ this.max_ = Infinity; /** - * The precision constraint for the value. + * The multiple to which this fields value is rounded. * @type {number} * @protected */ this.precision_ = 0; - Blockly.FieldNumber.superClass_.constructor.call( - this, opt_value || 0, opt_validator); + /** + * The number of decimal places to allow, or null to allow any number of + * decimal digits. + * @type {?number} + * @private + */ + this.decimalPlaces_ = null; - this.setConstraints(opt_min, opt_max, opt_precision); + Blockly.FieldNumber.superClass_.constructor.call( + this, opt_value || 0, opt_validator, opt_config); + + if (!opt_config) { // Only do one kind of configuration or the other. + this.setConstraints(opt_min, opt_max, opt_precision); + } }; Blockly.utils.object.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); @@ -85,7 +98,7 @@ Blockly.utils.object.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); */ Blockly.FieldNumber.fromJson = function(options) { return new Blockly.FieldNumber(options['value'], - options['min'], options['max'], options['precision']); + null, null, null, null, options); }; /** @@ -96,6 +109,18 @@ Blockly.FieldNumber.fromJson = function(options) { */ Blockly.FieldNumber.prototype.SERIALIZABLE = true; +/** + * Configure the field based on the given map of options. + * @param {!Object} config A map of options to configure the field based on. + * @private + */ +Blockly.FieldNumber.prototype.configure_ = function(config) { + Blockly.FieldNumber.superClass_.configure_.call(this, config); + this.setMinInternal_(config['min']); + this.setMaxInternal_(config['max']); + this.setPrecisionInternal_(config['precision']); +}; + /** * Set the maximum, minimum and precision constraints on this field. * Any of these properties may be undefined or NaN to be disabled. @@ -108,25 +133,119 @@ Blockly.FieldNumber.prototype.SERIALIZABLE = true; * @param {number|string|undefined} precision Precision for value. */ Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { - precision = Number(precision); - if (!isNaN(precision)) { - this.precision_ = precision; - } - var precisionString = this.precision_.toString(); - var decimalIndex = precisionString.indexOf('.'); - this.fractionalDigits_ = (decimalIndex == -1) ? -1 : - precisionString.length - (decimalIndex + 1); + this.setMinInternal_(min); + this.setMaxInternal_(max); + this.setPrecisionInternal_(precision); + this.setValue(this.getValue()); +}; + +/** + * Sets the minimum value this field can contain. Updates the value to reflect. + * @param {number|string|undefined} min Minimum value. + */ +Blockly.FieldNumber.prototype.setMin = function(min) { + this.setMinInternal_(min); + this.setValue(this.getValue()); +}; + +/** + * Sets the minimum value this field can contain. Called internally to avoid + * value updates. + * @param {number|string|undefined} min Minimum value. + * @private + */ +Blockly.FieldNumber.prototype.setMinInternal_ = function(min) { min = Number(min); if (!isNaN(min)) { this.min_ = min; } +}; + +/** + * Returns the current minimum value this field can contain. Default is + * -Infinity. + * @return {number} The current minimum value this field can contain. + */ +Blockly.FieldNumber.prototype.getMin = function() { + return this.min_; +}; + +/** + * Sets the maximum value this field can contain. Updates the value to reflect. + * @param {number|string|undefined} max Maximum value. + */ +Blockly.FieldNumber.prototype.setMax = function(max) { + this.setMaxInternal_(max); + this.setValue(this.getValue()); +}; + +/** + * Sets the maximum value this field can contain. Called internally to avoid + * value updates. + * @param {number|string|undefined} max Maximum value. + * @private + */ +Blockly.FieldNumber.prototype.setMaxInternal_ = function(max) { max = Number(max); if (!isNaN(max)) { this.max_ = max; } +}; + +/** + * Returns the current maximum value this field can contain. Default is + * Infinity. + * @return {number} The current maximum value this field can contain. + */ +Blockly.FieldNumber.prototype.getMax = function() { + return this.max_; +}; + +/** + * Sets the precision of this field's value, i.e. the number to which the + * value is rounded. Updates the field to reflect. + * @param {number|string|undefined} precision The number to which the + * field's value is rounded. + */ +Blockly.FieldNumber.prototype.setPrecision = function(precision) { + this.setPrecisionInternal_(precision); this.setValue(this.getValue()); }; +/** + * Sets the precision of this field's value. Called internally to avoid + * value updates. + * @param {number|string|undefined} precision The number to which the + * field's value is rounded. + * @private + */ +Blockly.FieldNumber.prototype.setPrecisionInternal_ = function(precision) { + precision = Number(precision); + if (!isNaN(precision)) { + this.precision_ = precision; + } + + var precisionString = this.precision_.toString(); + var decimalIndex = precisionString.indexOf('.'); + if (decimalIndex == -1) { + // If the precision is 0 (float) allow any number of decimals, + // otherwise allow none. + this.decimalPlaces_ = precision ? 0 : null; + } else { + this.decimalPlaces_ = precisionString.length - decimalIndex - 1; + } +}; + +/** + * Returns the current precision of this field. The precision being the + * number to which the field's value is rounded. A precision of 0 means that + * the value is not rounded. + * @return {number} The number to which this field's value is rounded. + */ +Blockly.FieldNumber.prototype.getPrecision = function() { + return this.precision_; +}; + /** * Ensure that the input value is a valid number (must fulfill the * constraints placed on the field). @@ -160,8 +279,9 @@ Blockly.FieldNumber.prototype.doClassValidation_ = function(opt_newValue) { n = Math.round(n / this.precision_) * this.precision_; } // Clean up floating point errors. - n = (this.fractionalDigits_ == -1) ? n : - Number(n.toFixed(this.fractionalDigits_)); + if (this.decimalPlaces_ != null) { + n = Number(n.toFixed(this.decimalPlaces_)); + } return n; }; diff --git a/core/field_textinput.js b/core/field_textinput.js index 3a556d262..336a2da24 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -47,15 +47,18 @@ goog.require('Blockly.utils.userAgent'); * @param {Function=} opt_validator A function that is called to validate * changes to the field's value. Takes in a string & returns a validated * string, or null to abort the change. + * @param {Object=} opt_config A map of options used to configure the field. + * See the [field creation documentation]{@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation} + * for a list of properties this parameter supports. * @extends {Blockly.Field} * @constructor */ -Blockly.FieldTextInput = function(opt_value, opt_validator) { +Blockly.FieldTextInput = function(opt_value, opt_validator, opt_config) { if (opt_value == null) { opt_value = ''; } Blockly.FieldTextInput.superClass_.constructor.call(this, - opt_value, opt_validator); + opt_value, opt_validator, opt_config); }; Blockly.utils.object.inherits(Blockly.FieldTextInput, Blockly.Field); diff --git a/tests/mocha/field_number_test.js b/tests/mocha/field_number_test.js index 1bce8ffcb..6bc67c2be 100644 --- a/tests/mocha/field_number_test.js +++ b/tests/mocha/field_number_test.js @@ -33,9 +33,9 @@ suite('Number Fields', function() { function assertNumberField(numberField, expectedMin, expectedMax, expectedPrecision, expectedValue) { assertValue(numberField, expectedValue); - assertEquals(numberField.min_, expectedMin); - assertEquals(numberField.max_, expectedMax); - assertEquals(numberField.precision_, expectedPrecision); + chai.assert.equal(numberField.getMin(), expectedMin); + chai.assert.equal(numberField.getMax(), expectedMax); + chai.assert.equal(numberField.getPrecision(), expectedPrecision); } function assertNumberFieldDefault(numberField) { assertNumberField(numberField, -Infinity, Infinity, 0, 0); @@ -375,4 +375,114 @@ suite('Number Fields', function() { }); }); }); + suite('Customizations', function() { + suite('Min', function() { + test('JS Constructor', function() { + var field = new Blockly.FieldNumber(0, -10); + assertNumberField(field, -10, Infinity, 0, 0); + }); + test('JSON Definition', function() { + var field = Blockly.FieldNumber.fromJson({ + min: -10, + }); + assertNumberField(field, -10, Infinity, 0, 0); + }); + test('Set Constraints', function() { + var field = new Blockly.FieldNumber(); + field.setConstraints(-10); + assertNumberField(field, -10, Infinity, 0, 0); + }); + test('Set Min', function() { + var field = new Blockly.FieldNumber(); + field.setMin(-10); + assertNumberField(field, -10, Infinity, 0, 0); + }); + test('JS Configuration - Simple', function() { + var field = new Blockly.FieldNumber( + undefined, undefined, undefined, undefined, undefined, { + min: -10 + }); + assertNumberField(field, -10, Infinity, 0, 0); + }); + test('JS Configuration - Ignore', function() { + var field = new Blockly.FieldNumber( + undefined, -1, undefined, undefined, undefined, { + min: -10 + }); + assertNumberField(field, -10, Infinity, 0, 0); + }); + }); + suite('Max', function() { + test('JS Constructor', function() { + var field = new Blockly.FieldNumber(0, undefined, 10); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + test('JSON Definition', function() { + var field = Blockly.FieldNumber.fromJson({ + max: 10, + }); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + test('Set Constraints', function() { + var field = new Blockly.FieldNumber(); + field.setConstraints(undefined, 10); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + test('Set Max', function() { + var field = new Blockly.FieldNumber(); + field.setMax(10); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + test('JS Configuration - Simple', function() { + var field = new Blockly.FieldNumber( + undefined, undefined, undefined, undefined, undefined, { + max: 10 + }); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + test('JS Configuration - Ignore', function() { + var field = new Blockly.FieldNumber( + undefined, undefined, 1, undefined, undefined, { + max: 10 + }); + assertNumberField(field, -Infinity, 10, 0, 0); + }); + }); + suite('Precision', function() { + test('JS Constructor', function() { + var field = new Blockly.FieldNumber(0, undefined, undefined, 1); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + test('JSON Definition', function() { + var field = Blockly.FieldNumber.fromJson({ + precision: 1, + }); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + test('Set Constraints', function() { + var field = new Blockly.FieldNumber(); + field.setConstraints(undefined, undefined, 1); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + test('Set Precision', function() { + var field = new Blockly.FieldNumber(); + field.setPrecision(1); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + test('JS Configuration - Simple', function() { + var field = new Blockly.FieldNumber( + undefined, undefined, undefined, undefined, undefined, { + precision: 1 + }); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + test('JS Configuration - Ignore', function() { + var field = new Blockly.FieldNumber( + undefined, undefined, undefined, .5, undefined, { + precision: 1 + }); + assertNumberField(field, -Infinity, Infinity, 1, 0); + }); + }); + }); });