Vue.js / JSON から情報を引っ張ってくる その7

f:id:jotaki:20190212100544p:plain

やること

  • 前回のファイルを引き継いで Vue Router を使用して新しいページ( /about/ )を作成する。

ページごとにファイルを分ける

/src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import top from '@/components/top'
import about from '@/components/about'
Vue.use(Router)
export default new Router({
  routes: [
    {
      path: "/",
      name: 'top',
      component: top
    },
    {
      path: "/about",
      name: 'about',
      component: about
    }
  ]
})

/src/App.vue

<template>
  <div id="app">
    <header>
      <h1><router-link to="/">works.yuheijotaki.com</router-link></h1>
      <nav>
        <ul>
          <li>
            <router-link to="/about">About</router-link>
          </li>
        </ul>
      </nav>
    </header>
    <main>
      <router-view></router-view>
    </main>
  </div>
</template>

<script>
// normalize.css を読み込む
import "normalize.css";
export default {
  name: 'App'
}
</script>

/src/components/top.vue

<template>
  <!-- トップページの `<router-view>` にいれる内容 -->
  <p>トップページです。</p>
</template>

<script>
export default {
  name: 'top',
  data () {
    return {
      ...
    }
  },
  methods: {
    ...
  }
}
</script>

/src/components/about.vue

<template>
  <!-- アバウトページの `<router-view>` にいれる内容 -->
  <p>アバウトページです。</p>
</template>

<script>
export default {
  name: 'about',
  data () {
    return {
      ...
    }
  },
  methods: {
    ...
  }
}
</script>

まとめ

Github

Vue.js / JSON から情報を引っ張ってくる その6

f:id:jotaki:20190212100544p:plain

やること

WordPress側(functions.php)でのAPIへのフィールド追加

display という自前のデータを v-show: を使って判定させるため下記をfunctions.phpに追加。

// ----------------------------------------
// `customData.display` のAPI登録
// ----------------------------------------
function register_my_custom_data() {
    register_rest_field(
        'post',
        'customData',
        array(
            'get_callback'    => 'get_my_custom_data',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
add_action( 'rest_api_init', 'register_my_custom_data' );

function get_my_custom_data() {
    return array(
        'display'  => true
    );
}

これでJSON側には

"customData":{
  "display":true
}

のように出力される。

静的なクラスと動的なクラス

<a :class="['naviLink' , { 'is-selected': category.selected }]">...</a>

とすると、 .naviLink というクラスは付きつつも、.is-selectedselectedtrue の場合だけつくようになる。

App.vue

<template>
  <div id="app">
    <header>
      <h1>works.yuheijotaki.com</h1>
      <nav>
        <ul>
          <li v-for="(category,index) in categories" :key="index">
            <a href="javascript:void(0);" @click="filterCategory" :data-category="category.name" :class="['naviLink' , { 'is-selected': category.selected }]">{{category.name}}</a>
          </li>
        </ul>
      </nav>
    </header>
    <main>
      <ul>
        <li v-for="(post,index) in posts" :key="index" v-show="post.customData.display">
          <a :href="post.acf.post_url" target="_blank">
            <figure><img :src="post.images.full" :alt="post.title.rendered"></figure>
            <div class="wrap" :style="{ color: post.acf.post_color_letter, background: post.acf.post_color_bg }">
              <div class="inner">
                <h2>{{post.title.rendered}}</h2>
                <p>{{post.category_name}}</p>
              </div>
            </div>
          </a>
        </li>
      </ul>
    </main>
  </div>
</template>

<script>
// normalize.css を読み込む
import "normalize.css";
// Ajax通信ライブラリ
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      categories: [
        {
          name: 'All',
          selected: true
        },
        {
          name: 'Front-end',
          selected: false
        },
        {
          name: 'WordPress',
          selected: false
        },
        {
          name: 'Web Design',
          selected: false
        },
        {
          name: 'Tumblr',
          selected: false
        }
      ],
      posts: []
    }
  },
  created: function(){
    this.request();
  },
  methods: {
    request: function(){
      axios.get( 'https://works.yuheijotaki.com/wp-json/wp/v2/posts?per_page=100' )
      .then( response => {
        this.posts = response.data;
        // console.log(this.posts);
      })
      .catch( error => {
        console.log(error);
      });
    },
    filterCategory: function(event) { // カテゴリーがクリックされたとき用のメソッド
      // 全体のナビゲーションのクラス削除
      var targetElements = document.getElementsByClassName('naviLink');
      [].forEach.call(targetElements, function(elem) {
        elem.classList.remove('is-selected');
      });
      // 選択したナビゲーションのクラス付与
      event.currentTarget.classList.add('is-selected');
      // 投稿の取得
      const posts = this.posts;
      const selectedCategory = event.currentTarget.getAttribute('data-category'); // クリックしたカテゴリーの取得
      if ( selectedCategory !== 'All' ) {
        // `All` 以外を選択した場合
        for (var i = 0; i < posts.length; i++) { // 投稿ごとのループ
          const categories = posts[i].category_name; // 投稿のカテゴリーを取得
          const categoriesArray = categories.split(' ,'); // 取得したカテゴリーを配列に変換
          for (var j = 0; j < categoriesArray.length; j++) { // 投稿内のカテゴリーごとのループ
            if ( categoriesArray.indexOf(selectedCategory) >= 0 ) { // 投稿に属するカテゴリーが含まれる場合
              posts[i].customData.display = true;
              break;
            } else { // マッチしない場合
              posts[i].customData.display = false;
            }
          }
        }
      } else {
        // `All` を選択した場合
        for (var i = 0; i < posts.length; i++) { // 投稿ごとのループ
          posts[i].customData.display = true; // すべての投稿の `display` を `true` に
        }
      }
    }
  }
};
</script>

