Skip to content

Commit d39273c

Browse files
committed
Initial commit
1 parent c8ccb5f commit d39273c

File tree

9 files changed

+361
-0
lines changed

9 files changed

+361
-0
lines changed

.babelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"presets": ["es2015", "stage-0", "react"]
3+
}

.eslintrc

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
"plugins": [
3+
"react"
4+
],
5+
"extends": "airbnb",
6+
"env": {
7+
"browser": true,
8+
"node": true,
9+
"es6": true
10+
},
11+
"parser": "babel-eslint",
12+
"parserOptions": {
13+
"ecmaVersion": 7,
14+
"sourceType": "module",
15+
"ecmaFeatures": {
16+
"experimentalObjectRestSpread": true,
17+
"jsx": true
18+
}
19+
},
20+
"rules": {
21+
"arrow-body-style": 0,
22+
"block-spacing": 2,
23+
"brace-style": [2, "1tbs"],
24+
"callback-return": [2, ["cb", "callback", "next"]],
25+
"camelcase": [2, { "properties": "never" }],
26+
"comma-spacing": 2,
27+
"comma-style": [2, "last"],
28+
"comma-dangle": [2, "never"],
29+
"consistent-return": 2,
30+
"curly": [2, "all"],
31+
"default-case": 2,
32+
"dot-notation": [2, { "allowKeywords": true }],
33+
"eol-last": 2,
34+
"eqeqeq": 2,
35+
"func-names": 0,
36+
"indent": [2, 4],
37+
"key-spacing": [2, {
38+
"beforeColon": false,
39+
"afterColon": true
40+
}],
41+
"max-len": [1, 160, 2, { "ignoreComments": true }],
42+
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
43+
"new-parens": 2,
44+
"no-alert": 2,
45+
"no-array-constructor": 2,
46+
"no-caller": 2,
47+
"no-cond-assign": [2, "except-parens"],
48+
"no-const-assign": 2,
49+
"no-console": [1, { "allow": ["assert", "warn", "error"]}],
50+
"no-else-return": 0,
51+
"no-lone-blocks": 0,
52+
"no-param-reassign": 0,
53+
"no-shadow": 0,
54+
"no-var": 1,
55+
"no-unused-expressions": [2, {
56+
"allowShortCircuit": true,
57+
"allowTernary": true
58+
}],
59+
"no-unused-vars": [1, {
60+
"vars": "local",
61+
"args": "none"
62+
}],
63+
"no-use-before-define": 0,
64+
"object-shorthand": 0,
65+
"one-var": 0,
66+
"one-var-declaration-per-line": 0,
67+
"prefer-const": 0,
68+
"prefer-template": 0,
69+
"quote-props": [0, "as-needed"],
70+
"quotes": [2, "single"],
71+
"space-before-function-paren": 0,
72+
"spaced-comment": 0,
73+
"vars-on-top": 0,
74+
75+
"react/jsx-boolean-value": 0,
76+
"react/jsx-curly-spacing": 1,
77+
"react/jsx-indent-props": [1, 4],
78+
"react/jsx-max-props-per-line": [1, { "maximum": 3 }],
79+
"react/jsx-no-bind": [1, {
80+
"ignoreRefs": false,
81+
"allowArrowFunctions": true,
82+
"allowBind": false
83+
}],
84+
"react/jsx-no-duplicate-props": 1,
85+
"react/jsx-no-undef": 1,
86+
"react/jsx-uses-react": 1,
87+
"react/jsx-uses-vars": 1,
88+
"react/no-danger": 1,
89+
"react/no-did-mount-set-state": 1,
90+
"react/no-did-update-set-state": 1,
91+
"react/no-multi-comp": [1, { "ignoreStateless": true }],
92+
"react/no-unknown-property": 1,
93+
"react/prefer-stateless-function": 1,
94+
"react/prop-types": [1, { "ignore": ["className"] }],
95+
"react/react-in-jsx-scope": 1,
96+
"react/require-extension": 1,
97+
"react/self-closing-comp": 1,
98+
"react/sort-comp": 0,
99+
"react/wrap-multilines": 1
100+
}
101+
}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
npm-debug.log
3+
/dist
4+
/coverage
5+
/.nyc_output

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
npm-debug.log
2+
/coverage
3+
/.nyc_output

