Electronのwebviewを使いやすくする [ electron-vue + vuetify ]

ElectronのWebviewはとても簡単にwebページをレンダリング出来て便利ですよね。
ただ、ペライチのページをレンダリングするなら良いのですが、webview内部でページ遷移させようとすると色々使い勝手が悪いです。

なので、WebviewのAPIとVuetifyのコンポーネントで機能追加して、使いやすくしようと思い、
以下のようなWebviewのコンポーネントを作りました。その解説です。

![Mar-28-2019 14-16-42.gif](https://qiita-image-store.s3.amazonaws.com/0/114396/a34a1e7e-a097-4004-304b-645f949bfc2b.gif)


# 基本のWebviewの使い方

webviewは以下Electronの独自タグで表示できます。とても簡単ですね。

```html
<webview :src="表示したいURL" width="幅" height="高さ"></webview>
```

ただ内部でページ遷移させることを考えると問題もあります。

### ⚠ ローディング中がわからない
ページ遷移を行う場合、致命的だと思いました。
ユーザーは意外にローディング中のクルクルを見てボタンクリックの可否を判断しています。
なので、ローディング中の表示がないと反応していないと判断し、ボタン連打してしまったりします。

### ⚠ 戻る・進むボタンが使えない
プラウザ操作でおそらく一番使う「戻る・進むボタン」がwebviewではないです。
なので、動線が明らかなサイト以外非常に操作しづらいです。

### ⚠ リロードが出来ない
これは上記２つに比べ、あまり必要性ないかもですが、
画像のレンダリングに失敗したときなど、リロードさせたいですよね。
それもwebviewでは出来ないので困ります。
 
![Mar-28-2019 10-28-00.gif](https://qiita-image-store.s3.amazonaws.com/0/114396/60bd2a97-49d6-7a3e-c5e2-a40c4665693f.gif)


# 機能追加したwebview
webviewの上部にローディングと各種操作が可能なツールバーを追加しました。
electronのボイラープレートのelectron-vueとvueのコンポーネントライブラリ、vuetifyを使っています。

追加した機能は以下の通りです。

* ローディング中表示
* 戻る・進む機能
* リロード機能
* ホームへ戻る機能
* URL入力・表示機能

以下コードです。

```vue:webview.vue
<template>
  <div>
    <!--loadingbar-->
    <v-progress-linear :indeterminate="loading" class="webview-progress-bar"></v-progress-linear>

    <!--toolbar-->
    <v-toolbar fixed class="webview-toolbar" height="50">
      <v-btn @click="goBack" icon>
        <v-icon>arrow_back</v-icon>
      </v-btn>
      <v-btn @click="goForward" icon>
        <v-icon>arrow_forward</v-icon>
      </v-btn>
      <v-btn @click="reload" icon>
        <v-icon>refresh</v-icon>
      </v-btn>
      <v-btn @click="goHome" icon>
        <v-icon>home</v-icon>
      </v-btn>
      <v-spacer></v-spacer>
      <v-text-field
          v-model="url"
          @keypress.enter="loadUrl"
      ></v-text-field>
    </v-toolbar>

    <!--webview-->
    <webview id="webview" :src="initUrl"></webview>
  </div>
</template>

<script>
export default { name: 'index',
  data () {
    return {
      loading: false,
      url: '',
      webview: ''
    }
  },
  props: {
    initUrl: {type: String, require: true, default: 'https://qiita.com'}
  },
  methods: {
    goBack () {
      this.webview.canGoBack() && this.webview.goBack()
    },
    goForward () {
      this.webview.canGoForward() && this.webview.goForward()
    },
    goHome () {
      this.webview.loadURL(this.initUrl)
    },
    reload () {
      this.webview.reload()
    },
    loadUrl () {
      this.url.match(/^https?:\/\//) ? this.webview.loadURL(this.url)
        : this.webview.loadURL(`http://${this.url}`)
    },
    setUrlBar (event) {
      if (event.isMainFrame) {
        this.url = event.url
      }
    }
  },
  mounted () {
    // 初期URLの設定
    this.url = this.initUrl

    // webviewの取得・設定
    this.webview = document.getElementById('webview')

    // loading eventの付与
    this.webview.addEventListener('did-start-loading', () => {
      this.loading = true
    })
    this.webview.addEventListener('did-stop-loading', () => {
      this.loading = false
    })

    // commit eventの付与
    this.webview.addEventListener('load-commit', (e) => {
      this.setUrlBar(e)
    })
  }
}
</script>

<style scoped lang="scss">
  .webview-progress-bar {
    position: fixed;
    margin: 0;
    z-index: 999999;
  }

  #webview {
    margin-top: 57px;
    display: inline-flex;
    width: 1000px;
    height: 563px
  }

  .webview-toolbar {
    margin-top: 7px !important;
  }