まとめ

Github

  • 基本的な構文への慣れはだいぶできた気がする。理解して使いこなせているかというとまだまだですが。。
  • やっぱりドキュメントは公式のが一番いいですね。ベースは公式を参考して発展的な使い方はググるのがいまのところ近道な気がします。
  • カテゴリーのクラス付与/削除はいまクラスの .add.remove でやってしまってますが、本来は categories.selected の値をいじるほうがよさそう。
  • URLが変更しないので変更できるようにしたい。

そのほか参考リンク

Vue.js / JSON から情報を引っ張ってくる その5

f:id:jotaki:20190212100544p:plain

やること

  • ポートフォリオページ のコンテンツのエンドポイントを WP REST API を用いて作成
  • 記事タイトルやサムネイル、ACF で登録している値を Vue.js で描画

画面読み込み時に methods のfunctionをレンダリングする

...
created: function(){
  this.request();
},
methods: {
  request: function(){
  ...
  }
},
...

参考

REST API で Advanced Custom Fields を使う

WP REST API に加えて、ACF to REST API | WordPress.org を入れると、https://works.yuheijotaki.com/wp-json/wp/v2/posts で出力される JSON に下記のように登録されている各 acf のオブジェクトが入る。

"acf": {
  "post_color_bg":"#ffffff",
  "post_color_letter":"#000000",
  "post_custom_title":"",
  "post_url":"https:\/\/tatsuhikoniijima.com\/",
  "post_archive":false,
  "post_thumbnail":173
}

Vue.js 側では、

{post.acf.post_url} のようにする。

REST API で投稿ごとのカテゴリーを取得する

投稿ごとのカテゴリー名を取得するにはWordPressのテーマ側(functions.php)を編集する必要がありそう。

functions.php

// 個別投稿毎にカテゴリ名を取得する
add_action( 'rest_api_init', 'register_category_name' );
function register_category_name() {
    //register_rest_field関数を用いget_category_name関数からカテゴリ名を取得し、追加する
    register_rest_field( 'post',
        'category_name',
        array(
            'get_callback' => 'get_category_name'
        )
    );
}

