Vue.jsのv-classとv-model

このエントリは、v-classを初めて使ったのでその説明だけ載せるようと思っていましたが、説明用に作成したコードで詰まりかけたので、
v-modelとの兼ね合いも書いておきます。

チェックボックスで使うv-class

v-classは、Vue.jsオブジェクトの値を元に、classを設定するディレクティブになります。

シンプルな形で書いてみます。

<div id="app">
  <input type="checkbox" v-model="hoge">hoge
  <input type="checkbox" v-model="hage">hage
  <hr>
  <p v-class="bold: hoge">hoge</p>
  <p v-class="bold: hage">hage</p>
</div>
var vm = new Vue({
  el: '#app',
  data: {
    hoge: false,
    hage: false
  }
});
.bold {
  font-weight: bold;
}

hogeをチェックすると、下のhogeが太字になります。hageも同様です。 このとき、v-modelでvue.jsに渡されるのは、trueかfalseです。 v-classで真偽値を扱う場合は、bold: hogeのように引数をつけます。

セレクトボックスで使うv-class

では、selectタグのように、v-modelで真偽値ではなく、文字列が渡される場合はどのようにしたら良いのでしょうか。 また簡単なサンプルを載せます。

<div id="app">
  <select v-model="freeza" size="4">
    <option value="first-form">第1形態</option>
    <option value="second-form">第2形態</option>
    <option value="third-form">第3形態</option>
    <option value="fourth-form">第4形態</option>
  </select>
  <div v-class="freeza"></div>
</div>
var vm = new Vue({
  el: '#app',
  data: {
    freeza: ""
  }
});
.first-form {
  background: url("../images/freeza-first.jpg");
  width: 400px;
  height: 232px;
}

.second-form {
  background: url("../images/freeza-second.jpg");
  width: 280px;
  height: 300px;
}

.third-form {
  background: url("../images/freeza-third.jpg");
  width: 200px;
  height: 288px;
}

.fourth-form {
  background: url("../images/freeza-fourth.jpg");
  width: 280px;
  height: 460px;
}

例えば、セレクトボックスの第1形態を選択すると、divのbackgroundで第1形態の画像がでてくるようにしました。 この場合、選択されていない状態は

data {
  freeza: ""
}

で、 選択されるとvalueの値がfreezaに設定されるイメージです。

data {
  freeza: "first-form"
}

divのclass名はv-class="freeza"としてありますが、 v-classは引数無しだと、vueオブジェクトの値をそのままclass名にしてくれます

これは実際セレクトボックスを選択したあとv-class="first-form"になります。 そのため、cssにはoptionのvalueをそのままクラス名として書けばOKなのです!

webpackの基本的な使い方(+Vue.js)

今まで自分で作ったプロジェクトは、ファイルは基本的な以下の3つで作成され、

ライブラリはVue.jsをインストールしているだけでした。 Vue.jsはBowerでインストールし、CSS,JavaScriptファイル(とVue.js)はHTMLから読み込んでいました。

実際にWeb開発をする、となった場合もっとたくさんのライブラリを使うだろうし、HTML,CSSコンパイルが必要なAltなんとかを使うこともあるでしょう。

今回のような単純なプロジェクトでは恩恵はあまりないかもしれませんが、モダンなWeb環境構築には慣れておかないといけないため、 webpack を利用してみることになりました。

やりたいこと

以下のような3つのファイルを用意します。Vue.jsを使って、p要素に赤文字の"hoge"が出力されるだけのページです。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>webpack test</title>
</head>
<body>
  <div id="app">
    <p>{{text}}</p>
  </div>
  <script type="text/javascript" src="js/app.js"></script>
</body>
</html>
/** reset **/
html, body, div, form, fieldset, legend, label {
  margin: 0;
  padding: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

th, td {
  text-align: left;
  vertical-align: top;
}

h1, h2, h3, h4, h5, h6, th, td, caption {
  font-weight:normal;
}

img {
  border: 0;
}

/* style */
#app p {
  color: #d33;
}
var vm = new Vue({
  el: '#app',
  data: {
      text: "hoge"
  }
});

