Google Apps Scriptは、HTML ServiceでウェブアプリケーションやスプレッドシートのダイアログのUIを構築可能です。しかし、標準ではUIを構築する為のライブラリやフレームワークを備えていないので、ユーザ自身が好きなフレームワークを導入して、HTML上に構築する必要があります。

自分は過去に、Google Apps Scriptのアプリをスマフォ向けとしては、framework7を使ってUIを構築しました。いずれ、framework7のまとめも作ってみたいと思います。今回はPC向けサイトとしてVuetifyを使って構築してみたいと思います。随時、自分がパーツを使うシーンがあれば、ここに追記していきたい。

図:現在作成中のElectronアプリでも使ってます

難易度:

今回使用するライブラリ等

VuetifyはVue.jsのUIフレームワークになるので(ちょうど、jQueryとjQuery UIの関係になる)、Vue.jsも利用する必要があります。

Google Apps ScriptでVue.jsを使ってみる

事前準備

ウェブ上で公開されてる事前準備では、vueファイルの作成であったり、vue-cliを使うためにnpmでインストールといったような説明書きをよく見かけますが、そもそもjQuery同様にvue.jsやvuetifyは単独で利用出来るようになっているので、あの手順にしたがってプロジェクトを作る必要性はありません。また、Google Apps Scriptでは当然vueファイルやらnpmでインストールなんてできません

Google Apps ScriptではいくつかのCDNで公開されてるライブラリをロードすることで、コード内で利用できるようになります。こちらのページUsage with CDNがそれに該当します。

Headerタグ内でCSSをロード

  • Header部分ではCSSを読み込みます。
  • 他にもGoogle FontsやViewportの設定も指定が必要になります。

Bodyタグ内の一番下でJSをロード

  • Vue.jsやVuetify.jsといったライブラリのJSファイルは、Header内で読み込ませても、エラーになります
  • Bodyの一番最後で読み込ませ、また初期化の為のスクリプトも同じ場所で記述する必要があります。
  • new Vue内にて初期化する際に、vuetifyを指定し、そこでnew Vuetifyを呼び出すことで、Vuetifyを初期化しています

viewportの指定

今回はPC向けということなのですが、モバイル向けとしてviewportの指定が必要な場合があります。しかし、通常通りHTML内に記述しても、Google Apps Scriptの場合それらのタグは無視されてしまいます。これらは、HTML Serviceの引数として、addMetaTagでの指定が必要です。faviconなども同様です。

