AngularJSに挑戦！入門 その6　カレンダーを作ろう

#はじめに

AngularJSにすこしずつ慣れてきたような気がします。
まだまだですが、がんばります。

さて、入門編として、ToDoを作成しました。次は、カレンダーを作りたいと思います。
完成すれば、ToDo機能をカレンダーに組み込んで、カレンダーアプリにしたいと思います。
まぁ、Googleカレンダーを使ったほうが便利ですが。。。

#カレンダーの作成

まず、カレンダーデータの作成を行います。

```lang:calendarSample.js
var MyApp = angular.module('calendarApp', []);

MyApp.service('CalendarService', [function () {

    this.header = function() {
        return ["月", "火", "水", "木", "金", "土", "日"];
    }

    this.calendar = function() {
        var date = new Date();              // 現在日付を取得
        var week = [6, 0, 1, 2, 3, 4, 5];   // 月曜始まりにするための変換テーブル
        var monthdays = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
        var year = date.getFullYear();
        // うるう年の計算
        if (((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0){
            monthdays[1] = 29;
        }

        // 月を取得
        var month = date.getMonth();
        // 今日の日を取得
        var today = date.getDate();

        // 一日を取得
        date.setDate(1);
        var startDay = date.getDay();

        var carendar = [];                  // カレンダーデータ
        var day;                            // ループ用
        var index = 1;                      // 日付


        for(var i = 0; i < 6; i++) {
            var tableLine = [];             // 1週間分のデータ
            var d = 0;                      // ループ用

            do {
                date.setDate(index);
                day = date.getDay();

                // 1段目：　前月の分を空白にする
                if (i == 0 && day > d) {
                    tableLine[d] = {dd: "", color: "none"};
                }
                // 6段目：　次の月の分を空白にする
                else if(i == 6 &&  monthdays[month] < day) {
                    tableLine[week[day]] = {dd: "", color: "none"};
                    index++;
                }
                // 最終日の段： 次の月の分を空白にする
                else if (index > monthdays[month]) {
                    tableLine[week[day]] = {dd: "", color: "none"};
                    index++;
                }
                // 日にちを設定する
                else {
                    tableLine[week[day]] = {dd: date.getDate(), color: date.getDate() == today ? "today" : "none"};
                    index++;
                }
                d++;
            // 月曜日開始のカレンダーにするため、土曜日になるまでループする
            } while (week[day] != 6)

            carendar.push(tableLine);
        }
        return carendar;
    }
}]);


MyApp.controller('CalendarCtrl', ['$scope', 'CalendarService', function ($scope, CalendarService) {

    $scope.header = function() {
        return CalendarService.header();
    }
    $scope.calendar = function() {
        return CalendarService.calendar();
    }
}]);
```

行っていることは、カレンダーを1週間分の配列を6つ格納した`calendar`変数を作成することです。
`dd:`には日付が入っていて、`color:`には、今日の場合だけ`today`が設定されます。
将来、休日などのデータを格納するのに使えると思います。

#カレンダービュー

```lang:calendarSample.html
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="calendarSample.css">
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="calendarSample.js"></script>
    <title>calendarSample</title>
</head>
<body ng-app="calendarApp">
    <div class="container">
        <div class="jumbotron">
            <h2>calendarSample!</h2>
        </div>
        <div ng-controller="CalendarCtrl" ng-Init="calendar = calendar()">
            <table class="table table-striped">
                <th ng-repeat="head in header()">{{head}}</th>
                <tr ng-repeat="week in calendar">
                    <td ng-repeat="day in week" class="today-{{day.color}}">{{day.dd}}</td>
                </tr>
            </table>
        </div>
    </div>
</body>
</html>
```

今回新しいキーワードが出てきました。`ng-Init="calendar = calendar()"`です。
簡単に言えば、初期化です。テンプレートが実行される前に行う処理を記述します。


```lang:calendarSample.css
.today-today {
    background: skyblue;
}

td {
    height: 50px;
}
```

#以外に簡単？いや、そうでもなかった

こうやって見ると、簡単にできたように思います。
でも実際にはそうでもありませんでした。
[StackOverflow](http://stackoverflow.com/questions/26753014/angularjs-error-rootscopeinfdig)で質問をして解決できた状態でした。

問題は、`ng-Init`を行わないと無限ループに陥る可能性があるため、
AngularJSのエラーが発生しました。

```
        <div ng-controller="CalendarCtrl" ng-Init="calendar = calendar()">
            <table class="table table-striped">
                <th ng-repeat="head in header()">{{head}}</th>
                <tr ng-repeat="week in calendar">
```
この部分を以下に変えてみるとわかります。

```
        <div ng-controller="CalendarCtrl">
1.            <table class="table table-striped">
                <th ng-repeat="head in header()">{{head}}</th>
2.                <tr ng-repeat="week in calendar()">
```

初期化せずに`ng-repeat`に関数を呼び出すと、Error: [$rootScope:infdig]になります。
ネスト構造の配列を使用する場合は、`ng-Init="list = list()"`のように初期化時に
値を取得して、ループ数を確定させる必要があります。

#おわりに

今回は、だいぶ勉強になりました。
[GitHub - AngularJS_Canendar](https://github.com/village333/AngularJS_Canendar)にソースをアップしています。
次は、Googleカレンダーっぽいものを作ってみたいです。
また、大変そうだなぁ。