JavaScript
jQuery
jquery.ui
Y'sDay 23

ライブラリいじってカレンダーをいい感じにした話

この記事は、株式会社Y's アドベントカレンダー23日目の記事になります。

はじめに

これはJqueryとJquery UIのみ使用可の案件に途中から入り、カレンダーをいい感じにしたときのお話である。
ちなみに今までにJquery UIは使ったことはないです。

そもそもjqueryUIとかdatepickerって?

ライブラリです。機能がいっぱいあります。
datepickerはその中のカレンダー機能みたいな感じです。ふわふわした説明ですみません…

まずは現状

jqueryUIのdatepickerを使用してカレンダーが作られている

cal-02.png
CSSあんまり書きたくないので今回はbootstrap3使って見た目それっぽくしてます。
datepickerの設置の仕方とかjqueryUIの使い方については割愛します。

やりたいこと

某旅行会社とかみたいに、選択期間が塗りつぶされるようにしたい

rakuten.png

期間が選択されていると塗りつぶされる

どう対応したか

まず塗りつぶす用のスタイルを作成

style.css
#ui-datepicker-div .ui-state-select {
  border: 1px solid #003eff;
  background: #87bdf3;
  font-weight: normal;
  color: #fff;
}

次はそのクラスをカレンダーに当てる
focusイベントで上記のクラス付与してみる
特に条件指定してないんで普通に付与できればすべての日付の部分が水色で染まるはず

calendar.js
$(document).on('focus', 'input', function () {
    $('.ui-state-default').addClass('ui-state-select');
});

cal-03.png

イベント発生時にDOMが生成されていないので当たらない
じゃあDelayさせてみる

calendar.js
$(document).on('focus', 'input', function () {
    setTimeout(function () {
        $('.ui-state-default').addClass('ui-state-select');
    }, 100);
});

cal-03-02.png

当たるっちゃ当たる
けど生成とイベント発生に時間差があるので表示時に微妙にチラついて気持ち悪い

じゃあDOM生成と同じタイミングでクラス当てればいいのでは???
ということで

ライブラリいじりました

jquery-ui.js
var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
    controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
    monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
    selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
    cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
    printDate, dRow, tbody, daySettings, otherMonth, unselectable,
    tempDate = new Date(),
    today = this._daylightSavingAdjust(
        new Date(tempDate.getFullYear(),       tempDate.getMonth(),tempDate.getDate())), // clear time
    isRTL = this._get(inst, "isRTL"),
    showButtonPanel = this._get(inst, "showButtonPanel"),
    hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
    navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
    numMonths = this._getNumberOfMonths(inst),
    showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
    stepMonths = this._get(inst, "stepMonths"),
    isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
    currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
        new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
    minDate = this._getMinMaxDate(inst, "min"),
    maxDate = this._getMinMaxDate(inst, "max"),
    drawMonth = inst.drawMonth - showCurrentAtPos,
    drawYear = inst.drawYear;


    /* user change */
    var startDate = $('#start-date').val(),
        endDate = $('#end-date').val(),
        startSel = false;

    if (startDate && endDate) {
        startSel = true;
        var startDate = new Date(startDate),
            endDate = new Date(endDate);
    }

/* user change */以降が追加した処理
datepickerで使用する変数沢山宣言してるところに追加で
開始日と終了日がどちらも入力されているとフラグtrue
開始日と終了日のDateインスタンスを作成

jquery-ui.js
for (dow = 0; dow < 7; dow++) { // create date picker days
    daySettings = (beforeShowDay ?
        beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
    otherMonth = (printDate.getMonth() !== drawMonth);
    unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
        (minDate && printDate < minDate) || (maxDate && printDate > maxDate);
    tbody += "<td class='" +
        ((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
        (otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
        ((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
            (defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
            // or defaultDate is current printedDate and defaultDate is selectedDate
            " " + this._dayOverClass : "") + // highlight selected day
        (unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "") + // highlight unselectable days
        (otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
            (printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
            (printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
        ((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
        (unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
        (otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
            (unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
                (printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
                (printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day

                /* user change */
                (startSel && startDate.getTime() <= printDate.getTime() && printDate.getTime() <= endDate.getTime() ? " ui-state-select" : "") +

                (otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
                "' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
    printDate.setDate(printDate.getDate() + 1);
    printDate = this._daylightSavingAdjust(printDate);
}

カレンダーのを生成している部分に
(// create date picker daysって書いてあるので分かりやすいですね)
先程のフラグがtrueであれば開始日〜終了日にクラス付与
という処理を記載

結果

cal-04.png

できたー
カレンダーを作成しながらクラスを付与しているので時間の差分によるチラつきとかもない

おわりに

あとでJquery UIのリファレンスをじっくり読んだ結果、
beforeShowDayというDOMの生成前にパラメータを渡せる素晴らしい関数が用意されていました (ライブラリいじる必要なかった…!)

ただ、ライブラリの中身を読んで内容をなんとなく理解して処理を足すというのは個人的に新鮮で、今後どこかで活かせるのではと思いました。

明日は@Larry_rahaiさんの記事です!乞うご期待!!!