Vue.js のコンポーネント  に JSライブラリ を統合する

# はじめに

Vue.js いいですよ、Vue.js。

シンプルでパワフルです。

Angular(JS)と較べて、学習コストが半端なく低い。
直感的にDOM操作を行えます。

同じくMVVMを基盤にするKnockoutJSよりもシンプルに
書けます。

日本語の情報が充実しているので、今以上に
ブレイクする可能性があると思っています。

公式サイトのガイド・APIリファレンスが有志の方のおかげで
日本語にも対応しているので、クライアントJSにあかるい方が
いれば、即採用可能な状況になっています。


# コンポーネント(View Model)へのJSライブラリ組込み

## v3.x

1) JSライブラリをディレクティブとしてラップし、
JSライブラリを適用する要素にディレクティブを独自属性と
して追加する

2) JSライブラリをディレクティブとしてラップし、
JSライブラリを適用する要素にディレクティブを独自属性と
して追加した上で、デイレクティブを適用した要素の親要素を
コンポーネントとしてラップし、コンポーネントを独自タグとして利用する

3) JSライブラリを適用する要素をコンポーネント
としてラップし、JSライブラリを適用する要素のかわりに
コンポーネントを独自タグとして利用する。場合によっては、更に
独自タグの親要素をコンポーネント化する

のどれかで、View ModelにスマートにJSライブラリを統合できます。
v2.xと同じ手法です。

Vue.js の vDOM 実装が書き直され、ディレクティブから
View Model (VM) へのアクセスのしかたが変わっています。

`vnode.context`ではなく`binding.instance`
で VM にアクセスします。

v3.xは、v2.x とはディレクティブetcの挙動が多少、異なりますので、
注意が必要です。


## v2.x

1) JSライブラリをディレクティブとしてラップし、
JSライブラリを適用する要素にディレクティブを独自属性と
して追加する

2) JSライブラリをディレクティブとしてラップし、
JSライブラリを適用する要素にディレクティブを独自属性と
して追加した上で、デイレクティブを適用した要素の親要素を
コンポーネントとしてラップし、コンポーネントを独自タグとして利用する

3) JSライブラリを適用する要素をコンポーネント
としてラップし、JSライブラリを適用する要素のかわりに
コンポーネントを独自タグとして利用する。場合によっては、更に
独自タグの親要素をコンポーネント化する

のどれかで、View ModelにスマートにJSライブラリを統合できます。

v1.xでの DOM に直接アクセスする実装から、v2.xでは、vDOMを介して、DOM に
アクセスする実装に変更されたため、v2.x はv 1.x とはディレクティブetcの
挙動が異なりますので、注意が必要です。

Vue.js 2.0でtwoWayなカスタムディレクティブを実装する方法

http://d.hatena.ne.jp/chi-bd/20161022/1477129270

## v1.0.x
JSライブラリをディレクティブとしてラップし、
JSライブラリを適用する要素にディレクティブを独自属性と
して追加することでView ModelにスマートにJSライブラリ
を統合できます。

1.0.xでも、ディレクティブではなくコンポーネントとしてラップする
ことは可能です。

## ※注意

上記手法を取らず、View Modelの外側から直接
JSライブラリを適用することも可能ですが、
その場合、v-for (旧v-repeat) 等で動的に追加したDOMノード等に
対してJSライブラリを適用できません。

# 組込み例

jQueryプラグイン bootstrap-tokenfield (Vue1.0まで)、
tagEditor (Vue2.0以降)を上記の手法でView Modelに
組み込んでいます。

bootstrap-tokenfield

http://sliptree.github.io/bootstrap-tokenfield/

tagEdtior

https://goodies.pixabay.com/jquery/tag-editor/demo.html

## 1. コンポーネント(View Model)への単純な組込み

### v3.x

[directive / composition api]

https://codepen.io/asip2k25/pen/JjzEqNe

[directive / options api]

https://codepen.io/asip2k25/pen/WNGLOPK

### v2.x

[directive]

https://codepen.io/asip2k25/pen/gOwdaXB

### v1.0

```html
<div id="form">
  <input id="text-content" v-tokenfield="content3" type="text">
  <p>
    <button id="btn-change" v-on:click="on_click">Change Content by JS</button>
  </p>
  <p>
    <button id="btn-log" v-on:click="on_click2">Check VM.content</button>
  </p>
  <p>{{content2}}</p>
</div>
```

### v0.10, 0.11

```html
<div id="form">
  <input id="text-content" v-tokenfield="content3" type="text">
  <p>
    <button id="btn-change" v-on="click: on_click()">Change Content by JS</button>
  </p>
  <p>
    <button id="btn-log" v-on="click: on_click2()">Check VM.content</button>
  </p>
  <p>{{content2}}</p>
</div>
```

