sortable.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. // Generated by CoffeeScript 2.0.0-beta4
  2. (function() {
  3. // ------------------------------------------------------------------------
  4. // 變數與常數設置
  5. // ------------------------------------------------------------------------
  6. // 模組名稱。
  7. var Attribute, ClassName, EVENT_NAMESPACE, Error, Event, MODULE_NAMESPACE, Metadata, Mode, NAME, Order, Selector, Settings;
  8. NAME = 'sortable';
  9. // 模組事件鍵名。
  10. EVENT_NAMESPACE = `.${NAME}`;
  11. // 模組命名空間。
  12. MODULE_NAMESPACE = `module-${NAME}`;
  13. // 滑鼠拖曳出外,回來可以點擊依然可以放到 pull 容器
  14. // 模組設定。
  15. Settings = {
  16. // 消音所有提示,甚至是錯誤訊息。
  17. silent: false,
  18. // 顯示除錯訊息。
  19. debug: true,
  20. // 監聽 DOM 結構異動並自動重整快取。
  21. observeChanges: true,
  22. // 指定的拖曳把手選擇器,設置為 `false` 則為整個元素皆可拖曳。
  23. handle: false,
  24. // 到拖曳開始之前必須按住的指定毫秒數,避免點擊成為不必要的拖曳。
  25. delay: 350,
  26. // 是否能在相同拖放排序內重新排序。
  27. sort: true,
  28. // 群組名稱,相同的名稱拖放排序清單可以交替其項目。
  29. group: false,
  30. // 此拖放排序的支援模式。(`all` 表示可拖放、`put` 表示僅可放入、`pull` 表示僅可移出、`all` 表示都可)
  31. mode: 'all',
  32. // 這個拖放排序是否已垂直清單為主,改為 `false` 會有利於水平清單。
  33. vertical: true,
  34. // 當拖拉開始時所會呼叫的回呼函式。
  35. onDragStart: (value) => {},
  36. // 當拖拉途中所會呼叫的回呼函式,間隔是 350 毫秒。
  37. onDrag: (value) => {},
  38. // 當拖拉結束並丟下元素時所會呼叫的回呼函式。
  39. onDrop: (value) => {},
  40. // 當放下被禁止(如:範圍外、被回呼函式拒絕)時所會呼叫的函式。
  41. onDeny: (value) => {},
  42. // 當放下時跟一開始沒有差異時所會呼叫的回呼函式。
  43. onCancel: (value) => {},
  44. // 當有變動(新增、移除、重新排序)時所會呼叫的回呼函式。
  45. onChange: (value) => {},
  46. // 當項目新增時所會呼叫的回呼函式,回傳 `false` 表示不接受此新增。
  47. onAdd: (value) => {},
  48. // 當項目被移出時所會呼叫的回呼函式。
  49. onRemove: (value) => {}
  50. };
  51. // 事件名稱。
  52. Event = {
  53. DRAGSTART: `dragstart${EVENT_NAMESPACE}`,
  54. DRAG: `drag${EVENT_NAMESPACE}`,
  55. DROP: `drop${EVENT_NAMESPACE}`,
  56. DENY: `deny${EVENT_NAMESPACE}`,
  57. CANCEL: `cancel${EVENT_NAMESPACE}`,
  58. CHANGE: `change${EVENT_NAMESPACE}`,
  59. ADD: `add${EVENT_NAMESPACE}`,
  60. REMOVE: `remove${EVENT_NAMESPACE}`,
  61. MOUSEMOVE: `mousemove${EVENT_NAMESPACE}`,
  62. MOUSEUP: `mouseup${EVENT_NAMESPACE}`,
  63. MOUSEDOWN: `mousedown${EVENT_NAMESPACE}`
  64. };
  65. // 中繼資料名稱。
  66. Metadata = {
  67. X_OFFSET: 'xOffset',
  68. Y_OFFSET: 'yOffset',
  69. ENABLE: 'enable'
  70. };
  71. // 模式 =
  72. Mode = {
  73. PULL: 'pull',
  74. PUT: 'put',
  75. ALL: 'all'
  76. };
  77. // 順序。
  78. Order = {
  79. AFTER: 'after',
  80. BEFORE: 'before'
  81. };
  82. // 元素標籤。
  83. Attribute = {
  84. GROUP: 'data-draggable-group',
  85. CONTAINER: 'data-draggable-container',
  86. DRAGGABLE: 'data-draggable',
  87. DRAGGING: 'data-draggable-dragging',
  88. VALUE: 'data-value',
  89. GHOST: 'data-draggable-ghost',
  90. PLACEHOLDER: 'data-draggable-placeholder',
  91. NATIVE_DRAGGABLE: 'draggable',
  92. HIDDEN: 'hidden'
  93. };
  94. // 樣式名稱。
  95. ClassName = {};
  96. // 選擇器名稱。
  97. Selector = {
  98. BODY: 'body',
  99. NATIVE_DRAGGABLE: `[${Attribute.NATIVE_DRAGGABLE}]`,
  100. DRAGGING: `[${Attribute.DRAGGING}]`,
  101. DRAGGABLE: `[${Attribute.DRAGGABLE}]`,
  102. GHOST: `[${Attribute.GHOST}]`,
  103. PLACEHOLDER: `[${Attribute.PLACEHOLDER}]`,
  104. CONTAINER: `[${Attribute.CONTAINER}]`,
  105. PLACEHOLDER: `[${Attribute.PLACEHOLDER}]`,
  106. GROUP: `[${Attribute.GROUP}]`,
  107. HIDDEN_DRAGGABLE: `[${Attribute.DRAGGABLE}][${Attribute.HIDDEN}]`,
  108. TRUE_DRAGGABLE: `[${Attribute.DRAGGABLE}]:not([${Attribute.HIDDEN}])`,
  109. DRAGGABLE_VALUE: (value) => {
  110. return `[${Attribute.VALUE}='${value}']`;
  111. }
  112. };
  113. // 錯誤訊息。
  114. Error = {};
  115. // ------------------------------------------------------------------------
  116. // 模組註冊
  117. // ------------------------------------------------------------------------
  118. ts.register({NAME, MODULE_NAMESPACE, Error, Settings}, ({$allModules, $this, element, debug, settings}) => {
  119. var $original, $placeholder, draggingInterval, draggingTimer, module;
  120. // ------------------------------------------------------------------------
  121. // 區域變數
  122. // ------------------------------------------------------------------------
  123. $original = ts();
  124. $placeholder = ts();
  125. draggingTimer = null;
  126. draggingInterval = 350;
  127. // ------------------------------------------------------------------------
  128. // 模組定義
  129. // ------------------------------------------------------------------------
  130. return module = {
  131. sort: (values) => {
  132. var i, len, value;
  133. for (i = 0, len = values.length; i < len; i++) {
  134. value = values[i];
  135. $this.find(Selector.DRAGGABLE_VALUE(value)).appendTo(element);
  136. }
  137. return $allModules;
  138. },
  139. enable: () => {
  140. $this.data(Metadata.ENABLE, true);
  141. return $allModules;
  142. },
  143. disable: () => {
  144. $this.data(Metadata.ENABLE, false);
  145. return $allModules;
  146. },
  147. start: {
  148. dragging: () => {
  149. draggingTimer = setInterval(() => {
  150. return module.trigger.drag();
  151. }, draggingInterval);
  152. return $original.attr(Attribute.DRAGGING, 'true');
  153. }
  154. },
  155. stop: {
  156. dragging: () => {
  157. return clearInterval(draggingTimer);
  158. }
  159. },
  160. reset: {
  161. dragging: () => {
  162. return ts(Selector.DRAGGING).removeAttr(Attribute.DRAGGING);
  163. }
  164. },
  165. hide: {
  166. original: () => {
  167. return $original.attr(Attribute.HIDDEN, 'hidden');
  168. }
  169. },
  170. unhide: {
  171. original: () => {
  172. return $(Selector.HIDDEN_DRAGGABLE).removeAttr(Attribute.HIDDEN);
  173. }
  174. },
  175. set: {
  176. group: (name) => {
  177. return $this.attr(Attribute.GROUP, settings.group);
  178. }
  179. },
  180. get: {
  181. $dragging: () => {
  182. return ts(Selector.DRAGGING);
  183. },
  184. $draggable: (element) => {
  185. return ts(element).closest(Selector.DRAGGABLE);
  186. },
  187. last: {
  188. $item: () => {
  189. return $this.find(Selector.DRAGGABLE).last();
  190. }
  191. },
  192. dragging: {
  193. element: () => {
  194. return ts(Selector.HIDDEN_DRAGGABLE).get();
  195. },
  196. value: () => {
  197. return ts(Selector.HIDDEN_DRAGGABLE).attr(Attribute.VALUE);
  198. }
  199. },
  200. container: (element) => {
  201. return ts(element).closest(Selector.CONTAINER);
  202. },
  203. group: {
  204. name: () => {
  205. return $this.attr(Attribute.GROUP);
  206. }
  207. },
  208. mode: () => {
  209. return settings.mode;
  210. },
  211. sort: () => {
  212. return settings.sort;
  213. },
  214. value: () => {
  215. var values;
  216. values = [];
  217. $this.find(Selector.DRAGGABLE).each(function() {
  218. var value;
  219. value = ts(this).attr(Attribute.VALUE);
  220. if (value) {
  221. return values.push(value);
  222. }
  223. });
  224. return values;
  225. }
  226. },
  227. is: {
  228. draggable: (element) => {
  229. return ts(element).attr(Attribute.DRAGGABLE) === 'true';
  230. },
  231. child: (element) => {
  232. return $this.contains(element);
  233. },
  234. sortable: () => {
  235. return settings.sort === true;
  236. },
  237. vertical: () => {
  238. return settings.vertical === true;
  239. },
  240. enable: () => {
  241. return $this.data(Metadata.ENABLE);
  242. },
  243. disable: () => {
  244. return !$this.data(Metadata.ENABLE);
  245. },
  246. same: {
  247. group: ($target) => {
  248. var $container, name;
  249. $container = module.get.container($target);
  250. if ($container.is(element)) {
  251. return true;
  252. }
  253. name = $container.sortable('get group name');
  254. return name !== null && name === module.get.group.name();
  255. },
  256. container: ($container) => {
  257. return $container.is(element);
  258. }
  259. },
  260. handle: (element) => {
  261. return ts(element).is(settings.handle);
  262. }
  263. },
  264. has: {
  265. dragging: () => {
  266. return $this.find(Selector.DRAGGING).exists();
  267. },
  268. placeholder: () => {
  269. return $this.find(Selector.PLACEHOLDER).exists();
  270. },
  271. item: () => {
  272. return $this.find(Selector.DRAGGABLE).exists();
  273. },
  274. handle: () => {
  275. return settings.handle !== false;
  276. }
  277. },
  278. create: {
  279. ghost: (x, y) => {
  280. return $original.clone().attr(Attribute.GHOST, 'true').data({
  281. [`${Metadata.X_OFFSET}`]: x - $original.rect().x,
  282. [`${Metadata.Y_OFFSET}`]: y - $original.rect().y
  283. }).css({
  284. width: $original.rect().width,
  285. height: $original.rect().height
  286. }).appendTo(Selector.BODY);
  287. },
  288. placeholder: () => {
  289. return $placeholder = $original.clone().attr(Attribute.PLACEHOLDER, 'true').removeAttr(Attribute.DRAGGING).appendTo(Selector.BODY);
  290. }
  291. },
  292. remove: {
  293. ghost: () => {
  294. return ts(Selector.GHOST).remove();
  295. },
  296. placeholder: () => {
  297. return ts(Selector.PLACEHOLDER).remove();
  298. }
  299. },
  300. move: {
  301. ghost: (x, y) => {
  302. var $ghost;
  303. $ghost = ts(Selector.GHOST);
  304. return $ghost.css({
  305. top: y - $ghost.data(Metadata.Y_OFFSET),
  306. left: x - $ghost.data(Metadata.X_OFFSET)
  307. });
  308. },
  309. placeholder: (x, y) => {
  310. var $container, $draggable, $last, $pointing, isAllOrSin, isTanOrCos, isVertical;
  311. $pointing = ts.fromPoint(x, y);
  312. $draggable = $pointing.closest(Selector.DRAGGABLE);
  313. $container = module.get.container($draggable);
  314. if ($draggable.exists()) {
  315. isAllOrSin = y - $draggable.rect().y < $draggable.rect().height / 2;
  316. isTanOrCos = x - $draggable.rect().x < $draggable.rect().width / 2;
  317. isVertical = $container.sortable('is vertical');
  318. if ((isAllOrSin && isVertical) || (isTanOrCos && !isVertical)) {
  319. $placeholder.insertBefore($draggable);
  320. } else {
  321. $placeholder.insertAfter($draggable);
  322. }
  323. return;
  324. }
  325. $container = module.get.container($pointing);
  326. $draggable = $container.find(Selector.TRUE_DRAGGABLE);
  327. if (!$container.exists()) {
  328. return;
  329. }
  330. if (!$draggable.exists()) {
  331. module.insert.placeholder($container);
  332. return;
  333. }
  334. $last = $draggable.last();
  335. if ($last.exists()) {
  336. return module.append.placeholder(Order.AFTER, $last);
  337. }
  338. },
  339. original: () => {
  340. return module.get.$dragging().insertAfter(ts(Selector.PLACEHOLDER));
  341. }
  342. },
  343. append: {
  344. placeholder: (order, to) => {
  345. switch (order) {
  346. case Order.BEFORE:
  347. return $placeholder.insertBefore(to);
  348. case Order.AFTER:
  349. return $placeholder.insertAfter(to);
  350. }
  351. }
  352. },
  353. insert: {
  354. placeholder: (to) => {
  355. return $placeholder.appendTo(to);
  356. }
  357. },
  358. trigger: {
  359. dragStart: () => {
  360. return $this.trigger(Event.DRAGSTART, module.get.dragging.element(), module.get.dragging.value());
  361. },
  362. drag: () => {
  363. return $this.trigger(Event.DRAG, module.get.dragging.element(), module.get.dragging.value());
  364. },
  365. drop: () => {
  366. return $this.trigger(Event.DROP, module.get.dragging.element(), module.get.dragging.value());
  367. },
  368. deny: () => {
  369. return $this.trigger(Event.DENY, module.get.dragging.element(), module.get.dragging.value());
  370. },
  371. cancel: () => {
  372. return $this.trigger(Event.CANCEL, module.get.dragging.element(), module.get.dragging.value());
  373. },
  374. change: () => {
  375. return $this.trigger(Event.CHANGE, module.get.dragging.element(), module.get.dragging.value());
  376. },
  377. add: (valueElement, value) => {
  378. debug('發生 ADD 事件', valueElement, value);
  379. return settings.onAdd.call(valueElement, value);
  380. },
  381. remove: () => {
  382. return $this.trigger(Event.REMOVE, module.get.dragging.element(), module.get.dragging.value());
  383. }
  384. },
  385. unbind: {
  386. mousemove: () => {
  387. return ts(Selector.BODY).off(Event.MOUSEMOVE);
  388. }
  389. },
  390. bind: {
  391. mousemove: () => {
  392. return ts(Selector.BODY).on(Event.MOUSEMOVE, (event) => {
  393. var $container, $element, $placeholderContainer, hasContainerItem, hasPlaceholder, isContainerSortable, isPullContainer, isPutContainer, isSameContainer, isSortable;
  394. debug('發生 MOUSEMOVE 事件', element, this);
  395. if (module.is.disable()) {
  396. return;
  397. }
  398. module.move.ghost(event.clientX, event.clientY);
  399. if (!module.has.dragging()) {
  400. return;
  401. }
  402. $element = ts.fromPoint(event.clientX, event.clientY);
  403. $container = module.get.container($element);
  404. if (!module.is.same.group($element)) {
  405. return;
  406. }
  407. if ($container.sortable('is disable')) {
  408. return;
  409. }
  410. hasPlaceholder = module.has.placeholder();
  411. isSortable = module.is.sortable();
  412. isPullContainer = $container.sortable('get mode') === Mode.PULL;
  413. isSameContainer = module.is.same.container($container);
  414. switch (false) {
  415. case !isPullContainer:
  416. return;
  417. case !(!isSortable && isSameContainer && hasPlaceholder):
  418. return;
  419. }
  420. $placeholderContainer = module.get.container($placeholder);
  421. hasContainerItem = $container.sortable('has item');
  422. isContainerSortable = $container.sortable('is sortable');
  423. isPutContainer = $placeholderContainer.sortable('get mode') === Mode.PUT;
  424. isSortable = $placeholderContainer.sortable('is sortable');
  425. switch (false) {
  426. case !(!isSameContainer && isPutContainer && isSortable):
  427. return;
  428. case !(!isSameContainer && !isContainerSortable):
  429. if (!hasContainerItem) {
  430. module.insert.placeholder($container);
  431. } else {
  432. module.append.placeholder(Order.AFTER, $container.sortable('get last $item'));
  433. }
  434. return;
  435. case !(!isSortable && isSameContainer && !hasPlaceholder):
  436. module.append.placeholder(Order.AFTER, $original);
  437. return;
  438. }
  439. return module.move.placeholder(event.clientX, event.clientY);
  440. });
  441. },
  442. events: () => {
  443. $this.on(Event.DRAGSTART, (event, context, value) => {
  444. debug('發生 DRAGSTART 事件', context, value);
  445. return settings.onDragStart.call(context, event, value);
  446. });
  447. $this.on(Event.DRAG, (event, context, value) => {
  448. debug('發生 DRAG 事件', context, value);
  449. return settings.onDrag.call(context, event, value);
  450. });
  451. $this.on(Event.DROP, (event, context, value) => {
  452. debug('發生 DROP 事件', context, value);
  453. return settings.onDrop.call(context, event, value);
  454. });
  455. $this.on(Event.DENY, (event, context, value) => {
  456. debug('發生 DENY 事件', context, value);
  457. return settings.onDeny.call(context, event, value);
  458. });
  459. $this.on(Event.CANCEL, (event, context, value) => {
  460. debug('發生 CANCEL 事件', context, value);
  461. return settings.onCancel.call(context, event, value);
  462. });
  463. $this.on(Event.CHANGE, (event, context, value) => {
  464. debug('發生 CHANGE 事件', context, value);
  465. return settings.onChange.call(context, event, value);
  466. });
  467. $this.on(Event.REMOVE, (event, context, value) => {
  468. debug('發生 REMOVE 事件', context, value);
  469. return settings.onRemove.call(context, event, value);
  470. });
  471. ts(Selector.BODY).on(Event.MOUSEDOWN, (event) => {
  472. var $target;
  473. debug('發生 MOUSEDOWN 事件', element, this);
  474. if (module.is.disable()) {
  475. return;
  476. }
  477. $target = ts(event.target);
  478. if (module.has.handle() && !module.is.handle($target)) {
  479. return;
  480. }
  481. if (!module.is.draggable($target)) {
  482. $target = module.get.$draggable($target);
  483. }
  484. switch (false) {
  485. case !!$target.exists():
  486. return;
  487. case !!module.is.child($target):
  488. return;
  489. case !(module.get.mode() === Mode.PUT && !module.is.sortable()):
  490. return;
  491. }
  492. $original = $target;
  493. module.trigger.dragStart();
  494. module.start.dragging();
  495. module.create.ghost(event.clientX, event.clientY);
  496. module.move.ghost(event.clientX, event.clientY);
  497. module.create.placeholder();
  498. module.move.placeholder(event.clientX, event.clientY);
  499. module.hide.original();
  500. return module.bind.mousemove();
  501. });
  502. return ts(Selector.BODY).on(Event.MOUSEUP, (event) => {
  503. var $container, newValue, oldValue;
  504. debug('發生 MOUSEUP 事件', element, this);
  505. if (module.is.disable()) {
  506. return;
  507. }
  508. module.stop.dragging();
  509. module.remove.ghost();
  510. module.unbind.mousemove();
  511. if (!module.has.dragging()) {
  512. return;
  513. }
  514. module.trigger.drop();
  515. $container = module.get.container(event.target);
  516. if ($container.exists() && module.is.same.container($container)) {
  517. oldValue = module.get.value().toString();
  518. module.move.original();
  519. newValue = module.get.value().toString();
  520. if (oldValue !== '' || newValue !== '') {
  521. if (oldValue === newValue) {
  522. module.trigger.cancel();
  523. } else {
  524. module.trigger.change();
  525. }
  526. }
  527. } else {
  528. if ($container.sortable('trigger add', module.get.dragging.element(), module.get.dragging.value())) {
  529. module.move.original();
  530. $container.sortable('trigger change');
  531. module.trigger.remove();
  532. module.trigger.change();
  533. } else {
  534. module.trigger.cancel();
  535. module.trigger.deny();
  536. }
  537. }
  538. module.remove.placeholder();
  539. module.unhide.original();
  540. return module.reset.dragging();
  541. });
  542. }
  543. },
  544. // ------------------------------------------------------------------------
  545. // 基礎方法
  546. // ------------------------------------------------------------------------
  547. initialize: () => {
  548. debug('初始化拖放排序', element);
  549. module.bind.events();
  550. module.enable();
  551. $this.attr(Attribute.CONTAINER, true).find(Selector.NATIVE_DRAGGABLE).removeAttr(Attribute.NATIVE_DRAGGABLE).attr(Attribute.DRAGGABLE, 'true');
  552. if (settings.group !== false) {
  553. return module.set.group(settings.group);
  554. }
  555. },
  556. instantiate: () => {
  557. return debug('實例化拖放排序', element);
  558. },
  559. refresh: () => {
  560. return $allModules;
  561. },
  562. destroy: () => {
  563. debug('摧毀拖放排序', element);
  564. $this.removeData(MODULE_NAMESPACE).off(EVENT_NAMESPACE);
  565. return $allModules;
  566. }
  567. };
  568. });
  569. }).call(this);