カレンダーを作った (iPhone向けウェブアプリ)

ここのところ、ウェブページなんだけど iPhone でアプリっぽく見せたり、フリック操作を扱ったり、オフラインでも動くようにキャッシュさせたりする方法を調べたので、ひとまずオフラインでも動くカレンダーを作った。

特徴:

  • オフラインで動く
  • 「ホーム画面に追加」したアイコンから起動すると、ちょっとアプリっぽく見える
  • 左右のフリックで月の移動
  • (日本の)祝日に対応(色が違うだけ)
  • 任意の日付にマークできる(3色から選択)
  • 色を消すには、同じ色を選択してもう一度日付をタップ

注意点:

  • 最初に祝日データを Google Calendar API から取得してくるので、最初はオンラインにする必要がある(過去1年から2年先の分を取得)
  • データはすべて Local Storage に保存
  • オフラインで動くと書いたように、application cacheも使ってる
  • Android 2.3系だと、メニューのアニメーション(CSS)が上手く動かない(4.0だったら動くかも知れないけど未確認)

自分が祝日表示と、有給休暇などの休みの日だけちょこっと確認する程度の機能を持ったカレンダーアプリが欲しかったのだが、探してみてもピンとくるのが無かったので作ってみた。

iPhoneアプリの Yearsがかなり理想に近かったが、祝日も自分で入れなきゃいけないのが面倒だなーと思ったのと、まあ勉強も兼ねて。

参考:

Advertisements
カレンダーを作った (iPhone向けウェブアプリ)

Google App Engine で HTML5 の Application Cache

参考:

マニフェストファイルを用意する

拡張子は .appcache (.manifestから変更された)。以下サンプル。

CACHE MANIFEST
# version 1.0

CACHE:
path/to/the/file.html
/could/be/absolute/path.js

NETWORK:
always/refer/to/serverside/content.html

# external source
http://example.com/

サーバを参照するものがある場合は、必ず NETWORK に指定(CACHEで指定したもの以外はサーバを参照する、という事ではない)。

マニフェストファイルが更新されないとブラウザはキャッシュし続ける。ファイルの構成が変わらなくても更新させたい場合を考えて、コメントでバージョンとか日付とか入れておき、キャッシュを更新させる場合はここを変更する。

HTMLとJavaScript

HTMLタグにmafifest属性を追加。

<html manifest="example.appcache">

JavaScriptでキャッシュ対象のファイルがアップデートされた時に更新する。

applicationCache.addEventListener("updateready", function(){
    applicationCache.swapCache();
}, false);

GAEの設定 (app.yaml)

mime_typeを text/cache-manifestにする。以下、例。

- url: /(.*)\.appcache
  static_files: static/\1.appcache
  upload: static/(.*)\.appcache
  mime_type: text/cache-manifest
Google App Engine で HTML5 の Application Cache

はじめての jQuery Plugin

前回のpostにフリックのイベントについて書いたが、イベントを検知する jQuery Plugin にしてみた。jQuery Touchwipeとほとんど一緒なんだけど、jQuery Mobileも参考に、touchだけじゃなくてmouseでも使えるようにした。

Pluginのテンプレート

(function($){
    $.fn.myplugin = function(option){
        // default values
        var config = {
            config1: "value"
        };

        // override config with the give option(s)
        if (option) {
            $.extend(config, option);
        }

        // "this" is a jQuery object
        this.each(function(){
            // in the function, "this" is a DOM element
            // set shorthand of jQuery object if necessary
            var $this = $(this);

            // do something
        });

        // return "this" not to break method chain
        return this;
    };
})(jQuery);

シンプルな Plugin なら、これの必要な箇所に必要な処理を入れてけばいい。作り方を解説するみたいなタイトル付けたけど、あんまり解説する事なくて、テンプレメモだ。。

参考:

今回作ったやつ (jQuery.swipeListener)

jQuery.swipeListener (GitHub)

(function($){

    $.fn.swipeListener = function(option){
        var config = {
            duration: 1000, // within this period
            minX: 20, // swipe L/R if touch position moved more than this horizontally
            minY: 20, // swipe U/D if touch position moved more than this vertically
            swipeUp: undefined,   // callback function invoked when swipe up
            swipeDown: undefined, // or swipe down,
            swipeLeft: undefined, // or swipe left,
            swipeRight: undefined // or swipe right
        };

        if (option) {
            $.extend(config, option);
        }

        this.each(function(){
            var start = undefined;
            var stop  = undefined;
            var $this = $(this);
            var isTouchDevice   = typeof this.ontouchstart !== "undefined";
            var touchStartEvent = isTouchDevice ? "touchstart" : "mousedown";
            var touchMoveEvent  = isTouchDevice ? "touchmove" : "mousemove";
            var touchEndEvent   = isTouchDevice ? "touchend" : "mouseup";

            $this.bind(touchStartEvent, touchStart);

            function touchStart(event){
                var e = isTouchDevice ? event.originalEvent.touches[0] : event;
                start = {
                    x: e.pageX,
                    y: e.pageY,
                    time: (new Date()).getTime()
                };
                event.stopPropagation();

                $this.bind(touchMoveEvent, touchMove).one(touchEndEvent, touchEnd);
            };

            function touchMove(event){
                if (!start) {
                    return;
                }

                event.preventDefault();

                var e = isTouchDevice ? event.originalEvent.touches[0] : event;
                stop = {
                    x: e.pageX,
                    y: e.pageY,
                    time: (new Date()).getTime()
                };
            };

            function touchEnd(event){
                $this.unbind("touchmove mousemove", touchMove);

                if (start && stop && stop.time-start.time < config.duration) {
                    diffX = start.x - stop.x;
                    diffY = start.y - stop.y;

                    if (Math.abs(diffX) > config.minX) {

                        if (diffX > 0 && config.swipeLeft) {
                            config.swipeLeft();
                        } else if (config.swipeRight) {
                            config.swipeRight();
                        }

                    } else if (Math.abs(diffY) > config.minY) {

                        if (diffY > 0 && config.swipeUp) {
                            config.swipeUp();
                        } else if (config.swipeDown) {
                            config.swipeDown();
                        }
                    }
                }

                start = stop = undefined;
            };
        });

        return this;
    };

})(jQuery);
はじめての jQuery Plugin