🐙

Vue.js と google.script.run は噛み合わせが悪い

2021/01/05に公開

問題点

google.script.runで取得した値をそのままVueに渡すと双方向バインディングが効きません。

xxx.js
const userData = await new Promise( resolve => {
  google.script.run
  .withSuccessHandler( resolve )
  .getUserData()
} )

const app = new Vue( {
  data: { userData, },
} )
xxx.html
<!-- 画面上で操作してもuserData.fooの値が変化してくれない -->
<p><input v-model="userData.foo" /></p>

理由

推測ですが、google.script.runが実行される画面(グローバルオブジェクト)がアプリの画面と異なるためだと思われます。

以前の記事で触れた通り GAS の Webアプリはiframeが2重にネストされており、google.script.runが定義されているのは1つめのiframe、こちらの書いたスクリプトは2つのiframeに置かれるため、同じObject型でもコンストラクタが別になります。

Vue.jsの方は関係する処理は見つけられなかったのですが、恐らくprototypeに依存する処理があるのだと思います。

ちなみに{{ value }}で表示させる箇所も正常に動作せず、[object Object]のような生の Javascript の表示になってしまいます。
こちらはVue.jsで該当箇所が見つかりましたので抜粋します。
これを見る限りtoStringObject.prototype.toStringと参照一致していないとJSON化してもらえないようです。

Vue.jsより抜粋
  /**
   * Get the raw type string of a value, e.g., [object Object].
   */
  var _toString = Object.prototype.toString;

//中略

  /**
   * Convert a value to a string that is actually rendered.
   */
  function toString (val) {
    return val == null
      ? ''
      : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
        ? JSON.stringify(val, null, 2)
        : String(val)
  }

解決策

最初はObject.assignを使おうかと思ったのですが、配列やnullと他の基本データ型を分けたり再帰的に処理したりと凝った内容になってしまうので、簡潔にJSON化してすぐパースする方法を取りました。

xxx.js
const userData = await new Promise( resolve => {
  google.script.run
  .withSuccessHandler( v => resolve( JSON.parse( JSON.stringify( v ) ) ) )
  .getUserData()
} )

const app = new Vue( {
  data: { userData, },
} )

以上!
Google Apps Script での Web アプリ開発は少し癖がある感じがありますね。

Discussion