孤独プログラマー譚

孤独死が近い。

Vue.js Ajaxを使った連動するセレクトボックス

1つ目のセレクトボックスの項目を非同期通信で取得。
そこで選択した項目を元に、2つ目のセレクトボックスの項目も非同期で取得する。

f:id:halation-summer:20190113232507g:plain

<div id="app">
  <select v-model="selected_category_id">
    <option v-for="item in category_list" :value="item.id">{{ item.name }}</option>
  </select>
  <div>{{ selected_category_id }}</div>
  <select v-model="selected_animal_id">
    <option v-for="item in animal_list" :value="item.id">{{ item.name }}</option>
  </select>
  <div>{{ selected_animal_id }}</div>
</div>


1つ目のセレクトボックス項目は created 内で取得。
2つ目のセレクトボックス項目は watch 内で取得。

new Vue({
  el: '#app',
  data: {
    category_list: [],
    selected_category_id: null,
    animal_list: [],
    selected_animal_id: null,
  },
  async created () {
    this.category_list = (await axios.get('http://localhost/animals/category/')).data
    this.selected_category_id = this.category_list[0].id
  },
  watch: {
    async selected_category_id () {
      this.animal_list = (await axios.get(`http://localhost/animals/animals/${this.selected_category_id}/`)).data
      this.selected_animal_id = this.animal_list[0].id
    },
  },
})


算出プロパティを使えるかと思ったけど、どうも非同期通信は使えないみたいで…。
そもそも算出プロパティは return で値を返すので、Promise では return を2回返すことはできないという…。

Vue.js 全て入力後に送信可能になるボタン

テキストフィールドに全て入力完了後、disabledが解除されて送信可能になるボタン。

f:id:halation-summer:20190112140948g:plain

<div id="app">
  <div><input v-model="field1"></div>
  <div><input v-model="field2"></div>
  <div><input v-model="field3"></div>
  <div>{{ entered_all }}</div>
  <button :disabled="!entered_all">submit</button>
</div>
new Vue({
  el: '#app',
  data: {
    field1: '',
    field2: '',
    field3: '',
  },
  computed: {
    entered_all () {
      const required_fields = [
        this.field1,
        this.field2,
        this.field3,
      ]
      return required_fields.indexOf('') === -1
    },
  },
})

jQueryはDOMからD0Mを直接触っていたが、
Vue.jsはViewModelを介す必要があるから、jQueryと考え方が違って面白い。

Vue.js 複数チェックボックスを全て選択で切り替え

よくある「全て選択」チェックボックス

f:id:halation-summer:20190112075758g:plain

<div id="app">
  <div v-for="(item, index) in items">
    <input type="checkbox" :value="item" v-model="checked_items">{{ labels[index] }}
  </div>
  <div>{{ checked_items }}</div>
  <input type="checkbox" v-model="checked_all">check_all<br>
  <div>{{ checked_all }}</div>
</div>
new Vue({
  el: '#app',
  data: {
    items: ['value1', 'value2', 'value3'], // 全てのvalue値
    labels: ['label1', 'label2', 'label3'],
    checked_items: ['value2'], // 初期値
  },
  computed: {
    checked_all: {
      get () {
        return this.checked_items.length === this.items.length
      },
      set (checked) {
        this.checked_items = checked ? this.items : []
      },
    },
  },
})


以下の部分は、サーバーサイドレンダリングで配列に値をつっこんでもいいし、
AJAXで取得してきてもいい。

data: {
  items: ['value1', 'value2', 'value3'], // 全てのvalue値
  labels: ['label1', 'label2', 'label3'],
  checked_items: ['value2'], // 初期値
},

Vue.js 単一ファイルコンポーネントと通常使用の併用

通常使用とは何なのかというと、HTMLのscriptタグ内で、new Vue() してやること…ということにします。

単一ファイルコンポーネントは使いたい。
でも、他のHTML要素の方も、Vue.jsで制御したい。
その場合、どうすればいいか、調べてもよく分からなった。

例えば、ボタンを押した時にアラートメッセージを出す…等の小さい制御も、Vue.jsで書きたい。
でもそれは、単一ファイルコンポーネントにする程ではない。
かと言って、大きめの動的な要素は、単一ファイルコンポーネントを使いたい。

そこで、以下の方法でやることにした。

main.js
Webpack等でバンドルする際のエントリーポイント。
Vue.extend()は、再利用可能なコンストラクタを生成するらしい。
components や data をVueコンストラクタに登録しつつも、新たなコンストラクタを作る。
それを、global.Vue …要はグローバル変数に入れてやる。
これで、各HTMLファイルから、new Vue() してやることが出来る。

global.Vue = Vue.extend({
  components: {
    menulist: Menu,
    detail: Detail,
  },
  data () {
    return {
      foo: 'foo'
    }
  },
  router,
})

以下、HTMLファイル。
Vueコンストラクタからインスタンスを生成している。

<div id="app">
  <div>{{ foo }}</div>
  <div>{{ bar }}</div>
</div>
<script src="bundle.js"></script>
<script>
new Vue({
  el: '#app',
  data: {
    bar: 'bar',
  },
})
</script>

Webアプリ共通の設定 → エントリーポイントで登録
各HTMLページ毎の設定 → scriptタグ内で登録

という感じで作ってやる。今のところこれが一番スッキリしてると思う。

Windows+PHP+Vdebug ブレイクポイントで止まらない

Vimを使う者として、IDE統合開発環境)に負けたくないという気持ちはある。

だが、さすがにログや画面にvar_dump()し続けることに疲れてきてしまった。

