Automapping ajax data during ajax put or post in knockout - javascript

During ajax get request, we can use ko.mapping.fromJS to get data from server and do automapping. Also, we can use ko.mapping.toJS to post or put ajax data to server in knockout.
However, the assumption is that every value in ko.mapping.toJS will be pass back in ajax call. We could use delete or ignore to remove the property that should not pass back to server.
Recently, I have came to a problem as follow. How could I pass the data I want without explicitly assign or ignore the data one by one as it is too cumbersome. I am thinking that restructuring the view model may do the job but does not know how to start.
function MyViewModel() {
var self = this;
// these data should not be pass in ajax call
self.data1 = ko.observable();
self.data2 = ko.observable();
self.data3 = ko.observable();
...
self.data50 = ko.observable();
// these data should not be pass in ajax call
self.noData1 = ko.observable();
...
self.noData10 = ko.observable();
// these should not be pass in ajax call
self.function1 = function() { }
self.function2 = function() { }
self.function3 = function() {
$.ajax({
..
type: 'POST',
data: { ko.mapping.toJS(self) },
success: {}
}
...
self.function50 = function() {}
};
ko.applyBindings(new MyViewModel());

The way I did it was sort of like #super cool's, except I had DTO's for to/from server that were JavaScript objects, but the properties still matched my model.
//use this for server interaction
var personFromDto = function(Person) {
this.Name = Person.Name;
this.Phone = Person.Phone;
}
var personToDto = function(Person) {
this.Name = Person.Name();
this.Phone = Person.Phone();
}
and you can always map that to your observable model representation as well.
var Person = function(Person) {
this.Name = ko.observable(Person.Name);
this.Phone = ko.observable(Person.Phone);
}
Just one of many ways I'm sure you could do it.

well there is a technique , I call it grouping the required set without disturbing the 2 way binding and other dependencies .
viewModel:
var ViewModel = function (first, last, age) {
this.firstName = ko.observable(first);
this.include = { // this set you can pass in you ajax call & skip remaining .
lastName: ko.observable(last),
age: ko.observable(age)
}
};
ko.applyBindings(new ViewModel("Planet", "Earth", 25));
working sample here with preview showing everything intact .
PS: IMHO its not a wise way to alter/rebuild our viewModel (in complex applications maintainability can be at stake)

Related

Merging history state object with constructor using knockout merge

i'm having issues with merging my history state object with a constructor that i have, that gets saved in the same history state for later use.
Plugin example page: https://rawgit.com/grofit/knockout.merge/master/example.html
Using the example that is shown in knockout merge plugin page that uses a constructor like my own i've built my code but unfortunately since i'm relatively new to knockout i ran into issues.
This is the a piece of code shown inside knockout merge's example
function Person()
{
this.Firstname = ko.observable();
this.Surname = ko.observable();
}
function ViewModel()
{
this.SimpleExampleModel = new Person();
this.MergeSimpleExample = function() {
var personJson = { Firstname: "James", Surname: "Bond" };
ko.merge.fromJS(this.SimpleExampleModel, personJson);
};
};
ko.applyBindings(new ViewModel());
Now my code:
(The object that is pushed to the history is the constuctor's observables as an object)
Constructor:
var searchTerm = function () {
this.MinPrice = ko.observable();
};
lbx.vm = {
term: new searchTerm(),
injectHistory: function () {
// ko.merge.fromJS(this.term, history.state); Doesn't work
// var json = ko.toJSON(history.state) - Doesn't work
//var json = JSON.Parse(history.state) - Doesn't work
//var json = { MinPrice: 222 }; Works
var json = { "MinPrice": 222 }; // Works
ko.merge.fromJS(this.term, json);
console.log("injected");
}
};
As you can see according to my testing, whenever i try to turn my JS object into json it doesn't work, but it does if i build the json manually.
Fiddle with my problem: https://jsfiddle.net/Badzpeed/05zdLgxh/1/
As you will see in the fiddle when i popstate, nothing happens, the observable value is always the same and it doesn't throw any error.
Any help would be appreciated !
Thank you in Advance
Finally fixed my issue, turns out that i forgot to add the constructor to the merge and i also had a double call to the function that was passing the object to my history state, making two of them.
Fixed the issue by changing those two things.
Thank you for your time !

How to handle empty and full objects in a Knockout ViewModel

I can't seem to find the answers I am looking for on stack. Maybe my scenario is just wrong but I'm going to ask the question anyway. I have a form which serves two purposes... Create new user and View User. Both have the same view model User. So let's look at my viewmodel:
var UserModel = function() {
var self = this;
self.UserId = ko.observable();
self.FirstName = ko.observable();
self.LastName = ko.observable();
self.FirstName.extend({required: true});
return self;
}
So my bindings are configured at a higher level, I have a viewmodel with a property called User. this property called User is bound to my form. For the simple scenario of creating a new user I would call:
this.User(new UserModel());
Note: this.User is observable, hence the brackets.
this will successfully create my user model with a blank form and all validation works.
Ok so scenario two! I get an full object from the API, how should I map this. If I use:
this.User(ko.mapping.fromJS(data, {}, UserModel));
the validation doesn't work. I have seen the mapping options which include the create method, but how do I use this without losing the functionality at the top?!
As a side note to make it a little more complicated:
User.ConfirmEmail exists in my viewmodel but not on the server! So that is my next hurdle. If I use the create mapping I know I can easily add it.
My Current Working Example
By using lots of constructor parameters I can accomplish both:
var UserModel = function(id, fname, lname) {
var self = this;
self.UserId = ko.observable(id);
self.FirstName = ko.observable(fname);
self.LastName = ko.observable(lname);
self.FirstName.extend({required: true});
return self;
}
If I now use, new UserModel() it still sets the properties up and I can also pass through the values. It just looks bad :(
Separate the creation of the user from the adding of the validation rule:
function userFactory(user) {
if (typeof user === 'undefined') {
user = new User();
}
user.firstName.extend({required: true});
}
Then you can do this:
var newUser = userFactory(ko.mapping.fromJS(data, {}, new UserModel()));
this.user(newUser);
... and keep the Validation.

JavaScript Call or Apply using Knockout ViewModelBase

I have this base view model:
var baseViewModel = function () {
var self = this;
// <!----- AJAX SAVING ------!> \\
self.saving = ko.observable();
// <!----- SEARCHING ------!> \\
self.fields = ko.observableArray();
self.selectedField = ko.observable();
self.searchTerm = ko.observable().extend({ throttle: 150 });
}
And I inherit it using this:
var viewModel = function () {
baseViewModel.call(this);
var self = this;
//stufff
}
viewModel.prototype = new baseViewModel();
And it works perfectly. Quite pleased with it.
Now, I want to setup the self.fields property with some initial data, that I want to send through the line baseViewModel.call(this) and I'm not sure whether to do this:
var viewModel = function () {
baseViewModel.call(this, new userModel()); // just a function object
var self = this;
}
OR:
var viewModel = function () {
baseViewModel.apply(this, new userModel()); // just a function object
var self = this;
}
So that the baseViewModel will do this:
var baseViewModel = function (data) {
var self = this;
// <!----- AJAX SAVING ------!> \\
self.saving = ko.observable();
// <!----- SEARCHING ------!> \\
self.fields = ko.observableArray().getKeys(data); // viewModel parameter passed here
self.selectedField = ko.observable();
self.searchTerm = ko.observable().extend({ throttle: 150 });
}
I have read this Difference between call and apply still not sure where to go and I have read the official documentation.
EDIT
I have just tried call because as I understand it the only difference is either putting in a bunch or args (with call) or putting in an array of args (with apply)
Its worked with call so far, just wondering if there are going to be any caveats with choosing this method?
Unless there are any caveats, the only difference is whether the args come as and array or separate objects
Call Link
Apply Link
with call you do baseViewModel.call(this [, arg1, arg2, .... argn])
with apply you do baseViewModel.apply(this [, arg_array[] ])

Difference between knockout View Models declared as object literals vs functions

In knockout js I see View Models declared as either:
var viewModel = {
firstname: ko.observable("Bob")
};
ko.applyBindings(viewModel );
or:
var viewModel = function() {
this.firstname= ko.observable("Bob");
};
ko.applyBindings(new viewModel ());
What's the difference between the two, if any?
I did find this discussion on the knockoutjs google group but it didn't really give me a satisfactory answer.
I can see a reason if I wanted to initialise the model with some data, for example:
var viewModel = function(person) {
this.firstname= ko.observable(person.firstname);
};
var person = ... ;
ko.applyBindings(new viewModel(person));
But if I'm not doing that does it matter which style I choose?
There are a couple of advantages to using a function to define your view model.
The main advantage is that you have immediate access to a value of this that equals the instance being created. This means that you can do:
var ViewModel = function(first, last) {
this.first = ko.observable(first);
this.last = ko.observable(last);
this.full = ko.computed(function() {
return this.first() + " " + this.last();
}, this);
};
So, your computed observable can be bound to the appropriate value of this, even if called from a different scope.
With an object literal, you would have to do:
var viewModel = {
first: ko.observable("Bob"),
last: ko.observable("Smith"),
};
viewModel.full = ko.computed(function() {
return this.first() + " " + this.last();
}, viewModel);
In that case, you could use viewModel directly in the computed observable, but it does get evaluated immediate (by default) so you could not define it within the object literal, as viewModel is not defined until after the object literal closed. Many people don't like that the creation of your view model is not encapsulated into one call.
Another pattern that you can use to ensure that this is always appropriate is to set a variable in the function equal to the appropriate value of this and use it instead. This would be like:
var ViewModel = function() {
var self = this;
this.items = ko.observableArray();
this.removeItem = function(item) {
self.items.remove(item);
}
};
Now, if you are in the scope of an individual item and call $root.removeItem, the value of this will actually be the data being bound at that level (which would be the item). By using self in this case, you can ensure that it is being removed from the overall view model.
Another option is using bind, which is supported by modern browsers and added by KO, if it is not supported. In that case, it would look like:
var ViewModel = function() {
this.items = ko.observableArray();
this.removeItem = function(item) {
this.items.remove(item);
}.bind(this);
};
There is much more that could be said on this topic and many patterns that you could explore (like module pattern and revealing module pattern), but basically using a function gives you more flexibility and control over how the object gets created and the ability to reference variables that are private to the instance.
I use a different method, though similar:
var viewModel = (function () {
var obj = {};
obj.myVariable = ko.observable();
obj.myComputed = ko.computed(function () { return "hello" + obj.myVariable() });
ko.applyBindings(obj);
return obj;
})();
Couple of reasons:
Not using this, which can confusion when used within ko.computeds etc
My viewModel is a singleton, I don't need to create multiple instances (i.e. new viewModel())

Assigning scope amongst jQuery.getJSON and a JS.Class

I'm trying to assign some JSON data to a property of a JS.Class instance.
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, function(data) {
Assign(data);
});
function Assign(data) { this.content = data; };
}
});
var m = new MyClass("uuid_goes_here");
m.write();
The JSON is received asynchronously, which is why there's a function call within the $.getJSON callback.
The problem I have now is that the this.content within the Assign function is not within the scope of the instance method named write. So whereas this.uuid returns correctly, this.content remains undefined (as you would expect).
Any ideas on how to correct this? I've tried using a global variable as a workaround but the async call doesn't allow for that (plus it's a crappy solution).
Some points to note, in case they matter: I have to use JSONP, so the "?callback=?" has to stay, and I'd like to keep it async.
I would usually go for either czarchaic's version, or replace Accept with a bound method from the object. What you have to bear in mind is that calling Accept() like that (as a function call rather than a method call) will bind this to the global object, i.e. window. I'd try this:
var MyClass = new JS.Class({
initialize: function(uuid) {
this.uuid = uuid;
},
write: function() {
$.getJSON(url+"?callback=?", {}, this.method('setContent'));
},
setContent: function(data) {
this.content = data;
}
});
See http://jsclass.jcoglan.com/binding.html for more info.
You should cache the current instance in the write method and update it after ajax.
write: function() {
var self=this;
$.getJSON(url+"?callback=?", {}, function(data) {
self.data=data;
});
}

Categories

Resources