File size: 4,103 Bytes
22c7264
 
3dfa288
b4b2753
22c7264
 
 
 
8755ee2
0c119f7
8755ee2
0c119f7
 
 
 
 
 
 
 
31d8a1a
bb8be8c
 
 
5aa283f
 
 
bb8be8c
 
d5e94aa
5aa283f
bb8be8c
 
 
 
 
8755ee2
 
 
 
bb8be8c
 
 
 
d5e94aa
5aa283f
22fca24
 
ede8cb9
 
 
 
 
5aa283f
 
 
22fca24
 
 
5aa283f
 
 
ede8cb9
 
 
22fca24
ede8cb9
22fca24
5aa283f
 
 
 
 
 
 
22fca24
bb8be8c
 
5aa283f
 
 
 
 
8755ee2
 
 
 
 
bb8be8c
 
 
 
8755ee2
 
 
 
 
bb8be8c
 
 
b4b2753
 
8755ee2
 
 
 
d5e94aa
 
 
 
 
 
5aa283f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8755ee2
 
 
 
22c7264
5aa283f
 
 
 
 
b4b2753
5aa283f
22c7264
5aa283f
22c7264
b4b2753
5aa283f
 
37f5ac4
5aa283f
 
b4b2753
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import Overlay from './overlay';
import Element from './element';
import './polyfill';

/**
 * Plugin class that drives the plugin
 */
export default class Sholo {
  /**
   * @param options
   */
  constructor(options = {}) {
    this.options = Object.assign({
      padding: 10,
      animate: true,
      opacity: 0.75,
    }, options);

    this.overlay = new Overlay(options);

    this.document = document;
    this.window = window;

    this.steps = [];            // steps to be presented if any
    this.currentStep = 0;       // index for the currently highlighted step

    this.onScroll = this.onScroll.bind(this);
    this.onResize = this.onResize.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);
    this.onClick = this.onClick.bind(this);

    // Event bindings
    this.bind();
  }

  /**
   * Binds any DOM events listeners
   * @todo: add throttling in all the listeners
   */
  bind() {
    this.document.addEventListener('scroll', this.onScroll, false);
    this.document.addEventListener('DOMMouseScroll', this.onScroll, false);
    this.window.addEventListener('resize', this.onResize, false);
    this.window.addEventListener('keyup', this.onKeyUp, false);
    this.window.addEventListener('click', this.onClick, false);
  }

  /**
   * Removes the popover if clicked outside the highlighted element
   * or outside the
   * @param e
   */
  onClick(e) {
    if (!this.hasHighlightedElement()) {
      // Has no highlighted element so ignore the click
      return;
    }

    const highlightedElement = this.overlay.getHighlightedElement();
    const popover = document.getElementById('sholo-popover-item');

    const clickedHighlightedElement = highlightedElement.node.contains(e.target);
    const clickedPopover = popover && popover.contains(e.target);

    // Remove the overlay If clicked outside the highlighted element
    if (!clickedHighlightedElement && !clickedPopover) {
      this.overlay.clear();
      return;
    }

    const nextClicked = e.target.classList.contains('sholo-next-btn');
    if (nextClicked) {
      this.currentStep += 1;
      this.overlay.highlight(this.steps[this.currentStep]);
    }
  }

  hasHighlightedElement() {
    const highlightedElement = this.overlay.getHighlightedElement();
    return highlightedElement && highlightedElement.node;
  }

  /**
   * Handler for the onScroll event on document
   * Refreshes without animation on scroll to make sure
   * that the highlighted part travels with the scroll
   */
  onScroll() {
    this.overlay.refresh(false);
  }

  /**
   * 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
   */
  onResize() {
    // Refresh with animation
    this.overlay.refresh(true);
  }

  /**
   * Clears the overlay on escape key process
   * @param event
   */
  onKeyUp(event) {
    if (event.keyCode === 27) {
      this.overlay.clear();
    }
  }

  defineSteps(steps) {
    this.steps = [];

    steps.forEach((step, index) => {
      if (!step.element) {
        throw new Error(`Element (query selector or a dom element) missing in step ${index}`);
      }

      const domElement = Sholo.findDomElement(step.element);
      const element = new Element(domElement, Object.assign({}, this.options, step));

      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]);
  }

  /**
   * Highlights the given selector
   * @param selector
   */
  highlight(selector) {
    const domElement = Sholo.findDomElement(selector);

    const element = new Element(domElement, this.options);
    this.overlay.highlight(element);
  }

  static findDomElement(selector) {
    if (typeof selector === 'string') {
      return document.querySelector(selector);
    }

    if (typeof selector === 'object') {
      return selector;
    }

    throw new Error('Element can only be string or the dom element');
  }
}