// $objectは現在の投稿の詳細データが入る
function get_category_name( $object ) {
    $formatted_categories = array();
    $categories = get_the_category( $object['id'] );
    foreach ($categories as $category) {
        $formatted_categories[] = $category->name;
    }
    $formatted_categories = implode(' ,', $formatted_categories); // カテゴリー配列をカンマ区切りに変換
    return $formatted_categories;
}

これで JSON には

"category_name": "Front-end,WordPress"

のようにカテゴリー名が出力される。
本当は App.vue 側でカンマ区切りにしたほうが良さげでしたが詰まって見送りにしました。。

Vue.js 側では、

{{post.category_name}} のようにする。

参考

REST API で画像URLを取得する

もともとACFで画像ID出力にしているフィールドの画像URLを出力させるのも functions.php に追記

functions.php

function ws_register_images_field() {
    register_rest_field(
        'post',
        'images',
        array(
            'get_callback'    => 'ws_get_images_urls',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
add_action( 'rest_api_init', 'ws_register_images_field' );

function ws_get_images_urls( $object, $field_name, $request ) {
    $full = wp_get_attachment_image_src( get_field( 'post_thumbnail', $object->id ), 'full' );
    $full_url = $full['0'];
    return array(
        'full'  => $full_url,
    );
}

これで JSON には、

...
"images":{
    "full":"https:\/\/works.yuheijotaki.com\/wwyjp\/wp-content\/uploads\/2018\/09\/tatsuhikoniijima.jpg"
    }
...

のように画像URLが出力される。

Vue.js 側では、

<img v-bind:src="post.images.full"> のようにする。

参考

App.vue

一部省略

<template>
  <div id="app">
    <header>
      <h1>works.yuheijotaki.com</h1>
    </header>
    <main>
      <ul>
        <li v-for="(post,index) in posts" :key="index">
          <a v-bind:href="post.acf.post_url" target="_blank">
            <figure><img v-bind:src="post.images.full" v-bind:alt="post.title.rendered"></figure>
            <div class="wrap" v-bind:style="{ color: post.acf.post_color_letter, background: post.acf.post_color_bg }">
              <div class="inner">
                <h2>{{post.title.rendered}}</h2>
                <p>{{post.category_name}}</p>
              </div>
            </div>
          </a>
        </li>
      </ul>
    </main>
  </div>
</template>

<script>
// normalize.css を読み込む
import "normalize.css";
// Ajax通信ライブラリ
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      posts: []
    }
  },
  created: function(){
    this.request();
  },
  methods: {
    request: function(){
      axios.get( 'https://works.yuheijotaki.com/wp-json/wp/v2/posts?per_page=100' )
      .then( response => {
        this.posts = response.data;
        // console.log(this.posts);
      })
      .catch( error => {
        console.log(error);
      });
    }
  }
};
</script>

まとめ

GitHub

  • 投稿の情報引っ張ってくるのだけなので前回よりはつまづかずにできた。
  • カテゴリーの JSON 格納形式など REST APIの出力を自分でいじれるからいいのですが、Vue.js 側で変換などしてあげる処理はいちいちつまづく。。JavaScript の問題なのか、Vue.js の問題なのかを区別して対処が必要と思う。
  • 次は元サイトと同じようにカテゴリーのフィルターをつくる。

【読書メモ】学びを結果に変えるアウトプット大全

今年はブログ書いたりアウトプットを習慣にしようと思っていたので、Amazon で評価の良かったこの本を読みました。

学びを結果に変えるアウトプット大全 (Sanctuary books)

学びを結果に変えるアウトプット大全 (Sanctuary books)

ボリュームはそこまで多くなく、全 5 章のなかでルールが計 80 個あるので気になったルールのみ読むなどの読み方もできると思います。

筆者の樺沢さんという方は精神科医で、方向性的には最初のルール 1 で「『現実』はアウトプットでしか変わらない」と書いており、アウトプットこそ最大の自己成長につながること、筆者もアウトプットによって本を何冊も執筆し自己成長につながった、というアウトプット賛美的な内容。