babel-tap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
process.execPath = require.resolve('.bin/babel-node');
5+
require('tap/bin/run.js');

package.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "react-sortablejs",
3+
"version": "0.1.0",
4+
"description": "A higher order React component for Sortable.js",
5+
"main": "dist/react-sortable.js",
6+
"scripts": {
7+
"prepublish": "npm run build && npm test",
8+
"build": "webpack",
9+
"test": "./babel-tap --coverage test/*.js",
10+
"coveralls": "./babel-tap --coverage --coverage-report=text-lcov test/*.js | node_modules/.bin/coveralls"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/cheton/react-sortablejs.git"
15+
},
16+
"author": "Cheton Wu <cheton@gmail.com>",
17+
"license": "MIT",
18+
"bugs": {
19+
"url": "https://github.com/cheton/react-sortablejs/issues"
20+
},
21+
"homepage": "https://github.com/cheton/react-sortablejs",
22+
"keywords": [
23+
"react",
24+
"react-component",
25+
"sortable",
26+
"reorder",
27+
"drag",
28+
"mixin"
29+
],
30+
"peerDependencies": {
31+
"react": "^0.14.0",
32+
"react-dom": "^0.14.0",
33+
"sortablejs": "1.x"
34+
},
35+
"dependencies": {
36+
"lodash": "^4.6.1"
37+
},
38+
"devDependencies": {
39+
"babel": "^6.5.2",
40+
"babel-core": "^6.7.0",
41+
"babel-eslint": "^5.0.0",
42+
"babel-loader": "^6.2.4",
43+
"babel-preset-es2015": "^6.6.0",
44+
"babel-preset-react": "^6.5.0",
45+
"babel-preset-stage-0": "^6.5.0",
46+
"eslint": "~2.2.0",
47+
"eslint-config-airbnb": "^6.1.0",
48+
"eslint-loader": "^1.3.0",
49+
"eslint-plugin-react": "^4.2.1",
50+
"tap": "^5.7.0",
51+
"webpack": "^1.12.14"
52+
}
53+
}

