├── .babelrc ├── .gitignore ├── .npmignore ├── README.md ├── demo.gif ├── example ├── app.css ├── index.template.html ├── main.js └── routes │ ├── ExampleTableView1.js │ ├── ExampleTableView2.js │ └── Home.js ├── lib ├── ReactRefreshInfiniteTableView.js ├── rri.js └── spinner.css ├── package.json ├── server.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.log 5 | *.mov 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.gif 2 | example 3 | node_modules 4 | .DS_Store 5 | dist 6 | *.log 7 | webpack.config.js 8 | server.js 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Thank you guys so much for all your support. The project has been discontinued. Instead, I will make another one with better performance on both desktop and mobile 2 | 3 | # React-Refresh-Infinite-TableView 4 | 5 | [![npm version](https://img.shields.io/npm/v/react-refresh-infinite-tableview.svg?style=flat-square)](https://www.npmjs.com/package/react-refresh-infinite-tableview) 6 | [![npm downloads](https://img.shields.io/npm/dm/react-refresh-infinite-tableview.svg?style=flat-square)](https://www.npmjs.com/package/react-refresh-infinite-tableview) 7 | ___ 8 | 9 | ![demo](demo.gif) 10 | 11 | ### Features 👀 12 | 13 | - Pull to Refresh 14 | - Pull to Load More 15 | - Fully Customizable Loading Indicator 16 | - Subclass-able React Component 17 | 18 | ### How to Install 😍? 19 | 20 | 0. via npm install 21 | ``` 22 | npm install --save react-refresh-infinite-tableview 23 | ``` 24 | 1. or manually 25 | - extract the ```rri.js``` and ```spinner.css``` from ```lib/```, and use them in your projects. 26 | 27 | ### How to Use 🤔? 28 | - You can use it with default spinners or your custom spinners 29 | - Below are some setups, if you are looking for detail, please take a look at /examples 30 | 31 | ``` 32 | import ReactRefreshInfiniteTableView from 'react-refresh-infinite-tableview' 33 | ``` 34 | 35 | - Use Default Spinners 36 | 37 | - subclass the ```ReactRefreshInfiniteTableView``` 38 | ```es6 39 | class ExampleTableView extends ReactRefreshInfiniteTableView { 40 | //... 41 | } 42 | ``` 43 | 44 | - attach an scroll event listener to your scrollview 45 | ```es6 46 |
47 | ``` 48 | - set props to your tableview component 49 | ```es6 50 | 52 | ``` 53 | - handle scroll events 54 | ```es6 55 | // handle onScrollToTop 56 | handleScrollToTop(completed) { 57 | // refresh data 58 | // ... 59 | 60 | // once received data 61 | completed() 62 | this.setState({data: newData}) 63 | } 64 | 65 | // handle onScrollToBottom 66 | handleScrollToBottom(completed) { 67 | // load more data 68 | // ... 69 | 70 | // once received data 71 | completed() 72 | this.setState({data1: newData}) 73 | } 74 | ``` 75 | - see ExampleTableView1 for details 76 | 77 | - Use your own loading indicators 78 | - first, you need to follow the basic set up as the above(use default spinner) 79 | - set useDefaultIndicator to false for your component 80 | ```es6 81 | useDefaultIndicator={false} 82 | ``` 83 | - construct your own indicators with jsx 84 | ```es6 85 | // customize your Refresh Indicator here 86 | refreshIndicator() { 87 | if (this.state.isRefreshing) { 88 | return ( 89 |
🏃...
90 | ) 91 | } 92 | return 93 | } 94 | // customize your Load-more Indicator here 95 | loadMoreIndicator() { 96 | if (this.state.isLoadingMore) { 97 | return ( 98 |
...🏃
99 | ) 100 | } 101 | return 102 | } 103 | ``` 104 | - render your indicators with your tableview 105 | ```es6 106 |
107 | {this.refreshIndicator()} 108 | {cells} 109 | {this.loadMoreIndicator()} 110 |
111 | ``` 112 | - see ExampleTableView2 for details 113 | 114 | - You can also disable the scrollToTop or scrollToBottom by just by just not setting the props. 115 | ```es6 116 | 117 | ``` 118 | 119 | ### TODO: 120 | 121 | - Customizable default spinner 122 | - Trigger scroll-to-top event when pull down if the tableview is already at the top 123 | 124 | ### Demo 😮 125 | 126 | - Run the demo with 127 | ``` 128 | npm install 129 | npm start 130 | ``` 131 | then go to http://localhost:3000/ 132 | 133 | - P.S. In the demo, you may notice that the page will auto-refresh after you change the code because the demo is based on my another repo [React-SPA-Starter](https://github.com/calvinchankf/React-SPA-Starter), which is a very handy starter-kit for react dev. 134 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calvinchankf/ReactRefreshinFiniteTableView/59fbb5f5afbbd4645bac9e2f8b7b933d49061cbe/demo.gif -------------------------------------------------------------------------------- /example/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #EEEEEE; 3 | } 4 | 5 | .title { 6 | font-size: large; 7 | height: 50px; 8 | line-height: 50px; 9 | } 10 | 11 | .tableView { 12 | height: calc( 100vh - 100px ); 13 | overflow-y: scroll; 14 | } 15 | 16 | .tableView .list-group-item:first-child, 17 | .tableView .list-group-item:last-child { 18 | border-radius: 0px; 19 | } 20 | -------------------------------------------------------------------------------- /example/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Refresh Infinite TableView 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { render } from 'react-dom' 3 | import { Router, Route, IndexRoute, Link, IndexLink, browserHistory } from 'react-router' 4 | 5 | import Home from './routes/Home.js' 6 | 7 | render(( 8 | 9 | 10 | 11 | ), document.getElementById('root')) 12 | -------------------------------------------------------------------------------- /example/routes/ExampleTableView1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | // use default loading spinners 5 | import ReactRefreshInfiniteTableView from '../../lib/ReactRefreshInfiniteTableView.js' 6 | 7 | export default class ExampleTableView1 extends ReactRefreshInfiniteTableView { 8 | 9 | constructor(props) { 10 | super(props) 11 | } 12 | 13 | render() { 14 | var cells = this.props.dataSource.map(function(item, index) { 15 | return 16 | {item} 17 | 18 | }) 19 | 20 | return ( 21 | // remember to invoke viewDidScroll from superclass(InfinitScrollView) 22 |
23 | {cells} 24 |
25 | ) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /example/routes/ExampleTableView2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | // customize loading Indicators 5 | import ReactRefreshInfiniteTableView from '../../lib/ReactRefreshInfiniteTableView.js' 6 | 7 | export default class ExampleTableView2 extends ReactRefreshInfiniteTableView { 8 | 9 | constructor(props) { 10 | super(props) 11 | } 12 | 13 | render() { 14 | 15 | var cells = this.props.dataSource.map(function(item, index) { 16 | return 17 | {item} 18 | 19 | }) 20 | 21 | return ( 22 | // remember to invoke viewDidScroll from superclass(InfinitScrollView) 23 |
24 | {this.refreshIndicator()} 25 | {cells} 26 | {this.loadMoreIndicator()} 27 |
28 | ) 29 | } 30 | 31 | // customize your Refresh Indicator here 32 | refreshIndicator() { 33 | if (this.state.isRefreshing) { 34 | return ( 35 |
🏃...
36 | ) 37 | } 38 | return 39 | } 40 | 41 | // customize your Load-more Indicator here 42 | loadMoreIndicator() { 43 | if (this.state.isLoadingMore) { 44 | return ( 45 |
...🏃
46 | ) 47 | } 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/routes/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../node_modules/bootstrap/dist/css/bootstrap.css' 3 | import '../app.css' 4 | 5 | import ExampleTableView1 from './ExampleTableView1.js' 6 | import ExampleTableView2 from './ExampleTableView2.js' 7 | 8 | export default class Home extends React.Component { 9 | 10 | constructor(props) { 11 | super(props) 12 | 13 | // initial data for tableviews 14 | var data1 = this.initData() 15 | var data2 = this.initData() 16 | this.state = {data1: data1, data2: data2} 17 | 18 | // recommend that you bind your event handlers in the constructor so they are only bound once for every instance 19 | // https://facebook.github.io/react/docs/reusable-components.html#no-autobinding 20 | this.handleScrollToTop1 = this.handleScrollToTop1.bind(this) 21 | this.handleScrollToBottom1 = this.handleScrollToBottom1.bind(this) 22 | 23 | this.handleScrollToTop2 = this.handleScrollToTop2.bind(this) 24 | this.handleScrollToBottom2 = this.handleScrollToBottom2.bind(this) 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 |
31 |
Default
32 |
Custom
33 |
34 |
35 | 40 | 46 |
47 |
48 | ) 49 | } 50 | 51 | initData() { 52 | var data = [] 53 | for (var i=0; i<20; i++) { 54 | data.push(i) 55 | } 56 | return data 57 | } 58 | 59 | moreData(oldData) { 60 | var newData = Object.assign([], oldData) 61 | var base = newData[newData.length-1] 62 | for (var i=base+1; i<=base+20; i++) { 63 | newData.push(i) 64 | } 65 | return newData 66 | } 67 | 68 | // example 1 69 | handleScrollToTop1(completed) { 70 | // refresh 71 | setTimeout(function() { 72 | var data = this.initData() 73 | console.log(data) 74 | 75 | // completed is a callback to tell infinite table to hide loading indicator 76 | // must invcke completed before setState 77 | completed() 78 | this.setState({data1: data}) 79 | 80 | }.bind(this), 1000) 81 | } 82 | 83 | handleScrollToBottom1(completed) { 84 | // load more 85 | setTimeout(function() { 86 | var newData = this.moreData(this.state.data1) 87 | console.log(newData) 88 | 89 | completed() 90 | this.setState({data1: newData}) 91 | 92 | }.bind(this), 1000) 93 | } 94 | 95 | // example 2 96 | handleScrollToTop2(completed) { 97 | // refresh 98 | setTimeout(function() { 99 | var data = this.initData() 100 | console.log(data) 101 | 102 | completed() 103 | this.setState({data2: data}) 104 | 105 | }.bind(this), 1000) 106 | } 107 | 108 | handleScrollToBottom2(completed) { 109 | // load more 110 | setTimeout(function() { 111 | var newData = this.moreData(this.state.data2) 112 | console.log(newData) 113 | 114 | completed() 115 | this.setState({data2: newData}) 116 | 117 | }.bind(this), 1000) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/ReactRefreshInfiniteTableView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './spinner.css' 4 | 5 | export default class ReactRefreshInfiniteTableView extends React.Component { 6 | 7 | constructor(props) { 8 | super(props) 9 | this.viewDidScroll = this.viewDidScroll.bind(this) 10 | this.state = { 11 | isRefreshing : false, 12 | isLoadingMore : false 13 | } 14 | } 15 | 16 | render() { 17 | // override this to render your own components 18 | return ( 19 |
20 | ) 21 | } 22 | 23 | findNodeIndex(dom) { 24 | var targetNodeIndex = 0 25 | var nodes = document.getElementsByClassName(dom.className) 26 | for (var i=0; i< nodes.length; i++) { 27 | if (nodes[i]==dom) { 28 | targetNodeIndex = i 29 | break 30 | } 31 | } 32 | return targetNodeIndex 33 | } 34 | 35 | viewDidScroll(event) { 36 | 37 | var dom = ReactDOM.findDOMNode(this) 38 | 39 | // vars for UI 40 | var tableViewIdName = dom.id 41 | var tableViewClassName = dom.className 42 | var targetNodeIndex = this.findNodeIndex(dom) // the index of target node within the nodes with same className 43 | var isFindNodeById = tableViewIdName ? true : false // prefer use id becox less calculation 44 | var indicatorClassName = "infinit-table-spinner" 45 | 46 | // vars for calculation 47 | var scrollviewOffsetY = dom.scrollTop 48 | var scrollviewFrameHeight = dom.clientHeight 49 | var scrollviewContentHeight = dom.scrollHeight 50 | var sum = scrollviewOffsetY+scrollviewFrameHeight 51 | 52 | if (sum <= scrollviewFrameHeight) { 53 | 54 | // disable scroll to top if onScrollToTop isn't set 55 | if (!this.props.onScrollToTop) { return } 56 | 57 | // console.log('ReactRefreshInfiniteTableView onScrollToTop') 58 | 59 | if (this.state.isRefreshing) { return } 60 | this.setState({isRefreshing: true}) 61 | 62 | // use default refresh indicator 63 | if (this.props.useDefaultIndicator) { 64 | // spinner for refreshing 65 | var refreshIndicator = document.createElement("div") 66 | refreshIndicator.className = indicatorClassName 67 | 68 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex] 69 | tableView.insertBefore(refreshIndicator, tableView.firstChild) 70 | } 71 | 72 | // event 73 | this.props.onScrollToTop(function() { 74 | 75 | this.setState({isRefreshing: false}) 76 | 77 | if (this.props.useDefaultIndicator) { 78 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex] 79 | var firstChild = tableView.firstChild 80 | if (firstChild.className.indexOf(indicatorClassName) > -1) { 81 | tableView.removeChild(firstChild) 82 | } 83 | } 84 | 85 | }.bind(this)) 86 | 87 | } else if (sum >= scrollviewContentHeight) { 88 | 89 | // disable scroll to top if onScrollToTop isn't set 90 | if (!this.props.onScrollToBottom) { return } 91 | 92 | // console.log('ReactRefreshInfiniteTableView onScrollToBottom'); 93 | 94 | if (this.state.isLoadingMore) { return } 95 | this.setState({isLoadingMore: true}) 96 | 97 | // use default load more indicator 98 | if (this.props.useDefaultIndicator) { 99 | // spinner for loading more 100 | var loadMoreIndicator = document.createElement("div") 101 | loadMoreIndicator.className = indicatorClassName 102 | 103 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex] 104 | tableView.insertBefore(loadMoreIndicator, tableView.lastChild.nextSibling) 105 | } 106 | 107 | // event 108 | this.props.onScrollToBottom(function() { 109 | 110 | this.setState({isLoadingMore: false}) 111 | 112 | if (this.props.useDefaultIndicator) { 113 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex] 114 | var lastChild = tableView.lastChild 115 | if (lastChild.className.indexOf(indicatorClassName) > -1) { 116 | tableView.removeChild(lastChild) 117 | } 118 | } 119 | 120 | }.bind(this)) 121 | } 122 | 123 | } 124 | } 125 | 126 | ReactRefreshInfiniteTableView.defaultProps = { 127 | useDefaultIndicator: true 128 | } 129 | -------------------------------------------------------------------------------- /lib/rri.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _reactDom = require('react-dom'); 14 | 15 | var _reactDom2 = _interopRequireDefault(_reactDom); 16 | 17 | require('./spinner.css'); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 22 | 23 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 24 | 25 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 26 | 27 | var ReactRefreshInfiniteTableView = function (_React$Component) { 28 | _inherits(ReactRefreshInfiniteTableView, _React$Component); 29 | 30 | function ReactRefreshInfiniteTableView(props) { 31 | _classCallCheck(this, ReactRefreshInfiniteTableView); 32 | 33 | var _this = _possibleConstructorReturn(this, (ReactRefreshInfiniteTableView.__proto__ || Object.getPrototypeOf(ReactRefreshInfiniteTableView)).call(this, props)); 34 | 35 | _this.viewDidScroll = _this.viewDidScroll.bind(_this); 36 | _this.state = { 37 | isRefreshing: false, 38 | isLoadingMore: false 39 | }; 40 | return _this; 41 | } 42 | 43 | _createClass(ReactRefreshInfiniteTableView, [{ 44 | key: 'render', 45 | value: function render() { 46 | // override this to render your own components 47 | return _react2.default.createElement('div', null); 48 | } 49 | }, { 50 | key: 'findNodeIndex', 51 | value: function findNodeIndex(dom) { 52 | var targetNodeIndex = 0; 53 | var nodes = document.getElementsByClassName(dom.className); 54 | for (var i = 0; i < nodes.length; i++) { 55 | if (nodes[i] == dom) { 56 | targetNodeIndex = i; 57 | break; 58 | } 59 | } 60 | return targetNodeIndex; 61 | } 62 | }, { 63 | key: 'viewDidScroll', 64 | value: function viewDidScroll(event) { 65 | 66 | var dom = _reactDom2.default.findDOMNode(this); 67 | 68 | // vars for UI 69 | var tableViewIdName = dom.id; 70 | var tableViewClassName = dom.className; 71 | var targetNodeIndex = this.findNodeIndex(dom); // the index of target node within the nodes with same className 72 | var isFindNodeById = tableViewIdName ? true : false; // prefer use id becox less calculation 73 | var indicatorClassName = "infinit-table-spinner"; 74 | 75 | // vars for calculation 76 | var scrollviewOffsetY = dom.scrollTop; 77 | var scrollviewFrameHeight = dom.clientHeight; 78 | var scrollviewContentHeight = dom.scrollHeight; 79 | var sum = scrollviewOffsetY + scrollviewFrameHeight; 80 | 81 | if (sum <= scrollviewFrameHeight) { 82 | 83 | // disable scroll to top if onScrollToTop isn't set 84 | if (!this.props.onScrollToTop) { 85 | return; 86 | } 87 | 88 | // console.log('ReactRefreshInfiniteTableView onScrollToTop') 89 | 90 | if (this.state.isRefreshing) { 91 | return; 92 | } 93 | this.setState({ isRefreshing: true }); 94 | 95 | // use default refresh indicator 96 | if (this.props.useDefaultIndicator) { 97 | // spinner for refreshing 98 | var refreshIndicator = document.createElement("div"); 99 | refreshIndicator.className = indicatorClassName; 100 | 101 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex]; 102 | tableView.insertBefore(refreshIndicator, tableView.firstChild); 103 | } 104 | 105 | // event 106 | this.props.onScrollToTop(function () { 107 | 108 | this.setState({ isRefreshing: false }); 109 | 110 | if (this.props.useDefaultIndicator) { 111 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex]; 112 | var firstChild = tableView.firstChild; 113 | if (firstChild.className.indexOf(indicatorClassName) > -1) { 114 | tableView.removeChild(firstChild); 115 | } 116 | } 117 | }.bind(this)); 118 | } else if (sum >= scrollviewContentHeight) { 119 | 120 | // disable scroll to top if onScrollToTop isn't set 121 | if (!this.props.onScrollToBottom) { 122 | return; 123 | } 124 | 125 | // console.log('ReactRefreshInfiniteTableView onScrollToBottom'); 126 | 127 | if (this.state.isLoadingMore) { 128 | return; 129 | } 130 | this.setState({ isLoadingMore: true }); 131 | 132 | // use default load more indicator 133 | if (this.props.useDefaultIndicator) { 134 | // spinner for loading more 135 | var loadMoreIndicator = document.createElement("div"); 136 | loadMoreIndicator.className = indicatorClassName; 137 | 138 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex]; 139 | tableView.insertBefore(loadMoreIndicator, tableView.lastChild.nextSibling); 140 | } 141 | 142 | // event 143 | this.props.onScrollToBottom(function () { 144 | 145 | this.setState({ isLoadingMore: false }); 146 | 147 | if (this.props.useDefaultIndicator) { 148 | var tableView = isFindNodeById ? document.getElementById(tableViewIdName) : document.getElementsByClassName(tableViewClassName)[targetNodeIndex]; 149 | var lastChild = tableView.lastChild; 150 | if (lastChild.className.indexOf(indicatorClassName) > -1) { 151 | tableView.removeChild(lastChild); 152 | } 153 | } 154 | }.bind(this)); 155 | } 156 | } 157 | }]); 158 | 159 | return ReactRefreshInfiniteTableView; 160 | }(_react2.default.Component); 161 | 162 | exports.default = ReactRefreshInfiniteTableView; 163 | 164 | 165 | ReactRefreshInfiniteTableView.defaultProps = { 166 | useDefaultIndicator: true 167 | }; 168 | -------------------------------------------------------------------------------- /lib/spinner.css: -------------------------------------------------------------------------------- 1 | .infinit-table-spinner { 2 | margin: 16px auto 16px auto; 3 | height: 32px; 4 | width: 32px; 5 | animation: rotate-spinner 1s infinite linear; 6 | border: 2px solid #3498db; 7 | border-right-color: transparent; 8 | border-radius: 50%; 9 | } 10 | 11 | @keyframes rotate-spinner { 12 | 0% { transform: rotate(0deg); } 13 | 100% { transform: rotate(360deg); } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-refresh-infinite-tableview", 3 | "version": "1.0.3", 4 | "description": "A Subclass-able React Component to make a simple Pull-To-Refresh and Infinite TableView", 5 | "main": "./lib/rri.js", 6 | "author": { 7 | "name": "calvinchankf", 8 | "email": "chan9118kin@gmail.com" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/calvinchankf/ReactRefreshinFiniteTableView.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/calvinchankf/ReactRefreshinFiniteTableView/issues" 17 | }, 18 | "scripts": { 19 | "start": "node server" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "react-component", 24 | "scrolling", 25 | "tableview", 26 | "pull to refresh", 27 | "refresh", 28 | "load more", 29 | "infinite" 30 | ], 31 | "dependencies": { 32 | }, 33 | "devDependencies": { 34 | "babel-preset-react-hmre": "^1.1.0", 35 | "react-addons-test-utils": "^0.14.3", 36 | "react-transform-hmr": "^1.0.0", 37 | "webpack-dev-middleware": "^1.2.0", 38 | "webpack-hot-middleware": "^2.2.0", 39 | "babel-cli": "^6.4.0", 40 | "babel-loader": "^6.2.4", 41 | "babel-preset-es2015": "^6.9.0", 42 | "babel-preset-react": "^6.5.0", 43 | "babel-preset-stage-0": "^6.5.0", 44 | "bootstrap": "^3.3.6", 45 | "css-loader": "^0.23.1", 46 | "express": "^4.13.4", 47 | "file-loader": "^0.8.5", 48 | "html-webpack-plugin": "^1.7.0", 49 | "json-loader": "^0.5.4", 50 | "react": "^0.14.3", 51 | "react-dom": "^0.14.3", 52 | "react-router": "^2.4.1", 53 | "style-loader": "^0.13.1", 54 | "url-loader": "^0.5.7", 55 | "webpack": "^1.13.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var webpackConfig = require('./webpack.config.js'); 5 | 6 | var port = process.env.PORT || 3000 ; 7 | var app = express(); 8 | 9 | var webpackMiddleware = require('webpack-dev-middleware'); 10 | var webpackHotMiddleware = require('webpack-hot-middleware'); 11 | 12 | var compiler = webpack(webpackConfig); 13 | var middleware = webpackMiddleware(compiler, { 14 | publicPath: webpackConfig.output.publicPath, 15 | contentBase: 'src', 16 | stats: { 17 | colors: true, 18 | hash: false, 19 | timings: true, 20 | chunks: false, 21 | chunkModules: false, 22 | modules: false 23 | } 24 | }); 25 | 26 | app.use(middleware); 27 | app.use(webpackHotMiddleware(compiler)); 28 | app.use(express.static(__dirname + '/dist')); 29 | app.get('*', function response(req, res) { 30 | res.write(middleware.fileSystem.readFileSync(path.join(__dirname, 'dist/index.html'))); 31 | }); 32 | 33 | app.listen(port, '0.0.0.0', function onStart(err) { 34 | if (err) { 35 | console.log(err); 36 | } 37 | console.info('==> 🌎 Listening on port %s. Open up http://0.0.0.0:%s/ in your browser.', port, port); 38 | }); 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | devtool: 'eval-source-map', 7 | entry: [ 8 | 'webpack-hot-middleware/client?reload=true', 9 | path.join(__dirname, 'example/main.js') 10 | ], 11 | output: { 12 | path: path.join(__dirname, '/dist/'), 13 | filename: '[name].js', 14 | publicPath: '/' 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ 18 | template: 'example/index.template.html', 19 | inject: 'body', 20 | filename: 'index.html' 21 | }), 22 | new webpack.HotModuleReplacementPlugin(), 23 | new webpack.NoErrorsPlugin(), 24 | new webpack.DefinePlugin({ 25 | 'process.env.NODE_ENV': JSON.stringify('development') 26 | }) 27 | ], 28 | module: { 29 | loaders: [{ 30 | test: /\.js?$/, 31 | exclude: /node_modules/, 32 | loader: 'babel', 33 | query: { 34 | "presets": ["react", "es2015", "stage-0", "react-hmre"] 35 | } 36 | }, { 37 | test: /\.json?$/, 38 | loader: 'json' 39 | }, { 40 | test: /\.css$/, 41 | loader: 'style!css' 42 | }, { 43 | // for bootstrap 44 | test: /\.woff($|\?)|\.woff2($|\?)|\.ttf($|\?)|\.eot($|\?)|\.svg($|\?)/, 45 | loader: 'url-loader', 46 | }] 47 | } 48 | }; 49 | --------------------------------------------------------------------------------