Child object data binding in Grails controller - javascript

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;

Related

Advice on structuring Backbone array data

I'm in the middle of a process where I'm retrieving data from a REST API for use in my Backbone.js application. At this point I don't need to do any manipulations to the data except for the fact that the data returned is an array. However, the future might bring such requests, so I've already created a Backbone model and collection for this type of data.
I've read that you could map your Array data into an object inside your Backbone collection, but I'm wondering since I already have a model, if it would be better practise to already map each element inside my Backbone model.
Since I'm not an expert in the Backbone.js framework, any links with more documentation about this section would be greatly appreciated.
UPDATE: I was actually looking for the parse method that is provided by the BackboneJS framework. By transforming the Array into an Object in the parse function I was able to solve the question.
You can use the parse method to parse any kind of transformation you'd like to do, like e.g. copying attributes, modifying attributes etc.
More information : http://backbonejs.org/#Collection-parse
Just as in the question you mentioned, this can achieved using parse, either on the Collection or the Model.
var UserModel = Backbone.Model.extend({
// String name is mapped to an object with the name property
parse: function(name) {
return {
name: name
};
}
});
var UserCollection = Backbone.Collection.extend({
model: UserModel
});
var collection = new UserCollection(['Ann', 'Joe', 'Jim', 'Bob'], {parse: true});
console.log(collection.at(0).get('name'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
In the example above, the collection is instantiated with data, in this case, parse doesn't run by default, so it needs to be set in the options, however if the collection normally gets its data from fetch, this will by default always parse.

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!

Setting Property of View Model to Knockout Value

Using MVC 4, I am trying to send a viewmodel back to the controller with a value that was populated using Knockout in the javascript part section of my view.
Psuedocode:
var ProgramOptionsVm = function() {
self = this;
self.AvailableOptions is populated (IList<RegistrationOption>)
}
I want to set AvailableOptions equal to field of viewmodel. Something like:
Model.AvailableOptions = ProgramOptionsVm.AvailableOptions
Any advice would be great!
You can't set Model.AvailableOptions directly with your Knockout view model. The two things exist in different scopes: the former is server-side, while the latter is client-side. You would have to POST the data back to another controller action.

Firebase + backbone: difference between collection.create() and collection.push()?

I see a difference using create() and push() on collections using Backfire and wonder if this is a misunderstanding on my part, or a bug.
I have an Animal model and Animals collection as below. Normally, the collection is created with an options object containing a zoo_id which is then used to populate the zoo_id in new models. It's a fixed value for this example.
var Animal = Backbone.Model.extend({
initialize: function(attributes, options) {
console.log("model initializing", attributes, options)
}
}),
Animals = Backbone.Firebase.Collection.extend({
firebase: myFirebaseUrl + "/animal",
initialize: function(models, options) {
this.model = function(attrs, opts) {
return new Animal(_.extend(attrs, {zoo_id: 4}))
};
this.on("add", function(model, collection, options) {
console.log("adding", model, collection, options, model.attributes)
})
}
})
var a= new Animals()
If there's data in Firebase, all of the retrieved animal models in a[] have zoo_id = 4, as expected.
When I push a new model
a.push({name: "racoon"})
all of the attribute objects logged to the console have zoo_id = 4. However, the returned object does not have a zoo_id, nor is zoo_id present for the new entry in the Forge.
When I create a new model
a.create({name: "ape"})
all of the attribute objects logged to the console have zoo_id = 4, the returned object has zoo_id = 4, and the new entry has zoo_id = 4 is in the Forge.
If I remove the Firebase extensions and just use a regular Backbone model and collection in the same manner, push returns an object with a zoo_id, and create fails as there's no url set up (as expected).
thanks in advance for clarification!
Push is not part of the functionality overridden by the Backfire API. It pretty much sticks to the same contract as Backbone.Collection. Thus, push simply appends a record to the end of the array without syncing it to any back end.
You could probably create the desired behavior by calling sync after push, as would normally be done with a Backbone collection. I'm not sure how the id would work here, you might need to add one onto the object before it can be synchronized.
However, it's probably simplest to use create/add instead, which are part of BackFire's API and handle server synchronization.

DurandalJS and composing Child Views with Parameters

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.

Categories

Resources