ブラウザにドラッグ&ドロップ

ちょっと前にはてブしたんだが。
Google Waveが発表されて、そのデモの中でブラウザにファイルをドラッグ&ドロップしてたとか。
そのやり方を解説してる方がいたので、そのままやってみた。


確かにできた。スゴイ!


で、javascriptでできるんだからFlashでもできるよね、と安直な発想でやってみた。

DnDSample.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" horizontalAlign="center" verticalAlign="middle" creationComplete="logic.onCreationCompleteHandler(event)" backgroundColor="0xffffff" backgroundGradientAlphas="[0,0]" xmlns:local="*">
    <local:DnDSampleLogic id="logic"/>
    <mx:Label id="lbl" text="" fontSize="48" />
</mx:Application>

まぁ、メイン部分はたいしたことやってない。


アプリケーションのメインロジック。GearsのjavascriptFlexを繋げてる。
DnDSampleLogic.as

package
{
    import flash.events.MouseEvent;
    import flash.external.ExternalInterface;
    import flash.net.URLRequest;
    import flash.net.navigateToURL;
    
    import mx.core.IMXMLObject;
    import mx.events.FlexEvent;
    
    import utils.JSUtil;

    public class DnDSampleLogic implements IMXMLObject
    {
        protected var view:DnDSample;
        public function DnDSampleLogic()
        {
        }

        public function initialized(document:Object, id:String):void
        {
            view = document as DnDSample;
        }
        
        public function onCreationCompleteHandler(event:FlexEvent):void
        {
            // gears_init.jsの内容をそのまま書き込む(こういう使い方して問題ないのか?)
            JSUtil.writeJSFunction(
                 "gears_init"
                ,[]
                , "if (window.google && google.gears) {"
                +     "return;"
                + "}"
                + "var factory = null;"
                + "/* Firefox */"
                + "if (typeof GearsFactory != 'undefined') {"
                +     "factory = new GearsFactory();"
                + "} else {"
                +     "/* IE */"
                +     "try {"
                +         "factory = new ActiveXObject('Gears.Factory');"
                +         "/* privateSetGlobalObject is only required and supported on IE Mobile on */"
                +         "/* WinCE. */"
                +         "if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {"
                +             "factory.privateSetGlobalObject(this);"
                +         "}"
                +     "} catch (e) {"
                +         "/* Safari */"
                +         "if ((typeof navigator.mimeTypes != 'undefined')"
                +             "&& navigator.mimeTypes['application/x-googlegears']) {"
                +             "factory = document.createElement('object');"
                +             "factory.style.display = 'none';"
                +             "factory.width = 0;"
                +             "factory.height = 0;"
                +             "factory.type = 'application/x-googlegears';"
                +             "document.documentElement.appendChild(factory);"
                +         "}"
                +     "}"
                + "}"
                + "if (!factory) {"
                +     "return false;"
                + "}"
                + "if (!window.google) {"
                +     "google = {};"
                + "}"
                + "if (!google.gears) {"
                +     "google.gears = {factory: factory};"
                + "}"
                + "return true;"
            );

            // 書き込んだgears_init.jsをおもむろに実行
            ExternalInterface.call("gears_init");

            // ドロップ時に呼ばれるコールバック関数を登録
            ExternalInterface.addCallback(
                "sendData",
                function(obj:Object):void{ // コールバック関数
                    trace(obj);
                }
            );

            // 以下、リンク先のサンプルのjavascriptを書き込む
            JSUtil.writeJSFunction(
                  "addEvent"
                , ["element", "name", "handler"]
                , "if(platform != 'IE') {"
                +     "element.addEventListener("
                +         "platform != 'Firefox' ? name : eventMap[name],"
                +         "handler,"
                +         "false"
                +     ");"
                + "} else {"
                +     "element.attachEvent('on' + name, handler);"
                + "}"
            );
            JSUtil.writeJSFunction(
                  "escapeHtml"
                , ["s"]
                , "return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');"
            );
            JSUtil.writeJSFunction(
                  "handleEnterOver"
                , ["event"]
                , "event.returnValue = false;"
                + "desktop.setDragCursor(event, 'copy');"
            );
            JSUtil.writeJSFunction(
                  "handleLeave"
                , ["event"]
                , "event.returnValue = false;"
                + "desktop.setDragCursor(event, 'none');"
            );
            JSUtil.writeJSFunction(
                  "handleDrop"
                , ["event"]
                , "event.stopPropagation && event.stopPropagation();"
                + "targetSWF.sendData(desktop.getDragData(event, 'application/x-gears-files'));"
            );

            JSUtil.writeJSFunction(
                  "dnd_init"
                , []
                , "desktop = null;"
                + "action  = ['install', 'インストール'];"
                + "platform  = null;"
                + "targetSWF = null;"
                + "var initialized = false;"
                + "try {"
                +     "if(!window.google || !window.google.gears) throw null;"
                +     "action = ['upgrade', '更新'];"
                +     "desktop = google.gears.factory.create('beta.desktop');"
                +     "var buildInfo = google.gears.factory.getBuildInfo();"
                +     "platform = buildInfo.indexOf(';ie')      > -1 ? 'IE'      : platform;"
                +     "platform = buildInfo.indexOf(';firefox') > -1 ? 'Firefox' : platform;"
                +     "platform = buildInfo.indexOf(';safari')  > -1 ? 'Safari'  : platform;"
                +     "platform = buildInfo.indexOf(';npapi')   > -1 ? 'Npapi'   : platform;"
                +     "eventMap = {"
                +         "'dragenter' : 'dragenter',"
                +         "'dragover'  : 'dragover',"
                +         "'dragleave' : 'dragexit',"
                +         "'drop'      : 'dragdrop'"
                +     "};"
                +     "targetSWF = document.getElementById('" + ExternalInterface.objectID + "');"
                +     "addEvent(targetSWF, 'dragenter', handleEnterOver);"
                +     "addEvent(targetSWF, 'dragover',  handleEnterOver);"
                +     "addEvent(targetSWF, 'dragleave', handleLeave);"
                +     "addEvent(targetSWF, 'drop',      handleDrop);"
                +     "initialized = true;"
                + "} catch(e) {"
                + "}"
                + "return initialized;"
            );
            if(!ExternalInterface.call("dnd_init")) {
                view.lbl.htmlText = "<a href='http://gears.google.com/?action=install'>Gearsをインストールしてください</a>";
                view.lbl.addEventListener(
                    MouseEvent.CLICK,
                    function(event:MouseEvent):void {
                        navigateToURL(new URLRequest("http://gears.google.com/?action=install"), "_blank");
                    }
                );
            } else {
                view.lbl.htmlText = "ここにドラッグ&ドロップしてください";
            }
        }
    }
}


