UI-grid saveState service circular logic - angularjs

Here is a summary of the problem: I set up a column sortChange() listener, which responds to sort changes by firing off a query to fetch newly sorted data. I save the grid state before the fetch, and restore the grid state after the fetch. The problem is that the restore gridState mechanism triggers the original sort listener, causing the whole process to start over again, and again, and again.
scope.sitesGrid.onRegisterApi = function(gridApi) {
scope.gridApi = gridApi;
scope.gridApi.core.on.sortChanged(scope, function () {
// load new sites on a sort change
scope.initialize();
});
};
scope.initialize = function() {
// save current grid state
scope.gridApi && (scope.gridState = scope.gridApi.saveState.save());
fetchSites().then(function (sites) {
scope.sitesGrid.data = sites
// restore current grid state, but inadvertently retrigger the 'sortChanged' listener
scope.gridApi.saveState.restore(scope,scope.gridState);
})
};
I was thinking that I could set up a click listener on each column header, instead of using a sortChange listener, however this solution seems ugly and requires going into every header cell template and making changes.

How about some kind of scope variable to track the loading of data?
scope.gridApi.core.on.sortChanged(scope, function () {
if (!scope.isLoading) {
scope.initialize();
}
});
and
fetchSites().then(function (sites) {
scope.isLoading = true;
scope.sitesGrid.data = sites;
scope.gridApi.saveState.restore(scope,scope.gridState);
scope.isLoading = false;
})
You might need to add some timeout() calls in places if there are timing issues with this. Creating a Plunker to demonstrate this would help in that case.

I think i find solution. I created restore function in my directive (u can use it where you want). I just block executing next iteration until action is finished.
function restoreState() {
if ($scope.gridState.columns !== undefined && !isRestoring) { //check is any state exists and is restored
isRestoring = true; //set flag
$scope.gridApi.saveState.restore($scope, $scope.gridState)
.then(function () {
isRestoring = false; //after execute release flag
});
}
}
function saveState() {
if (!isRestoring) {
$scope.gridState = $scope.gridApi.saveState.save();
}
}

Related

Search method to show record before data update in sql server Angularjs asp.netmvc

Angular js function updating some record. After updating record i am calling search method to show data on view.
But record does not updated before that search method call that does not get data so show null on view.
I have separate button for search on its ng-click this search method call. After some second if i click that button it shows data on view.
my code is,
vm.Update = function (value)
{
var test = value;
searchCriteria = {
From: vm.From,
To: vm.To,
Region: vm.Region,
City: vm.SelectedCity
}
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
});
vm.searchVisit(0);
}
This searchvisit call and service unable to update data in database so i do not get any record on view. When i call this searchvisit method from separate button for searching it shows record with updated data.
Hopes for your suggestions how to pause execution before calling searchvisit method or any alternative that it gets any response than move execution control to searchvisit method.
Thanks
This is due to the asynchronous nature in JS.
From your code, surveyService.UpdateVisit(searchCriteria,value) returns a promise. Thus, when vm.searchVisit(0); is called, surveyService.UpdateVisit(searchCriteria,value) has not been resolved yet, meaning updating is still in progress and have not been completed. There for vm.searchVisit(0); shows records that are not updated.
If your second function is dependent on the values of the first function call, please add it as shown below inside the success callback.
surveyService.UpdateVisit(searchCriteria,value).then(function (d) {
var Confrm = JSON.parse(d.data.data);
if (d.data.status) {
toastr.success(Updated, {
autoDismiss: false
});
}
else {
toastr.error(errorMsg);
}
//Add this here.
vm.searchVisit(0);
});

angular `$broadcast` issue - how to fix this?

In my app, I am boradcasting a event for certain point, with checking some value. it works fine But the issue is, later on whenever i am trigger the broadcast, still my conditions works, that means my condition is working all times after the trigger happend.
here is my code :
scope.$watch('ctrl.data.deviceCity', function(newcity, oldcity) {
if (!newcity) {
scope.preloadMsg = false;
return;
}
scope.$on('cfpLoadingBar:started', function() {
$timeout(function() {
if (newcity && newcity.originalObject.stateId) { //the condition not works after the first time means alwasy appends the text
console.log('each time');
$('#loading-bar-spinner').find('.spinner-icon span')
.text('Finding install sites...');
}
}, 100);
});
});
you can deregister the watcher by storing its reference in a variable and then calling it:
var myWatch = scope.$watch('ctrl.data.deviceCity', function(){
if( someCondition === true ){
myWatch(); //deregister the watcher by calling its reference
}
});
if you want to switch logic, just set some variable somewhere that dictates the control flow of the method:
var myWatch = scope.$watch('ctrl.data.deviceCity', function(){
scope.calledOnce = false;
if(!scope.calledOnce){
//... run this the first time
scope.calledOnce = true;
}
else {
// run this the second time (and every other time if you do not deregister this watch or change the variable)
// if you don't need this $watch anymore afterwards, just deregister it like so:
myWatch();
}
})

