建立新的欄位類型之前,請考慮用其他方法來自訂欄位類型是否符合需求。如果應用程式需要儲存新的值類型,或者您想為現有值類型建立新 UI,您可能需要建立新的欄位類型。
如要建立新欄位,請按照下列步驟操作:
- 實作建構函式。
- 註冊 JSON 金鑰並實作
fromJson
。 - 處理區塊 UI 和事件監聽器的初始化作業。
- 處理事件監聽器的棄置作業 (系統會自動處理 UI 的處置)。
- 導入價值處理方式。
- 以文字呈現欄位值,以便提供無障礙功能。
- 新增其他功能,例如:
- 設定欄位的其他層面,例如:
本節假設您已閱讀且熟悉欄位剖析中的內容。
如需自訂欄位的範例,請參閱自訂欄位示範。
實作建構函式
欄位的建構函式負責設定該欄位的初始值,並視需要設定本機驗證工具。無論來源區塊是在 JSON 或 JavaScript 中定義,系統會在來源區塊初始化期間呼叫自訂欄位的建構函式。因此在建構期間,自訂欄位無法存取來源區塊。
下列程式碼範例會建立名為 GenericField
的自訂欄位:
class GenericField extends Blockly.Field {
constructor(value, validator) {
super(value, validator);
this.SERIALIZABLE = true;
}
}
方法簽章
欄位建構函式通常會採用值和本機驗證工具。這個值是選用值,如果您未傳遞任何值 (或傳遞未通過類別驗證的值),系統就會使用父類別的預設值。如果是預設的 Field
類別,這個值為 null
。如果您不想要使用該預設值,請務必傳送適當的值。驗證工具參數僅適用於可編輯的欄位,且通常標示為選用項目。如要進一步瞭解驗證工具,請參閱驗證工具文件。
結構
建構函式中的邏輯應遵循以下流程:
- 呼叫繼承的超級建構函式 (所有自訂欄位應繼承
Blockly.Field
或其中一個子類別),以正確初始化值,並為欄位設定本機驗證工具。 - 如果您的欄位可序列化,請在建構函式中設定對應的屬性。可編輯欄位必須能夠序列化,且欄位預設為可編輯,因此,除非您知道這個屬性,否則建議您將這項屬性設為 true。
- 選用:套用其他自訂項目 (例如標籤欄位可讓您傳遞 css 類別,然後再套用至文字)。
JSON 和註冊
在 JSON 區塊定義中,欄位是由字串 (例如 field_number
、field_textinput
) 來描述。阻擋從這些字串到欄位物件的對應,並在建構期間針對適當的物件呼叫 fromJson
。
呼叫 Blockly.fieldRegistry.register
即可將欄位類型新增至此地圖,並傳入欄位類別做為第二個引數:
Blockly.fieldRegistry.register('field_generic', GenericField);
您也需要定義 fromJson
函式。您的實作方式首先應使用 replaceMessageReferences 取消參照任何字串資料表參照,然後將值傳遞至建構函式。
GenericField.fromJson = function(options) {
const value = Blockly.utils.parsing.replaceMessageReferences(
options['value']);
return new CustomFields.GenericField(value);
};
初始化中
建構欄位時,基本上只會包含一個值。初始化是指建構 DOM 的位置、建構模型 (如果該欄位有模型),且事件會繫結。
遮蔽式螢幕
在初始化期間,您需為欄位的區塊顯示建立所需的任何內容。
預設值、背景和文字
預設的 initView
函式會建立淺色的 rect
元素和 text
元素。如果您想讓欄位同時包含上述兩者,以及一些額外商品,請先呼叫父類別 initView
函式,再新增其餘的 DOM 元素。如果您希望欄位中只有一個元素,但不能同時包含兩者,可以使用 createBorderRect_
或 createTextElement_
函式。
自訂 DOM 建構
如果您的欄位是一般文字欄位 (例如「文字輸入」),系統會為您處理 DoM 建構作業。否則,您必須覆寫 initView
函式,建立日後轉譯欄位時所需的 DOM 元素。
例如,下拉式欄位可能包含圖片和文字。在 initView
中,會建立單一圖片元素和單一文字元素。接著,在 render_
期間,該元素會根據所選選項的類型顯示另一個元素,並隱藏另一個元素。
您可以使用 Blockly.utils.dom.createSvgElement
方法或傳統的 DOM 建立方法來建立 DOM 元素。
欄位的這類顯示必須符合下列規定:
- 所有 DOM 元素都必須是該欄位
fieldGroup_
的子項。系統會自動建立欄位群組。 - 所有 DOM 元素都必須維持在欄位回報的尺寸內。
如要進一步瞭解如何自訂及更新區塊顯示螢幕,請參閱「轉譯」一節。
新增文字符號
如果要在欄位的文字中加入符號 (例如角度欄位的度數符號),可以將符號元素 (通常包含在 <tspan>
中) 直接附加至欄位的 textElement_
。
輸入事件
根據預設,欄位會登錄工具提示事件和 mousedown 事件 (用來顯示編輯器)。如果您想監聽其他種類的事件 (例如想要處理欄位的拖曳動作),則應覆寫欄位的 bindEvents_
函式。
bindEvents_() {
// Call the superclass function to preserve the default behavior as well.
super.bindEvents_();
// Then register your own additional event listeners.
this.mouseDownWrapper_ =
Blockly.browserEvents.conditionalBind(this.getClickTarget_(), 'mousedown', this,
function(event) {
this.originalMouseX_ = event.clientX;
this.isMouseDown_ = true;
this.originalValue_ = this.getValue();
event.stopPropagation();
}
);
this.mouseMoveWrapper_ =
Blockly.browserEvents.conditionalBind(document, 'mousemove', this,
function(event) {
if (!this.isMouseDown_) {
return;
}
var delta = event.clientX - this.originalMouseX_;
this.setValue(this.originalValue_ + delta);
}
);
this.mouseUpWrapper_ =
Blockly.browserEvents.conditionalBind(document, 'mouseup', this,
function(_event) {
this.isMouseDown_ = false;
}
);
}
如要繫結至事件,一般應使用 Blockly.utils.browserEvents.conditionalBind
函式。這種繫結事件的方法會過濾拖曳期間的次要觸控動作。如果您希望處理常式在進行中拖曳期間也能執行,可以使用 Blockly.browserEvents.bind
函式。
棄置
如果您在欄位的 bindEvents_
函式中註冊了任何自訂事件監聽器,就必須在 dispose
函式中取消註冊這些事件監聽器。
如果您已正確初始化欄位的檢視畫面 (將所有 DOM 元素附加至 fieldGroup_
),系統就會自動處置欄位的 DOM。
價值處理
→ 如需欄位值和文字的相關資訊,請參閱欄位剖析。
驗證順序
實作類別驗證工具
欄位只能接受特定的值。例如,數字欄位應該只能包含數字,顏色欄位應該僅接受顏色等。這可以透過類別和本機驗證工具來確保。類別驗證工具遵循與本機驗證工具相同的規則,但同樣在建構函式中執行,因此不應參照來源區塊。
如要實作欄位的類別驗證工具,請覆寫 doClassValidation_
函式。
doClassValidation_(newValue) {
if (typeof newValue != 'string') {
return null;
}
return newValue;
};
處理有效值
如果傳遞至 setValue
欄位的值有效,您會收到 doValueUpdate_
回呼。根據預設,doValueUpdate_
函式:
- 將
value_
屬性設為newValue
。 - 將
isDirty_
屬性設為true
。
如果您只想儲存這個值,且不想執行任何自訂處理作業,不需要覆寫 doValueUpdate_
。
或者,如果您想:
newValue
的自訂儲存空間。- 根據
newValue
變更其他屬性。 - 儲存目前的值是否有效。
您必須覆寫 doValueUpdate_
:
doValueUpdate_(newValue) {
super.doValueUpdate_(newValue);
this.displayValue_ = newValue;
this.isValueValid_ = true;
}
處理無效值
如果傳遞至 setValue
欄位的值無效,您會收到 doValueInvalid_
回呼。根據預設,doValueInvalid_
函式不會執行任何動作。也就是說,系統不會預設顯示無效的值。此外,由於不會設定 isDirty_
屬性,因此系統不會重新轉譯欄位。
如果想顯示無效值,建議您覆寫 doValueInvalid_
。在多數情況下,您都應該將 displayValue_
屬性設為無效值,請將 isDirty_
設為 true
,並覆寫 render_,讓可區塊顯示螢幕根據 displayValue_
(而非 value_
) 更新。
doValueInvalid_(newValue) {
this.displayValue_ = newValue;
this.isDirty_ = true;
this.isValueValid_ = false;
}
多部分值
當欄位包含多部分值 (例如清單、向量、物件) 時,您可能會希望這些部分的處理方式是個別的值。
doClassValidation_(newValue) {
if (FieldTurtle.PATTERNS.indexOf(newValue.pattern) == -1) {
newValue.pattern = null;
}
if (FieldTurtle.HATS.indexOf(newValue.hat) == -1) {
newValue.hat = null;
}
if (FieldTurtle.NAMES.indexOf(newValue.turtleName) == -1) {
newValue.turtleName = null;
}
if (!newValue.pattern || !newValue.hat || !newValue.turtleName) {
this.cachedValidatedValue_ = newValue;
return null;
}
return newValue;
}
在上述範例中,newValue
的每個屬性會分別通過驗證。然後在 doClassValidation_
函式結尾,如果有任何個別屬性無效,該值會在傳回 null
(無效) 之前快取至 cacheValidatedValue_
屬性。使用個別驗證的屬性快取物件,可讓 doValueInvalid_
函式單獨處理這些物件,只需執行 !this.cacheValidatedValue_.property
檢查即可,而非個別重新驗證每項屬性。
這種驗證多部分值的模式也可用於本機驗證工具,但目前無法強制執行此模式。
isDirty_
isDirty_
是 setValue
函式和欄位其他部分使用的旗標,用於判斷該欄位是否需要重新轉譯。如果欄位的顯示值已變更,isDirty_
通常應設為 true
。
文字
→ 有關欄位文字使用位置以及欄位值與欄位差異的資訊,請參閱欄位剖析。
如果欄位文字與欄位值不同,則應覆寫 getText
函式來提供正確的文字。
getText() {
let text = this.value_.turtleName + ' wearing a ' + this.value_.hat;
if (this.value_.hat == 'Stovepipe' || this.value_.hat == 'Propeller') {
text += ' hat';
}
return text;
}
建立編輯器
如果您定義 showEditor_
函式,Blockly 會自動監聽點擊,並在適當時機呼叫 showEditor_
。您可以將任何 HTML 包裝在兩個特殊 div 之中 (稱為 DropDownDiv 和 WidgetDiv),以便在 Blockly 的其餘部分上方顯示。
DropDownDiv 和 WidgetDiv
DropDownDiv
是用來提供編輯者,位於與欄位連結的方塊內。系統會自動將本身定位在欄位附近,同時保持在可見的邊界內。角度挑選器和顏色挑選器都是 DropDownDiv
的絕佳範例。
WidgetDiv
是用來提供未隨附於盒子內的編輯者。數字欄位會使用 WidgetDiv 來涵蓋包含 HTML 文字輸入框的欄位。雖然 DropDownDiv 會為您處理位置,但 WidgetDiv 不會。您需要手動調整元素的位置。座標系統與視窗左上角的相對像素座標。文字輸入編輯器是 WidgetDiv
的絕佳範例。
DropDownDiv 程式碼範例
showEditor_() {
// Create the widget HTML
this.editor_ = this.dropdownCreate_();
Blockly.DropDownDiv.getContentDiv().appendChild(this.editor_);
// Set the dropdown's background colour.
// This can be used to make it match the colour of the field.
Blockly.DropDownDiv.setColour('white', 'silver');
// Show it next to the field. Always pass a dispose function.
Blockly.DropDownDiv.showPositionedByField(
this, this.disposeWidget_.bind(this));
}
WidgetDiv 程式碼範例
showEditor_() {
// Show the div. This automatically closes the dropdown if it is open.
// Always pass a dispose function.
Blockly.WidgetDiv.show(
this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
// Create the widget HTML.
var widget = this.createWidget_();
Blockly.WidgetDiv.getDiv().appendChild(widget);
}
清除所用資源
DropDownDiv 和 WidgetDiv 處理常式會刪除小工具 HTML 元素,但您必須手動處理已套用至這些元素的任何事件監聽器。
widgetDispose_() {
for (let i = this.editorListeners_.length, listener;
listener = this.editorListeners_[i]; i--) {
Blockly.browserEvents.unbind(listener);
this.editorListeners_.pop();
}
}
系統會在 DropDownDiv
的 null
結構定義中呼叫 dispose
函式。在 WidgetDiv
上,系統會在 WidgetDiv
的結構定義中呼叫該函式。無論是哪一種情況,最好在傳遞丟棄函式時使用 bind 函式,如上述 DropDownDiv
和 WidgetDiv
範例所示。
→ 如需不專門用於棄置編輯者的相關資訊,請參閱「處理」。
更新封鎖的螢幕
render_
函式可用來更新欄位的區塊顯示畫面,以符合內部值。
常見的例子包括:
- 變更文字 (下拉式選單)
- 變更顏色 (顏色)
預設值
預設的 render_
函式會將顯示文字設為 getDisplayText_
函式的結果。getDisplayText_
函式為了遵循文字長度上限而截斷後,會將欄位的 value_
屬性轉換為字串。
如果您使用預設的區塊顯示螢幕,且預設文字行為適用於欄位,就不需要覆寫 render_
。
如果欄位的預設文字行為適用於欄位,但欄位的區塊顯示螢幕含有其他靜態元素,您可以呼叫預設的 render_
函式,但仍須覆寫該函式來更新欄位大小。
如果預設文字行為不適用於您的欄位,或是欄位的區塊顯示顯示含有其他動態元素,您需要自訂 render_
函式。
自訂轉譯
如果預設轉譯行為不適用於您的欄位,您需要定義自訂顯示行為。這包括設定自訂顯示文字、變更圖片元素,或是更新背景顏色等。
所有 DOM 屬性變更均屬法律規範,唯一需要記住的兩件事:
render_() {
switch(this.value_.hat) {
case 'Stovepipe':
this.stovepipe_.style.display = '';
break;
case 'Crown':
this.crown_.style.display = '';
break;
case 'Mask':
this.mask_.style.display = '';
break;
case 'Propeller':
this.propeller_.style.display = '';
break;
case 'Fedora':
this.fedora_.style.display = '';
break;
}
switch(this.value_.pattern) {
case 'Dots':
this.shellPattern_.setAttribute('fill', 'url(#polkadots)');
break;
case 'Stripes':
this.shellPattern_.setAttribute('fill', 'url(#stripes)');
break;
case 'Hexagons':
this.shellPattern_.setAttribute('fill', 'url(#hexagons)');
break;
}
this.textContent_.nodeValue = this.value_.turtleName;
this.updateSize_();
}
正在更新大小
更新欄位的 size_
屬性非常重要,因為這會告知區塊轉譯程式碼如何定位欄位。想要確實找出 size_
應該,最好的方法就是進行實驗。
updateSize_() {
const bbox = this.movableGroup_.getBBox();
let width = bbox.width;
let height = bbox.height;
if (this.borderRect_) {
width += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
height += this.constants_.FIELD_BORDER_RECT_X_PADDING * 2;
this.borderRect_.setAttribute('width', width);
this.borderRect_.setAttribute('height', height);
}
// Note how both the width and the height can be dynamic.
this.size_.width = width;
this.size_.height = height;
}
比對區塊顏色
如果您希望欄位元素與所連結的區塊色彩相符,則應覆寫 applyColour
方法。建議您透過區塊的樣式屬性存取顏色。
applyColour() {
const sourceBlock = this.sourceBlock_;
if (sourceBlock.isShadow()) {
this.arrow_.style.fill = sourceBlock.style.colourSecondary;
} else {
this.arrow_.style.fill = sourceBlock.style.colourPrimary;
}
}
正在更新編輯權限
updateEditable
函式可根據是否可編輯,變更欄位的顯示方式。預設函式會使其在無法編輯時,讓背景提供/沒有懸停回應 (邊框)。區塊顯示畫面不應視其編輯能力變更大小,但可以允許所有其他變更。
updateEditable() {
if (!this.fieldGroup_) {
// Not initialized yet.
return;
}
super.updateEditable();
const group = this.getClickTarget_();
if (!this.isCurrentlyEditable()) {
group.style.cursor = 'not-allowed';
} else {
group.style.cursor = this.CURSOR;
}
}
序列化
序列化是指儲存欄位的狀態,以便之後重新載入至工作區。
工作區的狀態一律會包含欄位值,但也可以包含其他狀態,例如欄位 UI 的狀態。舉例來說,如果您的欄位是可讓使用者選取國家/地區的可縮放地圖,您也可以序列化縮放等級。
如果您的欄位可序列化,您必須將 SERIALIZABLE
屬性設為 true
。
為欄位提供兩組序列化掛鉤。一組掛鉤可與新的 JSON 序列化系統搭配使用,另一個掛鉤則能與舊 XML 序列化系統搭配運作。
saveState
和loadState
saveState
和 loadState
是可與新的 JSON 序列化系統搭配使用的序列化掛鉤。
在某些情況下,預設實作會正常運作,因此您不需要提供這些資訊。如果 (1) 您的欄位是基礎 Blockly.Field
類別的直接子類別,(2) 您的值為 JSON 可序列化類型,且 (3) 您只需要將值序列化,預設實作即可正常運作!
否則,您的 saveState
函式應傳回代表欄位狀態的 JSON 序列化物件/值。您的 loadState
函式應接受相同的 JSON 序列化物件/值,並將其套用至欄位。
saveState() {
return {
'country': this.getValue(), // Value state
'zoom': this.getZoomLevel(), // UI state
};
}
loadState(state) {
this.setValue(state['country']);
this.setZoomLevel(state['zoom']);
}
完整序列化與備份資料
saveState
也會收到選用參數 doFullSerialization
。這適用於通常會參照不同序列化程式 (例如備份資料模型) 序列化狀態的欄位。參數表示在區塊反序列化時將無法使用參照的狀態,因此該欄位應執行所有序列化作業。例如,當個別區塊序列化或複製區塊時,情況都是如此。
有兩種常見用途:
- 如果將個別區塊載入的工作區不存在備份資料模型,則該欄位本身狀態的資訊就足以建立新的資料模型。
- 複製區塊時,欄位一律會建立新的備份資料模型,而非參照現有模型。
系統會使用一個內建變數欄位。通常,它會序列化其參照的變數 ID,但如果 doFullSerialization
為 true,則會序列化其所有狀態。
saveState(doFullSerialization) {
const state = {'id': this.variable_.getId()};
if (doFullSerialization) {
state['name'] = this.variable_.name;
state['type'] = this.variable_.type;
}
return state;
}
loadState(state) {
const variable = Blockly.Variables.getOrCreateVariablePackage(
this.getSourceBlock().workspace,
state['id'],
state['name'], // May not exist.
state['type']); // May not exist.
this.setValue(variable.getId());
}
變數欄位會執行此動作,以確保將變數載入至不含變數的工作區時,能夠建立可以參照的新變數。
toXml
和fromXml
toXml
和 fromXml
是適用於舊版 XML 序列化系統的序列化掛鉤。只有在您必須 (例如正在使用尚未遷移的舊程式碼集) 時才使用這些掛鉤,否則請使用 saveState
和 loadState
。
您的 toXml
函式應傳回代表欄位狀態的 XML 節點。您的 fromXml
函式應接受相同的 XML 節點,並套用至欄位。
toXml(fieldElement) {
fieldElement.textContent = this.getValue();
fieldElement.setAttribute('zoom', this.getZoomLevel());
return fieldElement;
}
fromXml(fieldElement) {
this.setValue(fieldElement.textContent);
this.setZoomLevel(fieldElement.getAttribute('zoom'));
}
可編輯和可序列化的屬性
EDITABLE
屬性會決定該欄位是否應有 UI,以表明可與其互動。預設為 true
。
SERIALIZABLE
屬性會決定該欄位是否應序列化。預設為 false
。如果這個屬性為 true
,您可能需要提供序列化和去序列化函式 (請參閱「序列化」)。
自訂遊標
CURSOR
屬性會決定使用者將遊標懸停在欄位上時會看到的遊標。應為有效的 CSS 遊標字串。預設值為 .blocklyDraggable
定義的遊標,也就是擷取遊標。