上で使ってるFlexからjavascriptを書き込むユーティリティクラス。
JSUtil.as

package utils {
    import flash.external.ExternalInterface;

    public class JSUtil {

        public function JSUtil() {
        }

        public static function writeJS(
                js:String // javascriptコード
            ):Boolean {
            if (ExternalInterface.available == false)
                return false;
            try {
                ExternalInterface.call(
                    "document.insertScript = " + js
                );
            } catch(e:Error) {
                return false;
            }
            return true;
        }

        public static function writeJSFunction(
                functionName:String,    // 関数名
                functionArgments:Array, // 引数の配列
                functionContent:String  // 関数本体
            ):Boolean {
            if (ExternalInterface.available == false)
                return false;
            try {
                var argName:Object;
                var argSep:String = ", ";
                var insertJS:String = 
                      "function () {"
                    + "if(document. " + functionName + " == null) {"
                    + functionName + " = function (";
                if (functionArgments != null && functionArgments.length > 0) {
                    for each(argName in functionArgments) {
                        insertJS += argName.toString() + argSep;
                    }
                    insertJS = insertJS.substring(0, insertJS.lastIndexOf(argSep));
                }
                insertJS += ") {" + functionContent + "}" + "}" + "}";
                JSUtil.writeJS(insertJS);
            } catch(e:Error) {
                return false;
            }
            return true;
        }
    }
}


これで、デスクトップ(エクスプローラ)からドラッグされたファイルを、
Flex側で受け取ることができるようになる。


何かGears使ってjavascript使って、と回りくどいやり方。
Flashが最初からサポートしてくれるといいのに。