Skip to content

Commit

Permalink
Move bind methods to their own file (#222)
Browse files Browse the repository at this point in the history
* Move bind methods to their own file

* Extract handler setup function

* Use lodash instead of typeof
  • Loading branch information
RobbieTheWagner authored Aug 25, 2018
1 parent 1e5a06c commit c9faa02
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 116 deletions.
1 change: 1 addition & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ checks:
exclude_patterns:
- "dist/"
- "docs/"
- "src/eager/"
- "**/node_modules/"
- "**/test/"
- "**/tests/"
Expand Down
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
},
rules: {
'complexity': ['warn', 6],
'max-lines': ['warn', { max: 250, skipBlankLines: true, skipComments: true }],
'no-console': 'off'
},
overrides: [
Expand Down
87 changes: 87 additions & 0 deletions src/js/bind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { parseShorthand } from './utils';
import _ from 'lodash';

/**
* Sets up the handler to determine if we should advance the tour
* @private
*/
function _setupAdvanceOnHandler(selector) {
return (e) => {
if (this.isOpen()) {
const targetIsEl = this.el && e.target === this.el;
const targetIsSelector = !_.isUndefined(selector) && e.target.matches(selector);
if (targetIsSelector || targetIsEl) {
this.tour.next();
}
}
};
}

/**
* Bind the event handler for advanceOn
*/
export function bindAdvance() {
// An empty selector matches the step element
const { event, selector } = parseShorthand(this.options.advanceOn, ['selector', 'event']);
const handler = _setupAdvanceOnHandler.call(this, selector);

// TODO: this should also bind/unbind on show/hide
if (!_.isUndefined(selector)) {
const el = document.querySelector(selector);
el.addEventListener(event, handler);
} else {
document.body.addEventListener(event, handler);
}
this.on('destroy', () => {
return document.body.removeEventListener(event, handler);
});
}

/**
* 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
*/
export function bindButtonEvents(cfg, el) {
cfg.events = cfg.events || {};
if (!_.isUndefined(cfg.action)) {
// Including both a click event and an action is not supported
cfg.events.click = cfg.action;
}

_.forOwn(cfg.events, (handler, event) => {
if (_.isString(handler)) {
const page = handler;
handler = () => this.tour.show(page);
}
el.dataset.buttonEvent = true;
el.addEventListener(event, handler);

// Cleanup event listeners on destroy
this.on('destroy', () => {
el.removeAttribute('data-button-event');
el.removeEventListener(event, handler);
});
});
}

/**
* Add a click listener to the cancel link that cancels the tour
* @param {HTMLElement} link The cancel link element
*/
export function bindCancelLink(link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.cancel();
});
}

/**
* Take an array of strings and look up methods by name, then bind them to `this`
* @param {[String]} methods The names of methods to bind
*/
export function bindMethods(methods) {
methods.map((method) => {
this[method] = this[method].bind(this);
});
}
135 changes: 34 additions & 101 deletions src/js/step.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import _ from 'lodash';
import Popper from 'popper.js';
import { Evented } from './evented';
import 'element-matches';
import {
createFromHTML,
parsePosition,
parseShorthand
} from './utils';
import { bindAdvance, bindButtonEvents, bindCancelLink, bindMethods } from './bind';
import { createFromHTML, parsePosition } from './utils';

