Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

vue.js 2.x その0006 refで要素を参照する

参考にさせて頂いたページ

Vue.js

サンプルコード

以下のように書いてみました。

<div id="vue-app">
  <button v-on:click="clicked">click here</button>
  <div>
    <my-component ref="hogeElm" />
  </div>
  <div ref="fooElm">
    foo<br>foo
  </div>
</div>

<script src="js/vue.js"></script>
<script>
  new Vue({
    el: '#vue-app',
    data: {
    },
    components: {
      'my-component': {
        template: '<div><p ref="hogeChild">pタグ</p></div>',
      },
    },
    mounted: function(){
      console.log('mounted');
      // hogeElmの高さを取得します
      console.log(this.$refs.hogeElm.$el.clientHeight);
      // componentsの中にあるタグの高さを取得します
      console.log(this.$refs.hogeElm.$refs.hogeChild.clientHeight);
      // componentsにしていない要素ももちろん参照できます
      console.log(this.$refs.fooElm.clientHeight);
    },
    methods: {
      clicked: function(){
        // クリックイベントで要素を参照します
        console.log(this.$refs.fooElm.clientHeight);
      }
    }
  });
</script>

サンプルコードについて

createdではまだ参照できませんが、mountedではthis.$refsから参照できるようになります。

vue.js 2.x その0005 lodashのdebounce()とthrottle()で処理の実行を遅らせる

参考にさせて頂いたページ

はじめに

vue.jsの0.xと1.xではdebounce属性がありましたが、2.xからは無くなったことが公式ドキュメントに書いてあります。

Vue 1.x からの移行 - Vue.js

代わりにlodash.jsのdebounce()またはthrottle()を使用して、処理の実行を遅らせるサンプルを書いてみます。

サンプルコード

CDNでlodashを読み込んで、throttle()を使用するサンプルを書いてみました。

公式ドキュメントのサンプルではmethods内でdebounceを適用していますが、watch内でthrottleを適用するサンプルを書いてみました。

<div id="vue-app">
  <h1>Vue.jsの練習 静的なページ</h1>
  <input type="text" v-model="valText">
  <div>{{lateText}}</div>
<!--/#vue-app--></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="js/vue.js"></script>
<script>
  new Vue({
    el: '#vue-app',
    data: {
      valText: '',
      lateText: '',
    },
    watch: {
      valText: _.throttle(function(){
        console.log('changed');
        this.lateText = this.valText;
      }, 500)
    }
  });
</script>

vue.js 2.x その0004 アコーディオンを作る(jQueryのslideToggleみたいなものを作る)

2018年03月22日追記

アコーディオンひとつ分をコンポーネント化して、コンポーネント内だけで状態管理する方が良いので書き直しました。

vue.js 2.x その0004-02 アコーディオンを作る(jQueryのslideToggleみたいなものを作る) - Motomichi Works Blog

サンプルコード(v-forで出力した要素にそれぞれ)

v-forで出力した要素それぞれにアコーディオンを入れるイメージで、以下のように書いてみました。

<style>
.accordion{}
.accordion__trigger{}
.accordion__contents-outer-wrap{
  transition: max-height 2s;
  max-height: 0;
  overflow: hidden;
  background: #eeeeff;
}
.accordion__contents-inner-wrap{
  padding: 100px 10px;
}
</style>

<div id="accordion-app">
  <ul>
    <li class="accordion" v-for="(item, index) in items">
      <button class="accordion__trigger" v-on:click="slideToggle(index)">
        accordion-trigger-{{index}}
      </button>
      <div class="accordion__contents-outer-wrap" v-bind:style="{ maxHeight: items[index].contents.maxHeight + 'px' }">
        <div class="accordion__contents-inner-wrap" ref="contentsInner">
          {{item.title}}<br>
          {{item.contents.lead}}
        </div>
      </div>
    </li>
  </ul>
</div>

