popover.js 7.3 KB
Newer Older
K
Kamran Ahmed 已提交
1
import Element from './element';
K
Kamran Ahmed 已提交
2
import {
K
Kamran Ahmed 已提交
3 4 5 6
  CLASS_BTN_DISABLED,
  CLASS_CLOSE_BTN,
  CLASS_NEXT_STEP_BTN,
  CLASS_POPOVER_DESCRIPTION, CLASS_POPOVER_FOOTER,
K
Kamran Ahmed 已提交
7
  CLASS_POPOVER_TIP,
K
Kamran Ahmed 已提交
8
  CLASS_POPOVER_TITLE, CLASS_PREV_STEP_BTN,
K
Kamran Ahmed 已提交
9 10
  ID_POPOVER,
  POPOVER_HTML,
11
} from '../common/constants';
K
Kamran Ahmed 已提交
12

13 14 15 16
/**
 * Popover that is displayed on top of the highlighted element
 */
export default class Popover extends Element {
K
Kamran Ahmed 已提交
17 18 19 20 21
  /**
   * @param {Object} options
   * @param {Window} window
   * @param {Document} document
   */
K
Kamran Ahmed 已提交
22
  constructor(options, window, document) {
23 24
    super();

K
Kamran Ahmed 已提交
25 26 27 28 29 30 31 32 33 34 35
    this.options = Object.assign({
      isFirst: true,
      isLast: true,
      totalCount: 1,
      currentIndex: 0,
      doneBtnText: 'Done',
      closeBtnText: 'Close',
      nextBtnText: 'Next →',
      prevBtnText: '← Previous',
    }, options);

36 37 38
    this.window = window;
    this.document = document;

39
    this.makeNode();
40
    this.hide();
41 42
  }

K
Kamran Ahmed 已提交
43 44 45
  /**
   * Prepares the dom element for popover
   */
K
Kamran Ahmed 已提交
46
  makeNode() {
47
    let popover = this.document.getElementById(ID_POPOVER);
48 49 50
    if (!popover) {
      popover = Popover.createFromString(POPOVER_HTML);
      document.body.appendChild(popover);
51 52
    }

53 54 55 56
    this.node = popover;
    this.tipNode = popover.querySelector(`.${CLASS_POPOVER_TIP}`);
    this.titleNode = popover.querySelector(`.${CLASS_POPOVER_TITLE}`);
    this.descriptionNode = popover.querySelector(`.${CLASS_POPOVER_DESCRIPTION}`);
K
Kamran Ahmed 已提交
57 58 59 60
    this.footerNode = popover.querySelector(`.${CLASS_POPOVER_FOOTER}`);
    this.nextBtnNode = popover.querySelector(`.${CLASS_NEXT_STEP_BTN}`);
    this.prevBtnNode = popover.querySelector(`.${CLASS_PREV_STEP_BTN}`);
    this.closeBtnNode = popover.querySelector(`.${CLASS_CLOSE_BTN}`);
61 62
  }

63 64 65 66 67 68 69 70 71 72 73 74 75
  /**
   * Turn a string into a node
   * @param  {String} htmlString to convert
   * @return {Node}   Converted node element
   */
  static createFromString(htmlString) {
    const div = document.createElement('div');
    div.innerHTML = htmlString.trim();

    // Change this to div.childNodes to support multiple top-level nodes
    return div.firstChild;
  }

K
Kamran Ahmed 已提交
76 77 78 79
  /**
   * Gets the size for popover
   * @returns {{height: number, width: number}}
   */
80 81 82 83 84
  getSize() {
    return {
      height: Math.max(this.node.scrollHeight, this.node.offsetHeight),
      width: Math.max(this.node.scrollWidth, this.node.offsetWidth),
    };
85 86 87 88 89 90 91 92
  }

  hide() {
    this.node.style.display = 'none';
  }

  reset() {
    this.node.style.display = 'block';
93 94
    this.node.style.left = '0';
    this.node.style.top = '0';
95 96 97 98 99
    this.node.style.bottom = '';
    this.node.style.right = '';

    // Remove the positional classes from tip
    this.node
K
Kamran Ahmed 已提交
100 101
      .querySelector(`.${CLASS_POPOVER_TIP}`)
      .className = CLASS_POPOVER_TIP;
102 103
  }

K
Kamran Ahmed 已提交
104 105
  /**
   * Shows the popover at the given position
K
Kamran Ahmed 已提交
106
   * @param {Position} position
K
Kamran Ahmed 已提交
107
   */
108 109 110
  show(position) {
    this.reset();

111
    // Set the title and descriptions
K
Kamran Ahmed 已提交
112 113
    this.titleNode.innerHTML = this.options.title;
    this.descriptionNode.innerHTML = this.options.description;
114

K
Kamran Ahmed 已提交
115 116
    this.renderButtons();

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    // Position the popover around the given position
    switch (this.options.position) {
      case 'left':
        this.positionOnLeft(position);
        break;
      case 'right':
        this.positionOnRight(position);
        break;
      case 'top':
        this.positionOnTop(position);
        break;
      case 'bottom':
        this.positionOnBottom(position);
        break;
      case 'auto':
      default:
        this.autoPosition(position);
        break;
    }
K
Kamran Ahmed 已提交
136 137 138 139 140 141 142 143 144 145 146 147
  }

