建立新欄位類型前,請先考慮其他欄位自訂方法是否符合需求。如果應用程式需要儲存新的值類型,或是您想為現有的值類型建立新的 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、模型 (如果欄位有模型) 和繫結事件。
On-Block 顯示
在初始化期間,您有責任建立欄位在區塊顯示時所需的一切項目。
預設值、背景和文字
預設的 initView
函式會建立淺色的 rect
元素和 text
元素。如要讓欄位同時具備這兩項功能,以及一些額外好處,請在新增其餘 DOM 元素之前,呼叫超類別 initView
函式。如果欄位只需要其中一個元素,可以使用 createBorderRect_
或 createTextElement_
函式。
自訂 DOM 建構
如果您的欄位是通用文字欄位 (例如「Text Input」),系統會為您處理 DOM 建構作業。否則,您必須覆寫 initView
函式,才能建立日後算繪欄位時所需的 DOM 元素。
舉例來說,下拉式欄位可能同時包含圖片和文字。在 initView
中,這會建立單一圖片元素和單一文字元素。然後在 render_
期間,系統會根據所選選項的類型顯示有效元素,並隱藏其他元素。
建立 DOM 元素時,可以使用 Blockly.utils.dom.createSvgElement
方法,也可以使用傳統的 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
,並覆寫 on-block 顯示的 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();
}
}
系統會在 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
。
Blockly 提供兩組欄位序列化掛鉤。其中一組 Hook 適用於新的 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 序列化系統。請只在必要時使用這些 Hook (例如您正在處理尚未遷移的舊版程式碼集),否則請使用 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
,您可能需要提供序列化和還原序列化函式 (請參閱「序列化」)。
使用 CSS 自訂
您可以使用 CSS 自訂欄位。在 initView
方法中,將自訂類別新增至欄位的 fieldGroup_
,然後在 CSS 中參照這個類別。
舉例來說,如要使用其他游標:
initView() {
...
// Add a custom CSS class.
if (this.fieldGroup_) {
Blockly.utils.dom.addClass(this.fieldGroup_, 'myCustomField');
}
}
.myCustomField {
cursor: cell;
}
自訂游標
根據預設,擴充 FieldInput
的類別會在使用者將游標懸停在欄位上時使用 text
游標,拖曳的欄位會使用 grabbing
游標,所有其他欄位則會使用 default
游標。如要使用其他游標,請使用 CSS 設定。