dom/tests/mochitest/ajax/mochikit/MochiKit/Sortable.js

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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.Sortable');
    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.Sortable.EXPORT_OK = [
    51 ];
    53 MochiKit.Base.update(MochiKit.Sortable, {
    54     /***
    56     Manage sortables. Mainly use the create function to add a sortable.
    58     ***/
    59     sortables: {},
    61     _findRootElement: function (element) {
    62         while (element.tagName.toUpperCase() != "BODY") {
    63             if (element.id && MochiKit.Sortable.sortables[element.id]) {
    64                 return element;
    65             }
    66             element = element.parentNode;
    67         }
    68     },
    70     /** @id MochiKit.Sortable.options */
    71     options: function (element) {
    72         element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element));
    73         if (!element) {
    74             return;
    75         }
    76         return MochiKit.Sortable.sortables[element.id];
    77     },
    79     /** @id MochiKit.Sortable.destroy */
    80     destroy: function (element){
    81         var s = MochiKit.Sortable.options(element);
    82         var b = MochiKit.Base;
    83         var d = MochiKit.DragAndDrop;
    85         if (s) {
    86             MochiKit.Signal.disconnect(s.startHandle);
    87             MochiKit.Signal.disconnect(s.endHandle);
    88             b.map(function (dr) {
    89                 d.Droppables.remove(dr);
    90             }, s.droppables);
    91             b.map(function (dr) {
    92                 dr.destroy();
    93             }, s.draggables);
    95             delete MochiKit.Sortable.sortables[s.element.id];
    96         }
    97     },
    99     /** @id MochiKit.Sortable.create */
   100     create: function (element, options) {
   101         element = MochiKit.DOM.getElement(element);
   102         var self = MochiKit.Sortable;
   104         /** @id MochiKit.Sortable.options */
   105         options = MochiKit.Base.update({
   107             /** @id MochiKit.Sortable.element */
   108             element: element,
   110             /** @id MochiKit.Sortable.tag */
   111             tag: 'li',  // assumes li children, override with tag: 'tagname'
   113             /** @id MochiKit.Sortable.dropOnEmpty */
   114             dropOnEmpty: false,
   116             /** @id MochiKit.Sortable.tree */
   117             tree: false,
   119             /** @id MochiKit.Sortable.treeTag */
   120             treeTag: 'ul',
   122             /** @id MochiKit.Sortable.overlap */
   123             overlap: 'vertical',  // one of 'vertical', 'horizontal'
   125             /** @id MochiKit.Sortable.constraint */
   126             constraint: 'vertical',  // one of 'vertical', 'horizontal', false
   127             // also takes array of elements (or ids); or false
   129             /** @id MochiKit.Sortable.containment */
   130             containment: [element],
   132             /** @id MochiKit.Sortable.handle */
   133             handle: false,  // or a CSS class
   135             /** @id MochiKit.Sortable.only */
   136             only: false,
   138             /** @id MochiKit.Sortable.hoverclass */
   139             hoverclass: null,
   141             /** @id MochiKit.Sortable.ghosting */
   142             ghosting: false,
   144             /** @id MochiKit.Sortable.scroll */
   145             scroll: false,
   147             /** @id MochiKit.Sortable.scrollSensitivity */
   148             scrollSensitivity: 20,
   150             /** @id MochiKit.Sortable.scrollSpeed */
   151             scrollSpeed: 15,
   153             /** @id MochiKit.Sortable.format */
   154             format: /^[^_]*_(.*)$/,
   156             /** @id MochiKit.Sortable.onChange */
   157             onChange: MochiKit.Base.noop,
   159             /** @id MochiKit.Sortable.onUpdate */
   160             onUpdate: MochiKit.Base.noop,
   162             /** @id MochiKit.Sortable.accept */
   163             accept: null
   164         }, options);
   166         // clear any old sortable with same element
   167         self.destroy(element);
   169         // build options for the draggables
   170         var options_for_draggable = {
   171             revert: true,
   172             ghosting: options.ghosting,
   173             scroll: options.scroll,
   174             scrollSensitivity: options.scrollSensitivity,
   175             scrollSpeed: options.scrollSpeed,
   176             constraint: options.constraint,
   177             handle: options.handle
   178         };
   180         if (options.starteffect) {
   181             options_for_draggable.starteffect = options.starteffect;
   182         }
   184         if (options.reverteffect) {
   185             options_for_draggable.reverteffect = options.reverteffect;
   186         } else if (options.ghosting) {
   187             options_for_draggable.reverteffect = function (innerelement) {
   188                 innerelement.style.top = 0;
   189                 innerelement.style.left = 0;
   190             };
   191         }
   193         if (options.endeffect) {
   194             options_for_draggable.endeffect = options.endeffect;
   195         }
   197         if (options.zindex) {
   198             options_for_draggable.zindex = options.zindex;
   199         }
   201         // build options for the droppables
   202         var options_for_droppable = {
   203             overlap: options.overlap,
   204             containment: options.containment,
   205             hoverclass: options.hoverclass,
   206             onhover: self.onHover,
   207             tree: options.tree,
   208             accept: options.accept
   209         }
   211         var options_for_tree = {
   212             onhover: self.onEmptyHover,
   213             overlap: options.overlap,
   214             containment: options.containment,
   215             hoverclass: options.hoverclass,
   216             accept: options.accept
   217         }
   219         // fix for gecko engine
   220         MochiKit.DOM.removeEmptyTextNodes(element);
   222         options.draggables = [];
   223         options.droppables = [];
   225         // drop on empty handling
   226         if (options.dropOnEmpty || options.tree) {
   227             new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
   228             options.droppables.push(element);
   229         }
   230         MochiKit.Base.map(function (e) {
   231             // handles are per-draggable
   232             var handle = options.handle ?
   233                 MochiKit.DOM.getFirstElementByTagAndClassName(null,
   234                     options.handle, e) : e;
   235             options.draggables.push(
   236                 new MochiKit.DragAndDrop.Draggable(e,
   237                     MochiKit.Base.update(options_for_draggable,
   238                                          {handle: handle})));
   239             new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
   240             if (options.tree) {
   241                 e.treeNode = element;
   242             }
   243             options.droppables.push(e);
   244         }, (self.findElements(element, options) || []));
   246         if (options.tree) {
   247             MochiKit.Base.map(function (e) {
   248                 new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
   249                 e.treeNode = element;
   250                 options.droppables.push(e);
   251             }, (self.findTreeElements(element, options) || []));
   252         }
   254         // keep reference
   255         self.sortables[element.id] = options;
   257         options.lastValue = self.serialize(element);
   258         options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start',
   259                                 MochiKit.Base.partial(self.onStart, element));
   260         options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end',
   261                                 MochiKit.Base.partial(self.onEnd, element));
   262     },
   264     /** @id MochiKit.Sortable.onStart */
   265     onStart: function (element, draggable) {
   266         var self = MochiKit.Sortable;
   267         var options = self.options(element);
   268         options.lastValue = self.serialize(options.element);
   269     },
   271     /** @id MochiKit.Sortable.onEnd */
   272     onEnd: function (element, draggable) {
   273         var self = MochiKit.Sortable;
   274         self.unmark();
   275         var options = self.options(element);
   276         if (options.lastValue != self.serialize(options.element)) {
   277             options.onUpdate(options.element);
   278         }
   279     },
   281     // return all suitable-for-sortable elements in a guaranteed order
   283     /** @id MochiKit.Sortable.findElements */
   284     findElements: function (element, options) {
   285         return MochiKit.Sortable.findChildren(
   286             element, options.only, options.tree ? true : false, options.tag);
   287     },
   289     /** @id MochiKit.Sortable.findTreeElements */
   290     findTreeElements: function (element, options) {
   291         return MochiKit.Sortable.findChildren(
   292             element, options.only, options.tree ? true : false, options.treeTag);
   293     },
   295     /** @id MochiKit.Sortable.findChildren */
   296     findChildren: function (element, only, recursive, tagName) {
   297         if (!element.hasChildNodes()) {
   298             return null;
   299         }
   300         tagName = tagName.toUpperCase();
   301         if (only) {
   302             only = MochiKit.Base.flattenArray([only]);
   303         }
   304         var elements = [];
   305         MochiKit.Base.map(function (e) {
   306             if (e.tagName &&
   307                 e.tagName.toUpperCase() == tagName &&
   308                (!only ||
   309                 MochiKit.Iter.some(only, function (c) {
   310                     return MochiKit.DOM.hasElementClass(e, c);
   311                 }))) {
   312                 elements.push(e);
   313             }
   314             if (recursive) {
   315                 var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName);
   316                 if (grandchildren && grandchildren.length > 0) {
   317                     elements = elements.concat(grandchildren);
   318                 }
   319             }
   320         }, element.childNodes);
   321         return elements;
   322     },
   324     /** @id MochiKit.Sortable.onHover */
   325     onHover: function (element, dropon, overlap) {
   326         if (MochiKit.DOM.isParent(dropon, element)) {
   327             return;
   328         }
   329         var self = MochiKit.Sortable;
   331         if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
   332             return;
   333         } else if (overlap > 0.5) {
   334             self.mark(dropon, 'before');
   335             if (dropon.previousSibling != element) {
   336                 var oldParentNode = element.parentNode;
   337                 element.style.visibility = 'hidden';  // fix gecko rendering
   338                 dropon.parentNode.insertBefore(element, dropon);
   339                 if (dropon.parentNode != oldParentNode) {
   340                     self.options(oldParentNode).onChange(element);
   341                 }
   342                 self.options(dropon.parentNode).onChange(element);
   343             }
   344         } else {
   345             self.mark(dropon, 'after');
   346             var nextElement = dropon.nextSibling || null;
   347             if (nextElement != element) {
   348                 var oldParentNode = element.parentNode;
   349                 element.style.visibility = 'hidden';  // fix gecko rendering
   350                 dropon.parentNode.insertBefore(element, nextElement);
   351                 if (dropon.parentNode != oldParentNode) {
   352                     self.options(oldParentNode).onChange(element);
   353                 }
   354                 self.options(dropon.parentNode).onChange(element);
   355             }
   356         }
   357     },
   359     _offsetSize: function (element, type) {
   360         if (type == 'vertical' || type == 'height') {
   361             return element.offsetHeight;
   362         } else {
   363             return element.offsetWidth;
   364         }
   365     },
   367     /** @id MochiKit.Sortable.onEmptyHover */
   368     onEmptyHover: function (element, dropon, overlap) {
   369         var oldParentNode = element.parentNode;
   370         var self = MochiKit.Sortable;
   371         var droponOptions = self.options(dropon);
   373         if (!MochiKit.DOM.isParent(dropon, element)) {
   374             var index;
   376             var children = self.findElements(dropon, {tag: droponOptions.tag,
   377                                                       only: droponOptions.only});
   378             var child = null;
   380             if (children) {
   381                 var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
   383                 for (index = 0; index < children.length; index += 1) {
   384                     if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
   385                         offset -= self._offsetSize(children[index], droponOptions.overlap);
   386                     } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
   387                         child = index + 1 < children.length ? children[index + 1] : null;
   388                         break;
   389                     } else {
   390                         child = children[index];
   391                         break;
   392                     }
   393                 }
   394             }
   396             dropon.insertBefore(element, child);
   398             self.options(oldParentNode).onChange(element);
   399             droponOptions.onChange(element);
   400         }
   401     },
   403     /** @id MochiKit.Sortable.unmark */
   404     unmark: function () {
   405         var m = MochiKit.Sortable._marker;
   406         if (m) {
   407             MochiKit.Style.hideElement(m);
   408         }
   409     },
   411     /** @id MochiKit.Sortable.mark */
   412     mark: function (dropon, position) {
   413         // mark on ghosting only
   414         var d = MochiKit.DOM;
   415         var self = MochiKit.Sortable;
   416         var sortable = self.options(dropon.parentNode);
   417         if (sortable && !sortable.ghosting) {
   418             return;
   419         }
   421         if (!self._marker) {
   422             self._marker = d.getElement('dropmarker') ||
   423                         document.createElement('DIV');
   424             MochiKit.Style.hideElement(self._marker);
   425             d.addElementClass(self._marker, 'dropmarker');
   426             self._marker.style.position = 'absolute';
   427             document.getElementsByTagName('body').item(0).appendChild(self._marker);
   428         }
   429         var offsets = MochiKit.Position.cumulativeOffset(dropon);
   430         self._marker.style.left = offsets.x + 'px';
   431         self._marker.style.top = offsets.y + 'px';
   433         if (position == 'after') {
   434             if (sortable.overlap == 'horizontal') {
   435                 self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
   436             } else {
   437                 self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
   438             }
   439         }
   440         MochiKit.Style.showElement(self._marker);
   441     },
   443     _tree: function (element, options, parent) {
   444         var self = MochiKit.Sortable;
   445         var children = self.findElements(element, options) || [];
   447         for (var i = 0; i < children.length; ++i) {
   448             var match = children[i].id.match(options.format);
   450             if (!match) {
   451                 continue;
   452             }
   454             var child = {
   455                 id: encodeURIComponent(match ? match[1] : null),
   456                 element: element,
   457                 parent: parent,
   458                 children: [],
   459                 position: parent.children.length,
   460                 container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
   461             }
   463             /* Get the element containing the children and recurse over it */
   464             if (child.container) {
   465                 self._tree(child.container, options, child)
   466             }
   468             parent.children.push (child);
   469         }
   471         return parent;
   472     },
   474     /* Finds the first element of the given tag type within a parent element.
   475        Used for finding the first LI[ST] within a L[IST]I[TEM].*/
   476     _findChildrenElement: function (element, containerTag) {
   477         if (element && element.hasChildNodes) {
   478             containerTag = containerTag.toUpperCase();
   479             for (var i = 0; i < element.childNodes.length; ++i) {
   480                 if (element.childNodes[i].tagName.toUpperCase() == containerTag) {
   481                     return element.childNodes[i];
   482                 }
   483             }
   484         }
   485         return null;
   486     },
   488     /** @id MochiKit.Sortable.tree */
   489     tree: function (element, options) {
   490         element = MochiKit.DOM.getElement(element);
   491         var sortableOptions = MochiKit.Sortable.options(element);
   492         options = MochiKit.Base.update({
   493             tag: sortableOptions.tag,
   494             treeTag: sortableOptions.treeTag,
   495             only: sortableOptions.only,
   496             name: element.id,
   497             format: sortableOptions.format
   498         }, options || {});
   500         var root = {
   501             id: null,
   502             parent: null,
   503             children: new Array,
   504             container: element,
   505             position: 0
   506         }
   508         return MochiKit.Sortable._tree(element, options, root);
   509     },
   511     /**
   512      * Specifies the sequence for the Sortable.
   513      * @param {Node} element    Element to use as the Sortable.
   514      * @param {Object} newSequence    New sequence to use.
   515      * @param {Object} options    Options to use fro the Sortable.
   516      */
   517     setSequence: function (element, newSequence, options) {
   518         var self = MochiKit.Sortable;
   519         var b = MochiKit.Base;
   520         element = MochiKit.DOM.getElement(element);
   521         options = b.update(self.options(element), options || {});
   523         var nodeMap = {};
   524         b.map(function (n) {
   525             var m = n.id.match(options.format);
   526             if (m) {
   527                 nodeMap[m[1]] = [n, n.parentNode];
   528             }
   529             n.parentNode.removeChild(n);
   530         }, self.findElements(element, options));
   532         b.map(function (ident) {
   533             var n = nodeMap[ident];
   534             if (n) {
   535                 n[1].appendChild(n[0]);
   536                 delete nodeMap[ident];
   537             }
   538         }, newSequence);
   539     },
   541     /* Construct a [i] index for a particular node */
   542     _constructIndex: function (node) {
   543         var index = '';
   544         do {
   545             if (node.id) {
   546                 index = '[' + node.position + ']' + index;
   547             }
   548         } while ((node = node.parent) != null);
   549         return index;
   550     },
   552     /** @id MochiKit.Sortable.sequence */
   553     sequence: function (element, options) {
   554         element = MochiKit.DOM.getElement(element);
   555         var self = MochiKit.Sortable;
   556         var options = MochiKit.Base.update(self.options(element), options || {});
   558         return MochiKit.Base.map(function (item) {
   559             return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
   560         }, MochiKit.DOM.getElement(self.findElements(element, options) || []));
   561     },
   563     /**
   564      * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest.
   565      * These options override the Sortable options for the serialization only.
   566      * @param {Node} element    Element to serialize.
   567      * @param {Object} options    Serialization options.
   568      */
   569     serialize: function (element, options) {
   570         element = MochiKit.DOM.getElement(element);
   571         var self = MochiKit.Sortable;
   572         options = MochiKit.Base.update(self.options(element), options || {});
   573         var name = encodeURIComponent(options.name || element.id);
   575         if (options.tree) {
   576             return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
   577                 return [name + self._constructIndex(item) + "[id]=" +
   578                 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
   579             }, self.tree(element, options).children)).join('&');
   580         } else {
   581             return MochiKit.Base.map(function (item) {
   582                 return name + "[]=" + encodeURIComponent(item);
   583             }, self.sequence(element, options)).join('&');
   584         }
   585     }
   586 });
   588 // trunk compatibility
   589 MochiKit.Sortable.Sortable = MochiKit.Sortable;

mercurial