Skip to content

Commit 5a57c40

Browse files
committed
Initial commit, basics working, no collision, responsive
0 parents  commit 5a57c40

10 files changed

+320
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

.jshintrc

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"trailing" : false,
3+
"sub" : true,
4+
"laxcomma" : true,
5+
"bitwise" : true,
6+
"nonew" : false,
7+
"undef" : true,
8+
"node" : true,
9+
"eqnull" : true,
10+
"strict" : true,
11+
"quotmark" : false,
12+
"unused" : false,
13+
"newcap" : false,
14+
"esnext" : true,
15+
"browser" : true,
16+
"devel" : true,
17+
"expr" : true
18+
}

example.html

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<html>
2+
<head>
3+
<style>
4+
.gridItem {
5+
background: #ccc;
6+
border: 1px solid black;
7+
}
8+
#content {
9+
width: 100%;
10+
height: 100%;
11+
}
12+
</style>
13+
<script src="http://localhost:4002/bundle.js"></script>
14+
<title>RGL Example</title>
15+
</head>
16+
<body>
17+
<div id="content"></div>
18+
</body>
19+
</html>

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('./lib/ReactGridLayout.jsx');

lib/ReactGridLayout.jsx

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
'use strict';
2+
var React = require('react/addons');
3+
var _ = require('lodash');
4+
var Draggable = require('react-draggable');
5+
6+
var ReactGridLayout = module.exports = React.createClass({
7+
displayName: 'ReactGridLayout',
8+
mixins: [React.addons.PureRenderMixin],
9+
10+
propTypes: {
11+
// Layout is an array of object with the format:
12+
// {x: Number, y: Number, w: Number, h: Number}
13+
initialLayout: React.PropTypes.array,
14+
bounds: React.PropTypes.array,
15+
margin: React.PropTypes.array,
16+
// {name: pxVal}, e.g. {lg: 1200, md: 996, sm: 768, xs: 480}
17+
breakpoints: React.PropTypes.object
18+
},
19+
20+
componentDidMount() {
21+
window.addEventListener('resize', this.onResize);
22+
this.onResize();
23+
},
24+
25+
componentWillUnmount() {
26+
window.removeEventListener('resize', this.onResize);
27+
},
28+
29+
onResize() {
30+
// Set breakpoint
31+
var width = this.refs.layout.getDOMNode().offsetWidth;
32+
var breakpoint = _(this.props.breakpoints)
33+
.pairs()
34+
.sortBy(function(val) { return -val[1];})
35+
.find(function(val) {return width > val[1];})[0];
36+
37+
this.setState({width: width, lastWidth: this.state.width, breakpoint: breakpoint});
38+
},
39+
40+
getDefaultProps() {
41+
return {
42+
cols: 10, // # of cols, rows
43+
rowHeight: 150, // Rows have a static height, but you can change this based on breakpoints if you like
44+
initialWidth: 1280, // This allows setting this on the server side
45+
margin: [10, 10], // margin between items (x, y) in px
46+
initialBreakpoint: 'lg',
47+
breakpoints: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}
48+
};
49+
},
50+
51+
getInitialState() {
52+
return {
53+
layout: this.generateLayout(this.props.initialLayout),
54+
breakpoint: this.props.initialBreakpoint,
55+
width: this.props.initialWidth,
56+
// Fills it full of zeroes
57+
dragOffsets: _.range(0, this.props.children.length, 0)
58+
};
59+
},
60+
61+
/**
62+
* Get the absolute position of a child by index. This does not include drag offsets or window resizing.
63+
* @param {Number} i Index of the child.
64+
* @return {Object} X and Y coordinates, in px.
65+
*/
66+
getSimpleAbsolutePosition(i) {
67+
var s = this.state, p = this.props;
68+
return {
69+
x: (s.layout[i].x / p.cols) * s.width,
70+
y: s.layout[i].y * p.rowHeight
71+
};
72+
},
73+
74+
/**
75+
* Generate a layout using the initialLayout as a template.
76+
* Missing entries will be added, extraneous ones will be truncated.
77+
* @param {Array} initialLayout Layout passed in through props.
78+
* @return {Array} Working layout.
79+
*/
80+
generateLayout(initialLayout) {
81+
var layout = [].concat(initialLayout || []);
82+
if (layout.length !== this.props.children.length) {
83+
// Fill in the blanks
84+
}
85+
return layout;
86+
},
87+
88+
perc(num) {
89+
return num * 100 + '%';
90+
},
91+
92+
processGridItem(child, i) {
93+
var l = this.state.layout[i];
94+
var cols = this.props.cols;
95+
96+
// We can set the width and height on the child, but unfortunately we can't set the position
97+
child.props.style = {
98+
width: this.perc(l.w / cols),
99+
height: l.h * this.props.rowHeight + 'px',
100+
position: 'absolute'
101+
};
102+
103+
// We calculate the x and y every pass, even though it's only actually used the first time.
104+
var x = this.state.width * (l.x / cols);
105+
var y = this.props.rowHeight * l.y;
106+
107+
// If the width has changed, we need to change the x position.
108+
if (this.state.width !== this.props.initialWidth) {
109+
// This is what the x position would be without resizing.
110+
var originalX = this.props.initialWidth * (l.x / cols);
111+
112+
// If the item has been dragged, we need to take that into account.
113+
var widthMult = this.state.width / this.props.initialWidth;
114+
x += (this.state.dragOffsets[i] * widthMult);
115+
originalX += this.state.dragOffsets[i];
116+
117+
// Margin the child over by the difference. Draggable doesn't mess with the margin so this is
118+
// safe to set.
119+
child.props.style.marginLeft = x - originalX + 'px';
120+
}
121+
122+
return (
123+
<Draggable
124+
grid={[25, 25]}
125+
start={{x: x, y: y}}
126+
onStop={this.onDragStop.bind(this, i)}>
127+
{child}
128+
</Draggable>
129+
);
130+
},
131+
132+
/**
133+
* When dragging stops, record the new position of the element in dragOffsets. This is as an x offset
134+
* from its original position.
135+
* @param {Number} i Index of the child.
136+
* @param {Event} e DOM Event.
137+
*/
138+
onDragStop(i, e) {
139+
var widthMult = this.state.width / this.props.initialWidth;
140+
// Calculate the new position by using the existing left + marginLeft, and multiplying by the reciprocal
141+
// of the width difference (so a 50px move at 1/2 screen size = 100px)
142+
var newPosition = parseInt(e.target.style.left, 10) + parseInt(e.target.style.marginLeft, 10) * (1 / widthMult);
143+
// Calculate the offset - this is the new position minus the expected position. The offset needs to be
144+
// modulated by the width multiple
145+
var offset = (newPosition - this.getSimpleAbsolutePosition(i).x) * widthMult;
146+
147+
// Make the change. We use the immutability helpers for this so we can do a simple shouldComponentUpdate
148+
var change = {};
149+
change[i] = {$set: offset};
150+
var offsets = React.addons.update(this.state.dragOffsets, change);
151+
this.setState({dragOffsets: offsets});
152+
},
153+
154+
render() {
155+
var {className, initialLayout, ...props} = this.props;
156+
className = (className || "") + " reactGridLayout";
157+
var children = React.Children.map(this.props.children, this.processGridItem);
158+
return (
159+
<div {...props} className={className} style={{position: 'relative', height: '100%'}} ref="layout">
160+
{children}
161+
</div>
162+
);
163+
}
164+
});