<script src="js/vue.js"></script>
<script>
  var items = [
    {
      title: 'content-0',
      contents: {
        lead: 'lead-0',
        isActive: false,
        maxHeight: 0,
      }
    },
    {
      title: 'content-0',
      contents: {
        lead: 'lead-0',
        isActive: false,
        maxHeight: 0,
      }
    },
  ];

  var vueApp = new Vue({
    el: '#accordion-app',
    data: {
      items: items,
    },
    methods: {
      slideToggle: function(index){
        this.items[index].contents.isActive = !this.items[index].contents.isActive;
        if(this.items[index].contents.isActive){
          this.items[index].contents.maxHeight = this.$refs.contentsInner[index].clientHeight;
        }else{
          this.items[index].contents.maxHeight = 0;
        }
      },
    },
  });
</script>

サンプルソース(table-rowを開閉)

trタグの開閉も書いてみましたがHTMLタグが多くなりますね。 display: table-rowでも同じですね。

<style>
.container{
  width: 600px;
}
table{
  width: 100%;
  border-collapse: collapse;
  border-spacing: 0;
}
.contents-wrap{
  padding: 10px;
  background: #eeeeff;
}
tr + tr .contents-wrap{
  border-top: 1px solid #aaaaaa;
}
th,td{
  border: 0;
  padding: 0;
}
.accordion{
  transition: max-height 2s;
  max-height: 0;
  overflow: hidden;
}

</style>

<div id="vue-app">
  <div class="container">
    <table>

      <tr>
        <th>
          <div>
            <div class="contents-wrap">
              ラジオボタンrow
            </div>
          </div>
        </th>
        <td>
          <div>
            <div class="contents-wrap">
              <span class="controll">
                <input type="radio" name="hoge-radio" value="true" v-model="hoge">
                <label for="">開く</label>
              </span>
              <span class="controll">
                <input type="radio" name="hoge-radio" value="false" v-model="hoge">
                <label for="">閉じる</label>
              </span>
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}">
            <div class="contents-wrap" ref="hoge">
              トグルrow
            </div>
          </div>
        </th>
        <td>
          <div class="accordion" :style="{'max-height': state.hoge.maxHeight + 'px'}">
            <div class="contents-wrap">
              トグルコンテンツ
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div>
            <div class="contents-wrap">
              ラジオボタンrow
            </div>
          </div>
        </th>
        <td>
          <div>
            <div class="contents-wrap">
              <span class="controll">
                <input type="radio" name="foo-radio" value="true" v-model="foo">
                <label for="">開く</label>
              </span>
              <span class="controll">
                <input type="radio" name="foo-radio" value="false" v-model="foo">
                <label for="">閉じる</label>
              </span>
            </div>
          </div>
        </td>
      </tr>

      <tr>
        <th>
          <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}">
            <div class="contents-wrap" ref="foo">
              トグルrow
            </div>
          </div>
        </th>
        <td>
          <div class="accordion" :style="{'max-height': state.foo.maxHeight + 'px'}">
            <div class="contents-wrap">
              トグルコンテンツ
            </div>
          </div>
        </td>
      </tr>

    </table>
  </div>
</div>

<script src="js/vue.js"></script>
<script>
  var vueApp = new Vue({
    el: '#vue-app',
    data: {
      hoge: 'false',
      foo: 'false',
      piyo: 'false',
      state: {
        hoge: {
          isVisible: false,
          maxHeight: 0,
        },
        foo: {
          isVisible: false,
          maxHeight: 0,
        },
      }
    },
    watch: {
      hoge: function(val){
        this.slideToggle('hoge', val);
      },
      foo: function(val){
        this.slideToggle('foo', val);
      },
    },
    methods: {
      slideToggle: function(key, val){
        this.state[key].isVisible = !this.state[key].isVisible;

        if(this.state[key].isVisible){
          this.state[key].maxHeight = this.$refs[key].clientHeight;
        }else{
          this.state[key].maxHeight = 0;
        }
        console.log(this.state[key]);
      },
    },
  });
