diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index ef454ab..c0e68c9 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -3,3 +3,15 @@ Before you create a issue, check it: 1. Try [dev](https://github.com/RubaXa/Sortable/tree/dev/)-branch, perhaps the problem has been solved; 2. [Use the search](https://github.com/RubaXa/Sortable/search?q=problem), maybe already have an answer; 3. If not found, create example on [jsbin.com (draft)](http://jsbin.com/zunibaxada/1/edit?html,js,output) and describe the problem. + +Bindings: + - Angular + - 2.0+: https://github.com/SortableJS/angular-sortablejs/issues + - legacy: https://github.com/SortableJS/angular-legacy-sortablejs/issues + - React + - ES2015+: https://github.com/SortableJS/react-sortablejs/issues + - mixin: https://github.com/SortableJS/react-mixin-sortablejs/issues + - Polymer: https://github.com/SortableJS/polymer-sortablejs/issues + - Knockout: https://github.com/SortableJS/knockout-sortablejs/issues + - Meteor: https://github.com/SortableJS/meteor-sortablejs/issues + diff --git a/README.md b/README.md index 639448f..cb7b035 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,13 @@ Demo: http://rubaxa.github.io/Sortable/ * Smart auto-scrolling * Built using native HTML5 drag and drop API * Supports - * [Meteor](https://github.com/SortableJS/meteor) - * [AngularJS](#ng) - * [React](#react) + * [Meteor](https://github.com/SortableJS/meteor-sortablejs) + * AngularJS + * [2.0+](https://github.com/SortableJS/angular-sortablejs) + * [1.*](https://github.com/SortableJS/angular-legacy-sortablejs) + * React + * [ES2015+](https://github.com/SortableJS/react-sortablejs) + * [Mixin](https://github.com/SortableJS/react-mixin-sortablejs) * [Knockout](https://github.com/SortableJS/knockout-sortablejs) * [Polymer](https://github.com/SortableJS/polymer-sortablejs) * Supports any CSS library, e.g. [Bootstrap](#bs) @@ -387,193 +391,6 @@ The speed at which the window should scroll once the mouse pointer gets within t --- - -### Support AngularJS -Include [ng-sortable.js](ng-sortable.js) - -Demo: http://jsbin.com/naduvo/1/edit?html,js,output - -```html -
- - - - - -
-``` - - -```js -angular.module('myApp', ['ng-sortable']) - .controller('demo', ['$scope', function ($scope) { - $scope.items = ['item 1', 'item 2']; - $scope.foo = ['foo 1', '..']; - $scope.bar = ['bar 1', '..']; - $scope.barConfig = { - group: 'foobar', - animation: 150, - onSort: function (/** ngSortEvent */evt){ - // @see https://github.com/RubaXa/Sortable/blob/master/ng-sortable.js#L18-L24 - } - }; - }]); -``` - - ---- - - - -### Support React -Include [react-sortable-mixin.js](react-sortable-mixin.js). -See [more options](react-sortable-mixin.js#L26). - - -```jsx -var SortableList = React.createClass({ - mixins: [SortableMixin], - - getInitialState: function() { - return { - items: ['Mixin', 'Sortable'] - }; - }, - - handleSort: function (/** Event */evt) { /*..*/ }, - - render: function() { - return - } -}); - -ReactDOM.render(, document.body); - - -// -// Groups -// -var AllUsers = React.createClass({ - mixins: [SortableMixin], - - sortableOptions: { - ref: "user", - group: "shared", - model: "users" - }, - - getInitialState: function() { - return { users: ['Abbi', 'Adela', 'Bud', 'Cate', 'Davis', 'Eric'] }; - }, - - render: function() { - return (
-

Users

- -
- ); - } -}); - -var ApprovedUsers = React.createClass({ - mixins: [SortableMixin], - sortableOptions: { group: "shared" }, - - getInitialState: function() { - return { items: ['Hal', 'Judy'] }; - }, - - render: function() { - return - } -}); - -ReactDOM.render(
- -
- -
, document.body); -``` - -### Support React ES2015 / TypeScript syntax -As mixins are not supported in ES2015 / TypeScript syntax here is example of ES2015 ref based implementation. -Using refs is the preferred (by facebook) "escape hatch" to underlaying DOM nodes: [React: The ref Callback Attribute](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) - -```js -import * as React from "react"; -import Sortable from 'sortablejs'; - -export class SortableExampleEsnext extends React.Component { - - sortableContainersDecorator = (componentBackingInstance) => { - // check if backing instance not null - if (componentBackingInstance) { - let options = { - handle: ".group-title" // Restricts sort start click/touch to the specified element - }; - Sortable.create(componentBackingInstance, options); - } - }; - - sortableGroupDecorator = (componentBackingInstance) => { - // check if backing instance not null - if (componentBackingInstance) { - let options = { - draggable: "div", // Specifies which items inside the element should be sortable - group: "shared" - }; - Sortable.create(componentBackingInstance, options); - } - }; - - render() { - return ( -
-
-

Group 1

-
-
Swap them around
-
Swap us around
-
Swap things around
-
Swap everything around
-
-
-
-

Group 2

-
-
Swap them around
-
Swap us around
-
Swap things around
-
Swap everything around
-
-
-
- ); - } -} -``` - ---- - - ### Method diff --git a/bower.json b/bower.json index a21c6ec..cd27f02 100644 --- a/bower.json +++ b/bower.json @@ -1,9 +1,7 @@ { "name": "Sortable", "main": [ - "Sortable.js", - "ng-sortable.js", - "react-sortable-mixin.js" + "Sortable.js" ], "homepage": "http://rubaxa.github.io/Sortable/", "authors": [ diff --git a/ng-sortable.js b/ng-sortable.js deleted file mode 100644 index d830675..0000000 --- a/ng-sortable.js +++ /dev/null @@ -1,210 +0,0 @@ -/** - * @author RubaXa - * @licence MIT - */ -(function (factory) { - 'use strict'; - - if (typeof define === 'function' && define.amd) { - define(['angular', './Sortable'], factory); - } - else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { - require('angular'); - factory(angular, require('./Sortable')); - module.exports = 'ng-sortable'; - } - else if (window.angular && window.Sortable) { - factory(angular, Sortable); - } -})(function (angular, Sortable) { - 'use strict'; - - - /** - * @typedef {Object} ngSortEvent - * @property {*} model List item - * @property {Object|Array} models List of items - * @property {number} oldIndex before sort - * @property {number} newIndex after sort - */ - - var expando = 'Sortable:ng-sortable'; - - angular.module('ng-sortable', []) - .constant('ngSortableVersion', '0.4.0') - .constant('ngSortableConfig', {}) - .directive('ngSortable', ['$parse', 'ngSortableConfig', function ($parse, ngSortableConfig) { - var removed, - nextSibling; - - function getNgRepeatExpression(node) { - return node.getAttribute('ng-repeat') || node.getAttribute('data-ng-repeat') || node.getAttribute('x-ng-repeat'); - } - - // Export - return { - restrict: 'AC', - scope: { ngSortable: "=?" }, - priority: 1001, - compile: function ($element, $attr) { - - var ngRepeat = [].filter.call($element[0].childNodes, function (node) { - return node.nodeType === Node.ELEMENT_NODE && getNgRepeatExpression(node); - })[0]; - - if (!ngRepeat) { - return; - } - - var match = getNgRepeatExpression(ngRepeat) - .match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); - - if (!match) { - return; - } - - var rhs = match[2]; - - return function postLink(scope, $el) { - var itemsExpr = $parse(rhs); - var getSource = function getSource() { - return itemsExpr(scope.$parent) || []; - }; - - - var el = $el[0], - options = angular.extend(scope.ngSortable || {}, ngSortableConfig), - watchers = [], - offDestroy, - sortable - ; - - el[expando] = getSource; - - function _emitEvent(/**Event*/evt, /*Mixed*/item) { - var name = 'on' + evt.type.charAt(0).toUpperCase() + evt.type.substr(1); - var source = getSource(); - - /* jshint expr:true */ - options[name] && options[name]({ - model: item || source[evt.newIndex], - models: source, - oldIndex: evt.oldIndex, - newIndex: evt.newIndex, - originalEvent: evt - }); - } - - - function _sync(/**Event*/evt) { - var items = getSource(); - - if (!items) { - // Without ng-repeat - return; - } - - var oldIndex = evt.oldIndex, - newIndex = evt.newIndex; - - if (el !== evt.from) { - var prevItems = evt.from[expando](); - - removed = prevItems[oldIndex]; - - if (evt.clone) { - removed = angular.copy(removed); - prevItems.splice(Sortable.utils.index(evt.clone, sortable.options.draggable), 0, prevItems.splice(oldIndex, 1)[0]); - evt.from.removeChild(evt.clone); - } - else { - prevItems.splice(oldIndex, 1); - } - - items.splice(newIndex, 0, removed); - - evt.from.insertBefore(evt.item, nextSibling); // revert element - } - else { - items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); - - // move ng-repeat comment node to right position - if (nextSibling.nodeType === Node.COMMENT_NODE) { - evt.from.insertBefore(nextSibling, evt.item.nextSibling); - } - } - - scope.$apply(); - } - - function _destroy() { - offDestroy(); - - angular.forEach(watchers, function (/** Function */unwatch) { - unwatch(); - }); - - sortable.destroy(); - - el[expando] = null; - el = null; - watchers = null; - sortable = null; - nextSibling = null; - } - - - // Initialization - sortable = Sortable.create(el, Object.keys(options).reduce(function (opts, name) { - opts[name] = opts[name] || options[name]; - return opts; - }, { - onStart: function (/**Event*/evt) { - nextSibling = evt.from === evt.item.parentNode ? evt.item.nextSibling : evt.clone.nextSibling; - _emitEvent(evt); - scope.$apply(); - }, - onEnd: function (/**Event*/evt) { - _emitEvent(evt, removed); - scope.$apply(); - }, - onAdd: function (/**Event*/evt) { - _sync(evt); - _emitEvent(evt, removed); - scope.$apply(); - }, - onUpdate: function (/**Event*/evt) { - _sync(evt); - _emitEvent(evt); - }, - onRemove: function (/**Event*/evt) { - _emitEvent(evt, removed); - }, - onSort: function (/**Event*/evt) { - _emitEvent(evt); - } - })); - - // Create watchers for `options` - angular.forEach([ - 'sort', 'disabled', 'draggable', 'handle', 'animation', 'group', 'ghostClass', 'filter', - 'onStart', 'onEnd', 'onAdd', 'onUpdate', 'onRemove', 'onSort', 'onMove', 'onClone', 'setData' - ], function (name) { - watchers.push(scope.$watch('ngSortable.' + name, function (value) { - if (value !== void 0) { - options[name] = value; - - if (!/^on[A-Z]/.test(name)) { - sortable.option(name, value); - } - } - })); - }); - - offDestroy = scope.$on('$destroy', _destroy); - - } - } - }; - }]); -}); diff --git a/react-sortable-mixin.js b/react-sortable-mixin.js deleted file mode 100644 index 68792ea..0000000 --- a/react-sortable-mixin.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * @author RubaXa - * @licence MIT - */ - -(function (factory) { - 'use strict'; - - if (typeof module != 'undefined' && typeof module.exports != 'undefined') { - module.exports = factory(require('./Sortable')); - } - else if (typeof define === 'function' && define.amd) { - define(['./Sortable'], factory); - } - else { - /* jshint sub:true */ - window['SortableMixin'] = factory(Sortable); - } -})(function (/** Sortable */Sortable) { - 'use strict'; - - var _nextSibling; - - var _activeComponent; - - var _defaultOptions = { - ref: 'list', - model: 'items', - - animation: 100, - onStart: 'handleStart', - onEnd: 'handleEnd', - onAdd: 'handleAdd', - onUpdate: 'handleUpdate', - onRemove: 'handleRemove', - onSort: 'handleSort', - onFilter: 'handleFilter', - onMove: 'handleMove', - onClone: 'handleClone' - }; - - - function _getModelName(component) { - return component.sortableOptions && component.sortableOptions.model || _defaultOptions.model; - } - - - function _getModelItems(component) { - var name = _getModelName(component), - items = component.state && component.state[name] || component.props[name]; - - return items.slice(); - } - - - function _extend(dst, src) { - for (var key in src) { - if (src.hasOwnProperty(key)) { - dst[key] = src[key]; - } - } - - return dst; - } - - - /** - * Simple and easy mixin-wrapper for rubaxa/Sortable library, in order to - * make reorderable drag-and-drop lists on modern browsers and touch devices. - * - * @mixin - */ - var SortableMixin = { - sortableMixinVersion: '0.1.1', - - - /** - * @type {Sortable} - * @private - */ - _sortableInstance: null, - - - componentDidMount: function () { - var DOMNode, options = _extend(_extend({}, _defaultOptions), this.sortableOptions || {}), - copyOptions = _extend({}, options), - - emitEvent = function (/** string */type, /** Event */evt) { - var method = options[type]; - if (method && typeof method === "string") { - method = this[method]; - } - method && typeof method === "function" && method.call(this, evt, this._sortableInstance); - }.bind(this); - - - // Bind callbacks so that "this" refers to the component - 'onStart onEnd onAdd onSort onUpdate onRemove onFilter onMove'.split(' ').forEach(function (/** string */name) { - copyOptions[name] = function (evt) { - if (name === 'onStart') { - _nextSibling = evt.item.nextElementSibling; - _activeComponent = this; - } - else if (name === 'onAdd' || name === 'onUpdate') { - evt.from.insertBefore(evt.item, _nextSibling); - - var newState = {}, - remoteState = {}, - oldIndex = evt.oldIndex, - newIndex = evt.newIndex, - items = _getModelItems(this), - remoteItems, - item; - - if (name === 'onAdd') { - remoteItems = _getModelItems(_activeComponent); - item = remoteItems.splice(oldIndex, 1)[0]; - items.splice(newIndex, 0, item); - - remoteState[_getModelName(_activeComponent)] = remoteItems; - } - else { - items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); - } - - newState[_getModelName(this)] = items; - - if (copyOptions.stateHandler) { - this[copyOptions.stateHandler](newState); - } else { - this.setState(newState); - } - - (this !== _activeComponent) && _activeComponent.setState(remoteState); - } - - setTimeout(function () { - emitEvent(name, evt); - }, 0); - }.bind(this); - }, this); - - DOMNode = typeof this.getDOMNode === 'function' ? (this.refs[options.ref] || this).getDOMNode() : this.refs[options.ref] || this; - - /** @namespace this.refs — http://facebook.github.io/react/docs/more-about-refs.html */ - this._sortableInstance = Sortable.create(DOMNode, copyOptions); - }, - - componentWillReceiveProps: function (nextProps) { - var newState = {}, - modelName = _getModelName(this), - items = nextProps[modelName]; - - if (items) { - newState[modelName] = items; - this.setState(newState); - } - }, - - componentWillUnmount: function () { - this._sortableInstance.destroy(); - this._sortableInstance = null; - } - }; - - - // Export - return SortableMixin; -});