UNPKG

feature-router

Version:

Feature Based Navigation (using redux state)

272 lines (221 loc) 10.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.logf = undefined; exports.default = createRouteAspect; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _featureU = require('feature-u'); var _StateRouter = require('./StateRouter'); var _StateRouter2 = _interopRequireDefault(_StateRouter); var _verify = require('./util/verify'); var _verify2 = _interopRequireDefault(_verify); var _lodash = require('lodash.isfunction'); var _lodash2 = _interopRequireDefault(_lodash); var _lodash3 = require('lodash.isplainobject'); var _lodash4 = _interopRequireDefault(_lodash3); 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 _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } // peerDependencies // peerDependency: // our logger (integrated/activated via feature-u) var logf = exports.logf = _featureU.launchApp.diag.logf.newLogger('- ***feature-router*** routeAspect: '); // NOTE: See README for complete description function createRouteAspect() { var namedParams = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; // *** // *** validate parameters // *** var check = _verify2.default.prefix('createRouteAspect() parameter violation: '); // ... namedParams check((0, _lodash4.default)(namedParams), 'only named parameters may be supplied'); // descturcture our individual namedParams // ... NOTE: We do this here (rather in the function signature) to have access // to the overall namedParams variable - for validation purposes! // Access via the JavaScript implicit `arguments[0]` variable is // NOT reliable (in this context) exhibiting a number of quirks :-( var _namedParams$name = namedParams.name, name = _namedParams$name === undefined ? 'route' : _namedParams$name, fallbackElm = namedParams.fallbackElm, componentDidUpdateHook = namedParams.componentDidUpdateHook, _namedParams$allowNoR = namedParams.allowNoRoutes, allowNoRoutes = _namedParams$allowNoR === undefined ? false : _namedParams$allowNoR, unknownNamedArgs = _objectWithoutProperties(namedParams, ['name', 'fallbackElm', 'componentDidUpdateHook', 'allowNoRoutes']); // ... name (NOTE: name check takes precedence to facilitate `Aspect.name` identity in subsequent errors :-) check(name, 'name is required'); check(typeof name === 'string', 'name must be a string'); // NOTE: didn't want to introduce lodash.isstring dependency (in the mix of everything else going on in the 1.0.0 upgrade) // ... unrecognized positional parameter // NOTE: when defaulting entire struct, arguments.length is 0 check(arguments.length <= 1, 'name:' + name + ' ... unrecognized positional parameters (only named parameters can be specified) ... ' + arguments.length + ' positional parameters were found'); // ... unrecognized named parameter var unknownArgKeys = Object.keys(unknownNamedArgs); check(unknownArgKeys.length === 0, 'name:' + name + ' ... unrecognized named parameter(s): ' + unknownArgKeys); // ... fallbackElm check(fallbackElm, 'name:' + name + ' ... fallbackElm is required (a reactElm)'); // AI: check fallbackElm is a reactElm // ... componentDidUpdateHook if (componentDidUpdateHook) { check((0, _lodash2.default)(componentDidUpdateHook), 'name:' + name + ' ... componentDidUpdateHook must be a function (when supplied)'); } // ... allowNoRoutes check(allowNoRoutes === true || allowNoRoutes === false || Array.isArray(allowNoRoutes), 'name:' + name + ' ... allowNoRoutes must be a boolean -or- an array of routes'); // *** // *** create/promote our new aspect // *** var routeAspect = (0, _featureU.createAspect)({ name: name, validateFeatureContent: validateFeatureContent, assembleFeatureContent: assembleFeatureContent, initialRootAppElm: initialRootAppElm, config: { fallbackElm$: fallbackElm, componentDidUpdateHook$: componentDidUpdateHook, allowNoRoutes$: allowNoRoutes } }); return routeAspect; } /** * Validate self's aspect content on supplied feature. * * NOTE: To better understand the context in which any returned * validation messages are used, **feature-u** will prefix them * with: 'createFeature() parameter violation: ' * * @param {Feature} feature - the feature to validate, which is known * to contain this aspect. * * @return {string} an error message when the supplied feature * contains invalid content for this aspect (null when valid). * * @private */ function validateFeatureContent(feature) { var content = feature[this.name]; var errMsg = this.name + ' (when supplied) must be a routeCB or routeCB[] emitted from featureRoute()'; if (Array.isArray(content)) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = content[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var routeCB = _step.value; if (!isValid(routeCB)) { return errMsg; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } else if (!isValid(content)) { return errMsg; } return null; // valid } function isValid(routeCB) { if (!(0, _lodash2.default)(routeCB)) { return false; // must be a function } else if (!Number.isInteger(routeCB.routePriority)) { return false; // must be emitted from featureRoute() } else return true; // valid } /** * Accumulate all routes from our features. * * @param {Fassets} fassets the Fassets object used in cross-feature-communication. * * @param {Feature[]} activeFeatures - The set of active (enabled) * features that comprise this application. * * @private */ function assembleFeatureContent(fassets, activeFeatures) { var _this = this; // accumulate all routes from our features // ... also embellish each route with the featureName for diagnostic purposes var hookSummary = []; var routes = activeFeatures.reduce(function (accum, feature) { var routeContent = feature[_this.name]; if (routeContent) { hookSummary.push('\n Feature.name:' + feature.name + ' <-- promotes ' + _this.name + ' AspectContent'); if (Array.isArray(routeContent)) { accum.push.apply(accum, _toConsumableArray(routeContent)); routeContent.forEach(function (route) { return route.featureName = feature.name; }); } else { accum.push(routeContent); routeContent.featureName = feature.name; } } else { hookSummary.push('\n Feature.name:' + feature.name); } return accum; }, []); // report the accumulation of routes if (routes.length > 0) { logf('assembleFeatureContent() gathered routes from the following Features: ' + hookSummary); } // handle special case where NO routes were gathered from features else { // by default, this is an error condition (when NOT overridden by client) if (!this.config.allowNoRoutes$) { throw new Error('***ERROR*** feature-router found NO routes within your features ' + ('... did you forget to register Feature.' + this.name + ' aspects in your features? ') + '(please refer to the feature-router docs to see how to override this behavior).'); } // when client override is an array, interpret it as routes if (Array.isArray(this.config.allowNoRoutes$)) { logf.force('WARNING: NO routes were found in your Features (i.e. Feature.' + this.name + '), ' + 'but client override (routeAspect.config.allowNoRoutes$=[{routes}];) ' + 'directed a continuation WITH specified routes.'); routes = this.config.allowNoRoutes$; } // otherwise, we simply disable feature-router and continue on else { logf.force('WARNING: NO routes were found in your Features (i.e. Feature.' + this.name + '), ' + 'but client override (routeAspect.config.allowNoRoutes$=truthy;) ' + 'directed a continuation WITHOUT feature-router.'); } } // retain for later use this.routes = routes; } /** * Inject our `<StateRouter>` in the `rootAppElm`. * * We use `initialRootAppElm()` because `<StateRouter>` does NOT * support children (by design). * * @param {Fassets} fassets the Fassets object used in cross-feature-communication. * * @param {reactElm} curRootAppElm - the current react app element root. * * @return {reactElm} rootAppElm seeded with our `<StateRouter>`. * * @private */ function initialRootAppElm(fassets, curRootAppElm) { // no-op if we have NO routes if (this.routes.length === 0) { // NOTE: for this condition, the appropriate logf.force() is generated (above) return curRootAppElm; } // insure we don't clobber any supplied content // ... by design, <StateRouter> doesn't support children if (curRootAppElm) { throw new Error('***ERROR*** Please register routeAspect (from feature-router) before other Aspects ' + 'that inject content in the rootAppElm ... <StateRouter> does NOT support children.'); } // seed the rootAppElm with our StateRouter logf('initialRootAppElm() introducing <StateRouter> component into rootAppElm'); return _react2.default.createElement(_StateRouter2.default, { routes: this.routes, fallbackElm: this.config.fallbackElm$, componentDidUpdateHook: this.config.componentDidUpdateHook$, namedDependencies: { fassets: fassets } }); }