【Ginza.js#8 - LTスライド】Vue.jsでWeb画面にBashのhistory機能を実装する


# Bashのhistory機能、便利ですよね?

![bash-history.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416178/d6282a50-f1e1-654f-9ced-812812b59996.gif)

---

# Webのフォームにも、実装できたら便利なはず

ということで実装していきます

---

# まずはBashのhistoryの挙動を確認

### 挙動1

↑キーで1つ前、↓キーで1つ後に実行したコマンドを表示できる

### 挙動2

実行前の編集中のコマンドもhistoryに反映される

### 挙動3

コマンドを実行するたび、編集中だったコマンドはリセットされる

---

![bash-history.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416178/d6282a50-f1e1-654f-9ced-812812b59996.gif)

---

# 実装の流れ

1. まずは↑キーと↓キーで過去の入力値を表示できるようにする(Enterキーで入力値の確定をする)
2. 次に編集時の挙動を実装する

---

# ↑キー、↓キーとEnterキーの挙動を実装

### idつきのinputタグを用意

```history.html
<input id="form" placeholder="some text">
```

### 何はともあれ、まずは `v-model`

```history.html
<input
  id="form"
  placeholder="some text"
  v-model="text"
>
```
---

### キーボードイベントのバインディング

```history.html
<input
  ~ 省略 ~
  v-on:keydown.arrow-up.prevent="backHist"
  v-on:keydown.arrow-down.prevent="headHist"
  v-on:keydown.enter='confirm'
>
```

※ `prevent` でデフォルトの挙動を無効化しないと、カーソルの位置が飛ぶ

### Vueインスタンス作成

```history.js
const form=new Vue({
  el: "#form",
  data: {
    text: '', // フォームの初期値
    index: 0, // ヒストリのインデックス
    history: [ '' ] // ヒストリの先頭にもフォームの初期値を入れておく
  }
})
```
※配列の先頭が最新の値とする

---

### ↑キー↓キーイベント発火時の関数を定義

```history.js
const form=new Vue({
  ~ 省略 ~
  methods: {
    backHist(){
      // indexはhistoryの長さ未満
      (this.index + 1) < this.history.length
        ? this.index++
        : undefined

      this.text=this.history[this.index];
    },
    headHist(){
      // indexは0より小さくはならない
      (this.index - 1) < 0
        ? undefined
        : this.index--

      this.text=this.history[this.index];
    },
  }
});
```

---

### Enterキーイベント発火時の関数を定義

```history.js
const form=new Vue({
  ~ 省略 ~
  methods: {
    ~ 省略 ~
    confirm: function(){
      this.hist[0]=this.text; // 現在の値をヒストリの最新値として格納

      // 次のヒストリの初期値を入力(現在のテキストが空のときはヒストリを追加しない)
      this.text === ''
        ? undefined
        : this.hist=[ '', ...this.hist ]

      this.index=0; // インデックスを0(最新の値)に戻す
      this.text=''; // フォームをクリア
    }
  }
});
```

これで↑キー、↓キーとEnterキーの挙動は再現できました

---

# 編集時の挙動を実装

### キー入力イベントのバインディング

```history.html
<input
  ~ 省略 ~
  v-on:input="editHist"
>
```

キー入力されるたびにヒストリが「編集された」と考える

### ヒストリにオリジナルのテキストと、編集中のテキストを格納できるようにする

```history.js
const form=new Vue({
  ~ 省略 ~
  data: {
    ~ 省略 ~
    history: [{
      text: '', // オリジナル
      edit: undefined, // 編集中のテキスト(なければundefined)
    }]
  }
})
```
---

### 編集イベント発火用の関数を定義

```history.js
const form=new Vue({
  ~ 省略 ~
  methods: {
    ~ 省略 ~
    editHist: function(){
      this.hist[this.index].edit=this.text;
    }
  }
})
```

---

### `history` の値変更に合わせて `confirm` 関数を変更する

```history.js
const form=new Vue({
  ~ 省略 ~
  methods: {
    ~ 省略 ~
    confirm: function(){
      this.hist[0].text=this.text; // 現在の値をヒストリの最新値として格納

      // 編集中の値をクリア
      this.hist.forEach(
        h => h.edit=undefined
      );

      // 次のヒストリの初期値を入力(現在のテキストが空のときはヒストリを追加しない)
      this.text === ''
        ? undefined
        : this.hist=[{
            text: '',
            edit: undefined
          }, ...this.hist ]
            // .slice(0, 16) でhistoryの登録数上限(16)を指定することも可能

      this.index=0; // インデックスを0(最新の値)に戻す
      this.text=''; // フォームをクリア
    }
  }
})
```

以上で編集の挙動も実装できました

---

# なぜこれを実装したか

[Demo](https://www.masquerade.ninja)

---

# おわりに

簡単に実装できるので、ITエンジニアをターゲットにしたサービスで実装してみてはどうでしょう

---

# About Me

普段はインフラエンジニア

JavaScriptとShellScriptでインフラ自動化ができるOSSを開発しています

* Submarine.js - https://gitlab.com/mjusui/submarine/tree/latest

![Submarine1.0-with-text-170.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416178/bfae14fa-477d-12a6-800e-b2ac802ce511.png)


* Twitter: @submarinejs,  @Mjusui