How to handle empty and full objects in a Knockout ViewModel - javascript

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.

Related

Knockout JS - How to initialize a complex object as observable?

I have a ViewModel and have an observable property that will have a complex object after an edit link is clicked. This is a basic example of managing a set of Groups. User can click on the 'edit' link and I want to capture that Group in SelectedGroup property.
But I'm not sure how should I initialize the SelectedGroup and make every peoperty in this object as observable to begin with.
function ManageGroupsViewModel() {
var self = this;
self.Groups = ko.observableArray();
self.IsLoading = ko.observable(false);
self.SelectedGroup = ko.observable();
}
Typically you'd start out with SelectedGroup being null:
self.SelectedGroup = ko.observable(null);
...and then when you're ready to edit a group, just set it to a new instance; if that instance needs observable properties, you create them just like you did for ManageGroupsViewModel:
function GroupVM() {
this.name = ko.observable("");
this.members = ko.observableArray();
}
and
// Start editing a group
yourGroupsViewModel.SelectedGroup(new GroupVM());
This other answer of mine has a fairly thorough example of doing this.

Automapping ajax data during ajax put or post in knockout

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)

Match values in nested object to corresponding knockout bindings?

Let's say I have a list of knockout bindings placed in a nested/namespaced object, resembling this:
var bindings = {
event: {
eventid: ko.observable(),
office: ko.observable(),
employee: {
name: ko.observable(),
group: ko.observable()
}
},
...
}
Now let's say there are a number of different sets of data that might be loaded into this - so one does an ajax query and gets a JSON result like this:
{
"defaults": {
"event": {
"eventid": 1234,
"employee": {
"name": "John Smith"
}
},
...
}
}
Note that not every binding has a default value - but all defaults are mapped to a binding. What I want to do is read the defaults into whatever knockout binding they correspond to.
There are definitely ways to traverse a nested object and read its values. Adding an extra argument to that example, I can keep track of the default's full key (eg event.employee.name). Where I'm getting stumped is taking the default's key and using it to target the associated knockout binding. Obviously, even if i have key = "event.employee.name", bindings.key doesn't reference what I want. I can only think of using eval(), and that leaves me with a bad taste in my mouth.
How would one go about using a key to reference the same location in a different object? Perhaps knockout provides a way to auto-map an object to its bindings, and I've just overlooked it? Any insight would be helpful. Thanks in advance!
I would suggest you have a look at the Knockout Mapping Plugin which will do most of what you want to do. If that doesn't workout then you can turn your bindings object into a series of constructor functions that accepts a data parameter. Something like
var Employee = function (data){
var self = this;
self.name = ko.observbale(data.name || '');
self.group = ko.observable(data.group);
};
var Event = function(data){
var self = this;
self.eventid = ko.observable(data.id || 0);
self.office = ko.observable(data.office || '');
self.employee = ko.observable(new Employee(data.employee));
};
var bindings = function(data){
var self = this;
self.event = ko.observable(new Event(data));
}
I'll be putting Nathan Fisher's solution into a future update, but I wanted to share the fix I found for now as well. Each time the defaults object recurses, I simply pass the corresponding bindings object instead of tracking the entire keypath.
var setToDefaults = function(data){
loopDefaults(data.defaults, bindings);
};
var loopDefaults = function(defaults, targ){
for(var d in defaults){
if(defaults.hasOwnProperty(d) && defaults[d]!==null){
if(typeof(defaults[d])=="object"){
loopDefaults(defaults[d], targ[d]);
}else{
// defaults[d] is a value - set to corresponding knockout binding
targ[d](defaults[d]);
}
}
}
};

knockout.js accessing container model property in a contained viewModel