広い読者層のために本の中で使われる「自己成長」というものが何かしっくり来ていなかったのですが、最後のまとめに下記のように書いてありました。

本書のアウトプット術を実行すれば、あなたの魅力・能力が多くの人に広がり、あなたは適切な評価を請け、信頼され、人間関係は豊かになり、楽しい人生になることは間違いないでしょう。

このようなことを「自己成長」と一般的に言うのだなと最後に気づきつつも気になった箇所、何度も書かれていたことをメモしておきます。

  • インプット:アウトプット の比率は 3:7 が理想
  • 「アウトプット」と一言でいっても話す、書く、感想を言う、笑う、泣くなどいろいろな種類がある
  • デジタルよりアナログのほうが脳的によい
  • マルチタスクは基本できない。できても 3 つでパンパン
  • 睡眠 7 時間以上取らないと学習能力下がります
  • 一日のルーティーンを決めてスキマ時間を有効活用する
  • 時間や期限を決めて集中する
  • 1 時間だらだらやるより 15 分集中してやったほうがよい
  • アウトプットを習慣化することが大事
  • 教えることが自身の学習や知識向上につながる

Vue.js / JSON から情報を引っ張ってくる その4

f:id:jotaki:20190212100544p:plain

やること

前回の続き

  • WordPress の記事一覧を WP REST API を用いてエンドポイントを作成
  • Vue.js で、カテゴリー一覧、記事タイトルの一覧を表示させる
  • Vue.js で、カテゴリーごとの投稿一覧を表示する

GitHub

App.vue

<template>
  <div id="app">
    <header>
      <h1>blog.yuheijotaki.com</h1>
      <nav>
        <ul class="category_list">
          <li v-for="cat in categories" :key="cat.name.rendered">
            <a href="#" v-bind:data-category-id="cat.id" @click="filterCategory">{{cat.name}}</a>
          </li>
        </ul>
      </nav>
    </header>
    <main>
      <ul class="post_list">
        <li v-for="post in posts" :key="post.title.rendered">
          <a :href="post.link" v-html="post.title.rendered"></a>
        </li>
      </ul>
      <button
        class="button"
        :class="[{ 'is-loading': loading, 'is-disabled': disabled }]"
        :disabled="disabled"
        @click="load">次の記事を読み込む</button>
    </main>
  </div>
</template>