Create and destroy Angular watchers

I would like to turn off this watcher because it keeps hitting "kendoWidgetCreated" event over and over again, and causes an infinite loop where I hit the kendoGrid.refresh() .
How can I turn it off, then turn back on ?
scope.$on("kendoWidgetCreated", function (ev, widget) {
var kendoGrid = widget.element.parent().find('.k-grid').data("kendoGrid");
if (kendoGrid != undefined) {
kendoGrid.$angular_scope.compileTemplate();
kendoGrid.refresh();
}
});
I tried something like this, but couldn't get the watcher to trigger :
var kendoWidgetWatcher = scope.$watch("kendoWidgetCreated", refreshKendoWidgets);
var refreshKendoWidgets = function (ev, widget) {
// widget compile/refresh code here...
}
Advice is always appreciated...
regards,
Bob
***** UPDATE ****
My initial idea for creating an anonymous function was NOT working; however, Pankar's answer below worked for me.
Here's the updated, working version :
// setup new 'kendoWidgetWatcher' object for Kendo widget watcher, compile/refresh Kendo grids/charts
var kendoWidgetWatcher;
function registerWatcher() {
kendoWidgetWatcher = scope.$on("kendoWidgetCreated", refreshKendoWidgets);
}
function refreshKendoWidgets(ev, widget) {
var ht = widget.getSize().height;
var wt = widget.getSize().width;
var kendoGrid = widget.element.parent().find('.k-grid').data("kendoGrid");
if (kendoGrid != undefined) {
if (kendoWidgetWatcher) {
kendoWidgetWatcher(); // disable watch
}
kendoGrid.$angular_scope.compileTemplate(); // recompile the html tempate, then refresh kendo widget
kendoGrid.refresh();
registerWatcher(); // re-enable
}
}
You could easily turn off your your watcher by calling the watcher reference as function, and re-register it whenever you want it.
var kendoWidgetWatcher;
function refreshKendoWidgets(ev, widget) {
var kendoGrid = widget.element.parent().find('.k-grid').data("kendoGrid");
if (kendoGrid != undefined) {
kendoGrid.$angular_scope.compileTemplate();
kendoGrid.refresh();
// HOW TO DISABLE THE WATCH HERE ?
}
}
function registerWatcher (){
kendoWidgetWatcher = scope.$watch("kendoWidgetCreated", refreshKendoWidgets);
}
registerWatcher();
//you could call below code for re-registering the watcher
if(kendoWidgetWatcher)
kendoWidgetWatcher(); //to deregister it.
registerWatcher(); //re-register it.

Is it possible to prevent changing of router by clicking back button?

I'am building single page using BackboneJS and I need to prevent router executing on back button in a browser. To be exact I need to show confirmation custom popup with the text "Do you really want exit room? [yes|no]". So if user clicks yes then default actions should happens but if no then user should stay in the current screen.
I use Backbone.router with pushState: true. Does Backbonejs provide something like before router event to be possible prevent router handling or how could I archive it?
I'm not sure if this is still an issue, but this is how I would get around it. It may not be the best way, but could be a step in the right direction.
Backbone.History.prototype.loadUrl = function (fragment, options) {
var result = true;
if (fragment === void (0) && options === void (0) && this.confirmationDisplay !== void(0))
{
result = confirm('Are you sure you want to leave this room?');
}
var opts = options;
fragment = Backbone.history.fragment = Backbone.history.getFragment(fragment);
if (result) {
this.confirmationDisplay = true;
return _.any(Backbone.history.handlers, function (handler) {
if (handler.route.test(fragment)) {
//We just pass in the options
handler.callback(fragment, opts);
return true;
}
});
}
return this;
}
Essentially checking if we have a fragment and options, if not, we can assume the app just started, or the user clicked the back button.
Backbone router has an execute method which is called for every route change, we can return false to prevent the current transition. The code will probably look like below :
With an asynchronous popup (untested code, but should work)
Backbone.Route.extend({
execute: function(callback,args){
if(this.lastRoute === 'room'){
showPopup().done(function(){
callback & callback.apply(this,args);
}).fail(function(){
Backbone.history.navigate('room/486',{trigger:false});
});
}else{
callback && callback.apply(this,args);
}
},
showPopup: function(){
var html = "<<div><p>Do you really want to exit</p><button id='yes'>Yes</button><button id='no'>No</button></div>"
var promise = $.Deferred();
$('body').append(html);
$(document).on('click','button#yes',function(){
promise.resolve();
});
$(document).on('click','button#no',function(){
promise.reject();
});
return promise;
}
});
With synchronous confirm popup
Backbone.Route.extend({
execute: function(callback,args){
if(this.lastRoute === 'room'){
var conf = confirm("Do you really want to exit the room ?");
if(!conf){
//Change the route back to room
Backbone.history.navigate('room/486',{trigger:false});
return false;
}
};
callback && callback.apply(this,args);
}
});
References:
http://backbonejs.org/#Router-execute

