I find that in my application I have the following pattern repeated a lot (see below code).
I have to call BindMyEvents(true) for the first load, and then BindMyEvents(false) for subsequent data retrieval.
It is lazily loaded in, so I don't want the data serialised into the HTML source. Is there a better way than having to pass in a boolean flag into my Bind() method? Is there a standard pattern to achieving this with knockout?
I was thinking should I just set viewAlertsModel.alerts = null inside the view model definition, then let the Bind function check this. If set to null then call the mapping method followed by the applyBindings()?
function BindMyEvents(initialMap) {
// get alerts, map them to UI, then run colorbox on each alert
$.getJSON("/Calendar/MyEvents/", {},
function (data) {
if ( initialMap ) {
// set-up mapping
viewAlertsModel.alerts = ko.mapping.fromJS(data);
ko.applyBindings(viewAlertsModel,$("#alertedEventsContainer")[0]);
} else {
// update
ko.mapping.fromJS(data, viewAlertsModel.alerts);
}
});
}
I would re-arrange your code for a different flow.
first - define you data once.
viewAlertsModel.alerts = ko.observable();
Second, bind your data
ko.applyBindings(viewAlertsModel,$("#alertedEventsContainer")[0]);
Third, now work with your data
$.getJSON("/Calendar/MyEvents/", {},
function (data) {
ko.mapping.fromJS(data, viewAlertsModel.alerts);
});
Steps one and two can be done during an initialization phase. The key here is first define viewAlertsModel.alerts as an observable.
Step three is your run-time code. Now, your initialization code is completely separate from your run-time code. This the more the normal knockout style.
edit
With regards to your comments about using ko-mapping, I use the following code
var tempVar = ko.mapping.fromJS(data);
viewAlertsModel.alerts(tempVar); // !important - do not use = operator.
This is the convention that most people use. Your use of ko-mapping is normally used for specialized situations.
Related
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.
I want to update an Angular scope with data returned by some jQuery ajax call. The reason why I want to call the Ajax from outside Angular is that a) I want the call to return as fast as possible, so it should start even before document.ready b) there are multiple calls that initialize a complex model for a multiple-page web app; the calls have dependencies among themselves, and I don't want to duplicate any logic in multiple Angular controllers.
This is some code from the controller. Note that the code is somewhat simplified to fit here.
$scope.character = {};
$scope.attributeArray = [];
$scope.skillArray = [];
The reasoning for this is that a character's attributes and skills come as objects, but I display them using ng-repeat, so I need them as arrays.
$scope.$watch('character',function(){
$scope.attributeArray = getAttributeArray($scope.character);
$scope.skillArray = getSkillArray($scope.character);
});
In theory, when $scope.character changes, this piece of code updates the two arrays.
Now comes the hard part. I've tried updating $scope.character in two ways:
characterRequestNotifier.done(function() { // this is a jQuery deferred object
$scope.$apply(function(){ // otherwise it's happening outside the Angular world
$scope.character = CharacterRepository[characterId]; // initialized in the jquery ajax call's return function
});
});
This sometimes causes $digest is already in progress error. The second version uses a service I've written:
repository.getCharacterById($routeParams.characterId, function(character){
$scope.character = character;
});
, where
.factory('repository', function(){
return {
getCharacterById : function(characterId, successFunction){
characterRequestNotifier.done(function(){
successFunction( CharacterRepository[characterId] );
});
}
};
});
This doesn't always trigger the $watch.
So finally, the question is: how can I accomplish this task (without random errors that I can't identify the source of)? Is there something fundamentally wrong with my approaches?
Edit:
Try this jsfiddle here:
http://jsfiddle.net/cKPMy/3/
This is a simplified version of my code. Interestingly, it NEVER triggers the $watch when the deferred is resolved.
It is possible to check whether or not it is safe to call $apply by checking $scope.$$phase. If it returns something truthy--e.g. '$apply' or '$digest'--wrapping your code in the $apply call will result in that error message.
Personally I would go with your second approach, but use the $q service--AngularJS's promise implementation.
.factory('repository', function ($q) {
return {
getCharacterById : function (characterId) {
var deferred = $q.defer();
characterRequestNotifier.done(function () {
deferred.resolve(CharacterRepository[characterId]);
});
return deferred.promise;
}
};
});
Since AngularJS has native support for this promise implementation it means you can change your code to:
$scope.character = repository.getCharacterById(characterId);
When the AJAX call is done, the promise is resolved and AngularJS will automatically take care of the bindings, trigger the $watch etc.
Edit after fiddle was added
Since the jQuery promise is used inside the service, Angular has no way of knowing when that promise is resolved. To fix it you need to wrap the resolve in an $apply call. Updated fiddle. This solves the fiddle, I hope it solves your real problem too.
I have special implementation for my grid.
For this I wrote some code in onSelectRow and loadComplete methods of jqGrid.
In onSelectRow I need to update a global array and in loadComplete method I have to access the global array and need to some manupulation in jqGrid.
Till then I am okay. I've already done this.
Now I want to extend these two method in such a way that is implementation will be generic (other grid can use this without writing any code).
For that I thought of below steps.
I want to add a new js (e.g: jquery.jqGrid.additional.js) in my html with jqGrid.js
I want to assign my global variable by the data array of jqGrid
In this js I want to check the value of multiselect of the grid
If the value is true, then I want to extend onSelectRow, loadComplete methods in such way that jqGrid execute both my methods and the code written in onSelectRow, loadComplete methods also.
For example I have preLoadComplete and postLoadComplete which need to be executed just before and after loadComplete method execution. Similarly this hold true for onSelectRow method also.
I wrote below code in jquery.jqGrid.additional.js and then didn't get the alert (1), alert (2) after jqGrid load.
It only execute the code written in loadComplete method of jqGrid.
var oldLoadComplete = $.fn.jqGrid.loadComplete;
$.jqGrid.extend({
loadComplete: function (){
var ret;
// do someting before
alert(1);
ret = oldLoadComplete.call (this);
// do something after
alert(3);
return ret; // return original or modified results
}
});
I tried a lot with this and spent many hours.
You are trying to extend $.jqGrid, not $.fn.jqGrid. In the first line that would mean that $.jqGrid doesn't even exist and that all elements would not have access to your modified .jqGrid plugin but still to the default.
Though I tried to extend $.fn with a "new" append method did not work. You might have to overwrite those methods explicitly. I could imagine that jQuery has some safety routines for itself and plugins built in in the $.extend method.
var oldLoadComplete = $.fn.jqGrid.loadComplete;
$.fn.jqGrid.loadComplete = function( ) {
alert('pre');
var ret = oldLoadComplete.apply(this, arguments); // In case any arguments will be provided in future.
alert('post');
return ret;
};
Edit:
Forget about all that. It is irrelevant. After looking into jqGrid's source code on GitHub a little, it is obvious that jqGrid does not even use something like $.fn.jqGrid.loadComplete. That is something you made up yourself. Instead, it creates another object which provides a list of possible properties and defaults which then is overwritten by custom defaults and finally by the function parameter you pass in with your call to jqGrid.
I found out that jqGrid triggers two events jqGridLoadComplete and jqGridAfterLoadComplete on your set of matched elements (i.e. this) before and after the call to the loadComplete callback respectively.
So basically, you cannot use your attempt. Instead, the only solution that comes to my mind is to overwrite the jqGrid "constructor" (i.e. $.fn.jqGrid) using the same method and apply listeners to this like so:
var oldJqGrid = $.fn.jqGrid;
$.fn.jqGrid = function( ) {
this.on('jqGridLoadComplete', function( jqEvt ) {
alert('Pre');
})
.on('jqGridAfterLoadComplete', function( jqEvt ) {
alert('Post');
});
return oldJqGrid.apply(this, arguments);
};
About your multiselect... I don't know what you're talking about. I have only very limited time myself and have to deny further assist for now.
Sincerely.
The situation was that I wanted to create an instance of a helper class, but that helper class required initialisation through external scripts, so it was inherently asynchronous. With
var obj = new myObj();
clearly an call to
obj.myMethod();
would yield undefined, as obj would either be empty or undefined until its methods and params were loaded by the external script.
Yes, one could restructure things to have a callback pattern and work with the new object within that, but it gets cumbersome and awkward when working with a large and varied API with many dynamic objects as I've been working with.
My question has been, is there any possible way to cleverly get around this?
I imagine the academically trained programmers out there have a name for this sort of approach, but I put it here in case it's not better written somewhere.
What I've done is modify my loader class to use a placeholder+queue system to instantly return workable objects.
Here are the components. Sorry that there are jQuery bits mixed in, you can easily make this a pure-JS script but I've got it loaded anyway and I'm lazy.
'Client' makes this request, where 'caller' is my handler class:
var obj = caller.use('myObj',args);
In Caller, we have
Caller.prototype.use = function(objname,args) {
var _this = this;
var methods = ['method1','method2'];
var id = someRandomString();
this.myASyncLoader(objname,function(){
var q = [];
if (_this.objs[id].loadqueue) {
q = _this.objs[id].loadqueue;
}
_this.objs[id] = new myRemotelyLoadedClass(args);
//realise all our placeholder stuff is now gone, we kept the queue in 'q'
_this.objs[id].isloaded = true;
//once again, the jquery is unnecessary, sorry
$.each(q,function(a,b){
_this.objs[id][b['f']](b['a']);
});
});
_this.objs[id] = _this.createPlaceholderObj(methods,id);
return _this.objs[id];
}
This function basically initiates the loader function, and when that's done loads a new instance of the desired class. But in the meantime it immediately returns something, a placeholder object that we're going to load with all of our remotely loaded object's methods. In this example we have to explicitly declare them in an array which is a bit cumbersome but liveable, though I'm sure you can think of a better way to do it for your own purposes.
You see we're keeping both the temporary object and future object in a class-global array 'objs', associated with a random key.
Here's the createPlaceholderObj method:
Caller.prototype.createPlaceholderObj = function(methods,id) {
var _this = this;
var n = {};
n.tempid = id;
n.isloaded = false;
$.each(methods,function(a,methodCalled){
n[methodCalled] = function(){
_this.queueCall(id,methodCalled,arguments);
}
});
return n;
}
Here we're just loading up the new obj with the required methods, also storing the ID, which is important. We assign to the new methods a third function, queueCall, to which we pass the method called and any arguments it was sent with. Here's that method:
Caller.prototype.queueCall = function(id,methodName,args) {
if (this.objs[id].isloaded == true) {
this.objs[id][methodName](args);
} else {
if (this.objs[id].loadqueue) {
this.objs[id].loadqueue.push({'f':methodName,'a':args});
} else {
var arr = [{'f':methodName,'a':args}];
this.objs[id].loadqueue = arr;
}
}
}
This method will be called each time the client script is calling a method of our new object instance, whether its logic has actually been loaded or not. The IF statement here checks which is the case (isloaded is set to true in the caller method as soon as the async function is done). If the object is not loaded, the methodName and arguments are added to a queue array as a property of our placeholder. If it is loaded, then we can simply execute the method.
Back in the caller method, that last unexplained bit is where we check to see if there is a queue, and if there is, loop through it and execute the stored method names and arguments.
And that's it! Now I can do:
var obj = caller.use('myObj',args);
obj.someMethod('cool');
obj.anotherMethod('beans');
and while there might be a slight delay before those methods actually get executed, they'll run without complaint!
Not too short a solution, but if you're working on a big project you can just put this in one place and it will pay many dividends.
I'm hoping for some follow-ups to this question. I wonder, for example, how some of you would do this using a deferred-promise pattern? Or if there are any other ways? Or if anyone knows what this technique is called? Input from JS whizzes much appreciated.
I have a case i hadn't formerly, and really can't figure it out, what is the problem. :(
I tried to make a controller class for my old, unsettled js code, tried to make it a bit more object oriented (aye, fail for me, but practicing also is the mother of knowledge).
So, here is my code:
A function calls Main function, which handle variables and pops a form, like:
function Main(com, multiple, grid) {
if (CheckTableFunctions(arguments) == true)
{
var partner = Partner.Factory(com);
switch(com)
{
case "CreatePartners":
...
break;
case "GetPartners":
$e = ShowModal(); // <- this makes a form visible
Communicate(com, partner, function(data) <- ajax req. with callback
{
... // <- manipulate data, fill form, etc.
});
Main("EditPartners", multiple, grid); // <- calling Main Editpartners case
break;
In fact, the GetPartners case is before the EditPartners, but it is just for fill a form. The Edit, and the event binding goes to editpartners, as seems below:
case "EditPartners":
$e = GetModal(); // <- THE ERROR
$e.find("a.submit").click(function(e){
partner.fx_data.partner.data = getFormData($e)
Communicate(com, partner, function(data) // <- return the modified values by ajax
{
CloseModal();
});
});
break;
So, when i run the fn GetModal it returns with an empty object, but the function works correctly GetModal = fn(){$e=$(".poppedModal");return $e;} after the Main(EditP) runned. I think it's more logical or methodical error than anything else. And to tell the true, i called it with callback, which means i used ShowModal(callback) and when it was ready, i called Main(EditP), but did not work also.
Edit:
Sorry, i forgot the main problem (what i think is the main problem). So i don't have the form exactly i just have a html prototype of it. Prototype, because i always clone it when it needy.
So here is the showmodal fn and i think this makes the modal unreachable:
function ShowModal()
{
$container = $('#myModal');
$clone = $container.clone();
$clone.removeAttr("id").addClass("clonedModal");
$clone.modal('show');
return $clone;
}
function ShowModal()
{
$container = $('#partnerModal');
document.getElementById('partnerForm').reset();
$container.modal('show');
return $container;
}
Thanks for your help.
Répás
Several points:
Members within javascript functions are only localised if declared with var. Otherwise they are 'outer' members up to and including the global name space. Several of your vars need to be localised. Undeclared vars can result in nasty bugs.
.clone() will indeed create a clone of an DOM fragment but does not automatically inert it back into the DOM. You need to do that with jQuery instructions such as .append(), .prepend(), .before(), .after(). I strongly suspect that a large part of your problem is trying to do .modal() on an uninserted clone.
It's not clear why the form needs to be cloned each time it is used. This sort of programming is likely to cause memory leaks. It would be cleaner and more normal to reuse a single form. It's not hard to reset a form to clear out previously entered/selected values.
EDIT:
ShowModal with properly localised variable :
function ShowModal() {
var $container = $('#partnerModal');
document.getElementById('partnerForm').reset();
$container.modal('show');
return $container;
}
In fact, if .modal() is written to be chainable, as it should be, then ShowModal() can be simplified to avoid creating any named members, local or otherwise :
function ShowModal() {
$('#partnerForm').get(0).reset();
return $('#partnerModal').modal('show');
}
Also, please note that, by convention in javascript programming, only constructor functions should be named with an initial Capital. Constructor functions are those designed to be called with new Fn(). (It's not important here but there has been much discussion on the subject of constructors and some choose to write their code to avoid the use of new).