relational models and variable references within templates using backbone and marionette - backbone.js

I'm messing around with backbone, marionnette, and wondering how I should be dealing with relational models. My main reason is i'd like to be able to use data in two different related models in the same template. e.g.
Thing = Backbone.Model.extend({defaults: {label: null, uri: null}});
Things = Backbone.Collection.extend({model: Thing});
Relationship = Backbone.Model.extend({defaults: {subject: null, predicate: null, object: null}});
Relationships = Backbone.Collection.extend({model: Relationship});
var things = new Things([
new Thing({label: 'Sam', uri: 'AAAA'}),
new Thing({label: 'is friends with', uri: 'BBBB'}),
new Thing({label: 'Violet', uri: 'CCCC'}),
new Thing({label: 'Fred', uri: 'DDDD'})
]);
var relationships = new Relationships([
new Relationship({subject: "AAAA", predicate: "BBBB", object: "CCCC"}),
new Relationship({subject: "AAAA", predicate: "BBBB", object: "DDDD"})
]);
So, the relationships array is just holding references to items in the Things array matching the uri attribute. What I want to do in the Relationship template is something like this (using dot notation to get at the associated Thing model data):
<script type="text/template" id="relationship-template">
<td><%= subject.label %></td>
<td><%= predicate.label %></td>
<td><%= object.label %></td>
</script>
Any ideas the best way to handle this? I want to avoid duplicating things in memory.
Result should be:
Sam is friends with Violet
Sam is friends with Fred
I've looked at Backbone relational but haven't had any luck getting it working :(
This is what I tried:
Relationship = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasOne,
key: 'subject',
keySource: 'uri',
keyDestination: 'subjectObject',
relatedModel: Thing
}
]
});

Ah - i figured it out -
It's looking for an ID field to map whatever you have in key to - so I renamed uri to id and all is well. Too bad you can't explicitly tell it which field to map to.

Related

Marionette nested composite view

