はじめに
Atomでコマンドパレットを開いたときなど、1行入力欄の下に選択候補が並んで表示されるのだが、自作のパッケージでどうやって表示するのか調べてみた。
準備
基本的には、'atom-space-pen-views'に含まれるSelectListViewを継承したクラスを使う。
一般的に、Atomのコンポーネント(?)をどうやって表示したら良いのかは、コマンドパレットからStyleguide
を表示すると書いてある。今回もStyleguideを参考にした。
'atom-space-pen-views'は普通にnodeのパッケージなので、ターミナルから
$ npm install --save atom-space-pen-views
$ npm install --save fuzzaldrin
でインストールしておく。
今回は、ファジー検索パッケージfuzzaldrinも使用したので、一緒にインストールした。
実装
コマンドパレットの実装やSelectListViewの実装を参考に(写経して)書いていく。
基本的には、viewForItem()とpopulateList()を適切に書いてやれば動くようだが、populateList()のほうは、@itemsに適切なデータ(配列)を入れておいて、getFilterKey()で配列内のオブジェクトからソート時のキーになる名前を返してやれば動くっぽい。
@itemsにいれるデータだが、
@items = [
{
key1: value1_1
key2: value2_1
},
{
key1: value1_2
key2: value2_2
}
];
のような配列だと、SelectListViewがよきに計らってくれるので楽に実装出来る。
実際のコードは以下。
{SelectListView, $, $$} = require 'atom-space-pen-views'
{match} = require 'fuzzaldrin'
module.exports =
class MacroNameSelectListView extends SelectListView
panel: null
callback: null
initialize: (@listOfItems) ->
super
@setItems(@listOfItems)
show: ->
# この関数はひたすら写経
@panel ?= atom.workspace.addModalPanel(item: this)
@panel.show()
@storeFocusedElement()
if @previouslyFocusedElement[0] and @previouslyFocusedElement[0] isnt document.body
@eventElement = @previouslyFocusedElement[0]
else
@eventElement = atom.views.getView(atom.workspace)
@setItems(@items)
@focusFilterEditor() # これで入力フォームにフォーカスする。
hide: ->
@panel?.hide()
addItem: (item) ->
@items ?= []
@items.push
name: item # ここの'name'が、getFilterKey()で返すキーになる
@setItems @items
getFilterKey: -> # これがないとviewForItem()が動かない
'name'
clearText: ->
@filterEditorView.setText('')
setCallback: (callback) ->
@callback = callback
focus: ->
@focusFilterEditor()
getElement: ->
@element
cancel: ->
# キャンセル時(ESCキー)の処理
@hide()
confirmed: ({name}) ->
# 決定時(Enterキーなど)の処理
@clearText()
@callback?(name)
viewForItem: ({name}) ->
# だいたい写経
# Style matched characters in search results
filterQuery = @getFilterQuery() # 入力欄に入っているテキスト
matches = match(name, filterQuery)
$$ ->
highlighter = (command, matches, offsetIndex) =>
lastIndex = 0
matchedChars = [] # Build up a set of matched chars to be more semantic
for matchIndex in matches
matchIndex -= offsetIndex
continue if matchIndex < 0 # If marking up the basename, omit command matches
unmatched = command.substring(lastIndex, matchIndex)
if unmatched
@span matchedChars.join(''), class: 'character-match' if matchedChars.length
matchedChars = []
@text unmatched
matchedChars.push(command[matchIndex])
lastIndex = matchIndex + 1
@span matchedChars.join(''), class: 'character-match' if matchedChars.length
# Remaining characters are plain text
@text command.substring(lastIndex)
@li class: 'event', 'data-event-name': name, =>
@span title: name, -> highlighter(name, matches, 0)
追記:使用上の注意点
SelectListViewはあくまでもリストから選ぶために使うものなので、リストにないテキストを入力してもconfirmedは呼ばれない。