3回に分けてお送りしているdragdrop.
Draggable
ドラッグ可能要素のクラスです。機能としては、
- element
- ドラッグ可能になる要素です。
- handle
- ドラッグの”
把手” になる要素です。デフォルトではelementの要素と同一です。ドラッグ開始のためのmousedownイベントハンドラはこの要素を監視します。 - dragging
- 現在ドラッグ中かどうかのフラグです。
- delta
- ドラッグの出発地点です。[x,y]の配列です。
- offset
- ドラッグを開始したときの、
マウスポインタの位置と要素の位置のズレの量です。 - _clone
- options.
ghostingが有効のときに出発地点に表示される、 要素のクローンです。 - _isScrollChild
- element要素が、
options. scroll要素の子要素かどうかです。 - originalScrollLeft、
originalScrollTop - ドラッグを開始したときのoptions.
scroll要素のスクロール位置です。 - options.
handle - ドラッグの”
把手” になる要素を指定します。指定方法は、 DOM idか、 element要素の子要素が含むクラス名です。デフォルトはドラッグ可能要素と同一です。 - options.
starteffect - ドラッグを開始したときのフックです。デフォルトではEffect.
Opacityで要素をフェードアウトします。 - options.
change - ドラッグ中に位置が変化するたびに呼ぶフックです。
- options.
reverteffect - ドラッグをキャンセルしたとき、
または、 ドロップが失敗したときのフックです。デフォルトではEffect. Moveで要素が元の位置に戻る様子をアニメーションで表現します。 - options.
endeffect - ドラッグを終了したときのフックです。デフォルトではEffect.
Opacityで要素をフェードインします。 - options.
onDropped - ドロップした直後に呼ぶフックです。
- options.
zindex - ドラッグ中の要素のCSSのzindexプロパティの値を指定します。デフォルトは1000です。
- options.
revert - ドラッグを終了したときのフックを呼ぶかどうかです。デフォルトではfalseです。真偽値を与えるほかに、
関数を与えることもできます。この関数は、 ドラッグの終了時に、 ドラッグした要素を引数に呼ばれるので、 そこで真偽値を返すようにします。 - options.
quiet - ドラッグ中に、
ドロップ先の変化の表示を一切しないオプションです。デフォルトではfalseです。 - options.
scroll - ドラッグにあわせてスクロールする要素を指定します。デフォルトはwindowで、
ブラウザのスクロールが動きます。 - options.
scrollSensitivity - マウスポインタと、
options. scroll要素の短形領域の枠がこの値以下に近づくと、 スクロールを始めます。デフォルトは20pxです。大きければ大きいほどスクロールが始まりやすくなります。 - options.
scrollSpeed - スクロールの速度です。デフォルトは15px毎秒です。
- options.
snap - スナップするかどうか、
するならばその幅を指定します。デフォルトではfalseです。3つの指定方法があります。配列[x,y]か、 数値aか (配列[a,a]を与えるのと同じです)、 関数 (引数にx座標,y座標をとり、配列[x,y]を返すようにします) です。 - options.
delay - Draggablesのactivate関数で、
処理を指定ミリ秒間遅延するためのオプションです。デフォルトは0です。 - options.
ghosting - ドラッグ中に元の場所に要素のクローンを表示するかどうかです。デフォルトはfalseです。
それではコードを見ていきましょう。
0227:var Draggable = Class.create({
0228: initialize: function(element) {
0229: var defaults = {
0230: handle: false,
0231: reverteffect: function(element, top_offset, left_offset) {
0232: var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
0233: new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
0234: queue: {scope:'_draggable', position:'end'}
0235: });
0236: },
0237: endeffect: function(element) {
0238: var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
0239: new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
0240: queue: {scope:'_draggable', position:'end'},
0241: afterFinish: function(){
0242: Draggable._dragging[element] = false
0243: }
0244: });
0245: },
0246: zindex: 1000,
0247: revert: false,
0248: quiet: false,
0249: scroll: false,
0250: scrollSensitivity: 20,
0251: scrollSpeed: 15,
0252: snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
0253: delay: 0
0254: };
0255:
0256: if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
0257: Object.extend(defaults, {
0258: starteffect: function(element) {
0259: element._opacity = Element.getOpacity(element);
0260: Draggable._dragging[element] = true;
0261: new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
0262: }
0263: });
0264:
0265: var options = Object.extend(defaults, arguments[1] || { });
0266:
0267: this.element = $(element);
0268:
0269: if(options.handle && Object.isString(options.handle))
0270: this.handle = this.element.down('.'+options.handle, 0);
0271:
0272: if(!this.handle) this.handle = $(options.handle);
0273: if(!this.handle) this.handle = this.element;
0274:
0275: if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
0276: options.scroll = $(options.scroll);
0277: this._isScrollChild = Element.childOf(this.element, options.scroll);
0278: }
0279:
0280: Element.makePositioned(this.element); // fix IE
0281:
0282: this.options = options;
0283: this.dragging = false;
0284:
0285: this.eventMouseDown = this.initDrag.bindAsEventListener(this);
0286: Event.observe(this.handle, "mousedown", this.eventMouseDown);
0287:
0288: Draggables.register(this);
0289: },
0290:
227~290行目のinitializeは、
256~263行目で、
269行目で、
その場合、
272行目で、
273行目で、
275~278行目で、
280行目で、
286行目で、
288行目で、
0291: destroy: function() {
0292: Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
0293: Draggables.unregister(this);
0294: },
0295:
291~295行目のdestroyは、
292行目で、
293行目で、
0296: currentDelta: function() {
0297: return([
0298: parseInt(Element.getStyle(this.element,'left') || '0'),
0299: parseInt(Element.getStyle(this.element,'top') || '0')]);
0300: },
0301:
296~301行目のcurrentDeltaは、
0302: initDrag: function(event) {
0303: if(!Object.isUndefined(Draggable._dragging[this.element]) &&
0304: Draggable._dragging[this.element]) return;
0305: if(Event.isLeftClick(event)) {
0306: // abort on form elements, fixes a Firefox issue
0307: var src = Event.element(event);
0308: if((tag_name = src.tagName.toUpperCase()) && (
0309: tag_name=='INPUT' ||
0310: tag_name=='SELECT' ||
0311: tag_name=='OPTION' ||
0312: tag_name=='BUTTON' ||
0313: tag_name=='TEXTAREA')) return;
0314:
0315: var pointer = [Event.pointerX(event), Event.pointerY(event)];
0316: var pos = Position.cumulativeOffset(this.element);
0317: this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
0318:
0319: Draggables.activate(this);
0320: Event.stop(event);
0321: }
0322: },
0323:
302~323行目のinitDragは、
303行目で、
Firefoxでは、
317行目で、
319行目で、
0324: startDrag: function(event) {
0325: this.dragging = true;
0326: if(!this.delta)
0327: this.delta = this.currentDelta();
0328:
0329: if(this.options.zindex) {
0330: this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
0331: this.element.style.zIndex = this.options.zindex;
0332: }
0333:
0334: if(this.options.ghosting) {
0335: this._clone = this.element.cloneNode(true);
0336: this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
0337: if (!this.element._originallyAbsolute)
0338: Position.absolutize(this.element);
0339: this.element.parentNode.insertBefore(this._clone, this.element);
0340: }
0341:
0342: if(this.options.scroll) {
0343: if (this.options.scroll == window) {
0344: var where = this._getWindowScroll(this.options.scroll);
0345: this.originalScrollLeft = where.left;
0346: this.originalScrollTop = where.top;
0347: } else {
0348: this.originalScrollLeft = this.options.scroll.scrollLeft;
0349: this.originalScrollTop = this.options.scroll.scrollTop;
0350: }
0351: }
0352:
0353: Draggables.notify('onStart', this, event);
0354:
0355: if(this.options.starteffect) this.options.starteffect(this.element);
0356: },
0357:
324~357行目のstartDragは、
325行目で、
326行目で、
329~333行目で、
334~341行目で、
342~352行目で、
353行目で、
355行目で、
0358: updateDrag: function(event, pointer) {
0359: if(!this.dragging) this.startDrag(event);
0360:
0361: if(!this.options.quiet){
0362: Position.prepare();
0363: Droppables.show(pointer, this.element);
0364: }
0365:
0366: Draggables.notify('onDrag', this, event);
0367:
0368: this.draw(pointer);
0369: if(this.options.change) this.options.change(this);
0370:
0371: if(this.options.scroll) {
0372: this.stopScrolling();
0373:
0374: var p;
0375: if (this.options.scroll == window) {
0376: with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
0377: } else {
0378: p = Position.page(this.options.scroll);
0379: p[0] += this.options.scroll.scrollLeft + Position.deltaX;
0380: p[1] += this.options.scroll.scrollTop + Position.deltaY;
0381: p.push(p[0]+this.options.scroll.offsetWidth);
0382: p.push(p[1]+this.options.scroll.offsetHeight);
0383: }
0384: var speed = [0,0];
0385: if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
0386: if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
0387: if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
0388: if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
0389: this.startScrolling(speed);
0390: }
0391:
0392: // fix AppleWebKit rendering
0393: if(Prototype.Browser.WebKit) window.scrollBy(0,0);
0394:
0395: Event.stop(event);
0396: },
0397:
358~397行目のupdateDragは、
359行目で、
361~364行目で、
366行目で、
368行目で、
369行目で、
371~388行目で、
389行目で、
393行目で、
0398: finishDrag: function(event, success) {
0399: this.dragging = false;
0400:
0401: if(this.options.quiet){
0402: Position.prepare();
0403: var pointer = [Event.pointerX(event), Event.pointerY(event)];
0404: Droppables.show(pointer, this.element);
0405: }
0406:
0407: if(this.options.ghosting) {
0408: if (!this.element._originallyAbsolute)
0409: Position.relativize(this.element);
0410: delete this.element._originallyAbsolute;
0411: Element.remove(this._clone);
0412: this._clone = null;
0413: }
0414:
0415: var dropped = false;
0416: if(success) {
0417: dropped = Droppables.fire(event, this.element);
0418: if (!dropped) dropped = false;
0419: }
0420: if(dropped && this.options.onDropped) this.options.onDropped(this.element);
0421: Draggables.notify('onEnd', this, event);
0422:
0423: var revert = this.options.revert;
0424: if(revert && Object.isFunction(revert)) revert = revert(this.element);
0425:
0426: var d = this.currentDelta();
0427: if(revert && this.options.reverteffect) {
0428: if (dropped == 0 || revert != 'failure')
0429: this.options.reverteffect(this.element,
0430: d[1]-this.delta[1], d[0]-this.delta[0]);
0431: } else {
0432: this.delta = d;
0433: }
0434:
0435: if(this.options.zindex)
0436: this.element.style.zIndex = this.originalZ;
0437:
0438: if(this.options.endeffect)
0439: this.options.endeffect(this.element);
0440:
0441: Draggables.deactivate(this);
0442: Droppables.reset();
0443: },
0444:
398~444行目のfinishDragは、
399行目で、
401~405行目で、
407~413行目で、
408行目で、
411行目で、
416~419行目で、
417行目で、
420行目で、
421行目のDraggables.
423行目のoptions.
424行目で、
427~430行目で、
431行目で、
435行目で、
438行目で、
441行目のDraggables.
442行目のDroppables.
0445: keyPress: function(event) {
0446: if(event.keyCode!=Event.KEY_ESC) return;
0447: this.finishDrag(event, false);
0448: Event.stop(event);
0449: },
0450:
445~450行目のkeyPressは、
447行目で、
0451: endDrag: function(event) {
0452: if(!this.dragging) return;
0453: this.stopScrolling();
0454: this.finishDrag(event, true);
0455: Event.stop(event);
0456: },
0457:
451~457行目のendDragは、
453行目で、
454行目で、
0458: draw: function(point) {
0459: var pos = Position.cumulativeOffset(this.element);
0460: if(this.options.ghosting) {
0461: var r = Position.realOffset(this.element);
0462: pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
0463: }
0464:
0465: var d = this.currentDelta();
0466: pos[0] -= d[0]; pos[1] -= d[1];
0467:
0468: if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
0469: pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
0470: pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
0471: }
0472:
0473: var p = [0,1].map(function(i){
0474: return (point[i]-pos[i]-this.offset[i])
0475: }.bind(this));
0476:
0477: if(this.options.snap) {
0478: if(Object.isFunction(this.options.snap)) {
0479: p = this.options.snap(p[0],p[1],this);
0480: } else {
0481: if(Object.isArray(this.options.snap)) {
0482: p = p.map( function(v, i) {
0483: return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
0484: } else {
0485: p = p.map( function(v) {
0486: return (v/this.options.snap).round()*this.options.snap }.bind(this))
0487: }
0488: }}
0489:
0490: var style = this.element.style;
0491: if((!this.options.constraint) || (this.options.constraint=='horizontal'))
0492: style.left = p[0] + "px";
0493: if((!this.options.constraint) || (this.options.constraint=='vertical'))
0494: style.top = p[1] + "px";
0495:
0496: if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
0497: },
0498:
458~498行目のdraw関数は、
459行目のPosition.
460~463行目では、
466行目で、
468~471行目で、
473行目で、
477~488行目で、
478行目で、
481行目で、
484行目で、
490~494行目で、
0499: stopScrolling: function() {
0500: if(this.scrollInterval) {
0501: clearInterval(this.scrollInterval);
0502: this.scrollInterval = null;
0503: Draggables._lastScrollPointer = null;
0504: }
0505: },
0506:
499~506行目のstopScrollingは、
501行目で、
0507: startScrolling: function(speed) {
0508: if(!(speed[0] || speed[1])) return;
0509: this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
0510: this.lastScrolled = new Date();
0511: this.scrollInterval = setInterval(this.scroll.bind(this), 10);
0512: },
0513:
507~513行目のstartScrollingは、
511行目で、
510行目で、
0514: scroll: function() {
0515: var current = new Date();
0516: var delta = current - this.lastScrolled;
0517: this.lastScrolled = current;
0518: if(this.options.scroll == window) {
0519: with (this._getWindowScroll(this.options.scroll)) {
0520: if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
0521: var d = delta / 1000;
0522: this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
0523: }
0524: }
0525: } else {
0526: this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
0527: this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
0528: }
0529:
0530: Position.prepare();
0531: Droppables.show(Draggables._lastPointer, this.element);
0532: Draggables.notify('onDrag', this);
0533: if (this._isScrollChild) {
0534: Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
0535: Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
0536: Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
0537: if (Draggables._lastScrollPointer[0] < 0)
0538: Draggables._lastScrollPointer[0] = 0;
0539: if (Draggables._lastScrollPointer[1] < 0)
0540: Draggables._lastScrollPointer[1] = 0;
0541: this.draw(Draggables._lastScrollPointer);
0542: }
0543:
0544: if(this.options.change) this.options.change(this);
0545: },
0546:
514~546行目のscroll関数は、
516行目で、
518~524行目で、
そうでなければ、
520行目で、
531行目で、
532行目で、
533~542行目で、
535~540行目で、
541行目で、
544行目で、
0547: _getWindowScroll: function(w) {
0548: var T, L, W, H;
0549: with (w.document) {
0550: if (w.document.documentElement && documentElement.scrollTop) {
0551: T = documentElement.scrollTop;
0552: L = documentElement.scrollLeft;
0553: } else if (w.document.body) {
0554: T = body.scrollTop;
0555: L = body.scrollLeft;
0556: }
0557: if (w.innerWidth) {
0558: W = w.innerWidth;
0559: H = w.innerHeight;
0560: } else if (w.document.documentElement && documentElement.clientWidth) {
0561: W = documentElement.clientWidth;
0562: H = documentElement.clientHeight;
0563: } else {
0564: W = body.offsetWidth;
0565: H = body.offsetHeight
0566: }
0567: }
0568: return { top: T, left: L, width: W, height: H };
0569: }
0570:});
0571:
547~571行目で、
550~556行目で、
550行目で、
553行目で、
557~566行目で、
557行目で、
560行目で、
563行目で、
0572:Draggable._dragging = { };
0573:
572行目の_draggingは、