今回のSortableクラスは、
SortableObserver
ドラッグの開始、
0576:var SortableObserver = Class.create({
0577: initialize: function(element, observer) {
0578: this.element = $(element);
0579: this.observer = observer;
0580: this.lastValue = Sortable.serialize(this.element);
0581: },
0582:
576~582行目のinitializeは、
580行目で、
0583: onStart: function() {
0584: this.lastValue = Sortable.serialize(this.element);
0585: },
0586:
583~586行目のonStart関数は、
0587: onEnd: function() {
0588: Sortable.unmark();
0589: if(this.lastValue != Sortable.serialize(this.element))
0590: this.observer(this.element)
0591: }
0592:});
0593:
587~593行目のonEnd関数は、
588行目のunmark関数は、
589行目で、
Sortable
0594:var Sortable = {
0595: SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
0596:
0597: sortables: { },
0598:
595行目で、
597行目のsortablesは、
0599: _findRootElement: function(element) {
0600: while (element.tagName.toUpperCase() != "BODY") {
0601: if(element.id && Sortable.sortables[element.id]) return element;
0602: element = element.parentNode;
0603: }
0604: },
0605:
599~605行目の_findRootElementは、
0606: options: function(element) {
0607: element = Sortable._findRootElement($(element));
0608: if(!element) return;
0609: return Sortable.sortables[element.id];
0610: },
0611:
606~611行目のoptionsは、
0612: destroy: function(element){
0613: var s = Sortable.options(element);
0614:
0615: if(s) {
0616: Draggables.removeObserver(s.element);
0617: s.droppables.each(function(d){ Droppables.remove(d) });
0618: s.draggables.invoke('destroy');
0619:
0620: delete Sortable.sortables[s.element.id];
0621: }
0622: },
0623:
612~623行目のdestroyは、
616行目で、
617行目で、
618行目で、
620行目で、
0624: create: function(element) {
0625: element = $(element);
0626: var options = Object.extend({
0627: element: element,
0628: tag: 'li', // assumes li children, override with tag: 'tagname'
0629: dropOnEmpty: false,
0630: tree: false,
0631: treeTag: 'ul',
0632: overlap: 'vertical', // one of 'vertical', 'horizontal'
0633: constraint: 'vertical', // one of 'vertical', 'horizontal', false
0634: containment: element, // also takes array of elements (or id's); or false
0635: handle: false, // or a CSS class
0636: only: false,
0637: delay: 0,
0638: hoverclass: null,
0639: ghosting: false,
0640: quiet: false,
0641: scroll: false,
0642: scrollSensitivity: 20,
0643: scrollSpeed: 15,
0644: format: this.SERIALIZE_RULE,
0645:
0646: // these take arrays of elements or ids and can be
0647: // used for better initialization performance
0648: elements: false,
0649: handles: false,
0650:
0651: onChange: Prototype.emptyFunction,
0652: onUpdate: Prototype.emptyFunction
0653: }, arguments[1] || { });
0654:
624~740行目のcreate関数は、
まずは前半部分、
- tag
- デフォルトは'li'です。並び替えリストのアイテムにする要素のタグ名です。
- dropOnEmpty
- デフォルトはfalseです。trueにすると、
並び替えリストのコンテナ自身がドロップ可能要素になるので、 アイテムがひとつもないときでもドラッグを受け入れられるようになります。 - tree
- デフォルトはfalseです。trueにすると、
並び替えツリーを作ります。並び替えツリーでは、 treeTagの要素でできたツリー構造を並び替えることができます。 - treeTag
- デフォルトは'ul'です。並び替えツリーで、
ツリー構造を表現する要素のタグ名です。 - only
- デフォルトはfalseです。CSSクラスかその配列を指定します。受けつける要素を、
containmentでの制限以上に絞るためのオプションで、 それらのクラスのいずれかを持つ要素しか受けつけないようになります (containmentオプションは、 前回解説したように、 Droppablesクラスのオプションで、 要素のDOM idか、 DOM idの配列を指定し、 その子要素のドロップだけを受けつけるように制限するオプションです)。 - format
- アイテムのDOM id
(通常は"名称_アイテムID"の形式) から、 名称の部分とアイテムIDの部分の値を取り出すための正規表現です。 - elements
- デフォルトでは、
並び替えリストにする要素を全自動で計算するようになっていますが、 このオプションに、 そのような要素のDOM idの配列を人間の手で記述して渡してやることで処理時間を短縮できます。 - handles
- デフォルトでは、
並び替えリストのアイテムのハンドル (把手) にする要素を全自動で計算するようになっていますが、 このオプションに、 そのような要素のDOM idの配列を人間の手で記述して渡してやることで、 処理時間を短縮できます。 - onChange
- ドラッグに伴ってリストの並び順が変わった瞬間に呼ばれるフックです。また、
ドラッグ中の要素が別の並び替えリストに出て行ったときも、 このフックが呼ばれます。引数にドラッグ中の要素をとります。 - onUpdate
- ドラッグ終了時にリストの並び順が変わっていたときに呼ばれるフックです。引数に並び替えリストのコンテナをとります。このフックの呼び出し元のSortableObserver.
onEndにあるように、 このフックの動作はSortable. serializeの結果に依存しているので、 アイテムのDOM idが適切な形式になっているか、 くれぐれも注意してください。
0655: // clear any old sortable with same element
0656: this.destroy(element);
0657:
0658: // build options for the draggables
0659: var options_for_draggable = {
0660: revert: true,
0661: quiet: options.quiet,
0662: scroll: options.scroll,
0663: scrollSpeed: options.scrollSpeed,
0664: scrollSensitivity: options.scrollSensitivity,
0665: delay: options.delay,
0666: ghosting: options.ghosting,
0667: constraint: options.constraint,
0668: handle: options.handle };
0669:
0670: if(options.starteffect)
0671: options_for_draggable.starteffect = options.starteffect;
0672:
0673: if(options.reverteffect)
0674: options_for_draggable.reverteffect = options.reverteffect;
0675: else
0676: if(options.ghosting) options_for_draggable.reverteffect = function(element) {
0677: element.style.top = 0;
0678: element.style.left = 0;
0679: };
0680:
0681: if(options.endeffect)
0682: options_for_draggable.endeffect = options.endeffect;
0683:
0684: if(options.zindex)
0685: options_for_draggable.zindex = options.zindex;
0686:
0687: // build options for the droppables
0688: var options_for_droppable = {
0689: overlap: options.overlap,
0690: containment: options.containment,
0691: tree: options.tree,
0692: hoverclass: options.hoverclass,
0693: onHover: Sortable.onHover
0694: }
0695:
0696: var options_for_tree = {
0697: onHover: Sortable.onEmptyHover,
0698: overlap: options.overlap,
0699: containment: options.containment,
0700: hoverclass: options.hoverclass
0701: }
0702:
0703: // fix for gecko engine
0704: Element.cleanWhitespace(element);
0705:
0706: options.draggables = [];
0707: options.droppables = [];
0708:
0709: // drop on empty handling
0710: if(options.dropOnEmpty || options.tree) {
0711: Droppables.add(element, options_for_tree);
0712: options.droppables.push(element);
0713: }
0714:
0715: (options.elements || this.findElements(element, options) || []).each( function(e,i) {
0716: var handle = options.handles ? $(options.handles[i]) :
0717: (options.handle ? $(e).select('.' + options.handle)[0] : e);
0718: options.draggables.push(
0719: new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
0720: Droppables.add(e, options_for_droppable);
0721: if(options.tree) e.treeNode = element;
0722: options.droppables.push(e);
0723: });
0724:
0725: if(options.tree) {
0726: (Sortable.findTreeElements(element, options) || []).each( function(e) {
0727: Droppables.add(e, options_for_tree);
0728: e.treeNode = element;
0729: options.droppables.push(e);
0730: });
0731: }
0732:
0733: // keep reference
0734: this.sortables[element.id] = options;
0735:
0736: // for onupdate
0737: Draggables.addObserver(new SortableObserver(element, options.onUpdate));
0738:
0739: },
0740:
次に、
656行目で、
658~702行目で、
706行目のoptions.
707行目のoptions.
710行目で、
715~732行目が、
715行目で、
716行目で、
718行目で、
720行目で、
721行目で、
725行目で、
727行目で、
729行目で、
734行目で、
737行目で、
0741: // return all suitable-for-sortable elements in a guaranteed order
0742: findElements: function(element, options) {
0743: return Element.findChildren(
0744: element, options.only, options.tree ? true : false, options.tag);
0745: },
0746:
741~746行目のfindElementsは、
0747: findTreeElements: function(element, options) {
0748: return Element.findChildren(
0749: element, options.only, options.tree ? true : false, options.treeTag);
0750: },
0751:
747~751行目のfindTreeElementsは、
0752: onHover: function(element, dropon, overlap) {
0753: if(Element.isParent(dropon, element)) return;
0754:
0755: if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
0756: return;
0757: } else if(overlap>0.5) {
0758: Sortable.mark(dropon, 'before');
0759: if(dropon.previousSibling != element) {
0760: var oldParentNode = element.parentNode;
0761: element.style.visibility = "hidden"; // fix gecko rendering
0762: dropon.parentNode.insertBefore(element, dropon);
0763: if(dropon.parentNode!=oldParentNode)
0764: Sortable.options(oldParentNode).onChange(element);
0765: Sortable.options(dropon.parentNode).onChange(element);
0766: }
0767: } else {
0768: Sortable.mark(dropon, 'after');
0769: var nextElement = dropon.nextSibling || null;
0770: if(nextElement != element) {
0771: var oldParentNode = element.parentNode;
0772: element.style.visibility = "hidden"; // fix gecko rendering
0773: dropon.parentNode.insertBefore(element, nextElement);
0774: if(dropon.parentNode!=oldParentNode)
0775: Sortable.options(oldParentNode).onChange(element);
0776: Sortable.options(dropon.parentNode).onChange(element);
0777: }
0778: }
0779: },
0780:
752~780行目のonHoverは、
753行目で、
755行目で、
757~766行目が、
758行目で、
以下で、
759行目で、
760行目で、
761行目で、
762行目で、
763行目で、
765行目で、
以降は、
0781: onEmptyHover: function(element, dropon, overlap) {
0782: var oldParentNode = element.parentNode;
0783: var droponOptions = Sortable.options(dropon);
0784:
0785: if(!Element.isParent(dropon, element)) {
0786: var index;
0787:
0788: var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
0789: var child = null;
0790:
0791: if(children) {
0792: var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
0793:
0794: for (index = 0; index < children.length; index += 1) {
0795: if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
0796: offset -= Element.offsetSize (children[index], droponOptions.overlap);
0797: } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
0798: child = index + 1 < children.length ? children[index + 1] : null;
0799: break;
0800: } else {
0801: child = children[index];
0802: break;
0803: }
0804: }
0805: }
0806:
0807: dropon.insertBefore(element, child);
0808:
0809: Sortable.options(oldParentNode).onChange(element);
0810: droponOptions.onChange(element);
0811: }
0812: },
0813:
781~813行目のonEmptyHoverは、
785行目で、
788行目で、
791~806行目で、
807行目で、
809行目で、
810行目で、
0814: unmark: function() {
0815: if(Sortable._marker) Sortable._marker.hide();
0816: },
0817:
814~817行目のunmarkは、
0818: mark: function(dropon, position) {
0819: // mark on ghosting only
0820: var sortable = Sortable.options(dropon.parentNode);
0821: if(sortable && !sortable.ghosting) return;
0822:
0823: if(!Sortable._marker) {
0824: Sortable._marker =
0825: ($('dropmarker') || Element.extend(document.createElement('DIV'))).
0826: hide().addClassName('dropmarker').setStyle({position:'absolute'});
0827: document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
0828: }
0829: var offsets = Position.cumulativeOffset(dropon);
0830: Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
0831:
0832: if(position=='after')
0833: if(sortable.overlap == 'horizontal')
0834: Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
0835: else
0836: Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
0837:
0838: Sortable._marker.show();
0839: },
0840:
818~840行目のmarkは、
821行目で、
823行目で、
827行目で、
829行目で、
830行目で、
832行目で、
838行目で、
0841: _tree: function(element, options, parent) {
0842: var children = Sortable.findElements(element, options) || [];
0843:
0844: for (var i = 0; i < children.length; ++i) {
0845: var match = children[i].id.match(options.format);
0846:
0847: if (!match) continue;
0848:
0849: var child = {
0850: id: encodeURIComponent(match ? match[1] : null),
0851: element: element,
0852: parent: parent,
0853: children: [],
0854: position: parent.children.length,
0855: container: $(children[i]).down(options.treeTag)
0856: }
0857:
0858: /* Get the element containing the children and recurse over it */
0859: if (child.container)
0860: this._tree(child.container, options, child)
0861:
0862: parent.children.push (child);
0863: }
0864:
0865: return parent;
0866: },
0867:
841~867行目の_treeは、
842行目で、
845行目で、
849~856行目で、
850行目のidは、
851行目のelementは、
852行目のparentは、
853行目のchildrenは、
854行目のpositionは、
855行目のcontainerは、
859行目で、
862行目で、
865行目で、
0868: tree: function(element) {
0869: element = $(element);
0870: var sortableOptions = this.options(element);
0871: var options = Object.extend({
0872: tag: sortableOptions.tag,
0873: treeTag: sortableOptions.treeTag,
0874: only: sortableOptions.only,
0875: name: element.id,
0876: format: sortableOptions.format
0877: }, arguments[1] || { });
0878:
0879: var root = {
0880: id: null,
0881: parent: null,
0882: children: [],
0883: container: element,
0884: position: 0
0885: }
0886:
0887: return Sortable._tree(element, options, root);
0888: },
0889:
868~889行目のtreeは、
879行目で、
887行目で、
0890: /* Construct a [i] index for a particular node */
0891: _constructIndex: function(node) {
0892: var index = '';
0893: do {
0894: if (node.id) index = '[' + node.position + ']' + index;
0895: } while ((node = node.parent) != null);
0896: return index;
0897: },
0898:
890~898行目の_constructIndexは、
0899: sequence: function(element) {
0900: element = $(element);
0901: var options = Object.extend(this.options(element), arguments[1] || { });
0902:
0903: return $(this.findElements(element, options) || []).map( function(item) {
0904: return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
0905: });
0906: },
0907:
899~907行目のsequenceは、
903行目で、
904行目で、
0908: setSequence: function(element, new_sequence) {
0909: element = $(element);
0910: var options = Object.extend(this.options(element), arguments[2] || { });
0911:
0912: var nodeMap = { };
0913: this.findElements(element, options).each( function(n) {
0914: if (n.id.match(options.format))
0915: nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
0916: n.parentNode.removeChild(n);
0917: });
0918:
0919: new_sequence.each(function(ident) {
0920: var n = nodeMap[ident];
0921: if (n) {
0922: n[1].appendChild(n[0]);
0923: delete nodeMap[ident];
0924: }
0925: });
0926: },
0927:
908~927行目のsetSequenceは、
913行目で、
914行目で、
915行目で、
916行目で、
919行目で、
920行目で、
922行目で、
923行目で、
0928: serialize: function(element) {
0929: element = $(element);
0930: var options = Object.extend(Sortable.options(element), arguments[1] || { });
0931: var name = encodeURIComponent(
0932: (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
0933:
0934: if (options.tree) {
0935: return Sortable.tree(element, arguments[1]).children.map( function (item) {
0936: return [name + Sortable._constructIndex(item) + "[id]=" +
0937: encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
0938: }).flatten().join('&');
0939: } else {
0940: return Sortable.sequence(element, arguments[1]).map( function(item) {
0941: return name + "[]=" + encodeURIComponent(item);
0942: }).join('&');
0943: }
0944: }
0945:}
0946:
928~946行目のserializeは、
931行目で、
934行目で、
936行目で、
938行目で、
939行目で、
0947:// Returns true if child is contained within element
0948:Element.isParent = function(child, element) {
0949: if (!child.parentNode || child == element) return false;
0950: if (child.parentNode == element) return true;
0951: return Element.isParent(child.parentNode, element);
0952:}
0953:
947~953行目のElement.
951行目で、
0954:Element.findChildren = function(element, only, recursive, tagName) {
0955: if(!element.hasChildNodes()) return null;
0956: tagName = tagName.toUpperCase();
0957: if(only) only = [only].flatten();
0958: var elements = [];
0959: $A(element.childNodes).each( function(e) {
0960: if(e.tagName && e.tagName.toUpperCase()==tagName &&
0961: (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
0962: elements.push(e);
0963: if(recursive) {
0964: var grandchildren = Element.findChildren(e, only, recursive, tagName);
0965: if(grandchildren) elements.push(grandchildren);
0966: }
0967: });
0968:
0969: return (elements.length>0 ? elements.flatten() : []);
0970:}
0971:
954~971行目のElement.
955行目で、
957行目で、
959行目で、
960行目で、
963行目で、
965行目で、
969行目で、
0972:Element.offsetSize = function (element, type) {
0973: return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
0974:}
972~974行目のElement.
973行目で、
dragdrop.
おわりに
これで、
また、
最後に、