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