</script>

vue.js 2.x その0003-01 トランジション効果 簡単なフェードインとフェードアウトをやってみる

参考にさせて頂いたページ

transitionについて

動的コンポーネントについて

サンプルコード

以下のような感じで書いてみました。

<style>
.hoge-enter-active,
.hoge-leave-active {
  transition: opacity 2s
}
.hoge-enter,
.hoge-leave-to /* hoge-leave-to クラスはバージョン 2.1.8 以降でのみ利用可能 */ {
  opacity: 0
}
</style>

<div id="transition-app">
  <h1>Vue.jsの練習 静的なページ</h1>
  <button v-on:click="fade">click here</button>
  <transition name="hoge">
    <div v-show="state.contentIsShow">トグルするコンテンツ</div>
  </transition>
<!--/#transition-app--></div>

<script src="js/vue.js"></script>
<script>
  var fadeApp = new Vue({
    el: '#transition-app',
    data: {
      state: {
        contentIsShow: true,
      },
    },
    methods: {
      fade: function(){
        this.state.contentIsShow = !this.state.contentIsShow;
      },
    },
  });
</script>

サンプルコードについて

公式サイトにあるように、以下の条件のいずれかに当てはまるタグをtransitionタグで囲うことでenteringとleavingのトランジション効果が得られるということらしいので、サンプルコードではv-showを使ってみました。

  • 条件付きの描画 (v-if を使用している)
  • 条件付きの表示 (v-show を使用している)
  • 動的コンポーネント (v-bind:is を使用している)
  • コンポーネントルートノード (Component root nodes)

サンプルコードの場合は以下のような感じで動くようです。

トランジション効果が起こる直前や起こっているとき、完了した直後などに色々なクラスが付与されます。
そのクラス名については、公式ページを参考にするのが良いと思います。

デフォルトでは、v-leave-toなどのv-を接頭辞としたクラスが付与されますが、のようにnameを付与することでhoge-leave-toなどの任意の接頭辞にすることができます。

他にもありますが、vue.jsのトランジション効果により、例えば以下のようなクラスが付与されます。

  • .v-enter-active
  • .v-leave-active
  • .v-enter-to
  • .v-leave-to

vue.js 2.x その0002 v-forで表示しているリストの並べ替え

参考にさせていただいたページ

sortについて

v-forについて

ソースコードサンプル

すごく適当にデータを用意して、以下のように書いてみました。

<div id="sort-app">
  <h1>Vue.jsの練習 静的なページ</h1>
  <nav>
    <div><button v-on:click="sort('id')">id順に並べ替え</a></button>
    <div><button v-on:click="sort('rice_and_bread')">ごはんとパンの合計価格順に並べ替え</a></button>
    <div><button v-on:click="sort('rice_only')">ごはんの価格順に並べ替え</button></div>
    <div><button v-on:click="sort('bread_only')">パンの価格順に並べ替え</button></div>
  </nav>
  <ul>
    <li v-for="item in state.items">
      【{{item.name}}】 id:{{item.id}} 合計額:{{item.priceRiceAndBread}}円 ごはん:{{item.priceRice}}円 パン:{{item.priceBread}}円
    </li>
  </ul>
<!--/#sort-app--></div>

