[#119,view/slickgrid][m]: first pass implementation of slickgrid view courtesy of @amercader.

New setup supports:

* Column hiding
* Column reordering
* Column sorting
* Column resizing
* Fit columns to the div width

All these options are stored on the view state and applied when initializing the view if necessary.

Now also utilize slickgrid view as default grid view.
This commit is contained in:
Rufus Pollock
2012-05-27 23:31:42 +01:00
parent ea6f051aba
commit d339fea2d9
15 changed files with 1464 additions and 6 deletions

297
src/view-slickgrid.js Normal file
View File

@@ -0,0 +1,297 @@
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## SlickGrid Dataset View
//
// Provides a tabular view on a Dataset, based on SlickGrid.
//
// https://github.com/mleibman/SlickGrid
//
// Initialize it with a `recline.Model.Dataset`.
my.SlickGrid = Backbone.View.extend({
tagName: "div",
className: "recline-slickgrid",
initialize: function(modelEtc) {
var self = this;
this.el = $(this.el);
_.bindAll(this, 'render');
this.model.currentDocuments.bind('add', this.render);
this.model.currentDocuments.bind('reset', this.render);
this.model.currentDocuments.bind('remove', this.render);
var state = _.extend({
hiddenColumns: [],
columnsOrder: [],
columnsSort: {},
columnsWidth: [],
fitColumns: false
}, modelEtc.state
);
this.state = new recline.Model.ObjectState(state);
this.bind('view:show',function(){
// If the div is hidden, SlickGrid will calculate wrongly some
// sizes so we must render it explicitly when the view is visible
if (!self.rendered){
if (!self.grid){
self.render();
}
self.grid.init();
self.rendered = true;
}
self.visible = true;
});
this.bind('view:hide',function(){
self.visible = false;
});
},
events: {
},
render: function() {
var self = this;
this.el = $(this.el);
var options = {
enableCellNavigation: true,
enableColumnReorder: true,
explicitInitialization: true,
syncColumnCellResize: true,
forceFitColumns: this.state.get('fitColumns')
};
// We need all columns, even the hidden ones, to show on the column picker
var columns = [];
_.each(this.model.fields.toJSON(),function(field){
var column = {id:field['id'],
name:field['label'],
field:field['id'],
sortable: true,
minWidth: 80};
var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column == field.id});
if (widthInfo){
column['width'] = widthInfo.width;
}
columns.push(column);
});
// Restrict the visible columns
var visibleColumns = columns.filter(function(column) {
return _.indexOf(self.state.get('hiddenColumns'), column.id) == -1;
});
// Order them if there is ordering info on the state
if (this.state.get('columnsOrder')){
visibleColumns = visibleColumns.sort(function(a,b){
return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id);
});
columns = columns.sort(function(a,b){
return _.indexOf(self.state.get('columnsOrder'),a.id) > _.indexOf(self.state.get('columnsOrder'),b.id);
});
}
// Move hidden columns to the end, so they appear at the bottom of the
// column picker
var tempHiddenColumns = [];
for (var i = columns.length -1; i >= 0; i--){
if (_.indexOf(_.pluck(visibleColumns,'id'),columns[i].id) == -1){
tempHiddenColumns.push(columns.splice(i,1)[0]);
}
}
columns = columns.concat(tempHiddenColumns);
var data = this.model.currentDocuments.toJSON();
this.grid = new Slick.Grid(this.el, data, visibleColumns, options);
// Column sorting
var gridSorter = function(field, ascending, grid, data){
data.sort(function(a, b){
var result =
a[field] > b[field] ? 1 :
a[field] < b[field] ? -1 :
0
;
return ascending ? result : -result;
});
grid.setData(data);
grid.updateRowCount();
grid.render();
}
var sortInfo = this.state.get('columnsSort');
if (sortInfo){
var sortAsc = !(sortInfo['direction'] == 'desc');
gridSorter(sortInfo.column, sortAsc, self.grid, data);
this.grid.setSortColumn(sortInfo.column, sortAsc);
}
this.grid.onSort.subscribe(function(e, args){
gridSorter(args.sortCol.field,args.sortAsc,self.grid,data);
self.state.set({columnsSort:{
column:args.sortCol,
direction: (args.sortAsc) ? 'asc':'desc'
}});
});
this.grid.onColumnsReordered.subscribe(function(e, args){
self.state.set({columnsOrder: _.pluck(self.grid.getColumns(),'id')});
});
this.grid.onColumnsResized.subscribe(function(e, args){
var columns = args.grid.getColumns();
var defaultColumnWidth = args.grid.getOptions().defaultColumnWidth;
var columnsWidth = [];
_.each(columns,function(column){
if (column.width != defaultColumnWidth){
columnsWidth.push({column:column.id,width:column.width});
}
});
self.state.set({columnsWidth:columnsWidth});
});
var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid,
_.extend(options,{state:this.state}));
if (self.visible){
self.grid.init();
self.rendered = true;
} else {
// Defer rendering until the view is visible
self.rendered = false;
}
return this;
}
});
})(jQuery, recline.View);
/*
* Context menu for the column picker, adapted from
* http://mleibman.github.com/SlickGrid/examples/example-grouping
*
*/
(function ($) {
function SlickColumnPicker(columns, grid, options) {
var $menu;
var columnCheckboxes;
var defaults = {
fadeSpeed:250
};
function init() {
grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu);
options = $.extend({}, defaults, options);
$menu = $('<ul class="dropdown-menu slick-contextmenu" style="display:none;position:absolute;z-index:20;" />').appendTo(document.body);
$menu.bind('mouseleave', function (e) {
$(this).fadeOut(options.fadeSpeed)
});
$menu.bind('click', updateColumn);
}
function handleHeaderContextMenu(e, args) {
e.preventDefault();
$menu.empty();
columnCheckboxes = [];
var $li, $input;
for (var i = 0; i < columns.length; i++) {
$li = $('<li />').appendTo($menu);
$input = $('<input type="checkbox" />').data('column-id', columns[i].id).attr('id','slick-column-vis-'+columns[i].id);
columnCheckboxes.push($input);
if (grid.getColumnIndex(columns[i].id) != null) {
$input.attr('checked', 'checked');
}
$input.appendTo($li);
$('<label />')
.text(columns[i].name)
.attr('for','slick-column-vis-'+columns[i].id)
.appendTo($li);
}
$('<li/>').addClass('divider').appendTo($menu);
$li = $('<li />').data('option', 'autoresize').appendTo($menu);
$input = $('<input type="checkbox" />').data('option', 'autoresize').attr('id','slick-option-autoresize');
$input.appendTo($li);
$('<label />')
.text('Force fit columns')
.attr('for','slick-option-autoresize')
.appendTo($li);
if (grid.getOptions().forceFitColumns) {
$input.attr('checked', 'checked');
}
$menu.css('top', e.pageY - 10)
.css('left', e.pageX - 10)
.fadeIn(options.fadeSpeed);
}
function updateColumn(e) {
if ($(e.target).data('option') == 'autoresize') {
var checked;
if ($(e.target).is('li')){
var checkbox = $(e.target).find('input').first();
checked = !checkbox.is(':checked');
checkbox.attr('checked',checked);
} else {
checked = e.target.checked;
}
if (checked) {
grid.setOptions({forceFitColumns:true});
grid.autosizeColumns();
} else {
grid.setOptions({forceFitColumns:false});
}
options.state.set({fitColumns:checked});
return;
}
if (($(e.target).is('li') && !$(e.target).hasClass('divider')) ||
$(e.target).is('input')) {
if ($(e.target).is('li')){
var checkbox = $(e.target).find('input').first();
checkbox.attr('checked',!checkbox.is(':checked'));
}
var visibleColumns = [];
var hiddenColumnsIds = [];
$.each(columnCheckboxes, function (i, e) {
if ($(this).is(':checked')) {
visibleColumns.push(columns[i]);
} else {
hiddenColumnsIds.push(columns[i].id);
}
});
if (!visibleColumns.length) {
$(e.target).attr('checked', 'checked');
return;
}
grid.setColumns(visibleColumns);
options.state.set({hiddenColumns:hiddenColumnsIds});
}
}
init();
}
// Slick.Controls.ColumnPicker
$.extend(true, window, { Slick:{ Controls:{ ColumnPicker:SlickColumnPicker }}});
})(jQuery);