  /**
   * Enables, disables buttons, sets the text and
   * decides if to show them or not
   */
  renderButtons() {
    this.nextBtnNode.innerHTML = this.options.nextBtnText;
    this.prevBtnNode.innerHTML = this.options.prevBtnText;
    this.closeBtnNode.innerHTML = this.options.closeBtnText;

    // If there was only one item, hide the buttons
148
    if (!this.options.totalCount || this.options.totalCount === 1) {
K
Kamran Ahmed 已提交
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
      this.footerNode.style.display = 'none';
      return;
    }

    this.footerNode.style.display = 'block';
    if (this.options.isFirst) {
      this.prevBtnNode.classList.add(CLASS_BTN_DISABLED);
    } else {
      this.prevBtnNode.classList.remove(CLASS_BTN_DISABLED);
    }

    if (this.options.isLast) {
      this.nextBtnNode.innerHTML = this.options.doneBtnText;
    } else {
      this.nextBtnNode.innerHTML = this.options.nextBtnText;
    }
165 166 167 168
  }

  /**
   * Shows the popover on the left of the given position
K
Kamran Ahmed 已提交
169
   * @param {Position} elementPosition
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
   */
  positionOnLeft(elementPosition) {
    const popoverWidth = this.getSize().width;
    const popoverMargin = this.options.padding + 10;  // adding 10 to give it a little distance from the element

    this.node.style.left = `${elementPosition.left - popoverWidth - popoverMargin}px`;
    this.node.style.top = `${elementPosition.top - this.options.padding}px`;
    this.node.style.right = '';
    this.node.style.bottom = '';

    this.tipNode.classList.add('right');
  }

  /**
   * Shows the popover on the right of the given position
K
Kamran Ahmed 已提交
185
   * @param {Position} elementPosition
186 187 188 189 190 191 192 193 194 195 196 197 198 199
   */
  positionOnRight(elementPosition) {
    const popoverMargin = this.options.padding + 10;  // adding 10 to give it a little distance from the element

    this.node.style.left = `${elementPosition.right + popoverMargin}px`;
    this.node.style.top = `${elementPosition.top - this.options.padding}px`;
    this.node.style.right = '';
    this.node.style.bottom = '';

    this.tipNode.classList.add('left');
  }

  /**
   * Shows the popover on the top of the given position
K
Kamran Ahmed 已提交
200
   * @param {Position} elementPosition
201 202 203 204 205 206 207 208 209 210 211 212
   */
  positionOnTop(elementPosition) {
    const popoverHeight = this.getSize().height;
    const popoverMargin = this.options.padding + 10;  // adding 10 to give it a little distance from the element

    this.node.style.top = `${elementPosition.top - popoverHeight - popoverMargin}px`;
    this.node.style.left = `${elementPosition.left - this.options.padding}px`;
    this.node.style.right = '';
    this.node.style.bottom = '';

    this.tipNode.classList.add('bottom');
  }
K
Kamran Ahmed 已提交
213

214 215
  /**
   * Shows the popover on the bottom of the given position
K
Kamran Ahmed 已提交
216
   * @param {Position} elementPosition
217 218 219 220 221 222 223 224
   */
  positionOnBottom(elementPosition) {
    const popoverMargin = this.options.padding + 10;  // adding 10 to give it a little distance from the element

    this.node.style.top = `${elementPosition.bottom + popoverMargin}px`;
    this.node.style.left = `${elementPosition.left - this.options.padding}px`;
    this.node.style.right = '';
    this.node.style.bottom = '';
K
Kamran Ahmed 已提交
225

226 227 228 229 230 231
    this.tipNode.classList.add('top');
  }

  /**
   * Automatically positions the popover around the given position
   * such that the element and popover remain in view
K
Kamran Ahmed 已提交
232
   * @todo add the left and right positioning decisions
K
Kamran Ahmed 已提交
233
   * @param {Position} elementPosition
234 235 236 237
   */
  autoPosition(elementPosition) {
    const pageSize = this.getFullPageSize();
    const popoverSize = this.getSize();
238

239 240 241
    const pageHeight = pageSize.height;
    const popoverHeight = popoverSize.height;
    const popoverMargin = this.options.padding + 10;  // adding 10 to give it a little distance from the element
242

243
    const pageHeightAfterPopOver = elementPosition.bottom + popoverHeight + popoverMargin;
244 245 246

    // If adding popover would go out of the window height, then show it to the top
    if (pageHeightAfterPopOver >= pageHeight) {
247
      this.positionOnTop(elementPosition);
248
    } else {
249
      this.positionOnBottom(elementPosition);
250 251 252
    }
  }
}