If I use a comparator on a backbone collection, would I be able to nest the models that have the same attribute into a div? Examples
// My dummy json
[{name: 'Robert', team: 'Redskins'},{name: 'Aaron', team: 'Packers'},{name: 'Kirk', team: 'Redskins'}]
Then here is my collection.
var Players = Backbone.Collection.extend({
url: 'data/players.json',
comparator : 'team'
});
I see this is cool, because in my rendering view, I see the models are listed in order by teams, example.
<div>
<li>Robert</li>
<li>Redskins</li>
</div>
<div>
<li>Kirk</li>
<li>Redskins</li>
</div>
<div>
<li>Aaron</li>
<li>Packers</li>
</div>
Just in case here is the code for my view.
var Player = Backbone.View.extend({
el: '.app',
render: function() {
var self = this;
var players = new Players();
players.fetch({
success: function(players) {
var template = _.template($('#appTemplate').html(), {players: players.models});
self.$el.html(template);
}
});
}
});
Now to the meat, whats the cleanest way to group the teams into a nest, example below.
<div class="Redskins">
<div>
<li>Robert</li>
<li>Redskins</li>
</div>
<div>
<li>Kirk</li>
<li>Redskins</li>
</div>
</div>
<div class="Packers">
<div>
<li>Aaron</li>
<li>Packers</li>
</div>
</div>
I can't really think of any way to accomplish that without parsing/manipulating your data. You could hijack the toJSON() method or add a new method altogether. For example:
var Players = Backbone.Collection.extend({
url: 'data/players.json',
comparator : 'team',
toJSON: function() {
var team,
index = 0,
grouped = [],
raw = _.clone(this.attributes);
_.each(raw, function(element) {
var thisTeam = element.team;
if (thisTeam != team) { // create new grouping
grouped[index] = {
team: thisTeam,
players: []
}
grouped[index].players.push(element.name);
index++;
} else { // use existing grouping
grouped[index].players.push(element.name);
}
});
return grouped;
}
});
Caveat: this is untested code for illustration only. Anyway, this code will give you some JSON like:
[
{
team: "Packers",
players: ["Aaron"]
},
{
team: "Redskins",
players: ["Robert", "Kirk"]
}
]
Here is a sample Handlebars template:
{{#each this}}
<div class="{{team}}">
<div>
{{#each players}}
<li>{{this}}</li>
{{/each}}
</div>
</div>
{{/each}}
Related
I am trying to implement a accordion using angularfire . I am able to retrieve the top level list ("A1","D1","R1")for display but I am unable to figure out how to retrieve the child for each accordion tab that is selected. For Eg if I select "D1", it should open up and display "C1", "H1".
Here is my data on firebase
{
"A1" : {
"B1" : 50
},
"D1" : {
"C1 " : 98,
"H1" : 12
},
"R1" : {
"RR1" : 67,
"RD1" : 54
}
}
My code
var app=angular.module("sampleApp",["firebase"]);
app.controller("MyCtrl", ["$scope", "$firebaseArray", "$firebaseObject",
function($scope, $firebaseArray,$firebaseObject) {
var ref = firebase.database().ref("Products/");
var list = $firebaseArray(ref);
$scope.list = list;
$scope.activeTabs = [];
// check if the tab is active
$scope.isOpenTab = function (tab) {
// check if this tab is already in the activeTabs array
if ($scope.activeTabs.indexOf(tab) > -1) {
// if so, return true
return true;
} else {
// if not, return false
return false;
}
}
// function to 'open' a tab
$scope.openTab = function (tab) {
// check if tab is already open
if ($scope.isOpenTab(tab)) {
//if it is, remove it from the activeTabs array
$scope.activeTabs.splice($scope.activeTabs.indexOf(tab), 1);
} else {
// if it's not, add it!
$scope.activeTabs.push(tab);
}
}
}
]);
HTML Code
<div class="container accordion__container" ng-controller="MyCtrl">
<div class="accordion__tab" ng-repeat="products in list">
<div class="accordion__tab-title" ng-click="openTab(products.$id)">{{products.$id}} </div>
<div class="accordion__tab-content" ng-show="isOpenTab(products.$id)">
<div class="accordion__tab-contentdet" ng-repeat="productDet in <sub Product details>">
</div>
</div>
</div>
</div>
I made some changes in your code.
In HTML i used nav tabs.
<ul class="nav nav-tabs">
<li ng-repeat="products in list">
<a data-toggle="tab" href="#{{products.id}}">{{products.id}}</a>
</li>
</ul>
<div class="tab-content">
<div id="{{products.id}}" class="tab-pane fade" ng-repeat="products in list">
<h3>{{products.id}}</h3>
<p>Content : {{products.data}}.</p>
</div>
</div>
Controller
app.controller("MyCtrl", ["$scope", "$firebaseObject",
function($scope, $firebaseObject) {
var ref = firebase.database().ref("Products");
var list = $firebaseObject(ref);
list.$loaded().then(function() {
$scope.list = [];
angular.forEach(list, function(value,key){
$scope.list.push({ id: key, data: value})
})
});
}
]);
Another Method
Instead of using list.$loaded() you can use the below code:
ref.once('value', function(snapshot) {
$scope.list = [];
angular.forEach(snapshot.val(), function(value,key){
$scope.list.push({ id: key, data: value})
})
})
I just created a plunker for you. Please check it
https://plnkr.co/edit/5dOr7xAWIFlmdykAC1yh
if you have any doubt please let me know.
This is my first backbone code :)
How can I display my list here:
<title>list</title>
<ul id="container">
<li>
<%- name %>
</li>
</ul>
js:
var app = {}; // create namespace for our app
app.Mymodel = Backbone.Model.extend({
defaults:
{
name: ''
}
});
app.List = Backbone.Collection.extend({
model: app.Mymodel,
localStorage:new Store('vandaag')
});
// renders individual todo items list (li)
app.MyView = Backbone.View.extend({
el: '#container',
initialize: function () {
app.list = new app.List();
app.list.add({ name: 'piet' });
app.list.add({ name: 'ed' });
this.render();
},
render: function(){
this.$el.append(app.list);
//var view = new app.MyView({ model: new app.Mymodel({name:'ed',city:'ny'}));
//$('#todo-list').append(view.render().el);
}
});
app.myView = new app.MyView();
jsfiddle:http://jsfiddle.net/dingen2010/YBPG6/2/
First have your template created. In below fiddle it is template with ID list-template.
Then you can compile the template, add data to it and render the view.
Check this updated fiddle.
To know how Underscore Templates work try this.
So I am trying to acomplish this example: http://jsfiddle.net/pkozlowski_opensource/WXJ3p/15/
However, for some reason, when I click on one div, and then on another, it does not remove the "active" class from the previous div so it highlights both , hence all my divs end up with the class active if I click all of them. I want to make it to where it will actually remove the class if I click anywhere else on the body and also if I click on any other div, like the fiddle example.
My jsFIddle
http://jsfiddle.net/GeorgiAngelov/jUj56/4/
<div ng-controller="MyCtrl">
<button class="addQuestionButton btn btn-primary" ng-click="AddRootQuestion(questions)">Add node</button>
<div ng-repeat="question in questions" ng-include="question"> </div>
<script type="text/ng-template" id="question">
<!-- Single question starts here -->
<div ng-controller="QuestionController" ng-class="{active : isSelected(question)}">
<button class="btn btn-primary" ng-click="AddSubQuestion(question)">Add node</button>
<button class="btn btn-primary" ng-click = "editQuestion(question)">Edit Question</button>
</div>
<div ng-repeat="question in question.nodes" ng-include="question">
</div>
</script>
</div>
Since each single question has its own QuestionController, and QuestionControlleris where $scope.selected is being set, they don't interact with each other. That is to say, when you click edit, it sets selected for that individual controller.
The easy way to fix it would be to set a property on a parent scope (the containing controller) when clicking edit:
function MyCtrl($scope) {
$scope.questions = [];
$scope.AddRootQuestion = function(questions) {
questions.push({name: 'Question', nodes: []});
};
// added this method --v
$scope.setSelected = function(q) {
$scope.selected = q;
};
}
function QuestionController($scope) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
question.nodes.push({name: newName, id: 'it goes in here', nodes: []});
};
// modified this method --v
$scope.editQuestion = function(question){
$scope.setSelected(question);
};
$scope.isSelected = function(question) {
return $scope.selected === question;
};
}
But this makes QuestionController dependent upon a parent controller. A much better design would be to move all the data and data manipulation methods into a service:
var myApp = angular.module('myApp',[]);
myApp.factory('Question', function() {
return {
questions: [],
addRootQuestion: function() {
this.questions.push({name: 'Question', nodes: []});
},
addSubQuestion: function(question, data) {
question.nodes.push(data);
},
setSelectedQuestion: function(question) {
this.selectedQuestion = question;
},
isSelectedQuestion: function(question) {
return this.selectedQuestion == question;
}
};
});
You can then inject the service into your controllers:
function MyCtrl($scope, Question) {
$scope.questions = Question.questions;
$scope.AddRootQuestion = function() {
Question.addRootQuestion();
};
}
function QuestionController($scope, Question) {
$scope.choices = [];
$scope.choice = {text: ''};
$scope.AddSubQuestion = function(question, $element) {
var newName = 'Name of question goes here';
Question.addSubQuestion(question, {
name: newName, id: 'it goes in here', nodes: []
});
};
$scope.editQuestion = function(question){
Question.setSelectedQuestion(question);
};
$scope.isSelected = function(question) {
return Question.isSelectedQuestion(question);
};
}
Example: http://jsfiddle.net/BinaryMuse/pZkBv/
If you wanted, you could build up a richer data model using JavaScript OOP principles; for example, you could have Question instances with methods for manipulating the questions, which themselves live inside an instance of QuestionContainer (or Survey or Test or whatever).
I was playing around with Knockout and Backbone these past few days and came across the KnockbackJS framework which sort of combines the binding powers of Knockout with data modeling magic of Backbone.
In this simple example I have a Backbone collection of Backbone models and I'm having problems binding them to my HTML.
This is my javascript:
var Model = Backbone.Model.extend({
defaults: {
firstName: "",
lastName: ""
}
});
var Collection = Backbone.Collection.extend({
data : Model
});
var temp1 = new Model({firstName: "aaa", lastName:"bbb"});
console.log("new model");
var temp2 = new Model({firstName: "BBB", lastName:"CCCC"});
console.log("newest model");
var collection = new Collection([temp1, temp2]);
//---------Knockout/Backbone bridge---------
var view_model = kb.viewModel(collection, { read_only: true });
ko.applyBindings(view_model);
...and my simple HTML:
<div data-bind="foreach: data">
<span data-bind="text: name"></span>
<span data-bind="text: artist"></span>
</div>
This example in JSfiddle
I can't get to the data inside of a model in a collection. Any clues, hints, tips?
I've got it, here is the JS code:
console.clear();
var PersonModel = Backbone.Model.extend();
var model1 = new PersonModel({firstName: "aaa", lastName:"bbb"});
var model2 = new PersonModel({firstName: "CCCC", lastName:"DDDD"});
var PersonViewModel = function (person) {
this.firstName = kb.observable(person, 'firstName');
this.lastName = kb.observable(person, 'lastName');
this.fullName = ko.computed((function () {
return "" + (this.firstName()) + " " + (this.lastName());
}), this);
};
var PersonsModel = Backbone.Collection.extend({ model: PersonModel});
var personsModel = new PersonsModel([model1,model2]);
var PersonsViewModel = function (persons) {
this.persons = kb.collectionObservable(persons);
};
var personsViewModel = new PersonsViewModel(personsModel);
ko.applyBindings(personsViewModel, $('#kb_collection_observable')[0]);
...and here is the HTML:
<div id="kb_collection_observable">
<div data-bind="if: persons">
<span>Data </span>
</div>
<div data-bind="foreach: persons">
<p>First name: <input class='text' data-bind="value: firstName" /></p>
<p>Last name: <input class='input-small pull-right' data-bind="value: lastName" /></p>
</div>
</div>
Thanks to this SO post
So I am stuck. I got the great Backbone.Marionette to handle my nested childs/parents relationships and rendering(doing it with the bare backbone was a nightmare), but now i'm facing problems with my nested composite view,
I'm always getting a The specified itemViewContainer was not found: .tab-content from the parent composite view - CategoryCollectionView, although the itemViewContainer is available on the template, here is what I'm trying to do, I have a restaurant menu i need to present, so I have several categories and in each category I have several menu items, so my final html would be like this:
<div id="order-summary">Order Summary Goes here</div>
<div id="categories-content">
<ul class="nav nav-tabs" id="categories-tabs">
<li>Appetizers</li>
</ul>
<div class="tab-content" >
<div class="tab-pane" id="category-1">
<div class="category-title">...</div>
<div class="category-content">..the category items goes here.</div>
</div>
</div>
Here is what I have so far:
First the templates
template-skeleton
<div id="order-summary"></div>
<div id="categories-content"></div>
template-menu-core
<ul class="nav nav-tabs" id="categories-tabs"></ul>
<div class="tab-content" ></div>
template-category
<div class="category-title">
<h2><%=name%></h2>
<%=desc%>
</div>
<div class="category-content">
The menu items goes here
<ul class="menu-items"></ul>
</div>
template-menu-item
Item <%= name%>
<strong>Price is <%= price%></strong>
<input type="text" value="<%= quantity %>" />
Add
Now the script
var ItemModel = Backbone.Model.extend({
defaults: {
name: '',
price: 0,
quantity: 0
}
});
var ItemView = Backbone.Marionette.ItemView.extend({
template: '#template-menuitem',
modelEvents: {
"change": "update_quantity"
},
ui: {
"quantity" : "input"
},
events: {
"click .add": "addtoBasket"
},
addtoBasket: function (e) {
this.model.set({"quantity": this.ui.quantity.val() });
},
update_quantity: function () {
//#todo should we do a re-render here instead or is it too costy
this.ui.quantity.val(this.model.get("quantity"));
}
});
var ItemCollection = Backbone.Collection.extend({
model: ItemModel
});
var CategoryModel = Backbone.Model.extend({
defaults: {
name: ''
}
});
var CategoryView = Backbone.Marionette.CompositeView.extend({
template: '#template-category',
itemViewContainer: ".menu-items",
itemView: ItemView,
className: "tab-pane",
id: function(){
return "category-" + this.model.get("id");
},
initialize: function () {
this.collection = new ItemCollection();
var that = this;
_(this.model.get("menu_items")).each(function (menu_item) {
that.collection.add(new ItemModel({
id: menu_item.id,
name: menu_item.name,
price: menu_item.price,
desc: menu_item.desc
}));
});
}
});
var CategoryCollection = Backbone.Collection.extend({
url: '/api/categories',
model: CategoryModel
});
var CategoryCollectionView = Backbone.Marionette.CompositeView.extend({
el_tabs: '#categories-tabs',
template: '#template-menu-core',
itemViewContainer: ".tab-content", // This is where I'm getting the error
itemView: CategoryView,
onItemAdded: function (itemView) {
alert("halalouya");
//this.$el.append("<li>" + tab.get("name") + "</li>");
//$(this.el_tabs).append("<li><a href='#category-" + itemView.model.get("id") + "'>"
//+ itemView.model.get("name") + "</a></li>")
}
});
I know It's a bit hard to follow but you guys are my last resort. There is no problems with the templates and the cateogry fetching and the other stuff(it was already working before converting the CategoryCollectionView from a Marionette collection to a composite view.)
Edit 1
Added App initalizer on request:
AllegroWidget = new Backbone.Marionette.Application();
AllegroWidget.addInitializer(function (options) {
// load templates and append them as scripts
inject_template([
{ id: "template-menuitem", path: "/js/templates/ordering-widget-menuitem.html" },
{ id: "template-category", path: "/js/templates/ordering-widget-category.html" },
{ id: "template-menu-core", path: "/js/templates/ordering-widget-menu-core.html" },
{ id: "template-skeleton", path: "/js/templates/ordering-widget-skeleton.html" }
]);
// create app layout using the skeleton
var AppLayout = Backbone.Marionette.Layout.extend({
template: "#template-skeleton",
regions: {
order_summary: "#order-summary",
categories: "#categories-content"
}
});
AllegroWidget.layout = new AppLayout();
var layoutRender = AllegroWidget.layout.render();
jQuery("#allegro-ordering-widget").html(AllegroWidget.layout.el);
// Initialize the collection and views
var _category_collection = new CategoryCollection();
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
_category_collection.fetch({
beforeSend: function (xhr) {
xhr.setRequestHeader("X-ApiKey", window.XApiKey);
},
async: false
});
//AllegroWidget.addRegions({
/// mainRegion: "#allegro-ordering-widget"
//});
AllegroWidget.layout.categories.show(_cateogories_view);
});
AllegroWidget.start({api_key: window.XApiKey});
You are adding to the collection via fetch before you call show on the region.
Marionette.CompositeView is wired by default to append ItemViews when models are added to it's collection. This is a problem as the itemViewContainer .tab-content has not been added to the dom since show has not been called on the region.
Easy to fix, rework you code as below and it should work without overloading appendHtml.
// Initialize the collection and views
var _category_collection = new CategoryCollection();
// grab a promise from fetch, async is okay
var p = _category_collection.fetch({headers: {'X-ApiKey': window.XApiKey});
// setup a callback when fetch is done
p.done(function(data) {
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
AllegroWidget.layout.categories.show(_cateogories_view);
});
okay this is pretty weird but adding this in the CategoryCollectionView class:
appendHtml: function (collectionView, itemView, index) {
//#todo very weird stuff, assigning '.tab-content' to itemViewContainer should have been enough
collectionView.$(".tab-content").append(itemView.el);
}
solved the problem, however i have no idea why it works, asssigning '.tab-content' to the itemViewContainer should have been enough, any idea?