tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Sortable.js (20054B)


      1 /***
      2 Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
      3    Mochi-ized By Thomas Herve (_firstname_@nimail.org)
      4 
      5 See scriptaculous.js for full license.
      6 
      7 ***/
      8 
      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 }
     15 
     16 if (typeof(JSAN) != 'undefined') {
     17    JSAN.use("MochiKit.Base", []);
     18    JSAN.use("MochiKit.DOM", []);
     19    JSAN.use("MochiKit.Iter", []);
     20 }
     21 
     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 }
     31 
     32 if (typeof(MochiKit.Sortable) == 'undefined') {
     33    MochiKit.Sortable = {};
     34 }
     35 
     36 MochiKit.Sortable.NAME = 'MochiKit.Sortable';
     37 MochiKit.Sortable.VERSION = '1.4';
     38 
     39 MochiKit.Sortable.__repr__ = function () {
     40    return '[' + this.NAME + ' ' + this.VERSION + ']';
     41 };
     42 
     43 MochiKit.Sortable.toString = function () {
     44    return this.__repr__();
     45 };
     46 
     47 MochiKit.Sortable.EXPORT = [
     48 ];
     49 
     50 MochiKit.Sortable.EXPORT_OK = [
     51 ];
     52 
     53 MochiKit.Base.update(MochiKit.Sortable, {
     54    /***
     55 
     56    Manage sortables. Mainly use the create function to add a sortable.
     57 
     58    ***/
     59    sortables: {},
     60 
     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    },
     69 
     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    },
     78 
     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;
     84 
     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);
     94 
     95            delete MochiKit.Sortable.sortables[s.element.id];
     96        }
     97    },
     98 
     99    /** @id MochiKit.Sortable.create */
    100    create: function (element, options) {
    101        element = MochiKit.DOM.getElement(element);
    102        var self = MochiKit.Sortable;
    103 
    104        /** @id MochiKit.Sortable.options */
    105        options = MochiKit.Base.update({
    106 
    107            /** @id MochiKit.Sortable.element */
    108            element: element,
    109 
    110            /** @id MochiKit.Sortable.tag */
    111            tag: 'li',  // assumes li children, override with tag: 'tagname'
    112 
    113            /** @id MochiKit.Sortable.dropOnEmpty */
    114            dropOnEmpty: false,
    115 
    116            /** @id MochiKit.Sortable.tree */
    117            tree: false,
    118 
    119            /** @id MochiKit.Sortable.treeTag */
    120            treeTag: 'ul',
    121 
    122            /** @id MochiKit.Sortable.overlap */
    123            overlap: 'vertical',  // one of 'vertical', 'horizontal'
    124 
    125            /** @id MochiKit.Sortable.constraint */
    126            constraint: 'vertical',  // one of 'vertical', 'horizontal', false
    127            // also takes array of elements (or ids); or false
    128 
    129            /** @id MochiKit.Sortable.containment */
    130            containment: [element],
    131 
    132            /** @id MochiKit.Sortable.handle */
    133            handle: false,  // or a CSS class
    134 
    135            /** @id MochiKit.Sortable.only */
    136            only: false,
    137 
    138            /** @id MochiKit.Sortable.hoverclass */
    139            hoverclass: null,
    140 
    141            /** @id MochiKit.Sortable.ghosting */
    142            ghosting: false,
    143 
    144            /** @id MochiKit.Sortable.scroll */
    145            scroll: false,
    146 
    147            /** @id MochiKit.Sortable.scrollSensitivity */
    148            scrollSensitivity: 20,
    149 
    150            /** @id MochiKit.Sortable.scrollSpeed */
    151            scrollSpeed: 15,
    152 
    153            /** @id MochiKit.Sortable.format */
    154            format: /^[^_]*_(.*)$/,
    155 
    156            /** @id MochiKit.Sortable.onChange */
    157            onChange: MochiKit.Base.noop,
    158 
    159            /** @id MochiKit.Sortable.onUpdate */
    160            onUpdate: MochiKit.Base.noop,
    161 
    162            /** @id MochiKit.Sortable.accept */
    163            accept: null
    164        }, options);
    165 
    166        // clear any old sortable with same element
    167        self.destroy(element);
    168 
    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        };
    179 
    180        if (options.starteffect) {
    181            options_for_draggable.starteffect = options.starteffect;
    182        }
    183 
    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        }
    192 
    193        if (options.endeffect) {
    194            options_for_draggable.endeffect = options.endeffect;
    195        }
    196 
    197        if (options.zindex) {
    198            options_for_draggable.zindex = options.zindex;
    199        }
    200 
    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        }
    210 
    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        }
    218 
    219        // fix for gecko engine
    220        MochiKit.DOM.removeEmptyTextNodes(element);
    221 
    222        options.draggables = [];
    223        options.droppables = [];
    224 
    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) || []));
    245 
    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        }
    253 
    254        // keep reference
    255        self.sortables[element.id] = options;
    256 
    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    },
    263 
    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    },
    270 
    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    },
    280 
    281    // return all suitable-for-sortable elements in a guaranteed order
    282 
    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    },
    288 
    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    },
    294 
    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    },
    323 
    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;
    330 
    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    },
    358 
    359    _offsetSize: function (element, type) {
    360        if (type == 'vertical' || type == 'height') {
    361            return element.offsetHeight;
    362        } else {
    363            return element.offsetWidth;
    364        }
    365    },
    366 
    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);
    372 
    373        if (!MochiKit.DOM.isParent(dropon, element)) {
    374            var index;
    375 
    376            var children = self.findElements(dropon, {tag: droponOptions.tag,
    377                                                      only: droponOptions.only});
    378            var child = null;
    379 
    380            if (children) {
    381                var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
    382 
    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            }
    395 
    396            dropon.insertBefore(element, child);
    397 
    398            self.options(oldParentNode).onChange(element);
    399            droponOptions.onChange(element);
    400        }
    401    },
    402 
    403    /** @id MochiKit.Sortable.unmark */
    404    unmark: function () {
    405        var m = MochiKit.Sortable._marker;
    406        if (m) {
    407            MochiKit.Style.hideElement(m);
    408        }
    409    },
    410 
    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        }
    420 
    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';
    432 
    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    },
    442 
    443    _tree: function (element, options, parent) {
    444        var self = MochiKit.Sortable;
    445        var children = self.findElements(element, options) || [];
    446 
    447        for (var i = 0; i < children.length; ++i) {
    448            var match = children[i].id.match(options.format);
    449 
    450            if (!match) {
    451                continue;
    452            }
    453 
    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            }
    462 
    463            /* Get the element containing the children and recurse over it */
    464            if (child.container) {
    465                self._tree(child.container, options, child)
    466            }
    467 
    468            parent.children.push (child);
    469        }
    470 
    471        return parent;
    472    },
    473 
    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    },
    487 
    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 || {});
    499 
    500        var root = {
    501            id: null,
    502            parent: null,
    503            children: new Array,
    504            container: element,
    505            position: 0
    506        }
    507 
    508        return MochiKit.Sortable._tree(element, options, root);
    509    },
    510 
    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 || {});
    522 
    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));
    531 
    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    },
    540 
    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    },
    551 
    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 || {});
    557 
    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    },
    562 
    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);
    574 
    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 });
    587 
    588 // trunk compatibility
    589 MochiKit.Sortable.Sortable = MochiKit.Sortable;