Skip to content

Commit 64639dd

Browse files
authored
Merge pull request #1295 from usablica/intro-refresh-cached-element
fix: refactoring the introForElements function + adding tests
2 parents e55efdb + 9d328a9 commit 64639dd

File tree

7 files changed

+414
-173
lines changed

7 files changed

+414
-173
lines changed

src/core/fetchIntroSteps.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import forEach from "../util/forEach";
2+
import cloneObject from "../util/cloneObject";
3+
import createElement from "../util/createElement";
4+
5+
/**
6+
* Finds all Intro steps from the data-* attributes and the options.steps array
7+
*
8+
* @api private
9+
* @param targetElm
10+
* @returns {[]}
11+
*/
12+
export default function fetchIntroSteps(targetElm) {
13+
const allIntroSteps = targetElm.querySelectorAll("*[data-intro]");
14+
let introItems = [];
15+
16+
if (this._options.steps) {
17+
//use steps passed programmatically
18+
forEach(this._options.steps, (step) => {
19+
const currentItem = cloneObject(step);
20+
21+
//set the step
22+
currentItem.step = introItems.length + 1;
23+
24+
currentItem.title = currentItem.title || "";
25+
26+
//use querySelector function only when developer used CSS selector
27+
if (typeof currentItem.element === "string") {
28+
//grab the element with given selector from the page
29+
currentItem.element = document.querySelector(currentItem.element);
30+
}
31+
32+
//intro without element
33+
if (
34+
typeof currentItem.element === "undefined" ||
35+
currentItem.element === null
36+
) {
37+
let floatingElementQuery = document.querySelector(
38+
".introjsFloatingElement"
39+
);
40+
41+
if (floatingElementQuery === null) {
42+
floatingElementQuery = createElement("div", {
43+
className: "introjsFloatingElement",
44+
});
45+
46+
document.body.appendChild(floatingElementQuery);
47+
}
48+
49+
currentItem.element = floatingElementQuery;
50+
currentItem.position = "floating";
51+
}
52+
53+
currentItem.position = currentItem.position || this._options.tooltipPosition;
54+
currentItem.scrollTo = currentItem.scrollTo || this._options.scrollTo;
55+
56+
if (typeof currentItem.disableInteraction === "undefined") {
57+
currentItem.disableInteraction = this._options.disableInteraction;
58+
}
59+
60+
if (currentItem.element !== null) {
61+
introItems.push(currentItem);
62+
}
63+
});
64+
} else {
65+
//use steps from data-* annotations
66+
const elmsLength = allIntroSteps.length;
67+
let disableInteraction;
68+
69+
//if there's no element to intro
70+
if (elmsLength < 1) {
71+
return [];
72+
}
73+
74+
forEach(allIntroSteps, (currentElement) => {
75+
// start intro for groups of elements
76+
if (this._options.group && currentElement.getAttribute("data-intro-group") !== this._options.group) {
77+
return;
78+
}
79+
80+
// skip hidden elements
81+
if (currentElement.style.display === "none") {
82+
return;
83+
}
84+
85+
const step = parseInt(currentElement.getAttribute("data-step"), 10);
86+
87+
if (currentElement.hasAttribute("data-disable-interaction")) {
88+
disableInteraction = !!currentElement.getAttribute(
89+
"data-disable-interaction"
90+
);
91+
} else {
92+
disableInteraction = this._options.disableInteraction;
93+
}
94+
95+
if (step > 0) {
96+
introItems[step - 1] = {
97+
element: currentElement,
98+
title: currentElement.getAttribute("data-title") || "",
99+
intro: currentElement.getAttribute("data-intro"),
100+
step: parseInt(currentElement.getAttribute("data-step"), 10),
101+
tooltipClass: currentElement.getAttribute("data-tooltipclass"),
102+
highlightClass: currentElement.getAttribute("data-highlightclass"),
103+
position:
104+
currentElement.getAttribute("data-position") ||
105+
this._options.tooltipPosition,
106+
scrollTo:
107+
currentElement.getAttribute("data-scrollto") ||
108+
this._options.scrollTo,
109+
disableInteraction,
110+
};
111+
}
112+
});
113+
114+
//next add intro items without data-step
115+
//todo: we need a cleanup here, two loops are redundant
116+
let nextStep = 0;
117+
118+
forEach(allIntroSteps, (currentElement) => {
119+
// start intro for groups of elements
120+
if (this._options.group && currentElement.getAttribute("data-intro-group") !== this._options.group) {
121+
return;
122+
}
123+
124+
if (currentElement.getAttribute("data-step") === null) {
125+
while (true) {
126+
if (typeof introItems[nextStep] === "undefined") {
127+
break;
128+
} else {
129+
nextStep++;
130+
}
131+
}
132+
133+
if (currentElement.hasAttribute("data-disable-interaction")) {
134+
disableInteraction = !!currentElement.getAttribute(
135+
"data-disable-interaction"
136+
);
137+
} else {
138+
disableInteraction = this._options.disableInteraction;
139+
}
140+
141+
introItems[nextStep] = {
142+
element: currentElement,
143+
title: currentElement.getAttribute("data-title") || "",
144+
intro: currentElement.getAttribute("data-intro"),
145+
step: nextStep + 1,
146+
tooltipClass: currentElement.getAttribute("data-tooltipclass"),
147+
highlightClass: currentElement.getAttribute("data-highlightclass"),
148+
position:
149+
currentElement.getAttribute("data-position") ||
150+
this._options.tooltipPosition,
151+
scrollTo:
152+
currentElement.getAttribute("data-scrollto") ||
153+
this._options.scrollTo,
154+
disableInteraction,
155+
};
156+
}
157+
});
158+
}
159+
160+
//removing undefined/null elements
161+
const tempIntroItems = [];
162+
for (let z = 0; z < introItems.length; z++) {
163+
if (introItems[z]) {
164+
// copy non-falsy values to the end of the array
165+
tempIntroItems.push(introItems[z]);
166+
}
167+
}
168+
169+
introItems = tempIntroItems;
170+
171+
//Ok, sort all items with given steps
172+
introItems.sort((a, b) => a.step - b.step);
173+
174+
return introItems;
175+
}

