Controls in Angular break when configuration options come from service - javascript

I have a service that will return my some config options for an ng-grid. The getGridOptions function takes the name of the controller it's used on and returns the correct set of options (only one shown here for brevity).
service for ng-grid options:
angular.module('services').service('GridOptionsService',function(){
var documents = {
data: 'myData',
enablePaging: true,
showFooter:true,
totalServerItems: 'totalServerItems',
pagingOptions: {
pageSizes: [50,100,200],
pageSize: 50,
currentPage: 1
},
filterOptions: {
filterText: '',
useExternalFilter: false
},
enableCellEdit: false,
enableColumnReordering: true,
enablePinning: false,
showGroupPanel: false,
groupsCollapsedByDefault: true,
enableColumnResize: true,
showSelectionCheckbox: true,
selectWithCheckboxOnly: true,
columnDefs: [
{field:'docId', displayName:'Document ID', cellTemplate: NgGridDomUtil.toLink('#/documents/{{row.getProperty(col.field)}}')},
{field:'docTags', displayName:'Tags'},
{field:'lastSaveDate', displayName:'Last saved'},
{field:'documentVersion', displayName:'Version', width: 120},
{field:'busDocId', displayName:'Customer Doc ID'},
{field:'markedForDelete', displayName:'Deleted', width: 120, cellTemplate: NgGridDomUtil.toCheckbox('{{row.getProperty(col.field)}}')}]
};
var gridOptionManager = {
documents: documents
}
return {
getGridOptions: function(controllerName){
return gridOptionManager[controllerName];
}
}
})
The NgGridDomUtil class just makes it easier to style things on the grid:
var NgGridDomUtil = (function(){
var toLink = function(href){
var html = '<div class="ngCellText" ng-class="col.colIndex()"><a ng-href= "'+href+'" class="ngCellLink"><span ng-cell-text>{{row.getProperty(col.field)}}</span></a></div>'
return html;
}
var toCheckbox = function(_selected){
var html = '<div class="ngCellText" ng-class="col.colIndex()"><input type="checkbox" ng-change="console.log('+"TEST"+')" ng-model="COL_FIELD" ng-input="COL_FIELD"' + (_selected ? 'selected' : '') + ' /></div>'
return html
}
return {
toLink: toLink,
toCheckbox: toCheckbox
}
})();
My problem is what when I use the GridOptionsService to retrieve the data, the data is still presented to the grid correctly, but the text filtering no longer works and the paging is broken. However, the selectedFilterOption still works.
controller:
angular.module('controllers').controller('Repository', ['$scope', 'DataContext','GridOptionsService','$http', function($scope, DataContext,GridOptionsService,$http) {
$scope.filterOptions = {
filterText: '',
useExternalFilter: false
};
$scope.totalServerItems =0;
$scope.pagingOptions ={
pageSizes: [5,10,100],
pageSize: 5,
currentPage: 1
}
//filter!
$scope.dropdownOptions = [{
name: 'Show all'
},{
name: 'Show active'
},{
name: 'Show trash'
}];
//default choice for filtering is 'show active'
$scope.selectedFilterOption = $scope.dropdownOptions[1];
//three stage bool filter
$scope.customFilter = function(data){
var tempData = [];
angular.forEach(data,function(item){
if($scope.selectedFilterOption.name === 'Show all'){
tempData.push(item);
}
else if($scope.selectedFilterOption.name ==='Show active' && !item.markedForDelete){
tempData.push(item);
}
else if($scope.selectedFilterOption.name ==='Show trash' && item.markedForDelete){
tempData.push(item);
}
});
return tempData;
}
//grabbing data
$scope.getPagedDataAsync = function(pageSize, page, searchText){
var data;
if(searchText){
var ft = searchText.toLowerCase();
DataContext.getDocuments().success(function(largeLoad){
//filter the data when searching
data = $scope.customFilter(largeLoad).filter(function(item){
return JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
})
$scope.setPagingData($scope.customFilter(data),page,pageSize);
})
}
else{
DataContext.getDocuments().success(function(largeLoad){
var testLargeLoad = $scope.customFilter(largeLoad);
//filter the data on initial page load when no search text has been entered
$scope.setPagingData(testLargeLoad,page,pageSize);
})
}
};
//paging
$scope.setPagingData = function(data, page, pageSize){
var pagedData = data.slice((page -1) * pageSize, page * pageSize);
//filter the data for paging
$scope.myData = $scope.customFilter(pagedData);
$scope.myData = pagedData;
$scope.totalServerItems = data.length;
// if(!$scope.$$phase){
// $scope.$apply();
// }
}
//watch for filter option change, set the data property of gridOptions to the newly filtered data
$scope.$watch('selectedFilterOption',function(){
var data = $scope.customFilter($scope.myData);
$scope.myData = data;
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
$scope.setPagingData($scope.myData,$scope.pagingOptions.currentPage,$scope.pagingOptions.pageSize);
})
$scope.$watch('pagingOptions',function(newVal, oldVal){
$scope.getPagedDataAsync($scope.pagingOptions.pageSize,$scope.pagingOptions.currentPage,$scope.filterOptions.filterText);
$scope.setPagingData($scope.myData,$scope.pagingOptions.currentPage,$scope.pagingOptions.pageSize);
},true)
$scope.message ="This is a message";
$scope.gridOptions = {
data: 'myData',
enablePaging: true,
showFooter:true,
totalServerItems: 'totalServerItems',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions,
enableCellEdit: true,
enableColumnReordering: true,
enablePinning: true,
showGroupPanel: true,
groupsCollapsedByDefault: true,
enableColumnResize: true
}
$scope.gridOptions = GridOptionsService.getGridOptions('documents');
//get the data on page load
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
}]);
The grid options that I have hard coded into the controller are the same as the ones returned from the service. What I don't understand is why the grid renders, the dropdown filter works, but the paging is broken, only when the options for the grid come from a service? But it works as expected if it's hard coded into the controller.
EDIT: If someone can more eloquently state my problem, feel free to edit the title.