### v1.0, v0.11

```
$(function(){
  Vue.directive('tokenfield',{
    twoWay: true,
    bind: function(value){
      var self = this;
      $(this.el).tokenfield();
      $(this.el).on('tokenfield:editedtoken',function (e) {
        self.set($(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:createdtoken', function(e){
        self.set($(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:removedtoken', function(e){
        self.set($(this).tokenfield("getTokensList", ','));
      });
    },
    update: function(value){      
      $(this.el).tokenfield('setTokens',value);
    }
  });
    
  var vm = new Vue({
    el: '#form',
    data: {
      content2: '',
      content3: ''
    },
     methods: {
       on_click: function () {
         this.content3 = 'hi';
       },
       on_click2: function () {
         this.content2 = this.content3;
       }
    }
  });

});
```
v1.0: 
v0.11: 

### v0.10

```
$(function(){
  Vue.directive('tokenfield',{
    bind: function(value){
      var self = this;
      $(this.el).tokenfield();
      $(this.el).on('tokenfield:editedtoken',function (e) {
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:createdtoken', function(e){
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:removedtoken', function(e){
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','));
      });
    },
    update: function(value){
      $(this.el).tokenfield('setTokens',value);
    }
  });
    
  var vm = new Vue({
    el: '#form',
    data: {
      content2: '',
      content3: ''
    },
     methods: {
       on_click: function () {
          this.content3 = 'hi';
       },
       on_click2: function () {
         this.content2 = this.content3;
       }
    }
  });

});
```


## 2. v-for (旧v-repeat) のある コンポーネント(View Model)への組込み

### v3.x

[directive+component / composition api]

https://codepen.io/asip2k25/pen/qBvRzpb

[directive+component / options api]

https://codepen.io/asip2k25/pen/XWjoaEj

### v2.x

[directive+component]

https://codepen.io/asip2k25/pen/eYdLRxd

### v1.0

```
<div id="form">
    <div v-for="row in contents">
      <input  type="text" class="tag-editor" v-tokenfield="row.content3" >
      <p>
        <button id="btn-log" v-on:click="on_click2(row)">Check VM.content</button>
      </p>
        <p>{{row.content2}}</p>
    </div>
    <p>
      <button id="btn-change" v-on:click="on_click">Add Content by JS</button>
    </p>
</div>
</div>
```

### v1.0, v0.11

```
$(function(){  
  Vue.directive('tokenfield',{
    twoWay: true,
    bind: function(value){
      var self = this;
      $(this.el).tokenfield();
      $(this.el).on('tokenfield:editedtoken',function (e) {
        self.set($(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:createdtoken', function(e){
        self.set($(this).tokenfield("getTokensList", ','));
      }).on('tokenfield:removedtoken', function(e){
        self.set($(this).tokenfield("getTokensList", ','));
      });
    },
    update: function(value){      
      $(this.el).tokenfield('setTokens',value);
    }
  });
    
  var vm = new Vue({
    el: '#form',
    data: {
      contents: [
        {
          content2: '',
          content3: 'wi'
        },
        {
          content2: '',
          content3: 'fi'
        }
      ]
    },
     methods: {
       on_click: function () {
          vm.contents.push({
            content2:'',
            content3:''
          });
       },
       on_click2: function(row){
         row.content2 = row.content3;
       }
    }
  });
  
});
```
v1.0: 
v0.11: 

```
$(function(){  
  Vue.directive('tokenfield',{
    bind: function(value){
      var self = this;  
      $(this.el).tokenfield();
      $(this.el).on('tokenfield:editedtoken',function (e) {
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','))
      }).on('tokenfield:createdtoken', function(e){
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','))
      }).on('tokenfield:removedtoken', function(e){
        self.vm.$set(self.key,$(this).tokenfield("getTokensList", ','))
      });
    },
    update: function(value){
      $(this.el).tokenfield('setTokens',value);
    }
  });
    
  var vm = new Vue({
    el: '#form',
    data: {
      contents: [
        {
          content2: '',
          content3: 'wi'
        },
        {
          content2: '',
          content3: 'fi'
        }
      ]
    },
     methods: {
       on_click: function () {
          vm.contents.push({
            content2:'',
            content3:''
          });
       },
       on_click2: function (row) {
         row.content2 = row.content3;
       }
    }
  });
  
});
```


##最後に
組込み例の詳しい解説は書きません。
簡単なので、頑張って？理解してください。

レッツ・トライ、Vue.js。