本文主要是對(duì)handsontable官網(wǎng)中cell editor的簡(jiǎn)單翻譯搓谆,僅供參考激涤。
原文地址:https://handsontable.com/docs/javascript-data-grid/cell-editor/
editor簡(jiǎn)述
cell editor指的是單元格編輯器函數(shù)硬猫。handsontable將展示單元格的值的過程與改變單元格的值的過程分開丝里,Renderers(渲染器)主要責(zé)任是呈現(xiàn)數(shù)據(jù)吩蔑,Editor(編輯器)主要是修改數(shù)據(jù)替裆。因?yàn)镽enderers只有一個(gè)簡(jiǎn)單的任務(wù):獲取單元格的實(shí)際值并以HTML代碼的形式返回它的表示校辩,所以它們可以是一個(gè)函數(shù)。然而辆童,Editor需要做這些工作:處理用戶的輸入(鼠標(biāo)宜咒、鍵盤事件),驗(yàn)證數(shù)據(jù)把鉴,并且根據(jù)驗(yàn)證結(jié)果進(jìn)行不同的展示故黑,如果將這些功能都放到同一個(gè)函數(shù)中顯然不是很合理,所以在handsontable中編輯器是通過編輯器類(editor classes)來描述庭砍。
EditorManager
EditorManager是一個(gè)負(fù)責(zé)處理Handsontable中可用的所有編輯器的類场晶。Handsontable通過EditorManager對(duì)象與編輯器交互。在第一次調(diào)用Handsontable()構(gòu)造函數(shù)之后會(huì)運(yùn)行init()方法怠缸,并在這個(gè)方法中實(shí)例化EditorManager對(duì)象诗轻。EditorManager對(duì)象的引用在Handsontable實(shí)例中是私有的,不能直接訪問它揭北。但是扳炬,有一些方法可以更改EditorManager的默認(rèn)行為,稍后會(huì)詳細(xì)介紹搔体。
EditorManager的任務(wù)
EditorManager主要有以下四個(gè)任務(wù):
- 為活動(dòng)單元格選擇合適的編輯器
- 準(zhǔn)備編輯器
- 展示編輯器(基于用戶行為)
- 關(guān)閉編輯器(基于用戶行為)
以下恨樟,將詳細(xì)解釋每個(gè)任務(wù)。
為活動(dòng)單元格選擇合適的編輯器
首先解釋活動(dòng)單元格嫉柴,當(dāng)用戶選擇一個(gè)單元格時(shí)厌杜,當(dāng)前單元格即為活動(dòng)單元格(此時(shí)暫不討論多選的情況奉呛,因?yàn)槲視簳r(shí)也不清楚计螺,以免誤導(dǎo)讀者),EditorManager查找分配給該單元格的編輯器類瞧壮,檢查編輯器配置選項(xiàng)的值登馒。可以全局(為表中的所有單元格)咆槽、或者每列(為列中的所有單元格)陈轿,或單獨(dú)為每個(gè)單元格定義編輯器配置選項(xiàng)。具體配置詳情參考(https://handsontable.com/docs/javascript-data-grid/configuration-options/#cascading-configuration)。編輯器配置選項(xiàng)的值可以是指定字符串(代表某個(gè)特定編輯器的字符串麦射,例如:'text', 'checkbox', 'autocomplete'……)蛾娶,也可以是一個(gè)編輯器類。通過這個(gè)值潜秋,EditorManager將獲得一個(gè)被指定的編輯器類的實(shí)例蛔琅,注意,每個(gè)編輯器類對(duì)象都是單個(gè)表中的一個(gè)單例峻呛,這意味著每個(gè)表只調(diào)用一次它的構(gòu)造函數(shù)罗售。如果一個(gè)頁(yè)面上有3個(gè)表,每個(gè)表都有自己的編輯器類實(shí)例钩述。
準(zhǔn)備編輯器
當(dāng)EditorManager獲取到編輯器類實(shí)例(編輯器對(duì)象)后寨躁,會(huì)調(diào)用編輯器類實(shí)例的prepare()方法。prepare()方法設(shè)置與所選單元格相關(guān)的編輯器對(duì)象屬性牙勘,但不顯示編輯器职恳。每次用戶選擇一個(gè)單元格時(shí)都會(huì)調(diào)用prepare()。在某些情況下方面,可以對(duì)同一個(gè)單元格多次調(diào)用它话肖,而不更改選擇。
展示編輯器
prepare()執(zhí)行完之后葡幸,EditorManager等待觸發(fā)單元格編輯的用戶事件最筒。相關(guān)的用戶事件如下:
- 按下enter
- 按下shift + enter
- 雙擊單元格
- 按下 f2
以上任意事件被觸發(fā),EditorManager就會(huì)調(diào)用編輯器類實(shí)例的beginEditing()方法去展示編輯器蔚叨。
關(guān)閉編輯器
打開編輯器時(shí)床蜘,EditorManager等待應(yīng)該結(jié)束單元格編輯的用戶事件被觸發(fā)。相關(guān)的用戶事件如下:
- 點(diǎn)擊另一個(gè)單元格(保存更改)
- 按下enter(保存更改蔑水,并移動(dòng)至下一個(gè)單元格)
- 按下enter + shift (保存更改邢锯,并移動(dòng)至上一個(gè)單元格)
- 按下ctrl + enter或者alt + enter(在單元格內(nèi)新增一行)
- 按下esc(放棄更改)
- 按下tab(保存更改,并向右或者向左移動(dòng)(取決于表格布局))
- 按下shift + tab(保存更改搀别,并向左或者向右移動(dòng)(取決于表格布局))
- 按下Page Up或Page Down(保存更改丹擎,并向上或下移動(dòng)一屏)
如果觸發(fā)了這些事件中的任何一個(gè),EditorManager將會(huì)調(diào)用編輯器的finishiting()方法歇父,該方法應(yīng)該嘗試保存更改(除非按下了ESC鍵)并關(guān)閉編輯器蒂培。
覆蓋EditorManager的默認(rèn)行為
有時(shí)候我們可能希望更改導(dǎo)致編輯器打開或關(guān)閉的默認(rèn)事件。例如榜苫,默認(rèn)情況下护戳,編輯器可能使用向上和向下箭頭事件來執(zhí)行一些操作(例如增加或減少單元格值),并且我們不希望在用戶按下這些鍵時(shí)EditorManager關(guān)閉編輯器垂睬。這時(shí)候就可以通過beforeKeyDown鉤子來處理這種情況媳荒,因?yàn)镋ditorManager在處理用戶事件之前會(huì)運(yùn)行beforeKeyDown鉤子抗悍。如果為beforeKeyDown注冊(cè)了一個(gè)監(jiān)聽器,那么對(duì)事件對(duì)象EditorManager的stopImmediatePropagation()調(diào)用將執(zhí)行其默認(rèn)操作(這句翻譯感覺不是很準(zhǔn)確钳枕,暫時(shí)參考有道詞典)缴渊。更多信息參考下文。
BaseEditor
Handsontable.editors.BaseEditor是一個(gè)抽象類鱼炒,所有編輯器類都應(yīng)該繼承于它疟暖。它實(shí)現(xiàn)了一些基本的編輯器方法,并聲明了一些應(yīng)該由每個(gè)編輯器類實(shí)現(xiàn)的方法田柔。
公共方法
公共方法是由BaseEditor類實(shí)現(xiàn)的方法俐巴。它們包含了每個(gè)編輯器都應(yīng)該具備的一些核心邏輯。大多數(shù)時(shí)候硬爆,我們應(yīng)該盡量避免使用這些方法欣舵。但有的時(shí)候我們可能會(huì)希望重寫部分常用方法,來創(chuàng)建一些比較復(fù)雜的編輯器缀磕,在這種情況下缘圈,您應(yīng)該始終調(diào)用原始方法,然后再執(zhí)行特定于您的編輯器的其他操作袜蚕。
實(shí)例:
// CustomEditor 是一個(gè)類, 繼承自 BaseEditor
class CustomEditor extends BaseEditor {
prepare(row, col, prop, td, originalValue, cellProperties) {
// 執(zhí)行原始方法...
super.prepare(row, col, prop, td, originalValue, cellProperties);
// ...然后做一些特定于你的CustomEditor的東西
this.customEditorSpecificProperty = 'foo';
}
}
有下方這七個(gè)公共方法:
-
prepare(row: Number, col: Number, prop: Number|String, td: HTMLTableCellElement, originalValue: Mixed, cellProperties: Object):undefined
為給定單元格準(zhǔn)備要顯示的編輯器糟把。設(shè)置大多數(shù)實(shí)例屬性。返回undefined -
beginEditing(newInitialValue: Mixed, event: Mixed):undefined
設(shè)置編輯器值為newInitialValue牲剃。如果newInitialValue未定義遣疯,則編輯器值將設(shè)置為原始單元格值。內(nèi)部調(diào)用open()方法凿傅。返回undefined -
finishEditing(restoreOriginalValue: 'Boolean' [optional], ctrlDown: Boolean [optional], callback: Function)
嘗試結(jié)束單元格編輯缠犀。內(nèi)部調(diào)用saveValue()和discardEditor()。如果restoreOriginalValue設(shè)置為true聪舒,則將單元格值設(shè)置為其原始值(編輯之前的值)辨液。ctrlDown值作為第二個(gè)參數(shù)傳遞給saveValue()。
callback包含一個(gè)布爾參數(shù)箱残,這個(gè)布爾參數(shù)的值取決于新的值是否有效滔迈,或者allowInvalid配置選項(xiàng)被設(shè)置為true,否則參數(shù)為false -
discardEditor(result: Boolean):undefined
單元格驗(yàn)證結(jié)束時(shí)調(diào)用被辑。如果新值保存成功(result為true或allowInvalid屬性為true)燎悍,則調(diào)用close()方法,否則調(diào)用focus()方法并保持編輯器打開敷待。 -
saveValue(value: Mixed, ctrlDown: Boolean):undefined
嘗試將value保存為單元格的新值间涵。在內(nèi)部執(zhí)行驗(yàn)證仁热。如果ctrlDown設(shè)置為true榜揖,則新值將設(shè)置為所有選定的單元格勾哩。 -
isOpened():Boolean
如果編輯器打開則返回true,如果編輯器關(guān)閉則返回false举哟。在調(diào)用open()之后思劳,編輯器被認(rèn)為是打開的。在調(diào)用close()方法后妨猩,編輯器被認(rèn)為是關(guān)閉的潜叛。 -
extend():Function
返回Function—從當(dāng)前類繼承的類函數(shù)。返回類的原型方法可以被安全地覆蓋壶硅,而不會(huì)有改變父類原型的危險(xiǎn)威兜。(這是一個(gè)與單元格編輯過程無關(guān)的實(shí)用方法。)
實(shí)例1:從BaseEditor繼承并重寫它的方法
const CustomEditor = Handsontable.editors.BaseEditor.prototype.extend();
// 這不會(huì)更改 BaseEditor.prototype.beginEditing()方法
CustomEditor.prototype.beginEditing = function() {};
實(shí)例2:從其他editor類繼承
const CustomTextEditor = Handsontable.editors.TextEditor.prototype.extend();
// CustomTextEditor可以使用所有TextEditor中實(shí)現(xiàn)的方法庐椒。
// 你可以安全地覆蓋任何方法而不影響原始的TextEditor椒舵。
Editor特定的方法
特定于編輯器的方法是在BaseEditor中沒有實(shí)現(xiàn)的方法。每個(gè)編輯器類都必須實(shí)現(xiàn)這些方法约谈。
-
init()
init()方法在創(chuàng)建編輯器類的新實(shí)例時(shí)調(diào)用笔宿。每個(gè)表實(shí)例只會(huì)調(diào)用一次,因?yàn)樗芯庉嬈鞫荚诒韺?shí)例中作為單例使用棱诱。init()方法主要用來創(chuàng)建展示界面(翻譯可能有誤)泼橘。其結(jié)果可以在編輯器的生命周期中重用。最常見的操作是創(chuàng)建編輯器的HTML結(jié)構(gòu)迈勋。init()沒有返回值炬灭。 -
getValue()
getValue()方法應(yīng)返回當(dāng)前編輯器值,即應(yīng)保存為單元格新的值靡菇。 -
setValue(newValue: Mixed)
setValue()方法應(yīng)將編輯器值設(shè)置為newValue担败。
示例:實(shí)現(xiàn)一個(gè)DateEditor,它通過顯示日歷來幫助選擇日期镰官。getValue()和setValue()方法可以這樣工作:
class CalendarEditor extends TextEditor {
constructor(hotInstance) {
super(hotInstance);
}
getValue() {
// returns currently selected date, for example "2023/09/15"
return calendar.getDate();
}
setValue(newValue) {
// highlights given date on calendar
calendar.highlightDate(newValue);
}
}
-
open()
顯示編輯器提前,不需要返回任何值。示例:
class CustomEditor extends TextEditor {
open() {
this.editorDiv.style.display = '';
}
}
-
close()
在更改單元格值后隱藏編輯器泳唠,不需要返回任何值狈网。示例:
class CustomEditor extends TextEditor {
close() {
this.editorDiv.style.display = 'none';
}
}
-
focus()
聚焦編輯器,不需要返回任何值笨腥。當(dāng)用戶想要通過選擇另一個(gè)單元格來關(guān)閉編輯器拓哺,并且編輯器中的值不生效(當(dāng)allowInvalid為false)時(shí),調(diào)用此方法脖母。示例:
class CustomEditor extends TextEditor {
focus() {
this.editorInput.focus();
}
}
編輯器共有屬性
下面提到的所有屬性都可以通過this在編輯器實(shí)例中使用士鸥,例:this.instance。并且每次調(diào)用prepare()方法時(shí)都會(huì)更新谆级。
屬性 | 類型 | 描述 |
---|---|---|
instance | Handsontable.Core | 此編輯器對(duì)象所屬的Handsontable實(shí)例烤礁。在類構(gòu)造函數(shù)中設(shè)置讼积,在編輯器的整個(gè)生命周期中不可變。 |
row | Number | 活動(dòng)單元格行索引脚仔。 |
col | Number | 活動(dòng)單元格的列索引勤众。 |
prop | String | 與活動(dòng)單元格關(guān)聯(lián)的屬性名(僅當(dāng)數(shù)據(jù)源是對(duì)象數(shù)組時(shí)相關(guān))。 |
TD | HTMLTableCellNode | 活動(dòng)單元的節(jié)點(diǎn)對(duì)象鲤脏。 |
cellProperties | Object | 表示活動(dòng)單元格屬性的對(duì)象们颜。 |
如何創(chuàng)建自定義編輯器
如果只是想增強(qiáng)現(xiàn)有的編輯器,可以擴(kuò)展它的類并只覆蓋它的幾個(gè)方法猎醇。
示例:擴(kuò)展TextEditor窥突,創(chuàng)建PasswordEditor,顯示密碼也可以通過創(chuàng)建一個(gè)繼承自BaseEditor的新編輯器類來從頭構(gòu)建一個(gè)新的編輯器硫嘶。
示例:創(chuàng)建一個(gè)全新的SelectEditor波岛,它使用<select> list來改變cell的值。
創(chuàng)建PasswordEditor
TextEditor是Handsontable中最復(fù)雜的默認(rèn)編輯器音半。它顯示一個(gè)<textarea>则拷,它會(huì)自動(dòng)改變其大小以適應(yīng)其內(nèi)容。我們想創(chuàng)建一個(gè)PasswordEditor曹鸠,它保留了所有這些功能煌茬,但顯示<input type="password">字段而不是<textarea>。
我們需要?jiǎng)?chuàng)建一個(gè)繼承自TextEditor新的編輯器類彻桃,然后覆蓋它的一些方法坛善,用input:password替換<textarea>。文本區(qū)域和密碼輸入具有相同的API邻眷,因此我們所要做的就是替換負(fù)責(zé)創(chuàng)建HTML元素的代碼眠屎。TextEditor中的init()內(nèi)部調(diào)用了createElements()方法,該方法創(chuàng)建<textarea>節(jié)點(diǎn)肆饶,并在編輯器初始化期間將其附加到DOM中改衩。
具體代碼如下:
import Handsontable from 'handsontable';
class PasswordEditor extends Handsontable.editors.TextEditor {
createElements() {
super.createElements();
this.TEXTAREA = this.hot.rootDocument.createElement('input');
this.TEXTAREA.setAttribute('type', 'password');
this.TEXTAREA.setAttribute('data-hot-input', true); // Makes the element recognizable by HOT as its own component's element.
this.textareaStyle = this.TEXTAREA.style;
this.textareaStyle.width = 0;
this.textareaStyle.height = 0;
this.TEXTAREA_PARENT.innerText = '';
this.TEXTAREA_PARENT.appendChild(this.TEXTAREA);
}
}
使用:
const container = document.querySelector('#container')
const hot = new Handsontable(container, {
columns: [
{
type: 'text'
},
{
editor: PasswordEditor
// If you want to use string 'password' instead of passing
// the actual editor class check out section "Registering editor"
}
]
});
創(chuàng)建SelectEditor
SelectEditor允許用戶從定義好的<select>列表中選擇值作為單元格的新值。另外驯镊,用戶還可以使用ARROW_UP和ARROW_DOWN鍵更改當(dāng)前選擇的選項(xiàng)葫督。具體步驟如下:
- 創(chuàng)建一個(gè)繼承自Handsontable.editors.BaseEditor的新類。
const SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();
- 添加創(chuàng)建<select>標(biāo)簽并將其添加到DOM上的函數(shù)板惑。
有三個(gè)方法可以完成這一步橄镜,init(),prepare()冯乘,open()洽胶。
init()方法在創(chuàng)建編輯器類對(duì)象期間被調(diào)用。每個(gè)表實(shí)例最多只發(fā)生一次裆馒,因?yàn)橐坏﹦?chuàng)建了對(duì)象姊氓,每次EditorManager請(qǐng)求這個(gè)編輯器類實(shí)例時(shí)都會(huì)重用它丐怯。
prepare()方法在用戶選擇指定單元格時(shí)會(huì)調(diào)用,指定單元格指的是editor設(shè)置為特定編輯器類的的單元格他膳。因此响逢,如果我們將SelectEditor設(shè)置為整個(gè)列的編輯器绒窑,那么選擇該列中的任何單元格將調(diào)用SelectEditor的prepare()方法棕孙。換句話說,這個(gè)方法在表生命周期中可以被調(diào)用數(shù)百次些膨,特別是在處理大數(shù)據(jù)時(shí)蟀俊。另外,prepare()不應(yīng)該顯示編輯器(這是open()的工作)订雾。顯示編輯器是由用戶事件(按ENTER肢预、F2或雙擊單元格等)觸發(fā)的,因此在調(diào)用prepare()和實(shí)際顯示編輯器之間存在一段時(shí)間洼哎。盡管如此烫映,應(yīng)該盡可能快地完成prepare()執(zhí)行的操作,以提供最佳的用戶體驗(yàn)噩峦。
當(dāng)需要顯示編輯器時(shí)調(diào)用open()方法锭沟。在大多數(shù)情況下,該方法應(yīng)該將CSS里display屬性更改為block或執(zhí)行類似的操作识补。用戶希望在事件(按下適當(dāng)?shù)逆I或雙擊單元格)觸發(fā)后立即顯示編輯器族淮,因此open()方法應(yīng)該盡可能快地工作。
了解了所有這些凭涂,最合理的地方放置負(fù)責(zé)創(chuàng)建<select>輸入的代碼是在init()方法中的某個(gè)地方祝辣。DOM操作被認(rèn)為是相當(dāng)昂貴的(就資源消耗而言)操作,因此最好只執(zhí)行一次切油,并在編輯器的整個(gè)生命周期中重用生成的HTML節(jié)點(diǎn)蝙斜。
import Handsontable from 'handsontable';
class SelectEditor extends Handsontable.editors.BaseEditor {
/**
* Initializes editor instance, DOM Element and mount hooks.
*/
init() {
// Create detached node, add CSS class and make sure its not visible
this.select = this.hot.rootDocument.createElement('SELECT');
this.select.classList.add('htSelectEditor');
this.select.style.display = 'none';
// Attach node to DOM, by appending it to the container holding the table
this.hot.rootElement.appendChild(this.select);
}
}
.htSelectEditor {
/*
* This hack enables to change <select> dimensions in WebKit browsers
*/
-webkit-appearance: menulist-button !important;
position: absolute;
width: auto;
z-index: 300;
}
- 添加一個(gè)函數(shù),用于填充<select>下拉選項(xiàng)澎胡,對(duì)應(yīng)的下拉選項(xiàng)通過cell properties傳入乍炉。
通過配置項(xiàng)傳入下拉選項(xiàng)列表:
const container = document.querySelector('#container')
const hot = new Handsontable(container, {
columns: [
{
editor: SelectEditor,
selectOptions: ['option1', 'option2', 'option3']
}
]
});
在init()方法中填充列表不是很適合,因?yàn)閕nit()方法只會(huì)執(zhí)行一次滤馍,當(dāng)多個(gè)列都使用SelectEditor編輯器岛琼,并且每個(gè)列的下拉選項(xiàng)都不同,甚至同一列中的兩個(gè)單元格的下拉選項(xiàng)都不一致的時(shí)候巢株,init()方法就不能實(shí)現(xiàn)我們的需求了槐瑞。
我們只剩下兩個(gè)地方prepare()和open()。后一種方法更容易實(shí)現(xiàn)阁苞,但正如我們前面所說困檩,setValue()應(yīng)該盡可能快地工作祠挫,如果selectOptions包含一長(zhǎng)串選項(xiàng),那么創(chuàng)建<option>節(jié)點(diǎn)并將它們附加到DOM可能會(huì)很耗時(shí)悼沿。因此等舔,prepare()似乎是做這類工作更安全的地方。唯一要記住的是糟趾,當(dāng)重寫prepare()時(shí)慌植,我們應(yīng)該始終調(diào)用BaseEditor的原始方法。prepare()設(shè)置一些重要的屬性义郑,這些屬性被其他編輯器方法使用蝶柿。
// Create options in prepare() method
prepare(row, col, prop, td, originalValue, cellProperties) {
// Remember to invoke parent's method
super.prepare(row, col, prop, td, originalValue, cellProperties);
const selectOptions = this.cellProperties.selectOptions;
let options;
if (typeof selectOptions === 'function') {
options = this.prepareOptions(selectOptions(this.row, this.col, this.prop));
} else {
options = this.prepareOptions(selectOptions);
}
this.select.innerText = '';
Object.keys(options).forEach((key) => {
const optionElement = this.hot.rootDocument.createElement('OPTION');
optionElement.value = key;
optionElement.innerText = options[key];
this.select.appendChild(optionElement);
});
}
prepareOptions(optionsToPrepare) {
let preparedOptions = {};
if (Array.isArray(optionsToPrepare)) {
for (let i = 0, len = optionsToPrepare.length; i < len; i++) {
preparedOptions[optionsToPrepare[i]] = optionsToPrepare[i];
}
} else if (typeof optionsToPrepare === 'object') {
preparedOptions = optionsToPrepare;
}
return preparedOptions;
}
- 實(shí)現(xiàn)這些函數(shù):getValue(),setValue()非驮,open()交汤,close(),focus()劫笙。
getValue() {
return this.select.value;
}
setValue(value) {
this.select.value = value;
}
open() {
const {
top,
start,
width,
height,
} = this.getEditedCellRect();
const selectStyle = this.select.style;
this._opened = true;
selectStyle.height = `${height}px`;
selectStyle.minWidth = `${width}px`;
selectStyle.top = `${top}px`;
selectStyle[this.hot.isRtl() ? 'right' : 'left'] = `${start}px`;
selectStyle.margin = '0px';
selectStyle.display = '';
}
focus() {
this.select.focus();
}
close() {
this._opened = false;
this.select.style.display = 'none';
}
getvalue()芙扎、setvalue()和close()的實(shí)現(xiàn)很簡(jiǎn)單,但是open()需要一些注釋填大。首先戒洼,假設(shè)實(shí)現(xiàn)填充下拉列表的代碼放在prepare()中。其次栋盹,在顯示列表之前施逾,我們?cè)O(shè)置其height和minWidth,使其與相應(yīng)單元格的大小匹配例获。這是一個(gè)可選步驟汉额,但如果沒有它,編輯器將根據(jù)瀏覽器的不同而具有不同的大小榨汤。限制<select>的最大高度可能是一個(gè)好主意蠕搜。最后,由于<select>被附加到表格容器的末尾收壕,我們必須更改它的位置妓灌,以便它可以顯示在正在編輯的單元格上方。同樣蜜宪,這是一個(gè)可選的步驟虫埂,但是將編輯器放在適當(dāng)?shù)膯卧衽赃吽坪跏欠浅:侠淼摹?/p>
- 覆蓋默認(rèn)的EditorManager行為,這樣按向上(Arrow Up)和向下(Arrow Down)箭頭鍵不會(huì)關(guān)閉編輯器圃验,而是改變當(dāng)前選擇的值掉伏。
雖然我們不能直接訪問EditorManager實(shí)例,但是仍然可以覆蓋它的行為。在EditorManager開始處理鍵盤事件之前斧散,它會(huì)觸發(fā)beforeKeyDown鉤子供常。如果任何偵聽函數(shù)調(diào)用事件對(duì)象上的stopImmediatePropagation()方法,EditorManager將不再處理此事件鸡捐。因此栈暇,我們所要做的就是注冊(cè)一個(gè)beforeKeyDown監(jiān)聽器函數(shù),該函數(shù)檢查箭頭向上或箭頭向下是否被按下箍镜,如果是源祈,停止事件傳播并相應(yīng)地改變<select>列表中當(dāng)前選擇的值。
我們需要注意的是鹿寨,偵聽器應(yīng)該只在打開編輯器時(shí)工作新博。我們希望保留其他編輯器的默認(rèn)行為薪夕,以及沒有打開編輯器時(shí)的默認(rèn)行為脚草。這就是為什么注冊(cè)偵聽器的最合理的位置是open()方法,close()方法應(yīng)該包含將刪除偵聽器的代碼原献。
onBeforeKeyDown() {
const previousOptionIndex = this.select.selectedIndex - 1;
const nextOptionIndex = this.select.selectedIndex + 1;
switch (event.keyCode) {
case 38: // Arrow Up
if (previousOptionIndex >= 0) {
this.select[previousOptionIndex].selected = true;
}
event.stopImmediatePropagation();
event.preventDefault();
break;
case 40: // Arrow Down
if (nextOptionIndex <= this.select.length - 1){
this.select[nextOptionIndex].selected=true;
}
event.stopImmediatePropagation();
event.preventDefault();
break;
default:
break;
}
}
open() {
this.addHook('beforeKeyDown', () => this.onBeforeKeyDown());
}
close() {
this.clearHooks();
}
活動(dòng)編輯器是最近調(diào)用prepare()方法的編輯器馏慨。例如,如果選擇一個(gè)單元格姑隅,其編輯器是Handsontable.TextEditor写隶,那么getActiveEditor()將返回這個(gè)編輯器類的一個(gè)對(duì)象。如果選擇編輯器為Handsontable.DateEditor的單元格讲仰,活動(dòng)編輯器將會(huì)改變慕趴,現(xiàn)在getActiveEditor()將返回一個(gè)DateEditor類的對(duì)象。
- 注冊(cè)編輯器鄙陡。
創(chuàng)建編輯器時(shí)冕房,最好是為其分配一個(gè)別名,該別名將引用這個(gè)特定的編輯器類趁矾。Handsontable默認(rèn)定義了11個(gè)別名:
別名 | 類型 |
---|---|
autocomplete | Handsontable.editors.AutocompleteEditor |
base | Handsontable.editors.BaseEditor |
checkbox | Handsontable.editors.CheckboxEditor |
date | Handsontable.editors.DateEditor |
dropdown | Handsontable.editors.DropdownEditor |
handsontable | Handsontable.editors.HandsontableEditor |
numeric | Handsontable.editors.NumericEditor |
password | Handsontable.editors.PasswordEditor |
select | Handsontable.editors.SelectEditor |
text | Handsontable.editors.TextEditor |
time | Handsontable.editors.TimeEditor |
它為用戶提供了一種方便的方式來定義在更改某些單元格的值時(shí)應(yīng)該使用哪個(gè)編輯器耙册。用戶不需要知道哪個(gè)類負(fù)責(zé)顯示編輯器,他甚至根本不需要知道有任何類毫捣。此外详拙,您可以更改與別名關(guān)聯(lián)的類,而無需更改定義表的代碼蔓同。
要注冊(cè)自己的別名饶辙,請(qǐng)使用Handsontable.editors.registerEditor()函數(shù)。它接受兩個(gè)參數(shù):
- editorName - 字符串:代表編輯器的別名
- editorClass - 類:編輯器類
Handsontable.editors.registerEditor('select', SelectEditor);
盡量不要選擇Handsontable已經(jīng)定義了的別名斑粱。如果您以已注冊(cè)的名稱注冊(cè)編輯器弃揽,則目標(biāo)類將被覆蓋:
// 現(xiàn)在‘text’別名指向MyNewTextEditor
Handsontable.editors.registerEditor('text', MyNewTextEditor);
因此,除非您有意要覆蓋現(xiàn)有別名,否則請(qǐng)嘗試選擇唯一的名稱蹋宦。一個(gè)好的做法是用一些自定義名稱(例如您的GitHub用戶名)前綴您的別名披粟,以盡量減少名稱沖突的可能性。如果要發(fā)布編輯器,這一點(diǎn)尤其重要,因?yàn)槟肋h(yuǎn)不知道使用編輯器的用戶是否注冊(cè)了別名栏尚。
// 這樣定義比較好
Handsontable.editors.registerEditor('my.select', SelectEditor);
優(yōu)化編輯器代碼
如果你打算發(fā)布你的編輯器岂昭,或者只是想保持你的代碼整潔,有3個(gè)簡(jiǎn)單的步驟可以幫助你組織你的代碼背桐。
- 使用IIFE包裹代碼
將代碼放在模塊中,以避免污染全局命名空間。你可以使用AMD俺叭、CommonJS或任何其他模塊模式,但隔離代碼的最簡(jiǎn)單方法是使用普通的立即調(diào)用函數(shù)表達(dá)式(IIFE)泰偿。
(Handsontable => {
const CustomEditor = Handsontable.editors.BaseEditor.prototype.extend();
// ...rest of the editor code
})(Handsontable);
將Handsontable作為參數(shù)傳遞是可選的(因?yàn)樗侨侄x的)熄守,但使用盡可能少的全局對(duì)象是一個(gè)很好的實(shí)踐,以使模塊化和依賴管理更容易耗跛。
- 向?qū)S妹臻g添加編輯器
包含在IIFE中的代碼不能從外部訪問裕照,除非它是故意暴露的。使用Handsontable.editors.registerEditor方法將編輯器注冊(cè)到編輯器集合中调塌,以使事情井井有條晋南。通過這種方式,您可以在表定義期間使用編輯器羔砾,并且其他用戶如果想擴(kuò)展編輯器负间,可以輕松訪問編輯器。
(Handsontable => {
const CustomEditor = Handsontable.editors.BaseEditor.prototype.extend();
// ...rest of the editor code
// And at the end
Handsontable.editors.registerEditor('custom', CustomEditor);
})(Handsontable);
可以這樣使用:
const container = document.querySelector('#container');
const hot = new Handsontable(container, {
columns: [{
editor: Handsontable.editors.CustomEditor
}]
});
// 擴(kuò)展
const AnotherEditor = Handsontable.editors.getEditor('custom').prototype.extend();
- 注冊(cè)別名
最后一步是注冊(cè)編輯器別名姜凄,這樣用戶就可以輕松地引用它政溃,而不需要知道實(shí)際的類名。
(Handsontable => {
const CustomEditor = Handsontable.editors.BaseEditor.prototype.extend();
// ...rest of the editor code
// Put editor in dedicated namespace
Handsontable.editors.CustomEditor = CustomEditor;
// Register alias
Handsontable.editors.registerEditor('theBestEditor', CustomEditor);
})(Handsontable);
// 使用
const container = document.querySelector('#container')
const hot = new Handsontable(container, {
columns: [{
editor: 'theBestEditor'
}]
});