ディレクトリ構成はこのようになっています。

├── css
│   └── style.css
├── index.html
├── js
│   └── app.js
└── node_modules

上記の形から、vue.js部分を外出しして、以下のようにします。

  • app.htmlとapp.cssを追加
.
├── app.css
├── app.html
├── css
│   └── style.css
├── index.html
├── js
│   └── app.js
└── node_modules
  
  • app.html
<div id="app">
  <p>{{text}}</p>
</div>
/* style */
.test {
  color: #d33;
}

これから切り出したapp部分をwebpack側で読み込んで管理するようにします。 こうしておけばたとえHTMLやCSSが膨大になっても、機能単位で切り分けされてるので今後の開発がやりやすくなるでしょう!

webpackを使う前にpackage.jsonを作成してVue.jsをインストールしておきます。

npm init
npm install vue --save

インストール

webpackのインストールは、適当なプロジェクト用のディレクトリを作って、 npmコマンドでインストールします。

npm install webpack --save-dev

webpackのような開発用のツールは、本番環境では使わないので、-devオプションをつけて、package.jsonのdevDependenciesに追記されるようにします。

  "devDependencies": {
    "webpack": "^1.10.5"
  }

ひとまずwebpackコマンドが使えるようになりましたが、実際にあるファイルをパス指定してコマンド実行するのは面倒くさいので、グローバルにもインストールしておきます。

npm install webpack -g

webpackをプロジェクトで利用する準備

webpackは指定したJavascriptファイルをコンパイルして、そのコードをそのまま含むバンドルファイルを作成します。 このバンドルファイル、 bundle.jsによって依存関係などを気にせず開発ができるようなので、とりあえずindex.htmlで読み込むようにしておきましょう。 また、Vue.jsを読み込む部分と、app.jsの読み込む部分は今後webpackから読み込むので削除します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <!-- <script type="text/javascript" src="js/vue.min.js"></script> -->
    <link rel="stylesheet" type="text/css" href="css/style.css">
    <title>webpack test</title>
</head>
<body>
  <div id="app">
    <p>{{text}}</p>
  </div>
  <!-- <script type="text/javascript" src="js/app.js"></script> -->
  <script type="text/javascript" src="js/bundle.js"></script>
</body>
</html>

さらに、app.jsにVue.jsを読み込む記述を追加します。

var Vue = require('vue');

var vm = new Vue({
  el: '#app',
  data: {
      text: "hoge"
  }
});

とりあえず実行する

webpack js/app.js js/bundle.js

こちらを実行すると、webpackによってtodo.jsがコンパイルされてbundle.jsが作成されます。 成功すると以下のようなステータスが表示されます。

Hash: 49d346fa536be96181b6
Version: webpack 1.10.5
Time: 42ms
    Asset     Size  Chunks             Chunk Names
bundle.js  1.46 kB       0  [emitted]  main
   [0] ./js/app.js 70 bytes {0} [built]

ブラウザ上でもちゃんとvue.jsの処理も動いているのが確認できます。

設定ファイルの作成

webpackコマンドに引数でファイルを指定するのは面倒なので、 webpack.config.js を作って、webpackだけでコンパイル可能にしておきます。

module.exports = {
  entry: './js/app.js',
  output: {
    path: __dirname,
    filename: './js/bundle.js'
  }
};

これで、javascriptはとりあえずwebpackで管理されるようになりました。

HTML, CSSも管理する

やりたいこと で書いてあることと同様、app.htmlとapp.cssを作って、元ファイルから被るコードは削除しておきます。

  • index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <script type="text/javascript" src="js/vue.min.js"></script>
    <link rel="stylesheet" type="text/css" href="css/style.css">
    <title>webpack test</title>
