Hatena::Groupjavascript

lnzntのJavaScript日記 このページをアンテナに追加 RSSフィード

2011年07月01日(金)

Canvas API (復習2) ドラッグ & ドロップ

23:55 | Canvas API (復習2) ドラッグ & ドロップ - lnzntのJavaScript日記 を含むブックマーク はてなブックマーク - Canvas API (復習2) ドラッグ & ドロップ - lnzntのJavaScript日記 Canvas API (復習2) ドラッグ & ドロップ - lnzntのJavaScript日記 のブックマークコメント

HTML の DIV 要素にファイルをドロップして表示するサンプルです。

参考書籍『HTML5 ガイドブック』のロジックを真似てつくったものですが、コード自体は原型を留めておりません。

実行確認は、Ubuntu Linux 11.04 の Firefox 5.0 でしてます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <title>お試し</title>
    <style>
        body {
            background-color: #999999;
        }
        #dp {
            width:              640px;
            height:             480px;
            background-color:   #cccccc;
            border:             #000000 5px solid;
        }
    </style>

    <script src="utils.js"></script>
    <script>
        (function() {
            /*
                window の load イベントハンドラ
            */
            window.addEventListener('load', function() {
                var dp = get_element('dp');

                var show_image = function(file) {
                    try {
                        dp.remove_children();

                        file.is_image().read_as_url({onload: dp.create_img.bind(dp)});

                    } catch (e) { alert(e); }
                };

                dp.prevent_default('dragover');
                dp.prevent_default('drop');
                dp.addDropEventListener(show_image);
            }, false);
        })();
    </script>
</head>

<body>
    <div id="dp">ここに画像をドラッグ</div>
</body>
</html>

