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

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

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

K
Kamran Ahmed 已提交
23
    this.overlay = new Overlay(this.options, this.window, this.document);
24

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

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

    // Event bindings
    this.bind();
  }

K
Kamran Ahmed 已提交
37 38 39 40
  /**
   * Binds any DOM events listeners
   * @todo: add throttling in all the listeners
   */
K
Kamran Ahmed 已提交
41 42 43 44
  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 已提交
45
    this.window.addEventListener('keyup', this.onKeyUp, false);
K
Kamran Ahmed 已提交
46
    this.window.addEventListener('click', this.onClick, false);
47 48
  }

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

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

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

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

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

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

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

  /**
   * 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 已提交
108
      this.overlay.highlight(this.steps[this.currentStep]);
K
Kamran Ahmed 已提交
109 110
    } else {
      this.reset();
111
    }
K
Kamran Ahmed 已提交
112 113
  }

K
Kamran Ahmed 已提交
114 115 116 117 118 119 120 121 122 123 124 125
  /**
   * 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 已提交
126 127 128 129 130
  hasHighlightedElement() {
    const highlightedElement = this.overlay.getHighlightedElement();
    return highlightedElement && highlightedElement.node;
  }

K
Kamran Ahmed 已提交
131 132 133 134 135
  /**
   * 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 已提交
136 137 138 139
  onScroll() {
    this.overlay.refresh(false);
  }

K
Kamran Ahmed 已提交
140 141 142 143 144
  /**
   * 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 已提交
145 146 147
  onResize() {
    // Refresh with animation
    this.overlay.refresh(true);
148 149
  }

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

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

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

176
      // @todo pass the options such as position, button text etc
K
Kamran Ahmed 已提交
177
      const popover = new Popover(elementOptions, this.window, this.document);
178
      const element = new Element(domElement, elementOptions, popover, this.overlay, this.window, this.document);
K
Kamran Ahmed 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192

      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 已提交
193 194
  /**
   * Highlights the given selector
195 196
   * @param selector string query selector
   * @todo make it accept json or query selector
K
Kamran Ahmed 已提交
197
   */
K
Kamran Ahmed 已提交
198
  highlight(selector) {
199 200 201 202
    const domElement = this.document.querySelector(selector);
    if (!domElement) {
      console.warn(`Element to highlight ${selector} not found`);
      return;
K
Kamran Ahmed 已提交
203
    }
K
Kamran Ahmed 已提交
204

205
    // @todo add options such as position, button texts, additional classes etc
K
Kamran Ahmed 已提交
206
    const popover = new Popover(this.options, this.window, this.document);
207
    const element = new Element(domElement, this.options, popover, this.overlay, this.window, this.document);
208
    this.overlay.highlight(element);
209 210
  }
}