※faviconを指定するとウェブアプリのアイコンが指定のアイコンになります(サンプル

色々なコントロールを使ってみる

Vuetifyのサイトには様々なUIコントロールのサンプルが掲載されています。しかし、これらのコードをそのままGoogle Apps ScriptのHTML Serviceで動かそうとした場合、動かないケースがままあります。今回はいくつかのコントロールをテストしてみて、実際にこれなら動くという形で、ちょっとだけ変えてあります。

非常に沢山のコントロールと、それぞれにオプションやテクニック、APIが用意されているので、表現力豊かなアプリケーションが構築可能です。

以下のコードはHeaderやBody下部の共通部分については省略しています。

Appbar

アプリケーションの顔とも言える、アプリ上部にあるヘッダに該当するナビゲーションバーです。この他にもファイルやヘルプなどのツールバーなどもあったりします(もちろん、モバイル向けの下部ナビゲーションも)。ただし、サンプルのままで動作しようとすると、このナビゲーションバーにコンテンツ部分が被ったりと色々と面倒な部分もありましたが、以下のコードで動作させることが可能です。

ソースコード

  • v-app-barにおいて、absoluteの指定があると下のコンテンツ部分に被ってしまうので省略
  • shrink-on-scrollの指定がある場合、スクロールするとバーが自動的に半分くらいのサイズにミニマムになる
  • scroll-targetの指定がある場合、スクロールするエリアは指定したID(今回だとmain_content を持つv-sheet)が対象
  • v-btnがナビゲーションのボタンの部分を担当する
  • v-app-bar-nav-iconは左サイドのハンバーガーメニューです

サンプル表示

Bottom Sheet

スマフォなどではおなじみのボタンをクリックすると、下からメッセージがせり上がってくるアレです。アプリで使うシーンとなると、普通のalertの代わりという、承認時のメッセージであったりといったダイアログよりも目立たせたいのが、使うポイントになるかと思います。

ソースコード

JS側コード

  • dataの中のsheetをフラグとし、falseで閉じます
  • 承認と却下用のコマンドをmethodsに加えて起きます。
HTML側コード

  • ボトムを開くをクリックすると、ボトムシートが開かれます
  • 承認、却下はそれぞれがmethodsに規定したコマンドを@Clickにて実行し、this.sheet=falseで閉じるようになっています。

サンプル表示

Dialog

アプリケーションで非常によく使う要素の1つがダイアログでしょう。jQueryなどを使わずにオシャレなダイアログを構築する事が可能です。その表現の数も非常に充実しており、よくあるYES/NO、フルサイズの設定用ダイアログ、ダイアログのネスト、プログレス表示用などなど、ユーザの要求を適切に受け取ることが可能です。

ソースコード

JS側コード

  • コード自体はBottom Sheetとほとんど同じものになります。
  • dataの中のdialogをフラグにして、falseの時に閉じます。
HTML側コード 

  • 承認実行ボタンを押すとダイアログが表示されます。
  • ダイアログの2つのボタンにそれぞれ@Clickで実行するmethodを指定しておきます。

サンプル表示

Carousel

ブログパーツであったり、スマフォのアプリであったり、最近は様々なシーンで使われるようになったUIコントロールが「カルーセルスライダー」。主に写真であったり、プレゼンのスライドのようなものであったりに使われていますが、いざ自前で実装すると、結構大変です。このカルーセルをVuetifyは簡単に実装できるだけでなく、配列に値をpushするだけで自動で追加項目が反映するVue.jsのBindの特性が活かせるコントロールでもあります。

ソースコード

JS側コード

  • calitemという配列を格納してる変数に、連想配列でsrcを指定したデータを格納しておく
  • Vue.jsにてdataのitemsにcalitemを指定するとカルーセルスライダー用のデータとして利用される
  • addPictureメソッドにて、この配列に新たな画像データを動的に1個追加する
HTML側コード

  • v-carouselにて、itemsの中身をv-forでループ、バインドされたデータをカルーセルで表示するようになっています。
  • 画像が追加されると自動的にVue.jsにてそれらは動的に取り込まれすぐ反映してくれる。jQueryのようにリロードといったコードは不要となる。
  • サンプルでは、クリックすると、一番最後にハイビスカスの画像がカルーセルに追加されます。

サンプル表示

Navigation drawers

いわゆる、サイドバーのこと。例えば、ハンバーガーメニューをクリックすると左側から選択用のメニューが出てくるようなアレです。現代のアプリケーションを構築し、ナビゲーションをするには必須の項目ですね。MenusListsTreeViewを組み合わせてれば、リッチなUIを実現出来ます。

ソースコード

JS側

  • Drawerの開閉フラグは変数drawerが担当しています。
  • サイドバーの中身用のリストは、itemsの値から生成しています。
  • リストをクリックしたら発火する関数として、onListChangeを定義していて、そのタグ内のIDの値をalertで表示
  • onListChangeで引数で受けるIDはitemsで生成時にv-bindで動的に割り当てたものを利用しています。
HTML側

  • v-navigation-drawerにbottomを指定すると、サイドバーではなくアンダーバーになります。
  • v-list-itemではv-forにてitemsから値を取得し、v-list-itemを生成しています。
  • v-on:clickにてリストアイテムクリック時にonListChangeを実行しています。引数に$eventを指定すると、他の属性値(例えばidなど)を関数側で取得できます。
  • v-bindにて、itemsのargsをIDとして割り当ててます。
  • itemsのiconはMaterial iconの文字列を指定、textはリストのタイトル。それぞれをVue.jsで割当

サンプル表示

組み合わせの妙技

テキストボックスとカレンダー連動+α

非常に便利でリッチなUIを提供してくれるVuetifyですが、公式ドキュメントだけでは実務では困るケースが結構あります。特に今回社内向けアプリを作ってて困ったのが、テキストボックスとカレンダーの連携。困った点は以下の通り

  • テキストボックスクリックでカレンダー表示だと手修正時に面倒(ボタンを別途用意する必要性)
  • カレンダーを要するものが1個ならばVue.js内に用意した変数と連動で良いが、たくさんある場合にはこの手は非常に面倒(カレンダーの数だけ用意が必要)
  • Vuetifyのカレンダーは、yyyy-mm-ddの形式でなければならないが、テキストボックス内に表示されるのはyyyy/mm/dd(このまま渡すとエラーになる)
  • 値を読み書きする対象は、単一の値ではなくJSONの塊であるため、ロジックが必要(テキストボックスへの連結はv-modelで連結で良いのだが・・・)

これらを解消するにはちょっと複雑な仕組みが必要です。

ソースコード

HTML側コード

  • 入社日付テキストボックスとはv-modelにてJSONデータであるselerecのjsondateに連結
  • テキストボックス、ボタン、カレンダーであるv-date-pickerはv-menuで囲っておく(これ全体が一つの塊になる)
  • ボタンクリック時には、カレンダーを呼び出すonBindName関数をイベントとして割り当てておく(v-on=’on’も必要です)
  • v-date-pickerはv-modelにてpickerという後述のComputedのget, setの変数を割り当てておく(これが日付データの受け渡しを担当)
  • v-date-pickerはlocale=”ja-jp”で日本語化。しかし、日がくっついてくるので、:day-format=”date => new Date(date).getDate()”にて、綺麗にフォーマットする
  • CSSにてカレンダーの土日の文字色は変更可能です。
  • onBindName関数の引数には対象となるJSONのkey名を入れておき、読み書き対象の場所を特定できるように指定しておく。
JS側コード

  • グローバル変数binderにはonBindNameが取得した引数を格納。読み書き先のJSONのkey名を格納しておく(これにより、たくさんのカレンダー用の変数を用意する必要がなくなる)
  • onBindNameにエラートラップをしてる理由は、値が空である場合にエラーとなるので、これを回避する為。
  • onBindName関数にて、特定のJSONの日付データを取得。ただし、replaceを使って正規表現にて「スラッシュ」を「ハイフン」に変更して、dateに格納しておく
  • v-onにてカレンダーが開かれると、v-date-pickerが表示される。すると、v-modelで連結してるcomputedのPickerにあるgetが発火。dateに格納されてる日付データを取得し、カレンダーに反映する
  • カレンダー側で日付を選択すると、computedのpickerにあるsetが発火。日付データはvalに入ってるので、これを今度は「ハイフン」を「スラッシュ」に変更して、dateおよびJSONの対象のkeyの値に直接格納する
  • カレンダー外をクリックすると、カレンダーは自動的に閉じる。カレンダーの表示非表示用の変数もこれで省略しているので、たくさんの変数を用意する必要がない

ちなみに、selerecには、1レコード分の様々なデータがkeyと値のペアでたくさん格納されている。

図:この手は非常によく使うテクニック

タブとツールバーの連携技

自分のアプリでは、レコードをクリックすると、その詳細な内容を編集するダイアログを表示させ、その中ではジャンル毎に「タブ」でわけています。このタブをツールバーの中に配置しており、保存ボタンを別途用意しており、編集後に保存ボタンを押すことで、MySQL側へとデータを投げる仕組みになっています。

タブをクリックする事でジャンル毎の中身を切り替えて表示するのですが、各コンテンツ内容がダイアログの大きさよりも大きい場合、スクロールバーが表示されますが、ツールバーまでスクロールしてしまい、具合が悪いです。コンテンツ部分だけをスクロール出来るように細工が必要です。

ソースコード

ツールバーを固定化する為のCSS

HTML側コード

  • v-toolbarの中v-tabsや各種ボタンを配置する
  • v-tab-sliderにてタブ下部のスライダーの色を指定する事が可能です。
  • 各タブのhrefには下部のコンテンツ部分のv-tab-itemの各valueの項目に切り替わる仕組みです。
  • v-toolbarにはCSSで固定化するためにclass=”fixed-bar”を指定しています。
  • <v-app-bar app dense fixed>をtoolbarにくくり、コンテンツ部分をv-contentでくくることで、似たようがことが実現はできますが、レイアウトが崩れたりするので、自分はCSSでの調整を利用しています。こちらのほうがスッキリ決まります。

図:標準のタグよりもCSS調整のほうが綺麗に決まることもある

Vue.jsのプラグイン

Cheetah Grid

業務用アプリケーションを構築する上で、特にPC向けで構築する場合にほぼ必須にあるコンポーネントが「Grid」。VuetifyにもGridはあるのですが、欲しいのはこういうものじゃない・・・これまでは、w2ui-gridSlickGridなどを使ってましたが、前者は以前は高速だったのですが、現在は重く切り替えも非常にもたつく、後者は高速なのですが既にメンテされていないし、構築が結構面倒。

ということで見つけたのが、Cheetah Grid。Vuetifyじゃなく、Vue.jsのプラグインとして実装されているものになりますが、マテリアルデザインで表現されます。

列の固定やマルチプルカラム(複数列にまたがるカラムタイトルを作れる)などが可能で、非常に高速に表示が出来るというので、手を出してみました。デモページはこちらになります。Vue.jsとの相性も良いみたいです。表示する為のGridのデータの仕様はこちらになります。Vue.jsで使う場合のドキュメントはこちら

ソースコード

Vue.jsの拡張プラグインとして、CDN配布されているので、vue.jsの読み込み後に以下のコードで追加で読み込むようにします。

GAS側コード

  • スプレッドシートのデータをタイトル列をkeyに、連想配列データにして返す関数です
  • このデータをCheetah Gridのrecordsにそのまま、当て込みます
JS側コード

  • Vue.useで今回の拡張プラグインを読み込まないとエラーになります
  • google.script.run.withSuccessHandlerにてGAS側からデータを受け取る
  • 受け取ったデータを元にnew Vueでrecordsに当て込みます。
  • 今回はボタンをクリックするとレコード内容をalertで表示するメソッドを追加
HTML側コード

  • frozen-col-countにて固定する列の列数を指定します。2で初めの2列を固定化
  • データと連結するにはfieldプロパティを使ってrecordsのkeyと一致させる。personidならrecordsデータのpersonidのkeyの値がここに入ってきます。
  • 2段表示のタイトル列を作る場合は、c-grid-column-groupにて1段目を定義し、その中に2段目を個別に定義する形になります。
  • onClickRecordにて定義した関数が呼び出されますが、特に引数を指定せずともrecにレコードの全ての値が入ってくるので、これを元にデータを加工します。
Grid用データ

  • カラム名:値という形でのシンプルな連想配列になっています。
  • データ入れ替えを考えるならば、これをJSファイルとして切り出し、動的に入れ替えるようにすれば、あとはVue.jsが良しなにやってくれます。

サンプル表示

サンプル用データは疑似個人情報生成サービスを利用しています

こういうプラグインが豊富にあるのも、Vue.jsの良いところ

関連リンク

共有してみる: