Jump To …
backend.csv.js backend.dataproxy.js backend.elasticsearch.js backend.gdocs.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
');self.el.find('tbody').append(tr);varnewView=newmy.GridRow({model:doc,el:tr,fields:self.fields});newView.render();}); \ {{/cells}} \ ',events:{'click .data-table-cell-edit':'onEditClick','click .data-table-cell-editor .okButton':'onEditorOK','click .data-table-cell-editor .cancelButton':'onEditorCancel'},toTemplateJSON:function(){varself=this;vardoc=this.model;varcellData=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);varhtml=Mustache.render(this.template,this.toTemplateJSON());$(this.el).html(html);returnthis;},

view.grid.js

/*jshint multistr:true */

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

(function($, my) {

(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;
    this.el = $(this.el);
    _.bindAll(this, 'render', 'onHorizontalScroll');
    this.model.records.bind('add', this.render);
    this.model.records.bind('reset', this.render);
    this.model.records.bind('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: ' \
    
\ \ \ \ {{#fields}} \ \ {{/fields}} \ \ \ \ \
\ {{label}} \
\
\
', 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 = _.map(this.fields, 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 = 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);
    _.each(this.fields, 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 = $('

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 = $("
"
).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.el = $(this.el);
    this.model.bind('change', this.render);
  },

  template: ' \
      {{#cells}} \
      
\
\   \
{{{value}}}
\
\

=================== Cell Editor methods

  cellEditorTemplate: ' \
     \
  ',

  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);