Skip to content

Commit

Permalink
add deferring mechanism as per #3
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-github committed Oct 17, 2019
1 parent 183686e commit f1e1d34
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 38 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Type: `string`

CSS selector which will override searching by getNamespace() and be used for searching elements of given componentClass.

### dcFactory.init([root = document.body])
### dcFactory.init(root = document.body, withLazy = true)

Starts the factory on a given root: finds and creates all registered components within the root

Expand All @@ -76,6 +76,14 @@ Starts the factory on a given root: finds and creates all registered components
*Optional*<br>
Type: `HTMLElement`

#### withLazy

*Optional*<br>
Type: `boolean`

Defines whether or not components which are marked as lazy should be created during this particular initialization.
To mark components as lazy you need to add `data-dc-lazy` attribute on its element or any of its parent elements

### dcFactory.destroy(root)

Destroy all previously registered components within the passed element
Expand Down
2 changes: 1 addition & 1 deletion build/dc.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/dc.min.js.map

Large diffs are not rendered by default.

72 changes: 70 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@
position: relative;
padding: 1rem;
margin: 1rem -15px 0;
border: solid #f8f9fa;
border: solid #F8F9FA;
border-width: .2rem 0 0
}

@media (min-width: 576px) {
.example {
padding:1.5rem;
padding: 1.5rem;
margin-right: 0;
margin-left: 0;
border-width: .2rem
Expand Down Expand Up @@ -130,6 +130,74 @@ <h2>Simple example</h2>
</script>
</div>

<div class="mb-5">
<h2>Lazy initialization</h2>
<p>
You can defer your components initialization by marking its element or any of its parents with
<b>data-dc-lazy</b> attribute.
Passing an additional argument withLazy equals false to dcFactory.init(element, withLazy = true) you can manage whether or not components which are marked as lazy should be created.
It can be useful when some component(s) require specific action to become visible and interactive. (For example if you want to initialize components within the modal only when it is opened)
After those action have happened you can call dcComponent(element, true) and all component within the element will be created despite the data-dc-lazy attribute
</p>
<p>
Example below shows that one of the message components won't be created until the button is clicked
</p>

<div data-live-highlight-target="lazy"></div>

<div class="mb-5">

<style>
.message {
padding: 20px;
background-color: lightgray;
}

.is-hidden {
display: none;
}
</style>

<div data-live-highlight="lazy">
<div class="message" data-dc-message='{"message": "I am created at once"}'>
</div>

<button id="lazy-trigger-button"
class="btn btn-primary mt-2 mb-2">Create lazy components
</button>

<div id="lazy-target" data-dc-lazy class="is-hidden">
<div class="message "
data-dc-message='{"message": "I am created once button is clicked"}'>
</div>
</div>
</div>
</div>

<script data-live-highlight="lazy">
class MessageComponent extends DcBaseComponent {
static getNamespace() {
return 'message'
}

onInit() {
console.log('MessageComponent is created on node', this.element);
this.element.innerText = this.options.message;
}
}

dcFactory.register(MessageComponent);
dcFactory.init(document.body, false);

const lazyTriggerButton = document.getElementById('lazy-trigger-button');
const lazyTarget = document.getElementById('lazy-target');
lazyTriggerButton.addEventListener('click', () => {
lazyTarget.classList.remove('is-hidden');
dcFactory.init(lazyTarget, true);
})
</script>

</div>
</div>
</div>
</body>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@deleteagency/dc",
"version": "1.0.0",
"version": "1.1.0",
"description": "",
"main": "index.js",
"repository": {
Expand Down
21 changes: 20 additions & 1 deletion src/dc-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import utils from './utils';
const DC_NAMESPACE = 'data-dc';
const DC_NAMESPACED_ATTRIBUTE_REFERENCE = 'ref';
const DC_NAMESPACED_ATTRIBUTE_ID = 'id';
const DC_NAMESPACED_ATTRIBUTE_LAZY = 'lazy';

function getNamespacedAnchorAttribute(namespace) {
return `${DC_NAMESPACE}-${namespace}`;
Expand Down Expand Up @@ -33,6 +34,23 @@ function findElementsForInit(root, namespace, selector = null) {
return elements;
}

/**
* @param {HTMLElement} element
* @return {boolean}
*/
function isElementWithinLazyParent(element) {
let checkElement = element;
const attribute = `${DC_NAMESPACE}-${DC_NAMESPACED_ATTRIBUTE_LAZY}`;
while (checkElement) {
if (checkElement.hasAttribute(attribute)) {
return true;
}
checkElement = checkElement.parentElement;
}

return false;
}

/**
*
* @param {HTMLElement} element
Expand Down Expand Up @@ -134,5 +152,6 @@ export default {
getElementRefs,
getParentId,
getNamespacedAttributeValue,
findChildrenWithAttribute
findChildrenWithAttribute,
isElementWithinLazyParent
};
76 changes: 45 additions & 31 deletions src/dc-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const COMPONENT_STATE_NOT_INITED = 'not-inited';
* @type {ComponentState}
*/
const COMPONENT_STATE_INITIALIZING = 'initializing';
/**
* @type {ComponentState}
*/
const COMPONENT_STATE_LAZY_WAITING = 'lazy-waiting';
/**
* @type {ComponentState}
*/
Expand Down Expand Up @@ -61,22 +65,6 @@ class DcFactory {
});
}

/**
*
* @param {HTMLElement} element
* @param {typeof DcBaseComponent} componentClass
* @return boolean
* @private
*/
_isComponentCreatedOnElement(element, componentClass) {
const existedComponents = this._elementsComponents.get(element);
if (existedComponents) {
const state = existedComponents.get(componentClass);
return [COMPONENT_STATE_CREATED, COMPONENT_STATE_INITIALIZING].includes(state);
}
return false;
}

/**
*
* @param {HTMLElement} element
Expand Down Expand Up @@ -109,10 +97,11 @@ class DcFactory {
/**
* Starts the factory on a given root: finds and creates all registered components within the root
* @param {HTMLElement} root
* @param {boolean} withLazy - Whether or not initialize component which marked as lazy
*/
init(root = document.body) {
init(root = document.body, withLazy = true) {
this._registredComponents.forEach(({ componentClass, selector }) => {
this._initComponent(root, componentClass, selector);
this._initComponent(root, componentClass, selector, withLazy);
});

if (process.env.NODE_ENV === 'development') {
Expand All @@ -127,14 +116,15 @@ class DcFactory {
* @param {HTMLElement} root
* @param {typeof DcBaseComponent} componentClass
* @param {Function|string} selector
* @param {boolean} withLazy
* @private
*/
_initComponent(root, componentClass, selector) {
_initComponent(root, componentClass, selector, withLazy) {
try {
const elements = dcDom.findElementsForInit(root, componentClass.getNamespace(), selector);
if (elements.length > 0) {
elements.forEach((element) => {
this._initComponentOnElement(element, componentClass);
this._initComponentOnElement(element, componentClass, withLazy);
});
}
} catch (e) {
Expand All @@ -143,25 +133,49 @@ class DcFactory {
}
}

_isComponentLazy(element) {
return dcDom.isElementWithinLazyParent(element);
}

/**
* Init component class on elements
* @param {HTMLElement} element
* @param {typeof DcBaseComponent} componentClass
* @param {boolean} withLazy
* @private
*/
_initComponentOnElement(element, componentClass) {
if (!this._isComponentCreatedOnElement(element, componentClass)) {
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_INITIALIZING);
// TODO consider more sophisticated optimization technique
setTimeout(() => {
try {
const instance = this._createComponentOnElement(componentClass, element);
this._onComponentCreated(instance, componentClass);
} catch (error) {
this._onComponentCreationError(error, element, componentClass);
_initComponentOnElement(element, componentClass, withLazy) {
const state = this._getComponentStateOnElement(element, componentClass);
switch (state) {
// ignore components which are already created or in the middle of that process
case COMPONENT_STATE_CREATED:
case COMPONENT_STATE_ERROR:
case COMPONENT_STATE_INITIALIZING:
return;
case COMPONENT_STATE_LAZY_WAITING:
if (!withLazy) {
return;
}
}, 0);
}

// if component is lazy but we should not instantiate it according withLazy = false
// we need to mark this component and wait until withLazy = true
if (!withLazy && this._isComponentLazy(element)) {
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_LAZY_WAITING);
return;
}

// finally init component on element
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_INITIALIZING);
// TODO consider more sophisticated optimization technique
setTimeout(() => {
try {
const instance = this._createComponentOnElement(componentClass, element);
this._onComponentCreated(instance, componentClass);
} catch (error) {
this._onComponentCreationError(error, element, componentClass);
}
}, 0);
}

/**
Expand Down

0 comments on commit f1e1d34

Please sign in to comment.