From 555eac8b7f6d8107978a94401de7955122f5d0dc Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Wed, 29 Jun 2016 17:44:12 -0700 Subject: [PATCH] Simplify field validator chaining. --- core/field.js | 31 +++++++++++++++++++++++++++++++ core/field_angle.js | 32 ++------------------------------ core/field_checkbox.js | 7 ++----- core/field_colour.js | 7 ++----- core/field_date.js | 13 +++++-------- core/field_dropdown.js | 7 ++----- core/field_number.js | 34 +++------------------------------- core/field_textinput.js | 23 ++++++++++------------- core/field_variable.js | 31 +------------------------------ core/workspace_svg.js | 2 +- 10 files changed, 59 insertions(+), 128 deletions(-) diff --git a/core/field.js b/core/field.js index 0e919ecd..b6cff255 100644 --- a/core/field.js +++ b/core/field.js @@ -236,6 +236,37 @@ Blockly.Field.prototype.getValidator = function() { return this.validator_; }; +/** + * Calls the validation function for this field, as well as all the validation + * function for the field's class and its parents. + * @param {string} text Proposed text. + * @return {?string} Revised text, or null if invalid. + */ +Blockly.Field.prototype.callValidator = function(text) { + // Collect a list of validators, from Field, through to the subclass, ending + // with the user's validator. + var validators = [this.getValidator()]; + var fieldClass = this.constructor; + while (fieldClass) { + validators.unshift(fieldClass.classValidator); + fieldClass = fieldClass.superClass_; + } + // Call each validator in turn, allowing each to rewrite or reject. + for (var i = 0; i < validators.length; i++) { + var validator = validators[i]; + if (validator) { + var result = validator.call(this, text); + if (result === null) { + // Validator rejects value. Game over. + return null; + } else if (result !== undefined) { + text = result; + } + } + } + return text; +}; + /** * Gets the group element for this editable field. * Used for measuring the size and for positioning. diff --git a/core/field_angle.js b/core/field_angle.js index 35534a42..00b0c16a 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -50,35 +50,6 @@ Blockly.FieldAngle = function(text, opt_validator) { }; goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); -/** - * Sets a new change handler for angle field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldAngle.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the angle validator. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = Blockly.FieldAngle.angleValidator.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldAngle.angleValidator; - } - Blockly.FieldAngle.superClass_.setValidator.call(this, wrappedHandler); -}; - /** * Round angles to the nearest 15 degrees when using mouse. * Set to 0 to disable rounding. @@ -303,8 +274,9 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() { * Ensure that only an angle may be entered. * @param {string} text The user's text. * @return {?string} A string representing a valid angle, or null if invalid. + * @this {!Blockly.FieldAngle} */ -Blockly.FieldAngle.angleValidator = function(text) { +Blockly.FieldAngle.classValidator = function(text) { if (text === null) { return null; } diff --git a/core/field_checkbox.js b/core/field_checkbox.js index fb46e0a4..638aba94 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -107,12 +107,9 @@ Blockly.FieldCheckbox.prototype.setValue = function(strBool) { */ Blockly.FieldCheckbox.prototype.showEditor_ = function() { var newState = !this.state_; - if (this.sourceBlock_ && this.validator_) { + if (this.sourceBlock_) { // Call any validation function, and allow it to override. - var override = this.validator_(newState); - if (override !== undefined) { - newState = override; - } + newState = this.callValidator(newState); } if (newState !== null) { this.setValue(String(newState).toUpperCase()); diff --git a/core/field_colour.js b/core/field_colour.js index 65346ee2..30b7dc5e 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -213,12 +213,9 @@ Blockly.FieldColour.prototype.showEditor_ = function() { function(event) { var colour = event.target.getSelectedColor() || '#000000'; Blockly.WidgetDiv.hide(); - if (thisField.sourceBlock_ && thisField.validator_) { + if (thisField.sourceBlock_) { // Call any validation function, and allow it to override. - var override = thisField.validator_(colour); - if (override !== undefined) { - colour = override; - } + colour = thisField.callValidator(colour); } if (colour !== null) { thisField.setValue(colour); diff --git a/core/field_date.js b/core/field_date.js index 55e93fb8..24f3239e 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -82,11 +82,11 @@ Blockly.FieldDate.prototype.getValue = function() { * @param {string} date The new date. */ Blockly.FieldDate.prototype.setValue = function(date) { - if (this.sourceBlock_ && this.validator_) { - var validated = this.validator_(date); + if (this.sourceBlock_) { + var validated = this.callValidator(date); // If the new date is invalid, validation returns null. // In this case we still want to display the illegal result. - if (validated !== null && validated !== undefined) { + if (validated !== null) { date = validated; } } @@ -149,12 +149,9 @@ Blockly.FieldDate.prototype.showEditor_ = function() { function(event) { var date = event.date ? event.date.toIsoString(true) : ''; Blockly.WidgetDiv.hide(); - if (thisField.sourceBlock_ && thisField.validator_) { + if (thisField.sourceBlock_) { // Call any validation function, and allow it to override. - var override = thisField.validator_(date); - if (override !== undefined) { - date = override; - } + date = thisField.callValidator(date); } thisField.setValue(date); }); diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 294682fc..1e62cf50 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -108,12 +108,9 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() { var menuItem = e.target; if (menuItem) { var value = menuItem.getValue(); - if (thisField.sourceBlock_ && thisField.validator_) { + if (thisField.sourceBlock_) { // Call any validation function, and allow it to override. - var override = thisField.validator_(value); - if (override !== undefined) { - value = override; - } + value = thisField.callValidator(value); } if (value !== null) { thisField.setValue(value); diff --git a/core/field_number.js b/core/field_number.js index 00002299..b580d737 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -67,44 +67,16 @@ Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { this.min_ = isNaN(min) ? -Infinity : min; max = parseFloat(max); this.max_ = isNaN(max) ? Infinity : max; - this.setValue(this.numberValidator(this.getValue)); -}; - -/** - * Sets a new change handler for number field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldNumber.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the angle validator. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = this.numberValidator(v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = this.numberValidator; - } - Blockly.FieldNumber.superClass_.setValidator.call(this, wrappedHandler); + this.setValue(this.callValidator(this.getValue)); }; /** * Ensure that only a number in the correct range may be entered. * @param {string} text The user's text. * @return {?string} A string representing a valid number, or null if invalid. + * @this {!Blockly.FieldNumber} */ -Blockly.FieldNumber.prototype.numberValidator = function(text) { +Blockly.FieldNumber.classValidator = function(text) { if (text === null) { return null; } diff --git a/core/field_textinput.js b/core/field_textinput.js index 6ef6bf1d..ba9375e4 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -82,11 +82,11 @@ Blockly.FieldTextInput.prototype.setValue = function(text) { if (text === null) { return; // No change if null. } - if (this.sourceBlock_ && this.validator_) { - var validated = this.validator_(text); + if (this.sourceBlock_) { + var validated = this.callValidator(text); // If the new text is invalid, validation returns null. // In this case we still want to display the illegal result. - if (validated !== null && validated !== undefined) { + if (validated !== null) { text = validated; } } @@ -114,11 +114,8 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { goog.userAgent.IPAD)) { // Mobile browsers have issues with in-line textareas (focus & keyboards). var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_); - if (this.sourceBlock_ && this.validator_) { - var override = this.validator_(newValue); - if (override !== undefined) { - newValue = override; - } + if (this.sourceBlock_) { + newValue = this.callValidator(newValue); } this.setValue(newValue); return; @@ -210,8 +207,8 @@ Blockly.FieldTextInput.prototype.validate_ = function() { var valid = true; goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_); var htmlInput = Blockly.FieldTextInput.htmlInput_; - if (this.sourceBlock_ && this.validator_) { - valid = this.validator_(htmlInput.value); + if (this.sourceBlock_) { + valid = this.callValidator(htmlInput.value); } if (valid === null) { Blockly.addClass_(htmlInput, 'blocklyInvalidInput'); @@ -264,12 +261,12 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { var htmlInput = Blockly.FieldTextInput.htmlInput_; // Save the edit (if it validates). var text = htmlInput.value; - if (thisField.sourceBlock_ && thisField.validator_) { - var text1 = thisField.validator_(text); + if (thisField.sourceBlock_) { + var text1 = thisField.callValidator(text); if (text1 === null) { // Invalid edit. text = htmlInput.defaultValue; - } else if (text1 !== undefined) { + } else { // Validation function has changed the text. text = text1; } diff --git a/core/field_variable.js b/core/field_variable.js index 0a040a0a..cf903c04 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -48,35 +48,6 @@ Blockly.FieldVariable = function(varname, opt_validator) { }; goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); -/** - * Sets a new change handler for angle field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldVariable.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the variable rename handler. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = Blockly.FieldVariable.dropdownChange.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldVariable.dropdownChange; - } - Blockly.FieldVariable.superClass_.setValidator.call(this, wrappedHandler); -}; - /** * Install this dropdown on a block. */ @@ -158,7 +129,7 @@ Blockly.FieldVariable.dropdownCreate = function() { * handled (rename), or undefined if an existing variable was chosen. * @this {!Blockly.FieldVariable} */ -Blockly.FieldVariable.dropdownChange = function(text) { +Blockly.FieldVariable.classValidator = function(text) { function promptName(promptText, defaultText) { Blockly.hideChaff(); var newVar = window.prompt(promptText, defaultText); diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 6f5f26a1..07e43a22 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -712,7 +712,7 @@ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) { /** * Is the user currently dragging a block or scrolling the workspace? - * @return {boolean} True if currently dragging or scrolling + * @return {boolean} True if currently dragging or scrolling. */ Blockly.WorkspaceSvg.prototype.isDragging = function() { return Blockly.dragMode_ == Blockly.DRAG_FREE || this.isScrolling;