# view.grid.js

- Jump To … +
  backend.dataproxy.js backend.memory.js ecma-fixes.js model.js view.flot.js view.graph.js view.grid.js view.map.js view.multiview.js view.slickgrid.js view.timeline.js widget.facetviewer.js widget.fields.js widget.filtereditor.js widget.pager.js widget.queryeditor.js widget.valuefilter.js

- # view.grid.js

- ¶

      /*jshint multistr:true */

      this.recline = this.recline || {};
      this.recline.View = this.recline.View || {};

      (function($, my) {
        "use strict";

- ¶

  ## (Data) Grid Dataset View

  Provides a tabular view on a Dataset.

  Initialize it with a `recline.Model.Dataset`.

      my.Grid = Backbone.View.extend({
        tagName:  "div",
        className: "recline-grid-container",

        initialize: function(modelEtc) {
          var self = this;
          _.bindAll(this, 'render', 'onHorizontalScroll');
          this.listenTo(this.model.records, 'add reset remove', this.render);
          this.tempState = {};
          var state = _.extend({
              hiddenFields: []
            }, modelEtc.state
          );
          this.state = new recline.Model.ObjectState(state);
        },

        events: {

- ¶

  does not work here so done at end of render function ‘scroll .recline-grid tbody’: ‘onHorizontalScroll’

        },

- ¶

  ======================================================

- ¶

  Column and row menus

        setColumnSort: function(order) {
          var sort = [{}];
          sort[0][this.tempState.currentColumn] = {order: order};
          this.model.query({sort: sort});
        },

        hideColumn: function() {
          var hiddenFields = this.state.get('hiddenFields');
          hiddenFields.push(this.tempState.currentColumn);
          this.state.set({hiddenFields: hiddenFields});

- ¶

  change event not being triggered (because it is an array?) so trigger manually

          this.state.trigger('change');
          this.render();
        },

        showColumn: function(e) {
          var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column'));
          this.state.set({hiddenFields: hiddenFields});
          this.render();
        },

        onHorizontalScroll: function(e) {
          var currentScroll = $(e.target).scrollLeft();
          this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll);
        },

- ¶

  ======================================================

- ¶

  #### Templating

        template: ' \
          <div class="table-container"> \
          <table class="recline-grid table-striped table-condensed" cellspacing="0"> \
            <thead class="fixed-header"> \
              <tr> \
                {{#fields}} \
                  <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;" title="{{label}}"> \
                    <span class="column-header-name">{{label}}</span> \
                  </th> \
                {{/fields}} \
                <th class="last-header" style="width: {{lastHeaderWidth}}px; max-width: {{lastHeaderWidth}}px; min-width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
              </tr> \
            </thead> \
            <tbody class="scroll-content"></tbody> \
          </table> \
          </div> \
        ',

        toTemplateJSON: function() {
          var self = this;
          var modelData = this.model.toJSON();
          modelData.notEmpty = ( this.fields.length > 0 );

- ¶

  TODO: move this sort of thing into a toTemplateJSON method on Dataset?

          modelData.fields = this.fields.map(function(field) {
            return field.toJSON();
          });

- ¶

  last header width = scroll bar - border (2px) */

          modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2;
          return modelData;
        },
        render: function() {
          var self = this;
          this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) {
            return _.indexOf(self.state.get('hiddenFields'), field.id) == -1;
          }));

          this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions
          var numFields = this.fields.length;

- ¶

  compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)

          var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width;
          var width = parseInt(Math.max(50, fullWidth / numFields), 10);

- ¶

  if columns extend outside viewport then remainder is 0

          var remainder = Math.max(fullWidth - numFields * width,0);
          this.fields.each(function(field, idx) {

- ¶

  add the remainder to the first field width so we make up full col

            if (idx === 0) {
              field.set({width: width+remainder});
            } else {
              field.set({width: width});
            }
          });
          var htmls = Mustache.render(this.template, this.toTemplateJSON());
          this.$el.html(htmls);
          this.model.records.forEach(function(doc) {
            var tr = $('<tr />');
            self.$el.find('tbody').append(tr);
            var newView = new my.GridRow({
                model: doc,
                el: tr,
                fields: self.fields
              });
            newView.render();
          });

