lib/Bootstrapper.js (159 lines of code) (raw):

'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); 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; }; }(); /** * Copyright 2016-present, Deloitte Digital. * All rights reserved. * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ var _Habitat = require('./Habitat'); var _Habitat2 = _interopRequireDefault(_Habitat); var _Logger = require('./Logger'); var _Logger2 = _interopRequireDefault(_Logger); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var DEFAULT_HABITAT_SELECTOR = 'data-component'; /** * Safe callback wrapper * @param {null|function} cb - The callback * @param {object} context - The context of the callback * @param {...object} args - Arguments to apply * @private */ function _callback(cb, context) { if (typeof cb === 'function') { for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } cb.call.apply(cb, [context].concat(args)); } } /** * Bootstrapper class */ var Bootstrapper = function () { /** * Constructor */ function Bootstrapper() { _classCallCheck(this, Bootstrapper); // Sanity check if (!window || !window && !window.document) { throw new Error('React Habitat requires a window but cannot see one :('); } /** * The DOM component selector * @type {string} */ this.componentSelector = DEFAULT_HABITAT_SELECTOR; /** * The container * Slashes to avoid super collisions * @type {Container|null} * @private */ this.__container__ = null; } /** * Apply the container to nodes * @param {array} nodes - The elements to parse * @param {function} [cb=null] - Optional callback * @private */ _createClass(Bootstrapper, [{ key: '_apply', value: function _apply(nodes) { var _this = this; var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; // const factory = container.domFactory(); // const id = container.id(); var resolveQueue = []; // Iterate over component elements in the dom var _loop = function _loop(i) { var ele = nodes[i]; // Ignore elements that have already been connected if (_Habitat2.default.hasHabitat(ele)) { return 'continue'; } // Resolve components using promises var componentName = ele.getAttribute(_this.componentSelector); resolveQueue.push(_this.__container__.resolve(componentName, _this).then(function (registration) { // This is an expensive operation so only do on non prod builds if (process.env.NODE_ENV !== 'production') { if (ele.querySelector('[' + _this.componentSelector + ']')) { _Logger2.default.warn('RHW08', 'Component should not contain any nested components.', ele); } } // Generate props var props = _Habitat2.default.parseProps(ele); if (registration.meta.defaultProps) { props = Object.assign({}, registration.meta.defaultProps, props); } // Options var options = registration.meta.options || {}; // Inject the component _this.__container__.factory.inject(registration.component, props, _Habitat2.default.create(ele, _this.__container__.id, options)); }).catch(function (err) { _Logger2.default.error('RHW01', 'Cannot resolve component "' + componentName + '" for element.', err, ele); })); }; for (var i = 0; i < nodes.length; ++i) { var _ret = _loop(i); if (_ret === 'continue') continue; } // Trigger callback when all promises are finished // regardless if some fail Promise.all(resolveQueue.map(function (p) { return p.catch(function (e) { return e; }); })).then(function () { _callback(cb); }).catch(function (err) { // We should never get here.. if we do this is a bug throw err; }); } /** * Set the container * @param {object} container - The container * @param {function} [cb=null] - Optional callback */ }, { key: 'setContainer', value: function setContainer(container) { var _this2 = this; var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (this.__container__ !== null) { _Logger2.default.error('RHW02', 'A container is already set. ' + 'Please call dispose() before assigning a new one.'); return; } if (!container.factory || typeof container.factory.inject !== 'function' || typeof container.factory.dispose !== 'function') { _Logger2.default.error('RHE10', 'Incompatible factory'); return; } // Set the container this.__container__ = container; // Wire up the components from the container this.update(null, function () { _callback(cb, _this2); }); } /** * The container * @returns {Container} */ }, { key: 'update', /** * Apply the container to an updated dom structure * @param {node} node - Target node to parse or null for entire document body * @param {function} [cb=null] - Optional callback */ value: function update(node) { var _this3 = this; var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; // Check if we have a container before attempting an update if (!this.__container__) { _callback(cb); return; } var target = node || window.document.body; var query = target.querySelectorAll('[' + this.componentSelector + ']'); if (!query.length) { // Nothing to update return; } // Lifecycle event // Hook to allow developers to cancel operation if (typeof this.shouldUpdate === 'function') { if (this.shouldUpdate(target, query) === false) { _callback(cb, this); return; } } // Lifecycle event if (typeof this.willUpdate === 'function') { this.willUpdate(target, query); } this._apply(query, function () { // Lifecycle event if (typeof _this3.didUpdate === 'function') { _this3.didUpdate(target); } _callback(cb, _this3); }); } /** * Unmount all habitat instances for the container * @param {function} [cb=null] - Optional callback */ }, { key: 'unmountHabitats', value: function unmountHabitats() { var cb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; // Lifecycle event if (typeof this.willUnmountHabitats === 'function') { this.willUnmountHabitats(); } // Get open habitats for this container var habitats = _Habitat2.default.listHabitats(this.__container__.id); // Clean up for (var i = 0; i < habitats.length; ++i) { this.__container__.factory.dispose(habitats[i]); _Habitat2.default.destroy(habitats[i]); } // Lifecycle event if (typeof this.didUnmountHabitats === 'function') { this.didUnmountHabitats(); } // Handle callback _callback(cb, this); } /** * Dispose the container and destroy habitat instances * @param {function} [cb=null] - Optional callback */ }, { key: 'dispose', value: function dispose() { var _this4 = this; var cb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; this.unmountHabitats(function () { // Reset and release _this4.__container__ = null; // Lifecycle event if (typeof _this4.didDispose === 'function') { _this4.didDispose(); } // Handle callback _callback(cb, _this4); }); } }, { key: 'container', get: function get() { return this.__container__; } }]); return Bootstrapper; }(); exports.default = Bootstrapper; module.exports = exports['default'];