diff --git a/demos/multiview/app.js b/demos/multiview/app.js
index 2aec5896..929337c5 100755
--- a/demos/multiview/app.js
+++ b/demos/multiview/app.js
@@ -78,6 +78,8 @@ var createExplorer = function(dataset, state) {
gridOptions: {
editable: true,
enabledAddRow: true,
+ enabledDelRow: true,
+ autoEdit: false,
enableCellNavigation: true
},
columnsEditor: [
diff --git a/demos/search/demo.search.app.js b/demos/search/demo.search.app.js
index 49f5f9b9..9621ffbf 100644
--- a/demos/search/demo.search.app.js
+++ b/demos/search/demo.search.app.js
@@ -143,7 +143,7 @@ var SearchView = Backbone.View.extend({
this.el.find('.sidebar').append(view.el);
var pager = new recline.View.Pager({
- model: this.model.queryState
+ model: this.model
});
this.el.find('.pager-here').append(pager.el);
diff --git a/src/backend.memory.js b/src/backend.memory.js
index 0c83a7d1..8cb64ddf 100644
--- a/src/backend.memory.js
+++ b/src/backend.memory.js
@@ -101,6 +101,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
// register filters
var filterFunctions = {
term : term,
+ terms : terms,
range : range,
geo_distance : geo_distance
};
@@ -140,6 +141,14 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
return (value === term);
}
+ function terms(record, filter) {
+ var parse = getDataParser(filter);
+ var value = parse(record[filter.field]);
+ var terms = parse(filter.terms).split(",");
+
+ return (_.indexOf(terms, value) >= 0);
+ }
+
function range(record, filter) {
var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === '');
var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === '');
diff --git a/src/view.multiview.js b/src/view.multiview.js
index ccd35171..918f0e16 100644
--- a/src/view.multiview.js
+++ b/src/view.multiview.js
@@ -260,7 +260,7 @@ my.MultiView = Backbone.View.extend({
}, this);
this.pager = new recline.View.Pager({
- model: this.model.queryState
+ model: this.model
});
this.$el.find('.recline-results-info').after(this.pager.el);
diff --git a/src/view.slickgrid.js b/src/view.slickgrid.js
index f7512f8d..cd4f3ce1 100644
--- a/src/view.slickgrid.js
+++ b/src/view.slickgrid.js
@@ -5,6 +5,37 @@ this.recline.View = this.recline.View || {};
(function($, my) {
"use strict";
+ // Add new grid Control to display a new row add menu bouton
+ // It display a simple side-bar menu ,for user to add new
+ // row to grid
+
+ my.GridControl= Backbone.View.extend({
+ className: "recline-row-add",
+ // Template for row edit menu , change it if you don't love
+ template: '
',
+
+ initialize: function(options){
+ var self = this;
+ _.bindAll(this, 'render');
+ this.state = new recline.Model.ObjectState();
+ this.render();
+ },
+
+ render: function() {
+ var self = this;
+ this.$el.html(this.template)
+ },
+
+ events : {
+ "click .recline-row-add" : "addNewRow"
+ },
+
+ addNewRow : function(e){
+ e.preventDefault()
+ this.state.trigger("change")
+ }
+ }
+ );
// ## SlickGrid Dataset View
//
// Provides a tabular view on a Dataset, based on SlickGrid.
@@ -39,10 +70,14 @@ my.SlickGrid = Backbone.View.extend({
initialize: function(modelEtc) {
var self = this;
this.$el.addClass('recline-slickgrid');
+
+ // Template for row delete menu , change it if you don't love
+ this.templates = {
+ "deleterow" : 'X'
+ }
_.bindAll(this, 'render', 'onRecordChanged');
this.listenTo(this.model.records, 'add remove reset', this.render);
this.listenTo(this.model.records, 'change', this.onRecordChanged);
-
var state = _.extend({
hiddenColumns: [],
columnsOrder: [],
@@ -55,29 +90,32 @@ my.SlickGrid = Backbone.View.extend({
);
this.state = new recline.Model.ObjectState(state);
-
this._slickHandler = new Slick.EventHandler();
- },
- events: {
+ //add menu for new row , check if enableAddRow is set to true or not set
+ if(this.state.get("gridOptions")
+ && this.state.get("gridOptions").enabledAddRow != undefined
+ && this.state.get("gridOptions").enabledAddRow == true ){
+ this.editor = new my.GridControl()
+ this.elSidebar = this.editor.$el
+ this.listenTo(this.editor.state, 'change', function(){
+ this.model.records.add(new recline.Model.Record())
+ });
+ }
},
-
onRecordChanged: function(record) {
// Ignore if the grid is not yet drawn
if (!this.grid) {
return;
}
-
// Let's find the row corresponding to the index
var row_index = this.grid.getData().getModelRow( record );
this.grid.invalidateRow(row_index);
this.grid.getData().updateItem(record, row_index);
this.grid.render();
},
-
- render: function() {
+ render: function() {
var self = this;
-
var options = _.extend({
enableCellNavigation: true,
enableColumnReorder: true,
@@ -87,18 +125,48 @@ my.SlickGrid = Backbone.View.extend({
}, self.state.get('gridOptions'));
// We need all columns, even the hidden ones, to show on the column picker
- var columns = [];
+ var columns = [];
// custom formatter as default one escapes html
// plus this way we distinguish between rendering/formatting and computed value (so e.g. sort still works ...)
// row = row index, cell = cell index, value = value, columnDef = column definition, dataContext = full row values
var formatter = function(row, cell, value, columnDef, dataContext) {
- var field = self.model.fields.get(columnDef.id);
+ if(columnDef.id == "del"){
+ return self.templates.deleterow
+ }
+ var field = self.model.fields.get(columnDef.id);
if (field.renderer) {
- return field.renderer(value, field, dataContext);
- } else {
- return value;
+ return field.renderer(value, field, dataContext);
+ }else {
+ return value
}
};
+ // we need to be sure that user is entering a valid input , for exemple if
+ // field is date type and field.format ='YY-MM-DD', we should be sure that
+ // user enter a correct value
+ var validator = function(field){
+ return function(value){
+ if(field.type == "date" && isNaN(Date.parse(value))){
+ return {
+ valid: false,
+ msg: "A date is required, check field field-date-format"};
+ }else {
+ return {valid: true, msg :null }
+ }
+ }
+ };
+ //Add row delete support , check if enableDelRow is set to true or not set
+ if(this.state.get("gridOptions")
+ && this.state.get("gridOptions").enabledDelRow != undefined
+ && this.state.get("gridOptions").enabledDelRow == true ){
+ columns.push({
+ id: 'del',
+ name: 'del',
+ field: 'del',
+ sortable: true,
+ width: 80,
+ formatter: formatter,
+ validator:validator
+ })}
_.each(this.model.fields.toJSON(),function(field){
var column = {
id: field.id,
@@ -106,14 +174,13 @@ my.SlickGrid = Backbone.View.extend({
field: field.id,
sortable: true,
minWidth: 80,
- formatter: formatter
+ formatter: formatter,
+ validator:validator(field)
};
-
var widthInfo = _.find(self.state.get('columnsWidth'),function(c){return c.column === field.id;});
if (widthInfo){
column.width = widthInfo.width;
}
-
var editInfo = _.find(self.state.get('columnsEditor'),function(c){return c.column === field.id;});
if (editInfo){
column.editor = editInfo.editor;
@@ -137,13 +204,11 @@ my.SlickGrid = Backbone.View.extend({
}
}
columns.push(column);
- });
-
+ });
// Restrict the visible columns
var visibleColumns = _.filter(columns, 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') && this.state.get('columnsOrder').length > 0) {
visibleColumns = visibleColumns.sort(function(a,b){
@@ -163,12 +228,16 @@ my.SlickGrid = Backbone.View.extend({
}
}
columns = columns.concat(tempHiddenColumns);
-
// Transform a model object into a row
function toRow(m) {
var row = {};
self.model.fields.each(function(field){
- row[field.id] = m.getFieldValueUnrendered(field);
+ var render = "";
+ //when adding row from slickgrid the field value is undefined
+ if(!_.isUndefined(m.getFieldValueUnrendered(field))){
+ render =m.getFieldValueUnrendered(field)
+ }
+ row[field.id] = render
});
return row;
}
@@ -191,6 +260,7 @@ my.SlickGrid = Backbone.View.extend({
rows[i] = toRow(m);
models[i] = m;
};
+
}
var data = new RowSet();
@@ -200,7 +270,6 @@ my.SlickGrid = Backbone.View.extend({
});
this.grid = new Slick.Grid(this.el, data, visibleColumns, options);
-
// Column sorting
var sortInfo = this.model.queryState.get('sort');
if (sortInfo){
@@ -233,19 +302,25 @@ my.SlickGrid = Backbone.View.extend({
});
self.state.set({columnsWidth:columnsWidth});
});
-
+
this._slickHandler.subscribe(this.grid.onCellChange, function (e, args) {
// We need to change the model associated value
- //
var grid = args.grid;
var model = data.getModel(args.row);
var field = grid.getColumns()[args.cell].id;
var v = {};
v[field] = args.item[field];
model.set(v);
- });
-
- var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid,
+ });
+ this._slickHandler.subscribe(this.grid.onClick,function(e, args){
+ if (args.cell == 0 && self.state.get("gridOptions").enabledDelRow == true){
+ // We need to delete the associated model
+ var model = data.getModel(args.row);
+ model.destroy()
+ }
+ }) ;
+
+ var columnpicker = new Slick.Controls.ColumnPicker(columns, this.grid,
_.extend(options,{state:this.state}));
if (self.visible){
@@ -402,3 +477,4 @@ my.SlickGrid = Backbone.View.extend({
// Slick.Controls.ColumnPicker
$.extend(true, window, { Slick:{ Controls:{ ColumnPicker:SlickColumnPicker }}});
})(jQuery);
+
diff --git a/src/widget.pager.js b/src/widget.pager.js
index b2b5d03d..05e79237 100644
--- a/src/widget.pager.js
+++ b/src/widget.pager.js
@@ -25,34 +25,43 @@ my.Pager = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
- this.listenTo(this.model, 'change', this.render);
+ this.listenTo(this.model.queryState, 'change', this.render);
this.render();
},
onFormSubmit: function(e) {
e.preventDefault();
var newFrom = parseInt(this.$el.find('input[name="from"]').val());
+ newFrom = Math.min(this.model.recordCount, Math.max(newFrom, 1))-1;
var newSize = parseInt(this.$el.find('input[name="to"]').val()) - newFrom;
- newFrom = Math.max(newFrom, 0);
- newSize = Math.max(newSize, 1);
- this.model.set({size: newSize, from: newFrom});
+ newSize = Math.min(Math.max(newSize, 1), this.model.recordCount);
+ this.model.queryState.set({size: newSize, from: newFrom});
},
onPaginationUpdate: function(e) {
e.preventDefault();
var $el = $(e.target);
var newFrom = 0;
+ var currFrom = this.model.queryState.get('from');
+ var size = this.model.queryState.get('size');
+ var updateQuery = false;
if ($el.parent().hasClass('prev')) {
- newFrom = this.model.get('from') - Math.max(0, this.model.get('size'));
+ newFrom = Math.max(currFrom - Math.max(0, size), 1)-1;
+ updateQuery = newFrom != currFrom;
} else {
- newFrom = this.model.get('from') + this.model.get('size');
+ newFrom = Math.max(currFrom + size, 1);
+ updateQuery = (newFrom < this.model.recordCount);
+ }
+ if (updateQuery) {
+ this.model.queryState.set({from: newFrom});
}
- newFrom = Math.max(newFrom, 0);
- this.model.set({from: newFrom});
},
render: function() {
var tmplData = this.model.toJSON();
- tmplData.to = this.model.get('from') + this.model.get('size');
+ var from = parseInt(this.model.queryState.get('from'));
+ tmplData.from = from+1;
+ tmplData.to = Math.min(from+this.model.queryState.get('size'), this.model.recordCount);
var templated = Mustache.render(this.template, tmplData);
this.$el.html(templated);
+ return this;
}
});
diff --git a/test/backend.memory.test.js b/test/backend.memory.test.js
index 440e7d8d..cbe84ad6 100644
--- a/test/backend.memory.test.js
+++ b/test/backend.memory.test.js
@@ -99,6 +99,13 @@ test('filters', function () {
deepEqual(_.pluck(out.hits, 'country'), ['UK','UK','UK']);
});
+ query = new recline.Model.Query();
+ query.addFilter({type: 'terms', field: 'country', terms: ['UK','DE']});
+ data.query(query.toJSON()).then(function(out) {
+ equal(out.total, 5);
+ deepEqual(_.pluck(out.hits, 'country'), ['DE','UK','UK','UK','DE']);
+ });
+
query = new recline.Model.Query();
query.addFilter({type: 'range', field: 'date', from: '2011-01-01', to: '2011-05-01'});
data.query(query.toJSON()).then(function(out) {
@@ -307,6 +314,13 @@ test('filters', function () {
deepEqual(dataset.records.pluck('country'), ['UK', 'UK', 'UK']);
});
+ dataset = makeBackendDataset();
+ dataset.queryState.addFilter({type: 'terms', field: 'country', terms: ['UK','DE']});
+ dataset.query().then(function() {
+ equal(dataset.records.length, 5);
+ deepEqual(dataset.records.pluck('country'), ['DE','UK', 'UK', 'UK','DE']);
+ });
+
dataset = makeBackendDataset();
dataset.queryState.addFilter({type: 'range', field: 'date', from: '2011-01-01', to: '2011-05-01'});
dataset.query().then(function() {
diff --git a/test/index.html b/test/index.html
index 1dc22ed8..87698280 100644
--- a/test/index.html
+++ b/test/index.html
@@ -71,6 +71,7 @@
+
diff --git a/test/view.slickgrid.test.js b/test/view.slickgrid.test.js
index 597461cd..80c895c6 100644
--- a/test/view.slickgrid.test.js
+++ b/test/view.slickgrid.test.js
@@ -103,6 +103,77 @@ test('editable', function () {
view.remove();
});
+test('delete-row' , function(){
+ var dataset = Fixture.getDataset();
+ var view = new recline.View.SlickGrid({
+ model: dataset,
+ state: {
+ hiddenColumns:['x','lat','title'],
+ columnsOrder:['lon','id','z','date', 'y', 'country'],
+ columnsWidth:[
+ {column:'id',width: 250}
+ ],
+ gridOptions: {editable: true , "enabledDelRow":true},
+ columnsEditor: [{column: 'country', editor: Slick.Editors.Text}]
+ }
+ });
+
+ $('.fixtures .test-datatable').append(view.el);
+ view.render();
+ view.show();
+ old_length = dataset.records.length
+ dataset.records.on('remove', function(record){
+ equal(dataset.records.length, old_length -1 );
+ });
+
+ // Be sure a cell change triggers a change of the model
+ e = new Slick.EventData();
+ view.grid.onClick.notify({
+ row: 1,
+ cell: 0,
+ grid: view.grid
+ }, e, view.grid);
+
+ view.remove();
+
+
+});
+
+test('add-row' , function(){
+//To test adding row on slickgrid , we add some menu GridControl
+//I am based on the FlotControl in flot wiewer , to add a similary
+//to the sclickgrid , The GridControl add a bouton menu
+//one the .side-bar place , which will allow to add a row to
+//the grid on-click
+
+var dataset = Fixture.getDataset();
+ var view = new recline.View.SlickGrid({
+ model: dataset,
+ state: {
+ hiddenColumns:['x','lat','title'],
+ columnsOrder:['lon','id','z','date', 'y', 'country'],
+ columnsWidth:[
+ {column:'id',width: 250}
+ ],
+ gridOptions: {editable: true , "enabledAddRow":true},
+ columnsEditor: [{column: 'country', editor: Slick.Editors.Text}]
+ }
+ });
+
+// view will auto render ...
+assertPresent('.recline-row-add', view.elSidebar);
+// see recline.SlickGrid.GridControl widget
+//view.render()
+old_length = dataset.records.length
+dataset.records.on('add',function(record){
+ equal(dataset.records.length ,old_length + 1 )
+});
+
+view.elSidebar.find('.recline-row-add').click();
+
+});
+
+
test('update', function() {
var dataset = Fixture.getDataset();
var view = new recline.View.SlickGrid({
diff --git a/test/widget.pager.test.js b/test/widget.pager.test.js
new file mode 100644
index 00000000..4274fede
--- /dev/null
+++ b/test/widget.pager.test.js
@@ -0,0 +1,99 @@
+module("Widget - Pager");
+
+test('basics', function () {
+ var dataset = Fixture.getDataset();
+ var size = dataset.recordCount/2 + 1;
+ dataset.queryState.set({ size : size }, { silent : true });
+ var view = new recline.View.Pager({
+ model: dataset
+ });
+ $('.fixtures').append(view.el);
+ var fromSelector = 'input[name=from]';
+ var toSelector = 'input[name=to]';
+
+ assertPresent('.pagination', view.elSidebar);
+ // next and prev present
+ assertPresent('.prev', view.elSidebar);
+ assertPresent('.next', view.elSidebar);
+
+ // from and to inputs present
+ assertPresent(fromSelector, view.elSidebar);
+ assertPresent(toSelector, view.elSidebar);
+
+ // click next: -> reload from+size - recordCount
+ var prevFromVal = parseInt($(fromSelector).val());
+ var prevToVal = parseInt($(toSelector).val());
+ view.$el.find('.next a').click();
+ equal($(fromSelector).val(), prevFromVal+size);
+ // to = recordCount since size is more than half of record count
+ equal($(toSelector).val(), dataset.recordCount);
+ // UI is 1-based but model is zero-based
+ equal(dataset.queryState.get('from'), prevFromVal+size-1);
+
+ // click prev -> 1-4, model from=0
+ prevFromVal = parseInt($(fromSelector).val());
+ prevToVal = parseInt($(toSelector).val());
+ view.$el.find('.prev a').click();
+ equal($(fromSelector).val(), prevFromVal-size);
+ equal($(toSelector).val(), prevFromVal-1);
+ // UI is 1-based but model is zero-based
+ equal(dataset.queryState.get('from'), prevFromVal-size-1);
+
+ view.remove();
+});
+
+test('bounds checking', function () {
+ var dataset = Fixture.getDataset();
+ var size = dataset.recordCount/2 + 1;
+ dataset.queryState.set({ size : size }, { silent : true });
+ var view = new recline.View.Pager({
+ model: dataset
+ });
+ $('.fixtures').append(view.el);
+ var querySpy = sinon.spy(dataset, 'query');
+ var fromSelector = 'input[name=from]';
+ var toSelector = 'input[name=to]';
+
+ // click prev on beginning: nothing happens
+ view.$el.find('.prev a').click();
+ equal($(fromSelector).val(), 1);
+ equal($(toSelector).val(), size);
+ ok(!dataset.query.called);
+
+ // enter size-1 in from: reloads size-1 - size
+ var fromVal = size-1;
+ var toVal = parseInt($(toSelector).val());
+ $(fromSelector).val(fromVal).change();
+ equal($(fromSelector).val(), fromVal);
+ equal($(toSelector).val(), toVal);
+ // UI is 1-based but model is zero-based
+ equal(dataset.queryState.get('from'), fromVal-1);
+
+ // enter value past the end in from: reloads recordCount - recordCount
+ fromVal = dataset.recordCount + 10;
+ $(fromSelector).val(fromVal).change();
+ equal($(fromSelector).val(), dataset.recordCount);
+ equal($(toSelector).val(), dataset.recordCount);
+ // UI is 1-based but model is zero-based
+ equal(dataset.queryState.get('from'), dataset.recordCount-1);
+
+ // click next on end -> nothing happens
+ var queryCalls = querySpy.callCount;
+ fromVal = parseInt($(fromSelector).val());
+ toVal = parseInt($(toSelector).val());
+ view.$el.find('.next a').click();
+ equal(querySpy.callCount, queryCalls);
+ equal($(fromSelector).val(), fromVal);
+ equal($(toSelector).val(), toVal);
+
+ // reset from to 1
+ // type value past the end in to: 1-recordCount
+ fromVal = 1;
+ toVal = dataset.recordCount + 10;
+ $(fromSelector).val(fromVal);
+ $(toSelector).val(toVal).change();
+ equal($(fromSelector).val(), 1);
+ equal($(toSelector).val(), dataset.recordCount);
+
+ view.remove();
+});