(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

AngularJS でマトリクス状のタイルを消したり足したり

HTML の構造と作り方で悩む今日このごろ。発端は仕事で最近流行り(?)マトリクス状*1のタイルを並べるという要件があって、それを動的に追加したり削除したりできないといけなかったんだけど、「マトリクス状」に要素を並べる方法はインターネット初期から色々と方法があって、最近は更にその方法が増えているという現状でどうやるのがベターぽいかなーと考えてたところが始まりなんだけど。
凄く昔のやり方だと table タグの th だとか td だとかを駆使して網の目に頑張ってするという、なんというかエクセル方眼紙ライクなやり方が流行っていたような気がするんだけど、最近のやり方だと ul と li を使うという方が一般的な気がする*2

で、個人的に実装する人間の目線として楽なのは ul の中で li をずっと繰り返して右端に到達したら、自動的に折り返すように CSS で指定しておく方法。この場合、配列が 1 次元でいいため色々と手間がかからなくて良い。列に対して ul をひとつずつ割り当てるやり方を取ろうとすると JavaScript でゴニョゴニョする手間が増大するためプログラマが死ぬ。

簡単にコードで書くと前者がこうで

<ul>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ul>

後者がこう。

<ul>
  <li></li>
  <li></li>
</ul>
<ul>
  <li></li>
  <li></li>
</ul>

で、今回後者の方を AngularJS でどうやって実装しようかなーと思って適当に実装したので、適当にコード載せておく。

<div ng-controller="Ctrl">

  <a ng-click="add()">add</a>
  <div>
    <ul ng-repeat="users in userTable">
      <li ng-repeat="user in users">
        <a ng-click="remove(user)">remove</a>
        <span>{{user.id}}</span>
      </li>
    </ul>
  </div>

</div>
ul {
  display: table;
  border-collapse: separate;
  border-spacing: 5px 0;
  list-style-type: none;

  li {
    display: table-cell;
    height: 100px;
    width: 100px;
    background: red;
    border: solid;
  }
}
'use strict';

testApp.controller('Ctrl', ['$rootScope', '$scope', 'userTable',
  function($rootScope, $scope, userTable){
    userTable.initialize({height: 2, width: 3});
    $scope.userTable = userTable.matrix;

    $scope.remove = function(user){
      userTable.remove(user);
      $scope.userTable = userTable.reMakeMatrix();
    };

    $scope.add = function(){
      userTable.add();
      $scope.userTable = userTable.reMakeMatrix();
    };
  }
]);
'use strict';

testApp.factory('userTable', [
  function(){
    var _this = this,
        baseArray = [],
        matrixHeight,
        matrixWidth;

    var User = function(args){
      var args = args || {},
          _user = {};

      _user.id = args.id || '';

      return _user;
    };

    _this.matrix = [[]];

    _this.initialize = function(args){
      var args = args || {};

      matrixHeight = args.height || 5;
      matrixWidth = args.width || 5;
      baseArray = [];

      for(var i=0; i<(matrixHeight*matrixWidth); i++){
        baseArray.push(new User({id: ''+i}));
      }

      return _this.reMakeMatrix();
    };

    _this.reMakeMatrix = function(){
      if(baseArray.length === 0){ return [[]]; }

      var copyArray =  baseArray.slice(),
          result = [],
          innerResult = [];

      while(copyArray.length !== 0){
        innerResult = [];
        for(var w=0; w<matrixWidth; w++){
          if(copyArray.length === 0){ break; }
          innerResult.push(copyArray.shift());
        }
        result.push(innerResult);
      }

      _this.matrix = result;
      return _this.matrix;
    };

    _this.remove = function(user){
      var index = baseArray.indexOf(user);
      baseArray.splice(index, 1);
    };

    _this.add = function(){
      var lastUser = baseArray[baseArray.length-1] || {},
          lastUserId = lastUser.id || 0,
          user = new User({id: parseInt(lastUserId, 10)+1});

      baseArray.push(user);
    };

    return _this;
  }
]);

全体として足りない部分は想像力で補ってください。 JavaScript が汚いのは簡便してください。
そんな感じ。

*1:グリッド?

*2:違うならごめん、なんとなくそんな感じがするだけです