react-habitat
Advanced tools
Comparing version 0.4.2 to 0.5.0-beta
## React Habitat Change log | ||
### [0.5.0] | ||
- Updated packages in examples | ||
- Updated readme with JSON encoding information [#11](https://github.com/DeloitteDigitalAPAC/react-habitat/issues/11). Thanks @joshuakelly | ||
- Deprecated elements property from Bootstrapper | ||
- Added dynamic html wire up support [#12](https://github.com/DeloitteDigitalAPAC/react-habitat/issues/12) | ||
- Small optimisation wins for production builds | ||
- Removed all deprecated methods | ||
### [0.4.2] | ||
@@ -4,0 +13,0 @@ |
@@ -55,3 +55,3 @@ (function webpackUniversalModuleDefinition(root, factory) { | ||
/* 0 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -83,5 +83,5 @@ 'use strict'; | ||
/***/ }, | ||
/***/ }), | ||
/* 1 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -91,3 +91,3 @@ 'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -115,26 +115,30 @@ | ||
/** | ||
* Safely log to the console | ||
*/ | ||
log = function log(type, args) { | ||
/** | ||
* Safely log to the console | ||
*/ | ||
log = function log(type, args) { | ||
if (typeof console !== 'undefined' && console[type]) { | ||
if (console[type].apply) { | ||
console[type].apply(undefined, args); | ||
} else { | ||
// IE9 Fallback | ||
console[type](args); | ||
} | ||
} | ||
}; | ||
if (typeof console !== 'undefined' && console[type]) { | ||
console[type].apply(undefined, args); | ||
} | ||
}; | ||
/** | ||
* Concats the message and arguments into a single array | ||
*/ | ||
concatArgs = function concatArgs(msg, args) { | ||
var throwArgs = [msg]; | ||
/** | ||
* Concats the message and arguments into a single array | ||
*/ | ||
concatArgs = function concatArgs(msg, args) { | ||
var throwArgs = [msg]; | ||
if (args && args.length) { | ||
for (var i = 0; i < args.length; i++) { | ||
throwArgs.push(args[i]); | ||
} | ||
} | ||
if (args && args.length > 2) { | ||
for (var i = 2; i < args.length; i++) { | ||
throwArgs.push(args[i]); | ||
} | ||
} | ||
return throwArgs; | ||
}; | ||
return throwArgs; | ||
}; | ||
} | ||
@@ -147,35 +151,45 @@ | ||
var Logger = function () { | ||
function Logger() { | ||
_classCallCheck(this, Logger); | ||
} | ||
function Logger() { | ||
_classCallCheck(this, Logger); | ||
} | ||
_createClass(Logger, null, [{ | ||
key: 'warn', | ||
_createClass(Logger, null, [{ | ||
key: 'warn', | ||
/** | ||
* Log a warning | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The warning message | ||
*/ | ||
value: function warn(code, msg) { | ||
var args = concatArgs('WARNING: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), arguments); | ||
log('warn', args); | ||
} | ||
/** | ||
* Log a warning | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The warning message | ||
* @param {Array} debugs - Any debugging arguments | ||
*/ | ||
value: function warn(code, msg) { | ||
for (var _len = arguments.length, debugs = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
debugs[_key - 2] = arguments[_key]; | ||
} | ||
/** | ||
* Log an error | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The error message | ||
*/ | ||
var args = concatArgs('WARNING: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), debugs); | ||
log('warn', args); | ||
} | ||
}, { | ||
key: 'error', | ||
value: function error(code, msg) { | ||
var args = concatArgs('ERROR: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), arguments); | ||
log('error', args); | ||
} | ||
}]); | ||
/** | ||
* Log an error | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The error message | ||
* @param {Array} debugs - Any debugging arguments | ||
*/ | ||
return Logger; | ||
}, { | ||
key: 'error', | ||
value: function error(code, msg) { | ||
for (var _len2 = arguments.length, debugs = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { | ||
debugs[_key2 - 2] = arguments[_key2]; | ||
} | ||
var args = concatArgs('ERROR: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), debugs); | ||
log('error', args); | ||
} | ||
}]); | ||
return Logger; | ||
}(); | ||
@@ -186,5 +200,5 @@ | ||
/***/ }, | ||
/***/ }), | ||
/* 2 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -220,9 +234,25 @@ 'use strict'; | ||
/** | ||
* Parses a container and populate components | ||
* Safe callback wrapper | ||
* @param {null|function} cb - The callback | ||
* @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)); | ||
} | ||
} | ||
/** | ||
* Apply a container to nodes and populate components | ||
* @param {array} container The container | ||
* @param {array} elements The elements to parse | ||
* @param {array} nodes The elements to parse | ||
* @param {string} componentSelector The component selector | ||
* @param cb | ||
* @param {function} [cb=null] - Optional callback | ||
* @private | ||
*/ | ||
function parseContainer(container, elements, componentSelector) { | ||
function _applyContainer(container, nodes, componentSelector) { | ||
var cb = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; | ||
@@ -233,22 +263,48 @@ | ||
var id = container.id(); | ||
var resolveQueue = []; | ||
// Iterate over component elements in the dom | ||
for (var i = 0; i < elements.length; ++i) { | ||
var ele = elements[i]; | ||
var _loop = function _loop(i) { | ||
var ele = nodes[i]; | ||
// Ignore elements that have already been connected | ||
if (_Habitat2.default.hasHabitat(ele)) { | ||
return 'continue'; | ||
} | ||
var componentName = ele.getAttribute(componentSelector); | ||
var component = container.resolve(componentName); | ||
resolveQueue.push(container.resolve(componentName).then(function (component) { | ||
// This is an expensive operation so only do on non prod builds | ||
if (true) { | ||
if (ele.querySelector('[' + componentSelector + ']')) { | ||
_Logger2.default.warn('RHW08', 'Component should not contain any nested components.', ele); | ||
} | ||
} | ||
if (component) { | ||
if (ele.querySelector('[' + componentSelector + ']')) { | ||
_Logger2.default.warn('RHW08', 'Component should not contain any nested components.', ele); | ||
} | ||
// Inject the component | ||
factory.inject(component, _Habitat2.default.parseProps(ele), _Habitat2.default.create(ele, id)); | ||
} else { | ||
_Logger2.default.error('RHW01', 'Cannot resolve component "' + componentName + '" for element.', ele); | ||
} | ||
}).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; | ||
} | ||
if (typeof cb === 'function') { | ||
cb.call(); | ||
} | ||
// 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; | ||
}); | ||
} | ||
@@ -273,10 +329,28 @@ | ||
// Set dom component selector | ||
/** | ||
* The DOM component selector | ||
* @type {string} | ||
*/ | ||
this.componentSelector = DEFAULT_HABITAT_SELECTOR; | ||
// The target elements | ||
this._elements = null; | ||
/** | ||
* The container | ||
* @type {Container|null} | ||
* @private | ||
*/ | ||
this._container = null; | ||
// The container | ||
this._container = null; | ||
/** | ||
* The watcher's observer instance or null | ||
* @type {MutationObserver|null} | ||
* @private | ||
*/ | ||
this._observer = null; | ||
/** | ||
* Observing persistence status flag | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
this._isWatching = false; | ||
} | ||
@@ -294,2 +368,4 @@ | ||
value: function setContainer(container) { | ||
var _this = this; | ||
var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
@@ -306,10 +382,135 @@ | ||
// Find all the elements in the dom with the component selector attribute | ||
this._elements = window.document.body.querySelectorAll('[' + this.componentSelector + ']'); | ||
// Wire up the components from the container | ||
parseContainer(this._container, this._elements, this.componentSelector, cb); | ||
this.update(null, function () { | ||
_callback(cb, _this); | ||
}); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
}, { | ||
key: 'update', | ||
value: function update(node) { | ||
var _this2 = 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.querySelectorAll('[' + this.componentSelector + ']'); | ||
// Lifecycle event | ||
// Hook to allow developers to cancel operation | ||
if (typeof this.shouldUpdate === 'function') { | ||
if (this.shouldUpdate(target) === false) { | ||
_callback(cb, this); | ||
return; | ||
} | ||
} | ||
// Temporarily stop the watcher from triggering from our own Habitat injections | ||
// This is better for performance however, this could possibly miss any | ||
// mutations during the parsing time.. possible bug maybe? dont know yet. | ||
var shouldWatcherPersist = this._isWatching; | ||
if (shouldWatcherPersist) { | ||
this.stopWatcher(); | ||
} | ||
// Lifecycle event | ||
if (typeof this.willUpdate === 'function') { | ||
this.willUpdate(target); | ||
} | ||
_applyContainer(this._container, target, this.componentSelector, function () { | ||
// Restart the dom watcher if persisting | ||
if (shouldWatcherPersist) { | ||
_this2.startWatcher(); | ||
} | ||
// Lifecycle event | ||
if (typeof _this2.didUpdate === 'function') { | ||
_this2.didUpdate(target); | ||
} | ||
_callback(cb, _this2); | ||
}); | ||
} | ||
/** | ||
* Start DOM watcher for auto wire ups | ||
*/ | ||
}, { | ||
key: 'startWatcher', | ||
value: function startWatcher() { | ||
// Feature available? | ||
if (typeof MutationObserver === 'undefined') { | ||
_Logger2.default.error('RHE09', 'MutationObserver not available.'); | ||
return; | ||
} | ||
// Create observer if not assigned already | ||
if (!this._observer) { | ||
this._observer = new MutationObserver(this._handleDomMutation.bind(this)); | ||
} | ||
// Start observing for dom changes filtered by our component selector | ||
this._observer.observe(window.document.body, { | ||
childList: true, | ||
attributes: true, | ||
subtree: true, | ||
attributeOldValue: false, | ||
characterData: false, | ||
characterDataOldValue: false, | ||
attributeFilter: [this.componentSelector] | ||
}); | ||
// Set flag for persistence during update's | ||
this._isWatching = true; | ||
} | ||
/** | ||
* Stop the DOM watcher if running | ||
*/ | ||
}, { | ||
key: 'stopWatcher', | ||
value: function stopWatcher() { | ||
if (this._observer && this._observer.disconnect) { | ||
this._observer.disconnect(); | ||
} | ||
this._isWatching = false; | ||
} | ||
/** | ||
* Handle dom mutation event | ||
* @param {MutationRecord} mutationRecord - The mutation record | ||
*/ | ||
}, { | ||
key: '_handleDomMutation', | ||
value: function _handleDomMutation(mutationRecord) { | ||
if (typeof mutationRecord !== 'undefined') { | ||
// Only run update if nodes have been added | ||
var diff = mutationRecord.filter(function (r) { | ||
return r.addedNodes.length; | ||
}); | ||
if (diff.length) { | ||
this.update(); | ||
} | ||
} else { | ||
// Fallback | ||
this.update(); | ||
} | ||
} | ||
/** | ||
* Dispose the container and destroy habitat instances | ||
@@ -325,2 +526,5 @@ * @param {function} [cb=null] - Optional callback | ||
// Stop dom watcher if any | ||
this.stopWatcher(); | ||
// get the container's factory | ||
@@ -340,8 +544,7 @@ var factory = this._container.domFactory(); | ||
this._container = null; | ||
this._elements = null; | ||
this._observer = null; | ||
this._isWatching = false; | ||
// Handle callback | ||
if (typeof cb === 'function') { | ||
cb.call(); | ||
} | ||
_callback(cb, this); | ||
} | ||
@@ -356,5 +559,5 @@ }]); | ||
/***/ }, | ||
/***/ }), | ||
/* 3 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -367,11 +570,11 @@ 'use strict'; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
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 _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 _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** | ||
* 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. | ||
*/ | ||
@@ -382,6 +585,2 @@ var _ReactDomFactory = __webpack_require__(6); | ||
var _Logger = __webpack_require__(1); | ||
var _Logger2 = _interopRequireDefault(_Logger); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -396,8 +595,7 @@ | ||
*/ | ||
var assignId = function idFactory() { | ||
var _nextId = 0; | ||
var _assignId = function idFactory() { | ||
var nextId = 0; | ||
return function _assignId() { | ||
_nextId++; | ||
return 'C' + _nextId; | ||
nextId = nextId + 1; | ||
return 'C' + nextId; | ||
}; | ||
@@ -407,2 +605,12 @@ }(); | ||
/** | ||
* Determine if the object is a Promise | ||
* @param {*} obj - The test object | ||
* @returns {boolean} - True if deemed to be a promise | ||
* @private | ||
*/ | ||
var _isPromise = function _isPromise(obj) { | ||
return !!obj && ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; | ||
}; | ||
/** | ||
* The Container class | ||
@@ -420,3 +628,3 @@ */ | ||
this._components = {}; | ||
this._id = assignId(); | ||
this._id = _assignId(); | ||
} | ||
@@ -438,4 +646,4 @@ | ||
* Register a component in the container | ||
* @param {string} name - A unique component key | ||
* @param {object} comp - The component | ||
* @param {string} name - A unique component key | ||
* @param {object|Promise} comp - The component or Promise | ||
*/ | ||
@@ -469,4 +677,4 @@ | ||
* Resolve a component from the container | ||
* @param {string} name - The unique component key | ||
* @returns {object} | ||
* @param {string} name - The unique component key | ||
* @returns {Promise} | ||
*/ | ||
@@ -477,3 +685,13 @@ | ||
value: function resolve(name) { | ||
return this._components[name]; | ||
var registration = this._components[name]; | ||
if (!registration) { | ||
return Promise.reject(new Error('Not Registered')); | ||
} | ||
if (_isPromise(registration)) { | ||
return registration; | ||
} | ||
return Promise.resolve(registration); | ||
} | ||
@@ -491,42 +709,2 @@ | ||
} | ||
/** | ||
* Register a component in the container | ||
* @param {string} name - A unique component key | ||
* @param {object} comp - The component | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'registerComponent', | ||
value: function registerComponent(name, comp) { | ||
_Logger2.default.warn('RHW03', 'registerComponent is being deprecated. Please use "register" instead.'); | ||
this.register(name, comp); | ||
} | ||
/** | ||
* Register multiple components to the container | ||
* @param {object} comps - The components | ||
*/ | ||
}, { | ||
key: 'registerComponents', | ||
value: function registerComponents(comps) { | ||
_Logger2.default.warn('RHW03', 'registerComponents is being deprecated. Please use "registerAll" instead.'); | ||
this.registerAll(comps); | ||
} | ||
/** | ||
* Gets a component for key | ||
* @param name | ||
* @returns {Object} | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'getComponent', | ||
value: function getComponent(name) { | ||
_Logger2.default.warn('RHW03', 'getComponent is being deprecated. Please use "resolve" instead.'); | ||
return this.resolve(name); | ||
} | ||
}]); | ||
@@ -540,5 +718,5 @@ | ||
/***/ }, | ||
/***/ }), | ||
/* 4 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -628,3 +806,4 @@ 'use strict'; | ||
var props = { | ||
proxy: ele }; | ||
proxy: ele // Pass in a reference to the original node | ||
}; | ||
@@ -749,3 +928,4 @@ // Populate custom props from reading any ele attributes that start with 'data-prop-' | ||
try { | ||
// It might be better if we keep references in a weak map, need to look at this in the future | ||
// It might be better if we keep references in a weak map, need to look | ||
// at this in the future | ||
habitat[HABITAT_HOST_KEY] = host; | ||
@@ -814,5 +994,5 @@ } catch (e) { | ||
/***/ }, | ||
/***/ }), | ||
/* 5 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -825,2 +1005,5 @@ 'use strict'; | ||
exports._Mixin = 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; }; }(); | ||
exports.createBootstrapper = createBootstrapper; | ||
@@ -860,3 +1043,3 @@ | ||
*/ | ||
function _Mixin(spec) { | ||
function _Mixin(spec, callback) { | ||
_classCallCheck(this, _Mixin); | ||
@@ -877,2 +1060,7 @@ | ||
// Set the watcher value if defined | ||
if (typeof spec.enableWatcher === 'boolean') { | ||
_this.enableWatcher = spec.enableWatcher; | ||
} | ||
// Create a new container | ||
@@ -886,7 +1074,34 @@ var container = new _Container2.default(); | ||
_this._shouldUpdateProxy = spec.shouldUpdate || null; | ||
_this._willUpdateProxy = spec.willUpdate || null; | ||
_this._didUpdateProxy = spec.didUpdate || null; | ||
// Finally, set the container | ||
_this.setContainer(container); | ||
_this.setContainer(container, callback); | ||
return _this; | ||
} | ||
_createClass(_Mixin, [{ | ||
key: 'shouldUpdate', | ||
value: function shouldUpdate(node) { | ||
if (this._shouldUpdateProxy) { | ||
this._shouldUpdateProxy(node); | ||
} | ||
} | ||
}, { | ||
key: 'willUpdate', | ||
value: function willUpdate() { | ||
if (this._willUpdateProxy) { | ||
this._willUpdateProxy(); | ||
} | ||
} | ||
}, { | ||
key: 'didUpdate', | ||
value: function didUpdate() { | ||
if (this._didUpdateProxy) { | ||
this._didUpdateProxy(); | ||
} | ||
} | ||
}]); | ||
return _Mixin; | ||
@@ -901,8 +1116,10 @@ }(_Bootstrapper3.default); | ||
function createBootstrapper(spec) { | ||
return new _Mixin(spec); | ||
var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
return new _Mixin(spec, cb); | ||
} | ||
/***/ }, | ||
/***/ }), | ||
/* 6 */ | ||
/***/ function(module, exports, __webpack_require__) { | ||
/***/ (function(module, exports, __webpack_require__) { | ||
@@ -986,17 +1203,17 @@ 'use strict'; | ||
/***/ }, | ||
/***/ }), | ||
/* 7 */ | ||
/***/ function(module, exports) { | ||
/***/ (function(module, exports) { | ||
module.exports = __WEBPACK_EXTERNAL_MODULE_7__; | ||
/***/ }, | ||
/***/ }), | ||
/* 8 */ | ||
/***/ function(module, exports) { | ||
/***/ (function(module, exports) { | ||
module.exports = __WEBPACK_EXTERNAL_MODULE_8__; | ||
/***/ } | ||
/***/ }) | ||
/******/ ]) | ||
}); | ||
; |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("React"),require("ReactDOM")):"function"==typeof define&&define.amd?define(["React","ReactDOM"],t):"object"==typeof exports?exports.ReactHabitat=t(require("React"),require("ReactDOM")):e.ReactHabitat=t(e.React,e.ReactDOM)}(this,function(e,t){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(2),a=r(o),i=n(3),u=r(i),l=n(5);t.default={Bootstrapper:a.default,Container:u.default,createBootstrapper:l.createBootstrapper},e.exports=t.default},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=function(){},i=a,u=a,l="http://tinyurl.com/jxryd3s",c=function(){function e(){r(this,e)}return o(e,null,[{key:"warn",value:function(e,t){var n=u("WARNING: "+e+" "+t+" "+l+"#"+e.toLowerCase(),arguments);i("warn",n)}},{key:"error",value:function(e,t){var n=u("ERROR: "+e+" "+t+" "+l+"#"+e.toLowerCase(),arguments);i("error",n)}}]),e}();t.default=c,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t,n){for(var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o=e.domFactory(),a=e.id(),i=0;t.length>i;++i){var u=t[i],c=u.getAttribute(n),f=e.resolve(c);f?(u.querySelector("["+n+"]")&&s.default.warn("RHW08","Component should not contain any nested components.",u),o.inject(f,l.default.parseProps(u),l.default.create(u,a))):s.default.error("RHW01",'Cannot resolve component "'+c+'" for element.',u)}"function"==typeof r&&r.call()}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),u=n(4),l=r(u),c=n(1),s=r(c),f="data-component",d=function(){function e(){if(o(this,e),!window||!window&&!window.document)throw Error("React Habitat requires a window but cannot see one :(");this.componentSelector=f,this._elements=null,this._container=null}return i(e,[{key:"setContainer",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return null!==this._container?void s.default.error("RHW02","A container is already set. Please call dispose() before assigning a new one."):(this._container=e,this._elements=window.document.body.querySelectorAll("["+this.componentSelector+"]"),void a(this._container,this._elements,this.componentSelector,t))}},{key:"dispose",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=this._container.domFactory(),n=window.document.body.querySelectorAll('[data-habitat="'+this._container.id()+'"]'),r=0;n.length>r;++r)t.dispose(n[r]),l.default.destroy(n[r]);this._container=null,this._elements=null,"function"==typeof e&&e.call()}}]),e}();t.default=d,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},i=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),u=n(6),l=r(u),c=n(1),s=r(c),f=function(){var e=0;return function(){return e++,"C"+e}}(),d=function(){function e(){o(this,e),this._components={},this._id=f()}return i(e,[{key:"id",value:function(){return this._id}},{key:"register",value:function(e,t){if("string"!=typeof e)throw Error("Unexpected component key. Expects a string.",e);this._components[e]=t}},{key:"registerAll",value:function(e){if("object"!==(void 0===e?"undefined":a(e)))throw Error("Unexpected components type. Expects type object",e);Object.assign(this._components,e)}},{key:"resolve",value:function(e){return this._components[e]}},{key:"domFactory",value:function(){return l.default}},{key:"registerComponent",value:function(e,t){s.default.warn("RHW03",'registerComponent is being deprecated. Please use "register" instead.'),this.register(e,t)}},{key:"registerComponents",value:function(e){s.default.warn("RHW03",'registerComponents is being deprecated. Please use "registerAll" instead.'),this.registerAll(e)}},{key:"getComponent",value:function(e){return s.default.warn("RHW03",'getComponent is being deprecated. Please use "resolve" instead.'),this.resolve(e)}}]),e}();t.default=d,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e){var t=e.currentStyle||window.getComputedStyle(e,"");return t.display}function i(e){return e[1].toUpperCase()}function u(e,t){return t.replace(e,"").replace(/-([a-z])/g,i)}Object.defineProperty(t,"__esModule",{value:!0});var l=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),c=n(1),s=r(c),f="habitatHostElement",d="data-habitat",p="data-has-habitat",y="data-prop-",b="data-props",v="data-n-prop-",h="data-r-prop-",m=!1,w=function(){function e(){o(this,e)}return l(e,null,[{key:"parseProps",value:function(e){for(var t={proxy:e},n=0;e.attributes.length>n;n++){var r=e.attributes[n];if(0===r.name.indexOf(y)){var o=u(y,r.name),a=r.value||"";"string"==typeof a&&"false"===a.toLowerCase()&&(a=!1),"string"==typeof a&&"true"===a.toLowerCase()&&(a=!0),"string"==typeof a&&a.length>=2&&("{"===a[0]&&"}"===a[a.length-1]||"["===a[0]&&"]"===a[a.length-1])&&(a=JSON.parse(a)),"string"==typeof a&&"null"===a.toLowerCase()&&(a=null),t[o]=a}else if(r.name===b)Object.assign(t,JSON.parse(r.value));else if(0===r.name.indexOf("data-n-prop-")){var i=u(v,r.name);t[i]=parseFloat(r.value)}else if(window&&0===r.name.indexOf(h)){var l=u(h,r.name);t[l]=window[r.value]}}return t}},{key:"create",value:function(e,t){if(window.document.body===e||null===e||void 0===e)return s.default.warn("RHW04","Cannot open a habitat for element.",e),null;var n="span";"block"===a(e)&&(n="div");var r=window.document.createElement(n),o=e.getAttribute("data-habitat-class")||null,i=!1;if(null!==e.getAttribute("data-habitat-no-replace")&&(i="true"===e.getAttribute("data-habitat-no-replace").toLocaleLowerCase()),r.setAttribute(d,t),o&&(r.className=""+o),e.parentNode.insertBefore(r,e.nextSibling),"INPUT"!==e.tagName){if(!i){var u=e.parentNode.removeChild(e);try{r[f]=u}catch(l){m&&(s.default.warn("RHW06","Arbitrary properties are disabled. The container may not dispose correctly.",l),m=!0)}}}else e.setAttribute(p,"true"),"hidden"!==e.getAttribute("type")&&e.setAttribute("style","display: none;");return r}},{key:"hasHabitat",value:function(e){return null!==e.getAttribute(p)}},{key:"destroy",value:function(e){try{void 0!==e[f]&&e.parentNode.insertBefore(e[f],e)}finally{e.parentNode.removeChild(e)}}}]),e}();t.default=w,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(e){return new d(e)}Object.defineProperty(t,"__esModule",{value:!0}),t._Mixin=void 0,t.createBootstrapper=u;var l=n(2),c=r(l),s=n(3),f=r(s),d=t._Mixin=function(e){function t(e){o(this,t);var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));if(!e.container)return console.warn('"Container" property was not supplied'),a(n);e.componentSelector&&(n.componentSelector=e.componentSelector);for(var r=new f.default,i=0;e.container.length>i;i++)r.register(e.container[i].register,e.container[i].for);return n.setContainer(r),n}return i(t,e),t}(c.default)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(7),u=r(i),l=n(8),c=r(l),s=n(1),f=r(s),d=function(){function e(){o(this,e)}return a(e,null,[{key:"inject",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2];n?c.default.render(u.default.createElement(e,t||{}),n):f.default.warn("RHW07","Target element is null or undefined.")}},{key:"dispose",value:function(e){e&&c.default.unmountComponentAtNode(e)}}]),e}();t.default=d,e.exports=t.default},function(t,n){t.exports=e},function(e,n){e.exports=t}])}); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("React"),require("ReactDOM")):"function"==typeof define&&define.amd?define(["React","ReactDOM"],t):"object"==typeof exports?exports.ReactHabitat=t(require("React"),require("ReactDOM")):e.ReactHabitat=t(e.React,e.ReactDOM)}(this,function(e,t){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(2),a=r(o),i=n(3),u=r(i),l=n(5);t.default={Bootstrapper:a.default,Container:u.default,createBootstrapper:l.createBootstrapper},e.exports=t.default},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=function(){},i=a,u=a,l="http://tinyurl.com/jxryd3s",c=function(){function e(){r(this,e)}return o(e,null,[{key:"warn",value:function(e,t){for(var n=arguments.length,r=Array(n>2?n-2:0),o=2;n>o;o++)r[o-2]=arguments[o];var a=u("WARNING: "+e+" "+t+" "+l+"#"+e.toLowerCase(),r);i("warn",a)}},{key:"error",value:function(e,t){for(var n=arguments.length,r=Array(n>2?n-2:0),o=2;n>o;o++)r[o-2]=arguments[o];var a=u("ERROR: "+e+" "+t+" "+l+"#"+e.toLowerCase(),r);i("error",a)}}]),e}();t.default=c,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if("function"==typeof e){for(var n=arguments.length,r=Array(n>2?n-2:0),o=2;n>o;o++)r[o-2]=arguments[o];e.call.apply(e,[t].concat(r))}}function i(e,t,n){for(var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null,o=e.domFactory(),i=e.id(),u=[],l=function(r){var a=t[r];if(c.default.hasHabitat(a))return"continue";var l=a.getAttribute(n);u.push(e.resolve(l).then(function(e){o.inject(e,c.default.parseProps(a),c.default.create(a,i))}).catch(function(e){f.default.error("RHW01",'Cannot resolve component "'+l+'" for element.',e,a)}))},s=0;t.length>s;++s){l(s)}Promise.all(u.map(function(e){return e.catch(function(e){return e})})).then(function(){a(r)}).catch(function(e){throw e})}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),l=n(4),c=r(l),s=n(1),f=r(s),d="data-component",p=function(){function e(){if(o(this,e),!window||!window&&!window.document)throw Error("React Habitat requires a window but cannot see one :(");this.componentSelector=d,this._container=null,this._observer=null,this._isWatching=!1}return u(e,[{key:"setContainer",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return null!==this._container?void f.default.error("RHW02","A container is already set. Please call dispose() before assigning a new one."):(this._container=e,void this.update(null,function(){a(n,t)}))}},{key:"update",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!this._container)return void a(n);var r=e||window.document.body.querySelectorAll("["+this.componentSelector+"]");if("function"==typeof this.shouldUpdate&&this.shouldUpdate(r)===!1)return void a(n,this);var o=this._isWatching;o&&this.stopWatcher(),"function"==typeof this.willUpdate&&this.willUpdate(r),i(this._container,r,this.componentSelector,function(){o&&t.startWatcher(),"function"==typeof t.didUpdate&&t.didUpdate(r),a(n,t)})}},{key:"startWatcher",value:function(){return"undefined"==typeof MutationObserver?void f.default.error("RHE09","MutationObserver not available."):(this._observer||(this._observer=new MutationObserver(this._handleDomMutation.bind(this))),this._observer.observe(window.document.body,{childList:!0,attributes:!0,subtree:!0,attributeOldValue:!1,characterData:!1,characterDataOldValue:!1,attributeFilter:[this.componentSelector]}),void(this._isWatching=!0))}},{key:"stopWatcher",value:function(){this._observer&&this._observer.disconnect&&this._observer.disconnect(),this._isWatching=!1}},{key:"_handleDomMutation",value:function(e){if(void 0!==e){var t=e.filter(function(e){return e.addedNodes.length});t.length&&this.update()}else this.update()}},{key:"dispose",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;this.stopWatcher();for(var t=this._container.domFactory(),n=window.document.body.querySelectorAll('[data-habitat="'+this._container.id()+'"]'),r=0;n.length>r;++r)t.dispose(n[r]),c.default.destroy(n[r]);this._container=null,this._observer=null,this._isWatching=!1,a(e,this)}}]),e}();t.default=p,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},u=n(6),l=r(u),c=function(){var e=0;return function(){return e+=1,"C"+e}}(),s=function(e){return!!e&&("object"===(void 0===e?"undefined":i(e))||"function"==typeof e)&&"function"==typeof e.then},f=function(){function e(){o(this,e),this._components={},this._id=c()}return a(e,[{key:"id",value:function(){return this._id}},{key:"register",value:function(e,t){if("string"!=typeof e)throw Error("Unexpected component key. Expects a string.",e);this._components[e]=t}},{key:"registerAll",value:function(e){if("object"!==(void 0===e?"undefined":i(e)))throw Error("Unexpected components type. Expects type object",e);Object.assign(this._components,e)}},{key:"resolve",value:function(e){var t=this._components[e];return t?s(t)?t:Promise.resolve(t):Promise.reject(Error("Not Registered"))}},{key:"domFactory",value:function(){return l.default}}]),e}();t.default=f,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e){var t=e.currentStyle||window.getComputedStyle(e,"");return t.display}function i(e){return e[1].toUpperCase()}function u(e,t){return t.replace(e,"").replace(/-([a-z])/g,i)}Object.defineProperty(t,"__esModule",{value:!0});var l=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),c=n(1),s=r(c),f="habitatHostElement",d="data-habitat",p="data-has-habitat",h="data-prop-",y="data-props",v="data-n-prop-",b="data-r-prop-",m=!1,w=function(){function e(){o(this,e)}return l(e,null,[{key:"parseProps",value:function(e){for(var t={proxy:e},n=0;e.attributes.length>n;n++){var r=e.attributes[n];if(0===r.name.indexOf(h)){var o=u(h,r.name),a=r.value||"";"string"==typeof a&&"false"===a.toLowerCase()&&(a=!1),"string"==typeof a&&"true"===a.toLowerCase()&&(a=!0),"string"==typeof a&&a.length>=2&&("{"===a[0]&&"}"===a[a.length-1]||"["===a[0]&&"]"===a[a.length-1])&&(a=JSON.parse(a)),"string"==typeof a&&"null"===a.toLowerCase()&&(a=null),t[o]=a}else if(r.name===y)Object.assign(t,JSON.parse(r.value));else if(0===r.name.indexOf("data-n-prop-")){var i=u(v,r.name);t[i]=parseFloat(r.value)}else if(window&&0===r.name.indexOf(b)){var l=u(b,r.name);t[l]=window[r.value]}}return t}},{key:"create",value:function(e,t){if(window.document.body===e||null===e||void 0===e)return s.default.warn("RHW04","Cannot open a habitat for element.",e),null;var n="span";"block"===a(e)&&(n="div");var r=window.document.createElement(n),o=e.getAttribute("data-habitat-class")||null,i=!1;if(null!==e.getAttribute("data-habitat-no-replace")&&(i="true"===e.getAttribute("data-habitat-no-replace").toLocaleLowerCase()),r.setAttribute(d,t),o&&(r.className=""+o),e.parentNode.insertBefore(r,e.nextSibling),"INPUT"!==e.tagName){if(!i){var u=e.parentNode.removeChild(e);try{r[f]=u}catch(e){m&&(s.default.warn("RHW06","Arbitrary properties are disabled. The container may not dispose correctly.",e),m=!0)}}}else e.setAttribute(p,"true"),"hidden"!==e.getAttribute("type")&&e.setAttribute("style","display: none;");return r}},{key:"hasHabitat",value:function(e){return null!==e.getAttribute(p)}},{key:"destroy",value:function(e){try{void 0!==e[f]&&e.parentNode.insertBefore(e[f],e)}finally{e.parentNode.removeChild(e)}}}]),e}();t.default=w,e.exports=t.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return new p(e,t)}Object.defineProperty(t,"__esModule",{value:!0}),t._Mixin=void 0;var l=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();t.createBootstrapper=u;var c=n(2),s=r(c),f=n(3),d=r(f),p=t._Mixin=function(e){function t(e,n){o(this,t);var r=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));if(!e.container)return console.warn('"Container" property was not supplied'),a(r);e.componentSelector&&(r.componentSelector=e.componentSelector),"boolean"==typeof e.enableWatcher&&(r.enableWatcher=e.enableWatcher);for(var i=new d.default,u=0;e.container.length>u;u++)i.register(e.container[u].register,e.container[u].for);return r._shouldUpdateProxy=e.shouldUpdate||null,r._willUpdateProxy=e.willUpdate||null,r._didUpdateProxy=e.didUpdate||null,r.setContainer(i,n),r}return i(t,e),l(t,[{key:"shouldUpdate",value:function(e){this._shouldUpdateProxy&&this._shouldUpdateProxy(e)}},{key:"willUpdate",value:function(){this._willUpdateProxy&&this._willUpdateProxy()}},{key:"didUpdate",value:function(){this._didUpdateProxy&&this._didUpdateProxy()}}]),t}(s.default)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;t.length>n;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(7),u=r(i),l=n(8),c=r(l),s=n(1),f=r(s),d=function(){function e(){o(this,e)}return a(e,null,[{key:"inject",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2];n?c.default.render(u.default.createElement(e,t||{}),n):f.default.warn("RHW07","Target element is null or undefined.")}},{key:"dispose",value:function(e){e&&c.default.unmountComponentAtNode(e)}}]),e}();t.default=d,e.exports=t.default},function(t,n){t.exports=e},function(e,n){e.exports=t}])}); |
@@ -69,5 +69,20 @@ /** | ||
/** | ||
* Dispose of the container | ||
*/ | ||
dispose: () => void; | ||
* Apply the container to an updated dom structure | ||
*/ | ||
update: (node?: Element) => void; | ||
/** | ||
* Start DOM watcher for auto wire ups | ||
*/ | ||
startWatcher: () => void; | ||
/** | ||
* Stop the DOM watcher if running | ||
*/ | ||
stopWatcher: () => void; | ||
/** | ||
* Dispose of the container | ||
*/ | ||
dispose: () => void; | ||
} | ||
@@ -88,7 +103,17 @@ | ||
/** | ||
* Collection of elements matching the component selector | ||
* Apply the container to an updated dom structure | ||
*/ | ||
elements: NodeListOf<Element>; | ||
update: (node?: Element) => void; | ||
/** | ||
* Start DOM watcher for auto wire ups | ||
*/ | ||
startWatcher: () => void; | ||
/** | ||
* Stop the DOM watcher if running | ||
*/ | ||
stopWatcher: () => void; | ||
/** | ||
* Disposes the container | ||
@@ -95,0 +120,0 @@ */ |
@@ -30,9 +30,25 @@ 'use strict'; | ||
/** | ||
* Parses a container and populate components | ||
* Safe callback wrapper | ||
* @param {null|function} cb - The callback | ||
* @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)); | ||
} | ||
} | ||
/** | ||
* Apply a container to nodes and populate components | ||
* @param {array} container The container | ||
* @param {array} elements The elements to parse | ||
* @param {array} nodes The elements to parse | ||
* @param {string} componentSelector The component selector | ||
* @param cb | ||
* @param {function} [cb=null] - Optional callback | ||
* @private | ||
*/ | ||
function parseContainer(container, elements, componentSelector) { | ||
function _applyContainer(container, nodes, componentSelector) { | ||
var cb = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; | ||
@@ -43,22 +59,48 @@ | ||
var id = container.id(); | ||
var resolveQueue = []; | ||
// Iterate over component elements in the dom | ||
for (var i = 0; i < elements.length; ++i) { | ||
var ele = elements[i]; | ||
var _loop = function _loop(i) { | ||
var ele = nodes[i]; | ||
// Ignore elements that have already been connected | ||
if (_Habitat2.default.hasHabitat(ele)) { | ||
return 'continue'; | ||
} | ||
var componentName = ele.getAttribute(componentSelector); | ||
var component = container.resolve(componentName); | ||
resolveQueue.push(container.resolve(componentName).then(function (component) { | ||
// This is an expensive operation so only do on non prod builds | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (ele.querySelector('[' + componentSelector + ']')) { | ||
_Logger2.default.warn('RHW08', 'Component should not contain any nested components.', ele); | ||
} | ||
} | ||
if (component) { | ||
if (ele.querySelector('[' + componentSelector + ']')) { | ||
_Logger2.default.warn('RHW08', 'Component should not contain any nested components.', ele); | ||
} | ||
// Inject the component | ||
factory.inject(component, _Habitat2.default.parseProps(ele), _Habitat2.default.create(ele, id)); | ||
} else { | ||
_Logger2.default.error('RHW01', 'Cannot resolve component "' + componentName + '" for element.', ele); | ||
} | ||
}).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; | ||
} | ||
if (typeof cb === 'function') { | ||
cb.call(); | ||
} | ||
// 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; | ||
}); | ||
} | ||
@@ -83,10 +125,28 @@ | ||
// Set dom component selector | ||
/** | ||
* The DOM component selector | ||
* @type {string} | ||
*/ | ||
this.componentSelector = DEFAULT_HABITAT_SELECTOR; | ||
// The target elements | ||
this._elements = null; | ||
/** | ||
* The container | ||
* @type {Container|null} | ||
* @private | ||
*/ | ||
this._container = null; | ||
// The container | ||
this._container = null; | ||
/** | ||
* The watcher's observer instance or null | ||
* @type {MutationObserver|null} | ||
* @private | ||
*/ | ||
this._observer = null; | ||
/** | ||
* Observing persistence status flag | ||
* @type {boolean} | ||
* @private | ||
*/ | ||
this._isWatching = false; | ||
} | ||
@@ -104,2 +164,4 @@ | ||
value: function setContainer(container) { | ||
var _this = this; | ||
var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
@@ -116,10 +178,135 @@ | ||
// Find all the elements in the dom with the component selector attribute | ||
this._elements = window.document.body.querySelectorAll('[' + this.componentSelector + ']'); | ||
// Wire up the components from the container | ||
parseContainer(this._container, this._elements, this.componentSelector, cb); | ||
this.update(null, function () { | ||
_callback(cb, _this); | ||
}); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
}, { | ||
key: 'update', | ||
value: function update(node) { | ||
var _this2 = 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.querySelectorAll('[' + this.componentSelector + ']'); | ||
// Lifecycle event | ||
// Hook to allow developers to cancel operation | ||
if (typeof this.shouldUpdate === 'function') { | ||
if (this.shouldUpdate(target) === false) { | ||
_callback(cb, this); | ||
return; | ||
} | ||
} | ||
// Temporarily stop the watcher from triggering from our own Habitat injections | ||
// This is better for performance however, this could possibly miss any | ||
// mutations during the parsing time.. possible bug maybe? dont know yet. | ||
var shouldWatcherPersist = this._isWatching; | ||
if (shouldWatcherPersist) { | ||
this.stopWatcher(); | ||
} | ||
// Lifecycle event | ||
if (typeof this.willUpdate === 'function') { | ||
this.willUpdate(target); | ||
} | ||
_applyContainer(this._container, target, this.componentSelector, function () { | ||
// Restart the dom watcher if persisting | ||
if (shouldWatcherPersist) { | ||
_this2.startWatcher(); | ||
} | ||
// Lifecycle event | ||
if (typeof _this2.didUpdate === 'function') { | ||
_this2.didUpdate(target); | ||
} | ||
_callback(cb, _this2); | ||
}); | ||
} | ||
/** | ||
* Start DOM watcher for auto wire ups | ||
*/ | ||
}, { | ||
key: 'startWatcher', | ||
value: function startWatcher() { | ||
// Feature available? | ||
if (typeof MutationObserver === 'undefined') { | ||
_Logger2.default.error('RHE09', 'MutationObserver not available.'); | ||
return; | ||
} | ||
// Create observer if not assigned already | ||
if (!this._observer) { | ||
this._observer = new MutationObserver(this._handleDomMutation.bind(this)); | ||
} | ||
// Start observing for dom changes filtered by our component selector | ||
this._observer.observe(window.document.body, { | ||
childList: true, | ||
attributes: true, | ||
subtree: true, | ||
attributeOldValue: false, | ||
characterData: false, | ||
characterDataOldValue: false, | ||
attributeFilter: [this.componentSelector] | ||
}); | ||
// Set flag for persistence during update's | ||
this._isWatching = true; | ||
} | ||
/** | ||
* Stop the DOM watcher if running | ||
*/ | ||
}, { | ||
key: 'stopWatcher', | ||
value: function stopWatcher() { | ||
if (this._observer && this._observer.disconnect) { | ||
this._observer.disconnect(); | ||
} | ||
this._isWatching = false; | ||
} | ||
/** | ||
* Handle dom mutation event | ||
* @param {MutationRecord} mutationRecord - The mutation record | ||
*/ | ||
}, { | ||
key: '_handleDomMutation', | ||
value: function _handleDomMutation(mutationRecord) { | ||
if (typeof mutationRecord !== 'undefined') { | ||
// Only run update if nodes have been added | ||
var diff = mutationRecord.filter(function (r) { | ||
return r.addedNodes.length; | ||
}); | ||
if (diff.length) { | ||
this.update(); | ||
} | ||
} else { | ||
// Fallback | ||
this.update(); | ||
} | ||
} | ||
/** | ||
* Dispose the container and destroy habitat instances | ||
@@ -135,2 +322,5 @@ * @param {function} [cb=null] - Optional callback | ||
// Stop dom watcher if any | ||
this.stopWatcher(); | ||
// get the container's factory | ||
@@ -150,8 +340,7 @@ var factory = this._container.domFactory(); | ||
this._container = null; | ||
this._elements = null; | ||
this._observer = null; | ||
this._isWatching = false; | ||
// Handle callback | ||
if (typeof cb === 'function') { | ||
cb.call(); | ||
} | ||
_callback(cb, this); | ||
} | ||
@@ -158,0 +347,0 @@ }]); |
@@ -7,2 +7,5 @@ 'use strict'; | ||
exports._Mixin = 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; }; }(); | ||
exports.createBootstrapper = createBootstrapper; | ||
@@ -42,3 +45,3 @@ | ||
*/ | ||
function _Mixin(spec) { | ||
function _Mixin(spec, callback) { | ||
_classCallCheck(this, _Mixin); | ||
@@ -59,2 +62,7 @@ | ||
// Set the watcher value if defined | ||
if (typeof spec.enableWatcher === 'boolean') { | ||
_this.enableWatcher = spec.enableWatcher; | ||
} | ||
// Create a new container | ||
@@ -68,7 +76,34 @@ var container = new _Container2.default(); | ||
_this._shouldUpdateProxy = spec.shouldUpdate || null; | ||
_this._willUpdateProxy = spec.willUpdate || null; | ||
_this._didUpdateProxy = spec.didUpdate || null; | ||
// Finally, set the container | ||
_this.setContainer(container); | ||
_this.setContainer(container, callback); | ||
return _this; | ||
} | ||
_createClass(_Mixin, [{ | ||
key: 'shouldUpdate', | ||
value: function shouldUpdate(node) { | ||
if (this._shouldUpdateProxy) { | ||
this._shouldUpdateProxy(node); | ||
} | ||
} | ||
}, { | ||
key: 'willUpdate', | ||
value: function willUpdate() { | ||
if (this._willUpdateProxy) { | ||
this._willUpdateProxy(); | ||
} | ||
} | ||
}, { | ||
key: 'didUpdate', | ||
value: function didUpdate() { | ||
if (this._didUpdateProxy) { | ||
this._didUpdateProxy(); | ||
} | ||
} | ||
}]); | ||
return _Mixin; | ||
@@ -83,3 +118,5 @@ }(_Bootstrapper3.default); | ||
function createBootstrapper(spec) { | ||
return new _Mixin(spec); | ||
var cb = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; | ||
return new _Mixin(spec, cb); | ||
} |
@@ -7,11 +7,11 @@ 'use strict'; | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
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 _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 _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** | ||
* 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. | ||
*/ | ||
@@ -22,6 +22,2 @@ var _ReactDomFactory = require('./factories/ReactDomFactory'); | ||
var _Logger = require('./Logger'); | ||
var _Logger2 = _interopRequireDefault(_Logger); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -36,8 +32,7 @@ | ||
*/ | ||
var assignId = function idFactory() { | ||
var _nextId = 0; | ||
var _assignId = function idFactory() { | ||
var nextId = 0; | ||
return function _assignId() { | ||
_nextId++; | ||
return 'C' + _nextId; | ||
nextId = nextId + 1; | ||
return 'C' + nextId; | ||
}; | ||
@@ -47,2 +42,12 @@ }(); | ||
/** | ||
* Determine if the object is a Promise | ||
* @param {*} obj - The test object | ||
* @returns {boolean} - True if deemed to be a promise | ||
* @private | ||
*/ | ||
var _isPromise = function _isPromise(obj) { | ||
return !!obj && ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' || typeof obj === 'function') && typeof obj.then === 'function'; | ||
}; | ||
/** | ||
* The Container class | ||
@@ -60,3 +65,3 @@ */ | ||
this._components = {}; | ||
this._id = assignId(); | ||
this._id = _assignId(); | ||
} | ||
@@ -78,4 +83,4 @@ | ||
* Register a component in the container | ||
* @param {string} name - A unique component key | ||
* @param {object} comp - The component | ||
* @param {string} name - A unique component key | ||
* @param {object|Promise} comp - The component or Promise | ||
*/ | ||
@@ -109,4 +114,4 @@ | ||
* Resolve a component from the container | ||
* @param {string} name - The unique component key | ||
* @returns {object} | ||
* @param {string} name - The unique component key | ||
* @returns {Promise} | ||
*/ | ||
@@ -117,3 +122,13 @@ | ||
value: function resolve(name) { | ||
return this._components[name]; | ||
var registration = this._components[name]; | ||
if (!registration) { | ||
return Promise.reject(new Error('Not Registered')); | ||
} | ||
if (_isPromise(registration)) { | ||
return registration; | ||
} | ||
return Promise.resolve(registration); | ||
} | ||
@@ -131,42 +146,2 @@ | ||
} | ||
/** | ||
* Register a component in the container | ||
* @param {string} name - A unique component key | ||
* @param {object} comp - The component | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'registerComponent', | ||
value: function registerComponent(name, comp) { | ||
_Logger2.default.warn('RHW03', 'registerComponent is being deprecated. Please use "register" instead.'); | ||
this.register(name, comp); | ||
} | ||
/** | ||
* Register multiple components to the container | ||
* @param {object} comps - The components | ||
*/ | ||
}, { | ||
key: 'registerComponents', | ||
value: function registerComponents(comps) { | ||
_Logger2.default.warn('RHW03', 'registerComponents is being deprecated. Please use "registerAll" instead.'); | ||
this.registerAll(comps); | ||
} | ||
/** | ||
* Gets a component for key | ||
* @param name | ||
* @returns {Object} | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'getComponent', | ||
value: function getComponent(name) { | ||
_Logger2.default.warn('RHW03', 'getComponent is being deprecated. Please use "resolve" instead.'); | ||
return this.resolve(name); | ||
} | ||
}]); | ||
@@ -173,0 +148,0 @@ |
@@ -84,3 +84,4 @@ 'use strict'; | ||
var props = { | ||
proxy: ele }; | ||
proxy: ele // Pass in a reference to the original node | ||
}; | ||
@@ -205,3 +206,4 @@ // Populate custom props from reading any ele attributes that start with 'data-prop-' | ||
try { | ||
// It might be better if we keep references in a weak map, need to look at this in the future | ||
// It might be better if we keep references in a weak map, need to look | ||
// at this in the future | ||
habitat[HABITAT_HOST_KEY] = host; | ||
@@ -208,0 +210,0 @@ } catch (e) { |
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
@@ -27,26 +27,30 @@ | ||
/** | ||
* Safely log to the console | ||
*/ | ||
log = function log(type, args) { | ||
/** | ||
* Safely log to the console | ||
*/ | ||
log = function log(type, args) { | ||
if (typeof console !== 'undefined' && console[type]) { | ||
if (console[type].apply) { | ||
console[type].apply(undefined, args); | ||
} else { | ||
// IE9 Fallback | ||
console[type](args); | ||
} | ||
} | ||
}; | ||
if (typeof console !== 'undefined' && console[type]) { | ||
console[type].apply(undefined, args); | ||
} | ||
}; | ||
/** | ||
* Concats the message and arguments into a single array | ||
*/ | ||
concatArgs = function concatArgs(msg, args) { | ||
var throwArgs = [msg]; | ||
/** | ||
* Concats the message and arguments into a single array | ||
*/ | ||
concatArgs = function concatArgs(msg, args) { | ||
var throwArgs = [msg]; | ||
if (args && args.length) { | ||
for (var i = 0; i < args.length; i++) { | ||
throwArgs.push(args[i]); | ||
} | ||
} | ||
if (args && args.length > 2) { | ||
for (var i = 2; i < args.length; i++) { | ||
throwArgs.push(args[i]); | ||
} | ||
} | ||
return throwArgs; | ||
}; | ||
return throwArgs; | ||
}; | ||
} | ||
@@ -59,35 +63,45 @@ | ||
var Logger = function () { | ||
function Logger() { | ||
_classCallCheck(this, Logger); | ||
} | ||
function Logger() { | ||
_classCallCheck(this, Logger); | ||
} | ||
_createClass(Logger, null, [{ | ||
key: 'warn', | ||
_createClass(Logger, null, [{ | ||
key: 'warn', | ||
/** | ||
* Log a warning | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The warning message | ||
*/ | ||
value: function warn(code, msg) { | ||
var args = concatArgs('WARNING: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), arguments); | ||
log('warn', args); | ||
} | ||
/** | ||
* Log a warning | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The warning message | ||
* @param {Array} debugs - Any debugging arguments | ||
*/ | ||
value: function warn(code, msg) { | ||
for (var _len = arguments.length, debugs = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { | ||
debugs[_key - 2] = arguments[_key]; | ||
} | ||
/** | ||
* Log an error | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The error message | ||
*/ | ||
var args = concatArgs('WARNING: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), debugs); | ||
log('warn', args); | ||
} | ||
}, { | ||
key: 'error', | ||
value: function error(code, msg) { | ||
var args = concatArgs('ERROR: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), arguments); | ||
log('error', args); | ||
} | ||
}]); | ||
/** | ||
* Log an error | ||
* @param {string} code - The warning code | ||
* @param {string} msg - The error message | ||
* @param {Array} debugs - Any debugging arguments | ||
*/ | ||
return Logger; | ||
}, { | ||
key: 'error', | ||
value: function error(code, msg) { | ||
for (var _len2 = arguments.length, debugs = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { | ||
debugs[_key2 - 2] = arguments[_key2]; | ||
} | ||
var args = concatArgs('ERROR: ' + code + ' ' + msg + ' ' + WARN_DEFINITIONS_URL + '#' + code.toLowerCase(), debugs); | ||
log('error', args); | ||
} | ||
}]); | ||
return Logger; | ||
}(); | ||
@@ -94,0 +108,0 @@ |
{ | ||
"name": "react-habitat", | ||
"version": "0.4.2", | ||
"version": "0.5.0-beta", | ||
"description": "A React DOM Bootstrapper designed to harmonise a hybrid application", | ||
@@ -46,4 +46,4 @@ "main": "./lib/index.js", | ||
"eslint": "^3.2.2", | ||
"eslint-config-airbnb": "^10.0.0", | ||
"eslint-plugin-import": "^1.12.0", | ||
"eslint-config-deloitte": "^1.0.1", | ||
"eslint-plugin-import": "^1.16.0", | ||
"eslint-plugin-jasmine": "^1.8.1", | ||
@@ -53,8 +53,9 @@ "eslint-plugin-jsx-a11y": "^2.0.1", | ||
"jasmine-core": "^2.4.1", | ||
"karma": "^1.1.2", | ||
"karma": "^1.7.0", | ||
"karma-chrome-launcher": "^1.0.1", | ||
"karma-jasmine": "^1.0.2", | ||
"karma-phantomjs-launcher": "^1.0.1", | ||
"karma-firefox-launcher": "^1.0.1", | ||
"karma-jasmine": "^1.1.0", | ||
"karma-polyfill": "^1.0.0", | ||
"karma-webpack": "^1.8.0", | ||
"phantomjs-polyfill-object-assign": "0.0.2", | ||
"prop-types": "^15.5.10", | ||
"webpack": "^1.13.0" | ||
@@ -76,3 +77,4 @@ }, | ||
"scripts": { | ||
"test": "karma start --single-run --browsers PhantomJS", | ||
"test": "npm run lint && karma start --single-run", | ||
"debug": "NODE_ENV=development karma start", | ||
"build": "npm run build:lib && npm run build:umd && npm run build:umd:min", | ||
@@ -79,0 +81,0 @@ "build:lib": "babel src --out-dir lib", |
@@ -23,2 +23,11 @@ ![Deloitte Digital](https://raw.githubusercontent.com/DeloitteDigital/DDBreakpoints/master/docs/deloittedigital-logo-white.png) | ||
- [Options and Methods](#options-and-methods) | ||
- [Setting the habitats css class](#setting-the-habitats-css-class) | ||
- [Replace original node](#replace-original-node) | ||
- [Dynamic Updates](#dynamic-updates) | ||
- [Update Lifecycle](#update-lifecycle) | ||
- [Start the dom watcher](#start-watcher) | ||
- [Stop the dom watcher](#stop-watcher) | ||
- [Disposing the container](#disposing-the-container) | ||
- [Dynamic imports and code splitting](#dynamic-imports-and-code-splitting) | ||
- [Use encoded JSON in HTML attributes](#use-encoded-json-in-html-attributes) | ||
- [Contribute](#want-to-contribute) | ||
@@ -42,2 +51,3 @@ - [License information](#license-bsd-3-clause) | ||
- Adobe Experience Manager | ||
- Hybris | ||
- Umbraco | ||
@@ -52,3 +62,3 @@ - Drupal | ||
Typically if you're building a full-on one page React app that yanks data from restful API's... then this framework isn't really going to bring much benefit to you. However you are definitely invited to use it if you want to. | ||
Typically if you're using a router... then this framework isn't really going to bring much benefit to you. However you are definitely invited to use it if you want to. | ||
@@ -69,6 +79,11 @@ ## Features | ||
- Supports Browsers IE9+ and all the evergreens. (IE9-11 will require an "Object.assign" [Pollyfill](https://babeljs.io/docs/usage/polyfill/)) | ||
- Supports Browsers IE9+ and all the evergreens. | ||
- ES5, ES6/7 & TypeScript | ||
- React v15 and up | ||
Polyfills | ||
- IE9-11 Will require a [Promise Polyfill](http://babeljs.io/docs/usage/polyfill/) | ||
- IE9-10 Will require a [Object.assign Polyfill](http://babeljs.io/docs/usage/polyfill/) | ||
We highly recommend you use something like [WebPack](https://webpack.github.io/) or [Browserify](http://browserify.org/) when using this framework. | ||
@@ -78,9 +93,9 @@ | ||
Install with Node Package Manager (NPM) | ||
Install with [NPM](http://npmjs.com/) | ||
`npm install --save-dev react-habitat` | ||
This assumes that you’re using [npm](http://npmjs.com/) package manager with a module bundler like [Webpack](http://webpack.github.io) or [Browserify](http://browserify.org/). | ||
This assumes that you’re using a package manager with a module bundler like [Webpack](http://webpack.github.io) or [Browserify](http://browserify.org/). | ||
If you don’t yet use [npm](http://npmjs.com/) or a modern module bundler, and would rather prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactHabitat` available as a global object, you can grab a pre-built version from the dist folder. | ||
If you don’t use a module bundler, and would prefer a single-file [UMD](https://github.com/umdjs/umd) build that makes `ReactHabitat` available as a global object, you can grab a pre-built version from the dist folder. | ||
@@ -121,2 +136,3 @@ ## Getting Started | ||
So for our sample application we need to register all of our components (classes) to be exposed to the DOM so things get wired up nicely. | ||
Note in this example you can also define split points using React Habitat [dynamic imports](#dynamic-imports-and-code-splitting). | ||
@@ -138,3 +154,6 @@ ```javascript | ||
{register: 'SomeReactComponent', for: SomeReactComponent}, | ||
{register: 'AnotherReactComponent', for: AnotherReactComponent} | ||
{register: 'AnotherReactComponent', for: AnotherReactComponent}, | ||
// Register a dynamic import | ||
{register: 'AsyncReactComponent', for: import('./AsyncReactComponent')} | ||
] | ||
@@ -160,3 +179,3 @@ }); | ||
To *resolve* new instances of your components you need to attach a `data-component` attribute to a `div` or a `span` element in the HTML. | ||
To *resolve* new instances of your components you need to attach a `data-component` attribute to a `div` or a `span` element in the HTML. | ||
Any child components should be nested inside the React components themselves. | ||
@@ -209,6 +228,7 @@ | ||
**Note** It's important that the output built javascript file is included at the end of the DOM just before the closing </body> tag. | ||
**Note** It's important that the output built javascript file is included at the end of the DOM just before the closing body tag. | ||
**[⬆ back to top](#table-of-contents)** | ||
### Passing properties *(props)* to your components | ||
## Passing properties *(props)* to your components | ||
@@ -220,11 +240,14 @@ Resolving and registering components alone is not all that special, but passing data to it via html attributes is pretty useful. This allows the backend to | ||
- [data-props](#data-props) Maps JSON to props. | ||
- [data-prop-](#data-prop-) (Prefix) Maps in strings, booleans, null, array or JSON to a prop. | ||
- [data-n-prop-](#data-n-prop-) (Prefix) Maps in numbers and floats to a prop. | ||
- [data-r-prop-](#data-r-prop-) (Prefix) Maps in a reference to an object that exists on the global scope (window) to a prop. | ||
|Attribute|Description| | ||
|---|---| | ||
|[data-props](#data-props)|Maps [encoded JSON](#use-encoded-json-in-html-attributes) to props. | ||
|[data-prop-*](#data-prop-)|This [prefix](#prefix) maps in strings, booleans, null, array or [encoded JSON](#use-encoded-json-in-html-attributes) to a prop. | ||
|[data-n-prop-*](#data-n-prop-)|This [prefix](#prefix) maps in numbers and floats to a prop. | ||
|[data-r-prop-*](#data-r-prop-)|This [prefix](#prefix) in a reference to an object that exists on the global scope (window) to a prop. | ||
**PLEASE NOTE:** | ||
The last three options are attribute *prefixes*. This allow's you to define the property the name. | ||
Property names will be *automatically converted* from hyphens to camel case. | ||
### Prefix | ||
With an attribute *prefix* the **\*** may be replaced by any name. This allow's you to define the property name. | ||
Property names must be all lower case and hyphens will be *automatically converted* to camel case. | ||
For example | ||
@@ -236,7 +259,6 @@ | ||
### data-props | ||
#### data-props | ||
Set component props via a [encoded JSON](#use-encoded-json-in-html-attributes) string on the `data-props` attribute. | ||
Set component props via a JSON string on the `data-props` attribute. | ||
For example | ||
@@ -248,3 +270,3 @@ | ||
#### data-prop- | ||
### data-prop-* | ||
@@ -272,9 +294,9 @@ Set an component prop via prefixing attributes with `data-prop-`. | ||
var SomeReactComponent = React.createClass({ | ||
render: function() { | ||
render: function() { | ||
this.props.title === "A nice title"; //> true | ||
this.props.showTitle === true; //> true | ||
this.props.title === "A nice title"; //> true | ||
this.props.showTitle === true; //> true | ||
return <div>{ this.props.showTitle ? this.props.title : null }</div>; | ||
} | ||
return <div>{ this.props.showTitle ? this.props.title : null }</div>; | ||
} | ||
}); | ||
@@ -287,3 +309,3 @@ ``` | ||
<div data-component="SomeReactComponent" | ||
data-prop-person="{'name': 'john', 'age': 22}"> | ||
data-prop-person='{"name": "john", "age": 22}'> | ||
</div> | ||
@@ -296,10 +318,10 @@ ``` | ||
var SomeReactComponent = React.createClass({ | ||
render: function() { | ||
render: function() { | ||
return ( | ||
<div> | ||
Name: {this.props.person.name} | ||
Age: {this.props.person.age} | ||
</div> | ||
); | ||
return ( | ||
<div> | ||
Name: {this.props.person.name} | ||
Age: {this.props.person.age} | ||
</div> | ||
); | ||
} | ||
@@ -310,3 +332,3 @@ }); | ||
#### data-n-prop- | ||
### data-n-prop-* | ||
@@ -319,3 +341,3 @@ Set an component prop with type [number] via prefixing attributes with `data-n-prop-`. | ||
#### data-r-prop- | ||
### data-r-prop-* | ||
@@ -333,5 +355,7 @@ Referenced a global variable in your component prop via prefixing attributes with `data-r-prop-`. | ||
``` | ||
This is handy if you need to share properties between habitats or you need to set JSON onto the page. | ||
**[⬆ back to top](#table-of-contents)** | ||
## Passing values back again | ||
@@ -341,3 +365,3 @@ | ||
*Every* React Habitat instance is passed in a prop named `proxy`, this is a reference the original dom element. | ||
*Every* React Habitat instance is passed in a prop named `proxy`, this is a reference the original dom element. | ||
Please note only `<inputs />` are left in the DOM by default. To keep a generic element in the DOM, set the `data-habitat-no-replace="true"` attribute. | ||
@@ -359,2 +383,4 @@ | ||
**[⬆ back to top](#table-of-contents)** | ||
## Options & Methods | ||
@@ -374,8 +400,12 @@ | ||
**[⬆ back to top](#table-of-contents)** | ||
### Replace original node | ||
By default only `<inputs />` are left in the DOM when a React Habitat is created. | ||
By default only `<inputs />` are left in the DOM when a React Habitat is created. | ||
To keep a generic element in the DOM, set the `data-habitat-no-replace="true"` attribute. | ||
**[⬆ back to top](#table-of-contents)** | ||
### Changing the habitat query selector | ||
@@ -395,6 +425,6 @@ | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
@@ -404,2 +434,140 @@ } | ||
**[⬆ back to top](#table-of-contents)** | ||
### Dynamic Updates | ||
`update()` | ||
Will scan the DOM and for any components that require wiring up (i.e after ajaxing in some HTML). | ||
This can be evoked automatically by using a [watcher](#start-watcher). | ||
Example | ||
```javascript | ||
function MyApp() { | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
// This will scan the entire document body | ||
this.domContainer.update(); | ||
// Will scan just the children of the element with id 'content' | ||
this.domContainer.update(window.document.getElementById('content')); | ||
} | ||
``` | ||
By default *update()* will scan the entire body, however a parent node can optionally be passed in for better | ||
performance if you know where the update has occurred. | ||
Example | ||
```javascript | ||
function MyApp() { | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
// Will scan just the children of the element with id 'content' | ||
this.domContainer.update(window.document.getElementById('content')); | ||
} | ||
``` | ||
### Update Lifecycle | ||
React Habitat applications have update "lifecycle methods" that you can override to run code at particular times | ||
in the process. | ||
`shouldUpdate(node)` | ||
Called when an update has been requested. Return false to cancel the update. | ||
`willUpdate(node)` | ||
Called when an update is about to take place. | ||
`didUpdate(node)` | ||
Called after an update has taken place. | ||
Example | ||
```javascript | ||
function MyApp() { | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
shouldUpdate: function(node) { | ||
// Dont allow updates on div's | ||
if (node.tagName === 'div') { | ||
return false; | ||
} | ||
}, | ||
willUpdate: function(node) { | ||
// will update | ||
}, | ||
didUpdate: function(node) { | ||
// did update | ||
} | ||
}); | ||
} | ||
``` | ||
**[⬆ back to top](#table-of-contents)** | ||
### Start Watcher | ||
**Please Note** IE 9 & 10 will require a [MutationObserver polyfill](https://github.com/megawac/MutationObserver.js/tree/master) | ||
to use this feature. An alternative is to call [update](#update) manually. | ||
Start watching the DOM for any changes and wire up future components automatically (eg ajaxed HTML). | ||
Example | ||
```javascript | ||
function MyApp() { | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
// Wire up any future habitat elements automatically | ||
this.domContainer.startWatcher(); | ||
} | ||
``` | ||
**[⬆ back to top](#table-of-contents)** | ||
### Stop Watcher | ||
Will stop watching the DOM for any changes. | ||
Example | ||
```javascript | ||
function MyApp() { | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
componentSelector: 'myComponents' | ||
}); | ||
// Stop the watcher | ||
this.domContainer.stopWatcher(); | ||
} | ||
``` | ||
**[⬆ back to top](#table-of-contents)** | ||
### Disposing the container | ||
@@ -414,12 +582,111 @@ | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
... | ||
}); | ||
// Create a new react habitat bootstrapper | ||
this.domContainer = ReactHabitat.createBootstrapper({ | ||
... | ||
}); | ||
this.domContainer.dispose(); | ||
this.domContainer.dispose(); | ||
} | ||
``` | ||
**[⬆ back to top](#table-of-contents)** | ||
## Dynamic imports and code splitting | ||
React Habitat supports resolving components asynchronously by using Promises. To define async registrations, register a Promise (that resolves to a component) instead of a component. | ||
for example | ||
```javascript | ||
container.register('AsynReactComponent', new Promise(function(resolve, reject) { | ||
// .. do async work to get 'component', then | ||
resolve(component); | ||
})); | ||
``` | ||
or with registerAll | ||
```javascript | ||
container.registerAll({ | ||
'SomeReactComponent': new Promise(function(resolve, reject) { | ||
// .. do async work to get 'component', then | ||
resolve(component); | ||
}) | ||
}); | ||
``` | ||
React Habitat has no restrictions on how you want to resolve your components however this does enable you to define code split points. | ||
**Code splitting** is one great feature that means our visitors dont need to download the entire app before they can use it. | ||
Think of code splitting as incrementally download your application only as its needed. | ||
While there are other methods for code splitting we will use Webpack for these examples. | ||
Webpack 2 treats `import()` as a [split-point](https://webpack.js.org/guides/code-splitting-async/) and puts the requested module into a separate chunk. | ||
So for example, we could create a split point using `import()` like this: | ||
```javascript | ||
container.register('AsynReactComponent', new Promise(function(resolve, reject) { | ||
import('./components/MyComponent').then(function(MyComponent) { | ||
resolve(MyComponent); | ||
}).catch(function(err) { | ||
reject(err); | ||
}) | ||
})); | ||
``` | ||
**However**, since `import()` returns a Promise, we can actually simplify the above to: | ||
```javascript | ||
container.register('AsynReactComponent', import('./components/MyComponent')); | ||
``` | ||
Here is an example using `require.ensure()` to define a [split-point in webpack 1](https://webpack.github.io/docs/code-splitting.html) | ||
```javascript | ||
container.register('AsynReactComponent', new Promise(function(resolve, reject) { | ||
require.ensure(['./components/MyComponent'], function(MyComponent) { | ||
resolve(MyComponent); | ||
}); | ||
})); | ||
``` | ||
**[⬆ back to top](#table-of-contents)** | ||
### Use encoded JSON in HTML attributes | ||
When passing JSON to an attribute, you will need to encode the value so that content can be preserved and properly rendered. | ||
As a general rule, escape the following characters with HTML entity encoding: | ||
`&` --> `&` | ||
`<` --> `<` | ||
`>` --> `>` | ||
`"` --> `"` | ||
`'` --> `'` | ||
`/` --> `/` | ||
Example: | ||
`<div data-props="{"foo": "bar"}"></div>` | ||
Additionally, an encoder may replace [extended ASCII characters](https://en.wikipedia.org/wiki/Extended_ASCII) with the equivalent HTML entity encoding. | ||
Most backend systems are capable of doing this automatically. An alternative is to use the [data-r-prop-*](#data-r-prop-) option. | ||
**Single of Double Quotes?** | ||
Double quotes around attributes values are the most common and our recommendation for setting properties with React Habitat. | ||
However, there is a known hack of wrapping JSON attributes with single quotes and escaping nested single quotes. | ||
example | ||
`<div data-props='{"restaurant": "Bob\'s bar and grill"}'></div>` | ||
*We will use this method in the docs to maintain readability. However, we strongly recommend you encode in production code.* | ||
**[⬆ back to top](#table-of-contents)** | ||
## Want to contribute? | ||
@@ -473,1 +740,3 @@ | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
**[⬆ back to top](#table-of-contents)** |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
163959
15
1904
717
24
1