建立新欄位類型前,請先考量其他方法是否能滿足您自訂欄位的需求。如果應用程式需要儲存新的值類型,或是您想要為現有值類型建立新的 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
) 說明。Blockly 會維護從這些字串到欄位物件的對應,並在建構期間對適當物件呼叫 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 建構
如果欄位是一般文字欄位 (例如 Text Input),系統會為您處理 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_
函式結束時,如果任何個別屬性無效,系統會將該值快取到 cacheValidatedValue_
屬性,然後傳回 null
(無效)。將具有個別驗證屬性的物件快取後,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,方法是將 HTML 包裝在兩個特殊 div 之一 (稱為 DropDownDiv 和 WidgetDiv),這些 div 會浮動在 Blockly 的其他 UI 上方。
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();
}
}
dispose
函式會在 DropDownDiv
的 null
情境中呼叫。在 WidgetDiv
上,系統會在 WidgetDiv
的結構定義中呼叫該函式。無論是哪種情況,傳遞 dispose 函式時,最好使用 bind 函式,如上述 DropDownDiv
和 WidgetDiv
範例所示。
→ 如要瞭解如何處置非編輯器的項目,請參閱「處置」一文。
更新區塊內顯示內容
render_
函式可用於更新欄位在區塊上的顯示內容,以符合內部值。
常見範例包括:
- 變更文字 (下拉式選單)
- 變更顏色 (color)
預設值
預設的 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
。
Blockly 為欄位提供兩組序列化鉤子。一組鉤子可搭配新的 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
。這項屬性會由通常參照由不同序列化器 (例如支援資料模型) 序列化的狀態的欄位使用。這個參數會指出在區塊經過反序列化時,參照的狀態將無法使用,因此欄位應自行執行所有序列化作業。舉例來說,當個別區塊序列化或區塊複製貼上時,就會是 true。
這項功能有兩種常見用途:
- 當個別區塊載入至不含基礎資料模型的工作區時,欄位會在其自身狀態下提供足夠的資訊,以便建立新的資料模型。
- 當您複製及貼上區塊時,欄位一律會建立新的備援資料模型,而非參照現有資料模型。
內建變數欄位就是使用這個欄位的一個欄位。通常會將參照的變數 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
定義的游標,也就是抓取游標。