Skip to content

Commit

Permalink
Merge pull request #16 from okonet/master
Browse files Browse the repository at this point in the history
Better BaseDisplayObject and build scripts
  • Loading branch information
jrowny committed Dec 4, 2015
2 parents bc36ae9 + 295ba40 commit 622a4bd
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 112 deletions.
3 changes: 2 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"stage": 0,
"loose": "all"
"loose": "all",
"plugins": ["lodash"]
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
dist/
.idea
21 changes: 8 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Options (Properties)
| Property | Default | Description |
|---|:---:|---|
| **items** | [] | The array of items in the grid |
| **displayObject** | <GridItem/> | The React component used to display items |
| **displayObject** | (required) | The React component to render items |
| **keyProp** | 'key' | The property to be used as a key |
| **filterProp** | 'filtered' | The property to be used for filtering, if the filtered value is true, the item won't be displayed. It's important to not remove items from the array because that will cause React to remove the DOM, for performance we would rather hide it then remove it. |
| **sortProp** | 'sort' | The property to sort on |
Expand All @@ -39,26 +39,22 @@ Options (Properties)

Creating a DisplayObject component
------
Display objects will receive item, style, and index as properties. You must apply the style to the root element in your render. Here's the simplest possible example:

'use strict';
displayObject component will receive `item`, `index` and `itemsLength` as props. Here's the simplest possible example:

import React from 'react';
import BaseDisplayObject from '../lib/BaseDisplayObject.jsx';

