v1.6.0 Hashクラス
前回最後にHashクラスに言及したので見てみます。
【抜粋】 function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { if (function() { var i = 0, Test = function(value) { this.key = value }; Test.prototype.key = 'foo'; for (var property in new Test('bar')) i++; return i > 1; }()) { function each(iterator) { var cache = []; for (var key in this._object) { var value = this._object[key]; if (cache.include(key)) continue; cache.push(key); var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } } } else { function each(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } } } function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } return { initialize: function(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); }, _each: each, set: function(key, value) { return this._object[key] = value; }, get: function(key) { return this._object[key]; }, unset: function(key) { var value = this._object[key]; delete this._object[key]; return value; }, toObject: function() { return Object.clone(this._object); }, keys: function() { return this.pluck('key'); }, values: function() { return this.pluck('value'); }, index: function(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; }, merge: function(object) { return this.clone().update(object); }, update: function(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); }, toQueryString: function() { return this.map(function(pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return values.map(toQueryPair.curry(key)).join('&'); } return toQueryPair(key, values); }).join('&'); }, inspect: function() { return '#<Hash:{' + this.map(function(pair) { return pair.map(Object.inspect).join(': '); }).join(', ') + '}>'; }, toJSON: function() { return Object.toJSON(this.toObject()); }, clone: function() { return new Hash(this); } } })()); Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H;
最初の部分でいきなり躓きました。どうやらあらかじめprototypeにプロパティが設定されていて、インスタンス生成時または後に同名のプロパティを設定すると、別のプロパティになる場合があるということのようですが。手持ちの環境(Xp+IE,Firefox,Opera)では確認できませんでした。で、
http://dev.rubyonrails.org/browser/spinoffs/prototype/trunk/src
で調べようとしたら、最新(hash.js Revision 8139)からは消えてました^^; 「冗長」とか言われてます。
【hash.js Revision 8139】 function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } return { initialize: function(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); }, _each: function(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } }, :(中略) } })()); Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H;
普通に_eachが設定されています。・・・v1.6.0の分岐は気にしないことにします(逃)。
initializeメソッドで大幅な方針転換が示されています。以前はオブジェクト(連想配列)そのものにHashクラスを継承させていました。このため、Hashクラスが持つメソッドと同名のプロパティは使用できない、値として関数は取れないという制限がありました。これを、オブジェクトを複製してプロパティとして持つことで解消しています。
【参考URL】http://d.hatena.ne.jp/susie-t/20060802/1154487988
このため、メンバの参照・追加・変更・削除をオブジェクトに対して直に行うことができなくなります。
【参考】これは不可。 var hash = $H({ a: "A", b: "B" }); alert(hash.a);//undefined hash.c = "C"; hash.a = "AA"; delete hash.b; hash.each(function(pair){ alert(pair.key + ":" + pair.value); // a:A > b:B })
メンバの操作は原則、Hashのメソッド経由で行います。参照>get、追加・更新>set、削除>unsetです。
【例】これはOK var hash = $H({ a: "A", b: "B" }); alert(hash.get("a")); //A hash.set("c", "C"); hash.set("a", "AA"); hash.unset("b"); hash.each(function(pair){ alert(pair.key + ":" + pair.value); // a:AA > c:C })
また、mergeメソッドはhashインスタンスが持つオブジェクトのコピーに引数のオブジェクトを上書きしたものを返却するだけとなり、更新はしなくなりました。更新する場合はupdateメソッドを使用します。
【例】 var hash = $H({ a: "A", b: "B" }); obj = { b: "BB", c: "C" } var mrg = hash.merge(obj); alert(mrg.get("b")); // BB alert(hash.get("b")); // B var upd = hash.update(obj); alert(upd.get("b")); // BB alert(hash.get("b")); // BB
制限の解消のためとはいえ、大変なインターフェースの変更。これは泣いた人も多いんじゃなかろうか・・・。
v1.6.0 Templateクラス
v1.4.0の解読のように逐行解読はしませんです。読んでいて書きたくなったらメモするというスタンスで。
【抜粋】 var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return ''; var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3]; var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }.bind(this)); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
Templateクラス自体はv1.5.0preから追加されたもの。
文字列内に置換用文字列を埋め込んで使用すると、用意したオブジェクトの対応するプロパティの値に置換されるというもの。
基本的には以下のように使います。
【例】 var template = new Template("#{a}, #{b}, #{c}"); var obj = {a:"A", b:"B", c:"C"}; var str = template.evaluate(obj); alert(str); //「A, B, C」と表示される。
このあたりは他に言及されてるサイトも多いので問題ないと思います。
さて、ソースコードを読んでみるとそれだけの機能としてはなんだか複雑。特に正規表現。
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
基本的な知識が不足していて当初理解不能に陥りました。最初の部分、
[^.[]+
を「すべての文字(.)または「[」でないものの1以上繰り返し」と読んでしまい混乱。この「.」はメタキャラクタとして扱われないのですね。
ブラケット表現中では「^」を除くすべてのメタキャラクタがその意味を失います。*1
(「正規表現辞典 (DESKTOP REFERENCE)」03-01-04 p121)
更に悩んで、やっと分かりました。つまり、置換用の値を保持するオブジェクトに、入れ子構造を許容するためのものなのです。*2
【例】 var obj = { a:{ a:"AA", b:"AB" }, b:{ a:{ a:"BAA", b:"BAB" } }, c:{ "c]c": "C]C" } }; var template = new Template("#{a.a}, #{a[b]}, #{b.a.a}, #{b[a][b]}, #{c[c\\]c]}"); var str = template.evaluate(obj); alert(str); //「AA, AB, BAA, BAB, C]C」と表示される。
ドット(.)繋ぎでも、[ ]指定でもOKになっています。[ ]指定でプロパティ名に「]」を使用したい場合は「\\]」としてエスケープします。
・・・あ、忘れてた。以下の部分について。
if (Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements();
toTemplateReplacementsメソッドをもつのは現在Hashクラスのみです。つまり、置換用値保持オブジェクトがHashクラスインスタンスだった場合に、Hashクラスインスタンスが持つ本来のオブジェクトに置き換えるというものです。v1.4.0ではカスタムオブジェクト(連想配列)そのものにHashクラスを継承させていましたが、v1.6.0ではHashクラスインスタンスがそのオブジェクトをプロパティとして持つという形態になっているためです。
【参考URL】http://d.hatena.ne.jp/susie-t/20061106/1162785967
上記で言及した制限はv1.6.0で解消されていることになります。まあ、やっぱりそうなりますよね・・・。
v1.6.0 Classオブジェクト
【抜粋】 /* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } }; Class.Methods = { addMethods: function(source) { var ancestor = this.superclass && this.superclass.prototype; var properties = Object.keys(source); if (!Object.keys({ toString: true }).length) properties.push("toString", "valueOf"); for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames().first() == "$super") { var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } }); } this.prototype[property] = value; } return this; } };
【参考サイト】http://d.hatena.ne.jp/kazu-yamamoto/20071024/1193195233
詳しい解説は上記サイトにありますので、気が付いたことだけメモ。
随分変わってしまいましたね・・・。昔はたった7行だったのに^^;
【参考サイト】http://d.hatena.ne.jp/susie-t/20060710/1152510376
こんなことになったのはすべて、JAVAでいえばsuperにあたる機能を実装しようとしたからです。(逆に言うと、superなんて使わなければもとの7行コードでも十分な気がします)
とりあえず使ってみます。
【例】 var Animal = Class.create({ weight: 10, initialize: function(w){ this.weight = (w || 50); }, eat: function(){ alert("Animal : eat"); }, move: function(){ alert("Animal : move"); } }); var Dog = Class.create(Animal, { initialize: function($super, w){ $super(w); }, eat: function($super){ $super(); alert("Dog : eat"); }, move: function(c){ alert("Dog : move : " + c); } }); var a = new Animal(); alert(a.weight); a.eat(); var d = new Dog(100); alert(d.weight); d.eat(); d.move(5);
【結果(alert表示文字列の順)】 50 Animal : eat 100 Animal : eat Dog : eat Dog : move : 5
Class.create自体にカスタムオブジェクトを渡すことで、すぐにクラス定義が記述できるようになってます。以前はClass.createでオブジェクトを取得し、Object.extend等でprototypeプロパティを設定していました。
継承する場合は、第一引数を親クラス(関数)、第二引数をクラス定義用カスタムオブジェクトにします。
上記Dogクラスのinitialize, eatメソッドの第一仮引数に$superを設定しています。この名前は固定です。ここに設定される関数を実行すると、親クラスにある同名のメソッドが実行されます。ただし、メソッド呼び出し時の引数には、この$superにあたるものは必要ありません。これはprototype.jsが涙ぐましい努力によって、第一仮引数が$superである場合は、そこに親クラスの同名メソッド*1を入れ、第二仮引数以降に実行時引数を入れる、ということをしてくれているからです。
なので、子クラスだからといってすべてのメソッドの第一仮引数が$superである必要はありません。親クラスメソッドを使わないのなら、普通に仮引数を設定すればOKです。
JAVAだとsuper.methodとして別名のメソッドも扱えるのですが、この$superではそれはできません。・・・無理やりやると以下になります。
【参考】無理やり親クラスの別メソッドを実行する。 上記Dog.moveを以下のように変更。 move: function(c){ alert("Dog : move : " + c); this.constructor.superclass.prototype.eat.bind(this)(); //"Animal : eat"と表示される。 }
こっちのほうが汎用性があるかも^^;
以下はコードを見て気が付いたこと。(基本的なことも含めて。)
【抜粋】 var ancestor = this.superclass && this.superclass.prototype;
この場合の動作の理解があいまいでした。これは以下とほぼ同義なんですね。
var ancestor = (this.superclass) ? this.superclass.prototype : null;
または
var ancestor = null; if(this.superclass != null){ ancestor = this.superclass.prototype; }
以下はちょっと悩みました。
【抜粋】 if (!Object.keys({ toString: true }).length)
Object.keysはオブジェクトのプロパティ名の配列を返す拡張メソッド*2。それはいいとして、コードだけ見ると必ずfalseになる気が。
じつはこれは、toStringという名前のプロパティが、列挙可能になるかどうかを判定しているもの。
【参考サイト】http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object:propertyIsEnumerable
FireFox、Operaはfalseですが、IEはtrueになります。つまり、IEはtoStringという名前のプロパティを自動的に列挙不能にしているのです(ちなみにvalueOfも)。
【参考】上記は以下と同じ if(!{ toString: true }.propertyIsEnumerable("toString"))
最後に頭が痛かったのは、Function.wrapメソッド。
【抜粋】 var method = value, value = Object.extend((function(m) { return function() { return ancestor[m].apply(this, arguments) }; })(property).wrap(method), { valueOf: function() { return method }, toString: function() { return method.toString() } });
wrapメソッドの定義は以下。
【抜粋】Function.wrapメソッド Object.extend(Function.prototype, { :(省略) wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); } }, :(省略) });
wrapメソッドは、引数の関数wrapperを実行するクロージャを返却します。wrapper実行時引数は第一引数が自関数、第二引数以降がクロージャ実行時引数です。
ちょっと混乱したのは、「__method.bind(this)」の部分。でも良く考えてみると、これでいいのですね。もし以下のようにすると
wrap: function(wrapper) { var __method = this; return function() { return wrapper.apply(this, [__method].concat($A(arguments))); } },
前述$super実行時、自クラスのプロパティ・メソッドが使用できないことになります。__methodはクロージャ参照なので、そのまま実行するとwindowオブジェクトのメンバとして実行されます。これを解消するためbind(this)が必要なわけです。
オブジェクト調査用関数
作ったのを忘れないようメモ。たいしたものではありません。。。
2008/01/15 変更
関数そのもののprototype.js依存を解消しました。
既出のオブジェクトは追わないようにしました。
デフォルトで表示する階層を3、各プロパティを20までに制限しました。引数で変更可能です。
})(Prototype);
を
})(document.body, "test", 2, 30);
にすれば、bodyのメンバをid:testの要素内(別途作成)に、最大2階層、各30メンバまで表示、ということになります。(最大階層・メンバ数を大きくするとブラウザによりエラーとされる場合があります。)
<html> <head> <title></title> <body> </body> <script src="prototype.js"></script> <script> (function (obj, id, maxLevel, maxCount){ var div = document.createElement('div'); var text = document.createTextNode(''); div.appendChild(text); function escapeHTML(str) { text.data = str; return div.innerHTML; } var elm = (typeof(id) == "string") ? document.getElementById(id) : id; if(maxLevel == null) maxLevel = 3; if(maxCount == null) maxCount = 20; var already = []; var str = (function(_obj, level){ level++; var _callee = arguments.callee; var str = "<table border>"; var count = 0; for(var key in _obj){ if(count >= maxCount){ str += "<tr><th><i>(OVER MAX COUNT=" + maxCount + ")</i></th><td></td></tr>"; break; } var value = _obj[key]; switch(typeof(value)){ case "undefined": value = "undefined"; break; case "boolean": case "number": break; case "string": case "function": value = escapeHTML(value.toString()); break; case "object": if(value == null){ break; }else if(value == elm){ value = "<i>(THIS ELEMENT)</i>"; break; }else{ var flg = false; for(var i = 0; i < already.length; i++){ if(already[i] == value){ flg = true; break; } } if(flg){ value = "<i>(ALREDY EXISTS)</i>"; }else if(level >= maxLevel){ value = "<i>object (OVER MAX LEVEL=" + maxLevel + ")</i>"; }else{ already.push(value); value = _callee(value, level); } } break; default: var msg = "unknown type : " + typeof(value); alert(msg); throw new Error(msg); } str += "<tr><th>" + escapeHTML(key) + "</th><td>" + value + "</td></tr>"; count++; } str += "</table>"; return str; })(obj, 0); if(elm == null){ elm = document.createElement("div"); document.body.appendChild(elm); } elm.innerHTML = str; })(Prototype); </script> </html>
v1.6.0を読みはじめました(今更^^;)。やっぱりえらく複雑になってますね・・・。解読したのがv1.4.0の頃でよかった。今だったら挫折してる気がします><;
↑はその一環で書いたものです。
記録を載せるかは未定です。書くとしても個人的なメモ程度になると思います。
ドラッグアンドドロップサンプル(圧縮版)
「ドラッグアンドドロップサンプル - Backstage of theater.js」の圧縮版です。・・・単に書いてみたかっただけです^^;
「こうしたほうがいいよ」的なアドバイスがありましたらコメントくださるとありがたいです。
<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 //EN'> <html> <head> <script> /** * 要素ドラッガブル化関数 * id : 対象要素ID */ function makeDraggable(i){var I=parseInt,d=document,w=window,m=[d,'mousemove', function(v){s.left=I(P(v)-a)+'px';s.top=I(P(v,1)-b)+'px';C(v)}],u=[d,'mouseup', function(v){S.apply(w,m);S.apply(w,u);C(v)}],l=d.getElementById(i),s=l.style,p=G ('position'),a=b=0;s.cursor='pointer';if(!p||p=='static'){s.position='relative'; if(w.opera)s.top=s.left='0px'}O(l,'mousedown',function(v){var x=G('left'),y=G( 'top');a=P(v)-((!x||x=='auto')?0:I(x));b=P(v,1)-((!y||y=='auto')?0:I(y));O.apply (w,m);O.apply(w,u);C(v)});function G(n){var r=l.currentStyle;return (r)?r[n]:w. getComputedStyle(l,'').getPropertyValue(n)}function P(v,z){var t=(z)?'Y':'X',o= 'scroll'+((z)?'Top':'Left');return (v['client'+t]+(d.documentElement[o]||d.body[ o]))||v['page'+t]}function C(v){try{v.prevDefault();v.stopPropagation()}catch(e) {v.returnValue=0;v.cancelBubble=1;}}function O(l,n,f){try{l.addEventListener(n,f ,0)}catch(e){try{l.attachEvent('on'+n,f)}catch(e){}}}function S(l,n,f){try{l. removeEventListener(n,f,0)}catch(e){try{l.detachEvent('on'+n,f)}catch(r){}}}} function init(){ makeDraggable('test1'); makeDraggable('test2'); makeDraggable('test3'); } </script> <style> div{border:solid blue 2px;background-color:yellow;} </style> </head> <body onload='init();'> <div id='test1' style='width:300px;height:300px;'>TEST1 <div id='test2' style='width:200px;height:200px;'>TEST2 <div id='test3' style='width:100px;height:100px;'>TEST3 </div></div></div> </body> </html>
【注】DIV要素の(幅OR高さ)AND背景色をつけないと、IEで初回ドロップ後にイベントの有効範囲が文字の部分だけになります。
Ajax.Requestもどき隠しフレーム通信
2007/12/19追記。onExceptionオプションを追加しました。例外発生時の処理も変更しています。
2007/12/03追記。Opera9.24に対応しました。
以前書いたように、IE7ではローカル環境でAjaxが使えません。今まで使えたのに・・・。
拙作theater.jsはprototype.jsを使用しているとはいえ、Ajax関連は未使用です。けど、readmeでwikiのソースをそのまま使うためにAjax.Requestを使ってました。これがIE7だとまったく読めなくなります。
で、隠しフレーム通信に再登場願います。昔、Ajaxのなかった頃、ユーザーの注文に対して一日悩んだ挙句自力でこの方法を編み出すも、直後に持っていた本に載ってるのが発覚して凹んだことはすでになつかしい話・・・。
Ajax.Requestからの移行を容易にするために、インターフェースを似せてみます。
<html> <head> <title></title> <script> function getIFrameDocument(aID){ if (document.getElementById(aID).contentDocument){ return document.getElementById(aID).contentDocument; } else { return document.frames[aID].document; } } var HFT = {}; HFT._index = 0; HFT.Request = function(url, options) { this.url = url; this.options = options; var _this = this; var iframe = document.createElement('iframe'); with(iframe.style){ visibility = "hidden"; position = "absolute"; top = "0px"; left = "0px"; width = "0px"; height = "0px"; } //iframe.id = "_HFT_transport_" + new Date().getTime().toString(); //連続実行時に同じ値になる場合もあるので連番に変更・・・。 iframe.id = "_HFT_transport_" + ((HFT._index == Number.MAX_VALUE) ? 0 : HFT._index++); document.body.appendChild(iframe); try{ iframe.src = url; }catch(e){ dispatchException(e); return; } var count = 0; var tid = setInterval(function(){ var fdb = null; try{ fdb = getIFrameDocument(iframe.id).body; if(fdb == null) throw new Error(); }catch(e){ if(count++ > (options.timeout || 100)){ clearInterval(tid); document.body.removeChild(iframe); dispatchException(e); return; } return; } clearInterval(tid); var responseText = (fdb.innerHTML.match(/\<pre\>((?:[^<]|\r|\n)*)(?:\<\/pre\>)?/i)||["",""])[1]; responseText = responseText.replace(/</ig, "<") .replace(/>/ig, ">") .replace(/&/ig, "&"); var transport = { responseText: responseText }; options.onComplete(transport); document.body.removeChild(iframe); }, 100); function dispatchException(exception) { (options.onException || function(){})(_this, exception); } }; function init(){ new HFT.Request('test.txt', { onComplete: function(req){ document.getElementById("test").value = req.responseText; }, onException: function(hft, e){ alert("HFT Error : " + hft.url); } }); } </script> </head> <body onload="init();"> <textarea id="test" style="width:500px;height:500px;font-size:14px;"></textarea> </body> </html>
【test.txt】(utf-8) <div>HFT.Requestのテスト。</div> <div> <div>&&&</div> </div>
読み込むファイルの拡張子はtxtにすること。htm等だと動作しません。
test.txtには改行、空白、タグがありますがそのまま取得できます。
init関数の中身に注目。
new HFT.Request('test.txt', { onComplete: function(req){ document.getElementById("test").value = req.responseText; }, onException: function(hft, e){ alert("HFT Error : " + hft.url); } });
「HFT」(Hidden Frame Transportの略)を「Ajax」に変えればprototype.jsのAjax.Requestと同じ使い方ということがお分かりいただけると思います。・・・ただし、onComplete以外のoptionはないです。→12/19 オプションにonExceptionを追加しました。また、使えるのはresponseTextだけです。その他は追加未定^^;
(すみません、後で仕様整理します。。。)
あと、IE7が今手元にないのでIE7で動く保証が無いですwあとで確認して追記します・・・。→WinVista + IE7で動作を確認しました。動いてよかった^^;
※テキストファイルはutf-8にしましたが、shift_jisでも動作する模様。文字コードについての妥当な場合の条件はまだ不明確です。おいおい調べていきます・・・。
改行コード
上記で取得できるデータは、ブラウザによって改行コードが違います。
IE6,Opera9.24 | \r\n |
---|---|
Firefox2.0.0.11 | \n |
扱う場合はご注意ください。
ドラッグアンドドロップサンプル
某所で回答したものを保存します。
・・・prototype.jsからいろいろ持ってきてますが^^;*1
>>>(圧縮版はこちら。)<<<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 //EN"> <html> <head> <script> function getStyle(elem, IEStyleProp, CSSStyleProp){ if(elem.currentStyle){ return elem.currentStyle[IEStyleProp]; }else if(window.getComputedStyle){ var compStyle = window.getComputedStyle(elem, ""); return compStyle.getPropertyValue(CSSStyleProp); } } function pointerX(event) { return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); } function pointerY(event) { return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); } function stop(event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } } function observe(element, name, observer) { if (element.addEventListener) { element.addEventListener(name, observer, false); } else if (element.attachEvent) { element.attachEvent('on' + name, observer); } } function stopObserving(element, name, observer) { if (element.removeEventListener) { element.removeEventListener(name, observer, false); } else if (element.detachEvent) { try { element.detachEvent('on' + name, observer); } catch (e) {} } } /** * 要素ドラッガブル化関数 * id : 対象要素ID */ function makeDraggable(id){ var element = document.getElementById(id); var pos = getStyle(element, 'position', 'position'); if (pos == 'static' || !pos) { element.style.position = 'relative'; if (window.opera) { element.style.top = 0; element.style.left = 0; } } element.style.cursor = "pointer"; observe(element, "mousedown", start); var dx = dy = 0; function start(event){ event = event || window.event; var x = getStyle(element, 'left', 'left'); var y = getStyle(element, 'top', 'top'); x = (!x || x == 'auto') ? 0 : parseInt(x); y = (!y || y == 'auto') ? 0 : parseInt(y); dx = pointerX(event) - x; dy = pointerY(event) - y; observe(document, "mousemove", move); observe(document, "mouseup", end); stop(event); } function move(event){ event = event || window.event; var x = parseInt(pointerX(event) - dx); var y = parseInt(pointerY(event) - dy); element.style.left = x + "px"; element.style.top = y + "px"; stop(event); } function end(event){ event = event || window.event; stopObserving(document, "mousemove", move); stopObserving(document, "mouseup", end); stop(event); } } function init(){ makeDraggable("test1"); makeDraggable("test2"); makeDraggable("test3"); } </script> <style> div{ border:solid blue 2px;background-color:yellow; }; </style> </head> <body onload="init();"> <div id="test1" style="width:300px;height:300px;">TEST1 <div id="test2" style="width:200px;height:200px;">TEST2 <div id="test3" style="width:100px;height:100px;">TEST3 </div> </div> </div> </body> </html>
【注】DIV要素の(幅OR高さ)AND背景色をつけないと、IEで初回ドロップ後にイベントの有効範囲が文字の部分だけになります・・・。
*1:prototype.jsにドラッグアンドドロップ化機能そのものはありません。prototypeを使用しているscript.aculo.usにはあります。