Can I use nested composite view to make the output like this?
Nike
Football, Basketball
Reebok
Football, Basketball, Running
I did write nested composite view in this way. I'm not sure is there any other better way or best practice way to implement this.
Structure:
BrandCompositeView
itemView: Layout1
#brand<-BrandView (Nike, Reebok)
#sport<-SportCompositView
itemView: SportView
BrandCompositeView (generate Nike, Reebok),
itemView of BrandCompositeView is a layout1 with div id are(#brand, #sport)
SportCompositView (generate Football, Basketball, Running)
itemView of SportCompositView is SportView
BrandView and SportView are itemView
Layout1 is Marionette.Layout
inside Layout1.js
initialize:function(){
this.brandView = new BrandView({"model": this.model});
this.sportView = new SportCompositView({"collection":collection});
}
onRender: function () {
this.brand.show(this.brandView);
this.sport.show(this.sportView);
}
Yes it's possible.
Approach I have used for tree structures - article.
How about if I want to make this (collection with more than 1 child?)
Car: [
{
Car1
name: car1
image: [{img1}, {img2}, {img3}]
comment: [{comment1}, {comment2}, {comment3}]
price: $1000
},
{
Car2
name: car1
image: [{img1}, {img2}, {img3}]
comment: [{comment1}, {comment2}, {comment3}]
price: $2000
}
]
<script id="car-template" type="text/template">
<li><%= name %></li>
<div id="photo-container"></div>
<li><%= price %></li>
<div id="comment-container"></div>
</script>
I actually just wrote an answer that might help you a couple days ago. Check out this thread - Construct views for data nested several levels deep
Basically it has you use backbone relational to automatically handle all of your nesting. Then you can just abstract off of that for your composite view inside of your composite view.

using Backgrid and Backbone-relational

I have a model which has HasMany items in it.
var Checklist = Backbone.RelationalModel.extend( {
url: {{ url }}
relations: [ {
type: Backbone.HasMany,
key: 'items',
relatedModel: ChecklistItem,
collectionType: ChecklistItemCollection,
}],
});
I instantiate the model var checklist = new Checklist();
now I initialize Grid and fetch the checklist.
var grid = new Backbone.Grid({columns:columns, collection: checklist.get('items'));
checklist.fetch({reset:true});
I can see in Checklist's parse method that it has retrieved data from the server.
But the grid view doesn't show any data for it.
(When I used plain Backbone.model instead of backbone.RelationalModel, everything worked fine. So I know my setup is correct other than the backbone-relational + backgrid interaction is missing)
I'm new to backbone/javascript/backgrid/... world
I guess I need to hook up some events to make it work.
Please share an insight!

backbone.js with backgrid.js to populate JSON

I am using backgrid.js with backbone.js. I'm trying to populate JSON (user list) in backgrid. Below is my JSON,
[{"name": "kumnar", "emailId":"kumar#xxx.com",
"locations":{"name":"ABC Inc.,", "province":"CA"}
}]
I can access name & emailId as below,
var User = Backbone.Model.extend({});
var User = Backbone.Collection.extend({
model: User,
url: 'https://localhost:8181/server/rest/user',
});
var users = new User();
var columns = [{
name: "loginId",
label: "Name",
cell: "string"
}, {
name: "emailId",
label: "E-mail Id",
cell: "string"
}
];
var grid = new Backgrid.Grid({
columns: columns,
collection: users
});
$("#grid-result").append(grid.render().$el);
userEntities.fetch();
My question is, how do I add a column for showing locations.name?
I have specified locations.name in the name property of columns but it doesn't work.
{
name: "locations.name",
label: "E-mail Id",
cell: "string"
}
Thanks
Both backbone and backgrid currently don't offer any support for nested model attributes, although there are a number of tickets underway. To properly display the locations info, you can either turn the locations object into a string on the server and use a string cell in backgrid, or you can attempt to supply your own cell implementation for the locations column.
Also, you may try out backbone-deep-model as it seems to support the path syntax you are looking for. I haven't tried it before, but if it works, you can just create 2 string columns called location.name and location.province respectively.
It's really easy to extend Cell (or any of the existing extensions like StringCell). Here's a start for you:
var DeepstringCell = Backgrid.DeepstringCell = StringCell.extend({
render: function () {
this.$el.empty();
var modelDepth = this.column.get("name").split(".");
var lastValue = this.model;
for (var i = 0;i<modelDepth.length;i++) {
lastValue = lastValue.get(modelDepth[i]);
}
this.$el.text(this.formatter.fromRaw(lastValue));
this.delegateEvents();
return this;
},
});
In this example you'd use "deepstring" instead of "string" for your "cell" attribute of your column. Extend it further to use a different formatter (like EmailFormatter) if you want to reuse the built-in formatters along with the deep model support. That's what I've done and it works great. Even better is to override the Cell definitions to look for a "." in the name value and treat it as a deep model.
Mind you, this only works because I use backbone-relational which returns Model instances from "get" calls.

How to sort object data source in ng-repeat in AngularJS?

Suppose I have the following users:
$scope.users = {
"2": {
email: 'john#gmail.com',
name: 'John'
},
"3": {
email: 'elisa#gmail.com',
name: 'Elisa'
}
}
I would like to create a <select> with the following options:
<option value="3">Elisa</option>
<option value="2">John</option>
In other words, users should be sorted by name.
I tried the following using the (key, value) in expression syntax, but it doesn't work:
<option ng-repeat="(user_id, user) in users | orderBy:'user.name'"
value="{{ user.id }}">
{{ user.name }}
</option>
Live example here.
What am I missing?
Please do not suggest solutions with ng-options as I use ui-select2 which is incompatible with ng-options.
While you would see this in the thread the author's answer references in a link, I thought it would be good to put up one of the mentioned workarounds up on SO:
It is true that this is technically not implemented, but there is an easy work around if you're willing to change your data model slightly: use a custom filter to generate an array of the object's properties (without replacement). If you add the key field to the objects ("id" in the above case), you should be able to get the behavior your looking for:
app.filter("toArray", function(){
return function(obj) {
var result = [];
angular.forEach(obj, function(val, key) {
result.push(val);
});
return result;
};
});
...
$scope.users = {
1: {name: "John", email: "john#gmail.com", id: 1},
2: {name: "Elisa", email: "elisa#gmail.com", id: 2}
};
Here's the ng-repeat directive that could be used:
<option value="{{user.id}}" ng-repeat="user in users | toArray | orderBy:'name'">{{user.name}}</option>
And here's the plunkr.
Notice that the orderBy filter takes name as its parameter and not user.name.
Unfortunately, adding the id property to your objects does create potential for mismatch with it's key in the containing object.
In the link you mentioned in your answer, there are also proposed solutions that create the id property in the user objects on the fly, but I feel like this approach is a little less messy (at the cost of introducing data replication).
OK, I found the answer:
It is not implemented yet :(
Adding to the accepted answer and seeing your data format, you should transform your data as
app.filter('myFilterUsers',function(){
return function(data)
{
var newRes = [];
angular.forEach(data,function(val,key){
val["id"] = key; //Add the ID in the JSON object as you need this as well.
newRes.push(val);
});
return newRes;
}
});

Backbone.Marionette - Collection within CompositeView, which itself is nested in a CollectionView

**UPDATE: The problem probably involves the tour-template as I've discovered that it thinks the 'name' attribute is undefined. This leads me to think that it's not an array being passed on to the ToursView, but for some reason a string. **
After studying similar questions on StackOverflow:
How to handle nested CompositeView using Backbone.Marionette?
How do you properly display a Backbone marionette collection view based on a model javascript array property?
Nested collections with Backbone.Marionette
... and Derick Bailey's excellent blog's on this subject:
http://lostechies.com/derickbailey/2012/04/05/composite-views-tree-structures-tables-and-more/
... including the JSFiddle's:
http://jsfiddle.net/derickbailey/AdWjU/
I'm still having trouble with a displaying the last node of a nested CollectionView of CompositeViews. It is the final CollectionView within each CompositeView that is causing the problem.
CollectionView {
CompositeView{
CollectionView {} //**<-- This is the troublemaker!**
}
}
NOTE: I have already made a point of creating a valid Backbone.Collection given that the collection passed on to the final, child CollectionView is just a simple array.
Data returned from the api to ToursList:
[
{ "id": "1",
"name": "Venice",
"theTours": "[
{'name': u'test venice'},
{'name': u'test venice 2'}
]"
},
{ "id": "2",
"name": "Rome",
"theTours": "[
{'name': u'Test rome'}
]"
},
{ "id": "3",
"name": "Dublin",
"theTours": "[
{'name': u'test dublin'},
{'name': u'test dublin 2'}
]"
}
]
I'm trying to nest these in a dropdown where the nav header is the 'name' (i.e. Dublin), and the subsequent li 's are the individual tour names (i.e. 'test dublin', 'test dublin2', etc.)
Tour Models and Collections
Tour = TastypieModel.extend({});
ToursGroup = TastypieCollection.extend({
model: Tour
});
ToursByLoc = TastypieModel.extend({});
ToursList = TastypieCollection.extend({
model: ToursByLoc,
url:'/api/v1/location/',
});
Tour Views
TourView = Backbone.Marionette.ItemView.extend({
model: Tour,
template: '#tour-template',
tagName: 'li',
});
ToursByLocView = Backbone.Marionette.CompositeView.extend({
collection: ToursByLoc,
template: '#toursByLoc-template',
itemView: TourView,
itemViewContainer: '#ind',
initialize: function(){
//As per Derick Bailey's comments regarding the need to pass on a
//valid Backbone.Collection to the child CollectionView
//REFERENCE: https://stackoverflow.com/questions/12163118/nested-collections-with-backbone-marionette
var theTours = this.model.get('theTours');
this.collection = new ToursGroup(theTours);
console.log(this.model.get('name'));
console.log(theTours);
//To test the output --> I get:
//Venice
//[{'subtitle': u'ex. These prices include... but not...', 'description': u'Please enter the tour description here', 'start_time': datetime.time(2, 34, 24), 'adult_price': Decimal('123'), 'important': u'ex. Please contact us to negotiate a price if you want to book the Fiat for 1 person only.', 'teenager_student_price': Decimal('123'), 'child_price': Decimal('123'), 'under_6_price': Decimal('123'), 'location_id': 1, 'id': 1, 'name': u'test venice'}, {'subtitle': u'ex. These prices include... but not...', 'description': u'Please enter the tour description here', 'start_time': datetime.time(20, 4, 57), 'adult_price': Decimal('222'), 'important': u'ex. Please contact us to negotiate a price if you want to book the Fiat for 1 person only.', 'teenager_student_price': Decimal('222'), 'child_price': Decimal('222'), 'under_6_price': Decimal('222'), 'location_id': 1, 'id': 2, 'name': u'test 2'}] main.js:64
//Rome
//[{'subtitle': u'ex. These prices include... but not...', 'description': u'Please enter the tour description here', 'start_time': datetime.time(1, 28, 25), 'adult_price': Decimal('123'), 'important': u'ex. Please contact us to negotiate a price if you want to book the Fiat for 1 person only.', 'teenager_student_price': Decimal('333'), 'child_price': Decimal('333'), 'under_6_price': Decimal('333'), 'location_id': 2, 'id': 3, 'name': u'test rom1'}] main.js:64
//Dublin
//[]
this.collection = new ToursGroup(theTours);
}
});
ToursListView = Backbone.Marionette.CollectionView.extend({
itemView: ToursByLocView,
});
Templates
<script id="tour-template" type="text/template">
<%= name %> //<-- THIS IS GIVING ME ISSUES
</script>
<script id="toursByLoc-template" type="text/template">
<li class="nav-header"><%= name %></li>
<div id="ind" class="indTours"></div>
<li class="divider"></li>
</script>
<script id="tours-template" type="text/template">
<%= name %>
</script>
I don't think this is the problem, but this should be changed:
itemViewContainer: '#ind'
in your CompositeView definition. This is bad / invalid / may produce unexplainable results. The DOM should only container one instance of an HTML element with any given id. In this case, you have a <div id="ind"></div> for every CompositeView instance.
You should change this to a css class, and use the class selector for the itemViewContainer instead.
...
I built a JSFiddle with your code, and it appears to work: http://jsfiddle.net/derickbailey/QPg4D
what results are you seeing in your app? what are you expecting to see instead?
...
Look at the way your data is coming back:
"theTours": "[
{'name': u'test venice'},
{'name': u'test venice 2'}
]"
this is technically valid JSON, but it's going to return a string that looks like an array, instead of an actual array.
In my JSFiddle I had to correct this:
"theTours": [
{'name': 'test venice'},
{'name': 'test venice 2'}
]
Notice that I removed the " and " around the [ ] and I also removed the "u" in front of the string values.
It may be that your server is improperly serializing the data to a JSON document, and sending back this badly formatted doc.

Resources