CheckAll/UncheckAll issue with Subscribe ? Knockout

I been trying to do checkbox Checkall and UnCheckall using subscribe and i'm partially successful doing that but i am unable to find a fix in couple of scenarios when i am dealing with subscribe .
Using subscribe :
I am here able to checkAll uncheckAll but when i uncheck a child checkbox i.e test1 or test2 i need my parent checkbox name also to be unchecked and in next turn if i check test1 the parent checkbox should be checked i.e keeping condition both child checkboxes are checked .
For fiddle : Click Here
ViewModel :
self.selectedAllBox.subscribe(function (newValue) {
if (newValue == true) {
ko.utils.arrayForEach(self.People(), function (item) {
item.sel(true);
});
} else {
ko.utils.arrayForEach(self.People(), function (item) {
item.sel(false);
});
}
});
The same scenario can be done perfectly in easy way using computed but due some performance issues i need to use subscribe which is best way it wont fire like computed onload .
Reference : Using computed same thing is done perfectly check this Fiddle
I tried to use change event in individual checkbox binding but its a dead end till now.
Any help is appreciated .
Your subscription only applies to edits on the selectedAllBox. To do what you want, you'll need subscriptions on every Person checkbox as well, to check for the right conditions and uncheck the selectedAllBox in the right situations there.
It strikes me as odd that this would be acceptable but using computed() is not. Maybe you should reconsider that part of your answer. I would much rather compute a "isAllSelected" value based on my viewModel state, then bind the selectedAllBox to that.
I solved a similar problem in my own application a couple of years ago using manual subscriptions. Although the computed observable method is concise and easy to understand, it suffers from poor performance when there's a large number of items. Hopefully the code below speaks for itself:
function unsetCount(array, propName) {
// When an item is added to the array, set up a manual subscription
function addItem(item) {
var previousValue = !!item[propName]();
item[propName]._unsetSubscription = item[propName].subscribe(function (latestValue) {
latestValue = !!latestValue;
if (latestValue !== previousValue) {
previousValue = latestValue;
unsetCount(unsetCount() + (latestValue ? -1 : 1));
}
});
return previousValue;
}
// When an item is removed from the array, dispose the subscription
function removeItem(item) {
item[propName]._unsetSubscription.dispose();
return !!item[propName]();
}
// Initialize
var tempUnsetCount = 0;
ko.utils.arrayForEach(array(), function (item) {
if (!addItem(item)) {
tempUnsetCount++;
}
});
var unsetCount = ko.observable(tempUnsetCount);
// Subscribe to array changes
array.subscribe(function (changes) {
var tempUnsetCount = unsetCount();
ko.utils.arrayForEach(changes, function (change) {
if (change.moved === undefined) {
if (change.status === 'added') {
if (!addItem(change.value))
tempUnsetCount++;
} else {
if (!removeItem(change.value))
tempUnsetCount--;
}
}
});
unsetCount(tempUnsetCount);
}, null, 'arrayChange');
return unsetCount;
}
You'll still use a computed observable in your viewmodel for the the select-all value, but now it'll only need to check the unselected count:
self.unselectedPeopleCount = unsetCount(self.People, 'Selected');
self.SelectAll = ko.pureComputed({
read: function() {
return self.People().length && self.unselectedPeopleCount() === 0;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
}).extend({rateLimit:0});
Example: http://jsfiddle.net/mbest/dwnv81j0/
The computed approach is the right way to do this. You can improve some performance issues by using pureComputed and by using rateLimit. Both require more recent versions of Knockout than the 2.2.1 used in your example (3.2 and 3.1, respectively).
self.SelectAll = ko.pureComputed({
read: function() {
var item = ko.utils.arrayFirst(self.People(), function(item) {
return !item.Selected();
});
return item == null;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
}).extend({rateLimit:1});
http://jsfiddle.net/mbest/AneL9/98/

Resources