読者です 読者をやめる 読者になる 読者になる

【JavaScript】Web Storageを使ってTumblrの最新記事一覧をキャッシュする

なんでも世の中にはWeb Storageなる技術があって、そいつを使うとセッション単位でデータを保存しておけるらしいじゃないですか。

上記の記事にはまるで最新技術のように扱われていますが、2011年の記事ですし、3年弱も経ってれば十分枯れた技術です。

そんなわけで、こいつを使ってサイドバーの最新記事一覧をキャッシュしてやろうと思います。

ざっくりとした設計

sessionStorageとlocalStorageで永続化出来る期間が違うみたいなんですが、今回はsessionStorageにします。

あくまでもキャッシュが目的なので、ブラウザ開いて最初の一発目ぐらいは最新状態を取りに行きたいです。

キャッシュが最新の状態かどうかは各Postに割り振られているIDから判断します。IDが大きい方が新しい記事です。なので、こいつもキャッシュしておく必要があります。

じゃあ一番新しい記事のIDもAPIで取りに行かなきゃいけないね、と思ったんですが、各記事の投稿日時にパーマネントリンクがくっついているのでjQueryで取ってこれますね。

既に表示されているWebページもリソースとして使えるって言うのは、普段のコーディング(C#とかJavaとか)ではすっと出てこない発想なのでまだまだJavaScriptに慣れてない感があります。

こんなところでしょう。それじゃあ前回のコードをもりもり改造していきます。

コード

JavaScriptってどう書くと読みやすいんですかねー。てかスコープ欲しいんですけどー。functionはprivateで宣言したいんですけどー。

何かいい方法って言うか、常套手段みたいなのがあるんですかね。あるんでしょうね。探しておきます…。

/// <reference path="jquery-1.6.2.js">

function recent(displayCount) {

    var domain = "outofmem.tumblr.com";
    var apiKey = "";
    var limit = displayCount >= 20 ? 20 : displayCount;
    var baseUri = "http://api.tumblr.com/v2/blog/" + domain + "/posts/?api_key=" + apiKey + "&limit=" + limit;
    var $div = jQuery("#recent");
    var storage = sessionStorage;

    // ***** private methods start ***** //

    // 日付変換Function
    var convertDate = function (date) {
        var dateArray = date.split(" ");

        var y, m, d;

        if (dateArray[0].match(/(\d+)-(\d+)-(\d+)/)) {
            y = RegExp.$1;
            m = RegExp.$2;
            d = RegExp.$3;
        }

        // UTCで15時以降の時間なら日付を1増やす
        if (dateArray[1].match(/(\d+):\d+:\d+/)) {
            var h = RegExp.$1;
            if (h >= 15) {
                d = 1 + (+d);
                if (d < 10) d = "0" + d;
            }
        }

        return [y, m, d].join("/");
    };

    // 最新記事一覧のリストを作成するFunction
    var createRecentList = function (dataArray) {
        var $ul = jQuery("<ul/>").attr({ id: "sidebar-recent" });

        dataArray.forEach(function (postData) {
            // 日付のリンクを作成する
            var $daysLink = jQuery("<a/>").attr({ href: "http://" + domain + "/day/" + postData.date })
                                          .text(postData.date);

            // 日付にネストするulを作る
            var $innerUl = jQuery("<ul/>").attr({ class: "recent-posts" });
            postData.li.forEach(function (li) {
                var $postLink = jQuery("<a/>").attr({ href: li.url }).text(li.title);
                $innerUl.append(jQuery("<li/>").attr({ class: "recent-post" }).append($postLink));
            });

            // 日付のliの中に$innerUlを入れる
            $ul.append(jQuery("<li/>").attr({ class: "recent-day" }).append($daysLink), $innerUl);
        });

        jQuery("#recent > #loading").remove();
        $div.append($ul);
    };

    //Tumblrと通信しにいくFunction
    var tumblrApiCall = function (baseUri) {
        jQuery.ajax({
            type: "GET",
            url: baseUri,
            dataType: "jsonp",
            success: function (data) {

                // ステータスコードを見て判断
                if (data.meta.status != 200) {
                    $div.append($("<p/>").text("Error:" + data.meta.msg));
                    return;
                }

                // postsがなければ終了
                var json = data.response.posts;
                if (json == null || json.length <= 0) {
                    $div.append("<p>Empty</p>");
                    return;
                }

                var postDataArray = new Array();

                json.map(function (x) {
                    return { "date": x.date, "url": x.post_url, "title": x.title };
                }).forEach(function (ref) {
                    // 記事の日付を取得
                    var date = convertDate(ref.date);

                    if (!postDataArray.some(function (x) { return date == x.date; })) {
                        // キーがなければ新たに作成
                        postDataArray.push({ "date": date, "li": [{ "url": ref.url, "title": ref.title}] });
                    } else {
                        // キーがある場合はli配列にデータを追加
                        postDataArray.filter(function (x) { return date == x.date; })
                                 .forEach(function (x) { x.li.push({ "url": ref.url, "title": ref.title }); });
                    }
                });

                // データをキャッシュに保存
                storage.lastId = json[0].id;
                storage.dataArray = JSON.stringify(postDataArray);

                // 最新記事一覧作成
                createRecentList(postDataArray);
            }
        });
    };

    // ***** private methods end ***** //

    //sessionStorageが使えないブラウザの場合
    //もしくはsessionStorageにlastIdがない場合は普通に通信しに行く
    if (typeof storage === 'undefined' || !("lastId" in storage)) {
        tumblrApiCall(baseUri);
        return;
    }

    //パーマネントリンクからIDを取得する
    if (!jQuery("footer > .small > li > a").attr("href").match(/post\/(\d+)/)) {
        //取得できなかった場合は普通に通信
        tumblrApiCall(baseUri);
        return;
    }

    var id = RegExp.$1;

    //パーマネントリンクのIDがキャッシュより新しければ通信
    if (id > storage.lastId) {
        tumblrApiCall(baseUri);
        return;
    }

    //sessionStorageに保存されているキャッシュを展開する
    createRecentList(JSON.parse(storage.dataArray));
    
}

まとめ

いやーWebって便利になってるんですね。割とバカにしてました。

こう言う処理ってDBがないと出来ないんじゃないの?と思ってたんですが、ちゃんと進歩してるんですね。