SumOfUs/Champaign

View on GitHub
app/javascript/legacy/campaigner_facing/page_edit_bar.js

Summary

Maintainability
A
2 hrs
Test Coverage
import Backbone from 'backbone';
import _ from 'lodash';
import ee from '../../shared/pub_sub';
import ErrorDisplay from '../../shared/show_errors';

let PageModel = Backbone.Model.extend({
  urlRoot: '/api/pages',

  initialize: function() {
    this.lastSaved = null;
  },

  // override save to only actually save if it's new data
  save: function(data, callbacks) {
    if (_.isEqual(data, this.lastSaved)) {
      if (typeof callbacks.unchanged === 'function') {
        callbacks.unchanged();
      }
    } else {
      this.lastSaved = data;
      Backbone.Model.prototype.save.call(
        this,
        data,
        _.extend({ patch: true }, callbacks)
      );
    }
  },

  setLastSaved: function(data) {
    this.lastSaved = data;
  },
});

let PageEditBar = Backbone.View.extend({
  el: '.page-edit-bar',

  events: {
    'click .page-edit-bar__save-button': 'save',
    'click .page-edit-bar__error-message': 'findError',
    'change .page-edit-bar__toggle-autosave .onoffswitch__checkbox':
      'toggleAutosave',
  },

  initialize: function() {
    this.outstandingSaveRequest = false;
    this.addStepsToSidebar();
    this.model = new PageModel();
    this.setupAutosave();
    this.$saveBtn = this.$('.page-edit-bar__save-button');
    $('body').scrollspy({ target: '.scrollspy', offset: 150 });
    this.policeHeights();
  },

  addStepsToSidebar: function() {
    const $existing = $('ul.page-edit-bar__step-list li');
    $('.page-edit-step').each((ii, step) => {
      this.addStepToSidebar($(step));
    });
    $existing.remove();
    this.$('ul.page-edit-bar__step-list').append($existing);
  },

  addStepToSidebar: function($step) {
    let $ul = this.$('ul.page-edit-bar__step-list');
    const title = $step
      .find('.page-edit-step__title')[0]
      .childNodes[0].nodeValue.trim();
    const id = $step.attr('id');
    const icon = $step.data('icon') || 'cubes';
    const link_href = $step.data('link-to') ? $step.data('link-to') : `#${id}`;
    const link_target = $step.data('link-to') ? '_blank' : '_self';
    const li = `<li><a href="${link_href}" target="${link_target}"><i class="fa fa-${icon}"></i>${title}</a></li>`;
    $ul.append(li);
  },

  readData: function() {
    let data = {};
    $('form.one-form').each((ii, form) => {
      let $form = $(form);
      let type = $form.data('type') || 'base';
      if (!data.hasOwnProperty(type)) {
        data[type] = {};
      }
      $.extend(data[type], this.serializeForm($form));
    });
    data.id = data.page['page[id]'];
    return data;
  },

  serializeForm: function($form) {
    let data = {};
    _.each($form.serializeArray(), function(pair) {
      // this is to handle form arrays cause their name ends in []
      if (pair.name.endsWith('[]')) {
        let name = pair.name.slice(0, -2);
        if (!data.hasOwnProperty(name)) {
          data[name] = [];
        }
        data[name].push(pair.value);
      } else {
        data[pair.name] = pair.value;
      }
    });
    return data;
  },

  save: function() {
    ee.emit('wysiwyg:submit'); // for summernote + codemirror to update content
    if (!this.outstandingSaveRequest) {
      this.disableSubmit();
      this.model.save(this.readData(), {
        success: this.saved.bind(this),
        error: this.saveFailed.bind(this),
        unchanged: this.enableSubmit.bind(this),
      });
    }
  },

  saved: function(e, data) {
    if (data.refresh) {
      location.reload();
    }

    if (data.ak_resource_warning) {
      $('#ak-warning-msg').removeClass('hidden-irrelevant');
    } else {
      $('#ak-warning-msg').addClass('hidden-irrelevant');
    }

    this.enableSubmit();
    ee.emit('page:saved', data);

    $('.page-edit-bar__save-box').removeClass(
      'page-edit-bar__save-box--has-error'
    );
    $('.page-edit-bar__error-message').text('');
    $('.page-edit-bar__last-saved').text(
      window.I18n.t('pages.edit.last_saved_at', { time: this.currentTime() })
    );
    this.policeHeights();
  },

  currentTime: function() {
    const now = new Date();
    const minutes = `0${now.getMinutes()}`.slice(-2); // for leading zero
    const seconds = `0${now.getSeconds()}`.slice(-2); // for leading zero
    return `${now.getHours()}:${minutes}:${seconds}`;
  },

  saveFailed: function(e, data) {
    console.error('Save failed with', e, data);
    this.enableSubmit();
    $('.page-edit-bar__save-box').addClass(
      'page-edit-bar__save-box--has-error'
    );
    if (data.status == 422) {
      ErrorDisplay.show(e, data);
      $('.page-edit-bar__error-message').text(
        window.I18n.t('pages.edit.user_error')
      );
      ee.emit('page:errors');
    } else {
      $('.page-edit-bar__error-message').text(
        window.I18n.t('pages.edit.unknown_error')
      );
    }
    this.policeHeights();
  },

  findError: function() {
    if (
      this.$('.page-edit-bar__save-box').hasClass(
        'page-edit-bar__save-box--has-error'
      )
    ) {
      if ($('.has-error').length > 0) {
        $('html, body').animate(
          {
            scrollTop: $('.has-error')
              .first()
              .offset().top,
          },
          500
        );
      }
    }
  },

  toggleAutosave: function(e) {
    this.autosave = !this.autosave;
    this.$('.page-edit-bar__toggle-autosave')
      .find('.toggle-button')
      .toggleClass('btn-primary');
    if (this.autosave) {
      this.$('.page-edit-bar__btn-holder').addClass(
        'page-edit-bar__btn-holder--hidden'
      );
    } else {
      this.$('.page-edit-bar__btn-holder').removeClass(
        'page-edit-bar__btn-holder--hidden'
      );
    }
    window.setTimeout(() => {
      this.policeHeights();
    }, 200);
  },

  disableSubmit: function() {
    this.outstandingSaveRequest = true;
    this.$saveBtn.text(window.I18n.t('pages.edit.saving'));
    this.$saveBtn.addClass('disabled');
  },

  enableSubmit: function() {
    this.outstandingSaveRequest = false;
    this.$saveBtn.text(window.I18n.t('pages.edit.save_work'));
    this.$saveBtn.removeClass('disabled');
  },

  setupAutosave: function() {
    const SAVE_PERIOD = 5000; // milliseconds
    const shouldAutosave =
      this.$('.page-edit-bar__toggle-autosave').data('autosave') == true;
    this.autosave = true;
    this.model.setLastSaved(this.readData());
    if (shouldAutosave != this.autosave) {
      this.toggleAutosave();
    }
    window.setInterval(() => {
      if (this.autosave) {
        this.save();
      } else {
        this.showUnsavedAlert();
      }
    }, SAVE_PERIOD);
  },

  showUnsavedAlert: function() {
    ee.emit('wysiwyg:submit'); // update wysiwyg
    let $lastSaved = $('.page-edit-bar__last-saved');
    const noNotice =
      $lastSaved.find('.page-edit-bar__unsaved-notice').length < 1;
    const unsavedDataExists = !_.isEqual(this.model.lastSaved, this.readData());
    if (unsavedDataExists) {
      if (noNotice) {
        $lastSaved.append(
          `<div class="page-edit-bar__unsaved-notice">${window.I18n.t(
            'pages.edit.unsaved_changes'
          )}</div>`
        );
      }
    } else {
      $lastSaved.find('.page-edit-bar__unsaved-notice').remove();
    }
  },

  policeHeights: function() {
    if ($(window).width() <= 600) {
      return;
    }
    var height =
      $(window).height() -
      this.$('.page-edit-bar__logo').height() -
      this.$('.page-edit-bar__save-box').height() -
      this.$('.page-edit-bar__btn-holder').outerHeight();
    this.$('.page-edit-bar__step-list').css('height', `${height}px`);
  },
});

export default PageEditBar;