<script src="js/vue.js"></script>
<script>
  var sortApp = new Vue({
    el: '#sort-app',
    data: {
      state: {
        items: [
          {name: 'セット商品0', id: 0, priceRiceAndBread: 410, priceRice: 110, priceBread: 300},
          {name: 'セット商品1', id: 1, priceRiceAndBread: 550, priceRice: 350, priceBread: 210},
          {name: 'セット商品2', id: 2, priceRiceAndBread: 380, priceRice: 180, priceBread: 200},
          {name: 'セット商品3', id: 3, priceRiceAndBread: 610, priceRice: 200, priceBread: 410},
          {name: 'セット商品4', id: 4, priceRiceAndBread: 440, priceRice: 320, priceBread: 120},
        ],
      },
    },
    methods: {
      sort: function(action_type){
        switch (action_type) {
          case 'id':
            this.state.items.sort(function(a,b){
              if(a.id < b.id) return -1;
              if(a.id > b.id) return 1;
              return 0;
            });
            break;
          case 'rice_and_bread':
            this.state.items.sort(function(a,b){
              if(a.priceRiceAndBread < b.priceRiceAndBread) return -1;
              if(a.priceRiceAndBread > b.priceRiceAndBread) return 1;
              return 0;
            });
            break;
          case 'rice_only':
            this.state.items.sort(function(a,b){
              if(a.priceRice < b.priceRice) return -1;
              if(a.priceRice > b.priceRice) return 1;
              return 0;
            });
            break;
          case 'bread_only':
            this.state.items.sort(function(a,b){
              if(a.priceBread < b.priceBread) return -1;
              if(a.priceBread > b.priceBread) return 1;
              return 0;
            });
            break;
          default:
        };
      },
    },
  });
</script>

vue.js 2.x その0001 カスタムフィルタ(Custom Filters)を登録する基本サンプル

参考にさせて頂いたページ

ソースコードサンプル1

ソースコードサンプルは以下の通り。

<div id="filter-app">
  <input type="text" v-model="message">
  <div>{{message | reverse}}</div>
<!--/#filter-app--></div>

<script src="js/vue.js"></script>
<script>
Vue.filter('reverse', function (value) {
  console.log(value);
  return value.split('').reverse().join('');
})

var filterApp = new Vue({
  el: '#filter-app',
  data: {
    message: 'message',
  }
});
</script>

ソースコードサンプル1について

まずv-model="message"でdata.messageとバインディングしています。vue.jsのよくある記述です。

{{message | reverse}}は、ただdata.messageの値を出力するのではなく、フィルターを通した結果を出力しています。

newする前に、Vueコンストラクタにfilterを登録しています。
公式サイトにあるように、第一引数はfilterIDを文字列で渡して、第二引数は関数オブジェクトです。
第二引数として渡した関数の引数valueは上記した{{message | reverse}}のmessageが入ってきます。

その後でnewしています。

ソースコードサンプル2

newするときにfiltersプロパティを定義する記述方法は以下の通り。

<div id="filter-app">
  <input type="text" v-model="message">
  <div>{{message | reverse}}</div>
<!--/#filter-app--></div>

<script src="js/vue.js"></script>
<script>
var filterApp = new Vue({
  el: '#filter-app',
  data: {
    message: 'message',
  },
  filters: {
    reverse: function(value) {
      console.log(value);
      return value.split('').reverse().join('');
    }
  }
});
</script>

終わりに

vue.js 1.x まで使えた組み込みのフィルタは使えなくなったので、そのあたりのことは参考にさせて頂いた以下のページに書いてあったりしました。

今回はここまで。

railsその0001 vagrantでrails5の環境構築をする

参考にさせて頂いたページ

全体的なインストール手順について

はじめに

ポイントとしては、手順のなかにある以下のコマンドを実行するときに

bundle install --path 任意のパス

ホストと同期しているディレクトリをパスに指定するとエラーが出るらしい。ということです。

今回の環境(インストーラーなど)

ホストマシン環境

Windows10
vagrant_1.8.4.msi
VirtualBox-5.0.24-108355-Win.exe

ゲストマシン環境

vagrant upまでやってみてexitする

以前、以下の手順でvagrant upまでやって、ゲストマシンからexitしました。

vagrantその0027 Windows10にvagrant_1.9.7_x86_64.msiでインストールしなおしてvagrant sshまでやってみる - Motomichi Works Blog

ポートフォワーディング設定

localhost:3000でrailsのスタートページが閲覧できるようにvagrantの設定をしておきます。

Vagrantfileの

# config.vm.network "forwarded_port", guest: 80, host: 8080

