commit 4e17801d3c001dbd078c2410705616d1074956d3
parent 15b5bd5581a49e39b662938634816c81cb191b00
Author: Konstantin Tarkus <hello@tarkus.me>
Date: Thu, 7 May 2015 13:21:04 +0300
Render the top-level component to <div id="app"></div>, also...
* Update setViewport higher-order component
* Move it to ./src/components/decorators
* Update React Style Guide -> higher-order component example
* Add Css utility class for rendering CSS
Ref #4
Diffstat:
10 files changed, 146 insertions(+), 56 deletions(-)
diff --git a/README.md b/README.md
@@ -3,8 +3,8 @@
[](http://travis-ci.org/kriasoft/react-starter-kit)
[](https://david-dm.org/kriasoft/react-starter-kit)
[](https://david-dm.org/kriasoft/react-starter-kit#info=devDependencies)
-[](https://gratipay.com/koistya)
[](https://gitter.im/kriasoft/react-starter-kit)
+[](https://gratipay.com/koistya)
> This project template is a skeleton for an [isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/)
> web application (SPA) based on Facebook's [React](https://facebook.github.io/react/)
@@ -34,6 +34,7 @@
│ ├── /core/ # Core components (Flux dispatcher, base classes, utilities)
│ ├── /stores/ # Stores contain the application state and logic
│ ├── /templates/ # HTML templates for server-side rendering, emails etc.
+│ ├── /utils/ # Utility classes and functions
│ ├── /app.js # Client-side startup script
│ └── /server.js # Server-side startup script
│── gulpfile.js # Configuration file for automated builds
diff --git a/docs/react-style-guide.md b/docs/react-style-guide.md
@@ -137,6 +137,7 @@ Put custom methods and properties at the bottom of the file, after the render()
##### Higher-order React component example:
```js
+// setViewport.js
import React, { Component } from 'react';
import { canUseDOM } from 'react/lib/ExecutionEnvironment';
@@ -180,3 +181,19 @@ function setViewport(ComposedComponent) {
export default setViewport;
```
+
+```js
+// MyComponent.js
+import React from 'react';
+import setViewport from './setViewport';
+
+@setViewport
+class MyComponent {
+ render() {
+ let { width, height } = this.props.viewport;
+ return <div>{'Viewport: ' + width + 'x' + height}</div>;
+ }
+}
+
+export default MyComponent;
+```
diff --git a/src/app.js b/src/app.js
@@ -34,13 +34,13 @@ function run() {
onPageNotFound: emptyFunction
};
let element = React.createElement(App, props);
- React.render(element, document.body);
+ React.render(element, document.getElementById('app'));
// Update `Application.path` prop when `window.location` is changed
Dispatcher.register((action) => {
if (action.type === ActionTypes.CHANGE_LOCATION) {
element = React.cloneElement(element, {path: action.path});
- React.render(element, document.body);
+ React.render(element, document.getElementById('app'));
}
});
}
diff --git a/src/components/App/App.js b/src/components/App/App.js
@@ -12,7 +12,6 @@ import RegisterPage from '../RegisterPage';
import NotFoundPage from '../NotFoundPage';
import Feedback from '../Feedback';
import Footer from '../Footer';
-import setViewport from './setViewport';
const pages = { ContentPage, ContactPage, LoginPage, RegisterPage, NotFoundPage };
@@ -20,7 +19,6 @@ class App {
static propTypes = {
path: PropTypes.string.isRequired,
- viewport: PropTypes.object.isRequired,
onSetTitle: PropTypes.func.isRequired,
onSetMeta: PropTypes.func.isRequired,
onPageNotFound: PropTypes.func.isRequired
@@ -37,8 +35,7 @@ class App {
}
shouldComponentUpdate(nextProps) {
- return this.props.path !== nextProps.path ||
- this.props.viewport !== nextProps.viewport;
+ return this.props.path !== nextProps.path;
}
render() {
@@ -76,7 +73,7 @@ class App {
<Header />
{this.component}
<Feedback />
- <Footer viewport={this.props.viewport} />
+ <Footer />
</div>
);
}
@@ -140,4 +137,4 @@ class App {
}
-export default setViewport(App);
+export default App;
diff --git a/src/components/App/setViewport.js b/src/components/App/setViewport.js
@@ -1,44 +0,0 @@
-/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */
-
-import React, { Component } from 'react'; // eslint-disable-line no-unused-vars
-import { canUseDOM } from 'react/lib/ExecutionEnvironment';
-
-function setViewport(ComposedComponent) {
- return class AppViewport extends Component {
-
- constructor() {
- super();
-
- this.state = {
- viewport: canUseDOM ?
- {width: window.innerWidth, height: window.innerHeight} :
- {width: 1366, height: 768} // Default size for server-side rendering
- };
-
- this.handleResize = () => {
- let viewport = {width: window.innerWidth, height: window.innerHeight};
- if (this.state.viewport.width !== viewport.width ||
- this.state.viewport.height !== viewport.height) {
- this.setState({viewport: viewport});
- }
- };
- }
-
- componentDidMount() {
- window.addEventListener('resize', this.handleResize);
- window.addEventListener('orientationchange', this.handleResize);
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- window.removeEventListener('orientationchange', this.handleResize);
- }
-
- render() {
- return <ComposedComponent {...this.props} viewport={this.state.viewport}/>;
- }
-
- };
-}
-
-export default setViewport;
diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js
@@ -2,21 +2,36 @@
import React, { PropTypes } from 'react'; // eslint-disable-line no-unused-vars
import './Footer.less';
+import Css from '../../utils/Css';
+import setViewport from '../decorators/setViewport'; // eslint-disable-line no-unused-vars
+const css = new Css();
+
+@setViewport
class Footer {
static propTypes = {
- viewport: PropTypes.object.isRequired
+ viewport: PropTypes.shape({
+ width: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired
+ }).isRequired
};
render() {
+ // This is just an example how one can render CSS
+ let { width, height } = this.props.viewport;
+ css.set(`.Footer-viewport:after {content:' ${width}x${height}';}`);
+
return (
<div className="Footer">
<div className="Footer-container">
<span className="Footer-text">© Your Company</span>
+ <span className="Footer-spacer">·</span>
<a className="Footer-link" href="/">Home</a>
+ <span className="Footer-spacer">·</span>
<a className="Footer-link" href="/privacy">Privacy</a>
- <span className="Footer-text Footer-text--muted">| {'Viewport: ' + this.props.viewport.width + 'x' + this.props.viewport.height}</span>
+ <span className="Footer-spacer"> | </span>
+ <span ref="viewport" className="Footer-viewport Footer-text Footer-text--muted">Viewport:</span>
</div>
</div>
);
diff --git a/src/components/Footer/Footer.less b/src/components/Footer/Footer.less
@@ -21,6 +21,10 @@
}
}
+ &-spacer {
+ color: rgba(255, 255, 255, .3);
+ }
+
&-text,
&-link {
padding: 2px 5px;
diff --git a/src/components/decorators/setViewport.js b/src/components/decorators/setViewport.js
@@ -0,0 +1,58 @@
+/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */
+
+import React, { Component } from 'react'; // eslint-disable-line no-unused-vars
+import EventEmitter from 'eventemitter3';
+import { canUseDOM } from 'react/lib/ExecutionEnvironment';
+
+let EE;
+let viewport = {width: 1366, height: 768}; // Default size for server-side rendering
+const RESIZE_EVENT = 'resize';
+
+function handleWindowResize() {
+ if (viewport.width !== window.innerWidth || viewport.height !== window.innerHeight) {
+ viewport = {width: window.innerWidth, height: window.innerHeight};
+ EE.emit(RESIZE_EVENT, viewport);
+ }
+}
+
+function setViewport(ComposedComponent) {
+ return class Viewport extends Component {
+
+ constructor() {
+ super();
+
+ this.state = {
+ viewport: canUseDOM ? {width: window.innerWidth, height: window.innerHeight} : viewport
+ };
+ }
+
+ componentDidMount() {
+ if (!EE) {
+ EE = new EventEmitter();
+ window.addEventListener('resize', handleWindowResize);
+ window.addEventListener('orientationchange', handleWindowResize);
+ }
+ EE.on('resize', this.handleResize, this);
+ }
+
+ componentWillUnmount() {
+ EE.removeListener(RESIZE_EVENT, this.handleResize, this);
+ if (!EE.listeners(RESIZE_EVENT, true)) {
+ window.removeEventListener('resize', handleWindowResize);
+ window.removeEventListener('orientationchange', handleWindowResize);
+ EE = null;
+ }
+ }
+
+ render() {
+ return <ComposedComponent {...this.props} viewport={this.state.viewport}/>;
+ }
+
+ handleResize(value) {
+ this.setState({viewport: value});
+ }
+
+ };
+}
+
+export default setViewport;
diff --git a/src/templates/index.html b/src/templates/index.html
@@ -13,7 +13,7 @@
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
- <%= body %>
+ <div id="app"><%= body %></div>
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
<script>
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
diff --git a/src/utils/Css.js b/src/utils/Css.js
@@ -0,0 +1,42 @@
+/*! React Starter Kit | MIT License | http://www.reactstarterkit.com/ */
+
+import { canUseDOM } from 'react/lib/ExecutionEnvironment';
+
+class Css {
+
+ set(css) {
+ if (!canUseDOM) {
+ return;
+ }
+
+ let elem;
+
+ if (!this.styleElement) {
+ elem = this.styleElement = document.createElement('style');
+ elem.setAttribute('type', 'text/css');
+
+ if ('textContent' in elem) {
+ elem.textContent = css;
+ } else {
+ elem.styleSheet.cssText = css;
+ }
+
+ this.lastCss = css;
+ document.getElementsByTagName('head')[0].appendChild(elem);
+
+ } else if (this.lastCss !== css) {
+
+ elem = this.styleElement;
+
+ if ('textContent' in elem) {
+ elem.textContent = css;
+ } else {
+ elem.styleSheet.cssText = css;
+ }
+
+ }
+ }
+
+}
+
+export default Css;