I don't really know how the ngGrid is implemented, but a common pitfall I do know that exist in many directives, is they expect their configurations to be ready as soon as they're initialized. Meaning that instead of watching the configuration object, they assume it exists and use it directly in the link\controller functions which runs as soon as they're created.
If this is indeed the case, a quick workaround to the problem is initializing the directive only when you have the configuration object. Let's say you pass on the configuration object through the variable 'options' on your scope, you'll then write something like:
<!-- If options exists on your scope, it means you fetched it from the server -->
<div ng-if="options">
<div ng-grid ng-grid-options="options"></div>
</div>
Again, I'm not familiar with ngGrid or its usage, this is just an educated guess, take the conclusions and apply them on the correct API.

I haven't tested this, but one possible issue is that you are overwriting an object that is on the $scope. This can break the two way binding. For a quick test try
$scope.grid = {
Options: {
data: 'myData',
enablePaging: true,
showFooter:true,
totalServerItems: 'totalServerItems',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions,
enableCellEdit: true,
enableColumnReordering: true,
enablePinning: true,
showGroupPanel: true,
groupsCollapsedByDefault: true,
enableColumnResize: true
}
}
$scope.grid.Options = GridOptionsService.getGridOptions('documents');
You would need to update the grid options in the directives attribute as well of course.

The problem is that the controller functions for filtering and paging use the options defined on the controller $scope, but the ng-grid UI is not bound to those objects.
The controller methods for filtering and paging use $scope.pagingOptions as the data source. However, the ng-grid UI is bound to $scope.gridOptions.pagingOptions. When you create the $scope.gridOptions explicitly in the controller, $scope.gridOptions.pagingOptions refers to the same object as $scope.gridOptions, so making changes in the UI will change the value used in the controller functions.
However, when $scope.gridOptions is assigned using the service, the service is creating new pagingOptions, so there is no connection between $scope.gridOptions.pagingOptions and $scope.pagingOptions. Making changes in the UI will not change the values used in the controller functions.
The same is true for filterOptions.
One way to resolve the issue is
$scope.gridOptions = GridOptionsService.getGridOptions('documents');
$scope.pagingOptions = $scope.gridOptions.pagingOptions
$scope.filterOptions = $scope.gridOptions.filterOptions

Related

Does ExtJS's BufferedStore work with an OData REST API

