Skip to content
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

Use autoBind, pass context rather than manually binding #440

Merged
merged 1 commit into from
Jul 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"types": "src/types/shepherd.d.ts",
"module": "dist/js/shepherd.esm.js",
"dependencies": {
"auto-bind": "^2.1.0",
"body-scroll-lock": "^2.6.3",
"core-js": "^3.1.4",
"element-matches": "^0.1.2",
Expand Down
35 changes: 12 additions & 23 deletions src/js/step.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { isElement, isFunction, isUndefined } from './utils/type-check';
import autoBind from 'auto-bind';

import { Evented } from './evented.js';
import { bindAdvance, bindButtonEvents, bindCancelLink, bindMethods } from './utils/bind.js';
import { isElement, isFunction, isUndefined } from './utils/type-check';
import { bindAdvance, bindButtonEvents, bindCancelLink } from './utils/bind.js';
import { createFromHTML, setupTooltip, parseAttachTo } from './utils/general.js';

// Polyfills
Expand Down Expand Up @@ -106,23 +108,10 @@ export class Step extends Evented {
constructor(tour, options) {
super(tour, options);
this.tour = tour;
bindMethods.call(this, [
'_scrollTo',
'_setupElements',
'_show',
'cancel',
'complete',
'destroy',
'hide',
'isOpen',
'show'
]);

autoBind(this);

this._setOptions(options);
this.bindAdvance = bindAdvance.bind(this);
this.bindButtonEvents = bindButtonEvents.bind(this);
this.bindCancelLink = bindCancelLink.bind(this);
this.setupTooltip = setupTooltip.bind(this);
this.parseAttachTo = parseAttachTo.bind(this);

return this;
}
Expand Down Expand Up @@ -239,7 +228,7 @@ export class Step extends Evented {
`<button class="shepherd-button ${cfg.classes || ''}" tabindex="0">${cfg.text}</button>`
);
footer.appendChild(button);
this.bindButtonEvents(cfg, button);
bindButtonEvents(cfg, button, this);
});

content.appendChild(footer);
Expand All @@ -258,7 +247,7 @@ export class Step extends Evented {
header.appendChild(link);

element.classList.add('shepherd-has-cancel-link');
this.bindCancelLink(link);
bindCancelLink(link, this);
}
}

