sholo.js 5.2 KB
Newer Older
K
Kamran Ahmed 已提交
1 2
import Overlay from './overlay';
import Element from './element';
K
Kamran Ahmed 已提交
3
import './polyfill';
4

K
Kamran Ahmed 已提交
5 6 7 8
/**
 * Plugin class that drives the plugin
 */
export default class Sholo {
K
Kamran Ahmed 已提交
9
  /**
K
Kamran Ahmed 已提交
10
   * @param options
K
Kamran Ahmed 已提交
11
   */
K
Kamran Ahmed 已提交
12 13 14 15 16 17 18
  constructor(options = {}) {
    this.options = Object.assign({
      padding: 10,
      animate: true,
      opacity: 0.75,
    }, options);

K
Kamran Ahmed 已提交
19 20 21
    this.document = document;
    this.window = window;

22 23
    this.overlay = new Overlay(options, window, document);

K
Kamran Ahmed 已提交
24 25 26
    this.steps = [];            // steps to be presented if any
    this.currentStep = 0;       // index for the currently highlighted step

K
Kamran Ahmed 已提交
27 28
    this.onScroll = this.onScroll.bind(this);
    this.onResize = this.onResize.bind(this);
K
Kamran Ahmed 已提交
29
    this.onKeyUp = this.onKeyUp.bind(this);
K
Kamran Ahmed 已提交
30
    this.onClick = this.onClick.bind(this);
K
Kamran Ahmed 已提交
31 32 33 34 35

    // Event bindings
    this.bind();
  }

K
Kamran Ahmed 已提交
36 37 38 39
  /**
   * Binds any DOM events listeners
   * @todo: add throttling in all the listeners
   */
K
Kamran Ahmed 已提交
40 41 42 43
  bind() {
    this.document.addEventListener('scroll', this.onScroll, false);
    this.document.addEventListener('DOMMouseScroll', this.onScroll, false);
    this.window.addEventListener('resize', this.onResize, false);
K
Kamran Ahmed 已提交
44
    this.window.addEventListener('keyup', this.onKeyUp, false);
K
Kamran Ahmed 已提交
45
    this.window.addEventListener('click', this.onClick, false);
46 47
  }

K
Kamran Ahmed 已提交
48 49 50 51 52
  /**
   * Removes the popover if clicked outside the highlighted element
   * or outside the
   * @param e
   */
K
Kamran Ahmed 已提交
53 54 55
  onClick(e) {
    if (!this.hasHighlightedElement()) {
      // Has no highlighted element so ignore the click
56 57 58
      return;
    }

K
Kamran Ahmed 已提交
59
    const highlightedElement = this.overlay.getHighlightedElement();
60
    const popover = this.document.getElementById('sholo-popover-item');
K
Kamran Ahmed 已提交
61

K
Kamran Ahmed 已提交
62 63 64
    const clickedHighlightedElement = highlightedElement.node.contains(e.target);
    const clickedPopover = popover && popover.contains(e.target);

65
    // Remove the overlay If clicked outside the highlighted element
K
Kamran Ahmed 已提交
66
    if (!clickedHighlightedElement && !clickedPopover) {
67
      this.overlay.clear();
K
Kamran Ahmed 已提交
68 69 70 71
      return;
    }

    const nextClicked = e.target.classList.contains('sholo-next-btn');
K
Kamran Ahmed 已提交
72 73 74
    const prevClicked = e.target.classList.contains('sholo-prev-btn');
    const closeClicked = e.target.classList.contains('sholo-close-btn');

75 76 77 78 79
    if (closeClicked) {
      this.reset();
      return;
    }

K
Kamran Ahmed 已提交
80
    if (nextClicked) {
K
Kamran Ahmed 已提交
81 82 83
      this.moveNext();
    } else if (prevClicked) {
      this.movePrevious();
84
    }
K
Kamran Ahmed 已提交
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  }

