I am trying to implement a multiple line text area within a blockly block rather than a single line text input.
Which creates a single line text input field to the block. I need a multiline text input field, but this is not currently implemented in blockly.
So I am attempting to implement multiline field text area in blockly myself.
I created a new file, js\blockly\core\field_textarea.js, to implement a field text area in blockly.
And to get blockly to use/incorporate this new field text area, I had to make changes to the files
And I changed the expression block to use the new FieldTextArea instead of the FieldTextInput.
The problem with this implementation is that while I am able to create a multiline text area for editing the contents of the text input, I am not able to get the block to render as a multiline text field.
I am using the showEditor_ method to get a multiline text area for editing, but I can't figure out how to get it to render as a multiline field when not editing.
I have included my implementation of the field text area code below.
Has anyone been able to implement a multiline field text area for blockly blocks? if so please let me know, or if anyone else has any suggestions how to implement this capability.
/**
*/
'use strict';
goog.provide('Blockly.FieldTextArea');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.userAgent');
/**
* Class for an editable text field.
* @param {string} text The initial content of the field.
* @param {Function=} opt_changeHandler An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
* text, or null to abort the change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldTextArea = function(text, opt_changeHandler) {
Blockly.FieldTextArea.superClass_.constructor.call(this, text);
this.setChangeHandler(opt_changeHandler);
};
goog.inherits(Blockly.FieldTextArea, Blockly.Field);
/**
* Point size of text. Should match blocklyText's font-size in CSS.
*/
Blockly.FieldTextArea.FONTSIZE = 11;
/**
* Mouse cursor style when over the hotspot that initiates the editor.
*/
Blockly.FieldTextArea.prototype.CURSOR = 'text';
/**
* Close the input widget if this input is being deleted.
*/
Blockly.FieldTextArea.prototype.dispose = function() {
Blockly.WidgetDiv.hideIfOwner(this);
Blockly.FieldTextArea.superClass_.dispose.call(this);
};
/**
* Set the text in this field.
* @param {?string} text New text.
* @override
*/
Blockly.FieldTextArea.prototype.setText = function(text) {
if (text === null) {
// No change if null.
return;
}
if (this.sourceBlock_ && this.changeHandler_) {
var validated = this.changeHandler_(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) {
text = validated;
}
}
Blockly.Field.prototype.setText.call(this, text);
};
/**
* Show the inline free-text editor on top of the text.
* @param {boolean=} opt_quietInput True if editor should be created without
* focus. Defaults to false.
* @private
*/
Blockly.FieldTextArea.prototype.showEditor_ = function(opt_quietInput) {
var quietInput = opt_quietInput || false;
if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
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.changeHandler_) {
var override = this.changeHandler_(newValue);
if (override !== undefined) {
newValue = override;
}
}
if (newValue !== null) {
this.setText(newValue);
}
return;
}
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
var div = Blockly.WidgetDiv.DIV;
// Create the input.
var htmlInput = goog.dom.createDom('textarea', 'blocklyHtmlTextArea');
var fontSize = (Blockly.FieldTextArea.FONTSIZE *
this.sourceBlock_.workspace.scale) + 'pt';
div.style.fontSize = fontSize;
htmlInput.style.fontSize = fontSize;
Blockly.FieldTextArea.htmlInput_ = htmlInput;
htmlInput.style.height = '100px';
htmlInput.style.width = '500px';
div.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.text_;
htmlInput.oldValue_ = null;
this.validate_();
this.resizeEditor_();
if (!quietInput) {
htmlInput.focus();
htmlInput.select();
}
// Bind to keydown -- trap Enter without IME and Esc to hide.
htmlInput.onKeyDownWrapper_ =
Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
// Bind to keyup -- trap Enter; resize after every keystroke.
htmlInput.onKeyUpWrapper_ =
Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_);
// Bind to keyPress -- repeatedly resize when holding down a key.
htmlInput.onKeyPressWrapper_ =
Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_);
var workspaceSvg = this.sourceBlock_.workspace.getCanvas();
htmlInput.onWorkspaceChangeWrapper_ =
Blockly.bindEvent_(workspaceSvg, 'blocklyWorkspaceChange', this,
this.resizeEditor_);
};
/**
* Handle key down to the editor.
* @param {!Event} e Keyboard event.
* @private
*/
Blockly.FieldTextArea.prototype.onHtmlInputKeyDown_ = function(e) {
var htmlInput = Blockly.FieldTextArea.htmlInput_;
var tabKey = 9, enterKey = 13, escKey = 27;
if (e.keyCode == enterKey) {
Blockly.WidgetDiv.hide();
} else if (e.keyCode == escKey) {
this.setText(htmlInput.defaultValue);
Blockly.WidgetDiv.hide();
} else if (e.keyCode == tabKey) {
Blockly.WidgetDiv.hide();
this.sourceBlock_.tab(this, !e.shiftKey);
e.preventDefault();
}
};
/**
* Handle a change to the editor.
* @param {!Event} e Keyboard event.
* @private
*/
Blockly.FieldTextArea.prototype.onHtmlInputChange_ = function(e) {
var htmlInput = Blockly.FieldTextArea.htmlInput_;
var escKey = 27;
if (e.keyCode != escKey) {
// Update source block.
var text = htmlInput.value;
if (text !== htmlInput.oldValue_) {
this.sourceBlock_.setShadow(false);
htmlInput.oldValue_ = text;
this.setText(text);
this.validate_();
} else if (goog.userAgent.WEBKIT) {
// Cursor key. Render the source block to show the caret moving.
// Chrome only (version 26, OS X).
this.sourceBlock_.render();
}
}
};
/**
* Check to see if the contents of the editor validates.
* Style the editor accordingly.
* @private
*/
Blockly.FieldTextArea.prototype.validate_ = function() {
var valid = true;
goog.asserts.assertObject(Blockly.FieldTextArea.htmlInput_);
var htmlInput = Blockly.FieldTextArea.htmlInput_;
if (this.sourceBlock_ && this.changeHandler_) {
valid = this.changeHandler_(htmlInput.value);
}
if (valid === null) {
Blockly.addClass_(htmlInput, 'blocklyInvalidTextArea');
} else {
Blockly.removeClass_(htmlInput, 'blocklyInvalidTextArea');
}
};
/**
* Resize the editor and the underlying block to fit the text.
* @private
*/
Blockly.FieldTextArea.prototype.resizeEditor_ = function() {
var div = Blockly.WidgetDiv.DIV;
var bBox = this.fieldGroup_.getBBox();
div.style.width = bBox.width * this.sourceBlock_.workspace.scale + 'px';
div.style.height = bBox.height * this.sourceBlock_.workspace.scale + 'px';
var xy = this.getAbsoluteXY_();
// In RTL mode block fields and LTR input fields the left edge moves,
// whereas the right edge is fixed. Reposition the editor.
if (this.sourceBlock_.RTL) {
var borderBBox = this.getScaledBBox_();
xy.x += borderBBox.width;
xy.x -= div.offsetWidth;
}
// Shift by a few pixels to line up exactly.
xy.y += 1;
if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
// Firefox mis-reports the location of the border by a pixel
// once the WidgetDiv is moved into position.
xy.x -= 1;
xy.y -= 1;
}
if (goog.userAgent.WEBKIT) {
xy.y -= 3;
}
div.style.left = xy.x + 'px';
div.style.top = xy.y + 'px';
};
/**
* Draws the border with the correct width.
* Saves the computed width in a property.
* @private
*/
Blockly.FieldTextArea.prototype.render_ = function() {
this.size_.width = this.textElement_.getBBox().width + 5;
this.size_.height= (this.text_.split(/\n/).length ||1)*20 + (Blockly.BlockSvg.SEP_SPACE_Y+5) ;
if (this.borderRect_) {
this.borderRect_.setAttribute('width',
this.size_.width + Blockly.BlockSvg.SEP_SPACE_X);
this.borderRect_.setAttribute('height',
this.size_.height - (Blockly.BlockSvg.SEP_SPACE_Y+5));
}
};
/**
* Close the editor, save the results, and dispose of the editable
* text field's elements.
* @return {!Function} Closure to call on destruction of the WidgetDiv.
* @private
*/
Blockly.FieldTextArea.prototype.widgetDispose_ = function() {
var thisField = this;
return function() {
var htmlInput = Blockly.FieldTextArea.htmlInput_;
// Save the edit (if it validates).
var text = htmlInput.value;
if (thisField.sourceBlock_ && thisField.changeHandler_) {
var text1 = thisField.changeHandler_(text);
if (text1 === null) {
// Invalid edit.
text = htmlInput.defaultValue;
} else if (text1 !== undefined) {
// Change handler has changed the text.
text = text1;
}
}
thisField.setText(text);
thisField.sourceBlock_.rendered && thisField.sourceBlock_.render();
Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
Blockly.unbindEvent_(htmlInput.onWorkspaceChangeWrapper_);
Blockly.FieldTextArea.htmlInput_ = null;
// Delete style properties.
var style = Blockly.WidgetDiv.DIV.style;
style.width = 'auto';
style.height = 'auto';
style.fontSize = '';
};
};
/**
* Ensure that only a number may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid number, or null if invalid.
*/
Blockly.FieldTextArea.numberValidator = function(text) {
if (text === null) {
return null;
}
text = String(text);
// TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users.
text = text.replace(/O/ig, '0');
// Strip out thousands separators.
text = text.replace(/,/g, '');
var n = parseFloat(text || 0);
return isNaN(n) ? null : String(n);
};
/**
* Ensure that only a nonnegative integer may be entered.
* @param {string} text The user's text.
* @return {?string} A string representing a valid int, or null if invalid.
*/
Blockly.FieldTextArea.nonnegativeIntegerValidator = function(text) {
var n = Blockly.FieldTextArea.numberValidator(text);
if (n) {
n = String(Math.max(0, Math.floor(n)));
}
return n;
};