ということで、vdebug(ステップ実行できるVimプラグイン)を使うことにした。

環境:
Windows10
Docker
PHP7
Xdebug
Gvim(Kaoriya)

・vdebugの設定
let g:vdebug_options['path_maps'] = {"/var/www/html": "C:/Users/me/my_project/html"}

バックスラッシュで「C:\Users\me\my_project\html」と指定すると、動かない。
「C:/Users/me/my_project/html」通常スラッシュにすると、動く。

しかし、なぜかブレイクポイントで止まらない。ブレイクポイントを設定しても無視される。なぜ?

PHPStormだと、正常にブレイクポイントで止まる。でもvdebugだと止まらない。このままだと…IDEに負けてしまう!?

Xdebugのログを比べてみた。

(一部略)
・PHPStorm
breakpoint_set -f file:///var/www/html/index.php


・vdebug
breakpoint_set -f "file:///C:\Users\me\my_project\html\index.php"

ファイルの指定部分がローカルになっている…?

仕方なく、プラグインのソースを追ってみる。
うっ、Pythonで書かれてる…勉強せねば…。

・変数
remote : file:///var/www/html
local : C:/Users/me/my_project/html
filename : C:\Users\me\my_project\html\index.php


filename = filename.replace(local, remote)

どうも、/ と \ の違いが原因で、置換が上手くいってないっぽい。

/vdebug/python3/vdebug/util.py


def _create_remote(f):
 ret = f
 ret = ret.replace('\\', '/') ← 追加

これで、正常に置換できて、ブレイクポイントでも止まるようになった。

今日一日が無駄にならず済みました。

これからは「Vimでもステップ実行できる」と胸張って言えるぞ~。

jQueryでMVVM

案件によっては、自由にJSフレームワークを使えないことがあるかもしれない。
既存システムがjQueryだけで作られているので、それを踏襲する必要があるかもしれない。

そんな時、jQueryでMVVM…双方向データバインディングが出来ると、作業が楽になる。

以下のページで、超便利なラッパークラスが紹介されてた!

var Observable = (function () {
    function Observable(obj) {
        if (obj == null) return;
        for (var p in obj) {
            Object.defineProperty(this, p, createDescriptor(p, obj[p]));
        }
    }
    function createDescriptor(key, value0) {
        var value = value0;
        return {
            get: function () {
                return value;
            },
            set: function (v) {
                if (value === v) return;
                value = v;
                $(this).trigger("property_is_changed", [key, v]);
            },
            enumerable: true,
            configurable: true
        };
    }
    return Observable;
})();

連想配列JavaScriptで言うオブジェクト)でViewModelを作り、それを上記クラスでラッピングする。

<ul>
  <li><input data-model-key="num1"></li>
  <li><input data-model-key="num2"></li>
  <li><span data-model-key="total"></span></li>
  <li><span data-model-key="total2"></span></li>
</ul>
var model = (function(){
  var reload_view_is_finished;

  var model = {
    num1: 0,
    num2: 0,
    total: 0,
    reload_model: function(){
      reload_view_is_finished = false;

      this.total = parseInt(this.num1) + parseInt(this.num2);
    },
    reload_view: function(){
      if (reload_view_is_finished === true) { return; }
      reload_view_is_finished = true;

      $('[data-model-key="num1"').val(this.num1);
      $('[data-model-key="num2"').val(this.num2);
      $('[data-model-key="total"').text(this.total);
    },
  };
  return model;
}());

var observable_model = new Observable(model);
$(observable_model).on('property_is_changed', function(){
  this.reload_model();
  this.reload_view();
});

observable_model のプロパティに値の変更があれば、property_is_changed イベントが発火される。
その結果、reload_model() と reload_view() が実行される。

$(function(){
  // 初期表示
  $(observable_model).trigger('property_is_changed');

  $('input').on('change', function(){
    var key = $(this).attr('data-model-key');
    var value = $(this).val();
    observable_model[key] = value;
  });
});

フィールドに入力があれば、自動で ViewModel に反映される。
反映された ViewMoel から property_is_changed イベントが発火されるので、reload_view() で画面に反映される。

これで、開発がだいぶ楽になってくれるはず…。

PHP 引数の多いメソッド対策

以下のように、同じような引数が羅列されているソースコードをよく見る。

<?php
$this->getMemberAddress('秋元真夏', '乃木坂46', 'ソニーミュージック');
$this->getMemberAddress('白石麻衣', '乃木坂46', 'ソニーミュージック');
$this->getMemberAddress('山下美月', '乃木坂46', 'ソニーミュージック');

乃木坂46ソニーミュージック
の部分が重複しており、大変見苦しい。

そういう時は、カリー化して、引数の数を減らしてやる。

<?php
$curryingFunc = function($group, $company) {
    return function($name) use($group, $company) {
        return $this->getMemberAddress($name, $group, $company);
    };
}

$getMemberAddressByName = $curryingFunc('乃木坂46', 'ソニーミュージック');

echo $getMemberAddressByName('秋元真夏'); // 東京都…
echo $getMemberAddressByName('白石麻衣'); // 東京都…
echo $getMemberAddressByName('山下美月'); // 東京都…

重複部分が無くなった。

以下のように、部分適用としても良い。

<?php
$getMemberAddressByName = function($name) {
    return $this->getMemberAddress($name, '乃木坂46', 'ソニーミュージック');
};
echo $getMemberAddressByName('秋元真夏'); // 東京都…
echo $getMemberAddressByName('白石麻衣'); // 東京都…
echo $getMemberAddressByName('山下美月'); // 東京都…

引数が10個くらいあるメソッドを見てると、気が滅入ってくるよ。