DurandalJS and composing Child Views with Parameters - javascript

I'm having some difficulty transitioning from Knockout's template binding to the Durandal compose binding.
In my old project, I have a list of tabs which can be swapped into center stage by placing them into the "selectedTabSection" observable. The templateId was a property of the sub-view. So in my parent view, I created instances of my child models like this:
self.tabSections([
new BasicTabViewModel(self, db),
new BiometricTabViewModel(self, db),
new ActivityTabViewModel(self, db),
new SurveyTabViewModel(self, db),
new CommunicationTabViewModel(self, db),
new ReferralTabViewModel(self, db),
new GoalTabViewModel(self, db),
new NcpTabViewModel(self, db),
new CriticalValuesViewModel(self, db),
new ConditionManagement(self, db)
]);
Then when I wanted to show one, I'd place it into the active observable:
self.selectedTabSection(self.tabSections()[0]);
When I change to the compose binding, it seems that Durandal cannot find the associated Views for my ViewModels because I'm binding instances of the models rather than the constructor of the ViewModel itself. In other words,
self.selectedTabSection(BasicTabViewModel);
Finds the appropriate view, whereas
self.selectedTabSection(new BasicTabViewModel(self, db));
does not.
How can I get the viewLocator to understand that I'm passing an instance rather than the ViewModel constructor itself? If I cannot, how do I pass parameters to my child views since they haven't been initialized until they are composed?
EDIT/UPDATE:
It looks like there's something to do with how I've composed my child ViewModels. Durandal appears to have issue when you return an object from the ViewModel's constructor.
This seems to work as expected:
var viewModel = function (parentVm, db) {
var self = this;
}
Whereas this:
var viewModel = function(parentVm, db){
var self = this;
//public api
return {};
}
Does not. Something about returning an object from the constructor make DurandalJS get lost when trying to locate the View and also makes a mess of various scopes. I'm considering re-writing my scripts to fit, but this pattern of returning a object from a constructor has served me well for many moons (prior to Durandal) Curious...

The reason this works -
var viewModel = function (parentVm, db) {
var self = this;
}
Whereas this does not -
var viewModel = function(parentVm, db){
var self = this;
//public api
return {};
}
Is because you are erasing everything that your function did to create an object.
Consider this -
function newModule (path, params){
var self = this;
self.modulePath = path;
self.activationData = params;
}
Now you can create instances of this anonymous function and pass in the parameters to you want to bind your view / view model to.
var theseChildViewModels = ko.observableArray();
var someData = getDataFromSomewhere();
theseViewModels.push(new newModule('viewmodels/myViewModelOne', { data: someData }));
theseViewModels.push(new newModule('viewmodels/myViewModelTwo', { data: someData }));
Now you can bind to these in your parent view like this -
<ul data-bind="foreach: theseChildViewModels">
<li>
<!-- ko compose: { model: modulePath, activationData: activationData} -->
<!-- /ko -->
</li>
</ul>
And dynamically declare your paths and what data is passed into the activate callback during composition.

Related

How to assign a KnockoutJs observable to another observable?

I have a model defined as:
var TestModel = function () {
var self = this;
self.Item = ko.mapping.fromJS(new Item());
self.ItemList = ko.observableArray();
};
var Model = new TestModel();
ko.applyBindings(Model);
After an AJAX call I fill the array with:
ko.mapping.fromJS(data, {}, Model.ItemList);
The array is displayed in the screen and when the user selects an item, a modal dialog is opened to edit that item. The same modal dialog can be opened to add a new item. That modal has a data-bind="with: Item", so I can do something like this:
function add() {
ko.mapping.fromJS(new Item(), {}, Model.Commission); //Empty object
$('#dialog').dialog('open');
};
function saveAdd() {
//Clone the properties...
Model.ItemList.push(ko.mapping.fromJS(ko.mapping.toJS(Model.Item)));
}
function edit(i) {
//Clone properties!
ko.mapping.fromJS(ko.mapping.fromJS(ko.mapping.toJS(Model.ItemList()[i])), {}, Model.Item);
$('#dialog').dialog('open');
}
function saveEdit(i) {
//Clone properties!!!!
Model.ItemList()[i] = ko.mapping.fromJS(ko.mapping.toJS(Model.Item));
}
I want to avoid cloning the properties every time I need to assign the values of one observable to another one. I understand that if I manage to tell Knockout that the Model.Item is some Model.ItemList()[i], the objects references should make sure the changes in the UI reflect directly to my observableArray item, but I cannot just assing it as Model.Item = Model.ItemList()[i]; because that breaks the binding between the original Model.Item object and the view [*1].
Is there a way to assign an observable to another observable that's binded to the view, and maintain the references?
Failing to do that, is the a better way to do this? Basicaly a page where you store the list of items in an observableArray and then add/edit in another piece of HTML.
Edit: Explanation for [*1]
I cannot find the stackoverflow question where this was explained, but it was something like this:
When we do ko.applyBindings(Model);, the observable objects inside Model get binded to the view. At this point the object referenced by Model.Item is binded, so if I do Model.Item = Model.ItemList()[i]; the new object being referenced by Model.Item is not binded to anything. The original object (in memory) is still the one binded to the view, just that there are no references to it now.
If you make Model.Item an observable, with the mapped object inside of it, then you can set the observable's contents to the new item. The reference to the observable made by knockout remains intact because you're just updating the contents and not the property itself.
var TestModel = function () {
var self = this;
self.Item = ko.observable(ko.mapping.fromJS(new Item()));
self.ItemList = ko.observableArray();
};
...
Model.Item(Model.ItemList()[i]);

