Skip to content

Commit ef202f0

Browse files
authored
Merge pull request usablica#803 from usablica/events-and-instances
reliable events and instances
2 parents e965a0d + 2db2187 commit ef202f0

File tree

1 file changed

+205
-95
lines changed

1 file changed

+205
-95
lines changed

intro.js

Lines changed: 205 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,7 @@
114114
*/
115115
function _introForElement(targetElm, group) {
116116
var allIntroSteps = targetElm.querySelectorAll("*[data-intro]"),
117-
introItems = [],
118-
self = this;
117+
introItems = [];
119118

120119
if (this._options.steps) {
121120
//use steps passed programmatically
@@ -261,92 +260,87 @@
261260
});
262261

263262
//set it to the introJs object
264-
self._introItems = introItems;
263+
this._introItems = introItems;
265264

266265
//add overlay layer to the page
267-
if(_addOverlayLayer.call(self, targetElm)) {
266+
if(_addOverlayLayer.call(this, targetElm)) {
268267
//then, start the show
269-
_nextStep.call(self);
270-
271-
self._onKeyDown = function(e) {
272-
/*
273-
on keyCode:
274-
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
275-
This feature has been removed from the Web standards.
276-
Though some browsers may still support it, it is in
277-
the process of being dropped.
278-
Instead, you should use KeyboardEvent.code,
279-
if it's implemented.
280-
281-
jQuery's approach is to test for
282-
(1) e.which, then
283-
(2) e.charCode, then
284-
(3) e.keyCode
285-
https://github.com/jquery/jquery/blob/a6b0705294d336ae2f63f7276de0da1195495363/src/event.js#L638
286-
*/
287-
var code = (e.code === null) ? e.which : e.code;
288-
289-
// if code/e.which is null
290-
if (code === null) {
291-
code = (e.charCode === null) ? e.keyCode : e.charCode;
292-
}
293-
294-
if ((code === 'Escape' || code === 27) && self._options.exitOnEsc === true) {
295-
//escape key pressed, exit the intro
296-
//check if exit callback is defined
297-
_exitIntro.call(self, targetElm);
298-
} else if (code === 'ArrowLeft' || code === 37) {
299-
//left arrow
300-
_previousStep.call(self);
301-
} else if (code === 'ArrowRight' || code === 39) {
302-
//right arrow
303-
_nextStep.call(self);
304-
} else if (code === 'Enter' || code === 13) {
305-
//srcElement === ie
306-
var target = e.target || e.srcElement;
307-
if (target && target.className.match('introjs-prevbutton')) {
308-
//user hit enter while focusing on previous button
309-
_previousStep.call(self);
310-
} else if (target && target.className.match('introjs-skipbutton')) {
311-
//user hit enter while focusing on skip button
312-
if (self._introItems.length - 1 === self._currentStep && typeof (self._introCompleteCallback) === 'function') {
313-
self._introCompleteCallback.call(self);
314-
}
268+
_nextStep.call(this);
315269

316-
_exitIntro.call(self, targetElm);
317-
} else {
318-
//default behavior for responding to enter
319-
_nextStep.call(self);
320-
}
270+
if (this._options.keyboardNavigation) {
271+
DOMEvent.on(window, 'keydown', _onKeyDown, this, true);
272+
}
273+
//for window resize
274+
DOMEvent.on(window, 'resize', _onResize, this, true);
275+
}
276+
return false;
277+
}
321278

322-
//prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers
323-
if(e.preventDefault) {
324-
e.preventDefault();
325-
} else {
326-
e.returnValue = false;
327-
}
328-
}
329-
};
279+
function _onResize () {
280+
this.refresh.call(this);
281+
}
330282