/**
* Creates incremented ID for each newly created step
Expand All @@ -25,8 +22,21 @@ export class Step extends Evented {
constructor(tour, options) {
super(tour, options);
this.tour = tour;
this.bindMethods();
bindMethods.call(this, [
'_show',
'cancel',
'complete',
'destroy',
'hide',
'isOpen',
'render',
'scrollTo',
'show'
]);
this.setOptions(options);
this.bindAdvance = bindAdvance.bind(this);
this.bindButtonEvents = bindButtonEvents.bind(this);
this.bindCancelLink = bindCancelLink.bind(this);
return this;
}

Expand Down Expand Up @@ -77,14 +87,14 @@ export class Step extends Evented {
const text = createFromHTML('<div class="shepherd-text"></div>');
let paragraphs = this.options.text;

if (typeof paragraphs === 'function') {
if (_.isFunction(paragraphs)) {
paragraphs = paragraphs.call(this, text);
}

if (paragraphs instanceof HTMLElement) {
text.appendChild(paragraphs);
} else {
if (typeof paragraphs === 'string') {
if (_.isString(paragraphs)) {
paragraphs = [paragraphs];
}

Expand All @@ -109,7 +119,7 @@ export class Step extends Evented {
if (renderLocation instanceof HTMLElement) {
return renderLocation.appendChild(element);
}
if (typeof renderLocation === 'string') {
if (_.isString(renderLocation)) {
return document.querySelector(renderLocation).appendChild(element);
}
}
Expand Down Expand Up @@ -179,37 +189,6 @@ export class Step extends Evented {
return buttons;
}

bindMethods() {
const methods = [
'_show',
'show',
'hide',
'isOpen',
'cancel',
'complete',
'scrollTo',
'destroy',
'render'
];
methods.map((method) => {
this[method] = this[method].bind(this);
});
}

setOptions(options = {}) {
this.options = options;
const { when } = this.options;

this.destroy();
this.id = this.options.id || this.id || `step-${uniqueId()}`;

_.forOwn(when, (handler, event) => {
this.on(event, handler, this);
});

this._setUpButtons();
}

/**
* Returns the tour for the step
* @returns {Tour}
Expand All @@ -218,36 +197,11 @@ export class Step extends Evented {
return this.tour;
}

bindAdvance() {
// An empty selector matches the step element
const { event, selector } = parseShorthand(this.options.advanceOn, ['selector', 'event']);
const handler = (e) => {
if (this.isOpen()) {
const targetIsEl = this.el && e.target === this.el;
const targetIsSelector = !_.isUndefined(selector) && e.target.matches(selector);
if (targetIsSelector || targetIsEl) {
this.tour.next();
}
}
};

// TODO: this should also bind/unbind on show/hide
if (!_.isUndefined(selector)) {
const el = document.querySelector(selector);
el.addEventListener(event, handler);
} else {
document.body.addEventListener(event, handler);
}
this.on('destroy', () => {
return document.body.removeEventListener(event, handler);
});
}

getAttachTo() {
const opts = parsePosition(this.options.attachTo) || {};
const returnOpts = Object.assign({}, opts);

if (typeof opts.element === 'string') {
if (_.isString(opts.element)) {
// Can't override the element in user opts reference because we can't
// guarantee that the element will exist in the future.
try {
Expand All @@ -263,6 +217,20 @@ export class Step extends Evented {
return returnOpts;
}

setOptions(options = {}) {
this.options = options;
const { when } = this.options;

this.destroy();
this.id = this.options.id || this.id || `step-${uniqueId()}`;

_.forOwn(when, (handler, event) => {
this.on(event, handler, this);
});

this._setUpButtons();
}

setupPopper() {
if (_.isUndefined(Popper)) {
throw new Error('Using the attachment feature of Shepherd requires the Popper.js library');
Expand Down Expand Up @@ -432,39 +400,4 @@ export class Step extends Evented {

this.setupPopper();
}

bindCancelLink(link) {
link.addEventListener('click', (e) => {
e.preventDefault();
this.cancel();
});
}

/**
* 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
*/
bindButtonEvents(cfg, el) {
cfg.events = cfg.events || {};
if (!_.isUndefined(cfg.action)) {
// Including both a click event and an action is not supported
cfg.events.click = cfg.action;
}

_.forOwn(cfg.events, (handler, event) => {
if (_.isString(handler)) {
const page = handler;
handler = () => this.tour.show(page);
}
el.dataset.buttonEvent = true;
el.addEventListener(event, handler);

// Cleanup event listeners on destroy
this.on('destroy', () => {
el.removeAttribute('data-button-event');
el.removeEventListener(event, handler);
});
});
}
}
24 changes: 9 additions & 15 deletions src/js/tour.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import _ from 'lodash';
import { Evented } from './evented';
import { Step } from './step';
import { bindMethods } from './bind';

const Shepherd = new Evented();

export class Tour extends Evented {
constructor(options = {}) {
super(options);
this.bindMethods();
bindMethods.call(this, [
'back',
'cancel',
'complete',
'next'
]);
this.options = options;
this.steps = this.options.steps || [];

// Pass these events onto the global Shepherd object
const events = ['complete', 'cancel', 'start', 'show', 'active', 'inactive'];
const events = ['active', 'cancel', 'complete', 'inactive', 'show', 'start'];
events.map((event) => {
((e) => {
this.on(e, (opts) => {
Expand All @@ -26,18 +32,6 @@ export class Tour extends Evented {
return this;
}

bindMethods() {
const methods = [
'next',
'back',
'cancel',
'complete'
];
methods.map((method) => {
this[method] = this[method].bind(this);
});
}

/**
* Adds a new step to the tour
* @param {Object|Number|Step|String} arg1
Expand Down Expand Up @@ -193,7 +187,7 @@ export class Tour extends Evented {

Shepherd.activeTour = this;

const next = typeof key === 'string' ? this.getById(key) : this.steps[key];
const next = _.isString(key) ? this.getById(key) : this.steps[key];

if (!next) {
return;
Expand Down

0 comments on commit c9faa02

Please sign in to comment.