vue-touch-keyboardをv-model(databind)と共存させる
Vue.js v2でソフトウェアキーボードを使おうとすると、vue-touch-keyboardが第一候補に挙がるわけですが、公式のREADMEやJSFiddleのサンプルだけではイマイチピンとこないところがあるわけです。
その典型例が「どうやってv-modelでのdatabindと共存するの?」なわけですが、今回はそれをテーマにしたいと思います。
まず、オリジナルのJSFiddleサンプルをforkして、3つあるinputに同じ変数(ここではvm.data.value)をv-modelでdatabindさせたものがこれです。
試してみるとすぐわかりますが、ソフトウェアキーボードから入力した値がclose(またはnext)キーの押下で消えてしまいます。
これはvue-touch-keyboardが入力対象のDOMに対してのみ値を設定しており、databindされたviewmodel側の値の更新を一切行ってないからです。
では、どうすればviewmodel側の値を更新できるようになるのか?
これは「Vue.jsがDOMのnative eventをフックして値の変更を検知している」ことから、vue-touch-keyboardが入力対象としているDOMに対して'input'のnative eventを発火させればよいことになります。
なお「native event」とjQueryのtriggerで発火させることができる「jQuery event」とは別物なので注意してください(単なる「event」ではどちらのことを指しているか不明なので、native event/jQuery eventと呼び分けます)。
close及びnextキー押下の「確定」のタイミングで値の変更通知を発生させるようにしたものがこれです。
キモはこのコードになります。
accept(text) { console.log("Input text: " + text); this.change(text); ←ここでnative event発火メソッドを呼ぶ this.hide(); }, change(text) { ←native event発火処理の実装 // Trigger native 'input' event to notify Vue that the value has changed var evt = document.createEvent('HTMLEvents'); evt.initEvent('input', true, true); this.input.dispatchEvent(evt); },
vue-touch-keyboardタグでの定義:accept="accept"により、close及びnextキーを押下するとvm.acceptメソッドが呼ばれます。
<vue-touch-keyboard id="keyboard" v-if="visible" :layout="layout" :cancel="hide" :accept="accept" :input="input" :next="next" />
なので、vm.accept内でnative eventを発火させればよいわけです。実際にeventを発火させるコードはvm.changeメソッドに記載しています。
現在ソフトウェアキーボードが入力対象としているDOMはvm.showメソッドの下記の処理によりthis.inputに格納されていますので、
show(e) { this.input = e.target;
this.inputに対してdispatchEventを実行することでnative eventを発行できます(発行するeventの内容はevtに定義)。
さらに一歩すすんで、キー入力内容をリアルタイムで通知するバージョンはこれになります。
3つのinputに対して同じ値をdatabindしてあるので、リアルタイムに値が変更されていることが良くわかると思います。
これはvue-touch-keyboardタグに対して追加で:changeイベントハンドラの定義を行うだけです。
先ほどの実装で既にnative eventの発行をvm.changeメソッドに分離、かつchangeイベントハンドラのI/F要件を満たしてあるようにあったので、たったこれだけの追加で済みます。
<vue-touch-keyboard id="keyboard" v-if="visible" :layout="layout" :cancel="hide" :accept="accept" :input="input" :next="next" :change="change"←これ />
これでvue-touch-keyboardとv-modelを共存でき、かつリアルタイムに値の同期が行えるようになりました。めでたし、めでたし。
次回は引き続きvue-touch-keyboardでオリジナルのkeylayoutを作ってみたいと思います。
.csslintrcの構文変更
WEBフロントエンドのLint環境を整えていてハマったので覚書。
CSSLint のルールを定義する.csslintrcの構文が変更になっています。
以前は
{ "adjoining-classes": false, "box-model": true }
というbool値での書き方でしたが、これだと
adjoining-classes is not a valid option. Exiting...
というエラーが発生してしまいます。
ググったらGithubのIssue
https://github.com/CSSLint/csslint/issues/679#issuecomment-276173632
がヒットして、これによると
{ "ignore": [ "adjoining-classes" ], "warnings": [ ], "errors": [ "box-model" ] }
という書き方に全面的に変更されたとのことです。
従来trueで定義していたものが"errors"の、falseのものが"ignore"の配列要素として登録することになります。
これでエラーは出なくなりましたが、一方でHTMLHintの.htmlhintrcにおけるCSSチェック用定義は、従来通りのbool値による書き方なんですよね、これが。
{ "doctype-html5": true, "csslint": { "adjoining-classes": false, "box-model": true } }
Vue.jsで学ぶVirtual DOM
この記事はVue.js Advent Calendar 2016の7日目(代打投稿)です。
Vue.jsも2.0になってVirtual DOMが採用されたので、改めてVirtual DOMについてMVVMとの比較で考えてみます。
いきなり結論っぽい図ですが、これが分かればもうVirtual DOMは理解したといっても過言ではないかと。
- ObjectとDOMはそれぞれ独立した状態(値)を持っており、それを双方向に同期する。
- キー入力に起因するDOMの更新はDOMが自律的に行う。
- DOMはObjectの射影であり、DOMが自律的に更新することは無い。
- キー入力はイベントの形でObject側に通知され、Objectの値が変更されることによって、その射影という形でDOMが更新される。
ただ、かつての自分がそうだったように、この図だけでは特に「なぜ"1-way data flow"がこれだけモテはやされているのか?」が理解できないかもしれないので、実際にコードでそのメリットを感じてもらいたいと思います。
例として使うのは、前回Vuejs v2で無理やり2-wayディレクティブを再現してお茶を濁した3-stateチェックボックスです。
これを今回は「正しい」マイグレーション方法であるVirtual DOMを使ったコンポーネントで書き直してみます。
まずはVue.js v1のカスタムディレクティブ:MVVMで書いたコードを以下に再掲します。
function threeStateByClick(cb) { var val = true; if (cb.readonly) { // 本来はcb.indeterminate(値が保持されないのでreadonlyを代用) cb.readonly = cb.indeterminate = false; val = cb.checked = false; } else if (!cb.checked) { cb.readonly = cb.indeterminate = true; val = null; } return val; } function threeStateByVal(cb, val) { if (val === null) { cb.readonly = cb.indeterminate = true; cb.checked = false; } else { cb.readonly = cb.indeterminate = false; cb.checked = val; } } Vue.directive('three-state', { twoWay: true, bind: function() { this.handler = function() { var val = threeStateByClick(this.el); this.set(val); }.bind(this); this.el.addEventListener('click', this.handler); }, unbind: function() { this.el.removeEventListener('click', this.handler); }, update: function(val) { threeStateByVal(this.el, val); }, });
[htmlでの指定方法]
<input type="checkbox" v-three-state="cbVal" />
で、こっちがVue.js v2のコンポーネント:Virtual DOMで書き直したコードです。
Vue.component('checkbox3', { props: [ 'value' ], render: function(createElement) { return createElement('input', { attrs: { type: 'checkbox' }, domProps: { checked: this.value, indeterminate: (this.value === null) }, on: { click: this.onClick } }); }, methods: { onClick: function(event) { // 次の値の決定(false->true->null->false...) switch(this.value) { case null: this.value = false; break; case true: this.value = null; break; case false: this.value = true; break; } this.$emit('input', this.value); } } });
[htmlでの指定方法]
<checkbox3 v-model="cbVal"></checkbox3>
methodsは分かるものとして、propsとrenderはそれぞれプロパティと描画(レンダリング)の定義です。
props: [ 'value' ]
と書いておけば、Objectとの紐づけ(data bind)が組込みのv-modelディレクティブで可能となります。
コード量が減っていることが分かるかと思います。この差はどこから来るのかというと、ひとえに
- DOMを操作するためのコードが半分(vm→v方向のみ)で済むから
です。
冒頭のMVVMの図の左上を再度チェックしてほしいのですが、そこには「更新」という説明とともにループ状の矢印線が書いてあります。
これが単なるinputタグであれば、DOMの状態更新は何の追加コード無しに勝手に実行されます。
ですが、今回の3-stateチェックボックスのように「indeterminateプロパティはJavascriptのコードからのみ変更可能」というような場合は、表示状態の更新のためのコードが必要となります。
threeStateByClick関数がそれに該当し、このメソッドではclickされたことによる次の値の決定と合わせて表示を更新しています。
一方、Virtual DOMでの場合はvm→v方向の状態設定のみで済み、またその方法も"domProps"でindeterminateプロパティに直接必要な値を設定するだけです。
MVVMのときのように「DOM単体でも現在の自身の状態を把握する必要があるが、indeterminateプロパティは値が保持されないのでreadonlyにもセットしておく」などという小細工は必要ありません。
Virtual DOMにおけるDOMはそれに紐づけされたObjectの射影であり、それゆえDOM自身は状態を把握しなくてよいからです(更新が必要な時は常にrender関数が呼び出されてDOMが書き直されるため)。
「これは例が良くない:indeterminateプロパティという特殊事情がそうさせているだけでは?」と思うかもしれませんが、例えば良くあるUIエフェクトの
- inputタブへの値の入力有無でlabelの色や表示位置が変わる
などといった場合でも同様のことが言えます。
MVVMではUI入力によるDOMの更新はv→vm方向のデータ同期とは別に実行されることが前提なので、この例でのlabel制御はinputタグのイベントハンドラ内に頑張って実装するしかありませんし、vm→v方向のデータ同期時のlabel制御コードもやっぱり必要です(でないと、Ajax等で値を取ってきたときにlabel表示が変わらないというバグ持ちになる)。
一方、Virtual DOMではこの場合においてもobjectの値に対応したlabelの状態を設定するコードをrender関数に書けば終わりです。
Virtual DOMは「本当のDOMがいつ生成・消滅されるかわからない」ので既存のコード資産、特にjQueryを使った各種ライブラリとの併用が非常に困難(不可能ではない)という問題もありますが、「更新処理がシンプルになる」ことが「DOMとの状態のズレが発生し得ない」ことと合わせて最大のメリットなのかな、というのが今の自分の思うところです(「描画速度が速い」とかは二の次)。
【翻訳】自分の Vue アプリを 2.0 に移行し始めた件、及びこれまでに私が見つけたリソースに関する短い投稿
この記事はhttps://benjaminlistwon.com/blog/vue-20/の翻訳です。翻訳を快諾してくれたBenjaminさん、ありがとうございました。
原文のくだけた感じを残しつつ翻訳するように努めましたが、日本語がこなれていない部分もあるかと思います。そのような箇所を見つけた場合はコメント欄にて指摘してもらえると幸いです。
自分のVueアプリを2.0に移行し始めた件、及びこれまでに私が見つけたリソースに関する短い投稿
2016/9/14
数週間前に私は休暇から戻り、息子は新学期が始まったので、再び自宅オフィスは平穏で静かになりました。私が留守の間、VueのアプリでHandsontable(HOT)を使うために私が書くと言及していたVueコンポーネントに誰かが着手していました。私は素晴らしい将来のために、それをVue 2.0に書き換えようとしており、それが済んだら喜んでGitHubに追加したいと私は述べていました。
おやおや、大変な目に逢いそうだなぁ。
3行で頼む この記事の末尾にリソースのリンク集があります。またVue 2.0のHOTコンポーネントは来週完成の予定です(訳注:9/28付の記事"VUEX CHAT PART 2"によると「作業中」)。それまでの間、もし0.xのHOTコンポーネントを探しているなら、あなたはfundonによる偉大なプロジェクトを形成しているこのサンプル(訳注:リンク切れ修正)をベースにできます。
ところでVue 1.xについては?
確かに、もし少しの間Vueから離れていた、あるいは多分ちょっと瞬きしていただけでも、あなたは多くを逃している可能性があります。いくつかのハイライトを要約します:
- 2016年6月10日:v2.0.0-alpha.1 最初のv2リリース(続く6日間で3つ以上)
- 2016年6月16日:v1.0.25 まだv1で懸命に取り組んでいます
- 2016年7月28日:v1.0.26 現在の1.x安定版リリース
- 2016年7月7日:v2.0.0-beta.1 最初の2.0βリリース(8つのαの後、#やれやれ)
- 2016年8月10日:v2.0.0-rc.1 最初の2.0 RC(8つのβの後、#何ともはや)
- 2016年9月13日:v2.0.0-rc.6(ひと月に6リビジョン!)
- (訳注:2016年10月1日:v2.0正式版リリース)
OK、2.0への移行は何がそんなに難しいの?
私がすぐ見つけたように、様々なRCのリリースノートには「破壊的な変更」という言葉がたくさん使用されています。変更点とその理由、そしてどのように移行するかについてより多く説明しているいくつかの有用なリソースを偶然見つけるまでに私はしばらく時間がかかりましたが、0.8→2.0あるいは1.0→2.0でさえどのように移行するかの文書にはまだ多くのギャップがあります。
Vue 2.0は完全に書き直され、私が見ることができるほぼすべてのもの - より良いコードの構造、優れたパイプライン、より魅力的で、より速く、より少ないリソース消費など - は、どれを取っても2.0がアプリの構築のために本当に素晴らしいことをしようとしていることを意味するものです。
しかし、結果として2.0は間違いなくまだ非常に移ろいやすいです。あなたがA→Bのシナリオを探している場合、Bは良く知られているものですが、それはまだありません。そのことは既存のアプリを変換するのに時間を要することになります。
でも待って、もっとあるよ!
Vueの変更を把握したと思ったちょうどその時、2.0をサポートするためにvuexも2.0になっていたことに私は気づきました!
私のHOTコンポーネントはローカルコンポーネントdata
とcomputed
プロパティで動作するように作られていたため、vuex(訳注:原文の"vex"はtypoと思われる)のstore
を通すのみならず、全面書き直しの羽目になることも私は理解しました。
Vuexの変更はVueの変更と連動しており、ぶっちゃけ、主にmapGetters
、mapActions
、mapState
そしてmapMutations
の追加は、多くのVue自体の変更よりも私にとってはより魅力的です。
これら四騎士(訳注:mapGetters
、mapActions
、mapState
、mapMutations
の4つ)はヨハネの黙示録のそれではなく、その代わりあなたがコンポーネントを書く生活をものすごく楽にします。これらをES6のスプレッド演算子(...
)と組み合わせることで、すべてのimport foo from vuex/action
、その他過去にコンポーネント上で散らかしたもの無しに、あなたは非常に簡単にstoreのイベントやアクションにコンポーネントを添付できます。
マジで、mapState
はあなたの人生を変えるでしょう。
刑事コロンボが言うように、「あとひとつだけいいですか」
vue-routerのことを思い浮かべたあなた、大変良くできました。どのようにvuexが変更をディスパッチするかを同期し、一般的にして保持しするために、vue-router(訳注:原文の"vie"はtypoと思われる)は新しいリリースの束を持っています。最大の変更は最初のβ版のリリースノートで文書化されています。
正直に言うと、私はこれまでvue-routerの変更をこんなに深く掘り下げたことがありませんでした。なぜなら私のアプリのほとんどが「ページ」全体をロードするよりも、component :is=
、またはv-for
ループの一部として一緒に使用するtype
変数に基づいて動的にコンポーネントを挿入するためです。
アプリで「戻る」機能をサポートしたい場合、私はここで深堀りする必要があることを期待しています。
虎穴に入らずんば虎児を得ず
0.x上のこれら多くのためのこの中ですべての大きな変化 - 少なくともこれらオーサリングコンポーネントまたはより複雑な、データ駆動型アプリのためのもの - は2つの基礎的な変更に要約されます:
1. Vue 2.0では$dispatch
と$broadcast
が非推奨なので、もしあなたがコンポーネント間、またはアプリ内でのどこであってもイベントリスニングのためにそれらを使用している/していた場合、$emit
と$on
に移行する必要があります。これは単一のコンポーネント内ではあまり悪いことではありません(ほぼ単なる検索&置換)が、あなたが集中store、またはvuexを使用していなかった場合には、コンポーネント間通信は巨大な書き直しに等しいです。
2. Vuexは現在ミューテーションを次々commit
するアクションをトリガするためにdispatch
を使い、そしてdispatch
とcommit
の両方はもはや可変引数を取りません。すなわち、それらは固定数の引数を受け入れるだけであり、1番目はトリガされるアクションまたはミューテーション、2番目はペイロード(より多くの構造化データのblobを持つ必要がある場合は、オブジェクトにすることが可能)です。
このすべてがより良くするためのものです。2.0で動くようにスタンドアロンコンポーネントとアプリを移植した結果、私はVueがどのように動作するかについてより学んだだけでなく、すこし優れた、よりレスポンシブルなプログラマーになりました。
ここで重要なのは、Vueの基本(またはリアクティブなフロントエンド)は、コンポーネントはデータによって駆動されるべきであるということです。以上。
全てのデータの変更はそれらが発生するコンポーネントに反映され、そしてmapState
のようなツールの魔法はそこに出番があり重宝します。多く(ほとんど?)のケースでは追加のgetterを構築する必要はありません、なぜならcomputed
プロパティオブジェクトは直接共有状態への参照を含めることができるからです。
それ以上に、多くの理由によりデータのミューテーションを求めるどんなユーザーインタラクションもそのデータを直接操作すべきではありません。少なくともユーザーアクションを直接ミューテーションに縛ることはあまりに密結合なためそのアクションの将来の変更や修正が許されなくなることを意味します
このことはvuexのmodules
を動かす方法のためコードを少し冗長にします。サンプルやドキュメントでも、アクション、ミューテーション、その他に分割された面倒な個別のファイルを持っています。数十のモジュールのために数百の機能を持っていたファイルの単一モジュールのために何かを突きとめることはしばしば複雑でした。
2.0では、モジュール自体の中にミューテーションをカプセル化し、アクションのすべてをstoreのルートに配置することが容易です。これはモジュール間のミューテーションを構成することができるアクションを可能にし、すべてのアクションを参照するための単一点を提供します。より良いカプセル化のためにはいいですよね。
私は2.0に移行すべき?
- もし本番アプリで作業している場合は、いいえ、まだです。
- もしちょうど始めようとしている場合は、はい、2.0で始めましょう。
- もしなんとなく何かの途中、または周辺をいじっている場合は、それは間違いなくあなたがより良いアプリを書くのに役立ちますので、調べる価値があるかもしれません。
助けて、ドキュメントが無いよ
確かに、現時点では"どうやって ○○ Vue 2.0"でググっても100のStackOverflowの答えを得ることは期待できません。なので、私は可能な限りネットの海を彷徨って(訳注:原文の"beens"は"been"のtypoと思われる)、移行を容易にする(そしておそらくあなたを1,2錠のイブプロフェン(訳注:頭痛薬)から守る)のに役立ついくつかのリソースをここに置きます:
- Vue 2.0アナウンス:2.0がキックオフされています
- Vue 2.0変更リスト:何が入って、何が無くなり、そして何が変わったかの仕様書とチェックリスト
- Vue 2.0リリースノート:2.0が登場するのに合わせてフォローしてください
- Vue 2.0ドキュメント:私はこれを見つけるのに長い時間がかかりました。大切にし、共有し、決してリンクを失わないように!(訳注:現在はhttps://vuejs.org/が2.0化されています)
- Vue 2.0スターターキット:ちょうど今日見つけました。webpackを使用している場合は大きな修正を1つ備えています。
- Vuex 2.0リリースノート:Vueのリリースと足並みを揃えていますが、vuex固有の変更が含まれています
- Vue-router2.0リリースノート:これもまたVueのリリースと足並みを揃えています。
- Vueフォーラム:ぜひ参加しましょう。問題を解決するたくさんの議論とグループ。
- Gitter上のVue:slackと似ていますが、よりコアな技術オタクのために。
Vue.js 2.0でtwoWayなカスタムディレクティブを実装する方法
以下はvuejs-jp slackに投稿したネタですが、ここのブログにも書いておきます。
先日正式リリースされたVue.js 2.0では、React.jsと同様のVirtual DOMやOne way data flowが採用され高速描画が可能となった反面、ディレクティブは大幅に機能縮小され、"twoWay: true"による双方向データ同期機能は無くなってしまいました。
migration方法としては「コンポーネントで書き直してください」が公式回答なわけですが、正直言うとその辺の辛さがあってReactではなくVueを使っていた人間としては「うーむ」という感想なのです。ダッタラ初めからReactで良くね?…
というわけでいろいろ試行錯誤したところ、実はちょっとしたテクニックで2.0でも依然としてtwoWayなカスタムディレクティブを実装可能なことがわかりました。
ポイントはv⇒vm方向のデータ同期に使っていた
this.set(val);
を
Vue.set(vnode.context, binding.expression, val);
に置き換える、になります。
これだけでは分かる人にしか分からないので、以前に作成した3-stateチェックボックスの例で具体的に説明します。
1.0/2.0それぞれのLiveデモは以下
まず前提知識として、2.0ではディレクティブがインスタンスを持たなくなりました。これはすなわち1.0での"this.el"や"this.set"等が軒並み使えなくなったことを意味します。
これと"twoWay"プロパティの廃止をもって「双方向なカスタムディレクティブは作れなくなった」というわけです。
一方、one wayすなわちvm⇒v方向のデータ同期は従来と同じupdateフック関数で対応できますが、引数が1.0とは大きく異なります。
2.0のフック関数には、bind, unbind, update他の全関数共通で引数として"el", "binding", "vnode"が渡されます。それぞれ、
el: 1.0における"this.el"に対応
binding: ディレクティブの各種引数
binding.value: ディレクティブにbindされたvmオブジェクトの現在値
vnode: ディレクティブに対応するVirtual DOMのnode
という意味なので、update関数は
- update: function(val) { - threeStateByVal(this.el, val); + update: function(el, binding) { + threeStateByVal(el, binding.value); },
という修正で事足ります。
後はv⇒vm方向のデータ同期で使っていた"this.set"の代替ですが、2.0の各種ドキュメントをいろいろ漁っていたところ
vm.$setdeprecated, use Vue.set
という記述を発見しました。これはもしかして、と思ってVue.setの仕様を確認すると、
Vue.set( object, key, value )
Arguments:
{Object} object
{string} key
{any} valueUsage:
Set a property on an object.
となっています。valueは設定したい値そのものなので、あとはobjectとkeyに相当する値、それぞれvmオブジェクトと変数名をどっかから調達できればなんとかなりそうです。
ここで改めて2.0のフック関数に渡される引数を精査したところ、
binding.expression: ディレクティブにbindされたvmオブジェクトの変数名
vnode.context: ディレクティブにbindされたvmオブジェクト
であることがわかりました。ということは、
object = vnode.context
key = binding.expression
value = (v⇒vm方向に同期したい値)
をVue.setに渡せばよい、ということで一番最初に書いたポイントのとおりとなります。
ここまでの内容を整理すると、以下のdiffとなります。
--- 1.0 Sat Oct 22 17:30:36 2016 +++ 2.0 Sat Oct 22 17:31:52 2016 @@ -1,17 +1,17 @@ Vue.directive('three-state', { - twoWay: true, - bind: function() { + //twoWay: true, + bind: function(el, binding, vnode) { this.handler = function() { - var val = threeStateByClick(this.el); - this.set(val); + var val = threeStateByClick(el); + Vue.set(vnode.context, binding.expression, val); }.bind(this); - this.el.addEventListener('click', this.handler); + el.addEventListener('click', this.handler); }, - unbind: function() { - this.el.removeEventListener('click', this.handler); + unbind: function(el) { + el.removeEventListener('click', this.handler); }, - update: function(val) { - threeStateByVal(this.el, val); + update: function(el, binding) { + threeStateByVal(el, binding.value); }, });
Chromeのinput[type=date]タグ内に和暦を表示する
HTML5でinputタグに日付入力に特化したtype="date"及び"month"が追加され、特にChromeでは標準のDatePickerが和暦をサポートしているという便利な世の中となりましたが、一方で入力後の値は依然として西暦表示のままです。
↓
これを何とかしてinputタグ中に常時和暦を表示したいというのが今回のお題です。
ググると、「Chromeの"Shadow DOM"である"input::-webkit-datetime-edit-year-field"に対してbefore/after疑似要素を適応して…」というものがいくつかヒットしますが、残念ながら最新のChromeではShadow DOMに対する(before/after疑似要素を含む)各種セレクタは無効なようです。
https://bugs.chromium.org/p/chromium/issues/detail?id=605466
ですが、inputタグそのものに対するbefore/after疑似要素はver52.0現在でも有効かつinputタグの中にレンダリングされるので、これを使って和暦表示を追加してみます。
<style> input.era[data-year]::before{ content: attr(data-year); font-size: smaller; vertical-align: bottom; position: relative; top: 0.1em; } </style> <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> <script> (function($) { $.fn.date_era = function(options) { function _setYearAttr(e) { var obj = (typeof this.each == "function") ? this : $(this); obj.each(function() { var $date = $(this); var date = $date.val().split("-"); var year = +date[0]; if (year > 1988) date[0] = "H" + (year - 1988) ; else if (year > 1925) date[0] = "S" + (year - 1925); else date[0] = ""; $date.attr({ "data-year": date[0] }); }); } _setYearAttr.call(this); $(this).on("change", _setYearAttr) }; })(jQuery); </script>
このようなStyleSheetとJavascriptを書いておくと、
<body> <div> <input type="date" class="era"> <input type="month" class="era"> </div> <script> $(document).ready(function() { $("input.era").date_era(); }); </script> </body>
という感じで、目的のinput[type=date|month]タグに対してdata_era()で初期化すると西暦の直前に和暦が合わせて表示されるようになります。
jQueryの.val()によるプログラムでの値変更の場合は.val("2016-07-30").trigger("change")のようにchangeイベントが発生するようにしてください。
(元号再計算はonChangeハンドラで行っており、かつ.val()による値設定はchangeイベントを発生させないため)
また、今のJavascriptは手抜きして平成"H"と昭和"S"しかサポートしてませんが、必要であれば同様の方法で判定ロジックを追加することで対応可能です。
せっかくなので、必要性が薄いスピンボタンを消去して和暦表示用の幅を確保し、かつafter疑似要素も使ってDaterPicker起動アイコンを▼からカレンダーに置換えるためのコードも追加すると、
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <style> input.era::-webkit-inner-spin-button{ display: none; } input.era::after{ color: #666; font-family: 'Material Icons'; content: "\E916"; /* date-range */ position: relative; left: -1em; width: 0; margin: 3px 0; } input.era::-webkit-calendar-picker-indicator{ color: transparent; background-color: transparent; z-index: 1; } </style>
Vue.jsでtemplateURL風機能を実現する
Vue.js が templateURL をサポートしない理由というVue.js作者自身の記事があるように、本来Vue.jsでテンプレートやWebComponent開発を行うには.vue形式でソースを記述し、vueify(browserify)かvue-loader(webpack)といったビルドツールを使うのが筋です。
そして、これらツールを使いこなすためにはツールの実行環境であるnode.jsエコシステムも理解する必要があります。しかもnode.jsエコシステムは流行り廃りが激しく、その流れをキャッチアップし続けるのも正直しんどいです。
そうなると、本人の努力で何とでもなる趣味のプログラムや個人プロジェクトならともかく、特に企業内でのチーム開発プロジェクトにこれらの仕掛けを導入するのはなかなか骨が折れるのではないでしょうか。
いくら「Vue.jsの学習コストは低い」と言っても、「エディタとブラウザさえあれば開発できる」というスタイルからは一気にハードルが上がるからです。
もっとはっきり言うと、このハードルを越えた(あるいはハードルですらない)人たちはどんどんReact.js/Reduxでの開発に移行しているのが現状ではないかと。
であれば、「ビルドツールに高い学習コストを払って、あえてVue.jsを使うメリットは何ですか?」という本質的な疑問にぶつかることになります。
では、そういう状況下に置かれた開発者はテンプレートやWebComponentのメリットに背を向け、旧態依然としたコピペコードを書き続けるしかないのでしょうか?
React.jsの対抗馬としてRiot.jsが取りざたされているのも、「ビルド無しでもWebCompnent開発ができる」というお手軽さが受けているからではないでしょうか(もちろん事前ビルドも可能)。
というわけで、最初に紹介した記事が意味するところも理解したうえで、それでも「Vue.jsでもビルド無しでテンプレートが使いたい」という人向けの紹介となります。
まず、Vue.jsには"Vue.extend"や"Vue.Component"による「コンポーネント」機能が用意されており、これを使うと、HTMLのテンプレートを"template"プロパティに(Javascriptの)文字列として登録することができます。
また、別の方法として<script type="text/x-templete">タグでHTML中に記載することも可能です。
しかし、前者は文字列、後者はカスタム属性であるがゆえに所謂シンタックスハイライトが効きません。さらに、後者はHTML中にインラインで記述する必要があり、外部ファイルへの外出しもできません(ブラウザが読み込めないから)。
しかし、jQuery等のAjaxライブラリでは外部のHTMLファイルを読み込むことが可能です。そしてVue.componentのtemplateプロパティは「非同期コンポーネント」機能により、ファクトリ関数で設定することができます。
これを組み合わせると、例えばこんな方法でVue.Componentのtemplateプロパティに外部HTMLファイルの記述内容を設定することが可能となります。
Vue.component("my-component", function(resolve) { $.get("component.vue.html", function(data) { resolve({ template: data, props: [ "msg" ] }); }); });
component.vue.htmlには従来文字列やx-templateとして与えていたHTMLをそのまま記載できます。単なるHTMLファイルですからシンタックスハイライトが効きます。
<span>{{ msg }}</span>
テンプレートのHTMLファイルはAjaxで読み込みますから必ずWEBサーバでホストされる必要があります(file://プロトコルでは動作しません)。これは最初の記事にデメリットとして書かれているとおりです。
しかし、現在のWEBアプリ開発においてはREST APIコール無しに動作するもののほうが少ない(=開発環境でも何らかのWEBサーバでのホストが前提)のではないでしょうか。
そうであれば、この点に関してはデメリットですらないことになります。それよりも、シンタックスハイライトやテンプレート共通化によるメリットのほうがはるかに大きいかと。
まずは、この方法で「コピペコードからおさらばする」というメリットを皆が享受し、その実績を背景に「ビルドツールを使ったWebComponent開発に移行しましょう!」とアピールしてみてはどうでしょうか。
そうすれば闇雲に「今はビルドツールを使うの当たり前」と正論を吐くだけよりは、重い腰を上げてくれる可能性が上がるのでは?