JavaScript の関数は、以下の utils.js に書きました。

    /*
        FileReader.EVENT_LIST
    */
    FileReader.EVENT_LIST = [
                                'onloadstart',
                                'onprogress',
                                'onabort',
                                'onerror',
                                'onload',
                                'onloadend',
                            ];

    /*
        Module
    */
    var Module = {
        arguments_to_array: function(args) {
            return Array.prototype.slice.apply(args);
        },

        pp: function(obj) {
            var s = "";

            for (var prop in obj) {
                s = s + prop + ": " + obj[prop] + "\n";
            }

            return s;
        },

        nop: function() {},
    };

    var a2a = Module.arguments_to_array;    /* alias */
    var pp  = Module.pp;                    /* alias */

    /*
        Object.prototype.extend
    */
    Object.prototype.extend = function(mod, override) {
        for (func in mod) {
            var is_func  = (typeof mod[func] === 'function');
            var is_undef = (this[func] === undefined);

            if (is_func && (is_undef || override)) {
                this[func] = mod[func];
            }
        }

        return this;
    };

    /*
        Function.prototype.curry
        Function.prototype.bind
    */
    Function.prototype.extend({
        curry: function() {
            var self = this;
            var args = a2a(arguments);

            return function() {
                return self.apply(this, args.concat(a2a(arguments)));
            };
        },

        bind: function(obj) {
            var self = this;
            var args = a2a(arguments).slice(1);

            return function() {
                return self.apply(obj, args.concat(a2a(arguments)));
            };
        },
    }, true);

    /*
        Exception
    */
    var Exception = function(type) {
        var message = a2a(arguments).slice(1).join(" ");

        return {
            type:       function() { return type;                   },
            message:    function() { return message;                },
            toString:   function() { return type + ": " + message;  },
        };
    };

    Exception.def_exception = function(name) {
        return this[name] = this.curry.apply(this, a2a(arguments));
    }

    Exception.def_exception('ArgumentError');
    Exception.def_exception('CommError');
        Exception.def_exception('SendError');
        Exception.def_exception('RecvError');
    Exception.def_exception('IndexError');
        Exception.def_exception('KeyError');
    Exception.def_exception('IOError');
        Exception.def_exception('EOFError');
        Exception.def_exception('ReadError');
        Exception.def_exception('WriteError');
    Exception.def_exception('NameError');
    Exception.def_exception('RangeError');
    Exception.def_exception('RegexpError');
    Exception.def_exception('RuntimeError');
    Exception.def_exception('SecurityError');
    Exception.def_exception('TypeError');
        Exception.def_exception('FileTypeError');

    /*
        Module.ObjectEX

    */
    Module['ObjectEX'] = {
        is_array: function() {
            return false;
        },

        each: function(func) {
            for (var prop in this) {
                func.call(this, this[prop], prop);
            }

            return this;
        },
    };

    /*
        Module.ArrayEX
    */
    Module['ArrayEX'] = {
        is_array: function() {
            return true;
        },

        each: function(func) {
            for (var i = 0; i < this.length; i++) {
                func.call(this, this[i], i);
            }

            return this;
        },
    };

    /*
        Module.Enumerable
    */
    Module['Enumerable'] = {
        all: function(func) {
            var result = true;

            this.each(function(x, i) {
                result = result && !!(func ? func(x, i) : x);
            });

            return result;
        },

        any: function(func) {
            var result = false;

            this.each(function(x, i) {
                result = result || !!(func ? func(x, i) : x);
            });

            return result;
        },

        include: function(item, compare_index) {
            return this.any(function(x, i) {
                return compare_index ? (i === item) : (x === item);
            });
        },

        select: function(func, with_index) {
            var result = [];

            this.each(function(x, i) {
                if (func ? func(x, i) : x) {
                    result.push(with_index ? [i, x] : x);
                }
            });

            return result;
        },

        reject: function(func, with_index) {
            var result = [];

            this.each(function(x, i) {
                if (!(func ? func(x, i) : x)) {
                    result.push(with_index ? [i, x] : x);
                }
            });

            return result;
        },

        map: function(func, with_index) {
            var result = [];

            this.each(function(x, i) {
                var a = (func ? func(x, i) : x);
                result.push(with_index ? [i, a] : a);
            });

            return result;
        },
    };

    /*
        module load
    */
    Object.prototype.extend(Module.ObjectEX);
    Array.prototype.extend(Module.ArrayEX, true);
    Array.prototype.extend(Module.Enumerable);

    /*
        Module.FileUtils
    */
    Module['FileUtils'] = {
        is: function(pattern) {
            if (!pattern.test(this.type)) {
                throw Exception.FileTypeError(pattern + " expected.");
            }

            return this;
        },

        read: function(scheme, callbacks) {
            var reader = new FileReader();

            FileReader.EVENT_LIST.select(function(evt) {
                return (typeof callbacks[evt] === 'function');
            })
            .each(function(evt) {
                reader[evt] = function() {
                    callbacks[evt](reader.result, reader);
                };
            });

            reader[scheme](this);

            return this;
        },
    };

    Module.FileUtils.read_as_url = Module.FileUtils.read.curry('readAsDataURL');
    Module.FileUtils.is_image    = Module.FileUtils.is.curry(/^image\//);

    /*
        Module.DOM
    */
    Module['DOM'] = {
        create_element: function(tag, attrs) {
            var elem = document.createElement(tag);

            for (name in (attrs || {})) {
                if (typeof attrs[name] === 'string') {
                    elem.setAttribute(name, attrs[name]);
                }
            }

            return elem;
        },

        create_img: function(url, attrs) {
            attrs = (attrs || {});
            attrs['src'] = url;

            return Module.DOM.create_element('img', attrs);
        },
    };

    /*
        Module.NodeUtils
    */
    Module['NodeUtils'] = {
        create_child: function(tag, attrs) {
            this.appendChild(Module.DOM.create_element(tag, attrs));

            return this;
        },

        create_img: function(url, attrs) {
            this.appendChild(Module.DOM.create_img(url, attrs));

            return this;
        },

        remove_children: function() {
            while (this.firstChild) {
                this.removeChild(this.firstChild);
            }

            return this;
        },

        add_listener: function(type, listener, useCapture) {
            var args = a2a(arguments);

            args.splice(0, 3, type, listener, useCapture || false);

            this.addEventListener.apply(this, args);

            return this;
        },

        prevent_default: function(type) {
            var listener = function(evt) {
                evt.preventDefault();
            };

            return this.add_listener(type, listener);
        },

        addDropEventListener: function(listener) {
            var wrapper = function(evt) {
                evt.dataTransfer.files.each(function(file) {
                    file.extend(Module.FileUtils);
                });
                listener.apply(this, evt.dataTransfer.files);
            };

            var args = a2a(arguments);

            args.splice(0, 1, 'drop', wrapper);

            return this.add_listener.apply(this, args);
        },
    };

    var get_element = function(id) {
        return document.getElementById(id).extend(Module.NodeUtils);
    };
  • バージョン 0.01a。バギーです。
  • var で定義した変数
    • Module
    • Exception
    • a2a
    • pp
    • get_element
  • 自分で書いたわけ
    • prototype.js とか jQuery とかを調べるのが面倒
    • Mix-in やモジュール関数は、Ruby 風のが欲しかった
  • 大文字で始まる名前の理由
    • Ruby の流儀を真似ました
    • 「大文字で始まる関数は new する」慣習を知らなかった(クラスベースでないのに何で new ???)