diff --git a/core/field.js b/core/field.js index d878ad506..564150fba 100644 --- a/core/field.js +++ b/core/field.js @@ -156,7 +156,7 @@ Blockly.Field.prototype.init = function() { Blockly.bindEventWithChecks_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_); // Force a render. - this.updateTextNode_(); + this.render_(); }; /** @@ -285,33 +285,48 @@ Blockly.Field.prototype.getSvgRoot = function() { * @private */ Blockly.Field.prototype.render_ = function() { - if (this.visible_ && this.textElement_) { - var key = this.textElement_.textContent + '\n' + - this.textElement_.className.baseVal; - if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) { - var width = Blockly.Field.cacheWidths_[key]; - } else { - try { - var width = this.textElement_.getComputedTextLength(); - } catch (e) { - // MSIE 11 is known to throw "Unexpected call to method or property - // access." if Blockly is hidden. - var width = this.textElement_.textContent.length * 8; - } - if (Blockly.Field.cacheWidths_) { - Blockly.Field.cacheWidths_[key] = width; - } - } - if (this.borderRect_) { - this.borderRect_.setAttribute('width', - width + Blockly.BlockSvg.SEP_SPACE_X); - } - } else { - var width = 0; + if (!this.visible_) { + this.size_.width = 0; + return; + } + + // Replace the text. + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); + + var width = Blockly.Field.getCachedWidth(this.textElement_); + if (this.borderRect_) { + this.borderRect_.setAttribute('width', + width + Blockly.BlockSvg.SEP_SPACE_X); } this.size_.width = width; }; +/** + * Gets the width of a text element, caching it in the process. + * @param {!Element} textElement An SVG 'text' element. + * @retur {number} Width of element. + */ +Blockly.Field.getCachedWidth = function(textElement) { + var key = textElement.textContent + '\n' + textElement.className.baseVal; + if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) { + var width = Blockly.Field.cacheWidths_[key]; + } else { + try { + var width = textElement.getComputedTextLength(); + } catch (e) { + // MSIE 11 is known to throw "Unexpected call to method or property + // access." if Blockly is hidden. + var width = textElement.textContent.length * 8; + } + if (Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_[key] = width; + } + } + return width; +}; + /** * Start caching field widths. Every call to this function MUST also call * stopCache. Caches must not survive between execution threads. @@ -358,6 +373,31 @@ Blockly.Field.prototype.getScaledBBox_ = function() { bBox.height * this.sourceBlock_.workspace.scale); }; +/** + * Get the text from this field as displayed on screen. May differ from getText + * due to ellipsis, and other formatting. + * @return {string} Currently displayed text. + * @private + */ +Blockly.Field.prototype.getDisplayText_ = function() { + var text = this.text_; + if (!text) { + // Prevent the field from disappearing if empty. + return Blockly.Field.NBSP; + } + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; + } + // Replace whitespace with non-breaking spaces so the text doesn't collapse. + text = text.replace(/\s/g, Blockly.Field.NBSP); + if (this.sourceBlock_.RTL) { + // The SVG is LTR, force text to be RTL. + text += '\u200F'; + } + return text; +}; + /** * Get the text from this field. * @return {string} Current text. @@ -381,7 +421,8 @@ Blockly.Field.prototype.setText = function(text) { return; } this.text_ = text; - this.updateTextNode_(); + // Set width to 0 to force a rerender of this field. + this.size_.width = 0; if (this.sourceBlock_ && this.sourceBlock_.rendered) { this.sourceBlock_.render(); @@ -389,40 +430,6 @@ Blockly.Field.prototype.setText = function(text) { } }; -/** - * Update the text node of this field to display the current text. - * @private - */ -Blockly.Field.prototype.updateTextNode_ = function() { - if (!this.textElement_) { - // Not rendered yet. - return; - } - var text = this.text_; - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; - } - // Replace whitespace with non-breaking spaces so the text doesn't collapse. - text = text.replace(/\s/g, Blockly.Field.NBSP); - if (this.sourceBlock_.RTL && text) { - // The SVG is LTR, force text to be RTL. - text += '\u200F'; - } - if (!text) { - // Prevent the field from disappearing if empty. - text = Blockly.Field.NBSP; - } - - // Replace the text. - goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); - var textNode = document.createTextNode(text); - this.textElement_.appendChild(textNode); - - // Cached width is obsolete. Clear it. - this.size_.width = 0; -}; - /** * By default there is no difference between the human-readable text and * the language-neutral values. Subclasses (such as dropdown) may define this. diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 0f3568072..7d34d39ed 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -319,19 +319,11 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) { // Options are tuples of human-readable text and language-neutral values. if (options[i][1] == newValue) { var content = options[i][0]; - goog.dom.removeNode(this.imageElement_); if (typeof content == 'object') { this.imageJson_ = content; - this.imageElement_ = Blockly.createSvgElement('image', - {'y': 5, - 'height': content.height + 'px', - 'width': content.width + 'px'}, this.fieldGroup_); - this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', - 'xlink:href', content.src); this.setText(content.alt); } else { this.imageJson_ = null; - this.imageElement_ = null; this.setText(content); } return; @@ -343,64 +335,63 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) { }; /** - * Set the text in this field. Trigger a rerender of the source block. - * @param {?string} text New text. + * Draws the border with the correct width. + * @private */ -Blockly.FieldDropdown.prototype.setText = function(text) { +Blockly.FieldDropdown.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } if (this.sourceBlock_ && this.arrow_) { // Update arrow's colour. this.arrow_.style.fill = this.sourceBlock_.getColour(); } - if (text === null) { - // No change if null. - return; - } - this.text_ = text; - if (this.imageJson_) { - if (this.textElement_) { - this.textElement_.style.display = 'none'; - } - this.size_.height = Number(this.imageJson_.height) + 19; - this.render_(); - } else { - if (this.textElement_) { - this.textElement_.style.display = 'block'; - } - this.size_.height = Blockly.BlockSvg.MIN_BLOCK_Y; - this.updateTextNode_(); - } - // Unlike other editable fields, a dropdown can change height. - if (this.borderRect_) { - this.borderRect_.setAttribute('height', this.size_.height - 9); - } + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + goog.dom.removeNode(this.imageElement_); + this.imageElement_ = null; - if (this.textElement_) { + if (this.imageJson_) { + // Image option is selected. + this.imageElement_ = Blockly.createSvgElement('image', + {'y': 5, + 'height': this.imageJson_.height + 'px', + 'width': this.imageJson_.width + 'px'}, this.fieldGroup_); + this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', + 'xlink:href', this.imageJson_.src); + // Insert dropdown arrow. + this.textElement_.appendChild(this.arrow_); + var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); + this.size_.height = Number(this.imageJson_.height) + 19; + this.size_.width = Number(this.imageJson_.width) + arrowWidth; + if (this.sourceBlock_.RTL) { + this.imageElement_.setAttribute('x', arrowWidth); + this.textElement_.setAttribute('x', -1); + } else { + this.textElement_.setAttribute('text-anchor', 'end'); + this.textElement_.setAttribute('x', this.size_.width + 1); + } + + } else { + // Text option is selected. + // Replace the text. + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); // Insert dropdown arrow. if (this.sourceBlock_.RTL) { this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); } else { this.textElement_.appendChild(this.arrow_); } - } + this.textElement_.setAttribute('text-anchor', 'start'); + this.textElement_.setAttribute('x', 0); - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours_(); - } -}; - -/** - * Draws the border with the correct width. - * @private - */ -Blockly.FieldDropdown.prototype.render_ = function() { - if (!this.imageJson_) { - Blockly.FieldDropdown.superClass_.render_.call(this); - } else if (this.visible_ && this.borderRect_) { - this.size_.width = Number(this.imageJson_.width); - this.borderRect_.setAttribute('width', - this.size_.width + Blockly.BlockSvg.SEP_SPACE_X); + this.size_.height = Blockly.BlockSvg.MIN_BLOCK_Y; + this.size_.width = Blockly.Field.getCachedWidth(this.textElement_); } + this.borderRect_.setAttribute('height', this.size_.height - 9); + this.borderRect_.setAttribute('width', + this.size_.width + Blockly.BlockSvg.SEP_SPACE_X); }; /** diff --git a/core/field_label.js b/core/field_label.js index cb5fa7d39..8de97f26e 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -74,7 +74,7 @@ Blockly.FieldLabel.prototype.init = function() { this.textElement_.tooltip = this.sourceBlock_; Blockly.Tooltip.bindMouseEvents(this.textElement_); // Force a render. - this.updateTextNode_(); + this.render_(); }; /**