Creating a new collection inside existing collection

I am currently trying to put a backbone model inside an already existing model. I was wondering if this is even possible.
var rf = this.collection.create(attrs, options);
Model.set(table, rf);
Thanks
What you trying to do is "Nested Models & Collections". Backbone already has preferable approach. The common idea consist in storing of nested model directly in the instance of another model instead attributes.
So, you could create child model first and then pass it to parent model through options like the following:
var rf = this.collection.create(attrs, options);
var Model = Backbone.Model.extend({
initialize: function(attributes, options) {
_.isObject(options) || (options = {});
if (options.child) {
this.child = new options.child;
}
}
});
var model = new Model({}, {child: rf});
If you want to get a fully supported tree-like Backbone models you could try to use one of the following plugins.
Hope this helps!

In Backbone.js , whats the difference between initializing a view by passing a model in the options and passing model object as a paramaeter?

Case 1: Passing model as in options
var View1 = Backbone.View.extend({
initiliaze:function(){
}
});
Case 2 : Passing model as a param and setting it using this
var View2 = Backbone.View.extend({
initiliaze:function(model){
this.model = model
}
});
var view1 = new View1({model:someModel})
var view2 = new View2(someModel)
It's the same thing, except that in the first case you have less code in your view declaration because Backbone handle setting the model in the view for you (this.model = model).
In general Backbone can handle some parameters for you, you can take a look at the documentation to have more informations about it.

methology for keeping different backbone models in sync

I've tried to describe my problem in the below illustration.
When the page loads, a javascript object is parsed and becomes my backbone model called obj model. This obj model is passed along to many different independent and modular submodules that make use of the data in different ways.
Everything works great except for when I'm dealing with collections. To give the user the ability to manage "Photos" and "Comments" I need to create a separate collection/model data structure for them.
How should I sync back the changes to my "obj model"?
class Obj extends Backbone.DeepModel
class Comment extends Backbone.DeepModel
class CommentCollection extends Backbone.Collection
model: Comment
class Photo extends Backbone.DeepModel
class PhotoCollection extends Backbone.Collection
model: Photo
You're obj model should be externalized.
App.module("Entities",function(Entities,App,Backbone,Marionette,$,_){
"use strict";
//these are locally scoped, so they aren't accessable by the your app
var Obj = Backbone.Model.extend({
urlRoot:'api/obj',
});
var Data = {};
var API = {
//wrap request in deferred
getObjById : function(id, reset){
var deferred = $.Deferred();
//returned cached data if we don't request refreshed data
reset = reset || false;
if (Data[id] && !reset) {
return Data;
}
this._getObj(id, function(loadedObj) {
//cache object
Data[loadedObj.id] = loadedObj
deferred.resolve(loadedObj);
});
return deferred.promise();
},
_getObj: function(id, callback) {
var obj = new Obj({id:id});
obj.fetch({success:callback});
},
};
//Interface for mucking with Obj model
App.reqres.setHandler("obj:getObj", function(id) {
return API.getObjById(id);
});
});
You can get an obj like so. If reset isn't passed in, it will be the cached version (all sub modules can refer to same obj just by passing in ID or w/e your criteria is for loading):
$.when(App.request('obj:getObj', 123)).done(function(loadedObj) {
//show view, or do whatever
);
OR to get fresh data:
$.when(App.request('obj:getObj', 123, true)).done(function(loadedObj) {
//show view, or do whatever
);
These are just examples of how to GET data. You could extend the API and expose new handlers for UPDATING data. You could either have each sub module's controller listen to the obj (by all listening to the same, cached obj model), or just request the most up to date obj model each time you need it. This would be a round trip to the server if your cached data reflects the most recent changes. But then you have to worry about keeping things in sync.
Hope this helps

Child object data binding in Grails controller

I am trying to setup a javascript object such that when it data binds to a new object in the controller, the associated child objects in the DB are pulled based on the id field. This is documented in the Spring data binding as such:
Data binding and Associations
If you have a one-to-one or many-to-one association you can use
Grails' data binding capability to update these relationships too. For
example if you have an incoming request such as:
/book/save?author.id=20
Grails will automatically detect the .id suffix on the request
parameter and look-up the Author instance for the given id when doing
data binding such as:
def b = new Book(params)
I want to post a javascript object to the controller as a fully composed object with the child properties. How can I setup the javascript object so that the controller sees the child properties of the object as in the following url (the same as is documented) /book/save?author.id=20?
I have tried this but it doesn't seem to work:
var childObject= function(name, id) {
this.name = name;
this.id = id;
};
var viewModel = {
id: 1,
child: new childObject("Chuck",1)
}
The controller doesn't see 'child.id' when the viewModel is posted to the controller and automatically fetch the associated record. Am I totally off base thinking it works like this?
Perhaps another way to ask this is how can I serialize a javascript object so that its string representation is "object.property"?
Creating the javascript object as {child :{id:1} } should do what you are trying to achieve. Try something like this to create the required js object
var dataObj = {};
var childObj = {};
childObj['id'] = 1;
dataObj['child'] = childObj;
return dataObj;

Categories

Resources