Expand Down Expand Up @@ -396,7 +385,7 @@ export class Step extends Evented {
* @private
*/
_scrollTo(scrollToOptions) {
const { element } = this.parseAttachTo();
const { element } = parseAttachTo(this);

if (isFunction(this.options.scrollToHandler)) {
this.options.scrollToHandler(element);
Expand Down Expand Up @@ -438,10 +427,10 @@ export class Step extends Evented {
this._addKeyDownHandler(this.el);

if (this.options.advanceOn) {
this.bindAdvance();
bindAdvance(this);
}

this.setupTooltip();
setupTooltip(this);
}

/**
Expand Down
20 changes: 8 additions & 12 deletions src/js/tour.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { isFunction, isNumber, isString, isUndefined } from './utils/type-check';
import autoBind from 'auto-bind';
import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock/lib/bodyScrollLock.es6.js';
import tippy from 'tippy.js';

import { Evented } from './evented.js';
import { Modal } from './modal.js';
import { Step } from './step.js';
import { bindMethods } from './utils/bind.js';
import tippy from 'tippy.js';
import { disableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock/lib/bodyScrollLock.es6.js';
import { isFunction, isNumber, isString, isUndefined } from './utils/type-check';
import { defaults as tooltipDefaults } from './utils/tooltip-defaults';

import { cleanupSteps, cleanupStepEventListeners } from './utils/cleanup';
import { getElementForStep } from './utils/dom';
import { toggleShepherdModalClass } from './utils/modal';
Expand Down Expand Up @@ -47,13 +47,9 @@ export class Tour extends Evented {
*/
constructor(options = {}) {
super(options);
bindMethods.call(this, [
'back',
'cancel',
'complete',
'hide',
'next'
]);

autoBind(this);

this.options = options;
this.steps = this.options.steps || [];

Expand Down
34 changes: 19 additions & 15 deletions src/js/utils/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,33 @@ import { isString, isUndefined } from './type-check';

/**
* Sets up the handler to determine if we should advance the tour
* @param selector
* @param {string} selector
* @param {Step} step The step instance
* @return {Function}
* @private
*/
function _setupAdvanceOnHandler(selector) {
function _setupAdvanceOnHandler(selector, step) {
return (event) => {
if (this.isOpen()) {
const targetIsEl = this.el && event.target === this.el;
if (step.isOpen()) {
const targetIsEl = step.el && event.target === step.el;
const targetIsSelector = !isUndefined(selector) && event.target.matches(selector);

if (targetIsSelector || targetIsEl) {
this.tour.next();
step.tour.next();
}
}
};
}

/**
* Bind the event handler for advanceOn
* @param {Step} step The step instance
*/
export function bindAdvance() {
export function bindAdvance(step) {
// An empty selector matches the step element
const { event, selector } = this.options.advanceOn || {};
const { event, selector } = step.options.advanceOn || {};
if (event) {
const handler = _setupAdvanceOnHandler.call(this, selector);
const handler = _setupAdvanceOnHandler(selector, step);

// TODO: this should also bind/unbind on show/hide
let el;
Expand All @@ -39,12 +41,12 @@ export function bindAdvance() {
return console.error(`No element was found for the selector supplied to advanceOn: ${selector}`);
} else if (el) {
el.addEventListener(event, handler);
this.on('destroy', () => {
step.on('destroy', () => {
return el.removeEventListener(event, handler);
});
} else {
document.body.addEventListener(event, handler, true);
this.on('destroy', () => {
step.on('destroy', () => {
return document.body.removeEventListener(event, handler, true);
});
}
Expand All @@ -57,8 +59,9 @@ export function bindAdvance() {
* Bind events to the buttons for next, back, etc
* @param {Object} cfg An object containing the config options for the button
* @param {HTMLElement} el The element for the button
* @param {Step} step The step instance
*/
export function bindButtonEvents(cfg, el) {
export function bindButtonEvents(cfg, el, step) {
cfg.events = cfg.events || {};
if (!isUndefined(cfg.action)) {
// Including both a click event and an action is not supported
Expand All @@ -69,13 +72,13 @@ export function bindButtonEvents(cfg, el) {
Object.entries(cfg.events).forEach(([event, handler]) => {
if (isString(handler)) {
const page = handler;
handler = () => this.tour.show(page);
handler = () => step.tour.show(page);
}
el.dataset.buttonEvent = true;
el.addEventListener(event, handler);

// Cleanup event listeners on destroy
this.on('destroy', () => {
step.on('destroy', () => {
el.removeAttribute('data-button-event');
el.removeEventListener(event, handler);
});
Expand All @@ -86,11 +89,12 @@ export function bindButtonEvents(cfg, el) {
/**
* Add a click listener to the cancel link that cancels the tour
* @param {HTMLElement} link The cancel link element
* @param {Step} step The step instance
*/
export function bindCancelLink(link) {
export function bindCancelLink(link, step) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.cancel();
step.cancel();
});
}

Expand Down
55 changes: 30 additions & 25 deletions src/js/utils/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,36 @@ export function debounce(func, wait, immediate) {

/**
* Determines options for the tooltip and initializes
* `this.tooltip` as a Tippy.js instance.
* `step.tooltip` as a Tippy.js instance.
* @param {Step} step The step instance
*/
export function setupTooltip() {
export function setupTooltip(step) {
if (isUndefined(tippy)) {
throw new Error(missingTippy);
}

if (this.tooltip) {
this.tooltip.destroy();
if (step.tooltip) {
step.tooltip.destroy();
}

const attachToOpts = this.parseAttachTo();
const attachToOpts = parseAttachTo(step);

this.tooltip = _makeTippyInstance.call(this, attachToOpts);
step.tooltip = _makeTippyInstance(attachToOpts, step);

this.target = attachToOpts.element || document.body;
step.target = attachToOpts.element || document.body;

this.el.classList.add('shepherd-element');
step.el.classList.add('shepherd-element');
}

/**
* Checks if options.attachTo.element is a string, and if so, tries to find the element
* @param {Step} step The step instance
* @returns {{element, on}}
* `element` is a qualified HTML Element
* `on` is a string position value
*/
export function parseAttachTo() {
const options = this.options.attachTo || {};
export function parseAttachTo(step) {
const options = step.options.attachTo || {};
const returnOpts = Object.assign({}, options);

if (isString(options.element)) {
Expand Down Expand Up @@ -137,16 +139,17 @@ function _createClassModifier(className) {

/**
* Generates a `Tippy` instance from a set of base `attachTo` options
*
* @return {tippy} The final tippy instance
* @param attachToOptions
* @param {Step} step The step instance
* @return {tippy|Instance | Instance[]} The final tippy instance
* @private
*/
function _makeTippyInstance(attachToOptions) {
function _makeTippyInstance(attachToOptions, step) {
if (!attachToOptions.element) {
return _makeCenteredTippy.call(this);
return _makeCenteredTippy(step);
}

const tippyOptions = _makeAttachedTippyOptions.call(this, attachToOptions);
const tippyOptions = _makeAttachedTippyOptions(attachToOptions, step);

return tippy(attachToOptions.element, tippyOptions);
}
Expand All @@ -156,24 +159,25 @@ function _makeTippyInstance(attachToOptions) {
* target an element in the DOM.
*
* @param {Object} attachToOptions The local `attachTo` options
* @param {Step} step The step instance
* @return {Object} The final tippy options object
* @private
*/
function _makeAttachedTippyOptions(attachToOptions) {
function _makeAttachedTippyOptions(attachToOptions, step) {
const resultingTippyOptions = {
content: this.el,
content: step.el,
flipOnUpdate: true,
placement: attachToOptions.on || 'right'
};

Object.assign(resultingTippyOptions, this.options.tippyOptions);
Object.assign(resultingTippyOptions, step.options.tippyOptions);

if (this.options.title) {
if (step.options.title) {
Object.assign(defaultPopperOptions.modifiers, { addHasTitleClass });
}

if (this.options.tippyOptions && this.options.tippyOptions.popperOptions) {
Object.assign(defaultPopperOptions, this.options.tippyOptions.popperOptions);
if (step.options.tippyOptions && step.options.tippyOptions.popperOptions) {
Object.assign(defaultPopperOptions, step.options.tippyOptions.popperOptions);
}

resultingTippyOptions.popperOptions = defaultPopperOptions;
Expand All @@ -186,20 +190,21 @@ function _makeAttachedTippyOptions(attachToOptions) {
* target element in the DOM -- and thus is positioned in the center
* of the view
*
* @param {Step} step The step instance
* @return {tippy} The final tippy instance
* @private
*/
function _makeCenteredTippy() {
function _makeCenteredTippy(step) {
const tippyOptions = {
content: this.el,
content: step.el,
placement: 'top',
...this.options.tippyOptions
...step.options.tippyOptions
};

tippyOptions.arrow = false;
tippyOptions.popperOptions = tippyOptions.popperOptions || {};

if (this.options.title) {
if (step.options.title) {
Object.assign(defaultPopperOptions.modifiers, { addHasTitleClass });
}

Expand Down
Loading