testing/mochitest/MochiKit/Sortable.js

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /***
     2 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
     3     Mochi-ized By Thomas Herve (_firstname_@nimail.org)
     5 See scriptaculous.js for full license.
     7 ***/
     9 if (typeof(dojo) != 'undefined') {
    10     dojo.provide('MochiKit.DragAndDrop');
    11     dojo.require('MochiKit.Base');
    12     dojo.require('MochiKit.DOM');
    13     dojo.require('MochiKit.Iter');
    14 }
    16 if (typeof(JSAN) != 'undefined') {
    17     JSAN.use("MochiKit.Base", []);
    18     JSAN.use("MochiKit.DOM", []);
    19     JSAN.use("MochiKit.Iter", []);
    20 }
    22 try {
    23     if (typeof(MochiKit.Base) == 'undefined' ||
    24         typeof(MochiKit.DOM) == 'undefined' ||
    25         typeof(MochiKit.Iter) == 'undefined') {
    26         throw "";
    27     }
    28 } catch (e) {
    29     throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!";
    30 }
    32 if (typeof(MochiKit.Sortable) == 'undefined') {
    33     MochiKit.Sortable = {};
    34 }
    36 MochiKit.Sortable.NAME = 'MochiKit.Sortable';
    37 MochiKit.Sortable.VERSION = '1.4';
    39 MochiKit.Sortable.__repr__ = function () {
    40     return '[' + this.NAME + ' ' + this.VERSION + ']';
    41 };
    43 MochiKit.Sortable.toString = function () {
    44     return this.__repr__();
    45 };
    47 MochiKit.Sortable.EXPORT = [
    48 ];
    50 MochiKit.DragAndDrop.EXPORT_OK = [
    51     "Sortable"
    52 ];
    54 MochiKit.Sortable.Sortable = {
    55     /***
    57     Manage sortables. Mainly use the create function to add a sortable.
    59     ***/
    60     sortables: {},
    62     _findRootElement: function (element) {
    63         while (element.tagName.toUpperCase() != "BODY") {
    64             if (element.id && MochiKit.Sortable.Sortable.sortables[element.id]) {
    65                 return element;
    66             }
    67             element = element.parentNode;
    68         }
    69     },
    71     /** @id MochiKit.Sortable.Sortable.options */
    72     options: function (element) {
    73         element = MochiKit.Sortable.Sortable._findRootElement(MochiKit.DOM.getElement(element));
    74         if (!element) {
    75             return;
    76         }
    77         return MochiKit.Sortable.Sortable.sortables[element.id];
    78     },
    80     /** @id MochiKit.Sortable.Sortable.destroy */
    81     destroy: function (element){
    82         var s = MochiKit.Sortable.Sortable.options(element);
    83         var b = MochiKit.Base;
    84         var d = MochiKit.DragAndDrop;
    86         if (s) {
    87             MochiKit.Signal.disconnect(s.startHandle);
    88             MochiKit.Signal.disconnect(s.endHandle);
    89             b.map(function (dr) {
    90                 d.Droppables.remove(dr);
    91             }, s.droppables);
    92             b.map(function (dr) {
    93                 dr.destroy();
    94             }, s.draggables);
    96             delete MochiKit.Sortable.Sortable.sortables[s.element.id];
    97         }
    98     },
   100     /** @id MochiKit.Sortable.Sortable.create */
   101     create: function (element, options) {
   102         element = MochiKit.DOM.getElement(element);
   103         var self = MochiKit.Sortable.Sortable;
   105         /** @id MochiKit.Sortable.Sortable.options */
   106         options = MochiKit.Base.update({
   108             /** @id MochiKit.Sortable.Sortable.element */
   109             element: element,
   111             /** @id MochiKit.Sortable.Sortable.tag */
   112             tag: 'li',  // assumes li children, override with tag: 'tagname'
   114             /** @id MochiKit.Sortable.Sortable.dropOnEmpty */
   115             dropOnEmpty: false,
   117             /** @id MochiKit.Sortable.Sortable.tree */
   118             tree: false,
   120             /** @id MochiKit.Sortable.Sortable.treeTag */
   121             treeTag: 'ul',
   123             /** @id MochiKit.Sortable.Sortable.overlap */
   124             overlap: 'vertical',  // one of 'vertical', 'horizontal'
   126             /** @id MochiKit.Sortable.Sortable.constraint */
   127             constraint: 'vertical',  // one of 'vertical', 'horizontal', false
   128             // also takes array of elements (or ids); or false
   130             /** @id MochiKit.Sortable.Sortable.containment */
   131             containment: [element],
   133             /** @id MochiKit.Sortable.Sortable.handle */
   134             handle: false,  // or a CSS class
   136             /** @id MochiKit.Sortable.Sortable.only */
   137             only: false,
   139             /** @id MochiKit.Sortable.Sortable.hoverclass */
   140             hoverclass: null,
   142             /** @id MochiKit.Sortable.Sortable.ghosting */
   143             ghosting: false,
   145             /** @id MochiKit.Sortable.Sortable.scroll */
   146             scroll: false,
   148             /** @id MochiKit.Sortable.Sortable.scrollSensitivity */
   149             scrollSensitivity: 20,
   151             /** @id MochiKit.Sortable.Sortable.scrollSpeed */
   152             scrollSpeed: 15,
   154             /** @id MochiKit.Sortable.Sortable.format */
   155             format: /^[^_]*_(.*)$/,
   157             /** @id MochiKit.Sortable.Sortable.onChange */
   158             onChange: MochiKit.Base.noop,
   160             /** @id MochiKit.Sortable.Sortable.onUpdate */
   161             onUpdate: MochiKit.Base.noop,
   163             /** @id MochiKit.Sortable.Sortable.accept */
   164             accept: null
   165         }, options);
   167         // clear any old sortable with same element
   168         self.destroy(element);
   170         // build options for the draggables
   171         var options_for_draggable = {
   172             revert: true,
   173             ghosting: options.ghosting,
   174             scroll: options.scroll,
   175             scrollSensitivity: options.scrollSensitivity,
   176             scrollSpeed: options.scrollSpeed,
   177             constraint: options.constraint,
   178             handle: options.handle
   179         };
   181         if (options.starteffect) {
   182             options_for_draggable.starteffect = options.starteffect;
   183         }
   185         if (options.reverteffect) {
   186             options_for_draggable.reverteffect = options.reverteffect;
   187         } else if (options.ghosting) {
   188             options_for_draggable.reverteffect = function (innerelement) {
   189                 innerelement.style.top = 0;
   190                 innerelement.style.left = 0;
   191             };
   192         }
   194         if (options.endeffect) {
   195             options_for_draggable.endeffect = options.endeffect;
   196         }
   198         if (options.zindex) {
   199             options_for_draggable.zindex = options.zindex;
   200         }
   202         // build options for the droppables
   203         var options_for_droppable = {
   204             overlap: options.overlap,
   205             containment: options.containment,
   206             hoverclass: options.hoverclass,
   207             onhover: self.onHover,
   208             tree: options.tree,
   209             accept: options.accept
   210         }
   212         var options_for_tree = {
   213             onhover: self.onEmptyHover,
   214             overlap: options.overlap,
   215             containment: options.containment,
   216             hoverclass: options.hoverclass,
   217             accept: options.accept
   218         }
   220         // fix for gecko engine
   221         MochiKit.DOM.removeEmptyTextNodes(element);
   223         options.draggables = [];
   224         options.droppables = [];
   226         // drop on empty handling
   227         if (options.dropOnEmpty || options.tree) {
   228             new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
   229             options.droppables.push(element);
   230         }
   231         MochiKit.Base.map(function (e) {
   232             // handles are per-draggable
   233             var handle = options.handle ?
   234                 MochiKit.DOM.getFirstElementByTagAndClassName(null,
   235                     options.handle, e) : e;
   236             options.draggables.push(
   237                 new MochiKit.DragAndDrop.Draggable(e,
   238                     MochiKit.Base.update(options_for_draggable,
   239                                          {handle: handle})));
   240             new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
   241             if (options.tree) {
   242                 e.treeNode = element;
   243             }
   244             options.droppables.push(e);
   245         }, (self.findElements(element, options) || []));
   247         if (options.tree) {
   248             MochiKit.Base.map(function (e) {
   249                 new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
   250                 e.treeNode = element;
   251                 options.droppables.push(e);
   252             }, (self.findTreeElements(element, options) || []));
   253         }
   255         // keep reference
   256         self.sortables[element.id] = options;
   258         options.lastValue = self.serialize(element);
   259         options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start',
   260                                 MochiKit.Base.partial(self.onStart, element));
   261         options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end',
   262                                 MochiKit.Base.partial(self.onEnd, element));
   263     },
   265     /** @id MochiKit.Sortable.Sortable.onStart */
   266     onStart: function (element, draggable) {
   267         var self = MochiKit.Sortable.Sortable;
   268         var options = self.options(element);
   269         options.lastValue = self.serialize(options.element);
   270     },
   272     /** @id MochiKit.Sortable.Sortable.onEnd */
   273     onEnd: function (element, draggable) {
   274         var self = MochiKit.Sortable.Sortable;
   275         self.unmark();
   276         var options = self.options(element);
   277         if (options.lastValue != self.serialize(options.element)) {
   278             options.onUpdate(options.element);
   279         }
   280     },
   282     // return all suitable-for-sortable elements in a guaranteed order
   284     /** @id MochiKit.Sortable.Sortable.findElements */
   285     findElements: function (element, options) {
   286         return MochiKit.Sortable.Sortable.findChildren(
   287             element, options.only, options.tree ? true : false, options.tag);
   288     },
   290     /** @id MochiKit.Sortable.Sortable.findTreeElements */
   291     findTreeElements: function (element, options) {
   292         return MochiKit.Sortable.Sortable.findChildren(
   293             element, options.only, options.tree ? true : false, options.treeTag);
   294     },
   296     /** @id MochiKit.Sortable.Sortable.findChildren */
   297     findChildren: function (element, only, recursive, tagName) {
   298         if (!element.hasChildNodes()) {
   299             return null;
   300         }
   301         tagName = tagName.toUpperCase();
   302         if (only) {
   303             only = MochiKit.Base.flattenArray([only]);
   304         }
   305         var elements = [];
   306         MochiKit.Base.map(function (e) {
   307             if (e.tagName &&
   308                 e.tagName.toUpperCase() == tagName &&
   309                (!only ||
   310                 MochiKit.Iter.some(only, function (c) {
   311                     return MochiKit.DOM.hasElementClass(e, c);
   312                 }))) {
   313                 elements.push(e);
   314             }
   315             if (recursive) {
   316                 var grandchildren = MochiKit.Sortable.Sortable.findChildren(e, only, recursive, tagName);
   317                 if (grandchildren && grandchildren.length > 0) {
   318                     elements = elements.concat(grandchildren);
   319                 }
   320             }
   321         }, element.childNodes);
   322         return elements;
   323     },
   325     /** @id MochiKit.Sortable.Sortable.onHover */
   326     onHover: function (element, dropon, overlap) {
   327         if (MochiKit.DOM.isParent(dropon, element)) {
   328             return;
   329         }
   330         var self = MochiKit.Sortable.Sortable;
   332         if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
   333             return;
   334         } else if (overlap > 0.5) {
   335             self.mark(dropon, 'before');
   336             if (dropon.previousSibling != element) {
   337                 var oldParentNode = element.parentNode;
   338                 element.style.visibility = 'hidden';  // fix gecko rendering
   339                 dropon.parentNode.insertBefore(element, dropon);
   340                 if (dropon.parentNode != oldParentNode) {
   341                     self.options(oldParentNode).onChange(element);
   342                 }
   343                 self.options(dropon.parentNode).onChange(element);
   344             }
   345         } else {
   346             self.mark(dropon, 'after');
   347             var nextElement = dropon.nextSibling || null;
   348             if (nextElement != element) {
   349                 var oldParentNode = element.parentNode;
   350                 element.style.visibility = 'hidden';  // fix gecko rendering
   351                 dropon.parentNode.insertBefore(element, nextElement);
   352                 if (dropon.parentNode != oldParentNode) {
   353                     self.options(oldParentNode).onChange(element);
   354                 }
   355                 self.options(dropon.parentNode).onChange(element);
   356             }
   357         }
   358     },
   360     _offsetSize: function (element, type) {
   361         if (type == 'vertical' || type == 'height') {
   362             return element.offsetHeight;
   363         } else {
   364             return element.offsetWidth;
   365         }
   366     },
   368     /** @id MochiKit.Sortable.Sortable.onEmptyHover */
   369     onEmptyHover: function (element, dropon, overlap) {
   370         var oldParentNode = element.parentNode;
   371         var self = MochiKit.Sortable.Sortable;
   372         var droponOptions = self.options(dropon);
   374         if (!MochiKit.DOM.isParent(dropon, element)) {
   375             var index;
   377             var children = self.findElements(dropon, {tag: droponOptions.tag,
   378                                                       only: droponOptions.only});
   379             var child = null;
   381             if (children) {
   382                 var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
   384                 for (index = 0; index < children.length; index += 1) {
   385                     if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
   386                         offset -= self._offsetSize(children[index], droponOptions.overlap);
   387                     } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
   388                         child = index + 1 < children.length ? children[index + 1] : null;
   389                         break;
   390                     } else {
   391                         child = children[index];
   392                         break;
   393                     }
   394                 }
   395             }
   397             dropon.insertBefore(element, child);
   399             self.options(oldParentNode).onChange(element);
   400             droponOptions.onChange(element);
   401         }
   402     },
   404     /** @id MochiKit.Sortable.Sortable.unmark */
   405     unmark: function () {
   406         var m = MochiKit.Sortable.Sortable._marker;
   407         if (m) {
   408             MochiKit.Style.hideElement(m);
   409         }
   410     },
   412     /** @id MochiKit.Sortable.Sortable.mark */
   413     mark: function (dropon, position) {
   414         // mark on ghosting only
   415         var d = MochiKit.DOM;
   416         var self = MochiKit.Sortable.Sortable;
   417         var sortable = self.options(dropon.parentNode);
   418         if (sortable && !sortable.ghosting) {
   419             return;
   420         }
   422         if (!self._marker) {
   423             self._marker = d.getElement('dropmarker') ||
   424                         document.createElement('DIV');
   425             MochiKit.Style.hideElement(self._marker);
   426             d.addElementClass(self._marker, 'dropmarker');
   427             self._marker.style.position = 'absolute';
   428             document.getElementsByTagName('body').item(0).appendChild(self._marker);
   429         }
   430         var offsets = MochiKit.Position.cumulativeOffset(dropon);
   431         self._marker.style.left = offsets.x + 'px';
   432         self._marker.style.top = offsets.y + 'px';
   434         if (position == 'after') {
   435             if (sortable.overlap == 'horizontal') {
   436                 self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
   437             } else {
   438                 self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
   439             }
   440         }
   441         MochiKit.Style.showElement(self._marker);
   442     },
   444     _tree: function (element, options, parent) {
   445         var self = MochiKit.Sortable.Sortable;
   446         var children = self.findElements(element, options) || [];
   448         for (var i = 0; i < children.length; ++i) {
   449             var match = children[i].id.match(options.format);
   451             if (!match) {
   452                 continue;
   453             }
   455             var child = {
   456                 id: encodeURIComponent(match ? match[1] : null),
   457                 element: element,
   458                 parent: parent,
   459                 children: [],
   460                 position: parent.children.length,
   461                 container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
   462             }
   464             /* Get the element containing the children and recurse over it */
   465             if (child.container) {
   466                 self._tree(child.container, options, child)
   467             }
   469             parent.children.push (child);
   470         }
   472         return parent;
   473     },
   475     /* Finds the first element of the given tag type within a parent element.
   476        Used for finding the first LI[ST] within a L[IST]I[TEM].*/
   477     _findChildrenElement: function (element, containerTag) {
   478         if (element && element.hasChildNodes) {
   479             containerTag = containerTag.toUpperCase();
   480             for (var i = 0; i < element.childNodes.length; ++i) {
   481                 if (element.childNodes[i].tagName.toUpperCase() == containerTag) {
   482                     return element.childNodes[i];
   483                 }
   484             }
   485         }
   486         return null;
   487     },
   489     /** @id MochiKit.Sortable.Sortable.tree */
   490     tree: function (element, options) {
   491         element = MochiKit.DOM.getElement(element);
   492         var sortableOptions = MochiKit.Sortable.Sortable.options(element);
   493         options = MochiKit.Base.update({
   494             tag: sortableOptions.tag,
   495             treeTag: sortableOptions.treeTag,
   496             only: sortableOptions.only,
   497             name: element.id,
   498             format: sortableOptions.format
   499         }, options || {});
   501         var root = {
   502             id: null,
   503             parent: null,
   504             children: new Array,
   505             container: element,
   506             position: 0
   507         }
   509         return MochiKit.Sortable.Sortable._tree(element, options, root);
   510     },
   512     /**
   513      * Specifies the sequence for the Sortable.
   514      * @param {Node} element    Element to use as the Sortable.
   515      * @param {Object} newSequence    New sequence to use.
   516      * @param {Object} options    Options to use fro the Sortable.
   517      */
   518     setSequence: function (element, newSequence, options) {
   519         var self = MochiKit.Sortable.Sortable;
   520         var b = MochiKit.Base;
   521         element = MochiKit.DOM.getElement(element);
   522         options = b.update(self.options(element), options || {});
   524         var nodeMap = {};
   525         b.map(function (n) {
   526             var m = n.id.match(options.format);
   527             if (m) {
   528                 nodeMap[m[1]] = [n, n.parentNode];
   529             }
   530             n.parentNode.removeChild(n);
   531         }, self.findElements(element, options));
   533         b.map(function (ident) {
   534             var n = nodeMap[ident];
   535             if (n) {
   536                 n[1].appendChild(n[0]);
   537                 delete nodeMap[ident];
   538             }
   539         }, newSequence);
   540     },
   542     /* Construct a [i] index for a particular node */
   543     _constructIndex: function (node) {
   544         var index = '';
   545         do {
   546             if (node.id) {
   547                 index = '[' + node.position + ']' + index;
   548             }
   549         } while ((node = node.parent) != null);
   550         return index;
   551     },
   553     /** @id MochiKit.Sortable.Sortable.sequence */
   554     sequence: function (element, options) {
   555         element = MochiKit.DOM.getElement(element);
   556         var self = MochiKit.Sortable.Sortable;
   557         var options = MochiKit.Base.update(self.options(element), options || {});
   559         return MochiKit.Base.map(function (item) {
   560             return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
   561         }, MochiKit.DOM.getElement(self.findElements(element, options) || []));
   562     },
   564     /**
   565      * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest. 
   566      * These options override the Sortable options for the serialization only.
   567      * @param {Node} element    Element to serialize.
   568      * @param {Object} options    Serialization options.
   569      */
   570     serialize: function (element, options) {
   571         element = MochiKit.DOM.getElement(element);
   572         var self = MochiKit.Sortable.Sortable;
   573         options = MochiKit.Base.update(self.options(element), options || {});
   574         var name = encodeURIComponent(options.name || element.id);
   576         if (options.tree) {
   577             return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
   578                 return [name + self._constructIndex(item) + "[id]=" +
   579                 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
   580             }, self.tree(element, options).children)).join('&');
   581         } else {
   582             return MochiKit.Base.map(function (item) {
   583                 return name + "[]=" + encodeURIComponent(item);
   584             }, self.sequence(element, options)).join('&');
   585         }
   586     }
   587 };

mercurial