src/core/introForElement.js

Lines changed: 8 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,186 +1,27 @@
11
import addOverlayLayer from "./addOverlayLayer";
2-
import cloneObject from "../util/cloneObject";
3-
import forEach from "../util/forEach";
42
import DOMEvent from "./DOMEvent";
53
import { nextStep } from "./steps";
64
import onKeyDown from "./onKeyDown";
75
import onResize from "./onResize";
8-
import createElement from "../util/createElement";
6+
import fetchIntroSteps from "./fetchIntroSteps";
97

108
/**
119
* Initiate a new introduction/guide from an element in the page
1210
*
1311
* @api private
14-
* @method _introForElement
12+
* @method introForElement
1513
* @param {Object} targetElm
16-
* @param {String} group
1714
* @returns {Boolean} Success or not?
1815
*/
19-
export default function introForElement(targetElm, group) {
20-
const allIntroSteps = targetElm.querySelectorAll("*[data-intro]");
21-
let introItems = [];
22-
23-
if (this._options.steps) {
24-
//use steps passed programmatically
25-
forEach(this._options.steps, (step) => {
26-
const currentItem = cloneObject(step);
27-
28-
//set the step
29-
currentItem.step = introItems.length + 1;
30-
31-
currentItem.title = currentItem.title || "";
32-
33-
//use querySelector function only when developer used CSS selector
34-
if (typeof currentItem.element === "string") {
35-
//grab the element with given selector from the page
36-
currentItem.element = document.querySelector(currentItem.element);
37-
}
38-
39-
//intro without element
40-
if (
41-
typeof currentItem.element === "undefined" ||
42-
currentItem.element === null
43-
) {
44-
let floatingElementQuery = document.querySelector(
45-
".introjsFloatingElement"
46-
);
47-
48-
if (floatingElementQuery === null) {
49-
floatingElementQuery = createElement("div", {
50-
className: "introjsFloatingElement",
51-
});
52-
53-
document.body.appendChild(floatingElementQuery);
54-
}
55-
56-
currentItem.element = floatingElementQuery;
57-
currentItem.position = "floating";
58-
}
59-
60-
currentItem.scrollTo = currentItem.scrollTo || this._options.scrollTo;
61-
62-
if (typeof currentItem.disableInteraction === "undefined") {
63-
currentItem.disableInteraction = this._options.disableInteraction;
64-
}
65-
66-
if (currentItem.element !== null) {
67-
introItems.push(currentItem);
68-
}
69-
});
70-
} else {
71-
//use steps from data-* annotations
72-
const elmsLength = allIntroSteps.length;
73-
let disableInteraction;
74-
75-
//if there's no element to intro
76-
if (elmsLength < 1) {
77-
return false;
78-
}
79-
80-
forEach(allIntroSteps, (currentElement) => {
81-
// PR #80
82-
// start intro for groups of elements
83-
if (group && currentElement.getAttribute("data-intro-group") !== group) {
84-
return;
85-
}
86-
87-
// skip hidden elements
88-
if (currentElement.style.display === "none") {
89-
return;
90-
}
91-
92-
const step = parseInt(currentElement.getAttribute("data-step"), 10);
93-
94-
if (currentElement.hasAttribute("data-disable-interaction")) {
95-
disableInteraction = !!currentElement.getAttribute(
96-
"data-disable-interaction"
97-
);
98-
} else {
99-
disableInteraction = this._options.disableInteraction;
100-
}
101-
102-
if (step > 0) {
103-
introItems[step - 1] = {
104-
element: currentElement,
105-
title: currentElement.getAttribute("data-title") || "",
106-
intro: currentElement.getAttribute("data-intro"),
107-
step: parseInt(currentElement.getAttribute("data-step"), 10),
108-
tooltipClass: currentElement.getAttribute("data-tooltipclass"),
109-
highlightClass: currentElement.getAttribute("data-highlightclass"),
110-
position:
111-
currentElement.getAttribute("data-position") ||
112-
this._options.tooltipPosition,
113-
scrollTo:
114-
currentElement.getAttribute("data-scrollto") ||
115-
this._options.scrollTo,
116-
disableInteraction,
117-
};
118-
}
119-
});
120-
121-
//next add intro items without data-step
122-
//todo: we need a cleanup here, two loops are redundant
123-
let nextStep = 0;
124-
125-
forEach(allIntroSteps, (currentElement) => {
126-
// PR #80
127-
// start intro for groups of elements
128-
if (group && currentElement.getAttribute("data-intro-group") !== group) {
129-
return;
130-
}
131-
132-
if (currentElement.getAttribute("data-step") === null) {
133-
while (true) {
134-
if (typeof introItems[nextStep] === "undefined") {
135-
break;
136-
} else {
137-
nextStep++;
138-
}
139-
}
140-
141-
if (currentElement.hasAttribute("data-disable-interaction")) {
142-
disableInteraction = !!currentElement.getAttribute(
143-
"data-disable-interaction"
144-
);
145-
} else {
146-
disableInteraction = this._options.disableInteraction;
147-
}
148-
149-
introItems[nextStep] = {
150-
element: currentElement,
151-
title: currentElement.getAttribute("data-title") || "",
152-
intro: currentElement.getAttribute("data-intro"),
153-
step: nextStep + 1,
154-
tooltipClass: currentElement.getAttribute("data-tooltipclass"),
155-
highlightClass: currentElement.getAttribute("data-highlightclass"),
156-
position:
157-
currentElement.getAttribute("data-position") ||
158-
this._options.tooltipPosition,
159-
scrollTo:
160-
currentElement.getAttribute("data-scrollto") ||
161-
this._options.scrollTo,
162-
disableInteraction,
163-
};
164-
}
165-
});
166-
}
16+
export default function introForElement(targetElm ) {
17+
//set it to the introJs object
18+
const steps = fetchIntroSteps.call(this, targetElm);
16719

168-
//removing undefined/null elements
169-
const tempIntroItems = [];
170-
for (let z = 0; z < introItems.length; z++) {
171-
if (introItems[z]) {
172-
// copy non-falsy values to the end of the array
173-
tempIntroItems.push(introItems[z]);
174-
}
20+
if (steps.length === 0) {
21+
return false;
17522
}
17623

177-
introItems = tempIntroItems;
178-
179-
//Ok, sort all items with given steps
180-
introItems.sort((a, b) => a.step - b.step);
181-
182-
//set it to the introJs object
183-
this._introItems = introItems;
24+
this._introItems = steps;
18425

18526
//add overlay layer to the page
18627
if (addOverlayLayer.call(this, targetElm)) {

src/core/refresh.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { reAlignHints } from "./hint";
22
import setHelperLayerPosition from "./setHelperLayerPosition";
33
import placeTooltip from "./placeTooltip";
4+
import fetchIntroSteps from "./fetchIntroSteps";
45

56
/**
67
* Update placement of the intro objects on the screen
78
* @api private
9+
* @param {boolean} refreshSteps to refresh the intro steps as well
810
*/
9-
export default function refresh() {
11+
export default function refresh(refreshSteps) {
1012
// re-align intros
1113
setHelperLayerPosition.call(
1214
this,
@@ -21,6 +23,10 @@ export default function refresh() {
2123
document.querySelector(".introjs-disableInteraction")
2224
);
2325

26+
if (refreshSteps) {
27+
this._introItems = fetchIntroSteps.call(this, this._targetElement);
28+
}
29+
2430
// re-align tooltip
2531
if (this._currentStep !== undefined && this._currentStep !== null) {
2632
const oldArrowLayer = document.querySelector(".introjs-arrow");

0 commit comments

Comments
 (0)