331-
self._onResize = function() {
332-
self.refresh.call(self);
333-
};
283+
/**
284+
* on keyCode:
285+
* https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
286+
* This feature has been removed from the Web standards.
287+
* Though some browsers may still support it, it is in
288+
* the process of being dropped.
289+
* Instead, you should use KeyboardEvent.code,
290+
* if it's implemented.
291+
*
292+
* jQuery's approach is to test for
293+
* (1) e.which, then
294+
* (2) e.charCode, then
295+
* (3) e.keyCode
296+
* https://github.com/jquery/jquery/blob/a6b0705294d336ae2f63f7276de0da1195495363/src/event.js#L638
297+
*
298+
* @param type var
299+
* @return type
300+
*/
301+
function _onKeyDown (e) {
302+
var code = (e.code === null) ? e.which : e.code;
334303

335-
if (window.addEventListener) {
336-
if (this._options.keyboardNavigation) {
337-
window.addEventListener('keydown', self._onKeyDown, true);
338-
}
339-
//for window resize
340-
window.addEventListener('resize', self._onResize, true);
341-
} else if (document.attachEvent) { //IE
342-
if (this._options.keyboardNavigation) {
343-
document.attachEvent('onkeydown', self._onKeyDown);
304+
// if code/e.which is null
305+
if (code === null) {
306+
code = (e.charCode === null) ? e.keyCode : e.charCode;
307+
}
308+
309+
if ((code === 'Escape' || code === 27) && this._options.exitOnEsc === true) {
310+
//escape key pressed, exit the intro
311+
//check if exit callback is defined
312+
_exitIntro.call(this, this._targetElement);
313+
} else if (code === 'ArrowLeft' || code === 37) {
314+
//left arrow
315+
_previousStep.call(this);
316+
} else if (code === 'ArrowRight' || code === 39) {
317+
//right arrow
318+
_nextStep.call(this);
319+
} else if (code === 'Enter' || code === 13) {
320+
//srcElement === ie
321+
var target = e.target || e.srcElement;
322+
if (target && target.className.match('introjs-prevbutton')) {
323+
//user hit enter while focusing on previous button
324+
_previousStep.call(this);
325+
} else if (target && target.className.match('introjs-skipbutton')) {
326+
//user hit enter while focusing on skip button
327+
if (this._introItems.length - 1 === this._currentStep && typeof (this._introCompleteCallback) === 'function') {
328+
this._introCompleteCallback.call(this);
344329
}
345-
//for window resize
346-
document.attachEvent('onresize', self._onResize);
330+
331+
_exitIntro.call(this, this._targetElement);
332+
} else {
333+
//default behavior for responding to enter
334+
_nextStep.call(this);
335+
}
336+
337+
//prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers
338+
if(e.preventDefault) {
339+
e.preventDefault();
340+
} else {
341+
e.returnValue = false;
347342
}
348343
}
349-
return false;
350344
}
351345

352346
/*
@@ -567,11 +561,8 @@
567561
});
568562

569563
//clean listeners
570-
if (window.removeEventListener) {
571-
window.removeEventListener('keydown', this._onKeyDown, true);
572-
} else if (document.detachEvent) { //IE
573-
document.detachEvent('onkeydown', this._onKeyDown);
574-
}
564+
DOMEvent.off(window, 'keydown', _onKeyDown, this, true);
565+
DOMEvent.off(window, 'resize', _onResize, this, true);
575566

576567
//check if any callback is defined
577568
if (this._introExitCallback !== undefined) {
@@ -1503,6 +1494,112 @@
15031494
}
15041495
}
15051496

1497+
/**
1498+
* Mark any object with an incrementing number
1499+
* used for keeping track of objects
1500+
*
1501+
* @param Object obj Any object or DOM Element
1502+
* @param String key
1503+
* @return Object
1504+
*/
1505+
var _stamp = (function () {
1506+
var keys = {};
1507+
return function stamp (obj, key) {
1508+
1509+
// get group key
1510+
key = key || 'introjs-stamp';
1511+
1512+
// each group increments from 0
1513+
keys[key] = keys[key] || 0;
1514+
1515+
// stamp only once per object
1516+
if (obj[key] === undefined) {
1517+
// increment key for each new object
1518+
obj[key] = keys[key]++;
1519+
}
1520+
1521+
return obj[key];
1522+
};
1523+
})();
1524+
1525+
/**
1526+
* DOMEvent Handles all DOM events
1527+
*
1528+
* methods:
1529+
*
1530+
* on - add event handler
1531+
* off - remove event
1532+
*/
1533+
var DOMEvent = (function () {
1534+
function DOMEvent () {
1535+
var events_key = 'introjs_event';
1536+
1537+
/**
1538+
* Gets a unique ID for an event listener
1539+
*
1540+
* @param Object obj
1541+
* @param String type event type
1542+
* @param Function listener
1543+
* @param Object context
1544+
* @return String
1545+
*/
1546+
this._id = function (obj, type, listener, context) {
1547+
return type + _stamp(listener) + (context ? '_' + _stamp(context) : '');
1548+
};
1549+
1550+
/**
1551+
* Adds event listener
1552+
*
1553+
* @param Object obj
1554+
* @param String type event type
1555+
* @param Function listener
1556+
* @param Object context
1557+
* @param Boolean useCapture
1558+
* @return null
1559+
*/
1560+
this.on = function (obj, type, listener, context, useCapture) {
1561+
var id = this._id.apply(this, arguments),
1562+
handler = function (e) {
1563+
return listener.call(context || obj, e || window.event);
1564+
};
1565+
1566+
if ('addEventListener' in obj) {
1567+
obj.addEventListener(type, handler, useCapture);
1568+
} else if ('attachEvent' in obj) {
1569+
obj.attachEvent('on' + type, handler);
1570+
}
1571+
1572+
obj[events_key] = obj[events_key] || {};
1573+
obj[events_key][id] = handler;
1574+
};
1575+
1576+
/**
1577+
* Removes event listener
1578+
*
1579+
* @param Object obj
1580+
* @param String type event type
1581+
* @param Function listener
1582+
* @param Object context
1583+
* @param Boolean useCapture
1584+
* @return null
1585+
*/
1586+
this.off = function (obj, type, listener, context, useCapture) {
1587+
var id = this._id.apply(this, arguments),
1588+
handler = obj[events_key] && obj[events_key][id];
1589+
1590+
if ('removeEventListener' in obj) {
1591+
obj.removeEventListener(type, handler, useCapture);
1592+
} else if ('detachEvent' in obj) {
1593+
obj.detachEvent('on' + type, handler);
1594+
}
1595+
1596+
obj[events_key][id] = null;
1597+
};
1598+
}
1599+
1600+
return new DOMEvent();
1601+
})();
1602+
15061603
/**
15071604
* Append a class to an element
15081605
*
@@ -1753,15 +1850,12 @@
17531850

17541851
_addHints.call(this);
17551852

1756-
if (document.addEventListener) {
1757-
document.addEventListener('click', _removeHintTooltip.bind(this), false);
1758-
//for window resize
1759-
window.addEventListener('resize', _reAlignHints.bind(this), true);
1760-
} else if (document.attachEvent) { //IE
1761-
//for window resize
1762-
document.attachEvent('onclick', _removeHintTooltip.bind(this));
1763-
document.attachEvent('onresize', _reAlignHints.bind(this));
1764-
}
1853+
/*
1854+
todo:
1855+
these events should be removed at some point
1856+
*/
1857+
DOMEvent.on(document, 'click', _removeHintTooltip, this, false);
1858+
DOMEvent.on(window, 'resize', _reAlignHints, this, true);
17651859
}
17661860

17671861
/**
@@ -2200,22 +2294,30 @@
22002294
}
22012295

22022296
var introJs = function (targetElm) {
2297+
var instance;
2298+
22032299
if (typeof (targetElm) === 'object') {
22042300
//Ok, create a new instance
2205-
return new IntroJs(targetElm);
2301+
instance = new IntroJs(targetElm);
22062302

22072303
} else if (typeof (targetElm) === 'string') {
22082304
//select the target element with query selector
22092305
var targetElement = document.querySelector(targetElm);
22102306

22112307
if (targetElement) {
2212-
return new IntroJs(targetElement);
2308+
instance = new IntroJs(targetElement);
22132309
} else {
22142310
throw new Error('There is no element with given selector.');
22152311
}
22162312
} else {
2217-
return new IntroJs(document.body);
2313+
instance = new IntroJs(document.body);
22182314
}
2315+
// add instance to list of _instances
2316+
// passing group to _stamp to increment
2317+
// from 0 onward somewhat reliably
2318+
introJs.instances[ _stamp(instance, 'introjs-instance') ] = instance;
2319+
2320+
return instance;
22192321
};
22202322

22212323
/**
@@ -2226,6 +2328,14 @@
22262328
*/
22272329
introJs.version = VERSION;
22282330

2331+
/**
2332+
* key-val object helper for introJs instances
2333+
*
2334+
* @property instances
2335+
* @type Object
2336+
*/
2337+
introJs.instances = {};
2338+
22292339
//Prototype
22302340
introJs.fn = IntroJs.prototype = {
22312341
clone: function () {

0 commit comments

Comments
 (0)