  /**
   * Moves to the previous step if possible
   * otherwise resets the overlay
   */
  movePrevious() {
    this.currentStep -= 1;
    if (this.steps[this.currentStep]) {
      this.overlay.highlight(this.steps[this.currentStep]);
    } else {
      this.reset();
    }
  }

  /**
   * Moves to the next step if possible
   * otherwise resets the overlay
   */
  moveNext() {
    this.currentStep += 1;
    if (this.steps[this.currentStep]) {
K
Kamran Ahmed 已提交
107
      this.overlay.highlight(this.steps[this.currentStep]);
K
Kamran Ahmed 已提交
108 109
    } else {
      this.reset();
110
    }
K
Kamran Ahmed 已提交
111 112
  }

K
Kamran Ahmed 已提交
113 114 115 116 117 118 119 120 121 122 123 124
  /**
   * Resets the steps if any and clears the overlay
   */
  reset() {
    this.currentStep = 0;
    this.overlay.clear();
  }

  /**
   * Checks if there is any highlighted element or not
   * @returns {boolean}
   */
K
Kamran Ahmed 已提交
125 126 127 128 129
  hasHighlightedElement() {
    const highlightedElement = this.overlay.getHighlightedElement();
    return highlightedElement && highlightedElement.node;
  }

K
Kamran Ahmed 已提交
130 131 132 133 134
  /**
   * Handler for the onScroll event on document
   * Refreshes without animation on scroll to make sure
   * that the highlighted part travels with the scroll
   */
K
Kamran Ahmed 已提交
135 136 137 138
  onScroll() {
    this.overlay.refresh(false);
  }

K
Kamran Ahmed 已提交
139 140 141 142 143
  /**
   * Handler for the onResize DOM event
   * Refreshes with animation on scroll to make sure that
   * the highlighted part travels with the width change of window
   */
K
Kamran Ahmed 已提交
144 145 146
  onResize() {
    // Refresh with animation
    this.overlay.refresh(true);
147 148
  }

K
Kamran Ahmed 已提交
149 150 151 152
  /**
   * Clears the overlay on escape key process
   * @param event
   */
K
Kamran Ahmed 已提交
153 154 155 156 157 158
  onKeyUp(event) {
    if (event.keyCode === 27) {
      this.overlay.clear();
    }
  }

K
Kamran Ahmed 已提交
159 160 161 162
  defineSteps(steps) {
    this.steps = [];

    steps.forEach((step, index) => {
163 164
      if (!step.element || typeof step.element !== 'string') {
        throw new Error(`Element (query selector string) missing in step ${index}`);
K
Kamran Ahmed 已提交
165 166
      }

167 168 169 170 171 172 173 174 175
      const elementOptions = Object.assign({}, this.options, step);

      const domElement = this.document.querySelector(step.element);
      if (!domElement) {
        console.warn(`Element to highlight ${step.element} not found`);
        return;
      }

      const element = new Element(domElement, elementOptions, this.overlay, this.window, this.document);
K
Kamran Ahmed 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188 189

      this.steps.push(element);
    });
  }

  start() {
    if (!this.steps || this.steps.length === 0) {
      throw new Error('There are no steps defined to iterate');
    }

    this.currentStep = 0;
    this.overlay.highlight(this.steps[0]);
  }

K
Kamran Ahmed 已提交
190 191
  /**
   * Highlights the given selector
192 193
   * @param selector string query selector
   * @todo make it accept json or query selector
K
Kamran Ahmed 已提交
194
   */
K
Kamran Ahmed 已提交
195
  highlight(selector) {
196 197 198 199
    const domElement = this.document.querySelector(selector);
    if (!domElement) {
      console.warn(`Element to highlight ${selector} not found`);
      return;
K
Kamran Ahmed 已提交
200
    }
K
Kamran Ahmed 已提交
201

202 203
    const element = new Element(domElement, this.options, this.overlay, this.window, this.document);
    this.overlay.highlight(element);
204 205
  }
}