crowbar/crowbar-core

View on GitHub
crowbar_framework/vendor/assets/javascripts/jquery/dynamicTable.js

Summary

Maintainability
F
4 days
Test Coverage
;(function($, doc, win) {
  'use strict';

  function DynamicTable(el, options) {
    this.root = $(el);
    this.json = {};

    this.options = $.extend(
      {
        disabledSubmits: 'input[source=commit1], input[source=save1]',
        storage: '#proposal_attributes',
        namespace: this.root.data('namespace'),
        entries: this.root.data('dynamic'),
        invalid: this.root.data('invalid'),
        duplicate: this.root.data('duplicate'),
        optional: this.root.data('optional'),
        key: 'name'
      },
      options
    );

    this.clone = (function() {
      return function(obj) {
        Clone.prototype = obj;
        return new Clone();
      };

      function Clone() {}
    }());

    this.initialize();
  }

  DynamicTable.prototype.initialize = function() {
    this.prepareTemplate();
    this.prepareJson();
    this.registerEvents();
    this.renderEntries();
  };

  DynamicTable.prototype.prepareTemplate = function() {
    this.entriesTemplate = Handlebars.compile(
      $(this.options.entries).html()
    );
  };

  DynamicTable.prototype.prepareJson = function() {
    this.json = JSON.parse(
      $(this.options.storage).val()
    );
  };

  DynamicTable.prototype.writeJson = function() {
    $(this.options.storage).val(
      JSON.stringify(this.json)
    );
  };

  DynamicTable.prototype.duplicateEntry = function() {
    if (this.options.key == false) {
      return false;
    }

    var input = this.root.find(
      'tfoot [data-name={0}]'.format(
        this.options.key
      )
    );

    var data = this.json;
    var namespace = this.options.namespace.split('/');

    $(document).trigger({
      type: 'dynamicTableBeforeDuplicate',
      input: input,
      json: data,
      namespaced: namespace
    });

    while (namespace.length > 0) {
      var current_namespace = namespace.shift();

      if (!data[current_namespace]) {
        data[current_namespace] = {};
      }

      data = data[current_namespace];
    }

    if (data[$(input).val()]) {
      $(document).trigger({
        type: 'dynamicTableGotDuplicate',
        input: input,
        json: data,
        namespaced: namespace
      });

      return true;
    } else {
      return false;
    }
  };

  DynamicTable.prototype.invalidEntry = function() {
    var optionals = this.options.optional.toString().split(',');
    var isInvalid = false;
    var invalidInput = false

    var inputs = this.root.find('tfoot input');

    $(document).trigger({
      type: 'dynamicTableBeforeInvalid',
      optionals: optionals,
      inputs: inputs
    });

    $.each(inputs, function(index, input) {
      if ($.inArray($(input).data('name'), optionals) < 0) {
        if ($(input).val() == '') {
          isInvalid = true;
          invalidInput = $(input).data('name');
        }
      }
    });

    if (isInvalid) {
      $(document).trigger({
        type: 'dynamicTableGotInvalid',
        optionals: optionals,
        inputs: inputs,
        invalid: invalidInput
      });

      return true;
    } else {
      return false;
    }
  };

  DynamicTable.prototype.registerEvents = function() {
    var self = this;

    self.root.find('[data-add]').live('click', function(event) {
      self.prepareJson();
      event.preventDefault();

      if (self.invalidEntry()) {
        self.root.after(
          $.dangerMessage(
            self.options.invalid,
            true,
            true
          )
        );

        return false;
      }

      if (self.duplicateEntry()) {
        self.root.after(
          $.dangerMessage(
            self.options.duplicate,
            true,
            true
          )
        );

        return false;
      }

      var inputs = self.root.find('tfoot input');
      var selects = self.root.find('tfoot select');

      var data = self.json;
      var namespace = self.options.namespace.split('/');
      var values = {};

      var currentKey = false;
      var currentValue = false;

      $.each(inputs, function(index, input) {
        var name = $(input).data('name');

        var value = self.parseValue(
          $(input).data('type'),
          $(input).val()
        );

        if (self.options.key == name) {
          currentKey = name;
          currentValue = value;
        } else {
          values[name] = value;
        }
      });

      $.each(selects, function(index, select) {
        var name = $(select).data('name');

        var value;

        if ($(select).data('type') == 'array-string') {
          // for multi-selects
          value = $(select).val();
        } else {
          value = self.parseValue(
            $(select).data('type'),
            $(select).val()
          );
        }

        values[name] = value;
      });

      while (namespace.length > 1) {
        var current_namespace = namespace.shift();

        if (!data[current_namespace]) {
          data[current_namespace] = {};
        }

        data = data[current_namespace];
      }

      if (currentKey) {
        data[namespace.shift()][currentValue] = values;
      } else {
        data[namespace.shift()] = values;
      }

      self.writeJson();

      $(document).trigger({
        type: 'dynamicTableAddedEntry',
        json: data,
        namespaced: namespace,
        values: values,
        inputs: inputs
      });

      self.renderEntries();

      inputs.val('');
    });

    self.root.find('tfoot input').live('keydown', function(event) {
      if (event.keyCode == 13) {
        event.preventDefault();
        self.root.find('tfoot [data-add]').trigger('click');
      }
    });

    self.root.find('[data-remove]').live('click', function(event) {
      self.prepareJson();
      event.preventDefault();

      var data = self.json;
      var namespace = self.options.namespace.split('/');

      while (namespace.length > 1) {
        var current_namespace = namespace.shift();

        if (!data[current_namespace]) {
          data[current_namespace] = {};
        }

        data = data[current_namespace];
      }

      delete data[namespace.shift()][$(this).data('remove')];

      self.writeJson();

      $(document).trigger({
        type: 'dynamicTableRemovedEntry',
        json: data,
        namespaced: namespace
      });

      self.renderEntries();
    });

    self.root.find('tbody input').live('change', function(event) {
      self.prepareJson();

      var data = self.json;
      var namespace = $(this).data('update').toString().split('/');

      var optionals = self.options.optional.toString().split(',');

      if ($.inArray($(this).data('name'), optionals) < 0) {
        if ($(this).val() == '') {
          $(this).parents('td').addClass('has-error');
        } else {
          $(this).parents('td').removeClass('has-error');
        }
      }

      if ($('tbody td.has-error').length > 0) {
        $(self.options.disabledSubmits).attr('disabled', 'disabled');
      } else {
        $(self.options.disabledSubmits).removeAttr('disabled');
      }

      while (namespace.length > 1) {
        var current_namespace = namespace.shift();

        if (!data[current_namespace]) {
          data[current_namespace] = {};
        }

        data = data[current_namespace];
      }

      data[namespace.shift()] = self.parseValue(
        $(this).data('type'),
        $(this).val()
      );

      self.writeJson();

      $(document).trigger({
        type: 'dynamicTableUpdatedEntry',
        json: data,
        namespaced: namespace,
        optionals: optionals
      });
    });

    self.root.find('tbody select').live('change', function(event) {
      self.prepareJson();

      var data = self.json;
      var namespace = $(this).data('update').toString().split('/');

      while (namespace.length > 1) {
        var current_namespace = namespace.shift();

        if (!data[current_namespace]) {
          data[current_namespace] = {};
        }

        data = data[current_namespace];
      }

      var value;

      if ($(this).data('type') == 'array-string') {
        // for multi-selects
        value = $(this).val();
      } else {
        value = self.parseValue(
          $(this).data('type'),
          $(this).val()
        );
      }

      data[namespace.shift()] = value;

      self.writeJson();

      $(document).trigger({
        type: 'dynamicTableUpdatedEntry',
        json: data,
        namespaced: namespace,
        optionals: optionals
      });
    });
  };

  DynamicTable.prototype.renderEntries = function() {
    var self = this;

    var data = self.json;
    var namespace = self.options.namespace.split('/');
    var entries = null;

    while (namespace.length > 1) {
      var current_namespace = namespace.shift();

      if (!data[current_namespace]) {
        data[current_namespace] = {};
      }

      data = data[current_namespace];
    }

    try {
      if (self.options.key != false) {
        entries = $.map(data[namespace.shift()], function(values, key) {
          return $.extend(
            self.clone(values),
            { name: key }
          );
        });
      } else {
        entries = data[namespace.shift()]
      }
    } catch(e) {
      entries = {};
    }

    self.root.find('tbody').html(
      this.entriesTemplate({
        entries: entries
      })
    );

    self.root.find('tbody input[type=password]').hideShowPassword();

    $(document).trigger({
      type: 'dynamicTableRenderedEntry',
      json: data,
      namespaced: namespace,
      entries: entries
    });
  };

  DynamicTable.prototype.parseValue = function(type, value) {
    switch (type) {
      case 'string':
        return value.toString();
        break;
      case 'boolean':
        return value.toLowerCase() == 'true';
        break;
      case 'integer':
        return parseInt(value);
        break;
      case 'float':
        return parseFloat(value);
        break;
      case 'array-string':
        return this.splitValue(value);
        break;
      case 'array-boolean':
        value = this.splitValue(value);

        for (i in value) {
          value[i] = value[i].toLowerCase() == 'true';
        }

        return value;
        break;
      case 'array-integer':
        value = this.splitValue(value);

        for (i in value) {
          value[i] = parseInt(value[i]);
        }

        return value;
        break;
      case 'array-float':
        value = this.splitValue(value);

        for (i in value) {
          value[i] = parseFloat(value[i]);
        }

        return value;
        break;
    }
  };

  DynamicTable.prototype.splitValue = function(value) {
    // note(jhesketh): filters out empty elements
    return $.map(
      value.replace(/ /g, ',').replace(/,+/g, ',').split(','),
      $.trim
    ).filter(Boolean);
  };

  $.fn.dynamicTable = function(options) {
    return this.each(function() {
      new DynamicTable(this, options);
    });
  };
}(jQuery, document, window));