├── .gitignore
├── package.json
├── src
└── desklamp.js
├── README.md
└── lib
└── desklamp.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "desklamp",
3 | "version": "0.1.4",
4 | "description": "clean routing and state container for react",
5 | "main": "desklamp.js",
6 | "scripts": {
7 | "compile": "babel --presets es2015,react -d lib/ src/",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/desklamp-js/desklamp.git"
13 | },
14 | "keywords": [
15 | "react",
16 | "routing",
17 | "router",
18 | "persistant",
19 | "state",
20 | "desklamp"
21 | ],
22 | "author": "desklamp.io team (http://desklamp.io)",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/desklamp-js/desklamp/issues"
26 | },
27 | "homepage": "https://github.com/desklamp-js/desklamp#readme",
28 | "devDependencies": {
29 | "babel-cli": "^6.16.0",
30 | "babel-preset-es2015": "^6.16.0",
31 | "babel-preset-react": "^6.16.0",
32 | "react": "^15.3.2"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/desklamp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // Custom link component
3 | const Link = ({ view, tag }) => {
4 | return (
5 | {tag}
6 | );
7 | };
8 | // Custom link component to call async functions before routing to page
9 | const AsyncLink = ({ view, tag, func}) => {
10 | return (
11 | { e.preventDefault(); Desklamp.syncRoute(view, func); }} >{tag}
12 | );
13 | };
14 |
15 | // Object that contains all functions
16 | const Desklamp = {};
17 |
18 | class Container extends React.Component {
19 | constructor() {
20 | super();
21 | this.state = {
22 | view: '',
23 | renderNav: '',
24 | appState: {},
25 | routeStates: {},
26 | views: {},
27 | userFunctions: {},
28 | };
29 | // Array that stores the application history
30 | this.stateHistory = [];
31 | // Adds addFuncs control to the Desklamp obj
32 | this.addFuncs = this.addFuncs.bind(this);
33 | Desklamp.addFunc = this.addFuncs;
34 | // Binds routing and view functions
35 | this.changeView = this.changeView.bind(this);
36 | Desklamp.changeView = this.changeView;
37 | this.routeLink = this.routeLink.bind(this);
38 | this.getRoutes = this.getRoutes.bind(this);
39 | // Adds updateState and getState funcs to Desklamp obj
40 | this.updateState = this.updateState.bind(this);
41 | Desklamp.updateState = this.updateState;
42 | this.getState = this.getState.bind(this);
43 | Desklamp.getState = this.getState;
44 | // Adds history to Desklamp obj
45 | this.history = this.history.bind(this);
46 | Desklamp.history = this.history;
47 | // Adds the on function to Desklamp obj
48 | this.on = this.on.bind(this);
49 | Desklamp.on = this.on;
50 | // Allows the developer to use the componentWillMount on Container component
51 | this.onLoad = this.onLoad.bind(this);
52 | Desklamp.onLoad = this.onLoad;
53 | // Adds the on function to Desklamp obj to set a default route
54 | this.defaultRoute = this.defaultRoute.bind(this);
55 | Desklamp.defaultRoute = this.defaultRoute;
56 | // Adds the on function to Desklamp obj
57 | this.syncRoute = this.syncRoute.bind(this);
58 | Desklamp.syncRoute = this.syncRoute;
59 | }
60 |
61 | componentWillMount() {
62 | window.onhashchange = () => {
63 | const pathstring = location.hash;
64 | this.routeLink(pathstring.replace('#', ''));
65 | };
66 | this.getRoutes();
67 | this.onLoad();
68 | }
69 |
70 | // Runs all functions passed to onLoad
71 | onLoad(...args) {
72 | args.forEach((func) => {
73 | func();
74 | });
75 | }
76 |
77 | getRoutes() {
78 | const newRoutes = {};
79 | let startRoute;
80 | const children = this.props.children.constructor === Object ? [this.props.children] : this.props.children;
81 | // if no starting route passed in, go get starting route from first child
82 | // if there are no children of container, default route is '/'
83 | if (!this.props.children) {
84 | startRoute = '';
85 | throw new TypeError('Container must have children components in order to create Routes');
86 | } else {
87 | startRoute = children[0].type;
88 | children.forEach( (route) => {
89 | const routeName = '';
90 | if (route.props.children) {
91 | addChildrenRoutes(routeName, route);
92 | } else {
93 | if (typeof route.props.name === 'string') {
94 | newRoutes[`/${route.props.name}`] = route.type;
95 | } else {
96 | const routeName = route.type.name.toLowerCase();
97 | newRoutes[`/${routeName}`] = route.type;
98 | }
99 | }
100 |
101 | function addChildrenRoutes(topString, currentChild) {
102 | let name = 'type';
103 | if (typeof currentChild.props.name === 'string') {
104 | name = 'props';
105 | }
106 |
107 | const childRouteName = topString += `/${currentChild[name].name.toLowerCase()}`;
108 |
109 | newRoutes[childRouteName] = currentChild.type;
110 |
111 | if (currentChild.props.children) {
112 | const children = currentChild.props.children.constructor === Object ? [currentChild.props.children] : currentChild.props.children;
113 | for (let i = 0; i < children.length; i++) {
114 | let tempRouteName = childRouteName;
115 | const currChild = children[i];
116 | let otherName = 'type';
117 | if (typeof currChild.props.name === 'string') {
118 | otherName = 'props';
119 | }
120 | const newRouteName = tempRouteName += `/${currChild[otherName].name.toLowerCase()}`;
121 | newRoutes[newRouteName] = currChild.type;
122 | if (currChild.props.children) {
123 | addChildrenRoutes(tempRouteName, currChild.props.children);
124 | }
125 | }
126 | }
127 | }
128 | });
129 | }
130 | const newState = Object.assign({}, this.state.views, newRoutes);
131 | const routeName = children[0].props.name || children[0].type.name.toLowerCase();
132 | window.location.hash = (`#/${routeName}`);
133 | this.setState({ views: newState, view: startRoute });
134 | }
135 |
136 | syncRoute(view, func) {
137 | const first = new Promise(
138 | (resolve, reject) => {
139 | func();
140 | }
141 | );
142 | first.then(window.location.hash = view);
143 | }
144 |
145 | // Allows the developer to update the state of their application
146 | updateState(newObj) {
147 | if (newObj.constructor === Object) {
148 | // Save old appState to history
149 | this.history(this.state.appState);
150 | // Update appState with new state
151 | const newState = Object.assign({}, this.state.appState, newObj);
152 | this.setState({ appState: newState });
153 | } else {
154 | throw new Error('updateState(): arg must be an object.');
155 | }
156 | }
157 |
158 | // Displays the current application state
159 | getState() {
160 | return this.state.appState;
161 | }
162 |
163 | // Keeps a point in time snapshot of the application state
164 | history(newState) {
165 | const oldHistory = this.stateHistory;
166 | this.stateHistory = [...oldHistory, newState];
167 | }
168 |
169 | // Initializes the default state, user functions, start route and navbar.
170 | on(initState, userFuncs, navbar) {
171 | if (initState.constructor !== Object && initState !== undefined) {
172 | throw new TypeError('on(): takes an object as a first parameter representing initial state');
173 | }
174 | if (userFuncs.constructor !== Object && userFuncs !== undefined) {
175 | throw new TypeError('on(): takes an object as a second parameter which contains functions');
176 | }
177 | if (navbar.constructor !== Function && navbar !== undefined) {
178 | throw new TypeError('on(): takes a React component as a third param');
179 | }
180 | // Update the state to passed in initial state
181 | this.updateState(initState);
182 | // Add userFuncs to the userFunctions object
183 | this.addFuncs(userFuncs);
184 | // If navbar param is set to true we add navbar as the first children
185 | if (!navbar) {
186 | navbar = undefined;
187 | }
188 | this.setState({ renderNav: navbar });
189 | }
190 |
191 | defaultRoute(route) {
192 | const defaultView = Object.assign({}, this.state.views);
193 | let otherName = 'type';
194 | if (typeof route !== 'string') {
195 | if (typeof route.props.name === 'string') {
196 | otherName = 'props';
197 | }
198 | defaultView[`/${route[otherName].name.toLowerCase()}`] = route.type;
199 | defaultView.default = `/${route[otherName].name.toLowerCase()}`;
200 | } else {
201 | defaultView.default = route;
202 | }
203 | this.setState({ views: defaultView });
204 | }
205 |
206 | addFuncs(input) {
207 | if (input.constructor !== Object) {
208 | throw new TypeError('Input to addFuncs must be an object with methods that are functions');
209 | }
210 | for (let key in input) {
211 | if (input[key].constructor !== Function) {
212 | throw new TypeError(`Your input to addFuncs contains ${key} which is not a function`);
213 | }
214 | this.state.userFunctions[key] = input[key].bind(this);
215 | }
216 | }
217 |
218 | changeView(view, newState) {
219 | if (typeof view !== 'string') {
220 | throw new Error('changeView(): takes a string as a first parameter');
221 | }
222 | if (newState.constructor !== Object) {
223 | throw new Error('changeView(): takes an object as a second parameter');
224 | }
225 | // update appState only by copying
226 | const notAppState = Object.assign({}, this.state.appState, newState);
227 | // update appState on this.state
228 | this.setState({ appState: notAppState });
229 | window.location.hash = (`#/${view}`);
230 | }
231 |
232 | routeLink(view) {
233 | if (this.state.views[view]) {
234 | this.setState({ view: this.state.views[view] }); // TODO: let Dev pass in variable for url string
235 | } else {
236 | window.location.hash = (`#${this.state.views.default}`);
237 | }
238 | }
239 |
240 | render() {
241 | const navBar = (this.state.renderNav) ? : undefined;
242 | return (
243 |
244 | {navBar}
245 |
246 |
247 | );
248 | }
249 | }
250 |
251 | export { Container };
252 | export { Desklamp };
253 | export { Link };
254 | export { AsyncLink };
255 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Desklamp
2 | 
3 |  
4 |
5 | **Please help us improve Desklamp by filling out our [Feedback Survey](https://goo.gl/forms/tHYzymaXwLCHghSt1)**
6 |
7 | Desklamp is a React library which provides a state container and easy creation of routes.
8 |
9 | * No external dependencies
10 | * Provides `` that creates routes from child components
11 | * Passes state to all children of ``
12 | * Passes developer-defined functions to all children of ``
13 | * Robust Desklamp API
14 |
15 | ### Navigation
16 | * [Contribute](#contribute)
17 | * [Getting Started](#gettingstarted)
18 | * [Creating Routes](#createroutes)
19 | * [Creating Nested Routes](#nestedroutes)
20 | * [Initializing Your Application](#initialize)
21 | * [Creating Initial State](#createstate)
22 | * [Creating Custom Functions](#createfunctions)
23 | * [Creating Custom Navigation](#createnav)
24 | * [Initializing App with Desklamp.on](#desklampon)
25 | * [Built In Functions](#desklampfunctions)
26 | * [Built In Components](#desklampcomponents)
27 | * [Extra Features](#features)
28 |
29 |
30 | ## Quick Start
31 |
32 | To get started, `npm install --save desklamp`.
33 |
34 | ```jsx
35 | import { Desklamp, Container } from 'desklamp';
36 | import React from 'react';
37 | import ReactDOM from 'react-dom';
38 |
39 | // Normal React components
40 | import Home from './components/Home';
41 | import CreatePost from './components/CreatePost';
42 | import Nav from './components/Nav'; // custom Nav component that you create (See Nav documentation below)
43 |
44 | // Create an initial state object
45 | const initState = {
46 | posts: [],
47 | };
48 |
49 | // Create an object with your custom functions as its methods.
50 | const funcs = {}
51 |
52 | ReactDOM.render((
53 | // Child components here become your routable urls
54 |
55 | // optional name property for custom route/url name
56 | // by default, Desklamp will name your route after your component
57 |
58 | ), document.getElementById('app'));
59 |
60 | funcs.createPost = (post) => {
61 | alert(post);
62 | }
63 |
64 | // Initialize Desklamp below your ReactDOM.render
65 | // Pass in your initial state object, funcs object, and your imported Nav
66 | Desklamp.on(initState, funcs, Nav);
67 | ```
68 |
69 |
70 | ### Contribute:
71 | This module is in active development! We will release a few more iterations in the upcoming weeks. Please submit any issues and/or feature requests and we will try to incorporate them. Or reach out to our team on [Gitter](https://gitter.im/desklampio/Lobby)
72 |
73 |
74 | ## Getting Started
75 |
76 | ### Import What you Need
77 |
78 | To set up your own application, start with your `index.js` file and import `Desklamp` and `Container` from the `desklamp` module.
79 |
80 | ```js
81 | import { Desklamp, Container } from 'desklamp';
82 | import React from 'react';
83 | import ReactDOM from 'react-dom';
84 | ```
85 | `Desklamp` gives you access to our helper methods.
86 | `Container` gives you the container component with all the application state.
87 |
88 |
89 | ## Creating Routes
90 |
91 | Routing in Desklamp is meant to get you up and running with client-side page navigation and url updates, as well as browser history, as soon as possible. To create basic navigation, simply nest your components inside the `Container` component Desklamp provides. For example, if you want to create routes for components `Home` and `CreatePost`, first define these components as you normally would. Then import them into your index.js file, and then nest them inside the `Container` component like so:
92 |
93 | ```jsx
94 | import Home from './components/Home';
95 | import CreatePost from './components/CreatePost';
96 |
97 | ReactDOM.render((
98 |
99 | //creates route /home
100 |
101 |
102 | ), document.getElementById('app'));
103 | ```
104 |
105 | ### Nested Routes
106 |
107 | To create a nested route (like `/home/homey`), simply nest the `` component inside of the `` component within `` and your route will be created accordingly.
108 |
109 | ```jsx
110 | ReactDOM.render((
111 |
112 | // open tag for `/home`
113 | // this is the child of Home and creates the route `/home/homey`
114 | //closing tag for `/home`
115 |
116 |
117 | ), document.getElementById('app'));
118 | ```
119 |
120 |
121 | ## Initializing Your Application
122 |
123 | Desklamp allows you to keep your state in a single _state_ object.
124 | Desklamp gives you many options for state control.
125 | The _state_ is automatically available to all of your routes.
126 | This functionality is enabled by the `Desklamp.on()` function.
127 |
128 | ### Create initial state
129 |
130 | Create an object representing your initial state. This object represents all data that will be passed down to each route upon render as `props.state`.
131 |
132 | ```js
133 | const initState = {
134 | username: '',
135 | posts: [],
136 | userInfo: {},
137 | };
138 | ```
139 |
140 | ### Declaring custom functions
141 |
142 | Declare an object to hold your functions. Any functions added as methods to this object will be automatically bound and passed down to all views upon render as `props.powers`.
143 |
144 | ```jsx
145 | const funcs = {};
146 | funcs.hello = () => {
147 | console.log("Hello World")
148 | }
149 | ```
150 |
151 | ### Creating a Custom Navigation Component
152 |
153 | Create a Navigation React component using our custom `` component or simple anchor tags. You can mix and match these two approaches, if you wish to link to an external site or a server route on your navigation, simply use a standard anchor tag.
154 |
155 | ```jsx
156 | // example using Desklamp's provided tags
157 | const Nav = () => {
158 | return (
159 |
166 | );
167 | };
168 |
169 | // example using normal anchor tags
170 | const Nav = () => (
171 |
178 | );
179 | ```
180 |
181 | ### Initializing your App with Desklamp.on()
182 |
183 | `Desklamp.on` is the main function you will use to tell Desklamp about your application. This method takes three arguments: the initial state and functions objects we created above, and your `Nav` component. This will declare your initial state, bind your customized functions to the _state_ and display your custom Navbar across all views.
184 |
185 | The custom functions declared to Desklamp.on will be passed down to your routes as `props.powers`. You can then pass them as props down to child components as selectively as you would like. The initial state will become your `props.state`, also available to all the routes you have set up in your `Container`.
186 |
187 | `Desklamp.on()` will look like this:
188 |
189 | ```js
190 | Desklamp.on(initState, funcs, Nav);
191 | ```
192 | Example of a component, `CreatePost`, using its default passed in props and powers:
193 | ```jsx
194 | import React from 'react';
195 |
196 | const CreatePost = ({ state, powers }) => (
197 |
198 |
Add a Post
199 |
200 |
201 | );
202 | ```
203 |
204 |
205 | ## Built in Functions
206 |
207 | Desklamp provides some helper methods to make changing views easy.
208 |
209 | ### Desklamp.changeView()
210 | `Desklamp.changeView()` is a function that takes as the first parameter the string name of the route you wish to switch to. The optional second parameter is an object representing the state change you wish to make. This object will be automatically passed to Desklamp.updateState() to update the state of your application before routing. This function allows the developer to make asynchronous calls and change the view only after data is returned.
211 |
212 | ```js
213 | createPost: (post) => {
214 | $.post('http://localhost:3000/newPost', { post }, (data) => {
215 | Desklamp.changeView('posts', { posts: data.posts }); // call Desklamp.changeView() on successful response
216 | });
217 | }
218 | ```
219 |
220 | ### Desklamp.onLoad()
221 | `Desklamp.onLoad()` takes any number of functions and runs them in the `componentWillMount` section of the Container component. This allows you, the developer to run functions on the initial loading of the application at the highest level.
222 |
223 | ### Desklamp.updateState()
224 | `Desklamp.updateState()` is a function that allows you to update the state of your application from within your custom functions. `Desklamp.updateState()` takes in an object of the values you would like to change in your state. By default Desklamp.updateState maintains immutability and creates an new object with all of the changes before calling the default React.js setState function.
225 |
226 | ```js
227 | const initState = {
228 | username: '',
229 | posts: [],
230 | userInfo: {},
231 | }
232 | Desklamp.updateState({ username:"Harry" }); // maintains immutability by creating a new state object with username "Harry"
233 | ```
234 |
235 | ### Desklamp.getState()
236 | `Desklamp.getState()` is a simple function that can be called anywhere in your application to show the current state. It can be very useful for debugging and logging what your state looks like if you are experiencing issues with your state data not looking how you think it should. The function call returns the current state object.
237 |
238 |
239 | ## Built in Components
240 |
241 | ###
242 | Desklamp provides `` components to link to your routes. These components take a `view` property referring to the route (without the `#`) and `tag` refers to the displayed text of the link.
243 |
244 | ```jsx
245 |
246 | ```
247 |
248 | ###
249 | If you load data in your state or make other asynchronous calls before changing views, use `` rather than ``. This works well for 'get' requests. This component takes the same `view` and `tag` props as the `` component, as well as an additional prop called `func`. The `func` prop will be the function to be executed prior to the view rendering.
250 |
251 | ```jsx
252 |
253 | // you can define this function as part of your `powers` which will be passed in as a prop to the Navigation component.
254 | // declaring it in the `` file will also work.
255 | function getPosts() {
256 | $.get('http://localhost:3000/posts', (postsData) => {
257 | Desklamp.updateState({
258 | posts: postsData,
259 | });
260 | });
261 | }
262 |
263 |
264 | ```
265 |
266 |
267 | ## Upcoming Features
268 | Desklamp automatically keeps track of a history of application state. Currently developing useful rollback of state and exposure of history object to the developer.
269 |
270 | ### Debugging
271 | We are continually adding and improving error handling messages to help with debugging. Please submit any suggestions or requests to help us improve our error messages.
272 |
273 | ### Misc
274 | A floor lamp is a desk lamp if you put it on your desk.
275 |
276 | Here are some blog posts offering a tutorial walk through: [Desklamp: All-in-one routing and immutable state library for React.](https://medium.com/@MKulinski/desklamp-all-in-one-routing-and-immutable-state-library-for-react-47b23821c20#.opngfavlo), and a [React app built 3 ways](https://medium.com/@sneakykate/react-routing-and-state-3-ways-part-3-desklamp-1facca96ac33#.flpdclqkz).
--------------------------------------------------------------------------------
/lib/desklamp.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.AsyncLink = exports.Link = exports.Desklamp = exports.Container = undefined;
7 |
8 | 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; }; }();
9 |
10 | var _react = require('react');
11 |
12 | var _react2 = _interopRequireDefault(_react);
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15 |
16 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
17 |
18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
19 |
20 | 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; }
21 |
22 | 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; }
23 |
24 | // Custom link component
25 | var Link = function Link(_ref) {
26 | var view = _ref.view;
27 | var tag = _ref.tag;
28 |
29 | return _react2.default.createElement(
30 | 'a',
31 | { href: '#' + view },
32 | tag
33 | );
34 | };
35 | // Custom link component to call async functions before routing to page
36 | var AsyncLink = function AsyncLink(_ref2) {
37 | var view = _ref2.view;
38 | var tag = _ref2.tag;
39 | var func = _ref2.func;
40 |
41 | return _react2.default.createElement(
42 | 'a',
43 | { href: '#' + view, onClick: function onClick(e) {
44 | e.preventDefault();Desklamp.syncRoute(view, func);
45 | } },
46 | tag
47 | );
48 | };
49 |
50 | // Object that contains all functions
51 | var Desklamp = {};
52 |
53 | var Container = function (_React$Component) {
54 | _inherits(Container, _React$Component);
55 |
56 | function Container() {
57 | _classCallCheck(this, Container);
58 |
59 | var _this = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this));
60 |
61 | _this.state = {
62 | view: '',
63 | renderNav: '',
64 | appState: {},
65 | routeStates: {},
66 | views: {},
67 | userFunctions: {}
68 | };
69 | // Array that stores the application history
70 | _this.stateHistory = [];
71 | // Adds addFuncs control to the Desklamp obj
72 | _this.addFuncs = _this.addFuncs.bind(_this);
73 | Desklamp.addFunc = _this.addFuncs;
74 | // Binds routing and view functions
75 | _this.changeView = _this.changeView.bind(_this);
76 | Desklamp.changeView = _this.changeView;
77 | _this.routeLink = _this.routeLink.bind(_this);
78 | _this.getRoutes = _this.getRoutes.bind(_this);
79 | // Adds updateState and getState funcs to Desklamp obj
80 | _this.updateState = _this.updateState.bind(_this);
81 | Desklamp.updateState = _this.updateState;
82 | _this.getState = _this.getState.bind(_this);
83 | Desklamp.getState = _this.getState;
84 | // Adds history to Desklamp obj
85 | _this.history = _this.history.bind(_this);
86 | Desklamp.history = _this.history;
87 | // Adds the on function to Desklamp obj
88 | _this.on = _this.on.bind(_this);
89 | Desklamp.on = _this.on;
90 | // Allows the developer to use the componentWillMount on Container component
91 | _this.onLoad = _this.onLoad.bind(_this);
92 | Desklamp.onLoad = _this.onLoad;
93 | // Adds the on function to Desklamp obj to set a default route
94 | _this.defaultRoute = _this.defaultRoute.bind(_this);
95 | Desklamp.defaultRoute = _this.defaultRoute;
96 | // Adds the on function to Desklamp obj
97 | _this.syncRoute = _this.syncRoute.bind(_this);
98 | Desklamp.syncRoute = _this.syncRoute;
99 | return _this;
100 | }
101 |
102 | _createClass(Container, [{
103 | key: 'componentWillMount',
104 | value: function componentWillMount() {
105 | var _this2 = this;
106 |
107 | window.onhashchange = function () {
108 | var pathstring = location.hash;
109 | _this2.routeLink(pathstring.replace('#', ''));
110 | };
111 | this.getRoutes();
112 | this.onLoad();
113 | }
114 |
115 | // Runs all functions passed to onLoad
116 |
117 | }, {
118 | key: 'onLoad',
119 | value: function onLoad() {
120 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
121 | args[_key] = arguments[_key];
122 | }
123 |
124 | args.forEach(function (func) {
125 | func();
126 | });
127 | }
128 | }, {
129 | key: 'getRoutes',
130 | value: function getRoutes() {
131 | var newRoutes = {};
132 | var startRoute = void 0;
133 | var children = this.props.children.constructor === Object ? [this.props.children] : this.props.children;
134 | // if no starting route passed in, go get starting route from first child
135 | // if there are no children of container, default route is '/'
136 | if (!this.props.children) {
137 | startRoute = '';
138 | throw new TypeError('Container must have children components in order to create Routes');
139 | } else {
140 | startRoute = children[0].type;
141 | children.forEach(function (route) {
142 | var routeName = '';
143 | if (route.props.children) {
144 | addChildrenRoutes(routeName, route);
145 | } else {
146 | if (typeof route.props.name === 'string') {
147 | newRoutes['/' + route.props.name] = route.type;
148 | } else {
149 | var _routeName = route.type.name.toLowerCase();
150 | newRoutes['/' + _routeName] = route.type;
151 | }
152 | }
153 |
154 | function addChildrenRoutes(topString, currentChild) {
155 | var name = 'type';
156 | if (typeof currentChild.props.name === 'string') {
157 | name = 'props';
158 | }
159 |
160 | var childRouteName = topString += '/' + currentChild[name].name.toLowerCase();
161 |
162 | newRoutes[childRouteName] = currentChild.type;
163 |
164 | if (currentChild.props.children) {
165 | var _children = currentChild.props.children.constructor === Object ? [currentChild.props.children] : currentChild.props.children;
166 | for (var i = 0; i < _children.length; i++) {
167 | var tempRouteName = childRouteName;
168 | var currChild = _children[i];
169 | var otherName = 'type';
170 | if (typeof currChild.props.name === 'string') {
171 | otherName = 'props';
172 | }
173 | var newRouteName = tempRouteName += '/' + currChild[otherName].name.toLowerCase();
174 | newRoutes[newRouteName] = currChild.type;
175 | if (currChild.props.children) {
176 | addChildrenRoutes(tempRouteName, currChild.props.children);
177 | }
178 | }
179 | }
180 | }
181 | });
182 | }
183 | var newState = Object.assign({}, this.state.views, newRoutes);
184 | var routeName = children[0].props.name || children[0].type.name.toLowerCase();
185 | window.location.hash = '#/' + routeName;
186 | this.setState({ views: newState, view: startRoute });
187 | }
188 | }, {
189 | key: 'syncRoute',
190 | value: function syncRoute(view, func) {
191 | var first = new Promise(function (resolve, reject) {
192 | func();
193 | });
194 | first.then(window.location.hash = view);
195 | }
196 |
197 | // Allows the developer to update the state of their application
198 |
199 | }, {
200 | key: 'updateState',
201 | value: function updateState(newObj) {
202 | if (newObj.constructor === Object) {
203 | // Save old appState to history
204 | this.history(this.state.appState);
205 | // Update appState with new state
206 | var newState = Object.assign({}, this.state.appState, newObj);
207 | this.setState({ appState: newState });
208 | } else {
209 | throw new Error('updateState(): arg must be an object.');
210 | }
211 | }
212 |
213 | // Displays the current application state
214 |
215 | }, {
216 | key: 'getState',
217 | value: function getState() {
218 | return this.state.appState;
219 | }
220 |
221 | // Keeps a point in time snapshot of the application state
222 |
223 | }, {
224 | key: 'history',
225 | value: function history(newState) {
226 | var oldHistory = this.stateHistory;
227 | this.stateHistory = [].concat(_toConsumableArray(oldHistory), [newState]);
228 | }
229 |
230 | // Initializes the default state, user functions, start route and navbar.
231 |
232 | }, {
233 | key: 'on',
234 | value: function on(initState, userFuncs, navbar) {
235 | if (initState.constructor !== Object && initState !== undefined) {
236 | throw new TypeError('on(): takes an object as a first parameter representing initial state');
237 | }
238 | if (userFuncs.constructor !== Object && userFuncs !== undefined) {
239 | throw new TypeError('on(): takes an object as a second parameter which contains functions');
240 | }
241 | if (navbar.constructor !== Function && navbar !== undefined) {
242 | throw new TypeError('on(): takes a React component as a third param');
243 | }
244 | // Update the state to passed in initial state
245 | this.updateState(initState);
246 | // Add userFuncs to the userFunctions object
247 | this.addFuncs(userFuncs);
248 | // If navbar param is set to true we add navbar as the first children
249 | if (!navbar) {
250 | navbar = undefined;
251 | }
252 | this.setState({ renderNav: navbar });
253 | }
254 | }, {
255 | key: 'defaultRoute',
256 | value: function defaultRoute(route) {
257 | var defaultView = Object.assign({}, this.state.views);
258 | var otherName = 'type';
259 | if (typeof route !== 'string') {
260 | if (typeof route.props.name === 'string') {
261 | otherName = 'props';
262 | }
263 | defaultView['/' + route[otherName].name.toLowerCase()] = route.type;
264 | defaultView.default = '/' + route[otherName].name.toLowerCase();
265 | } else {
266 | defaultView.default = route;
267 | }
268 | this.setState({ views: defaultView });
269 | }
270 | }, {
271 | key: 'addFuncs',
272 | value: function addFuncs(input) {
273 | if (input.constructor !== Object) {
274 | throw new TypeError('Input to addFuncs must be an object with methods that are functions');
275 | }
276 | for (var key in input) {
277 | if (input[key].constructor !== Function) {
278 | throw new TypeError('Your input to addFuncs contains ' + key + ' which is not a function');
279 | }
280 | this.state.userFunctions[key] = input[key].bind(this);
281 | }
282 | }
283 | }, {
284 | key: 'changeView',
285 | value: function changeView(view, newState) {
286 | if (typeof view !== 'string') {
287 | throw new Error('changeView(): takes a string as a first parameter');
288 | }
289 | if (newState.constructor !== Object) {
290 | throw new Error('changeView(): takes an object as a second parameter');
291 | }
292 | // update appState only by copying
293 | var notAppState = Object.assign({}, this.state.appState, newState);
294 | // update appState on this.state
295 | this.setState({ appState: notAppState });
296 | window.location.hash = '#/' + view;
297 | }
298 | }, {
299 | key: 'routeLink',
300 | value: function routeLink(view) {
301 | if (this.state.views[view]) {
302 | this.setState({ view: this.state.views[view] }); // TODO: let Dev pass in variable for url string
303 | } else {
304 | window.location.hash = '#' + this.state.views.default;
305 | }
306 | }
307 | }, {
308 | key: 'render',
309 | value: function render() {
310 | var navBar = this.state.renderNav ? _react2.default.createElement(this.state.renderNav, { state: this.state.appState, powers: this.state.userFunctions }) : undefined;
311 | return _react2.default.createElement(
312 | 'div',
313 | null,
314 | navBar,
315 | _react2.default.createElement(this.state.view, { state: this.state.appState, powers: this.state.userFunctions })
316 | );
317 | }
318 | }]);
319 |
320 | return Container;
321 | }(_react2.default.Component);
322 |
323 | exports.Container = Container;
324 | exports.Desklamp = Desklamp;
325 | exports.Link = Link;
326 | exports.AsyncLink = AsyncLink;
--------------------------------------------------------------------------------