github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/public/js/nestable/jquery.nestable.js (about)

     1  /*!
     2   * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
     3   * Dual-licensed under the BSD or MIT licenses
     4   */
     5  ;(function($, window, document, undefined)
     6  {
     7      var hasTouch = 'ontouchstart' in window;
     8  
     9      /**
    10       * Detect CSS pointer-events property
    11       * events are normally disabled on the dragging element to avoid conflicts
    12       * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
    13       */
    14      var hasPointerEvents = (function()
    15      {
    16          var el    = document.createElement('div'),
    17              docEl = document.documentElement;
    18          if (!('pointerEvents' in el.style)) {
    19              return false;
    20          }
    21          el.style.pointerEvents = 'auto';
    22          el.style.pointerEvents = 'x';
    23          docEl.appendChild(el);
    24          var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
    25          docEl.removeChild(el);
    26          return !!supports;
    27      })();
    28  
    29      var eStart  = hasTouch ? 'touchstart'  : 'mousedown',
    30          eMove   = hasTouch ? 'touchmove'   : 'mousemove',
    31          eEnd    = hasTouch ? 'touchend'    : 'mouseup';
    32          eCancel = hasTouch ? 'touchcancel' : 'mouseup';
    33  
    34      var defaults = {
    35              listNodeName    : 'ol',
    36              itemNodeName    : 'li',
    37              rootClass       : 'dd',
    38              listClass       : 'dd-list',
    39              itemClass       : 'dd-item',
    40              dragClass       : 'dd-dragel',
    41              handleClass     : 'dd-handle',
    42              collapsedClass  : 'dd-collapsed',
    43              placeClass      : 'dd-placeholder',
    44              noDragClass     : 'dd-nodrag',
    45              emptyClass      : 'dd-empty',
    46              expandBtnHTML   : '<button data-action="expand" type="button">Expand</button>',
    47              collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
    48              group           : 0,
    49              maxDepth        : 5,
    50              threshold       : 20
    51          };
    52  
    53      function Plugin(element, options)
    54      {
    55          this.w  = $(window);
    56          this.el = $(element);
    57          this.options = $.extend({}, defaults, options);
    58          this.init();
    59      }
    60  
    61      Plugin.prototype = {
    62  
    63          init: function()
    64          {
    65              var list = this;
    66  
    67              list.reset();
    68  
    69              list.el.data('nestable-group', this.options.group);
    70  
    71              list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
    72  
    73              $.each(this.el.find(list.options.itemNodeName), function(k, el) {
    74                  list.setParent($(el));
    75              });
    76  
    77              list.el.on('click', 'button', function(e) {
    78                  if (list.dragEl || (!hasTouch && e.button !== 0)) {
    79                      return;
    80                  }
    81                  var target = $(e.currentTarget),
    82                      action = target.data('action'),
    83                      item   = target.parent(list.options.itemNodeName);
    84                  if (action === 'collapse') {
    85                      list.collapseItem(item);
    86                  }
    87                  if (action === 'expand') {
    88                      list.expandItem(item);
    89                  }
    90              });
    91  
    92              var onStartEvent = function(e)
    93              {
    94                  var handle = $(e.target);
    95                  if (!handle.hasClass(list.options.handleClass)) {
    96                      if (handle.closest('.' + list.options.noDragClass).length) {
    97                          return;
    98                      }
    99                      handle = handle.closest('.' + list.options.handleClass);
   100                  }
   101                  if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) {
   102                      return;
   103                  }
   104                  e.preventDefault();
   105                  list.dragStart(hasTouch ? e.touches[0] : e);
   106              };
   107  
   108              var onMoveEvent = function(e)
   109              {
   110                  if (list.dragEl) {
   111                      e.preventDefault();
   112                      list.dragMove(hasTouch ? e.touches[0] : e);
   113                  }
   114              };
   115  
   116              var onEndEvent = function(e)
   117              {
   118                  if (list.dragEl) {
   119                      e.preventDefault();
   120                      list.dragStop(hasTouch ? e.touches[0] : e);
   121                  }
   122              };
   123  
   124              if (hasTouch) {
   125                  list.el[0].addEventListener(eStart, onStartEvent, false);
   126                  window.addEventListener(eMove, onMoveEvent, false);
   127                  window.addEventListener(eEnd, onEndEvent, false);
   128                  window.addEventListener(eCancel, onEndEvent, false);
   129              } else {
   130                  list.el.on(eStart, onStartEvent);
   131                  list.w.on(eMove, onMoveEvent);
   132                  list.w.on(eEnd, onEndEvent);
   133              }
   134  
   135          },
   136  
   137          serialize: function()
   138          {
   139              var data,
   140                  depth = 0,
   141                  list  = this;
   142                  step  = function(level, depth)
   143                  {
   144                      var array = [ ],
   145                          items = level.children(list.options.itemNodeName);
   146                      items.each(function()
   147                      {
   148                          var li   = $(this),
   149                              item = $.extend({}, li.data()),
   150                              sub  = li.children(list.options.listNodeName);
   151                          if (sub.length) {
   152                              item.children = step(sub, depth + 1);
   153                          }
   154                          array.push(item);
   155                      });
   156                      return array;
   157                  };
   158              data = step(list.el.find(list.options.listNodeName).first(), depth);
   159              return data;
   160          },
   161  
   162          serialise: function()
   163          {
   164              return this.serialize();
   165          },
   166  
   167          reset: function()
   168          {
   169              this.mouse = {
   170                  offsetX   : 0,
   171                  offsetY   : 0,
   172                  startX    : 0,
   173                  startY    : 0,
   174                  lastX     : 0,
   175                  lastY     : 0,
   176                  nowX      : 0,
   177                  nowY      : 0,
   178                  distX     : 0,
   179                  distY     : 0,
   180                  dirAx     : 0,
   181                  dirX      : 0,
   182                  dirY      : 0,
   183                  lastDirX  : 0,
   184                  lastDirY  : 0,
   185                  distAxX   : 0,
   186                  distAxY   : 0
   187              };
   188              this.moving     = false;
   189              this.dragEl     = null;
   190              this.dragRootEl = null;
   191              this.dragDepth  = 0;
   192              this.hasNewRoot = false;
   193              this.pointEl    = null;
   194          },
   195  
   196          expandItem: function(li)
   197          {
   198              li.removeClass(this.options.collapsedClass);
   199              li.children('[data-action="expand"]').hide();
   200              li.children('[data-action="collapse"]').show();
   201              li.children(this.options.listNodeName).show();
   202          },
   203  
   204          collapseItem: function(li)
   205          {
   206              var lists = li.children(this.options.listNodeName);
   207              if (lists.length) {
   208                  li.addClass(this.options.collapsedClass);
   209                  li.children('[data-action="collapse"]').hide();
   210                  li.children('[data-action="expand"]').show();
   211                  li.children(this.options.listNodeName).hide();
   212              }
   213          },
   214  
   215          expandAll: function()
   216          {
   217              var list = this;
   218              list.el.find(list.options.itemNodeName).each(function() {
   219                  list.expandItem($(this));
   220              });
   221          },
   222  
   223          collapseAll: function()
   224          {
   225              var list = this;
   226              list.el.find(list.options.itemNodeName).each(function() {
   227                  list.collapseItem($(this));
   228              });
   229          },
   230  
   231          setParent: function(li)
   232          {
   233              if (li.children(this.options.listNodeName).length) {
   234                  li.prepend($(this.options.expandBtnHTML));
   235                  li.prepend($(this.options.collapseBtnHTML));
   236              }
   237              li.children('[data-action="expand"]').hide();
   238          },
   239  
   240          unsetParent: function(li)
   241          {
   242              li.removeClass(this.options.collapsedClass);
   243              li.children('[data-action]').remove();
   244              li.children(this.options.listNodeName).remove();
   245          },
   246  
   247          dragStart: function(e)
   248          {
   249              var mouse    = this.mouse,
   250                  target   = $(e.target),
   251                  dragItem = target.closest(this.options.itemNodeName);
   252  
   253              this.placeEl.css('height', dragItem.height());
   254  
   255              mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
   256              mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
   257              mouse.startX = mouse.lastX = e.pageX;
   258              mouse.startY = mouse.lastY = e.pageY;
   259  
   260              this.dragRootEl = this.el;
   261  
   262              this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
   263              this.dragEl.css('width', dragItem.width());
   264  
   265              // fix for zepto.js
   266              //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
   267              dragItem.after(this.placeEl);
   268              dragItem[0].parentNode.removeChild(dragItem[0]);
   269              dragItem.appendTo(this.dragEl);
   270  
   271              $(document.body).append(this.dragEl);
   272              this.dragEl.css({
   273                  'left' : e.pageX - mouse.offsetX,
   274                  'top'  : e.pageY - mouse.offsetY
   275              });
   276              // total depth of dragging item
   277              var i, depth,
   278                  items = this.dragEl.find(this.options.itemNodeName);
   279              for (i = 0; i < items.length; i++) {
   280                  depth = $(items[i]).parents(this.options.listNodeName).length;
   281                  if (depth > this.dragDepth) {
   282                      this.dragDepth = depth;
   283                  }
   284              }
   285          },
   286  
   287          dragStop: function(e)
   288          {
   289              // fix for zepto.js
   290              //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
   291              var el = this.dragEl.children(this.options.itemNodeName).first();
   292              el[0].parentNode.removeChild(el[0]);
   293              this.placeEl.replaceWith(el);
   294  
   295              this.dragEl.remove();
   296              this.el.trigger('change');
   297              if (this.hasNewRoot) {
   298                  this.dragRootEl.trigger('change');
   299              }
   300              this.reset();
   301          },
   302  
   303          dragMove: function(e)
   304          {
   305              var list, parent, prev, next, depth,
   306                  opt   = this.options,
   307                  mouse = this.mouse;
   308  
   309              this.dragEl.css({
   310                  'left' : e.pageX - mouse.offsetX,
   311                  'top'  : e.pageY - mouse.offsetY
   312              });
   313  
   314              // mouse position last events
   315              mouse.lastX = mouse.nowX;
   316              mouse.lastY = mouse.nowY;
   317              // mouse position this events
   318              mouse.nowX  = e.pageX;
   319              mouse.nowY  = e.pageY;
   320              // distance mouse moved between events
   321              mouse.distX = mouse.nowX - mouse.lastX;
   322              mouse.distY = mouse.nowY - mouse.lastY;
   323              // direction mouse was moving
   324              mouse.lastDirX = mouse.dirX;
   325              mouse.lastDirY = mouse.dirY;
   326              // direction mouse is now moving (on both axis)
   327              mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
   328              mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
   329              // axis mouse is now moving on
   330              var newAx   = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
   331  
   332              // do nothing on first move
   333              if (!mouse.moving) {
   334                  mouse.dirAx  = newAx;
   335                  mouse.moving = true;
   336                  return;
   337              }
   338  
   339              // calc distance moved on this axis (and direction)
   340              if (mouse.dirAx !== newAx) {
   341                  mouse.distAxX = 0;
   342                  mouse.distAxY = 0;
   343              } else {
   344                  mouse.distAxX += Math.abs(mouse.distX);
   345                  if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
   346                      mouse.distAxX = 0;
   347                  }
   348                  mouse.distAxY += Math.abs(mouse.distY);
   349                  if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
   350                      mouse.distAxY = 0;
   351                  }
   352              }
   353              mouse.dirAx = newAx;
   354  
   355              /**
   356               * move horizontal
   357               */
   358              if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
   359                  // reset move distance on x-axis for new phase
   360                  mouse.distAxX = 0;
   361                  prev = this.placeEl.prev(opt.itemNodeName);
   362                  // increase horizontal level if previous sibling exists and is not collapsed
   363                  if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
   364                      // cannot increase level when item above is collapsed
   365                      list = prev.find(opt.listNodeName).last();
   366                      // check if depth limit has reached
   367                      depth = this.placeEl.parents(opt.listNodeName).length;
   368                      if (depth + this.dragDepth <= opt.maxDepth) {
   369                          // create new sub-level if one doesn't exist
   370                          if (!list.length) {
   371                              list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
   372                              list.append(this.placeEl);
   373                              prev.append(list);
   374                              this.setParent(prev);
   375                          } else {
   376                              // else append to next level up
   377                              list = prev.children(opt.listNodeName).last();
   378                              list.append(this.placeEl);
   379                          }
   380                      }
   381                  }
   382                  // decrease horizontal level
   383                  if (mouse.distX < 0) {
   384                      // we can't decrease a level if an item preceeds the current one
   385                      next = this.placeEl.next(opt.itemNodeName);
   386                      if (!next.length) {
   387                          parent = this.placeEl.parent();
   388                          this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
   389                          if (!parent.children().length) {
   390                              this.unsetParent(parent.parent());
   391                          }
   392                      }
   393                  }
   394              }
   395  
   396              var isEmpty = false;
   397  
   398              // find list item under cursor
   399              if (!hasPointerEvents) {
   400                  this.dragEl[0].style.visibility = 'hidden';
   401              }
   402              this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
   403              if (!hasPointerEvents) {
   404                  this.dragEl[0].style.visibility = 'visible';
   405              }
   406              if (this.pointEl.hasClass(opt.handleClass)) {
   407                  this.pointEl = this.pointEl.parent(opt.itemNodeName);
   408              }
   409              if (this.pointEl.hasClass(opt.emptyClass)) {
   410                  isEmpty = true;
   411              }
   412              else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
   413                  return;
   414              }
   415  
   416              // find parent list of item under cursor
   417              var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
   418                  isNewRoot   = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
   419  
   420              /**
   421               * move vertical
   422               */
   423              if (!mouse.dirAx || isNewRoot || isEmpty) {
   424                  // check if groups match if dragging over new root
   425                  if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
   426                      return;
   427                  }
   428                  // check depth limit
   429                  depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
   430                  if (depth > opt.maxDepth) {
   431                      return;
   432                  }
   433                  var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
   434                      parent = this.placeEl.parent();
   435                  // if empty create new list to replace empty placeholder
   436                  if (isEmpty) {
   437                      list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
   438                      list.append(this.placeEl);
   439                      this.pointEl.replaceWith(list);
   440                  }
   441                  else if (before) {
   442                      this.pointEl.before(this.placeEl);
   443                  }
   444                  else {
   445                      this.pointEl.after(this.placeEl);
   446                  }
   447                  if (!parent.children().length) {
   448                      this.unsetParent(parent.parent());
   449                  }
   450                  if (!this.dragRootEl.find(opt.itemNodeName).length) {
   451                      this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
   452                  }
   453                  // parent root list has changed
   454                  if (isNewRoot) {
   455                      this.dragRootEl = pointElRoot;
   456                      this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
   457                  }
   458              }
   459          }
   460  
   461      };
   462  
   463      $.fn.nestable = function(params)
   464      {
   465          var lists  = this,
   466              retval = this;
   467  
   468          lists.each(function()
   469          {
   470              var plugin = $(this).data("nestable");
   471  
   472              if (!plugin) {
   473                  $(this).data("nestable", new Plugin(this, params));
   474                  $(this).data("nestable-id", new Date().getTime());
   475              } else {
   476                  if (typeof params === 'string' && typeof plugin[params] === 'function') {
   477                      retval = plugin[params]();
   478                  }
   479              }
   480          });
   481  
   482          return retval || lists;
   483      };
   484  
   485  })(window.jQuery || window.Zepto, window, document);