I'm working on an application which is supported by an OData REST API. The API works fine in tools like Postman but when we try and use it with our Web App and pull the returned data (which is Json) into a Grid Panel there is an issue.
My company wants to be able to support large data sets, so we choose Ext JS based on it's BufferedStore Grid.
So the problem is we can't get it to work. The way the API is set up is based on the current OData 4 Spec. So it uses $skip and $top exactly in the same way you would use the $startParam and $limitParam within the Proxy of my Ext Store.
So for instance when I want to fetch data from my API the URL would look something like this http://127.0.0.1/odata/BEER/?$skip=0&$top=100 to fetch the first 100 rows of data,
And http://127.0.0.1/odata/BEER/?$skip=1&$top=100 to fetch the for the next 100 rows. The data is in Json and returns back with now issues outside of the BufferedStore Grid.
The Json looks like so
{"#odata.context":"http://127.0.0.1:8080/odata/$metadata#BEER_SAMPLE_LOT","value":[{"Id":17452707,"Name":"BS860-2","Barcode":"BS860-2","Sequence":2578,"Created":null,"Modified":null,"Active":true,"LikedBy":0,"FollowedBy":0,"CI_LOT_NUM":2,"CI_INTENDED_USAGE":"Turbidity Testing","CI_TRIGGER_RUNS":0},{"Id":17452798,"Name":"BS986-2","Barcode":"BS986-2","Sequence":2596,"Created":null,"Modified":null,"Active":true,"LikedBy":0,"FollowedBy":0,"CI_LOT_NUM":2,"CI_INTENDED_USAGE":"Turbidity Testing","CI_TRIGGER_RUNS":0}]}
The problem simple is that when the page loads the Grid will load the first 100 rows, but then as you scroll down the it doesn't prefetch (as the documentation and demos promise it should). It just stops at 100. Nothing we have tried will make it try and fetch the next set. How is this supposed to work?
Here is my code for the store which supports the grid.
var oDataModel = Ext.create('Ext.data.Model',{
fields: []
});
var oDataStore = Ext.create('Ext.data.BufferedStore', {
storeId: 'beerStore',
buffered: true,
autoLoad: true,
model: oDataModel,
leadingBufferZone: 100,
pageSize: 100,
proxy:{
type: 'rest',
url: http:127.0.0.1/odata/BEER
pageParam: false, //to remove param "page"
startParam: '$skip',
limitParam: '$top',
noCache: false, //to remove param "_dc"
method: 'GET',
withCredentials: true,
headers : {
"Content-Type": "text/json",
},
reader:{
type:'json',
rootProperty: 'value',
},
listeners: {
exception: function(proxy, response, operation, eOpts) {
console.log("Exception");
console.log(response);
}
}
}
});
Below is my Grid which is dynamic.
// Create Basic Ext Grid
var oDataGrid = Ext.create('Ext.grid.Panel', {
title: 'oData Entity Table',
store: oDataStore,
height: 400,
loadMask: true,
selModel: {
pruneRemoved: false
},
plugins: [{
ptype: 'bufferedrenderer',
trailingBufferZone: 10,
leadingBufferZone: 10,
scrollToLoadBuffer: 10
}],
renderTo: 'oData-grid'
});
// Add columns to the data grid
_that.buildColumns(_that._properties.property, oDataGrid);
buildColumns: function(__cols,_bGrid){
var _that = this;
// Add the row number first
_bGrid.headerCt.insert(_bGrid.columns.length - 1,{ xtype: 'rownumberer', width: 50 });
var _column = '';
for (var n=0; n<__cols.length; n++) {
//console.log(__cols[n].name);
_column = Ext.create('Ext.grid.column.Column', {
text: __cols[n].name,
width: 100,
dataIndex: __cols[n].name,
filter: true,
renderer: function(__value){
var _val = '';
if(__value instanceof Array){
for(var i = 0; i < __value.length; i++){
_val += __value[i].Barcode + "<br>";
}
} else if (__value instanceof Object){
if(!(__value instanceof Date)){
_val = __value.Barcode + "<br>";
}else{
_val = __value;
}
} else {
_val = __value;
}
return _val;
}
});
_bGrid.headerCt.insert(_bGrid.columns.length - 1, _column); //inserting the dynamic column into grid
}
}

Why is my angular paging not working?

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

Angular JS selectize error

