Handsontable中的cell editor(2023-11-16)

本文主要是對(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)建自定義編輯器

  1. 如果只是想增強(qiáng)現(xiàn)有的編輯器,可以擴(kuò)展它的類并只覆蓋它的幾個(gè)方法猎醇。
    示例:擴(kuò)展TextEditor窥突,創(chuàng)建PasswordEditor,顯示密碼

  2. 也可以通過創(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)葫督。具體步驟如下:

  1. 創(chuàng)建一個(gè)繼承自Handsontable.editors.BaseEditor的新類。
const SelectEditor = Handsontable.editors.BaseEditor.prototype.extend();
  1. 添加創(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;
}
  1. 添加一個(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;
}
  1. 實(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>

  1. 覆蓋默認(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ì)象。

  1. 注冊(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)單的步驟可以幫助你組織你的代碼背桐。

  1. 使用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í)踐,以使模塊化和依賴管理更容易耗跛。

  1. 向?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();
  1. 注冊(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'
  }]
});
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末檀葛,一起剝皮案震驚了整個(gè)濱河市玩祟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屿聋,老刑警劉巖空扎,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異润讥,居然都是意外死亡转锈,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門楚殿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撮慨,“玉大人,你說我怎么就攤上這事∑瞿纾” “怎么了影涉?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)规伐。 經(jīng)常有香客問我蟹倾,道長(zhǎng),這世上最難降的妖魔是什么猖闪? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任鲜棠,我火速辦了婚禮,結(jié)果婚禮上培慌,老公的妹妹穿的比我還像新娘豁陆。我一直安慰自己,他們只是感情好吵护,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布盒音。 她就那樣靜靜地躺著,像睡著了一般何址。 火紅的嫁衣襯著肌膚如雪里逆。 梳的紋絲不亂的頭發(fā)上进胯,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天用爪,我揣著相機(jī)與錄音,去河邊找鬼胁镐。 笑死偎血,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盯漂。 我是一名探鬼主播颇玷,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼就缆!你這毒婦竟也來了帖渠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤竭宰,失蹤者是張志新(化名)和其女友劉穎空郊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體切揭,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狞甚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了廓旬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哼审。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涩盾,到底是詐尸還是另有隱情十气,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布春霍,位于F島的核電站桦踊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏终畅。R本人自食惡果不足惜籍胯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望离福。 院中可真熱鬧杖狼,春花似錦、人聲如沸妖爷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽絮识。三九已至绿聘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間次舌,已是汗流浹背熄攘。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留彼念,地道東北人挪圾。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像逐沙,于是被迫代替她去往敵國(guó)和親哲思。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容