support-foo/web

View on GitHub
app/assets/javascripts/widget.js.erb

Summary

Maintainability
Test Coverage
// Helpful Embed JS

// When somebody with an Account includes a script tag to this file on their own
// website, it'll embed a simple web embed that posts to Helpful.
//
// It's attached to the DOM with the use of the `data-helpful` attribute:
//
//    <a href="#" data-helpful="my-account-slug">Click me to show embed</a>
//
// The value of `data-helpful` should be the account slug of the Helpful account
// where the new message should be created.
//
//
// No need for jQuery! Plain old Javascript!
//
(function() {
  // HelpfulEmbed Class
  var HelpfulEmbed = function () {
  }

  HelpfulEmbed.prototype.load = function () {
    var load_js = function () {
      var js_el = document.createElement('script');
      js_el.type = 'text/javascript';
      <% if Rails.env.development? %>
        js_el.src = '<%= javascript_url('widget-content') %>'
      <% else %>
        js_el.src = '//assets.helpful.io/assets/widget-content.js'
      <% end %>
      document.body.appendChild(js_el);
    }

    var css_el = document.createElement('link');
    css_el.rel = 'stylesheet';
    css_el.type = 'text/css';
    css_el.addEventListener('load', load_js);
    <% if Rails.env.development? %>
      css_el.href = '<%= stylesheet_url('widget') %>'
    <% else %>
      css_el.href = '//assets.helpful.io/assets/widget.css';
    <% end %>
    css_el.media = 'all';
    document.head.appendChild(css_el);
  }

  HelpfulEmbed.prototype.getSourceOffsetTop = function () {
    var currentElement = this.source;
    var offset = this.source.offsetTop;

    while (currentElement.offsetParent)
    {
      offset += currentElement.offsetParent.offsetTop;
      currentElement = currentElement.offsetParent;
    }

    return offset;
  }

  HelpfulEmbed.prototype.getSourceOffsetLeft = function () {
    var currentElement = this.source;
    var offset = this.source.offsetLeft;

    while (currentElement.offsetParent)
    {
      offset += currentElement.offsetParent.offsetLeft;
      currentElement = currentElement.offsetParent;
    }

    return offset;
  }

  HelpfulEmbed.prototype.showWidget = function () {
    // remove old style
    this.widget.className = this.widget.className.replace('helpful-shown-below', '');

    // check overlay
    if (!this.options.overlay) {
      if (this.overlay.className.indexOf('transparent') == -1)
        this.overlay.className += ' transparent';
    } else
    {
      this.overlay.className = this.overlay.className.replace('transparent', '');
    }

    // get documents measurements
    var document_h = Math.max(
      document.body.scrollHeight, document.documentElement.scrollHeight,
      document.body.offsetHeight, document.documentElement.offsetHeight,
      document.body.clientHeight, document.documentElement.clientHeight
    );

    var document_w = Math.max(
      document.body.scrollWidth, document.documentElement.scrollWidth,
      document.body.offsetWidth, document.documentElement.offsetWidth,
      document.body.clientWidth, document.documentElement.clientWidth
    );

    // get screen measurements
    var screen_w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
    var screen_h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)

    if (this.options.modal) {
      document.querySelector('.helpful-pointer').style.display = 'none';

      this.widget.style.top = screen_h / 2 - 170 + 'px';
      this.widget.style.left = screen_w / 2 - 175 + 'px';
      this.widget.style.position = 'fixed';
    } else
    {
      // calculate widget location, width = 300px, height = 325px, minimum viewport margin = 25px
      var el_pos = this.source.getBoundingClientRect();

      var widget_top = 0;
      var widget_left = 0;

      // check if enough space on screen above source AND if enough space below in document UNLESS not enough above in document
      if ((el_pos.top < 355) && ((this.getSourceOffsetTop() + this.source.offsetHeight + 355) < document_h || this.getSourceOffsetTop() < 355)) {
        // show below
        widget_top = this.getSourceOffsetTop() + this.source.offsetHeight + 15;
        this.widget.className += ' helpful-shown-below';
      } else
      {
        // show above
        widget_top = this.getSourceOffsetTop() - 355;
      }

      widget_left = this.getSourceOffsetLeft() + (this.source.offsetWidth / 2) - 175; // center relative to source
      document.querySelector('.helpful-pointer').style.left = '50%';

      if (widget_left < 0) {
        widget_left = Math.min(this.getSourceOffsetLeft(), 25); // compensate when centering would cause offscreen pos

        // position arrow
        var arrow_left = this.getSourceOffsetLeft() + this.source.offsetWidth / 2 - widget_left;
        document.querySelector('.helpful-pointer').style.left = arrow_left + 'px';
      }

      if (widget_left + 350 > document_w) {
        widget_left = Math.min(this.getSourceOffsetLeft() + this.source.offsetWidth - 300, document_w - 25); // compensate when centering would cause offscreen pos

        // position arrow
        var arrow_left = this.getSourceOffsetLeft() + this.source.offsetWidth / 2 - widget_left;
        document.querySelector('.helpful-pointer').style.left = arrow_left + 'px';
      }

      this.widget.style.top = widget_top + 'px';
      this.widget.style.left = widget_left + 'px';
    }

    // fill out predefined values
    document.querySelector('#helpful-name').value = this.options.name;
    document.querySelector('#helpful-email').value = this.options.email;

    this.overlay.style.display = 'block';
    this.container.style.display = 'block';

    // focus question field
    document.querySelector('#helpful-question').focus();
  }

  HelpfulEmbed.prototype.checkTextarea = function () {
    var textarea = document.querySelector('.helpful-embed textarea');

    if (textarea.value.length > 0) {
      if (this.widget.className.indexOf('helpful-textarea-filled') == -1)
        this.widget.className += ' helpful-textarea-filled';
    } else
    {
      this.widget.className = this.widget.className.replace('helpful-textarea-filled', '');
    }
  }

  HelpfulEmbed.prototype.setupEvents = function (skipOverlay) {
    var that = this;

    // event listener for closing on overlay click
    if (!skipOverlay) {
      document.querySelector('.helpful-overlay').addEventListener('click', function (e) {
        e.stopPropagation();
        helpful_embed.close();
      });
    }

    // event listener to change styles for textarea input
    document.querySelector('.helpful-embed textarea').addEventListener('keyup', function () {
      that.checkTextarea();
    });

    // event listener to continue to next screen
    document.querySelector('.helpful-question-container button').addEventListener('click', function () {
      document.querySelector('.helpful-question-container').style.display = 'none';
      document.querySelector('.helpful-details-container').style.display = 'block';
      that.widget.className = that.widget.className.replace('helpful-textarea-filled', '');
    });

    // event listener to go back to first screen
    document.querySelector('.helpful-back-button').addEventListener('click', function () {
      document.querySelector('.helpful-details-container').style.display = 'none';
      document.querySelector('.helpful-question-container').style.display = 'block';
      that.checkTextarea();
    });

    // event listener to close the screen
    [].forEach.call(document.querySelectorAll('.helpful-close-button'), function (el) {
      el.addEventListener('click', function () {
        helpful_embed.close();
      });
    });

    // event listener to ask another question
    document.querySelector('.helpful-btn-return').addEventListener('click', function () {
      document.querySelector('.helpful-embed textarea').value = '';
      document.querySelector('.helpful-thanks-container').style.display = 'none';
      document.querySelector('.helpful-question-container').style.display = 'block';
      that.checkTextarea();
    });

    // event listener for submiting data
    document.querySelector('.helpful-embed input[type=submit]').addEventListener('click', function (e) {
      var params = 'content='+encodeURIComponent(document.querySelector('#helpful-question').value);
          params += '&email='+encodeURIComponent(document.querySelector('#helpful-name').value + ' <' + document.querySelector('#helpful-email').value + '>')
          params += '&account='+encodeURIComponent(that.options.company)
          params += '&callback=helpful_embed.gotResponse';

      var js_el = document.createElement('script');
      js_el.type = 'text/javascript';
      <% if Rails.env.development? %>
        js_el.src = '<%= Rails.application.routes.url_helpers.incoming_message_path %>?' + params;
      <% else %>
        js_el.src = '//helpful.io/incoming_message?' + params;
      <% end %>

      document.body.appendChild(js_el);
    });
  }

  HelpfulEmbed.prototype.gotResponse = function (data) {
    document.querySelector('.helpful-details-container').style.display = 'none';
    document.querySelector('.helpful-thanks-container').style.display = 'block';
  }

  HelpfulEmbed.prototype.createContainer = function () {
    this.container = document.createElement('div');
    this.container.style.display = 'none';
    this.container.className = 'helpful-container';

    this.overlay = document.createElement('div');
    this.overlay.style.display = 'none';
    this.overlay.className = 'helpful-overlay';

    document.body.appendChild(this.overlay);
    document.body.appendChild(this.container);
  }

  // Opens the embed on top of an element
  HelpfulEmbed.prototype.open = function (source) {
    var reload = false;

    if (this.source != source) {
      // different source of widget, mark for reload
      reload = true;
    }

    // set source of the click
    this.source = source;

    // load options from data-* attributes
    this.options = {
      company: source.getAttribute('data-helpful'),
      overlay: source.getAttribute('data-helpful-overlay') != 'off',
      modal: source.getAttribute('data-helpful-modal') == 'on',
      name: source.getAttribute('data-helpful-name') || '',
      email: source.getAttribute('data-helpful-email') || '',
      strings: source.getAttribute('data-helpful-strings')
    };

    if (this.loaded && this.widget && this.widget.parentElement.parentElement.parentElement) {
      if (reload) {
        // new source, widget needs to be rebuild
        this.setHTML();

        // attach event handlers to new html, except container
        this.setupEvents(true);
      }

      return this.showWidget();
    }

    // create container element
    this.createContainer();

    // load css & js
    this.load();
  }

  HelpfulEmbed.prototype.setHTML = function () {
    var output = this.HTMLcache;

    // replace strings
    for (var key in this.stringDefaults) {
      output = output.replace('[' + key + ']', this.getString(key));
    }

    // put loaded html into container
    this.container.innerHTML = output;

    // set widget element
    this.widget = document.querySelector('.helpful-embed');
  }

  HelpfulEmbed.prototype.htmlLoaded = function (data) {
    this.HTMLcache = data.html;

    // inject html
    this.setHTML();

    // show widget
    this.showWidget();

    // setup event handlers
    this.setupEvents();

    this.loaded = true;
  }

  // Closes the embed popup at the target
  HelpfulEmbed.prototype.close = function () {
    this.container.style.display = 'none';
    this.overlay.style.display = 'none';
  }

  HelpfulEmbed.prototype.stringDefaults = {
    title: 'How may we help you?',
    message_placeholder: 'Type your message here...',
    next: 'Next',
    contact_information: 'Your contact information.',
    contact_information_info: 'So we can respond to your question.',
    name_placeholder: 'Name',
    email_placeholder: 'Email',
    submit: 'Submit your Question',
    thanks: 'Thanks!',
    thanks_message: 'Have a wonderful day.',
    submit_another: 'Submit another question?'
  }

  HelpfulEmbed.prototype.getString = function (key) {
    if (this.source.hasAttribute('data-helpful-' + key.replace('_', '-')))
      return this.source.getAttribute('data-helpful-' + key.replace('_', '-'));

    if (typeof this.options.strings == 'string' && typeof window.HelpfulStrings != 'undefined' && typeof window.HelpfulStrings[this.options.strings] != 'undefined' && typeof window.HelpfulStrings[this.options.strings][key] != 'undefined')
      return window.HelpfulStrings[this.options.strings][key];

    return this.stringDefaults[key];
  }

  window.helpful_embed = new HelpfulEmbed();

  document.addEventListener('click', function (e) {
    if (e.target.hasAttribute('data-helpful')) {
      e.preventDefault();
      e.stopPropagation();
      helpful_embed.open(e.target);
    }
  });

})();