</style>
```

以下各機能を解説していきます。

## ローディング表示の追加
これはvuetifyのwebviewのeventリスナーと、progress-linerコンポーネントを使い実装しています。

まず、jsでwebviewを取得して、addEventListnerでローディング開始の'did-start-loading'イベントと、ローディング終了の'did-stop-loading'イベントの発火時に、dataのloadingの状態を変化させるようにします。

```js
  data () {
    return {
      loading: false, //ローディング表示可否
      url: '',
      webview: ''
    }
  },
  mounted () {
    ...
    // webviewの取得・設定
    this.webview = document.getElementById('webview')

    // loading eventの付与
    this.webview.addEventListener('did-start-loading', () => {
      this.loading = true
    })
    this.webview.addEventListener('did-stop-loading', () => {
      this.loading = false
    })
    ...
  }
```

あとはvuetifyのprogress-linerの表示可否をdataのloadingと連携させればOKです。

```vue
    <!--loadingbar-->
    <v-progress-linear :indeterminate="loading" class="webview-progress-bar"></v-progress-linear>
```


## 戻る・進むボタンの追加

これはwebviewのAPI、getBackとgetFoawrdを使います。
https://electronjs.org/docs/api/webview-tag#webviewreload

js側は、以下です。
can~で戻る・進むが使用可能か判定して、可能な場合は戻る・進むの動作をさせるメソッドを追加します。

```js:webview.vue
  methods: {
    goBack () {
      this.webview.canGoBack() && this.webview.goBack()
    },
    goForward () {
      this.webview.canGoForward() && this.webview.goForward()
    },
  ....
```

テンプレート側は、iconを追加してクリックイベントでmethodを実行するだけです。

```vue
    <v-toolbar fixed class="webview-toolbar" height="50">
      <v-btn @click="goBack" icon>
        <v-icon>arrow_back</v-icon>
      </v-btn>
      <v-btn @click="goForward" icon>
        <v-icon>arrow_forward</v-icon>
      </v-btn>
   ...
```

## リロードボタンの追加

こちらも同じくwebviewのAPIにreloadがあるのでそれを使います
https://electronjs.org/docs/api/webview-tag#webviewreload

```js
methods: {
    ...
    reload () {
      this.webview.reload()
    },
```

buttonは先程と同様にclickイベントにmethodを設定します。

```vue
      <v-btn @click="reload" icon>
        <v-icon>refresh</v-icon>
      </v-btn>
```

## ホームボタンの追加

こちらは、不満点ではないのですが、合ったほうが良いかなと思って追加しました。
webviewのloadURLを使い、propsで渡されたinitUrlで指定したURLに遷移するようにしています。

https://electronjs.org/docs/api/webview-tag#webviewreload

```js
methods: {
    ...
    goHome () {
      this.webview.loadURL(this.initUrl)
    },
```

template側は同じくclickイベントでmethodを指定しています。

```vue
      <v-btn @click="goHome" icon>
        <v-icon>home</v-icon>
      </v-btn>
```

## 現在のURLの表示と、URL直打ちへの対応
こちらも不満ではないのですが、
せっかくvueを使っているので双方向データバインディングを使って上手くできないかなと作ってみました。

load完了で発火されるload-commitにテキストフィールドの値を変化させるsetUrlBarを紐づけています。

https://electronjs.org/docs/api/webview-tag#event-load-commit

```js
  mounted () {
    ...
    this.webview.addEventListener('load-commit', this.setUrlBar)
```

setUrlBarの実装はこちらです。
mainFrameのURLのみ設定したいため、isMainFrameで判定しています。

```js
methods: {
    ...
    setUrlBar (event) {
      if (event.isMainFrame) {
        this.url = event.url
      }
    }
```

また、UrlBarに直接入力した場合に、そのアドレスに遷移するように、loadUrlというmethodも追加しました。
httpなしで指定した場合でも遷移するように、正規表現でhttpを判定し追加しています。

```js
methods: {
    ...
    loadUrl () {
      this.url.match(/^https?:\/\//) ? this.webview.loadURL(this.url)
        : this.webview.loadURL(`http://${this.url}`)
    },
```

テンプレート側は、テキストフィールドにv-modelでurlフィールドをバインディングしています。
Enterを押した場合にloadUrlメソッドが実行されるように、@keypress.enterで指定しています。

```vue
      <v-text-field
          v-model="url"
          @keypress.enter="loadUrl"
      ></v-text-field>
```

# 終わり

以上Electronのwebviewを使いやすくするでした。
electronも素の状態より、やはりvueやreact入れたほうがシンプルに実装できる気がします。
あとvuetifyマジで便利です。