1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/tests/mochitest/ajax/mochikit/MochiKit/Sortable.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,589 @@ 1.4 +/*** 1.5 +Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 1.6 + Mochi-ized By Thomas Herve (_firstname_@nimail.org) 1.7 + 1.8 +See scriptaculous.js for full license. 1.9 + 1.10 +***/ 1.11 + 1.12 +if (typeof(dojo) != 'undefined') { 1.13 + dojo.provide('MochiKit.Sortable'); 1.14 + dojo.require('MochiKit.Base'); 1.15 + dojo.require('MochiKit.DOM'); 1.16 + dojo.require('MochiKit.Iter'); 1.17 +} 1.18 + 1.19 +if (typeof(JSAN) != 'undefined') { 1.20 + JSAN.use("MochiKit.Base", []); 1.21 + JSAN.use("MochiKit.DOM", []); 1.22 + JSAN.use("MochiKit.Iter", []); 1.23 +} 1.24 + 1.25 +try { 1.26 + if (typeof(MochiKit.Base) == 'undefined' || 1.27 + typeof(MochiKit.DOM) == 'undefined' || 1.28 + typeof(MochiKit.Iter) == 'undefined') { 1.29 + throw ""; 1.30 + } 1.31 +} catch (e) { 1.32 + throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!"; 1.33 +} 1.34 + 1.35 +if (typeof(MochiKit.Sortable) == 'undefined') { 1.36 + MochiKit.Sortable = {}; 1.37 +} 1.38 + 1.39 +MochiKit.Sortable.NAME = 'MochiKit.Sortable'; 1.40 +MochiKit.Sortable.VERSION = '1.4'; 1.41 + 1.42 +MochiKit.Sortable.__repr__ = function () { 1.43 + return '[' + this.NAME + ' ' + this.VERSION + ']'; 1.44 +}; 1.45 + 1.46 +MochiKit.Sortable.toString = function () { 1.47 + return this.__repr__(); 1.48 +}; 1.49 + 1.50 +MochiKit.Sortable.EXPORT = [ 1.51 +]; 1.52 + 1.53 +MochiKit.Sortable.EXPORT_OK = [ 1.54 +]; 1.55 + 1.56 +MochiKit.Base.update(MochiKit.Sortable, { 1.57 + /*** 1.58 + 1.59 + Manage sortables. Mainly use the create function to add a sortable. 1.60 + 1.61 + ***/ 1.62 + sortables: {}, 1.63 + 1.64 + _findRootElement: function (element) { 1.65 + while (element.tagName.toUpperCase() != "BODY") { 1.66 + if (element.id && MochiKit.Sortable.sortables[element.id]) { 1.67 + return element; 1.68 + } 1.69 + element = element.parentNode; 1.70 + } 1.71 + }, 1.72 + 1.73 + /** @id MochiKit.Sortable.options */ 1.74 + options: function (element) { 1.75 + element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element)); 1.76 + if (!element) { 1.77 + return; 1.78 + } 1.79 + return MochiKit.Sortable.sortables[element.id]; 1.80 + }, 1.81 + 1.82 + /** @id MochiKit.Sortable.destroy */ 1.83 + destroy: function (element){ 1.84 + var s = MochiKit.Sortable.options(element); 1.85 + var b = MochiKit.Base; 1.86 + var d = MochiKit.DragAndDrop; 1.87 + 1.88 + if (s) { 1.89 + MochiKit.Signal.disconnect(s.startHandle); 1.90 + MochiKit.Signal.disconnect(s.endHandle); 1.91 + b.map(function (dr) { 1.92 + d.Droppables.remove(dr); 1.93 + }, s.droppables); 1.94 + b.map(function (dr) { 1.95 + dr.destroy(); 1.96 + }, s.draggables); 1.97 + 1.98 + delete MochiKit.Sortable.sortables[s.element.id]; 1.99 + } 1.100 + }, 1.101 + 1.102 + /** @id MochiKit.Sortable.create */ 1.103 + create: function (element, options) { 1.104 + element = MochiKit.DOM.getElement(element); 1.105 + var self = MochiKit.Sortable; 1.106 + 1.107 + /** @id MochiKit.Sortable.options */ 1.108 + options = MochiKit.Base.update({ 1.109 + 1.110 + /** @id MochiKit.Sortable.element */ 1.111 + element: element, 1.112 + 1.113 + /** @id MochiKit.Sortable.tag */ 1.114 + tag: 'li', // assumes li children, override with tag: 'tagname' 1.115 + 1.116 + /** @id MochiKit.Sortable.dropOnEmpty */ 1.117 + dropOnEmpty: false, 1.118 + 1.119 + /** @id MochiKit.Sortable.tree */ 1.120 + tree: false, 1.121 + 1.122 + /** @id MochiKit.Sortable.treeTag */ 1.123 + treeTag: 'ul', 1.124 + 1.125 + /** @id MochiKit.Sortable.overlap */ 1.126 + overlap: 'vertical', // one of 'vertical', 'horizontal' 1.127 + 1.128 + /** @id MochiKit.Sortable.constraint */ 1.129 + constraint: 'vertical', // one of 'vertical', 'horizontal', false 1.130 + // also takes array of elements (or ids); or false 1.131 + 1.132 + /** @id MochiKit.Sortable.containment */ 1.133 + containment: [element], 1.134 + 1.135 + /** @id MochiKit.Sortable.handle */ 1.136 + handle: false, // or a CSS class 1.137 + 1.138 + /** @id MochiKit.Sortable.only */ 1.139 + only: false, 1.140 + 1.141 + /** @id MochiKit.Sortable.hoverclass */ 1.142 + hoverclass: null, 1.143 + 1.144 + /** @id MochiKit.Sortable.ghosting */ 1.145 + ghosting: false, 1.146 + 1.147 + /** @id MochiKit.Sortable.scroll */ 1.148 + scroll: false, 1.149 + 1.150 + /** @id MochiKit.Sortable.scrollSensitivity */ 1.151 + scrollSensitivity: 20, 1.152 + 1.153 + /** @id MochiKit.Sortable.scrollSpeed */ 1.154 + scrollSpeed: 15, 1.155 + 1.156 + /** @id MochiKit.Sortable.format */ 1.157 + format: /^[^_]*_(.*)$/, 1.158 + 1.159 + /** @id MochiKit.Sortable.onChange */ 1.160 + onChange: MochiKit.Base.noop, 1.161 + 1.162 + /** @id MochiKit.Sortable.onUpdate */ 1.163 + onUpdate: MochiKit.Base.noop, 1.164 + 1.165 + /** @id MochiKit.Sortable.accept */ 1.166 + accept: null 1.167 + }, options); 1.168 + 1.169 + // clear any old sortable with same element 1.170 + self.destroy(element); 1.171 + 1.172 + // build options for the draggables 1.173 + var options_for_draggable = { 1.174 + revert: true, 1.175 + ghosting: options.ghosting, 1.176 + scroll: options.scroll, 1.177 + scrollSensitivity: options.scrollSensitivity, 1.178 + scrollSpeed: options.scrollSpeed, 1.179 + constraint: options.constraint, 1.180 + handle: options.handle 1.181 + }; 1.182 + 1.183 + if (options.starteffect) { 1.184 + options_for_draggable.starteffect = options.starteffect; 1.185 + } 1.186 + 1.187 + if (options.reverteffect) { 1.188 + options_for_draggable.reverteffect = options.reverteffect; 1.189 + } else if (options.ghosting) { 1.190 + options_for_draggable.reverteffect = function (innerelement) { 1.191 + innerelement.style.top = 0; 1.192 + innerelement.style.left = 0; 1.193 + }; 1.194 + } 1.195 + 1.196 + if (options.endeffect) { 1.197 + options_for_draggable.endeffect = options.endeffect; 1.198 + } 1.199 + 1.200 + if (options.zindex) { 1.201 + options_for_draggable.zindex = options.zindex; 1.202 + } 1.203 + 1.204 + // build options for the droppables 1.205 + var options_for_droppable = { 1.206 + overlap: options.overlap, 1.207 + containment: options.containment, 1.208 + hoverclass: options.hoverclass, 1.209 + onhover: self.onHover, 1.210 + tree: options.tree, 1.211 + accept: options.accept 1.212 + } 1.213 + 1.214 + var options_for_tree = { 1.215 + onhover: self.onEmptyHover, 1.216 + overlap: options.overlap, 1.217 + containment: options.containment, 1.218 + hoverclass: options.hoverclass, 1.219 + accept: options.accept 1.220 + } 1.221 + 1.222 + // fix for gecko engine 1.223 + MochiKit.DOM.removeEmptyTextNodes(element); 1.224 + 1.225 + options.draggables = []; 1.226 + options.droppables = []; 1.227 + 1.228 + // drop on empty handling 1.229 + if (options.dropOnEmpty || options.tree) { 1.230 + new MochiKit.DragAndDrop.Droppable(element, options_for_tree); 1.231 + options.droppables.push(element); 1.232 + } 1.233 + MochiKit.Base.map(function (e) { 1.234 + // handles are per-draggable 1.235 + var handle = options.handle ? 1.236 + MochiKit.DOM.getFirstElementByTagAndClassName(null, 1.237 + options.handle, e) : e; 1.238 + options.draggables.push( 1.239 + new MochiKit.DragAndDrop.Draggable(e, 1.240 + MochiKit.Base.update(options_for_draggable, 1.241 + {handle: handle}))); 1.242 + new MochiKit.DragAndDrop.Droppable(e, options_for_droppable); 1.243 + if (options.tree) { 1.244 + e.treeNode = element; 1.245 + } 1.246 + options.droppables.push(e); 1.247 + }, (self.findElements(element, options) || [])); 1.248 + 1.249 + if (options.tree) { 1.250 + MochiKit.Base.map(function (e) { 1.251 + new MochiKit.DragAndDrop.Droppable(e, options_for_tree); 1.252 + e.treeNode = element; 1.253 + options.droppables.push(e); 1.254 + }, (self.findTreeElements(element, options) || [])); 1.255 + } 1.256 + 1.257 + // keep reference 1.258 + self.sortables[element.id] = options; 1.259 + 1.260 + options.lastValue = self.serialize(element); 1.261 + options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start', 1.262 + MochiKit.Base.partial(self.onStart, element)); 1.263 + options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end', 1.264 + MochiKit.Base.partial(self.onEnd, element)); 1.265 + }, 1.266 + 1.267 + /** @id MochiKit.Sortable.onStart */ 1.268 + onStart: function (element, draggable) { 1.269 + var self = MochiKit.Sortable; 1.270 + var options = self.options(element); 1.271 + options.lastValue = self.serialize(options.element); 1.272 + }, 1.273 + 1.274 + /** @id MochiKit.Sortable.onEnd */ 1.275 + onEnd: function (element, draggable) { 1.276 + var self = MochiKit.Sortable; 1.277 + self.unmark(); 1.278 + var options = self.options(element); 1.279 + if (options.lastValue != self.serialize(options.element)) { 1.280 + options.onUpdate(options.element); 1.281 + } 1.282 + }, 1.283 + 1.284 + // return all suitable-for-sortable elements in a guaranteed order 1.285 + 1.286 + /** @id MochiKit.Sortable.findElements */ 1.287 + findElements: function (element, options) { 1.288 + return MochiKit.Sortable.findChildren( 1.289 + element, options.only, options.tree ? true : false, options.tag); 1.290 + }, 1.291 + 1.292 + /** @id MochiKit.Sortable.findTreeElements */ 1.293 + findTreeElements: function (element, options) { 1.294 + return MochiKit.Sortable.findChildren( 1.295 + element, options.only, options.tree ? true : false, options.treeTag); 1.296 + }, 1.297 + 1.298 + /** @id MochiKit.Sortable.findChildren */ 1.299 + findChildren: function (element, only, recursive, tagName) { 1.300 + if (!element.hasChildNodes()) { 1.301 + return null; 1.302 + } 1.303 + tagName = tagName.toUpperCase(); 1.304 + if (only) { 1.305 + only = MochiKit.Base.flattenArray([only]); 1.306 + } 1.307 + var elements = []; 1.308 + MochiKit.Base.map(function (e) { 1.309 + if (e.tagName && 1.310 + e.tagName.toUpperCase() == tagName && 1.311 + (!only || 1.312 + MochiKit.Iter.some(only, function (c) { 1.313 + return MochiKit.DOM.hasElementClass(e, c); 1.314 + }))) { 1.315 + elements.push(e); 1.316 + } 1.317 + if (recursive) { 1.318 + var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName); 1.319 + if (grandchildren && grandchildren.length > 0) { 1.320 + elements = elements.concat(grandchildren); 1.321 + } 1.322 + } 1.323 + }, element.childNodes); 1.324 + return elements; 1.325 + }, 1.326 + 1.327 + /** @id MochiKit.Sortable.onHover */ 1.328 + onHover: function (element, dropon, overlap) { 1.329 + if (MochiKit.DOM.isParent(dropon, element)) { 1.330 + return; 1.331 + } 1.332 + var self = MochiKit.Sortable; 1.333 + 1.334 + if (overlap > .33 && overlap < .66 && self.options(dropon).tree) { 1.335 + return; 1.336 + } else if (overlap > 0.5) { 1.337 + self.mark(dropon, 'before'); 1.338 + if (dropon.previousSibling != element) { 1.339 + var oldParentNode = element.parentNode; 1.340 + element.style.visibility = 'hidden'; // fix gecko rendering 1.341 + dropon.parentNode.insertBefore(element, dropon); 1.342 + if (dropon.parentNode != oldParentNode) { 1.343 + self.options(oldParentNode).onChange(element); 1.344 + } 1.345 + self.options(dropon.parentNode).onChange(element); 1.346 + } 1.347 + } else { 1.348 + self.mark(dropon, 'after'); 1.349 + var nextElement = dropon.nextSibling || null; 1.350 + if (nextElement != element) { 1.351 + var oldParentNode = element.parentNode; 1.352 + element.style.visibility = 'hidden'; // fix gecko rendering 1.353 + dropon.parentNode.insertBefore(element, nextElement); 1.354 + if (dropon.parentNode != oldParentNode) { 1.355 + self.options(oldParentNode).onChange(element); 1.356 + } 1.357 + self.options(dropon.parentNode).onChange(element); 1.358 + } 1.359 + } 1.360 + }, 1.361 + 1.362 + _offsetSize: function (element, type) { 1.363 + if (type == 'vertical' || type == 'height') { 1.364 + return element.offsetHeight; 1.365 + } else { 1.366 + return element.offsetWidth; 1.367 + } 1.368 + }, 1.369 + 1.370 + /** @id MochiKit.Sortable.onEmptyHover */ 1.371 + onEmptyHover: function (element, dropon, overlap) { 1.372 + var oldParentNode = element.parentNode; 1.373 + var self = MochiKit.Sortable; 1.374 + var droponOptions = self.options(dropon); 1.375 + 1.376 + if (!MochiKit.DOM.isParent(dropon, element)) { 1.377 + var index; 1.378 + 1.379 + var children = self.findElements(dropon, {tag: droponOptions.tag, 1.380 + only: droponOptions.only}); 1.381 + var child = null; 1.382 + 1.383 + if (children) { 1.384 + var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 1.385 + 1.386 + for (index = 0; index < children.length; index += 1) { 1.387 + if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) { 1.388 + offset -= self._offsetSize(children[index], droponOptions.overlap); 1.389 + } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 1.390 + child = index + 1 < children.length ? children[index + 1] : null; 1.391 + break; 1.392 + } else { 1.393 + child = children[index]; 1.394 + break; 1.395 + } 1.396 + } 1.397 + } 1.398 + 1.399 + dropon.insertBefore(element, child); 1.400 + 1.401 + self.options(oldParentNode).onChange(element); 1.402 + droponOptions.onChange(element); 1.403 + } 1.404 + }, 1.405 + 1.406 + /** @id MochiKit.Sortable.unmark */ 1.407 + unmark: function () { 1.408 + var m = MochiKit.Sortable._marker; 1.409 + if (m) { 1.410 + MochiKit.Style.hideElement(m); 1.411 + } 1.412 + }, 1.413 + 1.414 + /** @id MochiKit.Sortable.mark */ 1.415 + mark: function (dropon, position) { 1.416 + // mark on ghosting only 1.417 + var d = MochiKit.DOM; 1.418 + var self = MochiKit.Sortable; 1.419 + var sortable = self.options(dropon.parentNode); 1.420 + if (sortable && !sortable.ghosting) { 1.421 + return; 1.422 + } 1.423 + 1.424 + if (!self._marker) { 1.425 + self._marker = d.getElement('dropmarker') || 1.426 + document.createElement('DIV'); 1.427 + MochiKit.Style.hideElement(self._marker); 1.428 + d.addElementClass(self._marker, 'dropmarker'); 1.429 + self._marker.style.position = 'absolute'; 1.430 + document.getElementsByTagName('body').item(0).appendChild(self._marker); 1.431 + } 1.432 + var offsets = MochiKit.Position.cumulativeOffset(dropon); 1.433 + self._marker.style.left = offsets.x + 'px'; 1.434 + self._marker.style.top = offsets.y + 'px'; 1.435 + 1.436 + if (position == 'after') { 1.437 + if (sortable.overlap == 'horizontal') { 1.438 + self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px'; 1.439 + } else { 1.440 + self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px'; 1.441 + } 1.442 + } 1.443 + MochiKit.Style.showElement(self._marker); 1.444 + }, 1.445 + 1.446 + _tree: function (element, options, parent) { 1.447 + var self = MochiKit.Sortable; 1.448 + var children = self.findElements(element, options) || []; 1.449 + 1.450 + for (var i = 0; i < children.length; ++i) { 1.451 + var match = children[i].id.match(options.format); 1.452 + 1.453 + if (!match) { 1.454 + continue; 1.455 + } 1.456 + 1.457 + var child = { 1.458 + id: encodeURIComponent(match ? match[1] : null), 1.459 + element: element, 1.460 + parent: parent, 1.461 + children: [], 1.462 + position: parent.children.length, 1.463 + container: self._findChildrenElement(children[i], options.treeTag.toUpperCase()) 1.464 + } 1.465 + 1.466 + /* Get the element containing the children and recurse over it */ 1.467 + if (child.container) { 1.468 + self._tree(child.container, options, child) 1.469 + } 1.470 + 1.471 + parent.children.push (child); 1.472 + } 1.473 + 1.474 + return parent; 1.475 + }, 1.476 + 1.477 + /* Finds the first element of the given tag type within a parent element. 1.478 + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ 1.479 + _findChildrenElement: function (element, containerTag) { 1.480 + if (element && element.hasChildNodes) { 1.481 + containerTag = containerTag.toUpperCase(); 1.482 + for (var i = 0; i < element.childNodes.length; ++i) { 1.483 + if (element.childNodes[i].tagName.toUpperCase() == containerTag) { 1.484 + return element.childNodes[i]; 1.485 + } 1.486 + } 1.487 + } 1.488 + return null; 1.489 + }, 1.490 + 1.491 + /** @id MochiKit.Sortable.tree */ 1.492 + tree: function (element, options) { 1.493 + element = MochiKit.DOM.getElement(element); 1.494 + var sortableOptions = MochiKit.Sortable.options(element); 1.495 + options = MochiKit.Base.update({ 1.496 + tag: sortableOptions.tag, 1.497 + treeTag: sortableOptions.treeTag, 1.498 + only: sortableOptions.only, 1.499 + name: element.id, 1.500 + format: sortableOptions.format 1.501 + }, options || {}); 1.502 + 1.503 + var root = { 1.504 + id: null, 1.505 + parent: null, 1.506 + children: new Array, 1.507 + container: element, 1.508 + position: 0 1.509 + } 1.510 + 1.511 + return MochiKit.Sortable._tree(element, options, root); 1.512 + }, 1.513 + 1.514 + /** 1.515 + * Specifies the sequence for the Sortable. 1.516 + * @param {Node} element Element to use as the Sortable. 1.517 + * @param {Object} newSequence New sequence to use. 1.518 + * @param {Object} options Options to use fro the Sortable. 1.519 + */ 1.520 + setSequence: function (element, newSequence, options) { 1.521 + var self = MochiKit.Sortable; 1.522 + var b = MochiKit.Base; 1.523 + element = MochiKit.DOM.getElement(element); 1.524 + options = b.update(self.options(element), options || {}); 1.525 + 1.526 + var nodeMap = {}; 1.527 + b.map(function (n) { 1.528 + var m = n.id.match(options.format); 1.529 + if (m) { 1.530 + nodeMap[m[1]] = [n, n.parentNode]; 1.531 + } 1.532 + n.parentNode.removeChild(n); 1.533 + }, self.findElements(element, options)); 1.534 + 1.535 + b.map(function (ident) { 1.536 + var n = nodeMap[ident]; 1.537 + if (n) { 1.538 + n[1].appendChild(n[0]); 1.539 + delete nodeMap[ident]; 1.540 + } 1.541 + }, newSequence); 1.542 + }, 1.543 + 1.544 + /* Construct a [i] index for a particular node */ 1.545 + _constructIndex: function (node) { 1.546 + var index = ''; 1.547 + do { 1.548 + if (node.id) { 1.549 + index = '[' + node.position + ']' + index; 1.550 + } 1.551 + } while ((node = node.parent) != null); 1.552 + return index; 1.553 + }, 1.554 + 1.555 + /** @id MochiKit.Sortable.sequence */ 1.556 + sequence: function (element, options) { 1.557 + element = MochiKit.DOM.getElement(element); 1.558 + var self = MochiKit.Sortable; 1.559 + var options = MochiKit.Base.update(self.options(element), options || {}); 1.560 + 1.561 + return MochiKit.Base.map(function (item) { 1.562 + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 1.563 + }, MochiKit.DOM.getElement(self.findElements(element, options) || [])); 1.564 + }, 1.565 + 1.566 + /** 1.567 + * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest. 1.568 + * These options override the Sortable options for the serialization only. 1.569 + * @param {Node} element Element to serialize. 1.570 + * @param {Object} options Serialization options. 1.571 + */ 1.572 + serialize: function (element, options) { 1.573 + element = MochiKit.DOM.getElement(element); 1.574 + var self = MochiKit.Sortable; 1.575 + options = MochiKit.Base.update(self.options(element), options || {}); 1.576 + var name = encodeURIComponent(options.name || element.id); 1.577 + 1.578 + if (options.tree) { 1.579 + return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) { 1.580 + return [name + self._constructIndex(item) + "[id]=" + 1.581 + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 1.582 + }, self.tree(element, options).children)).join('&'); 1.583 + } else { 1.584 + return MochiKit.Base.map(function (item) { 1.585 + return name + "[]=" + encodeURIComponent(item); 1.586 + }, self.sequence(element, options)).join('&'); 1.587 + } 1.588 + } 1.589 +}); 1.590 + 1.591 +// trunk compatibility 1.592 +MochiKit.Sortable.Sortable = MochiKit.Sortable;