🐙
Vue.js と google.script.run は噛み合わせが悪い
問題点
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
で該当箇所が見つかりましたので抜粋します。
これを見る限りtoString
がObject.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