I have an ng-grid that I am trying to fill with some search results.
The queries work as long as the result has a different amount of rows...
If a query has the same amount of rows as the previous query... the result will not show.
// after a rest call
// we have the result
// #1 Clean gridData and local array
$scope.gridData.length = 0;
_labels.length = 0;
// #2 feed the grid
// these two functions add rows to the grid + data to the rows (and my local array)
addLabels(result.Labels);
addPhrases(result.Phrases);
// Second version attempting to call $scope.$apply
// does not work either
$scope.gridData.length = 0;
if (!$scope.$$phase) {
$scope.$apply();
}
_labels.length = 0;
addLabels(result.Labels);
addPhrases(result.Phrases);
if (!$scope.$$phase) {
$scope.$apply();
}
here is a plunker that demonstrates this:
http://plnkr.co/edit/gSFtuL?p=preview
note that if you push one more (or one less) item, the grid refreshes
It seems that ngGrid specifically watches the array.length before it passes in the options.data. Copied from here:
$scope.$parent.$watch(options.data, dataWatcher);
$scope.$parent.$watch(options.data + '.length', function() {
dataWatcher($scope.$eval(options.data));
});
I can't explain why, but it works for me if I assign an empty array in stead of the resetting the arrays length. Like this:
$scope.changeData = function() {
/* $scope.myData.length =0; -- this doesn't work */
$scope.myData = [];
$scope.myData.push({name: "aMoroni", age: 51});
$scope.myData.push({name: "bMoroni", age: 52});
$scope.myData.push({name: "cMoroni", age: 53});
$scope.myData.push({name: "dMoroni", age: 54});
$scope.myData.push({name: "eMoroni", age: 55});
};
I added this as an issue on ng-grids github site. It turned out that others had had the same problem, and that there is a solution:
(you need to get your hands dirty though and alter the ng-grid code.)
https://github.com/angular-ui/ng-grid/issues/632
Im guessing it will make it into version 3.0
Related
I'm working on a form where I need to pull the contents of a spreadsheet column like 50 times, to try to input multiple items from a list. I see that I can do this by defining a few variables and redoing a small piece of Script again and again. I want to see if anyone can help me overcome this lengthy script to make it smaller with fewer iterations. Thanks.
function updateForm(){
// call the form and connect to the drop-down items
var Form_SQ = FormApp.openById("FORM ID");
var SQ_IT01_List = Form_SQ.getItemById("ITEM 01").asListItem();
var SQ_IT02_List = Form_SQ.getItemById("ITEM 02").asListItem();
//Similarly defining upto 50 dropdown lists.
var SS01 = SpreadsheetApp.getActive();
var SQ_IT01_Names = SS01.getSheetByName("Sheet2");
var SQ_IT02_Names = SS01.getSheetByName("Sheet2");
//Similarly defining upto 50 names lists.
// Item_01 Part Number Dropdown
var SQ_IT01_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT01_Names.getMaxRows() - 1).getValues();
var SQ_IT01_Items = [];
for(var i = 0; i < SQ_IT01_Values.length; i++)
if(SQ_IT01_Values[i][0] != "")
SQ_IT01_Items[i] = SQ_IT01_Values[i][0];
SQ_IT01_List.setChoiceValues(SQ_IT01_Items);
// Item_02 Part Number Dropdown
var SQ_IT02_Values = SQ_IT01_Names.getRange(2, 1, SQ_IT02_Names.getMaxRows() - 1).getValues();
var SQ_IT02_Items = [];
for(var i = 0; i < SQ_IT02_Values.length; i++)
if(SQ_IT02_Values[i][0] != "")
SQ_IT02_Items[i] = SQ_IT02_Values[i][0];
SQ_IT02_List.setChoiceValues(SQ_IT02_Items);
//Similarly defining upto 50 lookup lists.
}
Problem
Reusing code and making use of loops. Scripting is all about efficiency (see DRY principle): make as little assignments and same-functionality coding as possible - use loops, move reusable code snippets to functions that can be called on demand, etc.
Solution
This sample makes several assumptions:
SQ_IT01_Names is different for each item (in your sample it always is Sheet2 - if this is the case, you don't have to reassign it 50 times, one variable assignment will do just fine).
You intended to do something when a value is an empty string (the sample just filters them out). As you use the [index] notation, those values in the resulting Array will be undefined (and that's not something one would want in an Array of choice values).
All items are choice items (if you need id filtering, the sample is easily expanded).
function updateForm() {
var form = FormApp.openById("FORM ID");
//access every item;
var items = form.getItems();
var ss = SpreadsheetApp.getActive();
//loop over items;
items.forEach(function(item,i){
var namesSheet = ss.getSheetByName('Sheet'+i); //assuming this is diff each time;
var namesRange = namesSheet.getRange(2,1,namesSheet.getLastRow());
var namesValues = namesRange.getValues();
//map values to first column;
namesValues = namesValues.map(function(value){
return value[0];
});
//filter out undefined (undefined and false functional equivalence);
namesValues = namesValues.filter(function(value){
return value;
});
item.asListItem().setChoiceValues(namesValues);
});
}
Notes
Please, use closures {} with loops and if statements, this way you'll be able to keep track of which statements are enclosed in it and save yourself debugging time when looping / conditioning multiple statements.
Since you only need rows that have data in them, use the getLastRow() method instead of the getMaxRows()-1 calc you have to perform in your script.
Reference
forEach() method reference;
filter() method reference;
map() method reference;
getLastRow() method reference;
I have an angularjs app using ui.grid with the infinite scrolling module. I am using whole row filtering as described in the documentation like so:
function MyController($scope){
var that = this;
var getData = function(){
//logic left out for brevity
};
var onRegisterApi = function(gridApi){
gridApi.grid.registerRowsProcessor(function (rows) {
return that.filterRowProcessor.apply(that, [rows]);
}, 200);
gridApi.infiniteScroll.on.needLoadMoreData($scope, getData);
};
this.options["onRegisterApi"] = onRegisterApi;
}
//...code excluded for brevity...
MyController.prototype.filterRowProcessor = function(renderableRows){
renderableRows.forEach(function(row) {
if (this.selectedMap[row.entity["Id"]]) {
row.visible = false;
}
});
return renderableRows;
}
The idea is to filter out rows which have an Id belonging to a specific collection; which works as designed. My problem is that when I get my first page of data the filter row processor removes enough rows from visibility that my scroll bar disappears. This in turn causes the infinite scroll api to never raise the "needLoadMoreData" event.
Is this uncharted territory, or is there a way around this? I am also open to not filtering by that mechanism if its easier to do another way.
UPDATE (01/08/2016)
I have found a work around that I don't like very much. Essentially I have a known page size and if the data coming in to the grid is less than that page size and my callback returns false for "end of query", I automatically issue a next page query. I would rather find a solution via the grid api, but for now this will work.
if(this.itemsSource.data.length < constants.PAGE_SIZE && !continuation.endOfQuery){
//call get data again
}
After thinking about it for a while I decided on the below method as my solution. I am still open to suggestions if it makes more sense to do it a different way. Rather than relying on a length of data (which only loosely translates to having a scroll bar) I decided to calculate the height of the total rows visible, compared to the viewport of the grid.
//this method get called from the callback from needsMoreData
//hasMoreData is the same boolean flag sent in to dataLoaded
var shouldRetrieveMore = function (gridApi, hasMoreData){
if (!hasMoreData) {
return false;
}
var totalCountOfRows = gridApi.grid.getVisibleRowCount();
if (totalCountOfRows === 0) {
return true;
}
var height = gridApi.grid.getViewportHeight();
var heightOfRow = gridApi.grid.getVisibleRows()[0].$$height;
return ((heightOfRow * totalCountOfRows) <= height);
}
One additional addendum to the solution could be to sum the $$heights of all the rows, but I decided against it since in my uses they are always the same height.
I am currently working on an AngularJS project in which I have to get project information, for a specific month and year, from the server and show them to the user.
First of all I'm getting a list of Project Id's (projectList), which can be variable, and then I need to get the info on those projects for a specific year and month. With this code I'm trying to get the data and to refresh the data when the last projects is successful. After the data is fetched, I use a ng-repeat to show it to the user.
$scope.getData = function(){
$scope.projectInfoList = [];
for(var index=0; index < $scope.projectList.length; index++){
projectService.getProject($scope.model.year, $scope.model.month, parseInt($scope.projectList[index]) ).success(function(data){
var listInput = { projectID : $scope.projectList[index], data : data};
$scope.projectInfoList.push(listInput);
if(index == $scope.projectList.length - 1){
$scope.$apply();
}
});
};
}
This has 2 mistakes.
It adds only data to the last index.
It doesn't refresh the data immediately when I request data for another month or year
I have looked for solutions with $q.all but I'm not sure how I would use it together with variable amount of functions of 'projectService.getProject(..)'
The anonymous callback that you give to the function success use a closure to the variable index.
But, your anonymous callback will be called asynchronously (when the call will be done). So when it will be called when index will be the last index of the array (so here $scope.projectList.length - 1).
To avoid that, you can use the following pattern:
for(var index=0; index < $scope.projectList.length; index++){
(function (index) {
projectService.getProject($scope.model.year, $scope.model.month, parseInt($scope.projectList[index]) ).success(function(data){
var listInput = { projectID : $scope.projectList[index], data : data};
$scope.projectInfoList.push(listInput);
if(index == $scope.projectList.length - 1){
$scope.$apply();
}
});
})(index)
}
Your second mistake is probably because you change the reference of the array projectInfoList in your function with $scope.projectInfoList = [];.
Take a look at this post for more details on this last problem: ng-repeat not updating on update of array
I need to dynamically change the decimal precision of a column.
I have tried different approaches and non of them worked.
I did a quick example in Plunker in which there is a button to swap data. They have different data and different columnDefs: one has 3 decimals and the other 4.
You can see there that the first rows maintain the first format.
I'm using notifyDataChange in gridApi but is not working for this kind of change:
$scope.gridApi.core.notifyDataChange( this.uiGridConstants.dataChange.ALL );
This is a problem for me because in my case I want to keep the same data but dynamically change the decimal precision of a column.
Does anybody knows how to refresh/reload that new columnDefs configuration?
I would really appretiate it!
Sadly it seems none of the usual methods to refresh/update the grid will work with this use case.
I managed to make it work though by deleting all rows and then recreating them after a $timeout.
I updated your plunker here.
The main edit is as follows:
$scope.swapData = function() {
$scope.gridOpts.data = []{
$timeout(function() {
if (!$scope.isData2) {
$scope.gridOpts.data = data2;
$scope.gridOpts.columnDefs = columnDefs2;
}
else {
$scope.gridOpts.data = data1;
$scope.gridOpts.columnDefs = columnDefs1;
}
$scope.isData2 = !$scope.isData2;
});
};
Looks like there is an answer on the ui-grid github repo.
From a contributor:
The cellFilter is joined in with the cell template when the cell
template is fetched/used. It doesn't get updated live.
With possible solution:
colDef.cellFilter = 'date:col.filterFormat'
uiGridApi.core.registerColumnsProcessor(function(columns, rows){
angular.forEach(columns, function(col){
var filterFormat = col.colDef.filterFormat;
if ( filterFormat ){ col.filterFormat = filterFormat; }
});
return rows;
}, 40);
I am getting data from service and display on view using ng-repeat .Actually I am getting event when user scroll to bottom mean when user reached to bottom I will do something.When It reached to bottom I am changing the contend of my array .I am getting the correct contend in ng-repeat array (display array) but it is not reflect on view why ? May I use $scope.apply() or $scope.digest()
Here is my code
http://plnkr.co/edit/XgOxJnPXZk4DneJonlKV?p=preview
Here I am changing the contend of my display array which is not reflect on view
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if(scope.var<scope.arrays.length)
scope.display=[];
var nextvar =++counter;
var increment=counter+1
console.log("nextvar:"+nextvar+"increment:"+increment)
scope.display=scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
}
As #Claies mentioned you should use apply(). Though the digest() would probably have worked as well.apply() calls digest() internally. He also mentioned that your variable that seems to be storing the page number gets reset to 0 each time you scroll. You should store that in a scope variable outside that handler.
I tried to fix with minimum change
http://plnkr.co/edit/izV3Dd7raviCt4j7C8wu?p=preview
.directive("scrollable", function() {
return function(scope, element, attrs) {
var container = angular.element(element);
container.bind("scroll", function(evt) {
console.log('scroll called'+container[0].scrollTop);
var counter = scope.page;
if (container[0].scrollTop <= 0) {
if (scope.var > 0)
scope.display = scope.arrays[--scope.var].concat(scope.arrays[scope.var+1]);
}
if (container[0].offsetHeight + container[0].scrollTop >= container[0].scrollHeight) {
if (scope.var < scope.arrays.length)
scope.display = [];
var nextvar = ++counter;
var increment = counter + 1
console.log("nextvar:" + nextvar + "increment:" + increment)
scope.display = scope.arrays[nextvar].concat(scope.arrays[increment]);
console.log(scope.display)
scope.page = counter;
}
scope.$apply();
});
};
})
generally I would have implemented this differently. For example by having a spinning wheel on the bottom of the list that when displayed you get the rest of data.
It is difficult to give you a full working plunker. Probably you should have multiple JSON files in the plunker, each containing the data for one page so that we can add the data to the bottom of the display list.
After you modify display array you just have to call scope.$apply() so that it runs the $digest cycle and updates the view. Also you need the initialize scope.var either in your controller or the directive and modify it conditionally.
I dont if this is what you want. I have modified the plunker take a look.
http://plnkr.co/edit/J89VDMQGIXvFnK86JUxx?p=preview