desklamp
Version:
clean routing and state container for react
326 lines (290 loc) • 12.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AsyncLink = exports.Link = exports.Desklamp = exports.Container = undefined;
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; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
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; }
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; }
// Custom link component
var Link = function Link(_ref) {
var view = _ref.view;
var tag = _ref.tag;
return _react2.default.createElement(
'a',
{ href: '#' + view },
tag
);
};
// Custom link component to call async functions before routing to page
var AsyncLink = function AsyncLink(_ref2) {
var view = _ref2.view;
var tag = _ref2.tag;
var func = _ref2.func;
return _react2.default.createElement(
'a',
{ href: '#' + view, onClick: function onClick(e) {
e.preventDefault();Desklamp.syncRoute(view, func);
} },
tag
);
};
// Object that contains all functions
var Desklamp = {};
var Container = function (_React$Component) {
_inherits(Container, _React$Component);
function Container() {
_classCallCheck(this, Container);
var _this = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this));
_this.state = {
view: '',
renderNav: '',
appState: {},
routeStates: {},
views: {},
userFunctions: {}
};
// Array that stores the application history
_this.stateHistory = [];
// Adds addFuncs control to the Desklamp obj
_this.addFuncs = _this.addFuncs.bind(_this);
Desklamp.addFunc = _this.addFuncs;
// Binds routing and view functions
_this.changeView = _this.changeView.bind(_this);
Desklamp.changeView = _this.changeView;
_this.routeLink = _this.routeLink.bind(_this);
_this.getRoutes = _this.getRoutes.bind(_this);
// Adds updateState and getState funcs to Desklamp obj
_this.updateState = _this.updateState.bind(_this);
Desklamp.updateState = _this.updateState;
_this.getState = _this.getState.bind(_this);
Desklamp.getState = _this.getState;
// Adds history to Desklamp obj
_this.history = _this.history.bind(_this);
Desklamp.history = _this.history;
// Adds the on function to Desklamp obj
_this.on = _this.on.bind(_this);
Desklamp.on = _this.on;
// Allows the developer to use the componentWillMount on Container component
_this.onLoad = _this.onLoad.bind(_this);
Desklamp.onLoad = _this.onLoad;
// Adds the on function to Desklamp obj to set a default route
_this.defaultRoute = _this.defaultRoute.bind(_this);
Desklamp.defaultRoute = _this.defaultRoute;
// Adds the on function to Desklamp obj
_this.syncRoute = _this.syncRoute.bind(_this);
Desklamp.syncRoute = _this.syncRoute;
return _this;
}
_createClass(Container, [{
key: 'componentWillMount',
value: function componentWillMount() {
var _this2 = this;
window.onhashchange = function () {
var pathstring = location.hash;
_this2.routeLink(pathstring.replace('#', ''));
};
this.getRoutes();
this.onLoad();
}
// Runs all functions passed to onLoad
}, {
key: 'onLoad',
value: function onLoad() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
args.forEach(function (func) {
func();
});
}
}, {
key: 'getRoutes',
value: function getRoutes() {
var newRoutes = {};
var startRoute = void 0;
var children = this.props.children.constructor === Object ? [this.props.children] : this.props.children;
// if no starting route passed in, go get starting route from first child
// if there are no children of container, default route is '/'
if (!this.props.children) {
startRoute = '';
throw new TypeError('Container must have children components in order to create Routes');
} else {
startRoute = children[0].type;
children.forEach(function (route) {
var routeName = '';
if (route.props.children) {
addChildrenRoutes(routeName, route);
} else {
if (typeof route.props.name === 'string') {
newRoutes['/' + route.props.name] = route.type;
} else {
var _routeName = route.type.name.toLowerCase();
newRoutes['/' + _routeName] = route.type;
}
}
function addChildrenRoutes(topString, currentChild) {
var name = 'type';
if (typeof currentChild.props.name === 'string') {
name = 'props';
}
var childRouteName = topString += '/' + currentChild[name].name.toLowerCase();
newRoutes[childRouteName] = currentChild.type;
if (currentChild.props.children) {
var _children = currentChild.props.children.constructor === Object ? [currentChild.props.children] : currentChild.props.children;
for (var i = 0; i < _children.length; i++) {
var tempRouteName = childRouteName;
var currChild = _children[i];
var otherName = 'type';
if (typeof currChild.props.name === 'string') {
otherName = 'props';
}
var newRouteName = tempRouteName += '/' + currChild[otherName].name.toLowerCase();
newRoutes[newRouteName] = currChild.type;
if (currChild.props.children) {
addChildrenRoutes(tempRouteName, currChild.props.children);
}
}
}
}
});
}
var newState = Object.assign({}, this.state.views, newRoutes);
var routeName = children[0].props.name || children[0].type.name.toLowerCase();
window.location.hash = '#/' + routeName;
this.setState({ views: newState, view: startRoute });
}
}, {
key: 'syncRoute',
value: function syncRoute(view, func) {
var first = new Promise(function (resolve, reject) {
func();
});
first.then(window.location.hash = view);
}
// Allows the developer to update the state of their application
}, {
key: 'updateState',
value: function updateState(newObj) {
if (newObj.constructor === Object) {
// Save old appState to history
this.history(this.state.appState);
// Update appState with new state
var newState = Object.assign({}, this.state.appState, newObj);
this.setState({ appState: newState });
} else {
throw new Error('updateState(): arg must be an object.');
}
}
// Displays the current application state
}, {
key: 'getState',
value: function getState() {
return this.state.appState;
}
// Keeps a point in time snapshot of the application state
}, {
key: 'history',
value: function history(newState) {
var oldHistory = this.stateHistory;
this.stateHistory = [].concat(_toConsumableArray(oldHistory), [newState]);
}
// Initializes the default state, user functions, start route and navbar.
}, {
key: 'on',
value: function on(initState, userFuncs, navbar) {
if (initState.constructor !== Object && initState !== undefined) {
throw new TypeError('on(): takes an object as a first parameter representing initial state');
}
if (userFuncs.constructor !== Object && userFuncs !== undefined) {
throw new TypeError('on(): takes an object as a second parameter which contains functions');
}
if (navbar.constructor !== Function && navbar !== undefined) {
throw new TypeError('on(): takes a React component as a third param');
}
// Update the state to passed in initial state
this.updateState(initState);
// Add userFuncs to the userFunctions object
this.addFuncs(userFuncs);
// If navbar param is set to true we add navbar as the first children
if (!navbar) {
navbar = undefined;
}
this.setState({ renderNav: navbar });
}
}, {
key: 'defaultRoute',
value: function defaultRoute(route) {
var defaultView = Object.assign({}, this.state.views);
var otherName = 'type';
if (typeof route !== 'string') {
if (typeof route.props.name === 'string') {
otherName = 'props';
}
defaultView['/' + route[otherName].name.toLowerCase()] = route.type;
defaultView.default = '/' + route[otherName].name.toLowerCase();
} else {
defaultView.default = route;
}
this.setState({ views: defaultView });
}
}, {
key: 'addFuncs',
value: function addFuncs(input) {
if (input.constructor !== Object) {
throw new TypeError('Input to addFuncs must be an object with methods that are functions');
}
for (var key in input) {
if (input[key].constructor !== Function) {
throw new TypeError('Your input to addFuncs contains ' + key + ' which is not a function');
}
this.state.userFunctions[key] = input[key].bind(this);
}
}
}, {
key: 'changeView',
value: function changeView(view, newState) {
if (typeof view !== 'string') {
throw new Error('changeView(): takes a string as a first parameter');
}
if (newState.constructor !== Object) {
throw new Error('changeView(): takes an object as a second parameter');
}
// update appState only by copying
var notAppState = Object.assign({}, this.state.appState, newState);
// update appState on this.state
this.setState({ appState: notAppState });
window.location.hash = '#/' + view;
}
}, {
key: 'routeLink',
value: function routeLink(view) {
if (this.state.views[view]) {
this.setState({ view: this.state.views[view] }); // TODO: let Dev pass in variable for url string
} else {
window.location.hash = '#' + this.state.views.default;
}
}
}, {
key: 'render',
value: function render() {
var navBar = this.state.renderNav ? _react2.default.createElement(this.state.renderNav, { state: this.state.appState, powers: this.state.userFunctions }) : undefined;
return _react2.default.createElement(
'div',
null,
navBar,
_react2.default.createElement(this.state.view, { state: this.state.appState, powers: this.state.userFunctions })
);
}
}]);
return Container;
}(_react2.default.Component);
exports.Container = Container;
exports.Desklamp = Desklamp;
exports.Link = Link;
exports.AsyncLink = AsyncLink;