[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/lib/jquery.ui/ -> jquery.ui.sortable.js (source)

   1  /*!
   2   * jQuery UI Sortable 1.9.2
   3   * http://jqueryui.com
   4   *
   5   * Copyright 2012 jQuery Foundation and other contributors
   6   * Released under the MIT license.
   7   * http://jquery.org/license
   8   *
   9   * http://api.jqueryui.com/sortable/
  10   *
  11   * Depends:
  12   *    jquery.ui.core.js
  13   *    jquery.ui.mouse.js
  14   *    jquery.ui.widget.js
  15   */
  16  (function( $, undefined ) {
  17  
  18  $.widget("ui.sortable", $.ui.mouse, {
  19      version: "1.9.2",
  20      widgetEventPrefix: "sort",
  21      ready: false,
  22      options: {
  23          appendTo: "parent",
  24          axis: false,
  25          connectWith: false,
  26          containment: false,
  27          cursor: 'auto',
  28          cursorAt: false,
  29          dropOnEmpty: true,
  30          forcePlaceholderSize: false,
  31          forceHelperSize: false,
  32          grid: false,
  33          handle: false,
  34          helper: "original",
  35          items: '> *',
  36          opacity: false,
  37          placeholder: false,
  38          revert: false,
  39          scroll: true,
  40          scrollSensitivity: 20,
  41          scrollSpeed: 20,
  42          scope: "default",
  43          tolerance: "intersect",
  44          zIndex: 1000
  45      },
  46      _create: function() {
  47  
  48          var o = this.options;
  49          this.containerCache = {};
  50          this.element.addClass("ui-sortable");
  51  
  52          //Get the items
  53          this.refresh();
  54  
  55          //Let's determine if the items are being displayed horizontally
  56          this.floating = this.items.length ? o.axis === 'x' || (/left|right/).test(this.items[0].item.css('float')) || (/inline|table-cell/).test(this.items[0].item.css('display')) : false;
  57  
  58          //Let's determine the parent's offset
  59          this.offset = this.element.offset();
  60  
  61          //Initialize mouse events for interaction
  62          this._mouseInit();
  63  
  64          //We're ready to go
  65          this.ready = true
  66  
  67      },
  68  
  69      _destroy: function() {
  70          this.element
  71              .removeClass("ui-sortable ui-sortable-disabled");
  72          this._mouseDestroy();
  73  
  74          for ( var i = this.items.length - 1; i >= 0; i-- )
  75              this.items[i].item.removeData(this.widgetName + "-item");
  76  
  77          return this;
  78      },
  79  
  80      _setOption: function(key, value){
  81          if ( key === "disabled" ) {
  82              this.options[ key ] = value;
  83  
  84              this.widget().toggleClass( "ui-sortable-disabled", !!value );
  85          } else {
  86              // Don't call widget base _setOption for disable as it adds ui-state-disabled class
  87              $.Widget.prototype._setOption.apply(this, arguments);
  88          }
  89      },
  90  
  91      _mouseCapture: function(event, overrideHandle) {
  92          var that = this;
  93  
  94          if (this.reverting) {
  95              return false;
  96          }
  97  
  98          if(this.options.disabled || this.options.type == 'static') return false;
  99  
 100          //We have to refresh the items data once first
 101          this._refreshItems(event);
 102  
 103          //Find out if the clicked node (or one of its parents) is a actual item in this.items
 104          var currentItem = null, nodes = $(event.target).parents().each(function() {
 105              if($.data(this, that.widgetName + '-item') == that) {
 106                  currentItem = $(this);
 107                  return false;
 108              }
 109          });
 110          if($.data(event.target, that.widgetName + '-item') == that) currentItem = $(event.target);
 111  
 112          if(!currentItem) return false;
 113          if(this.options.handle && !overrideHandle) {
 114              var validHandle = false;
 115  
 116              $(this.options.handle, currentItem).find("*").andSelf().each(function() { if(this == event.target) validHandle = true; });
 117              if(!validHandle) return false;
 118          }
 119  
 120          this.currentItem = currentItem;
 121          this._removeCurrentsFromItems();
 122          return true;
 123  
 124      },
 125  
 126      _mouseStart: function(event, overrideHandle, noActivation) {
 127  
 128          var o = this.options;
 129          this.currentContainer = this;
 130  
 131          //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
 132          this.refreshPositions();
 133  
 134          //Create and append the visible helper
 135          this.helper = this._createHelper(event);
 136  
 137          //Cache the helper size
 138          this._cacheHelperProportions();
 139  
 140          /*
 141           * - Position generation -
 142           * This block generates everything position related - it's the core of draggables.
 143           */
 144  
 145          //Cache the margins of the original element
 146          this._cacheMargins();
 147  
 148          //Get the next scrolling parent
 149          this.scrollParent = this.helper.scrollParent();
 150  
 151          //The element's absolute position on the page minus margins
 152          this.offset = this.currentItem.offset();
 153          this.offset = {
 154              top: this.offset.top - this.margins.top,
 155              left: this.offset.left - this.margins.left
 156          };
 157  
 158          $.extend(this.offset, {
 159              click: { //Where the click happened, relative to the element
 160                  left: event.pageX - this.offset.left,
 161                  top: event.pageY - this.offset.top
 162              },
 163              parent: this._getParentOffset(),
 164              relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
 165          });
 166  
 167          // Only after we got the offset, we can change the helper's position to absolute
 168          // TODO: Still need to figure out a way to make relative sorting possible
 169          this.helper.css("position", "absolute");
 170          this.cssPosition = this.helper.css("position");
 171  
 172          //Generate the original position
 173          this.originalPosition = this._generatePosition(event);
 174          this.originalPageX = event.pageX;
 175          this.originalPageY = event.pageY;
 176  
 177          //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
 178          (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
 179  
 180          //Cache the former DOM position
 181          this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
 182  
 183          //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
 184          if(this.helper[0] != this.currentItem[0]) {
 185              this.currentItem.hide();
 186          }
 187  
 188          //Create the placeholder
 189          this._createPlaceholder();
 190  
 191          //Set a containment if given in the options
 192          if(o.containment)
 193              this._setContainment();
 194  
 195          if(o.cursor) { // cursor option
 196              if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
 197              $('body').css("cursor", o.cursor);
 198          }
 199  
 200          if(o.opacity) { // opacity option
 201              if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
 202              this.helper.css("opacity", o.opacity);
 203          }
 204  
 205          if(o.zIndex) { // zIndex option
 206              if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
 207              this.helper.css("zIndex", o.zIndex);
 208          }
 209  
 210          //Prepare scrolling
 211          if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
 212              this.overflowOffset = this.scrollParent.offset();
 213  
 214          //Call callbacks
 215          this._trigger("start", event, this._uiHash());
 216  
 217          //Recache the helper size
 218          if(!this._preserveHelperProportions)
 219              this._cacheHelperProportions();
 220  
 221  
 222          //Post 'activate' events to possible containers
 223          if(!noActivation) {
 224               for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, this._uiHash(this)); }
 225          }
 226  
 227          //Prepare possible droppables
 228          if($.ui.ddmanager)
 229              $.ui.ddmanager.current = this;
 230  
 231          if ($.ui.ddmanager && !o.dropBehaviour)
 232              $.ui.ddmanager.prepareOffsets(this, event);
 233  
 234          this.dragging = true;
 235  
 236          this.helper.addClass("ui-sortable-helper");
 237          this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
 238          return true;
 239  
 240      },
 241  
 242      _mouseDrag: function(event) {
 243  
 244          //Compute the helpers position
 245          this.position = this._generatePosition(event);
 246          this.positionAbs = this._convertPositionTo("absolute");
 247  
 248          if (!this.lastPositionAbs) {
 249              this.lastPositionAbs = this.positionAbs;
 250          }
 251  
 252          //Do scrolling
 253          if(this.options.scroll) {
 254              var o = this.options, scrolled = false;
 255              if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
 256  
 257                  if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
 258                      this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
 259                  else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
 260                      this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
 261  
 262                  if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
 263                      this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
 264                  else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
 265                      this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
 266  
 267              } else {
 268  
 269                  if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
 270                      scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
 271                  else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
 272                      scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
 273  
 274                  if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
 275                      scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
 276                  else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
 277                      scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
 278  
 279              }
 280  
 281              if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
 282                  $.ui.ddmanager.prepareOffsets(this, event);
 283          }
 284  
 285          //Regenerate the absolute position used for position checks
 286          this.positionAbs = this._convertPositionTo("absolute");
 287  
 288          //Set the helper position
 289          if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
 290          if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
 291  
 292          //Rearrange
 293          for (var i = this.items.length - 1; i >= 0; i--) {
 294  
 295              //Cache variables and intersection, continue if no intersection
 296              var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
 297              if (!intersection) continue;
 298  
 299              // Only put the placeholder inside the current Container, skip all
 300              // items form other containers. This works because when moving
 301              // an item from one container to another the
 302              // currentContainer is switched before the placeholder is moved.
 303              //
 304              // Without this moving items in "sub-sortables" can cause the placeholder to jitter
 305              // beetween the outer and inner container.
 306              if (item.instance !== this.currentContainer) continue;
 307  
 308              if (itemElement != this.currentItem[0] //cannot intersect with itself
 309                  &&    this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
 310                  &&    !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
 311                  && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
 312                  //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
 313              ) {
 314  
 315                  this.direction = intersection == 1 ? "down" : "up";
 316  
 317                  if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
 318                      this._rearrange(event, item);
 319                  } else {
 320                      break;
 321                  }
 322  
 323                  this._trigger("change", event, this._uiHash());
 324                  break;
 325              }
 326          }
 327  
 328          //Post events to containers
 329          this._contactContainers(event);
 330  
 331          //Interconnect with droppables
 332          if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
 333  
 334          //Call callbacks
 335          this._trigger('sort', event, this._uiHash());
 336  
 337          this.lastPositionAbs = this.positionAbs;
 338          return false;
 339  
 340      },
 341  
 342      _mouseStop: function(event, noPropagation) {
 343  
 344          if(!event) return;
 345  
 346          //If we are using droppables, inform the manager about the drop
 347          if ($.ui.ddmanager && !this.options.dropBehaviour)
 348              $.ui.ddmanager.drop(this, event);
 349  
 350          if(this.options.revert) {
 351              var that = this;
 352              var cur = this.placeholder.offset();
 353  
 354              this.reverting = true;
 355  
 356              $(this.helper).animate({
 357                  left: cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
 358                  top: cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
 359              }, parseInt(this.options.revert, 10) || 500, function() {
 360                  that._clear(event);
 361              });
 362          } else {
 363              this._clear(event, noPropagation);
 364          }
 365  
 366          return false;
 367  
 368      },
 369  
 370      cancel: function() {
 371  
 372          if(this.dragging) {
 373  
 374              this._mouseUp({ target: null });
 375  
 376              if(this.options.helper == "original")
 377                  this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
 378              else
 379                  this.currentItem.show();
 380  
 381              //Post deactivating events to containers
 382              for (var i = this.containers.length - 1; i >= 0; i--){
 383                  this.containers[i]._trigger("deactivate", null, this._uiHash(this));
 384                  if(this.containers[i].containerCache.over) {
 385                      this.containers[i]._trigger("out", null, this._uiHash(this));
 386                      this.containers[i].containerCache.over = 0;
 387                  }
 388              }
 389  
 390          }
 391  
 392          if (this.placeholder) {
 393              //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
 394              if(this.placeholder[0].parentNode) this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
 395              if(this.options.helper != "original" && this.helper && this.helper[0].parentNode) this.helper.remove();
 396  
 397              $.extend(this, {
 398                  helper: null,
 399                  dragging: false,
 400                  reverting: false,
 401                  _noFinalSort: null
 402              });
 403  
 404              if(this.domPosition.prev) {
 405                  $(this.domPosition.prev).after(this.currentItem);
 406              } else {
 407                  $(this.domPosition.parent).prepend(this.currentItem);
 408              }
 409          }
 410  
 411          return this;
 412  
 413      },
 414  
 415      serialize: function(o) {
 416  
 417          var items = this._getItemsAsjQuery(o && o.connected);
 418          var str = []; o = o || {};
 419  
 420          $(items).each(function() {
 421              var res = ($(o.item || this).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
 422              if(res) str.push((o.key || res[1]+'[]')+'='+(o.key && o.expression ? res[1] : res[2]));
 423          });
 424  
 425          if(!str.length && o.key) {
 426              str.push(o.key + '=');
 427          }
 428  
 429          return str.join('&');
 430  
 431      },
 432  
 433      toArray: function(o) {
 434  
 435          var items = this._getItemsAsjQuery(o && o.connected);
 436          var ret = []; o = o || {};
 437  
 438          items.each(function() { ret.push($(o.item || this).attr(o.attribute || 'id') || ''); });
 439          return ret;
 440  
 441      },
 442  
 443      /* Be careful with the following core functions */
 444      _intersectsWith: function(item) {
 445  
 446          var x1 = this.positionAbs.left,
 447              x2 = x1 + this.helperProportions.width,
 448              y1 = this.positionAbs.top,
 449              y2 = y1 + this.helperProportions.height;
 450  
 451          var l = item.left,
 452              r = l + item.width,
 453              t = item.top,
 454              b = t + item.height;
 455  
 456          var dyClick = this.offset.click.top,
 457              dxClick = this.offset.click.left;
 458  
 459          var isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
 460  
 461          if(       this.options.tolerance == "pointer"
 462              || this.options.forcePointerForContainers
 463              || (this.options.tolerance != "pointer" && this.helperProportions[this.floating ? 'width' : 'height'] > item[this.floating ? 'width' : 'height'])
 464          ) {
 465              return isOverElement;
 466          } else {
 467  
 468              return (l < x1 + (this.helperProportions.width / 2) // Right Half
 469                  && x2 - (this.helperProportions.width / 2) < r // Left Half
 470                  && t < y1 + (this.helperProportions.height / 2) // Bottom Half
 471                  && y2 - (this.helperProportions.height / 2) < b ); // Top Half
 472  
 473          }
 474      },
 475  
 476      _intersectsWithPointer: function(item) {
 477  
 478          var isOverElementHeight = (this.options.axis === 'x') || $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
 479              isOverElementWidth = (this.options.axis === 'y') || $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
 480              isOverElement = isOverElementHeight && isOverElementWidth,
 481              verticalDirection = this._getDragVerticalDirection(),
 482              horizontalDirection = this._getDragHorizontalDirection();
 483  
 484          if (!isOverElement)
 485              return false;
 486  
 487          return this.floating ?
 488              ( ((horizontalDirection && horizontalDirection == "right") || verticalDirection == "down") ? 2 : 1 )
 489              : ( verticalDirection && (verticalDirection == "down" ? 2 : 1) );
 490  
 491      },
 492  
 493      _intersectsWithSides: function(item) {
 494  
 495          var isOverBottomHalf = $.ui.isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
 496              isOverRightHalf = $.ui.isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
 497              verticalDirection = this._getDragVerticalDirection(),
 498              horizontalDirection = this._getDragHorizontalDirection();
 499  
 500          if (this.floating && horizontalDirection) {
 501              return ((horizontalDirection == "right" && isOverRightHalf) || (horizontalDirection == "left" && !isOverRightHalf));
 502          } else {
 503              return verticalDirection && ((verticalDirection == "down" && isOverBottomHalf) || (verticalDirection == "up" && !isOverBottomHalf));
 504          }
 505  
 506      },
 507  
 508      _getDragVerticalDirection: function() {
 509          var delta = this.positionAbs.top - this.lastPositionAbs.top;
 510          return delta != 0 && (delta > 0 ? "down" : "up");
 511      },
 512  
 513      _getDragHorizontalDirection: function() {
 514          var delta = this.positionAbs.left - this.lastPositionAbs.left;
 515          return delta != 0 && (delta > 0 ? "right" : "left");
 516      },
 517  
 518      refresh: function(event) {
 519          this._refreshItems(event);
 520          this.refreshPositions();
 521          return this;
 522      },
 523  
 524      _connectWith: function() {
 525          var options = this.options;
 526          return options.connectWith.constructor == String
 527              ? [options.connectWith]
 528              : options.connectWith;
 529      },
 530  
 531      _getItemsAsjQuery: function(connected) {
 532  
 533          var items = [];
 534          var queries = [];
 535          var connectWith = this._connectWith();
 536  
 537          if(connectWith && connected) {
 538              for (var i = connectWith.length - 1; i >= 0; i--){
 539                  var cur = $(connectWith[i]);
 540                  for (var j = cur.length - 1; j >= 0; j--){
 541                      var inst = $.data(cur[j], this.widgetName);
 542                      if(inst && inst != this && !inst.options.disabled) {
 543                          queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), inst]);
 544                      }
 545                  };
 546              };
 547          }
 548  
 549          queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not('.ui-sortable-placeholder'), this]);
 550  
 551          for (var i = queries.length - 1; i >= 0; i--){
 552              queries[i][0].each(function() {
 553                  items.push(this);
 554              });
 555          };
 556  
 557          return $(items);
 558  
 559      },
 560  
 561      _removeCurrentsFromItems: function() {
 562  
 563          var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
 564  
 565          this.items = $.grep(this.items, function (item) {
 566              for (var j=0; j < list.length; j++) {
 567                  if(list[j] == item.item[0])
 568                      return false;
 569              };
 570              return true;
 571          });
 572  
 573      },
 574  
 575      _refreshItems: function(event) {
 576  
 577          this.items = [];
 578          this.containers = [this];
 579          var items = this.items;
 580          var queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]];
 581          var connectWith = this._connectWith();
 582  
 583          if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
 584              for (var i = connectWith.length - 1; i >= 0; i--){
 585                  var cur = $(connectWith[i]);
 586                  for (var j = cur.length - 1; j >= 0; j--){
 587                      var inst = $.data(cur[j], this.widgetName);
 588                      if(inst && inst != this && !inst.options.disabled) {
 589                          queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
 590                          this.containers.push(inst);
 591                      }
 592                  };
 593              };
 594          }
 595  
 596          for (var i = queries.length - 1; i >= 0; i--) {
 597              var targetData = queries[i][1];
 598              var _queries = queries[i][0];
 599  
 600              for (var j=0, queriesLength = _queries.length; j < queriesLength; j++) {
 601                  var item = $(_queries[j]);
 602  
 603                  item.data(this.widgetName + '-item', targetData); // Data for target checking (mouse manager)
 604  
 605                  items.push({
 606                      item: item,
 607                      instance: targetData,
 608                      width: 0, height: 0,
 609                      left: 0, top: 0
 610                  });
 611              };
 612          };
 613  
 614      },
 615  
 616      refreshPositions: function(fast) {
 617  
 618          //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
 619          if(this.offsetParent && this.helper) {
 620              this.offset.parent = this._getParentOffset();
 621          }
 622  
 623          for (var i = this.items.length - 1; i >= 0; i--){
 624              var item = this.items[i];
 625  
 626              //We ignore calculating positions of all connected containers when we're not over them
 627              if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
 628                  continue;
 629  
 630              var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
 631  
 632              if (!fast) {
 633                  item.width = t.outerWidth();
 634                  item.height = t.outerHeight();
 635              }
 636  
 637              var p = t.offset();
 638              item.left = p.left;
 639              item.top = p.top;
 640          };
 641  
 642          if(this.options.custom && this.options.custom.refreshContainers) {
 643              this.options.custom.refreshContainers.call(this);
 644          } else {
 645              for (var i = this.containers.length - 1; i >= 0; i--){
 646                  var p = this.containers[i].element.offset();
 647                  this.containers[i].containerCache.left = p.left;
 648                  this.containers[i].containerCache.top = p.top;
 649                  this.containers[i].containerCache.width    = this.containers[i].element.outerWidth();
 650                  this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
 651              };
 652          }
 653  
 654          return this;
 655      },
 656  
 657      _createPlaceholder: function(that) {
 658          that = that || this;
 659          var o = that.options;
 660  
 661          if(!o.placeholder || o.placeholder.constructor == String) {
 662              var className = o.placeholder;
 663              o.placeholder = {
 664                  element: function() {
 665  
 666                      var el = $(document.createElement(that.currentItem[0].nodeName))
 667                          .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
 668                          .removeClass("ui-sortable-helper")[0];
 669  
 670                      if(!className)
 671                          el.style.visibility = "hidden";
 672  
 673                      return el;
 674                  },
 675                  update: function(container, p) {
 676  
 677                      // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
 678                      // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
 679                      if(className && !o.forcePlaceholderSize) return;
 680  
 681                      //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
 682                      if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css('paddingTop')||0, 10) - parseInt(that.currentItem.css('paddingBottom')||0, 10)); };
 683                      if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css('paddingLeft')||0, 10) - parseInt(that.currentItem.css('paddingRight')||0, 10)); };
 684                  }
 685              };
 686          }
 687  
 688          //Create the placeholder
 689          that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
 690  
 691          //Append it after the actual current item
 692          that.currentItem.after(that.placeholder);
 693  
 694          //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
 695          o.placeholder.update(that, that.placeholder);
 696  
 697      },
 698  
 699      _contactContainers: function(event) {
 700  
 701          // get innermost container that intersects with item
 702          var innermostContainer = null, innermostIndex = null;
 703  
 704  
 705          for (var i = this.containers.length - 1; i >= 0; i--){
 706  
 707              // never consider a container that's located within the item itself
 708              if($.contains(this.currentItem[0], this.containers[i].element[0]))
 709                  continue;
 710  
 711              if(this._intersectsWith(this.containers[i].containerCache)) {
 712  
 713                  // if we've already found a container and it's more "inner" than this, then continue
 714                  if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0]))
 715                      continue;
 716  
 717                  innermostContainer = this.containers[i];
 718                  innermostIndex = i;
 719  
 720              } else {
 721                  // container doesn't intersect. trigger "out" event if necessary
 722                  if(this.containers[i].containerCache.over) {
 723                      this.containers[i]._trigger("out", event, this._uiHash(this));
 724                      this.containers[i].containerCache.over = 0;
 725                  }
 726              }
 727  
 728          }
 729  
 730          // if no intersecting containers found, return
 731          if(!innermostContainer) return;
 732  
 733          // move the item into the container if it's not there already
 734          if(this.containers.length === 1) {
 735              this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
 736              this.containers[innermostIndex].containerCache.over = 1;
 737          } else {
 738  
 739              //When entering a new container, we will find the item with the least distance and append our item near it
 740              var dist = 10000; var itemWithLeastDistance = null;
 741              var posProperty = this.containers[innermostIndex].floating ? 'left' : 'top';
 742              var sizeProperty = this.containers[innermostIndex].floating ? 'width' : 'height';
 743              var base = this.positionAbs[posProperty] + this.offset.click[posProperty];
 744              for (var j = this.items.length - 1; j >= 0; j--) {
 745                  if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) continue;
 746                  if(this.items[j].item[0] == this.currentItem[0]) continue;
 747                  var cur = this.items[j].item.offset()[posProperty];
 748                  var nearBottom = false;
 749                  if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
 750                      nearBottom = true;
 751                      cur += this.items[j][sizeProperty];
 752                  }
 753  
 754                  if(Math.abs(cur - base) < dist) {
 755                      dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
 756                      this.direction = nearBottom ? "up": "down";
 757                  }
 758              }
 759  
 760              if(!itemWithLeastDistance && !this.options.dropOnEmpty) //Check if dropOnEmpty is enabled
 761                  return;
 762  
 763              this.currentContainer = this.containers[innermostIndex];
 764              itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
 765              this._trigger("change", event, this._uiHash());
 766              this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
 767  
 768              //Update the placeholder
 769              this.options.placeholder.update(this.currentContainer, this.placeholder);
 770  
 771              this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
 772              this.containers[innermostIndex].containerCache.over = 1;
 773          }
 774  
 775  
 776      },
 777  
 778      _createHelper: function(event) {
 779  
 780          var o = this.options;
 781          var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper == 'clone' ? this.currentItem.clone() : this.currentItem);
 782  
 783          if(!helper.parents('body').length) //Add the helper to the DOM if that didn't happen already
 784              $(o.appendTo != 'parent' ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
 785  
 786          if(helper[0] == this.currentItem[0])
 787              this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
 788  
 789          if(helper[0].style.width == '' || o.forceHelperSize) helper.width(this.currentItem.width());
 790          if(helper[0].style.height == '' || o.forceHelperSize) helper.height(this.currentItem.height());
 791  
 792          return helper;
 793  
 794      },
 795  
 796      _adjustOffsetFromHelper: function(obj) {
 797          if (typeof obj == 'string') {
 798              obj = obj.split(' ');
 799          }
 800          if ($.isArray(obj)) {
 801              obj = {left: +obj[0], top: +obj[1] || 0};
 802          }
 803          if ('left' in obj) {
 804              this.offset.click.left = obj.left + this.margins.left;
 805          }
 806          if ('right' in obj) {
 807              this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
 808          }
 809          if ('top' in obj) {
 810              this.offset.click.top = obj.top + this.margins.top;
 811          }
 812          if ('bottom' in obj) {
 813              this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
 814          }
 815      },
 816  
 817      _getParentOffset: function() {
 818  
 819  
 820          //Get the offsetParent and cache its position
 821          this.offsetParent = this.helper.offsetParent();
 822          var po = this.offsetParent.offset();
 823  
 824          // This is a special case where we need to modify a offset calculated on start, since the following happened:
 825          // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
 826          // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
 827          //    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
 828          if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
 829              po.left += this.scrollParent.scrollLeft();
 830              po.top += this.scrollParent.scrollTop();
 831          }
 832  
 833          if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
 834          || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.ui.ie)) //Ugly IE fix
 835              po = { top: 0, left: 0 };
 836  
 837          return {
 838              top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
 839              left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
 840          };
 841  
 842      },
 843  
 844      _getRelativeOffset: function() {
 845  
 846          if(this.cssPosition == "relative") {
 847              var p = this.currentItem.position();
 848              return {
 849                  top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
 850                  left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
 851              };
 852          } else {
 853              return { top: 0, left: 0 };
 854          }
 855  
 856      },
 857  
 858      _cacheMargins: function() {
 859          this.margins = {
 860              left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
 861              top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
 862          };
 863      },
 864  
 865      _cacheHelperProportions: function() {
 866          this.helperProportions = {
 867              width: this.helper.outerWidth(),
 868              height: this.helper.outerHeight()
 869          };
 870      },
 871  
 872      _setContainment: function() {
 873  
 874          var o = this.options;
 875          if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
 876          if(o.containment == 'document' || o.containment == 'window') this.containment = [
 877              0 - this.offset.relative.left - this.offset.parent.left,
 878              0 - this.offset.relative.top - this.offset.parent.top,
 879              $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
 880              ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
 881          ];
 882  
 883          if(!(/^(document|window|parent)$/).test(o.containment)) {
 884              var ce = $(o.containment)[0];
 885              var co = $(o.containment).offset();
 886              var over = ($(ce).css("overflow") != 'hidden');
 887  
 888              this.containment = [
 889                  co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
 890                  co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
 891                  co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
 892                  co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
 893              ];
 894          }
 895  
 896      },
 897  
 898      _convertPositionTo: function(d, pos) {
 899  
 900          if(!pos) pos = this.position;
 901          var mod = d == "absolute" ? 1 : -1;
 902          var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
 903  
 904          return {
 905              top: (
 906                  pos.top                                                                    // The absolute mouse position
 907                  + this.offset.relative.top * mod                                        // Only for relative positioned nodes: Relative offset from element to offset parent
 908                  + this.offset.parent.top * mod                                            // The offsetParent's offset without borders (offset + border)
 909                  - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
 910              ),
 911              left: (
 912                  pos.left                                                                // The absolute mouse position
 913                  + this.offset.relative.left * mod                                        // Only for relative positioned nodes: Relative offset from element to offset parent
 914                  + this.offset.parent.left * mod                                            // The offsetParent's offset without borders (offset + border)
 915                  - ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
 916              )
 917          };
 918  
 919      },
 920  
 921      _generatePosition: function(event) {
 922  
 923          var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
 924  
 925          // This is another very weird special case that only happens for relative elements:
 926          // 1. If the css position is relative
 927          // 2. and the scroll parent is the document or similar to the offset parent
 928          // we have to refresh the relative offset during the scroll so there are no jumps
 929          if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
 930              this.offset.relative = this._getRelativeOffset();
 931          }
 932  
 933          var pageX = event.pageX;
 934          var pageY = event.pageY;
 935  
 936          /*
 937           * - Position constraining -
 938           * Constrain the position to a mix of grid, containment.
 939           */
 940  
 941          if(this.originalPosition) { //If we are not dragging yet, we won't check for options
 942  
 943              if(this.containment) {
 944                  if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
 945                  if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
 946                  if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
 947                  if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
 948              }
 949  
 950              if(o.grid) {
 951                  var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
 952                  pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
 953  
 954                  var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
 955                  pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
 956              }
 957  
 958          }
 959  
 960          return {
 961              top: (
 962                  pageY                                                                // The absolute mouse position
 963                  - this.offset.click.top                                                    // Click offset (relative to the element)
 964                  - this.offset.relative.top                                                // Only for relative positioned nodes: Relative offset from element to offset parent
 965                  - this.offset.parent.top                                                // The offsetParent's offset without borders (offset + border)
 966                  + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
 967              ),
 968              left: (
 969                  pageX                                                                // The absolute mouse position
 970                  - this.offset.click.left                                                // Click offset (relative to the element)
 971                  - this.offset.relative.left                                                // Only for relative positioned nodes: Relative offset from element to offset parent
 972                  - this.offset.parent.left                                                // The offsetParent's offset without borders (offset + border)
 973                  + ( ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
 974              )
 975          };
 976  
 977      },
 978  
 979      _rearrange: function(event, i, a, hardRefresh) {
 980  
 981          a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction == 'down' ? i.item[0] : i.item[0].nextSibling));
 982  
 983          //Various things done here to improve the performance:
 984          // 1. we create a setTimeout, that calls refreshPositions
 985          // 2. on the instance, we have a counter variable, that get's higher after every append
 986          // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
 987          // 4. this lets only the last addition to the timeout stack through
 988          this.counter = this.counter ? ++this.counter : 1;
 989          var counter = this.counter;
 990  
 991          this._delay(function() {
 992              if(counter == this.counter) this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
 993          });
 994  
 995      },
 996  
 997      _clear: function(event, noPropagation) {
 998  
 999          this.reverting = false;
1000          // We delay all events that have to be triggered to after the point where the placeholder has been removed and
1001          // everything else normalized again
1002          var delayedTriggers = [];
1003  
1004          // We first have to update the dom position of the actual currentItem
1005          // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
1006          if(!this._noFinalSort && this.currentItem.parent().length) this.placeholder.before(this.currentItem);
1007          this._noFinalSort = null;
1008  
1009          if(this.helper[0] == this.currentItem[0]) {
1010              for(var i in this._storedCSS) {
1011                  if(this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
1012              }
1013              this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
1014          } else {
1015              this.currentItem.show();
1016          }
1017  
1018          if(this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
1019          if((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
1020  
1021          // Check if the items Container has Changed and trigger appropriate
1022          // events.
1023          if (this !== this.currentContainer) {
1024              if(!noPropagation) {
1025                  delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
1026                  delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
1027                  delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
1028              }
1029          }
1030  
1031  
1032          //Post events to containers
1033          for (var i = this.containers.length - 1; i >= 0; i--){
1034              if(!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
1035              if(this.containers[i].containerCache.over) {
1036                  delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
1037                  this.containers[i].containerCache.over = 0;
1038              }
1039          }
1040  
1041          //Do what was originally in plugins
1042          if(this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
1043          if(this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
1044          if(this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index
1045  
1046          this.dragging = false;
1047          if(this.cancelHelperRemoval) {
1048              if(!noPropagation) {
1049                  this._trigger("beforeStop", event, this._uiHash());
1050                  for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1051                  this._trigger("stop", event, this._uiHash());
1052              }
1053  
1054              this.fromOutside = false;
1055              return false;
1056          }
1057  
1058          if(!noPropagation) this._trigger("beforeStop", event, this._uiHash());
1059  
1060          //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
1061          this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
1062  
1063          if(this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;
1064  
1065          if(!noPropagation) {
1066              for (var i=0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
1067              this._trigger("stop", event, this._uiHash());
1068          }
1069  
1070          this.fromOutside = false;
1071          return true;
1072  
1073      },
1074  
1075      _trigger: function() {
1076          if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
1077              this.cancel();
1078          }
1079      },
1080  
1081      _uiHash: function(_inst) {
1082          var inst = _inst || this;
1083          return {
1084              helper: inst.helper,
1085              placeholder: inst.placeholder || $([]),
1086              position: inst.position,
1087              originalPosition: inst.originalPosition,
1088              offset: inst.positionAbs,
1089              item: inst.currentItem,
1090              sender: _inst ? _inst.element : null
1091          };
1092      }
1093  
1094  });
1095  
1096  })(jQuery);


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1