の行を以下のようにします。

config.vm.network "forwarded_port", guest: 3000, host: 3000

vagrant環境内とホストマシンのディレクトリを同期設定とディレクトリの作成

Vagrantfileの

# config.vm.synced_folder "../data", "/vagrant_data"

の行を以下のようにします。

config.vm.synced_folder "./my_app", "/var/www/rails_project/my_app"

Vagrantfileがあるディレクトリに my_app ディレクトリを作成しておきます。

ホストマシンのこのディレクトリが、ゲストマシンの /var/www/rails_project/my_app と同期されることになります。

Vagrantfileの設定変更を反映してディレクトリを同期する

以下のコマンドでVagrantfileの編集内容を反映します。

vagrant reload

ゲストマシン内の/var/www/rails_project/my_appディレクトリはこのとき作成されて、同期されます。

yumで色々インストールする

まず仮想環境にログインします。

vagrant ssh

参考ページにならって、以下のコマンドを実行しました。

sudo yum install -y gcc-c++ patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison git vim

以下のコマンドでgitがインストールされたことが確認できました。

git --version

rbenvでrubyをインストールする

参考ページにならって、以下のコマンドを実行しました。

git clone git://github.com/sstephenson/rbenv.git ~/.rbenv

以下のように表示されて /home/vagrant/ にcloneされました。

Initialized empty Git repository in /home/vagrant/.rbenv/.git/

参考ページにならって、続けて以下のコマンドを実行しました。
パスを通したり、ruby-buildをインストールします。

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bashrc
$ exec $SHELL -l
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh
$ rbenv install -l

インストール可能なrubyのバージョンの一覧が出ます。

続いて以下のコマンドでrubyをインストールします。

$ rbenv install 2.4.1

が、2017年2月には大丈夫だったと思うのですが、2017年5月に同じ手順でやったら以下のエラーが。

Downloading ruby-2.4.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.bz2
error: failed to download ruby-2.4.1.tar.bz2

BUILD FAILED (CentOS release 6.7 (Final) using ruby-build 20170405-4-g365dd1f)

CentOS7にRails環境を整えるまで①−2(rbenv編) - Qiita を参考にさせて頂いて、ログを確認します。

まずは以下のコマンドでログファイルのファイル名を確認します。

$ ls /tmp/

次にログファイルの内容を確認します。 ログファイル名はご自身の環境にあわせてご確認ください。

$ cat ruby-build.20170505140334.2859.log

を実行してみたら以下のように表示されました。

curl: (35) SSL connect error

ということで、CentOS6.xのlibcurlが古くてcurl: (35) SSL connect errorが発生する件 - Qiitaのページを参考にさせていただきました。

以下のコマンドを実行しました。

$ sudo rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-1-13.rhel6.noarch.rpm
$ sudo vim /etc/yum.repos.d/city-fan.org.repo

enabled=1のところを下記のように0に編集しました。

[city-fan.org]
name=city-fan.org repository for Red Hat Enterprise Linux (and clones) $releasever ($basearch)
#baseurl=http://mirror.city-fan.org/ftp/contrib/yum-repo/rhel$releasever/$basearch
mirrorlist=http://mirror.city-fan.org/ftp/contrib/yum-repo/mirrorlist-rhel$releasever
#enabled=1
enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-city-fan.org

次に以下のコマンドを実行してlibcurlをアップデートしました。

sudo yum update --enablerepo=city-fan.org libcurl

ようやくrubyのビルドが実行できます。

$ rbenv install 2.4.1
$ rbenv rehash
$ rbenv global 2.4.1

以下のコマンドで正常にインストールできているか確認します。

$ ruby -v
$ which gem
$ which ruby

bundlerをインストールする

参考ページにならって、以下のコマンドを実行しました。

$ gem install bundler --no-rdoc --no-ri
$ rbenv rehash

railsのインストー

