I've created my first ng-grid table and loading it with data asynchronously from the server. All my code seems to get executed created and function as desired behind the scenes except for one major problem.
ng-grid's pagination buttons get rendered in HTML as 'button' tags like this:
<button class="ngPagerButton" ng-click="pageForward()" ng-disabled="cantPageForward()" title="Next Page">
<div class="ngPagerLastTriangle ngPagerNextTriangle"></div>
</button>
But there is no type attribute put on the button so it defaults to type='submit'.
Whenever I click on the next page button, behind the scenes everything works, but it also does an extraneous POST because of the button type. This is completely undesired.
Anyone else run into this? How do you get around it? Am I doing something wrong?
The basic setup is as follows:
HTML:
<div id='deposits' class='gridStyle' ng-grid='gridOptions'></div>
JS controller code (really nothing special here, taken pretty much right from the ng-grid docs...but not working!):
$scope.reportingForm = {
startDate: new Date(2014, 1, 1), // just for testing
endDate: new Date(2014, 1, 7),
};
$scope.filterOptions = {
filterText: '',
useExternalFilter: true
};
$scope.totalServerItems = 0;
$scope.pagingOptions = {
pageSizes: [7, 14],
pageSize: 7,
currentPage: 1
};
$scope.setPagingData = function(data, page, pageSize) {
var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
$scope.data = pagedData;
$scope.totalServerItems = data.length;
if (!$scope.$$phase) {
$scope.$apply();
}
};
$scope.getPagedDataAsync = function(pageSize, page, searchText) {
setTimeout(function () {
var data;
if (searchText) {
var filter = searchText.toLowerCase();
myService.getDataAsync(
$scope.reportingForm.startDate, $scope.reportingForm.endDate
function(result) {
data = result.result.data.filter(function(item) {
return JSON.stringify(item).toLowerCase().indexOf(filter) != -1;
});
$scope.setPagingData(data, page, pageSize);
}
);
} else {
myService.getDataAsync(
$scope.reportingForm.startDate, $scope.reportingForm.endDate,
function(result) {
$scope.setPagingData(result.result.data, page, pageSize);
}
);
}
}, 100);
};
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
$scope.$watch('pagingOptions', function (newVal, oldVal) {
if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
}
}, true);
$scope.$watch('filterOptions', function (newVal, oldVal) {
if (newVal !== oldVal) {
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
}
}, true);
$scope.gridOptions = {
data: 'data',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions,
enablePaging: true,
showFooter: true,
columnDefs: [
{ field: 'date', displayName: 'Date' },
{ field: 'id', displayName: 'Id' },
{ field: 'location', displayName: 'Location' },
{ field: 'amount', displayName: 'Amount' },
]
};
ng-grid renders the previous and next page buttons as <button> and does not specify a button type so it defaults to type='submit'.
In our page the ng-grid table lived within a form. I didn't know this but after some testing realized submit <button>s within a form do a POST, but submit <button>s outside of a form do NOT do a POST.
By moving our ng-grid table outside of the form we no longer have this problem.
Really though, this seems to be an oversight in the ng-grid code. It should really be rendering the buttons as type='button' to prevent this kind of issue in the future. I can't imagine any cases where you'd want the last/next page buttons to do POSTs.
This appears to have been fixed in ng-grid 2.0.8 whenever it is released.
https://github.com/angular-ui/ng-grid/pull/693
Related
I have been trying to figure out what is going on but haven't yet.
Please see the code below.It is a inconsistent behavior, so I am having hard time to catch what triggers it. This grid is inside a popup, I noticed when i refresh the page and try it, it works fine on first attempt. Then i save/cancel popup and keep repeating it fails at some point and starts accumulate null rows.
I tried to check the value of the grid using
$('#gridFldListItems').data("kendoGrid").dataSource.data()
It shows there is no data but as soon as click "Add new Record" it shows 2.
It does not necessarily fail on second attempt, but it never fails on first. I suspect that everytime I open the popup it is not necessarily empty(after first few tries) and it carries some data from previous attempts. I might be wrong.
When I click add new record, it adds a line with null value and gives me an option to input on the second row.
I also When I put itemname and click update, it does not trigger the "create" event and looks like this:
At this point the grid broken. Here is the code for the grid
var grid = $("#gridFldListItems").kendoGrid({
editable: {
"confirmation": "Are you sure you want to delete this item?",
"mode": "inline",
"createAt": "bottom"
},
selectable: true,
autoBind: false,
toolbar: ["create" ],
columns: [
{ field: 'Item' },
{
command: ['edit', 'destroy',
{ iconClass: "k-icon k-i-arrow-up", click: $.proxy(this, 'selectedFieldDef_onClkMoveUp'), name: 'Up' },
{ iconClass: "k-icon k-i-arrow-down", click: $.proxy(this, 'selectedFieldDef_onClkMoveDown'), name: 'Down' }], title: ' '
}
],
dataSource: this.selectedFieldDef_dsItems,
}).data("kendoGrid");
selectedFieldDef_dsItems: new kendo.data.DataSource({
transport: {
read: function (e) {
var field = editViewModel.get("selectedFieldDef");
var mapItems = $.map(field.Items, function (item, idx) {
return {
Item: item
};
});
//on success
e.success(mapItems);
},
create: function (e) {
// on success
e.success(e.data);
},
update: function (e) {
// on success
e.success();
},
destroy: function (e) {
var vm = editViewModel;
// locate item in original datasource and remove it
var field = vm.get("selectedFieldDef");
if (field.DefaultValue && !vm.selectedFieldDef_dsItemsFindItem(vm.selectedFieldDef_dsItems.data(), field.DefaultValue)) {
field.DefaultValue = null;
vm.set("selectedFieldDef", field);
$("#inpFldRegex").kendoDropDownList().data("kendoDropDownList").trigger("change");
}
// on success
e.success();
}
},
error: function (e) {
alert("Status: " + e.status + "; Error message: " + e.errorThrown);
},
schema: {
model: {
id: "Item",
fields: {
Item: { editable: true, nullable: true }
}
}
}
})
Any help would be much appreciated.
UPDATE:
It works fine when I refresh the page
I have a grid with thousands of records. It works almost fine. When I programmatically call this method:
selectionModel.selectAll(true);
it works great. My script is not freezed, because of this true parameter, which according to docs suppresses any select events. However, when I select all records via a header checkbox (which selects and deselects all records), my script gets freezed. I guess, it is because by default select events are not suppressed. I tried to find a config, which globally supresses select events, but could not find it. So, my question is how can I fix this issue? How can I globally suppress select events in my grid selModel?
You can handle using overriding onHeaderClick method of grid CheckboxModel.
In this Fiddle, I have created a demo using same method onHeaderClick.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
var store = Ext.create('Ext.data.Store', {
fields: ['number'],
data: (() => {
let data = [];
for (let i = 1; i <= 50000; i++) {
data.push({
'number': i
})
}
return data;
})()
});
Ext.create('Ext.grid.Panel', {
title: 'Demo',
store: store,
itemId: 'demogrid',
columns: [{
text: 'Number',
flex: 1,
dataIndex: 'number'
}],
height: window.innerHeight,
renderTo: Ext.getBody(),
selModel: {
checkOnly: false,
mode: 'SIMPLE',
onHeaderClick: function (headerCt, header, e) {
var me = this,
store = me.store,
isChecked, records, i, len,
selections, selection;
if (me.showHeaderCheckbox !== false && header === me.column && me.mode !== 'SINGLE') {
e.stopEvent();
isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
if (isChecked) {
console.time();
me.deselectAll(true);
console.timeEnd();
} else {
console.time();
me.selectAll(true);
console.timeEnd();
}
}
}
},
selType: 'checkboxmodel',
buttons: [{
text: 'Select All',
handler: function (btn) {
var grid = btn.up('#demogrid');
console.time();
grid.getSelectionModel().selectAll(true);
console.timeEnd();
}
}, {
text: 'Deselect All',
handler: function (btn) {
var grid = btn.up('#demogrid');
console.time()
grid.getSelectionModel().deselectAll(true);
console.timeEnd();
}
}]
});
}
});
use suspendEvents function on model to stop all events and after you done you can start all events by resumeEvents
grid.getSelectionModel().suspendEvents();
grid.getSelectionModel().resumeEvents();
I am in the process of learning Backbone.js and using BackGrid to render data and provide the end user a way to edit records on an Microsoft MVC website. For the purposes of this test grid I am using a Vendor model. The BackGrid makes the data editable by default (which is good for my purpose). I have added the following JavaScript to my view.
var Vendor = Backbone.Model.extend({
initialize: function () {
Backbone.Model.prototype.initialize.apply(this, arguments);
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.url = "/Vendor/BackGridSave";
model.save();
});
}
});
var PageableVendors = Backbone.PageableCollection.extend(
{
model: Vendor,
url: "/Vendor/IndexJson",
state: {
pageSize: 3
},
mode: "client" // page entirely on the client side.
});
var pageableVendors = new PageableVendors();
//{ data: "ID" },
//{ data: "ClientID" },
//{ data: "CarrierID" },
//{ data: "Number" },
//{ data: "Name" },
//{ data: "IsActive" }
var columns = [
{
name: "ID", // The key of the model attribute
label: "ID", // The name to display in the header
editable: false, // By default every cell in a column is editable, but *ID* shouldn't be
// Defines a cell type, and ID is displayed as an integer without the ',' separating 1000s.
cell: Backgrid.IntegerCell.extend({
orderSeparator: ''
})
}, {
name: "ClientID",
label: "ClientID",
cell: "integer" // An integer cell is a number cell that displays humanized integers
}, {
name: "CarrierID",
label: "CarrierID",
cell: "number" // A cell type for floating point value, defaults to have a precision 2 decimal numbers
}, {
name: "Number",
label: "Number",
cell: "string"
}, {
name: "Name",
label: "Name",
cell: "string"
},
{
name: "IsActive",
label: "IsActive",
cell: "boolean"
}
];
// initialize a new grid instance.
var pageableGrid = new Backgrid.Grid({
columns: [
{
name:"",
cell: "select-row",
headercell: "select-all"
}].concat(columns),
collection: pageableVendors
});
// render the grid.
var $p = $("#vendor-grid").append(pageableGrid.render().el);
// Initialize the paginator
var paginator = new Backgrid.Extension.Paginator({
collection: pageableVendors
});
// Render the paginator
$p.after(paginator.render().el);
// Initialize a client-side filter to filter on the client
// mode pageable collection's cache.
var filter = new Backgrid.Extension.ClientSideFilter({
collection: pageableVendors,
fields: ['Name']
});
// REnder the filter.
$p.before(filter.render().el);
//Add some space to the filter and move it to teh right.
$(filter.el).css({ float: "right", margin: "20px" });
// Fetch some data
pageableVendors.fetch({ reset: true });
#{
ViewBag.Title = "BackGridIndex";
}
<h2>BackGridIndex</h2>
<div id="vendor-grid"></div>
#section styles {
#Styles.Render("~/Scripts/backgrid.css")
#Styles.Render("~/Scripts/backgrid-select-all.min.css")
#Styles.Render("~/Scripts/backgrid-filter.min.css")
#Styles.Render("~/Scripts/backgrid-paginator.min.css")
}
#section scripts {
#Scripts.Render("~/Scripts/underscore.min.js")
#Scripts.Render("~/Scripts/backbone.min.js")
#Scripts.Render("~/Scripts/backgrid.js")
#Scripts.Render("~/Scripts/backgrid-select-all.min.js")
#Scripts.Render("~/Scripts/backbone.paginator.min.js")
#Scripts.Render("~/Scripts/backgrid-paginator.min.js")
#Scripts.Render("~/Scripts/backgrid-filter.min.js")
#Scripts.Render("~/Scripts/Robbys/BackGridIndex.js")
}
When the user edits a row, it successfully fires the hits the model.Save() method and passes the model to the save Action, in this case BackGridSave and it successfully saves the record that changed, but seems to save all of the vendors in model when only one of the vendors changed. Is there a way from the JavaScript/Backbone.js/BackGrid to only pass one Vendor - the vendor that changed?
Update: I realized that it is not sending every vendor, but it is sending the same vendor multiple times as though the change event was firing multiple times.
I guess I answered my own question. Well, at least I am getting the desired result. I just added a call to off after the first on. Seems like this would not be necessary though.
var Vendor = Backbone.Model.extend({
initialize: function () {
Backbone.Model.prototype.initialize.apply(this, arguments);
this.on("change", function (model, options) {
if (options && options.save === false) return;
model.url = "/Robbys/BackGridSave";
model.save();
model.off("change", null, this); // prevent the change event from being triggered many times.
});
}
});
I'm having a table whose contents(name and status) i'm trying to refresh every 1 min in such a way that the status should change from 'schedule' to 'active' to 'running' w/o having user manually refresh it.
here is the js:
$cls.superclass.constructor.call(this, Ext.apply({
useTemplate: true,
directFn: Service('getRecordService').getRecord,
fields: [
{
name : 'name',
column: {
header: "Name",
renderer: function (record) {
return name(record.data.name);
},
}
},
{
name : 'status',
column: {
header: "status",
renderer: function (record) {
return status(record.data.status);
},
}
},
],
viewConfig: {
getRecord: function (){
///get single record info
}
};
});
I'm not sure how can i write a function in the above snippet that will allow me to auto refresh the table contents every 1 min. Any ideas would be appreciated!
Thanks!
You should take a look at Ext.util.TaskRunner and the singleton Ext.TaskMgr.
It provides methods to start/stop task functions which are executed in a configurable interval.
With that you could try something like below.
var grid = ; // your grid instance
var store = grid.getStore(); // store of your grid, you want to reload
var refreshTask = { // task which reloads the store each minute
run: function() {
store.reload();
},
interval: 60 * 1000 // 1 Minute
};
Ext.util.TaskRunner.start(refreshTask);
The paging worked when data was populated on load. After I included enteredValue/search functionality to populate ng-grid I no longer see 5 items per page and the next/previous buttons don't work. I believe the change of data in gridOptions from 'myData' to 'source' broke the pagination. I am trying to pass $scope.source into setPagingData function but having issues. How do I get the paging to work properly?
$scope.setPagingData = function(data, page, pageSize) {
var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
$scope.myData = pagedData;
$scope.totalServerItems = data.length;
if (!$scope.$$phase) {
$scope.$apply();
}
};
$scope.gridOptions = {
data: 'source',
enablePaging: true,
pagingOptions: $scope.pagingOptions,
showFooter: true
};
Here's plunker
Ok, let's assume we add such a piece of code to use mock data in controller.
$scope.getPagedDataAsync = function(url, pageSize, page, searchText) {
setTimeout(function() {
var data;
/// this is new and required only for sake of plunker
if(typeof url !== 'string'){
$scope.setPagingData(url, page, pageSize);
}
Now it becomes clear that you messed up grid options and provided as source unfiltered data.
$scope.gridOptions = {
data: 'source',//should be myData
enablePaging: true,
pagingOptions: $scope.pagingOptions,
showFooter: true
};