**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.
Related
For example, I have the following collection used for ng-repeat:
$scope.pokedex = [{
type: "Fire",
pokemon: ["Charizard", "Moltres"]
},{
type: "Rock",
pokemon: []
},{
type: "Fighting",
pokemon: ["Machamp", "Hitmonchan"]
},{
type: "Dragon",
pokemon: []
}];
This collection will be churned out in a ng-repeat directive. In the actual application, the collection will be retrieved from a database, so it may be unsorted. I want to sort it in the following manner: priority sort types with Pokemon to the top, then sort each group by name.
Edit: I need to clarify what the backend data consists of. In the above example, $scope.pokedex consists of a constant number of types - these are categories. The application retrieves Pokemon from the database and fill up each category's list accordingly. The full range of types is intentionally hardcoded into the array and will remain unchanged regardless of whether the list of Pokemon in it is empty or not.
When the web page is generated using ng-repeat, the desired end state is as follows:
An accordion is displayed with each type as a header, and the list of Pokemon in the body as a list/table.
All empty categories shall be disabled but still visible, their headers given a particular CSS format, and all of them PUSHED TO THE BOTTOM beneath the non-empty categories.
The empty group and non-empty group shall individually be sorted by category/type name.
Everything except the pushing of empty groups to the bottom and the sorting by name have been implemented. These are my final requirements to implement.
How can I do that? Is there a way to do it in advance, or via orderBy during ng-repeat, or any other workable solution?
You Can Try like this This
<ul>
<li ng-repeat="people in peoples | orderObjectBy:'pokemon':true">{{ people.name }}</li>
</ul>
Please try the code below
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="pokemonController">
<ul>
<li ng-repeat="people in peoples | orderBy:['-pokemon.length','name']">
name: {{people.name}} - number of Pokemons: {{people.pokemon.length}}
<hr />
</li>
</ul>
</div>
</div>
<script>
var app = angular.module('myApp',[]);
app.controller('pokemonController', ['$scope', function($scope){
$scope.peoples = [{
name: "Obama",
pokemon: ["Charizard", "Dragonite", "Moltres"]
},{
name: "Merkel",
pokemon: []
},{
name: "Putin",
pokemon: ["Machamp", "Hitmonchan"]
},{
name: "Kim",
pokemon: []
}];
}]);
</script>
So, when i try to send a collection from server with duplicate entry (same id) backbone doesn't render that element.
But on model.fetch() if i return a model with an already existing id, it works.So now we have a collection with same ids.
Why is this different behaviour, i was thinking backbone will verify(check if that id alread exist) the incoming model before updating it and not render it.
EDIT
on collection.fetch i get this
var coll = [{name: 'foo', id: 1 }, {'name': 'bar', id: 2 }] // just representation;
now i do model.fetch() for second model, and the server responds this
{'name': 'new bar', 'id': 1} // no-error the view gets updated
Let's say I have a 4 div tags in my view:
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
And let's say that when the user selects clicks on one of them I want the rest to, say, turn red. Normally in a dirty jQuery style I'd use something like:
var tiles = $('.tile');
tiles.click(function()
{
tiles.css('background', 'red');
});
However, how would I do this in the world of AngularJS? Would I stick this code in the controller and have it relative to the $scope? Or would I create a directive and bind that to each tile element?
Assuming that you wouldn't just have 4 random tiles in your interface not bound to some kind of model, you could do something like this:
http://jsfiddle.net/V4YC9/1/
HTML
<div ng-app ng-controller="x">
<div ng-repeat="tile in tiles" ng-click="selectTile(tile)" ng-class="tile.class">{{tile.name}}</div>
</div>
JavaScript
function x($scope) {
$scope.selectedTileIndex = null;
$scope.tiles = [
{id: 1, name: 'tile 1'},
{id: 2, name: 'tile 2'},
{id: 3, name: 'tile 3'},
{id: 4, name: 'tile 4'}
];
// provide default class to all tiles
angular.forEach($scope.tiles, function (tile) {
tile.class = 'tile';
});
$scope.selectTile = function (clickedTile) {
angular.forEach($scope.tiles, function (tile) {
tile.class = 'tileNotSelected';
});
clickedTile.class = 'tileSelected';
}
}
Edit: There are probably 10 different ways to do it. If you don't want to muddy up your model, you could store a separate array in $scope and calculate the class real time by saying ng-class="calculateTileClass(tile)", similar to what I did in my initial response: http://jsfiddle.net/V4YC9/1/
I am trying to pull in a collection from the url attribute and am having some problems. It seems fetch() returns successfully, but then I cannot access the models in my collection with get(). I am using bbb and requireJS to develop my modules
var rooms = new Rooms.Collection(); // calls the rooms module
rooms.fetch({success: function(){
console.log(rooms.get(1)); // should output the first model
});
Here is my collection code in the rooms module:
Rooms.Collection = Backbone.Collection.extend({
model: Rooms.Model,
url: 'http://localhost:8888/projects/meeting-room/app/data/rooms.json'
});
If I output rooms, everything turns out fine. But when I try for a specific model, that is when I get an error.
[{
"id": 12345,
"name": "Ford",
"occupied": false
},
{
"id": 23458,
"name": "Chevy",
"occupied": false
},
{
"id": 83565,
"name": "Honda",
"occupied": false
}]
The collection.get method looks up a model by id. If you want to find a model by position, use collection.at instead.
Also notice that array indices in javascript are 0-based, so the first model can be found with:
var model = collection.at(0);
For convenience, Backbone collections also implement some of underscore's array and collection methods, including first. That means you can also find the first model with:
var model = collection.first();
var MenuListView = Backbone.View.extend({
el : '#menus',
reset : function() {
var hIndex = this.getTheMenuIndexHighlighting();
_.each(this.model.models[hIndex].attributes.subMenus, function(model, index) {
model.set({highlight : false});
});
});
_.each not rotating in the above code.
UPDATE
here is how my submenu json
[ {title:'Applications',bmUrl:'',id:'1',subMenus:[ {title: 'MsWord', bmUrl : 'msword.com';, id: 1, subMenus: null}, {title: 'MsExcel', bmUrl : 'msexcel.com';, id: 2, subMenus: null}, {title: 'MsPP', bmUrl : 'mspp.com';, id: 3, subMenus: null}, {title: 'MsOneNote', bmUrl : 'msonenote.com';, id: 4, subMenus: null}, {title: 'MsOutlook', bmUrl : 'msoutlook.com';, id: 5, subMenus: null} ],imgUrl:''}]
Can any body tell me why?
Replacing _.each to $.each is rotating the loop but doesn't trigger the appropriate model view updating method.
you should change this line
this.model.models[hIndex].attributes.subMenus
to
this.model.at(hIndex).get("subMenus")
EDIT:- by saying this i was assuming that subMenus in itself is a collection
but now it appears that its an array and going by the underscore site _.each() isnt available for arrays and only can be used on collections. where as jquerys each can be applied to arrays.
http://documentcloud.github.com/underscore/#arrays
link to underscore website notice that each isnt included for arrays
It certainly seems the answer will have to be put by guessing , because you posted a meager amount of code. But here is my try
I think this should be
this.model.models[hIndex].attributes.subMenus converted to this.collection.models[hIndex].attributes.subMenus assuming this.model refers to a collection which certainly has the models property.
Second if you need model at a index there is at method in collection thus simplifying to this.collection.at(hIndex).attributes,subMenus
third since we use collection (referring to subMenus) you could do this now this.collection.at(hIndex).get("subMenus").each(function(subMenu){alert(subMenu.toJSON());})
your final code must be ( with loads of assumptions)
var MenuListView = Backbone.View.extend({
el: '#menus',
reset: function () {
var hIndex = this.getTheMenuIndexHighlighting();
this.collection.at(hIndex).get("subMenus").each(function (subMenu) {
alert(subMenu.toJSON());
})
});
Update
Since you still seem to be noob to BBjs world. Create a view instance like below and you will have the above code work like jiffy
var viewInstance = new MenuListView({collection:Menu})
where Menu is the instance(new) of Collection (which you use)