I've been trying to understand async, promises, etc. and I think I have a basic understanding of it, but I'm not getting the results I expect.
I have a HTML table, with the following:
<table data-bind="visible: viewPrincipal()">
viewPrincipal() is a function that should return true or false. This does work at the most basic level if viewPrincipal() just consists of return false or return true. But what I'm trying to do is call an async function to get the true or false value from there.
function viewPrincipal() {
console.log("Seeing if person is in principal group");
return IsCurrentUserMemberOfGroup("Principal Members", function (isCurrentUserInGroup) {
console.log(isCurrentUserInGroup);
return isCurrentUserInGroup;
});
}
The console.log works, and returns a true or false as I'd expect it to. But I want the parent viewPrincipal() function to return that true or false value, and all I get is "undefined".
I understand why this is happening - the IsCurrentUserMemberOfGroup() function is taking a bit of time to complete - but I don't know how to fix it. I know how to chain functions together, but when I'm trying to use something like knockout.js to determine if a table should be visible or not, I don't know how to chain.
Can anyone help?
The best way is to use an observable bool, and let your a-sync function change it's value. Let the magic of two-way-bindings do the rest.
Example:JSFIDDLE
function vm() {
this.viewPrincipal = ko.observable(false);
};
var vm = new vm();
ko.applyBindings(vm);
function fakeAsync() {
setTimeout(() => {
vm.viewPrincipal(true);
}, 1500);
}
fakeAsync();
I am a bit lost with your approach, but I'll try to help.
First, please double-think whether you really want to implement access control on the client side. Simply hiding an element if the user does not have sufficient rights is pretty dangerous, since the (possibly) sensitive content is still there in the DOM, it is still downloaded, all you do like this is not displaying it. Even a newbie hacker would find a way to display it though - if nothing else he can simply view it using the F12 tools.
Second, is that triple embedding of functions really necessary? You have an outermost function, that calls a function, which, in turn, calls the provided callback. You could clear this up by using computed observables:
function viewModel() {
var self = this;
var serverData = ko.observable(null);
this.viewPrincipal = ko.computed(function() {
var srvDataUnwrapped = serverData(); // access the inner value
if (!srvDataUnwrapped) {
return false;
}
// Do your decision logic here...
// return false by default
return false;
});
// Load the permission details from the server, this will set
// a variable that the viewPrincipal depends on, this will allow
// Knockout to use its dependency tracking magic and listen for changes.
(function() {
$.ajax(url, {
// other config
success: function (data) {
serverData(data);
}
);
})();
};
var vm = new viewModel();
and then in your view:
<table data-bind="visible: viewPrincipal">
note the lack if ()'s here, it is an observable, so Knockout will know how to use it.
If this seems overly complicated to add to your already existing code, then you could simply define an observable instead, and set the value of that inside your callback:
function viewModel() {
// other stuff ...
this.viewPrincipal = ko.observable(false);
// Call this wherever it fits your requirements, perhaps in an init function.
function checkPrincipal() {
IsCurrentUserMemberOfGroup("Principal Members", function (isCurrentUserInGroup) {
viewPrincipal(isCurrentUserInGroup);
});
};
};
With this approach, the markup would be the same as in the previous one, that is, without the parentheses:
<table data-bind="visible: viewPrincipal">
Doing it this way will simply set the inner value of an observable inside the callback you pass to IsCurrentUserMemberOfGroup, and because Knockout is able to track changes of observables, the value change will be reflected in the UI.
Hope that helps.
Related
How to stop $watch while changing the object
Here is a $watch function
$scope.$watch($scope.OneTime,function(old,new)
{
// my function
});
The above $watch function will be fire whenever my (OneTime) object value has been changed.
But I won't to watch the object on every change, I just want to fire the $watch function when I change the my object on first time only.
I also tried something and find out a function from angular.js script file But I don't know what the below function doing exactly.
You can find this function from angular.js script file
function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
var unwatch, lastValue;
return unwatch = scope.$watch(function oneTimeWatch(scope) {
return parsedExpression(scope);
}, function oneTimeListener(value, old, scope) {
lastValue = value;
if (isFunction(listener)) {
listener.apply(this, arguments);
}
if (isDefined(value)) {
scope.$$postDigest(function () {
if (isDefined(lastValue)) {
unwatch();
}
});
}
}, objectEquality);
}
But am seeing a pretty word unwatch();inside the function . So i think I need to use $unwatch for the object when end of the $watch function. But I couldn't get anything about $unwatch concept anywhere in angular document. but I can see it on angular script.
I had some idea about manually stop this $watch function by this below way
var unwatch = $scope.$watch("OneTime", function() {
//...
});
setTimeout(function() {
unwatch();
}, 1000);
But I am thinking about if angular provide to unwatch function to stop the abject watching, it would be easy to handle in my whole application. So planed to take override something in angular.js file in my application. let me know if you have any idea about override angular.js script file to create $unwatch function as same as $watch function. And also let me know angular had any$unwatch function.
I think you need one way binding over here
you can achieve this br
{{::oneTime}}
in your html page One-time expressions will stop recalculating once they are stable, which happens after the first digest
var $unwatch=$scope.$watch('onetime',function(){
unregister();
}
AngularJS does already provide such function, exactly as you mentioned above. When you create a watcher, it returns you a function that may be used to stop watching it.
From the $rootScope.Scope documentation,
$watch(watchExpression, listener, [objectEquality]);
Returns: function() Returns a deregistration function for this listener.
The only thing you need to do to unwatch your object would be calling the returned function. You could call it inside your watch function so it will be executed at the first time your watcher is invoked.
var unwatch = null;
// start watching the object
var unwatch = $scope.$watch($scope.OneTime, function(old, new)
{
// my function
if (unwatch != null) {
unwatch();
}
});
I'm needing a way to avoid useless computations;
In reactive functions, I have a reactive object, I just need one property, but if one other of this object change, all the function is recalculated:
Template.registerHelper('name', function() {
console.log("running");
return Meteor.user().profile.name;
});
(In html
<body><p>My name: {{name}}</p></body>
)
Now let change your age:
Meteor.users.update({_id:Meteor.userId()},{$set:{"profile.age":20}});
You guess what you see in your console(for the second time...)
running x2
but it should run only once because the name haven't changed
Why it's important ? In my application I have complex calculs, and user online/idle status is changing easily
The computation is rerun whenever the value of used reactive function changes. In your case, the reactive function is Meteor.user(), so whenever result of that method changes, rerun is triggered.
To limit reruns to where it's really necessary, you need to use (or create) a reactive function that will return exactly the value you want to track and nothing more. For example:
var prop = new ReactiveVar();
Template.template.onRendered(function() {
this.autorun(function() {
prop.set(Meteor.user().profile.name);
});
});
Template.template.helpers({
hlpr: function() {
console.log("RERUN!!!");
return prop.get();
},
});
the isCheckedIn function is being called when the view is initialized and the checkin function is being called when a button is clicked.
I'm not sure why but the $scope.users_events is undefined in isCheckedIn but has a value in checkIn
What am I doing wrong?
These are the two functions
$scope.isCheckedIn = function() {
console.log($scope.users_events);
//more code
}
$scope.checkIn = function() {
console.log($scope.users_events);
//more code
}
This is the function where the variable is coming from
function getCheckedInUsers(){
dataService.getCollectionForId('events', id, 'users')
.then(function(response) {
$scope.users = response.data.relatedObjects.users;
$scope.users_events = response.data.data;
});
}
getCheckedInUsers();
the isCheckedIn function is being called when the view is initialized and the checkIn function is being called when a button is clicked.
dataService.getCollectionForId('events', id, 'users') is going to return a Promise. The first function passed into then will be executed after the Promise resolves successfully.
This execution probably happens after the view has initialized, which is why checkedIn works but isCheckedIn doesn't.
To "fix" this you could make sure you re-evaluate computed expressions after retrieving data (instead of relying on the digest cycle), or more generally, any time any of the inputs have changed. I'm assuming isCheckedIn is doing other useful initialization, in which case maybe it should be called inside the then function at the end.
Normally you would just use a directive or view binding like {{ $scopeExpression }} to display a value on the $scope that will stay up to date as it changes... If that expression is expensive in time to compute (or needs to make a call to a service) then you need to do something else to keep the digest cycle quick and application responsive.
For example, here's a general solution to the general problem - if you had a set of computations that needed to be recomputed from a variety of input sources anytime any (1) of the inputs changed, assuming the inputs were relatively small (like numbers or short strings) and not arrays (you just have to be careful about how you define "equality") and you could do this with just (1) watch:
$scope.$watch(function () {
return {
input1: expression1,
input2: expression2,
// ...
}
}, function (value) {
// This function will only execute if at least one of the inputs changed
var computations = [];
computations.push(computationA(value.input1, value.input2 /* ,.. */));
computations.push(computationB(value.input1, value.input2 /* ,.. */));
computations.push(computationC(value.input1, value.input2 /* ,.. */));
// ...
$q.all(promises).then(function (computations) {
$scope.computationAResult = computations[0];
$scope.computationBResult = computations[1];
$scope.computationCResult = computations[2];
// Do synchronous work here on inputs so that all $scope variables get updated at once.
});
}, true /* Deep watch */);
This way you don't do lots of expensive computations on each digest cycle.
EDIT:
Everything is working as I expected. It was just an error calling the template method. I mistyped a () so I was trying template.method instead of template().method;
Anyway, if somebody would like to explain me if this is a valid design pattern or if I should go in a different way I will be definitively very grateful.
I read about the module pattern and I'm trying to implement it in some of my projects. The problem is that, in my opinion, I'm twisting it too much.
I'm inspired by the google apps script style where many objects returns other objects with methods and so on and they pass arguments.
something like
object.method(var).otherMethod();
What I want to achieve is a method that receives a parameter, sets an internal variable to that parameter and then returns an object with methods that uses that variable. Here is a minified version of the code that does not work:
var H_UI =(function (window) {
var selectedTemplate,
compileTemplate = function(){},
parseTemplateFields = function(){};
//template subModule. Collect: collects the template fields and returns a JSON representation.
var template = function(templateString){
if(templateString) selectedTemplate = templateString;
return {
getHtml:function(){ return compileTemplate( parseTemplateFields( selectedTemplate ) ) } ,
collect:function(){
.. operating over selectedTemplate ...
return JSON.stringify(result)}
} };
return {
template:template
};
})(window);
If I remove the line :
if(templateString) selectedTemplate = templateString;
and replace selectedTemplate with the parameter templateString in the methods of the returned object it works as expected. I know that I cant create a set() method in the returned object and use it like this
H_UI.template().set(var)
But I find it ugly. Anyway I think that I'm messing things up.
What is the best way to construct this?
If you want H_UI.template() creates a new object every time you call template() on it, your solution does not work. Because the variable selectedTemplate is created only once when the immediate function is called.
However if your intent is this your solution works fine. (variable selectedTemplate is shared for all calls to template()).
But if you want to every call to template creates a new object. Please tell me to write my idea
Is this a valid design pattern or if I should go in a different way
Yes, enabling chaining is definitely a valid design pattern.
However, if your template() method returns a new object, that object and its methods should only depend on itself (including the local variables and parameters of the template call), but not on anything else like the parent object that template was called on.
So either remove that "global" selectedTemplate thing:
var H_UI = (function () {
function compileTemplate(){}
function parseTemplateFields(){}
// make a template
function template(templateString) {
return {
getHtml: function(){
return compileTemplate(parseTemplateFields(templateString));
},
collect: function(){
// .. operating over templateString ...
return JSON.stringify(result)
}
}
}
return {template:template};
})();
or make only one module with with a global selectedTemplate, a setter for it, and global methods:
var H_UI = (function () {
var selectedTemplate;
function compileTemplate(){}
function parseTemplateFields(){}
return {
template: function(templateString){
if (templateString)
selectedTemplate = templateString;
return this; // for chaining
},
getHtml: function(){
return compileTemplate(parseTemplateFields(selectedTemplate));
},
collect: function(){
// .. operating over selectedTemplate ...
return JSON.stringify(result)}
}
};
})();
The difference is striking when we make two templates with that method:
var templ1 = H_UI.template("a"),
templ2 = H_UI.template("b");
What would you expect them to do? In a functional design, templ1 must not use "b". With the first snippet we have this, and templ1 != templ2. However, if .template() is a mere setter, and every call affects the whole instance (like in the second snippet), we have templ1 == H_UI and templ2 == H_UI.
I have this viewmodel, on my web I have a dropdown that updates the sortedallemployees option. It works fine except my table is empty initially. Once I sort the first time I get data. Seems like when the vm is created it doesn't wait for allemployees to be populated.
var vm = {
activate: activate,
allemployees: allemployees,
sortedallemployees:ko.computed( {
return allemployees.sort(function(f,s) {
var ID = SelectedOptionID();
var name = options[ ID - 1].OptionText;
if (f[name] == s[name]) {
return f[name] > s[name] ? 1 : f[name] < s[name] ? -1 : 0;
}
return f[name] > s[name] ? 1 : -1;
});
}
Without the rest of your code, its difficult to tell exactly how this will behave. That being said, you are doing several very odd things that I would recommend you avoid.
First, defining all but the simplest viewmodels as object literals will cause you pain. Anything with a function or a computed will almost certainly behave oddly, or more likely not at all, when defined this way.
I would recommend using a constructor function for your viewmodels.
var Viewmodel = function(activate, allEmployees) {
var self = this;
self.activate = activate;
self.allEmployees = ko.observableArray(allEmployees);
self.sortedEmployees = ko.computed(function() {
return self.allEmployees().sort(function(f,s) {
//your sort function
});
});
};
var vm = new Viewmodel(activate, allemployees);
This method has several advantages. First, it is reusable. Second, you can reference its properties properly during construction, such as during the computed definition. It is necessary for a computed to reference at least one observable property during definition for it to be reactive.
Your next problem is that your computed definition is not a function, but an object. It isn't even a legal object, it has a return in it. This code shouldn't even compile. This is just wrong. The Knockout Documentation is clear on this point: computed's are defined with a function.
Your last problem is that your sort function is referencing things outside the viewmodel: SelectedOptionID(). This won't necessarily stop it from working, but its generally bad practice.