This is driving me nuts, as I can find no real reason for it.
I have an Angular 1.3.2 project. On one view, with one controller, one factory, and 2 json files as sources. I added 2 selectize menus, both work perfectly.
I did a save as to create a new file, new controller, new factory, new json file.
The html I kept the same but adjusted the selectize to match the new data:
<input type="text" selectize="partnerSelectMenu.options" options="partnerList" ng-model="partner.selected" />
The factory is
app.factory('DatagroupsFactory', ['$http',
function($http) {
var datagroupsData;
return {
getDataGroups: function() {
if (!datagroupsData) {
datagroupsData = $http.get('scripts/data/datagroups.json').then(function(response) {
return response.data;
});
}
return datagroupsData;
}
};
}
]);
the controller is
app.controller('AddDatagroupCtrl', function($scope, $log, PartnerFactory) {
var partnerList = [];
PartnerFactory.getPartners().then(function(response) {
$scope.partnerList = response.partners;
});
$scope.partnerSelectMenu = {
options: {
valueField: 'name',
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button']
}
};
});
And a fragment of the json:
{
"partners" : [
{
"value" : "CPPRT0002706",
"name" : "Axis Promotions"
},
{
"value" : "CPPRT0005006",
"name" : "Band of Outsiders"
}
]
}
Here's the kicker: the menu works fine, as expected, and displays my data. The problem is I also get this error:
I have tried commenting out every part of this path of files. Nothing. Log debug statements show the data is correctly being passed; heck, the menu even works.
The other pages on my site with selectize menus -- with the same options -- do not throw this error. I am at a loss on how to track down.
I think you need to change:
var partnerList = [];
to
$scope.partnerList = [];
in your controller.
app.controller('AddDatagroupCtrl', function($scope, $log, PartnerFactory) {
$scope.partnerList = [];
PartnerFactory.getPartners().then(function(response) {
$scope.partnerList = response.partners;
});
$scope.partnerSelectMenu = {
options: {
valueField: 'name',
labelField: 'name',
searchField: ['name'],
plugins: ['remove_button']
}
};
});
I think before your promise returns, partnerList is undefined and your selectize is trying to get a name property from that.

Kendo Grid details causes parent grid refresh?

I can't figure out what is going on here. I'm trying to make a custom directive for grids and will use element attributes to customize a given instance. In doing so i've made two files
grid-controller.js
app.controller('gridController', ['$scope', function ($scope ) {
//Initilization code
$scope.gridOptions = {
//Setup options
};
$scope.detailOptions = function (e) {
console.log('winning');
return {
dataSource: {
transport: {
read: {
url: "/detail" + e.OrderNumber + ".json",
dataType: 'json'
}
},
error: function (e) {
console.log(e);
},
pageSize: true,
serverPaging: false,
serverFiltering: false,
serverSorting: false,
},
columns: [
{
field: "ItemCode",
label: "lblItemCode",
title: ""
}, {
field: "ItemDesc",
label: "lblItemDesc",
title: ""
}, {
field: "QuantityOrdered",
label: "lblQuantityOrdered",
title: ""
}
],
scrollable: false,
sortable: true
};
}
}]);
grid-directive.js
app.directive('grid', function () {
return {
// Restrict E for element
restrict: 'E',
// Here we setup the template for the code we want to replace our directive
template: "<div> \n\
<div kendo-grid='grid' \n\
k-options='gridOptions'\n\
k-data-source='dataSource'>\n\
</div> \n\
<script type='text/x-kendo-template'\n\
id='details'>\n\
<div kendo-grid >\n\
</div>\n\
</script>\n\
</div>",
replace: true,
scope: {
},
controller: "gridController",
link: function (scope, element, attrs) {
//Gather some attribute data and set it to the gridOptions object
if(scope.$eval(attrs.detailsGrid))
{
scope.gridOptions.detailTemplate = kendo.template($("#details").html());
scope.gridOptions.detailInit = scope.detailOptions;
}
//More customization code
scope.dataSource = new kendo.data.DataSource({
//Setup dataSource options for main grid
});
}
};
});
For sake of brevity i've excluded a lot of the extra code.
My problem is whenever I try to open the details of a row the row opens...closes...and the grid appears to refresh. It almost looks like something is crashing and the main grid is refreshing as a result.
Here is the associated plunkr with the commented portions fleshed out.
So the day after I posted the question angular-kendo released an update that addressed this issue. After updating the library and fixing up my code a bit the details grid works as expected!

next page button on ng-grid does a post

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

Categories

Resources