package.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "react-grid-layout",
3+
"version": "0.0.1",
4+
"description": "A draggable and resizable grid layout with responsive breakpoints, for React.",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"build": "./node_modules/.bin/webpack",
9+
"dev-server": "webpack-dev-server --config webpack-dev-server.config.js --hot --progress --colors --port 4002 --content-base . & open http://localhost:4002/example.html"
10+
},
11+
"repository": {
12+
"type": "git",
13+
"url": "[email protected]:STRML/react-grid-layout.git"
14+
},
15+
"keywords": [
16+
"react",
17+
"grid",
18+
"drag",
19+
"draggable",
20+
"resize",
21+
"resizable",
22+
"fluid",
23+
"responsive"
24+
],
25+
"author": "Samuel Reed <[email protected]> (http://strml.net/)",
26+
"license": "MIT",
27+
"bugs": {
28+
"url": "https://github.com/STRML/react-grid-layout/issues"
29+
},
30+
"homepage": "https://github.com/STRML/react-grid-layout",
31+
"devDependencies": {
32+
"jsx-loader": "^0.12.2",
33+
"lodash": "^2.4.1",
34+
"react": "^0.12.2",
35+
"react-hot-loader": "^1.0.4",
36+
"webpack": "^1.4.14",
37+
"webpack-dev-server": "^1.7.0"
38+
}
39+
}

test/TestLayout.jsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
var React = require('react/addons');
3+
typeof window !== "undefined" && (window.React = React) // for devtools
4+
var _ = require('lodash');
5+
var ReactGridLayout = require('../lib/ReactGridLayout.jsx');
6+
7+
var TestLayout = module.exports = React.createClass({
8+
displayName: 'TestLayout',
9+
10+
generate() {
11+
return _.map(_.range(12), function(i) {
12+
return (<div key={i} className="gridItem">{i}</div>);
13+
});
14+
},
15+
16+
render() {
17+
var items = this.generate();
18+
var layout = _.map(items, function(item, i) {
19+
return {x: i * 2 % 12, y: Math.floor(i / 6) * 2, w: 2, h: 2};
20+
});
21+
return (
22+
<ReactGridLayout className="layout" initialLayout={layout} cols={12} rowHeight={150}>
23+
{this.generate()}
24+
</ReactGridLayout>
25+
);
26+
}
27+
});

test/test.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
var Layout = require('./TestLayout.jsx');
3+
var React = require('react');
4+
document.addEventListener("DOMContentLoaded", function(event) {
5+
var contentDiv = document.getElementById('content');
6+
React.render(React.createElement(Layout), contentDiv);
7+
});

webpack-dev-server.config.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module.exports = {
2+
context: __dirname,
3+
entry: [
4+
"webpack-dev-server/client?http://localhost:4002",
5+
"webpack/hot/dev-server",
6+
"./test/test.js",
7+
],
8+
output: {
9+
path: __dirname + "/dist",
10+
filename: "bundle.js",
11+
sourceMapFilename: "debugging/[file].map",
12+
},
13+
module: {
14+
loaders: [
15+
{test: /\.jsx$/, loader: 'jsx-loader?harmony'},
16+
{test: /\.jsx$/, loader: 'react-hot-loader'}
17+
]
18+
},
19+
debug: true,
20+
resolve: {
21+
extensions: ["", ".webpack.js", ".web.js", ".js", ".jsx"]
22+
}
23+
};

webpack.config.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module.exports = {
2+
context: __dirname,
3+
entry: [
4+
"./index",
5+
],
6+
output: {
7+
path: __dirname + "/dist",
8+
filename: "bundle.js",
9+
sourceMapFilename: "debugging/[file].map",
10+
},
11+
module: {
12+
loaders: [
13+
{test: /\.jsx$/, loader: 'jsx-loader?harmony'}
14+
]
15+
},
16+
debug: true,
17+
resolve: {
18+
extensions: ["", ".webpack.js", ".web.js", ".js", ".jsx"]
19+
}
20+
};

0 commit comments

Comments
 (0)