src/index.jsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import extend from 'lodash/extend';
2+
import merge from 'lodash/merge';
3+
import React from 'react';
4+
import ReactDOM from 'react-dom';
5+
import Sortable from 'sortablejs';
6+
7+
const defaultOptions = {
8+
ref: 'list',
9+
model: 'items',
10+
onStart: 'handleStart',
11+
onEnd: 'handleEnd',
12+
onAdd: 'handleAdd',
13+
onUpdate: 'handleUpdate',
14+
onRemove: 'handleRemove',
15+
onSort: 'handleSort',
16+
onFilter: 'handleFilter',
17+
onMove: 'handleMove'
18+
};
19+
20+
let _nextSibling = null;
21+
let _activeWrapperComponent = null;
22+
23+
const refName = 'sortableComponent';
24+
25+
const getModelItems = (wrapperComponent) => {
26+
const model = wrapperComponent.sortableOptions.model;
27+
const sortableComponent = wrapperComponent.refs[refName];
28+
const { state = {}, props = {} } = sortableComponent;
29+
const items = state[model] || props[model] || [];
30+
return items.slice(); // returns a shallow copy of the items array
31+
};
32+
33+
const SortableMixin = (Component, sortableOptions = defaultOptions) => class extends React.Component {
34+
sortableInstance = null;
35+
sortableOptions = sortableOptions;
36+
37+
componentDidMount() {
38+
const wrapperComponent = this;
39+
const sortableComponent = wrapperComponent.refs[refName];
40+
const options = merge({}, defaultOptions, wrapperComponent.sortableOptions);
41+
const emitEvent = (type, evt) => {
42+
const methodName = options[type];
43+
const method = sortableComponent[methodName];
44+
method && method.call(sortableComponent, evt, wrapperComponent.sortableInstance);
45+
};
46+
47+
let copyOptions = extend({}, options);
48+
[ // Bind callbacks
49+
'onStart',
50+
'onEnd',
51+
'onAdd',
52+
'onSort',
53+
'onUpdate',
54+
'onRemove',
55+
'onFilter',
56+
'onMove'
57+
].forEach((name) => {
58+
copyOptions[name] = (evt) => {
59+
if (name === 'onStart') {
60+
_nextSibling = evt.item.nextElementSibling;
61+
_activeWrapperComponent = wrapperComponent;
62+
} else if (name === 'onAdd' || name === 'onUpdate') {
63+
evt.from.insertBefore(evt.item, _nextSibling);
64+
65+
const oldIndex = evt.oldIndex;
66+
const newIndex = evt.newIndex;
67+
let newState = {};
68+
let remoteState = {};
69+
let items = getModelItems(wrapperComponent);
70+
71+
if (name === 'onAdd') {
72+
let remoteItems = getModelItems(_activeWrapperComponent);
73+
let item = remoteItems.splice(oldIndex, 1)[0];
74+
items.splice(newIndex, 0, item);
75+
76+
remoteState[_activeWrapperComponent.sortableOptions.model] = remoteItems;
77+
} else {
78+
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]);
79+
}
80+
81+
newState[wrapperComponent.sortableOptions.model] = items;
82+
83+
if (copyOptions.stateHandler) {
84+
sortableComponent[copyOptions.stateHandler](newState);
85+
} else {
86+
sortableComponent.setState(newState);
87+
}
88+
89+
if (_activeWrapperComponent !== wrapperComponent) {
90+
_activeWrapperComponent.refs[refName].setState(remoteState);
91+
}
92+
}
93+
94+
setTimeout(() => {
95+
emitEvent(name, evt);
96+
}, 0);
97+
};
98+
});
99+
100+
const domNode = ReactDOM.findDOMNode(sortableComponent.refs[options.ref] || sortableComponent);
101+
this.sortableInstance = Sortable.create(domNode, copyOptions);
102+
}
103+
componentWillReceiveProps(nextProps) {
104+
const wrapperComponent = this;
105+
const sortableComponent = wrapperComponent.refs[refName];
106+
const model = wrapperComponent.sortableOptions.model;
107+
const items = nextProps[model];
108+
109+
if (items) {
110+
let newState = {};
111+
newState[model] = items;
112+
sortableComponent.setState(newState);
113+
}
114+
}
115+
componentWillUnmount() {
116+
if (this.sortableInstance) {
117+
this.sortableInstance.destroy();
118+
this.sortableInstance = null;
119+
}
120+
}
121+
122+
render() {
123+
return (
124+
<Component ref={refName} {...this.props} />
125+
);
126+
}
127+
};
128+
129+
export default SortableMixin;

test/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { test } from 'tap';
2+
3+
test('noop', (t) => {
4+
t.end();
5+
});

webpack.config.babel.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import path from 'path';
2+
import webpack from 'webpack';
3+
4+
export default {
5+
cache: true,
6+
target: 'web',
7+
entry: path.resolve(__dirname, 'src/index.jsx'),
8+
output: {
9+
path: path.join(__dirname, 'dist'),
10+
filename: 'react-sortable.js',
11+
libraryTarget: 'umd',
12+
library: 'SortableMixin'
13+
},
14+
externals: {
15+
'react': {
16+
root: 'React',
17+
commonjs2: 'react',
18+
commonjs: 'react',
19+
amd: 'react'
20+
},
21+
'react-dom': {
22+
root: 'ReactDOM',
23+
commonjs2: 'react-dom',
24+
commonjs: 'react-dom',
25+
amd: 'react-dom'
26+
},
27+
'sortablejs': {
28+
root: 'Sortable',
29+
commonjs2: 'sortablejs',
30+
commonjs: 'sortablejs',
31+
amd: 'sortablejs'
32+
}
33+
},
34+
module: {
35+
preLoaders: [
36+
{
37+
test: /\.jsx?$/,
38+
loaders: ['eslint'],
39+
exclude: /node_modules/
40+
}
41+
],
42+
loaders: [
43+
{
44+
test: /\.jsx?$/,
45+
loader: 'babel',
46+
exclude: /(node_modules|bower_components)/,
47+
query: {
48+
presets: ['es2015', 'stage-0', 'react'],
49+
plugins: []
50+
}
51+
}
52+
]
53+
},
54+
resolve: {
55+
extensions: ['', '.js', '.jsx']
56+
}
57+
};

0 commit comments

Comments
 (0)