- ¶

  hide extra header col if no scrollbar to avoid unsightly overhang

          var $tbody = this.$el.find('tbody')[0];
          if ($tbody.scrollHeight <= $tbody.offsetHeight) {
            this.$el.find('th.last-header').hide();
          }
          this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0));
          this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll);
          return this;
        },

- ¶

  ### _scrollbarSize

  Measure width of a vertical scrollbar and height of a horizontal scrollbar.

  @return: { width: pixelWidth, height: pixelHeight }

        _scrollbarSize: function() {
          var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
          var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
          $c.remove();
          return dim;
        }
      });

- ¶

  ## GridRow View for rendering an individual record.

  Since we want this to update in place it is up to creator to provider the element to attach to.

  In addition you must pass in a FieldList in the constructor options. This should be list of fields for the Grid.

  Example:

      var row = new GridRow({
        model: dataset-record,
          el: dom-element,
          fields: mydatasets.fields // a FieldList object
        });

      my.GridRow = Backbone.View.extend({
        initialize: function(initData) {
          _.bindAll(this, 'render');
          this._fields = initData.fields;
          this.listenTo(this.model, 'change', this.render);
        },

        template: ' \
            {{#cells}} \
            <td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;"> \
              <div class="data-table-cell-content"> \
                <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
                <div class="data-table-cell-value">{{{value}}}</div> \
              </div> \
            </td> \
            {{/cells}} \
          ',
        events: {
          'click .data-table-cell-edit': 'onEditClick',
          'click .data-table-cell-editor .okButton': 'onEditorOK',
          'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
        },

        toTemplateJSON: function() {
          var self = this;
          var doc = this.model;
          var cellData = this._fields.map(function(field) {
            return {
              field: field.id,
              width: field.get('width'),
              value: doc.getFieldValue(field)
            };
          });
          return { id: this.id, cells: cellData };
        },

        render: function() {
          this.$el.attr('data-id', this.model.id);
          var html = Mustache.render(this.template, this.toTemplateJSON());
          this.$el.html(html);
          return this;
        },

- ¶

  ===================

- ¶

  Cell Editor methods

        cellEditorTemplate: ' \
          <div class="menu-container data-table-cell-editor"> \
            <textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
            <div id="data-table-cell-editor-actions"> \
              <div class="data-table-cell-editor-action"> \
                <button class="okButton btn primary">Update</button> \
                <button class="cancelButton btn danger">Cancel</button> \
              </div> \
            </div> \
          </div> \
        ',

        onEditClick: function(e) {
          var editing = this.$el.find('.data-table-cell-editor-editor');
          if (editing.length > 0) {
            editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden");
          }
          $(e.target).addClass("hidden");
          var cell = $(e.target).siblings('.data-table-cell-value');
          cell.data("previousContents", cell.text());
          var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()});
          cell.html(templated);
        },

        onEditorOK: function(e) {
          var self = this;
          var cell = $(e.target);
          var rowId = cell.parents('tr').attr('data-id');
          var field = cell.parents('td').attr('data-field');
          var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
          var newData = {};
          newData[field] = newValue;
          this.model.set(newData);
          this.trigger('recline:flash', {message: "Updating row...", loader: true});
          this.model.save().then(function(response) {
              this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'});
            })
            .fail(function() {
              this.trigger('recline:flash', {
                message: 'Error saving row',
                category: 'error',
                persist: true
              });
            });
        },

        onEditorCancel: function(e) {
          var cell = $(e.target).parents('.data-table-cell-value');
          cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
        }
      });

      })(jQuery, recline.View);