</head>
<body>
  <!--
  <div id="app">
    <p>{{text}}</p>
  </div>
  -->
  <!-- <script type="text/javascript" src="js/app.js"></script> -->
  <script type="text/javascript" src="js/bundle.js"></script>
</body>
</html>
/** reset **/
html, body, div, form, fieldset, legend, label {
  margin: 0;
  padding: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

th, td {
  text-align: left;
  vertical-align: top;
}

h1, h2, h3, h4, h5, h6, th, td, caption {
  font-weight:normal;
}

img {
  border: 0;
}

/* style */
/*
#app p {
  color: #d33;
}
*/

HTML, CSS用ローダーのインストール

webpackデフォルトではHTML, CSSを読み込めないので、専用のモジュールをインストールしておきます。 ここでも --save-devオプションは指定しておきます。

$ npm install --save-dev html-loader css-loader style-loader

設定ファイルでモジュールを読み込む

webpack.config.jsにモジュールを読み込む記述が必要です。

module.exports = {
  entry: './js/app.js',
  output: {
    path: __dirname,
    filename: './js/bundle.js'
  },
  module: {
    loaders: [
      { test: /\.html$/, loader: 'html?minimize' },
      { test: /\.css$/, loader: "style!css" }
    ]
  }
};

JavaScriptでHTML, CSSを読み込む

app.jsにHTMLとCSSを読み込む記述を追加します。 相対パスであることに注意してください。

var html = require('../app.html')
document.body.innerHTML = html;

require('../css/style.css');
require('../css/app.css');

var Vue = require('vue');

var vm = new Vue({
  el: '#app',
  data: {
      text: "hoge"
  }
});

webpackコマンド

これで、ファイルを切り分けても無事元と同じ画面が出力されるようになりました。 最後に、webpackコマンドは webpack —watch として実行することによって、ファイルの更新を監視することができます。そうしておけば、ファイルを更新してまたコマンドを打たなくても、webブラウザのリロードだけで確認できるようになります。

gitで管理する場合

webpack --watchしていると、bundle.jsはどんどん更新されていてgitでは面倒なことになるので、.gitignoreに記述しておきましょう。

Vue.jsのコンポーネントオプション(computed, created)

前回はHTML側に指定されている、v-repeatv-modelを確認したので、
今回はJavaScriptコード内にある、コンポーネントオプションの確認をしていきます。

コードの確認

var app = new Vue({
    el: '#todo',
    data: {
        todos: []
    },
    computed: {
        lefts: function(){
            return this.todos.filter(function(t){ return !t.isDone }).length;
        }
    },
    created: function(){
        this.title = "My Todos";
        var suffix = ["a","b","c","d","e"] 
        for(var i = 0; i < suffix.length; i++){
            this.todos.push({ isDone:false , title:"task-" + suffix[i] });
        }
    },
    methods:{
        addTodo: function(){
            this.todos.push({ isDone:false , title:"task-" + this.todos.length });
        }
    }
})

1st stepに使用されていたのは、el, data, methodだけでした。 今回は、

  • computed
  • created

が新しく登場しているので、こちらの確認をしてみます。

computedオプション

computed: {
      lefts: function(){
          return this.todos.filter(function(t){ return !t.isDone }).length;
      }
}

computedオプションの、leftsプロパティは, HTML側を見ると、

<div>
    left {{lefts}} tasks.
</div>

Mustache形式でバインドされています。 この部分は、残っているtodoの数を表示するためのものです。

元記事の説明文を参照すると・・・

dataの値を利用した計算式のようなフィールドを表現するのに使用する。

ということなので、まさしくtodoの残り数を計算するために、computedを使っているのでしょう。 もう少し、leftsの中で何をやっているか見てみます。

lefts: function(){
          return this.todos.filter(function(t){ return !t.isDone }).length;
}

this.todosですが、以下のtodosプロパティの配列を指しています。

  data: {
      todos: []
  },

この配列に入ったデータにたいして、filterメソッドを使用しています。 ここでは、「todosの配列の中にある要素で、t.isDoneではないもの(false)」の配列を作成し、さらに.lengthメソッドで配列の要素の数を返す」ということを行っています。

createdオプション

  created: function(){
      this.title = "My Todos";
      var suffix = ["a","b","c","d","e"]
      for(var i = 0; i < suffix.length; i++){
          this.todos.push({ isDone:false , title:"task-" + suffix[i] });
      }
  },

createdオプションは、ガイドを見ると、

DOM のコンパイルは開始されておらず、$el プロパティはまだ有効ではありません。

元記事では、

createdは、初期化処理に使用できる。

とのことなので、DOM生成前にデータを作成しておきたい場合に使用するオプションのようです。 (※コードを見るとまずthis.titleとありますが、 HTML側にはバインドされておらず、実際に使用されていないようなのでこれは無視します。)

次に、アルファベットの文字を格納したsuffixという配列を用意しています。 次のfor文ではtodosの配列に、{ isDone:false, title:"task-" + suffix[i] }という値をpushメソッドによって追加しています。

つまり、todosという配列には、(前回の記事)[]で確認したv-modelディレクティブによって渡される、

  • チェックボックスのON/OFFを判定する isDoneプロパティ
  • 画面に表示される文字列(0:task-a)の titleプロパティ

が入っており、createdオプションによってTODOの初期データが生成されていることがわかりました。

Vue.jsのリアクティブディレクティブを幾つか試す その2

前回は1st stepにあるリアクティブディレクトリを試したので、今回も続いて2nd Stepにあるリアクティブディレクトリについて確認してみたいと思います。

まず2nd Stepの動作確認

My_Todos.png

最初から5つのtodoが用意された状態で、チェックをするとチェックされたtodoが一番下に移動します。
ただし、todoの名前の一文字目の数値は、移動したらその場所の数値に変化しています。(これは0から始まっているため、「0:task-a」をチェックした場合「4:task-a」になります)
「add todo」ボタンを押すと、一番下に新しいtodoが追加されますが、最初に用意されていたtodoとは違い、最後の文字はアルファベットではなく番号が振られるようになります。
「left 5 tasks.」の部分は、チェックしていないtodoの数が表示されています。

コードの確認

<div id="todo">
    <h1>My Todos</h1>
    <div v-repeat="t : todos | orderBy 'isDone'">
        <input type="checkbox" v-model="t.isDone" />
        {{$index}}:{{t.title}}
    </div>
    <input type="button" v-on="click: addTodo" value="add todo" />
    <div>
        left {{lefts}} tasks.
    </div>
</div>

1st stepには無かったリアクティブディレクティブ、

  • v-repeat
  • v-model

が書かれています。今回はこの2つを見ていきます。

v-repeat

<div v-repeat="t : todos | orderBy 'isDone'">

v-repeatは、配列やオブジェクトに複数の要素を入れていくためのディレクティブです。 v-repeatに指定されている値はtodosですが、こちらはJS側のコードを見ると空の配列データになっています。

  data: {
      todos: []
  },

さらに、todosにはtの引数が指定されています。 公式サイトのガイドを見ると・・・

引数が与えられたときは、エイリアスキーを引数の文字列としてラッパーのデータオブジェクトが常に作られます。これにより、テンプレート内でのより明示的なプロパティへのアクセスが可能になります

この引数によって、todosのプロパティisDoneなどは、t.isDoneといった形でアクセスが可能になっているようです。

フィルタ

上記のv-repeatには、| orderBy 'isDone'">が書かれています。 これはVue.jsのフィルタ機能で、orderBy構文によって、repeat-vの結果を'isDone'をキーにしてソートしています。

v-model

<input type="checkbox" v-model="t.isDone" />

v-modelはinput要素にだけ使用できるディレクティブです。 ガイドでは「双方向バインディングを作成」すると書かれていますが、
これはつまり、
input要素(例えばtextbox)へユーザーが値を入力 -> その値がVue.js側のオブジェクトに反映 -> 別のDOMにバインド
といったことが可能になります。 今回の"t.isDone"は、チェックボックスのON/OFFにバインドされており、Vue側ではtrue/falseが反映されるようです。

$index

{{$index}}:{{t.title}}

このMustacheを見ると、後者はtodosの要素、titleをtextContentにバインドしています。
$indexですが、ガイドでは

レンダリングされたインスタンスの配列インデックスに対応する $index プロパティにもアクセスすることができます。

とのことなので、配列インデックスの数字をバインドしているだけということがわかりました。

コンポーネントオプション

続いてJS側のコードを見ていきたいと思いますが、コンポーネントオプションが中心になりますので、記事を分けます。

Vue.jsのリアクティブディレクティブを幾つか試す その1

qiitaに投稿した記事そのままです。

Vue.jsのリアクティブディレクティブを幾つか試す

こちらの記事の1st stepをベースに、spanタグの中にあるリアクティブディレクティブを試しました。 公式サイトのAPIリファレンスを一度見てもよくわからないことが多いので、実際に動かしてみてどのような命令なのか確認してみます。

まず1st Stepの動作確認

クリックすると、Hello Vue.jsのフォントサイズと色が変わりました!

コードの確認

詳しい説明は元記事に書いてありますが、自分なりに解釈してみます。

<div id="simple">
    <span v-text="message" v-on="click: magnify" v-attr="style: font"></span>
</div>
var app = new Vue({
    el: '#simple',
    data: {
        message: "Hello Vue.js",
        font: ""
    },
    methods:{
        magnify: function(){
            this.font = "font-size:20px;color:red"
        }
    }
})

Vueインスタンスの生成で代入されたappは ViewModelインスタンス になります。 Vueインスタンスの引数にある、 el (コンポーネントオプション) は、HTML要素のidを指定しており、これによって<div id="simple">以下のDOM要素はVue管理下となります。

data、methodsのデータは、HTMLのspanタグにあるv-から始まる属性(リアクディブディレクティブ)によって、様々な命令でバインドすることができます。バインドされた要素は同期されるため、Vueインスタンス内にある値の更新によって変更することができます。

v-text

spanタグのテキストは何も入っていませんが、messageプロパティの値、"Hello Vue.js"が、v-textでバインドされているため、DOMのtextContentが更新されています。

<span>Hello Vue.js</span>

v-textをMustacheバインディングに置き換え

{{ Mustache }}という書き方でバインドすることもできるようなので(Mustacheバインディング)、上記のv-textでの指定方法を変えてみました。

<div id="simple">
    <span v-on="click: magnify" v-attr="style: font">{{message}}</span>
</div>

同じようにtextContentが更新されました!

v-attr

v-attrは、v-textと違い、style:という引数が指定されています。 このディレクティブは、引数に指定した属性で要素を更新する役割ですが、fontプロパティが空の文字列になっているので、バインドされていても最初はspanに属性が追加されません。 わかりやすいようにスタイルを指定してみます。

var app = new Vue({
    el: '#simple',
    data: {
        message: "Hello Vue.js",
        font: "color:green"
    },
    methods:{
        magnify: function(){
            this.font = "font-size:20px;color:red"
        }
    }
})

style属性が追加されて「Hello Vue.js」の文字が緑色になりました。

<span style="color: green">Hello Vue.js</span>

v-style

v-attrでスタイルを変更しましたが、CSSスタイル専用のディレクティブ、v-styleがあるので、こちらで上記と同じスタイルの更新をしてみます。 この場合は命令がスタイルの更新と決まっているので、引数が必要なくなります。

<div id="simple">
    <span v-on="click: magnify" v-style="font">{{message}}</span>
</div>

これでも同じように「Hello Vue.js」の文字を緑にすることができました。

<span style="color: green">Hello Vue.js</span>

v-on

引数に指定されているclick:は、イベントタイプです。 その名の通りクリックイベントを監視して、クリックされたらmethodsメソッドを呼び出します。 今回はクリックされたらfontスタイルを変えるだけではなく、テキストも変えてみることにします。

var app = new Vue({
    el: '#simple',
    data: {
        message: "Hello Vue.js",
        font: "color:green"
    },
    methods:{
        magnify: function(){
            this.message = "Magnified"
            this.font = "font-size:20px;color:red"
        }
    }
})

これでクリックするとtextContentが"Hello Vue.js"から"Magnified"に変わり、fontスタイルも変更されました。

<span style="font-size: 20px; color: red;">Magnified</span>

Bowerを利用してVue.jsを使えるところまでのインストール手順

qiitaへ投稿した記事そのままです

Bowerを利用してVue.jsを使えるところまでのインストール手順

Vue.jsに入門するため、とりあえず動かすことができる環境を作りました。 ローカルでhtmlファイルを作成して、公式サイトの「10秒で分かる例」を動作させることを目標にします。 ついでにJavaScriptのエコシステムを体験したいので、Bowerからインストールしていきます。

※ Node.jsのインストールは省略します。

Bowerのインストール

$ sudo npm install bower -g
$ bower -v

BowerでVue.js入門用のプロジェクトを作成

プロジェクト用に適当なディレクトリを作成して、そこへ移動

$ mkdir hello_vue
$ cd hello_vue

Bowerの初期化コマンドを打って、bower.json(設定ファイル)を作成

$ bower init

このあとたくさん質問が出てきましたが、今回はname, descriptionだけ指定して、他はEnterもしくはYesにしました。

{
  "name": "hello_vue",
  "version": "0.0.0",
  "authors": [
    "xxx <xxx@gmail.com>"
  ],
  "description": "My first Vue.js application.",
  "main": "index.html",
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

BowerでVue.jsをインストール

$ bower install vue --save

Vue.jsのパッケージは、 bower_components というディレクトリの中にインストールされており、vueディレクトリの中にはなにやらたくさんファイルができています。

$ ls -1 bower_components/vue
LICENSE
bower.json
build
circle.yml
dist
perf
src

bowerはgithubから取ってくるだけなのでこうなっているようですが、今回の例であれば実際は dist の中にある、

  • vue.js
  • vue.min.js

しか必要ありません。

HTMLファイルを作成して、Vue.jsのサンプルコードを動かす

サンプルコードは公式サイトの「10秒で分かる例」そのままです。

  • HTMLファイルのひな形(index.html)を作成
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Hello Vue</title>
</head>
<body>

</body>
</html>
  • Vue.jsを読み込む
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Hello Vue</title>
  <script type="text/javascript" src="bower_components/vue/dist/vue.min.js"></script>
</head>
<body>

</body>
</html>
  • bodyタグの中にサンプルコードを挿入
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Hello Vue</title>
  <script type="text/javascript" src="bower_components/vue/dist/vue.min.js"></script>
</head>
  <body>
    <div id="demo">
      <p>{{message}}</p>
      <input v-model="message">
    </div>
    <script>
    var demo = new Vue({
      el: '#demo',
      data: {
          message: 'Hello Vue.js!'
      }
    })
    </script>
  </body>
</html>

ここで script部分をheadタグの中に入れてしまうと、DOM読み込み前なのでうまく動きませんでした。

最後にWebブラウザでindex.htmlを開いて、動作確認できれば終了です!

番外: gitで上記のようなBowerプロジェクトを管理するとき

パッケージインストール時の--saveオプションによって記載される、 bower.jsonの"dependencies"(依存関係)に書かれているパッケージは、bower installコマンド(パッケージ名を指定しない)で勝手にインストールされます。 そのため、ファイルサイズなどを考慮するとbower_componentsディレクトリ自体をgitの管理対象外にしておいたほうが良いようです。

bower_componentsディレクトリを無視する

  • .gitignoreを作成 ディレクトリ名を一行書くだけです。
bower_components/
  • .gitignoreをstageしてcommit
$ git add .gitignore
$ git commit