-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom Event handling methods as solution to circular dependencies #1091
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/** | ||
* IntroJs main class | ||
* | ||
* @class IntroJs | ||
*/ | ||
function IntroJs(obj) { | ||
this._targetElement = obj; | ||
this._introItems = []; | ||
|
||
this._options = { | ||
/** Next button label in tooltip box */ | ||
nextLabel: "Next →", | ||
/** Previous button label in tooltip box */ | ||
prevLabel: "← Back", | ||
/** Skip button label in tooltip box */ | ||
skipLabel: "Skip", | ||
/** Done button label in tooltip box */ | ||
doneLabel: "Done", | ||
/** Hide previous button in the first step? Otherwise, it will be disabled button. */ | ||
hidePrev: false, | ||
/** Hide next button in the last step? Otherwise, it will be disabled button. */ | ||
hideNext: false, | ||
/** Default tooltip box position */ | ||
tooltipPosition: "bottom", | ||
/** Next CSS class for tooltip boxes */ | ||
tooltipClass: "", | ||
/** CSS class that is added to the helperLayer */ | ||
highlightClass: "", | ||
/** Close introduction when pressing Escape button? */ | ||
exitOnEsc: true, | ||
/** Close introduction when clicking on overlay layer? */ | ||
exitOnOverlayClick: true, | ||
/** Show step numbers in introduction? */ | ||
showStepNumbers: true, | ||
/** Let user use keyboard to navigate the tour? */ | ||
keyboardNavigation: true, | ||
/** Show tour control buttons? */ | ||
showButtons: true, | ||
/** Show tour bullets? */ | ||
showBullets: true, | ||
/** Show tour progress? */ | ||
showProgress: false, | ||
/** Scroll to highlighted element? */ | ||
scrollToElement: true, | ||
/** | ||
* Should we scroll the tooltip or target element? | ||
* | ||
* Options are: 'element' or 'tooltip' | ||
*/ | ||
scrollTo: "element", | ||
/** Padding to add after scrolling when element is not in the viewport (in pixels) */ | ||
scrollPadding: 30, | ||
/** Set the overlay opacity */ | ||
overlayOpacity: 0.5, | ||
/** Precedence of positions, when auto is enabled */ | ||
positionPrecedence: ["bottom", "top", "right", "left"], | ||
/** Disable an interaction with element? */ | ||
disableInteraction: false, | ||
/** Set how much padding to be used around helper element */ | ||
helperElementPadding: 10, | ||
/** Default hint position */ | ||
hintPosition: "top-middle", | ||
/** Hint button label */ | ||
hintButtonLabel: "Got it", | ||
/** Adding animation to hints? */ | ||
hintAnimation: true, | ||
/** additional classes to put on the buttons */ | ||
buttonClass: "introjs-button", | ||
/** additional classes to put on progress bar */ | ||
progressBarAdditionalClass: false, | ||
}; | ||
} | ||
|
||
export default IntroJs; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/** empty function, used for gracefully removing event listeners */ | ||
const noop = () => {}; | ||
|
||
/** | ||
* events: extends a class to accept custom event listener methods: | ||
* (on/off/fire/once) | ||
*/ | ||
export const events = { | ||
/** | ||
* Adds custom event listeners to an instance | ||
* | ||
* @param {string | Record<string, () => void>} types | ||
* @param {() => void} fn | ||
* @param {*} context | ||
*/ | ||
on(types, fn, context) { | ||
// types can be a map of types/handlers | ||
if (typeof types === "object") { | ||
for (let type in types) { | ||
this._on(type, types[type], fn); | ||
} | ||
} else { | ||
this._on(types, fn, context); | ||
} | ||
|
||
return this; | ||
}, | ||
|
||
/** | ||
* Removes custom event listeners from an instance | ||
* | ||
* @param {string | Record<string, () => void>} types | ||
* @param {() => void} fn | ||
* @param {*} context | ||
*/ | ||
off(types, fn, context) { | ||
if (!types) { | ||
// clear all listeners if called without arguments | ||
delete this._events; | ||
} else if (typeof types === "object") { | ||
for (let type in types) { | ||
this._off(type, types[type], fn); | ||
} | ||
} else { | ||
this._off(types, fn, context); | ||
} | ||
|
||
return this; | ||
}, | ||
|
||
/** | ||
* Internal add event listener | ||
* | ||
* @param {string} type | ||
* @param {() => void} fn | ||
* @param {*} context | ||
*/ | ||
_on(type, fn, context) { | ||
this._events = this._events || {}; | ||
|
||
/* lazy initialize this._events[type] */ | ||
const listeners = this._events[type] || []; | ||
this._events[type] = listeners; | ||
|
||
const newListener = { fn: fn, ctx: context }; | ||
|
||
// check if fn already there | ||
for (const listener of listeners) { | ||
if (listener.fn === fn && listener.ctx === context) { | ||
return; | ||
} | ||
} | ||
|
||
listeners.push(newListener); | ||
}, | ||
|
||
/** keeps track of calls to fire, so we can safely remove event listeners inside of event listeners */ | ||
_firingCount: 0, | ||
|
||
/** | ||
* Internal remove event listener | ||
* | ||
* @param {string} type | ||
* @param {() => void} fn | ||
* @param {*} context | ||
*/ | ||
_off(type, fn, context) { | ||
if (!this._events) { | ||
return; | ||
} | ||
|
||
let listeners = this._events[type] || []; | ||
|
||
if (listeners.length === 0) { | ||
return; | ||
} | ||
|
||
if (!fn) { | ||
// Set all removed listeners to noop so they are not called if remove happens in fire | ||
for (const listener of listeners) { | ||
listener.fn = noop; | ||
} | ||
// clear all listeners for a type if function isn't specified | ||
delete this._events[type]; | ||
return; | ||
} | ||
|
||
// find fn and remove it | ||
for (let i = 0, len = listeners.length; i < len; i++) { | ||
const listener = listeners[i]; | ||
if (listener.ctx === context && listener.fn === fn) { | ||
// set the removed listener to noop so that's not called if remove happens in fire | ||
listener.fn = noop; | ||
|
||
if (this._firingCount) { | ||
// copy array in case events are being fired | ||
this._events[type] = listeners = listeners.slice(); | ||
} | ||
|
||
listeners.splice(i, 1); | ||
|
||
return; | ||
} | ||
} | ||
}, | ||
|
||
/** | ||
* Calls all event listeners that listen for a given event type | ||
* @param {string} type name of event | ||
* @param {*} data data to be passed to event handler | ||
*/ | ||
fire(type, data) { | ||
if (!this._events || !this.listens(type)) { | ||
return this; | ||
} | ||
|
||
const listeners = this._events[type] || []; | ||
|
||
if (listeners.length > 0) { | ||
const event = Object.assign({}, data, { | ||
type: type, | ||
target: this, | ||
sourceTarget: (data && data.sourceTarget) || this, | ||
}); | ||
|
||
this._firingCount++; | ||
for (const listener of listeners) { | ||
listener.fn.call(listener.ctx || this, event); | ||
} | ||
this._firingCount--; | ||
} | ||
|
||
return this; | ||
}, | ||
|
||
/** | ||
* Determine if an event type has any listeners | ||
* | ||
* @param {string} type event type | ||
* @returns boolean | ||
*/ | ||
listens(type) { | ||
const listeners = this._events && this._events[type]; | ||
|
||
return listeners && listeners.length > 0; | ||
}, | ||
|
||
/** | ||
* Adds custom event listeners to an instance which fire only once | ||
* | ||
* @param {string | Record<string, () => void>} types | ||
* @param {() => void} fn | ||
* @param {*} context | ||
*/ | ||
once(types, fn, context) { | ||
if (typeof types === "object") { | ||
for (const type in types) { | ||
this.once(type, types[type], fn); | ||
} | ||
return this; | ||
} | ||
|
||
const removeFn = function () { | ||
this.off(types, fn, context).off(types, removeFn, context); | ||
}.bind(this); | ||
|
||
// add a listener that's executed once and removed after that | ||
return this.on(types, fn, context).on(types, removeFn, context); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,12 @@ | ||
import forEach from "../util/forEach"; | ||
import DOMEvent from "./DOMEvent"; | ||
import onKeyDown from "./onKeyDown"; | ||
import onResize from "./onResize"; | ||
import removeShowElement from "./removeShowElement"; | ||
import removeChild from "../util/removeChild"; | ||
|
||
/** | ||
* Exit from intro | ||
* | ||
* @api private | ||
* @this {import('./IntroJs').default} | ||
* @method _exitIntro | ||
* @param {Object} targetElement | ||
* @param {Boolean} force - Setting to `true` will skip the result of beforeExit callback | ||
|
@@ -34,15 +32,6 @@ export default function exitIntro(targetElement, force) { | |
forEach(overlayLayers, (overlayLayer) => removeChild(overlayLayer)); | ||
} | ||
|
||
//remove all helper layers | ||
const helperLayer = targetElement.querySelector(".introjs-helperLayer"); | ||
removeChild(helperLayer, true); | ||
|
||
const referenceLayer = targetElement.querySelector( | ||
".introjs-tooltipReferenceLayer" | ||
); | ||
removeChild(referenceLayer); | ||
|
||
//remove disableInteractionLayer | ||
const disableInteractionLayer = targetElement.querySelector( | ||
".introjs-disableInteraction" | ||
|
@@ -55,15 +44,13 @@ export default function exitIntro(targetElement, force) { | |
|
||
removeShowElement(); | ||
|
||
//clean listeners | ||
DOMEvent.off(window, "keydown", onKeyDown, this, true); | ||
DOMEvent.off(window, "resize", onResize, this, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we handling these somewhere else? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh nvm, just saw the |
||
// signal to all listeners that introjs has exited | ||
this.fire('exit'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💯 |
||
|
||
//check if any callback is defined | ||
// check if any callback is defined | ||
if (this._introExitCallback !== undefined) { | ||
this._introExitCallback.call(this); | ||
} | ||
|
||
//set the step to zero | ||
this._currentStep = undefined; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we don't need this anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed in another file