export default class SampleDisplay extends BaseDisplayObject{

export default class SampleDisplay extends React.Component {

render() {
//IMPORTANT: Without the style, nothing happens :(
var itemStyle = super.getStyle.call(this);
return <div style={itemStyle}></div>;
// Supposing your item shape is something like {name: 'foo'}
const { item, index, itemsLength } = this.props;
return <div>Item {index} of {itemsLength}: {item.name}</div>;
}
}

Once you've created a display object, use it like this:

var dispalyObject = (<SampleDisplay />);
var grid = (<AbsoluteGrid ... displayObject={displayObject}/>);
var grid = (<AbsoluteGrid ... displayObject={(<SampleDisplay {...pass props normally} />)}/>);

What Makes AbsoluteGrid Unique?
----
Expand All @@ -70,7 +66,6 @@ Each GridItem component is passed the following props.

| Property | Description |
|---|:---|
| **style** | The inline styles that ReactAbsoluteGrid generates for the grid item. **NOTE**: Remember to apply to your GridItem element |
| **item** | The data associated with the GridItem |
| **index** | The index of the item data in the `items` array |
| **itemsLength** | The total length of the `items` array |
Expand Down
2 changes: 1 addition & 1 deletion demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function demo() {
var onMoveDebounced = _.debounce(onMove, 80);

var unMountTest = function(){
if(React.unmountComponentAtNode(document.getElementById('Demo'))){
if(ReactDOM.unmountComponentAtNode(document.getElementById('Demo'))){
ReactDOM.render(<button onClick={unMountTest}>Remount</button>, document.getElementById('UnmountButton'));
}else{
render();
Expand Down
12 changes: 7 additions & 5 deletions demo/SampleDisplay.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
'use strict';

import React from 'react';
import BaseDisplayObject from '../lib/BaseDisplayObject.jsx';

export default class SampleDisplay extends BaseDisplayObject{
export default class SampleDisplay extends React.Component {

render() {
//IMPORTANT: Without the style, nothing happens :(
var itemStyle = super.getStyle.call(this);
itemStyle.backgroundImage = 'url(\'' + this.props.item.url + '\')';
const itemStyle = {
display: 'block',
width: '100%',
height: '100%',
backgroundImage: `url('${this.props.item.url}')`
};

return <div
style={itemStyle}
Expand Down
96 changes: 53 additions & 43 deletions lib/AbsoluteGrid.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import GridItem from './GridItem.jsx';
import { debounce, sortBy } from 'lodash';
import BaseStyleObject from './BaseDisplayObject.jsx';
import LayoutManager from './LayoutManager.js';
import DragManager from './DragManager.js';
import debounce from 'lodash.debounce';
import sortBy from 'lodash.sortby';

export default class AbsoluteGrid extends React.Component {

running;

constructor(props){
super(props);
this.running = false;
constructor(props, context){
super(props, context);
this.onResize = debounce(this.onResize, 150);
this.dragManager = new DragManager(this.props.onMove, this.props.keyProp);
this.state = {
Expand All @@ -25,57 +20,78 @@ export default class AbsoluteGrid extends React.Component {

render() {
if(!this.state.layoutWidth || !this.props.items.length){
return <div></div>;
return <div ref={node => this.container = node}></div>;
}

var options = {
const options = {
itemWidth: this.props.itemWidth,
itemHeight: this.props.itemHeight,
verticalMargin: this.props.verticalMargin,
zoom: this.props.zoom
};

var layout = new LayoutManager(options, this.state.layoutWidth);
const layout = new LayoutManager(options, this.state.layoutWidth);

var filteredIndex = 0;
var sortedIndex = {};
let filteredIndex = 0;
let sortedIndex = {};

/*
If we actually sorted the array, React would re-render the DOM nodes
Creating a sort index just tells us where each item should be
This also clears out filtered items from the sort order and
eliminates gaps and duplicate sorts
*/
sortBy(this.props.items, this.props.sortProp).forEach((item) => {
sortBy(this.props.items, this.props.sortProp).forEach(item => {
if(!item[this.props.filterProp]){
var key = item[this.props.keyProp];
const key = item[this.props.keyProp];
sortedIndex[key] = filteredIndex;
filteredIndex++;
}
});

var gridItems = this.props.items.map((item) => {
var key = item[this.props.keyProp];
var index = sortedIndex[key];
var style = layout.getStyle(index, this.props.animation, item[this.props.filterProp]);

var gridItem = React.cloneElement(this.props.displayObject, {
...this.props.displayObject.props, style, item, index, key,
itemsLength: this.props.items.length,
dragEnabled: this.props.dragEnabled,
dragManager: this.dragManager
});

return gridItem;
const itemsLength = this.props.items.length;
const gridItems = this.props.items.map(item => {
const key = item[this.props.keyProp];
const index = sortedIndex[key];
const style = layout.getStyle(index, this.props.animation, item[this.props.filterProp]);

return (
<BaseStyleObject
style={style}
item={item}
index={index}
id={key}
key={key}
dragEnabled={this.props.dragEnabled}
dragManager={this.dragManager}
>
{
React.cloneElement(this.props.displayObject, {
...this.props.displayObject.props,
item,
index,
itemsLength
})
}
</BaseStyleObject>
);
});

var gridStyle = {
const gridStyle = {
position: 'relative',
display: 'block',
height: layout.getTotalHeight(filteredIndex)
};

return <div style={gridStyle} className="absoluteGrid">{gridItems}</div>;
return (
<div
style={gridStyle}
className="absoluteGrid"
ref={node => this.container = node}
>
{gridItems}
</div>
);
}

componentDidMount() {
Expand All @@ -91,20 +107,15 @@ export default class AbsoluteGrid extends React.Component {
}

onResize = () => {
if (!this.running) {
this.running = true;

if (window.requestAnimationFrame) {
window.requestAnimationFrame(this.getDOMWidth);
} else {
setTimeout(this.getDOMWidth, 66);
}

if (window.requestAnimationFrame) {
window.requestAnimationFrame(this.getDOMWidth);
} else {
setTimeout(this.getDOMWidth, 66);
}
}

getDOMWidth = () => {
var width = ReactDOM.findDOMNode(this).clientWidth;
const width = this.container && this.container.clientWidth;

if(this.state.layoutWidth !== width){
this.setState({layoutWidth: width});
Expand All @@ -117,7 +128,7 @@ export default class AbsoluteGrid extends React.Component {

AbsoluteGrid.propTypes = {
items: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
displayObject: React.PropTypes.object,
displayObject: React.PropTypes.element.isRequired,
itemWidth: React.PropTypes.number,
itemHeight: React.PropTypes.number,
verticalMargin: React.PropTypes.number,
Expand All @@ -133,7 +144,6 @@ AbsoluteGrid.propTypes = {

AbsoluteGrid.defaultProps = {
items: [],
displayObject: <GridItem/>,
keyProp: 'key',
filterProp: 'filtered',
sortProp: 'sort',
Expand Down
47 changes: 25 additions & 22 deletions lib/BaseDisplayObject.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
'use strict';

import React from 'react';
import ReactDOM from 'react-dom';

export default class BaseDisplayObject extends React.Component {

constructor(){
super();
this.onDrag = this.onDrag.bind(this);
}

updateDrag(x, y){
updateDrag(x, y) {
//Pause Animation lets our item return to a snapped position without being animated
var pauseAnimation = false;
if(!this.props.dragManager.dragItem){
pauseAnimation = true;
setTimeout(() => {
this.setState({pauseAnimation: false});
});
}, 0);
}
this.setState({
dragX: x,
Expand All @@ -26,20 +20,18 @@ export default class BaseDisplayObject extends React.Component {
});
}

onDrag(e){
onDrag = (e) => {
if(this.props.dragManager){
var domNode = ReactDOM.findDOMNode(this);
this.props.dragManager.startDrag(e, domNode, this.props.item, this.updateDrag.bind(this));
this.props.dragManager.startDrag(e, this.domNode, this.props.item, this.updateDrag.bind(this));
}
}

getStyle(){

getStyle() {
//If this is the object being dragged, return a different style
if(this.props.dragManager.dragItem === this.props.item){
if (this.props.dragManager.dragItem === this.props.item) {
var dragStyle = this.props.dragManager.getStyle(this.state.dragX, this.state.dragY);
return {...this.props.style, ...dragStyle};
}else if(this.state && this.state.pauseAnimation){
} else if (this.state && this.state.pauseAnimation) {
var pauseAnimationStyle = {...this.props.style};
pauseAnimationStyle.WebkitTransition = 'none';
pauseAnimationStyle.MozTransition = 'none';
Expand All @@ -51,20 +43,31 @@ export default class BaseDisplayObject extends React.Component {
return this.props.style;
}

componentDidMount(){
if(this.props.dragEnabled){
ReactDOM.findDOMNode(this).addEventListener('mousedown', this.onDrag);
ReactDOM.findDOMNode(this).addEventListener('touchstart', this.onDrag);
ReactDOM.findDOMNode(this).setAttribute('data-key', this.props.key);
componentDidMount() {
if (this.props.dragEnabled) {
this.domNode.addEventListener('mousedown', this.onDrag);
this.domNode.addEventListener('touchstart', this.onDrag);
this.domNode.setAttribute('data-key', this.props.id);
}
}

componentWillUnmount() {
if (this.props.dragEnabled) {
this.props.dragManager.endDrag();
this.domNode.removeEventListener('mousedown', this.onDrag);
this.domNode.removeEventListener('touchstart', this.onDrag);
}
}

componentWillUnmount(){
this.props.dragManager.endDrag();
render() {
return (
<div ref={node => this.domNode = node} style={this.getStyle()}>{ this.props.children }</div>
);
}
}

BaseDisplayObject.propTypes = {
id: React.PropTypes.any,
item: React.PropTypes.object,
style: React.PropTypes.object,
index: React.PropTypes.number,
Expand Down
16 changes: 0 additions & 16 deletions lib/GridItem.jsx

This file was deleted.

Loading

0 comments on commit 622a4bd

Please sign in to comment.