I have nested view models like below. I am trying to access value in container view model from the contained view model (child). I got undefined error when the modelA.prop1 trying to get mainVM.prop1 value. Thanks for your help.
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(mainVM.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
If you want to make sub-ViewModels dependent/aware of their parent you'll have to pass it to them. E.g.:
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA(self);
self.modelB = new modelB(self);
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(parent){
var self = this;
self.prop1 = ko.observable(parent.prop1); //I'd like to get value in containing view model above
}
function modelB(parent){....}
$(function () {
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Think carefully though if this dependency is something you want in your design.
An alternative (though arguably worse from a design standpoint) solution would be to give them access through the scope, e.g.:
$(function () {
function mainVM() {
var self = this;
//chain associated view models
self.modelA = new modelA();
self.modelB = new modelB();
self.prop1 = ko.observable("some value from mainVM.prop1");
}
function modelA(){
var self = this;
self.prop1 = ko.observable(viewModel.prop1); //I'd like to get value in containing view model above
}
function modelB(){....}
var viewModel = new mainVM();
ko.applyBindings(viewModel);
});
Some additional thoughts to #Jeroen answer
Having dependencies to parent from children is not only bad design it can create hard to find memory leaks
If you use the parent from a computed in the child KO will hook up a dependency, if you remove the child it's computed will still fire when the parent change state.
My general way of solving dependencies between models is to use a EventAggregator pattern, I have made one for this library
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
Its a signalR library, if you do not need singalR you can extract the event aggregation part
Demo
http://jsfiddle.net/jh8JV/
ViewModel = function() {
this.events = ko.observableArray();
this.subModel = new SubViewModel();
signalR.eventAggregator.subscribe(Event, this.onEvent, this);
};
ViewModel.prototype = {
onEvent: function(e) {
this.events.push(e);
}
};
I think you've got an "XY problem" here: you want to accomplish task X (which you haven't named here) and you think that implementation Y (in this case, a child VM having a dependency on its parent) is the way to do it, even though Y might not be the best (or even a good) way to do it.
What's the actual problem you're trying to solve? If you need to access the parent property from within a child binding, Knockout's binding context ($root, $parent, $parents[], etc.) will let you do it, e.g.
<div data-bind="with:modelA">
<p>prop2 is <span data-bind="text:prop2"></span>
and prop1 from the main model is
<span data-bind="text:$root.prop1"></span>
</p>
</div>
In this case you could use $parent in place of $root since there's only one level of nesting.

Adding more functions to Backbone Models

I am attempting to add some functions to backbone so that I can communicate with mongodb. Now I know this won't work client side; however, I do like backbone's functionality for server side model logic as well. I noticed that I would be doing a bunch of repeat work if I kept adding the same functionality for each model so decided to create a "app_model" file to extend backbone when I'm server side. I also don't want to override the standard Backbone functions because they will be useful client side.
So let's take this user class for instance:
var Backbone = require('./app_model');
var User = Backbone.Model.extend({
name : "users",
defaults: function() {
return {
username: "default",
role: 2,
created: new Date(),
updated: new Date(),
logged: new Date()
};
},
idAttribute: "username",
/**
* A predefined listing of user roles
*/
userRoles: [
"admin", //0
"author", //1
"user" //2
],
initialize: function() {
if(!!app) {
this.svrInit();
}
}
});
module.exports = User;
And I want to append functions onto backbone by using my "app_model.js" file, which looks something like this currently:
var Backbone = require('backbone'),
Deferred = require('Deferred'),
when = Deferred.when;
Backbone.Model.prototype.svrInit = function() {
//TODO: perhaps the code below should be made static some how so we don't have a bunch of instances of collection
var model = this;
if(!!app.db){
app.db.collection(this.name,function(err,collection){
model.collection = collection;
});
}
};
Backbone.Model.prototype.svrSave = function() {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.insert(model, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
Backbone.Model.prototype.svrFind = function(options) {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.find(options, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
module.exports = Backbone;
I ran my tests when I abstracted this out and it seemed to work alright. Is there a better way to do any of this? Any pit falls? I am using the global "app" variable, is that bad? If so what are some ways around it? I do find it ugly that I had to put this.svrInit() inside the init function at the model level is there anyway to automatically make that happen after creation?
So I've been thinking about this question for a couple days and I the cleanest thing I've come up with is something like this:
var MyModel = function( attributes, options ) {
Backbone.Model.apply( this, arguments );
this.specialInitializer();
};
MyModel.extend = Backbone.Model.extend;
_.extend( MyModel.prototype, Backbone.Model.prototype, {
specialInitializer: function() {
// called after the users 'initialize'
console.log("MyModel initialized.", this);
},
otherNewMethod: function() {
// this is just like any other instance method,
// just as if Backbone.Model implemented it
}
} );
So what this does is basically make an entirely new 'kind' of Backbone.Model. One which also calls specialInitializer. If you look at the backbone source just after the constructor definition for Backbone.Model you'll see this is a similar strategy.
Construct the instance.
Call an initializer the implementor is supposed to define.
Extend the prototype with functionality (in their case Backbone.Events, in ours, Backbone.Model).
Your new initializer can of course call whatever else it needs, etc.
As for your other questions about the static collection stuff and global app variable, I'm afraid I don't follow exactly what is going on there since I don't see a definition for app and don't know what you're using the collection for.
Here's a fiddle that demonstrates this with some extra logging and such.
I'm working on a fairly large code-base with 4-5 levels of inheritance in the views. This is the pattern I'm using:
var BaseView = Backbone.Model.extend({
somefunc: function() {
//contents
},
otherfunc: function(a,b,c) {
//contents
},
//...
});
var User = BaseView.extend({
// things in user view can now access somefunc and otherfunc
});
Here's a quick example in a jsfiddle (note the doSearch function being inherited)

Categories

Resources