ここからは Vagrant で Ruby on Rails 4 の環境構築 - Qiita の「Rails のインストール」あたりから参考にさせて頂いて進めます。

MySQLを使用する場合はさきほどのページがよさそうです。

$ gem install rails
$ rbenv rehash
$ rails -v
$ which rails

正常にインストールできたことが確認できました。

バージョンを指定しない場合、最新バージョンがインストールされるので、5.0.1がインストールされました。

bundle installでプロジェクトにrailsをインストールする

$ cd /var/www/rails_project/my_app
$ bundle init

するとGemfileが作成されるので、参考ページにならってGemfileを編集します。

以下の行のコメントを解除します。

# gem "rails"

bundle installコマンドを実行してrailsをプロジェクトにインストールしますが、このとき指定するパスは、vagrantでホストマシンと同期しているディレクトリだとエラーになるので、ひとつ上の階層にvendorディレクトリを作成して、 ../vendor/bundle を指定しています。

$ su
vagrant(パスワード入力)
# mkdir ../vendor
# chmod 777 ../vendor
# exit
$ bundle install --path ../vendor/bundle

同期しているディレクトリを指定した場合は以下のようなエラーが出ると思います。

Text file busy @ unlink_internal - ./siteconf20170728-2850-1xca5va.rb

Gem files will remain installed in /var/www/rails_project/my_app/vendor/bundle/ruby/2.4.0/gems/nio4r-2.1.0 for inspection.
Results logged to /var/www/rails_project/my_app/vendor/bundle/ruby/2.4.0/extensions/x86_64-linux/2.4.0-static/nio4r-2.1.0/gem_make.out

An error occurred while installing nio4r (2.1.0), and Bundler cannot continue.
Make sure that `gem install nio4r -v '2.1.0'` succeeds before bundling.

In Gemfile:
  rails was resolved to 5.1.2, which depends on
    actioncable was resolved to 5.1.2, which depends on
      nio4r

「ホストマシンと同期しているディレクトリにbundle installするとエラーになる」件については解決策が以下のページに書いてありました。

RailsのプロジェクトをVMで共有した際にbundle installでこける問題 - Qiita

rails newしてアプリケーションを作成する

引き続き「Vagrant で Ruby on Rails 4 の環境構築 - Qiita」を参考にrailsプロジェクトを作成します。

bundle exec rails new .

参考ページにあるとおり、Gemfileを上書きするか確認されるのでYでenterします。

以下のようなエラーが出ました。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /var/www/rails_project/vendor/bundle/ruby/2.4.0/gems/sqlite3-1.3.13/ext/sqlite3
/home/vagrant/.rbenv/versions/2.4.1/bin/ruby -r ./siteconf20170505-15415-4si8oi.rb extconf.rb
checking for sqlite3.h... no
sqlite3.h is missing. Try 'brew install sqlite3',
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
and check your shared library search path (the
location where your sqlite3 shared library is located).
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

上記のメッセージにあるとおり、yumsqliteをインストールして、bundle installします。

sudo yum -y install sqlite-devel
bundle install

次にrailsサーバーを起動します。

bundle exec rails s

以下のエラーが出ました。

Gem Load Error is: Could not find a JavaScript runtime.

Gemfileの以下の行のコメントを解除します。

# gem 'therubyracer', platforms: :ruby

以下のコマンドでtherubyracerをインストールします。

bundle install

今度はrailsサーバーが起動できるはず。

bundle exec rails s

localhost:3000にブラウザでアクセスすると、railsのスタートページが表示されました。

この記事はとりあえずここまでですが、vagrantでの開発では「railsその0002 vagrant+rails5の環境でmodelやcontrollerのファイルを編集しても反映されない問題を解決する - Motomichi Works Blog」のような問題が発生したので認識しておくと良さそうな気がします。

ここまで確認できたら、ドットインストールの「http://dotinstall.com/lessons/basic_rails_v2」とかをやると良いかもしれないです。

ctrl+c でrailsサーバーを止めます。