Skip to content

Commit f1e1d34

Browse files
committed
add deferring mechanism as per #3
1 parent 183686e commit f1e1d34

File tree

7 files changed

+147
-38
lines changed

7 files changed

+147
-38
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Type: `string`
6767

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

70-
### dcFactory.init([root = document.body])
70+
### dcFactory.init(root = document.body, withLazy = true)
7171

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

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

79+
#### withLazy
80+
81+
*Optional*<br>
82+
Type: `boolean`
83+
84+
Defines whether or not components which are marked as lazy should be created during this particular initialization.
85+
To mark components as lazy you need to add `data-dc-lazy` attribute on its element or any of its parent elements
86+
7987
### dcFactory.destroy(root)
8088

8189
Destroy all previously registered components within the passed element

build/dc.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/dc.min.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.html

+70-2
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@
4040
position: relative;
4141
padding: 1rem;
4242
margin: 1rem -15px 0;
43-
border: solid #f8f9fa;
43+
border: solid #F8F9FA;
4444
border-width: .2rem 0 0
4545
}
4646

4747
@media (min-width: 576px) {
4848
.example {
49-
padding:1.5rem;
49+
padding: 1.5rem;
5050
margin-right: 0;
5151
margin-left: 0;
5252
border-width: .2rem
@@ -130,6 +130,74 @@ <h2>Simple example</h2>
130130
</script>
131131
</div>
132132

133+
<div class="mb-5">
134+
<h2>Lazy initialization</h2>
135+
<p>
136+
You can defer your components initialization by marking its element or any of its parents with
137+
<b>data-dc-lazy</b> attribute.
138+
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.
139+
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)
140+
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
141+
</p>
142+
<p>
143+
Example below shows that one of the message components won't be created until the button is clicked
144+
</p>
145+
146+
<div data-live-highlight-target="lazy"></div>
147+
148+
<div class="mb-5">
149+
150+
<style>
151+
.message {
152+
padding: 20px;
153+
background-color: lightgray;
154+
}
155+
156+
.is-hidden {
157+
display: none;
158+
}
159+
</style>
160+
161+
<div data-live-highlight="lazy">
162+
<div class="message" data-dc-message='{"message": "I am created at once"}'>
163+
</div>
164+
165+
<button id="lazy-trigger-button"
166+
class="btn btn-primary mt-2 mb-2">Create lazy components
167+
</button>
168+
169+
<div id="lazy-target" data-dc-lazy class="is-hidden">
170+
<div class="message "
171+
data-dc-message='{"message": "I am created once button is clicked"}'>
172+
</div>
173+
</div>
174+
</div>
175+
</div>
176+
177+
<script data-live-highlight="lazy">
178+
class MessageComponent extends DcBaseComponent {
179+
static getNamespace() {
180+
return 'message'
181+
}
182+
183+
onInit() {
184+
console.log('MessageComponent is created on node', this.element);
185+
this.element.innerText = this.options.message;
186+
}
187+
}
188+
189+
dcFactory.register(MessageComponent);
190+
dcFactory.init(document.body, false);
191+
192+
const lazyTriggerButton = document.getElementById('lazy-trigger-button');
193+
const lazyTarget = document.getElementById('lazy-target');
194+
lazyTriggerButton.addEventListener('click', () => {
195+
lazyTarget.classList.remove('is-hidden');
196+
dcFactory.init(lazyTarget, true);
197+
})
198+
</script>
199+
200+
</div>
133201
</div>
134202
</div>
135203
</body>

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@deleteagency/dc",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "",
55
"main": "index.js",
66
"repository": {

src/dc-dom.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import utils from './utils';
33
const DC_NAMESPACE = 'data-dc';
44
const DC_NAMESPACED_ATTRIBUTE_REFERENCE = 'ref';
55
const DC_NAMESPACED_ATTRIBUTE_ID = 'id';
6+
const DC_NAMESPACED_ATTRIBUTE_LAZY = 'lazy';
67

78
function getNamespacedAnchorAttribute(namespace) {
89
return `${DC_NAMESPACE}-${namespace}`;
@@ -33,6 +34,23 @@ function findElementsForInit(root, namespace, selector = null) {
3334
return elements;
3435
}
3536

37+
/**
38+
* @param {HTMLElement} element
39+
* @return {boolean}
40+
*/
41+
function isElementWithinLazyParent(element) {
42+
let checkElement = element;
43+
const attribute = `${DC_NAMESPACE}-${DC_NAMESPACED_ATTRIBUTE_LAZY}`;
44+
while (checkElement) {
45+
if (checkElement.hasAttribute(attribute)) {
46+
return true;
47+
}
48+
checkElement = checkElement.parentElement;
49+
}
50+
51+
return false;
52+
}
53+
3654
/**
3755
*
3856
* @param {HTMLElement} element
@@ -134,5 +152,6 @@ export default {
134152
getElementRefs,
135153
getParentId,
136154
getNamespacedAttributeValue,
137-
findChildrenWithAttribute
155+
findChildrenWithAttribute,
156+
isElementWithinLazyParent
138157
};

src/dc-factory.js

+45-31
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ const COMPONENT_STATE_NOT_INITED = 'not-inited';
1212
* @type {ComponentState}
1313
*/
1414
const COMPONENT_STATE_INITIALIZING = 'initializing';
15+
/**
16+
* @type {ComponentState}
17+
*/
18+
const COMPONENT_STATE_LAZY_WAITING = 'lazy-waiting';
1519
/**
1620
* @type {ComponentState}
1721
*/
@@ -61,22 +65,6 @@ class DcFactory {
6165
});
6266
}
6367

64-
/**
65-
*
66-
* @param {HTMLElement} element
67-
* @param {typeof DcBaseComponent} componentClass
68-
* @return boolean
69-
* @private
70-
*/
71-
_isComponentCreatedOnElement(element, componentClass) {
72-
const existedComponents = this._elementsComponents.get(element);
73-
if (existedComponents) {
74-
const state = existedComponents.get(componentClass);
75-
return [COMPONENT_STATE_CREATED, COMPONENT_STATE_INITIALIZING].includes(state);
76-
}
77-
return false;
78-
}
79-
8068
/**
8169
*
8270
* @param {HTMLElement} element
@@ -109,10 +97,11 @@ class DcFactory {
10997
/**
11098
* Starts the factory on a given root: finds and creates all registered components within the root
11199
* @param {HTMLElement} root
100+
* @param {boolean} withLazy - Whether or not initialize component which marked as lazy
112101
*/
113-
init(root = document.body) {
102+
init(root = document.body, withLazy = true) {
114103
this._registredComponents.forEach(({ componentClass, selector }) => {
115-
this._initComponent(root, componentClass, selector);
104+
this._initComponent(root, componentClass, selector, withLazy);
116105
});
117106

118107
if (process.env.NODE_ENV === 'development') {
@@ -127,14 +116,15 @@ class DcFactory {
127116
* @param {HTMLElement} root
128117
* @param {typeof DcBaseComponent} componentClass
129118
* @param {Function|string} selector
119+
* @param {boolean} withLazy
130120
* @private
131121
*/
132-
_initComponent(root, componentClass, selector) {
122+
_initComponent(root, componentClass, selector, withLazy) {
133123
try {
134124
const elements = dcDom.findElementsForInit(root, componentClass.getNamespace(), selector);
135125
if (elements.length > 0) {
136126
elements.forEach((element) => {
137-
this._initComponentOnElement(element, componentClass);
127+
this._initComponentOnElement(element, componentClass, withLazy);
138128
});
139129
}
140130
} catch (e) {
@@ -143,25 +133,49 @@ class DcFactory {
143133
}
144134
}
145135

136+
_isComponentLazy(element) {
137+
return dcDom.isElementWithinLazyParent(element);
138+
}
139+
146140
/**
147141
* Init component class on elements
148142
* @param {HTMLElement} element
149143
* @param {typeof DcBaseComponent} componentClass
144+
* @param {boolean} withLazy
150145
* @private
151146
*/
152-
_initComponentOnElement(element, componentClass) {
153-
if (!this._isComponentCreatedOnElement(element, componentClass)) {
154-
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_INITIALIZING);
155-
// TODO consider more sophisticated optimization technique
156-
setTimeout(() => {
157-
try {
158-
const instance = this._createComponentOnElement(componentClass, element);
159-
this._onComponentCreated(instance, componentClass);
160-
} catch (error) {
161-
this._onComponentCreationError(error, element, componentClass);
147+
_initComponentOnElement(element, componentClass, withLazy) {
148+
const state = this._getComponentStateOnElement(element, componentClass);
149+
switch (state) {
150+
// ignore components which are already created or in the middle of that process
151+
case COMPONENT_STATE_CREATED:
152+
case COMPONENT_STATE_ERROR:
153+
case COMPONENT_STATE_INITIALIZING:
154+
return;
155+
case COMPONENT_STATE_LAZY_WAITING:
156+
if (!withLazy) {
157+
return;
162158
}
163-
}, 0);
164159
}
160+
161+
// if component is lazy but we should not instantiate it according withLazy = false
162+
// we need to mark this component and wait until withLazy = true
163+
if (!withLazy && this._isComponentLazy(element)) {
164+
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_LAZY_WAITING);
165+
return;
166+
}
167+
168+
// finally init component on element
169+
this._setComponentStateOnElement(element, componentClass, COMPONENT_STATE_INITIALIZING);
170+
// TODO consider more sophisticated optimization technique
171+
setTimeout(() => {
172+
try {
173+
const instance = this._createComponentOnElement(componentClass, element);
174+
this._onComponentCreated(instance, componentClass);
175+
} catch (error) {
176+
this._onComponentCreationError(error, element, componentClass);
177+
}
178+
}, 0);
165179
}
166180

167181
/**

0 commit comments

Comments
 (0)