<script>
// normalize.css を読み込む
import "normalize.css";
// Ajax通信ライブラリ
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      posts: [],
      loading: false,
      disabled: false,
      categories: '', //[WIP] ここひとつにまとめれそう
      category: '', //[WIP] ここひとつにまとめれそう
      categoryArray: [ //[WIP] ここは連想配列でなくてオブジェクトでできないか。 `data` の `category` もなくてもできそう。
        {
          id: 2,
          slug: 'develop',
          page: 0
        },
        {
          id: 3,
          slug: 'design',
          page: 0
        },
        {
          id: 4,
          slug: 'others',
          page: 0
        }
      ]
    }
  },
  mounted() {
    this.getCategories();
    this.category = 2; // 初期はDevelopカテゴリー
    this.categoryArray[0].page = 1; // 初期はDevelopカテゴリーの1ページ目
  },
  watch: {
    categoryArray: {
      handler: function(val){
        if ( this.category === 2 ) {
          var categoryPage = this.categoryArray[0].page;
        } else if ( this.category === 3 ) {
          var categoryPage = this.categoryArray[1].page;
        } else if ( this.category === 4 ) {
          var categoryPage = this.categoryArray[2].page;
        }
        const url = `https://blog.yuheijotaki.com/wp-json/wp/v2/posts?categories=${this.category}&per_page=10&page=${categoryPage}`;
        // console.log(url);
        // 非同期でJSON URLから投稿を取得
        (async () => {
          try {
            const res = await axios.get(url);
            this.posts = this.posts.concat(res.data);
            this.loading = false;
          } catch (error) {
            // alert('取得できませんでした。')
            console.log(error);
            this.empty();
          }
        })();
      },
      deep: true,
    }
  },
  methods: {
    getCategories: function () {
      const categoryUrl = `https://blog.yuheijotaki.com/wp-json/wp/v2/categories`;
      // 非同期でJSON URLから投稿を取得
      (async () => {
        try {
          const res = await axios.get(categoryUrl);
          const categories = res.data;
          categories.shift(); // [WIP] 先頭の配列(`未分類`)を削除 ただし先頭が`未分類`と限らないので要修正
          // オブジェクト `categories.page` の追加
          for ( var i = 0; i < categories.length; ++i ) {
            categories[i].page = 0;
          }
          // console.log(categories);
          this.categories = categories;
        } catch (error) {
          console.log(error);
        }
      })();
    },
    load() { // `次の記事を読み込む` ボタンが押されたとき用のメソッド
      // this.category = this.category; // カテゴリーは `this.category` のままになる
      this.loading = true;
      if ( this.category === 2 ) {
        this.categoryArray[0].page++;
      } else if ( this.category === 3 ) {
        this.categoryArray[1].page++;
      } else if ( this.category === 4 ) {
        this.categoryArray[2].page++;
      }
    },
    empty() { // 記事がない or 通信エラーのとき用のメソッド
      this.loading = false;
      this.disabled = true;
    },
    filterCategory: function(event) { // カテゴリーがクリックされたとき用のメソッド
      // カテゴリーが選択された場合は一度投稿を削除してから該当の一覧を表示させる
      this.loading = true;
      this.disabled = false;
      this.posts.splice(0, 9999); //[WIP] 0記事目から9999記事目まで削除 決め打ちなので要修正
      const categoryId = event.currentTarget.getAttribute('data-category-id'); // クリックしたカテゴリーの取得
      this.category = Number(categoryId); // `string` から `number` に変換
      if ( this.category === 2 ) {
        this.categoryArray[0].page = 0; //[WIP] 一度 `0` に戻して `1` に増加させないと `watch` が効かない
        this.categoryArray[0].page = 1;
      } else if ( this.category === 3 ) {
        this.categoryArray[1].page = 0;
        this.categoryArray[1].page = 1;
      } else if ( this.category === 4 ) {
        this.categoryArray[2].page = 0;
        this.categoryArray[2].page = 1;
      }
    }
  },
};
</script>

つまづいたところ

watch で配列のデータを監視

前回で watch の仕組みは data に変更があった際の常時監視、実行ということが分かったのですが、data が配列の場合はうまくいってなさそうだったのですが、ちょっとひと手間加える必要がありました。

連想配列の Object を丸ごと監視したい場合は、処理は handler: function(){}, の方に記述して、deep: true, が必要です。 連想配列Object を個々に監視したい場合は、watch のキーをクォーテーションで囲って 'individuallyObj.aaa' のようにします。

ここでは categoryArray という配列を監視するために使う。

参考:vue.js 2.x その 0009 watch で配列(array)や連想配列(object)を監視する - Motomichi Works Blog

メソッドを mounted 時に走らせる

...
mounted() {
  this.methodFunctionName();
},
...

ここでは loadCategory() でカテゴリー一覧を取得して読み込み時に表示する。

参考:javascript - VueJS Syntax: Running method on mount - Stack Overflow

WordPress の カテゴリーの取得

https://blog.yuheijotaki.com/wp-json/wp/v2/categories のように .../wp-json/wp/v2/categories がカテゴリー一覧の JSON URL になる。

参考:【Vue.js】No.007 Vue.js から WP REST API にアクセスし、カテゴリーを取得してみた

まとめ

  • Vue.js もそうですが JavaScript の オブジェクトや配列の操作や扱いでつまづいた。。
    オブジェクトのキー取得、splice などのメソッドも覚える必要あり。
  • watchcomputed の違いがやっぱりいまいち分からない。今回の computed でもできそう。
  • this の使い方がぼんやりなのでここも覚える必要がある。

[WIP] でコメント入れたように課題はたくさんありますが、触わり飽きたので次回は別の WordPress REST API を叩いてやってみようと思います。