Making a Rails app with Backbone on the frontend. There are two Backbone models, Publications and Articles. I am having issues rendering one of my Backbone views. Here is the code that is giving me an issue:
SimpleGoogleReader.Views.PublicationsIndex = Backbone.View.extend({
template: JST['publications/index'],
el: '#publication',
events:{
'click #new_feed': 'createFeed'
},
initialize: function() {
this.listenTo(this.model, "sync", this.render);
},
render: function(){
this.$el.html( this.template({publications: this.model.toJSON()}) );
return this;
},
createFeed: function(e){
e.preventDefault();
var feed_url = $('#new_feed_name').val();
// var that = this;
this.model.create(
{url: feed_url},
{ success: function(data){
$.post('/articles/force_update', {url: feed_url, publication_id: data.id}, function(data){
});
}
}
);
}
});
I know the render function is getting called and when I console.log(this.model.toJSON()) it does not return the JSON objects. However the render function works just fine within the Articles View:
SimpleGoogleReader.Views.ArticlesIndex = Backbone.View.extend({
template: JST['articles/index'],
el: '#article',
initialize: function() {
this.listenTo(this.model, "sync", this.render);
},
render: function(){
console.log(this.model.toJSON());
this.$el.html( this.template({articles: this.model.toJSON()}) );
return this;
}
});
** Edit ** here is my router where I instantiate the views:
SimpleGoogleReader.Routers.Publications = Backbone.Router.extend({
routes: {
'': 'home',
'all_articles': 'get_all_articles',
'publications/:id': 'articles_by_id',
'publications/:id/delete': 'delete_publication'
},
home: function(){
var publications = new SimpleGoogleReader.Collections.Publications();
var articles = new SimpleGoogleReader.Collections.Articles();
var pubIndex = new SimpleGoogleReader.Views.PublicationsIndex({model: publications});
var artIndex = new SimpleGoogleReader.Views.ArticlesIndex({model: articles});
articles.listenTo(publications, "sync", function(){
articles.fetch( {success: function(){}} );
});
publications.fetch();
},
Any ideas on where the discrepancy may lie?
Related
I can not understand how to show a single product. When I want to show product page (model view - app.ProductItemView in productPageShow) I get "Uncaught TypeError: Cannot read property 'get' of undefined at child.productPageShow (some.js:58)" >> this.prod = this.prodList.get(id);
Here is my code:
// Models
var app = app || {};
app.Product = Backbone.Model.extend({
defaults: {
coverImage: 'img/placeholder.png',
id: '1',
name: 'Unknown',
price: '100'
}
});
app.ProductList = Backbone.Collection.extend({
model: app.Product,
url: 'php/listProducts.php'
});
// Views
app.ProductListView = Backbone.View.extend({
el: '#product-list',
initialize: function() {
this.collection = new app.ProductList();
this.collection.fetch({ reset: true });
this.render();
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each(function(item) {
this.renderProduct(item);
}, this);
},
renderProduct: function(item) {
app.productView = new app.ProductView({
model: item
});
this.$el.append(app.productView.render().el);
}
});
app.ProductItemView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#productPage').html()),
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
app.ProductView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#productTemplate').html()),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
// Router
app.Router = Backbone.Router.extend({
routes: {
"list": 'list',
"product/:id": "productPageShow"
},
initialize: function() {
this.$content = $("#product-list");
},
list: function() {
this.prodList = new app.ProductList();
this.productListView = new app.ProductListView({ model: this.prodList });
this.prodList.fetch();
this.$content.html(app.productListView.el);
},
productPageShow: function(id) {
this.prod = this.prodList.get(id);
this.prodItView = new app.ProductItemView({ model: this.prod });
this.$content.html(this.prodItView.el);
}
});
$(function() {
new app.Router();
Backbone.history.start();
});
There are some conceptual problems with the code, without getting into too much details, there are a lot of things happening in the Router that don't belong there, but for a (currently) non-complex application that's manageable.
I'll focus on the app.Router file because that's the culprit of your problems most probably.
routes: {
"list": 'list',
"product/:id": "productPageShow"
}
Let's start with the basics, when you define a list of routes in Backbone Router( or any other Router in other frameworks ) you give a route a key that will correspond to something in the URL that the Router will recognize and call a callback method.
If you navigate your browser to:
http://your-url#list
Backbone will call the list callback
Similarly:
http://your-url#product/1
Backbone will call productPageShow callback
Thing to know: ONLY ONE ROUTE CALLBACK CAN EVER BE CALLED! The first time a Backbone Router finds a matching route it will call that callback and skip all others.
In your code you're relying on the fact that this.prodList will exist in productPageShow method but that will only happen if you first go to list route and then to product/{id} route.
Another thing .. in your listcallback in the Router you set a model on the ProductListView instance .. but that model is neither user, nor is it a model since this.productList is a Backbone.Collection
Additionally, you need to know that fetch is an asynchronous action, and you're not using any callbacks to guarantee that you'll have the data when you need it ( other than relying on the 'reset' event ).
So this would be my attempt into making this workable:
// Models
var app = app || {};
app.Product = Backbone.Model.extend({
defaults: {
coverImage: 'img/placeholder.png',
id: '1',
name: 'Unknown',
price: '100'
}
});
app.ProductList = Backbone.Collection.extend({
model: app.Product,
url: 'php/listProducts.php'
});
// Views
app.ProductListView = Backbone.View.extend({
el: '#product-list',
initialize: function() {
this.render();
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.collection.each(function(item) {
this.renderProduct(item);
}, this);
},
renderProduct: function(item) {
app.productView = new app.ProductView({
model: item
});
this.$el.append(app.productView.render().el);
}
});
app.ProductItemView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#productPage').html()),
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
app.ProductView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#productTemplate').html()),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
// Router
app.Router = Backbone.Router.extend({
routes: {
"": "list",
"product/:id": "productPageShow"
},
initialize: function() {
this.$content = $("#product-list");
},
list: function() {
this.prodList = new app.ProductList();
this.productListView = new app.ProductListView({ collection: this.prodList });
this.prodList.fetch({reset:true});
this.$content.html(app.productListView.el);
},
productPageShow: function(id) {
try {
this.prod = this.prodList.get(id);
this.prodItView = new app.ProductItemView({ model: this.prod });
this.$content.html(this.prodItView.el);
} catch (e) {
// Navigate back to '' route that will show the list
app.Router.navigate("", {trigger:'true'})
}
}
});
$(function() {
app.Router = new app.Router();
Backbone.history.start();
});
So with a bit of shooting in the dark without the complete picture, this is what changed:
ProductListView is no longer instantiating ProductList collection that is done in the list callback in Router
Changed the route from 'list' to '', that will guarantee that the list is shown immediately
In case there are no product data available in productPageShow navigate back to list
This is my first time using backbone, so I'm pretty confused about everything. I'm trying to make a todo list. Once I click "finished" on the todo, I want it to append to the "Completed" list.
I've been following this tutorial, and I tried to replicate the code(I tried to create a new completedTodo view and stuff like that), and I tried to do when clicking "finished" it would delete the $el, and I would add to the completedTodos. I think the problem here is even if it's added, it's not doing anything.
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
I put in a debugger there to see if it actually added to the collection, and when i did completedTodos.toJSON();, it does give me back the new thing I just added.
However, it does not append to my collection list.
Here is my whole entire script file, in case I named anything wrong.
var Todo = Backbone.Model.extend({
defaults: {
task: '',
priority: ''
}
});
var CompletedTodo = Backbone.Model.extend({
defaults: {
completedTask: '',
completedPriority: ''
}
});
var Todos = Backbone.Collection.extend({});
var todos = new Todos();
var CompletedTodos = Backbone.Collection.extend({});
var completedTodos = new CompletedTodos();
//Backbone view for one todo
var TodoView = Backbone.View.extend({
model: new Todo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.todos-list-template').html());
},
events: {
'click .finished-todo': 'done',
'click .delete-todo' : 'delete'
},
done: function() {
var completed = new CompletedTodo({
completedTask: this.$('.task').html(),
completedPriority: this.$('.priority').html()
});
completedTodos.add(completed);
this.model.destroy();
},
delete: function() {
this.model.destroy();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//Backbone view for all todos
var TodosView = Backbone.View.extend({
model: todos,
el: $('.todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
this.model.on('remove', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(todo) {
self.$el.append((new TodoView({model: todo})).render().$el);
});
return this;
}
});
//View for one Completed Todo
var CompletedTodoView = Backbone.View.extend({
model: new CompletedTodo(),
tagName: 'tr',
initialize: function() {
this.template = _.template($('.completed-todos-template').html());
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
//View for all Completed Todos
var CompletedTodosView = Backbone.View.extend({
model: completedTodos,
el: $('.completed-todos-list'),
initialize: function() {
this.model.on('add', this.render, this);
},
render: function() {
var self = this;
this.$el.html('');
_.each(this.model.toArray(), function(completedTodo) {
self.$el.append((new CompletedTodoView({model: completedTodo})).render().$el);
});
return this;
}
});
var todosView = new TodosView();
$(document).ready(function() {
$('.add-todo').on('click', function() {
var todo = new Todo({
task: $('.task-input').val(),
priority: $('.priority-input').val()
});
$('.task-input').val('');
$('.priority-input').val('');
todos.add(todo);
});
});
After this, I also have to figure out how to use Parse to make it persist to the database. I figured I'd get everything working in backbone first, and then try to do put in the database. I'm also suppose to use node/express, so would that help? I'm pretty much a Ruby on Rails kind of person, so I don't really know any of these javascript framework type of stuff.
Thanks for your help!
Alright,
It was just because I didn't initialize the view.
var completedTodosView = new CompletedTodosView();
This fixed it.
I'm new to backbone.js and I worked through Jeffery Way's tutorial with Laravel and Backbone. I currently have a list of teams displayed and attached their ids from the database. I set up an event so when I click on a team it sends a get request to laravel and returns all users that have that team id.
The request works great, except I'm stuck trying to generate a usersview. I can't pull out any data from the collection in the App.Views.Users other than an id.
Am I doing this correctly?
View.js
//Global App View
App.Views.App = Backbone.View.extend({
initialize: function() {
var allTeamsView = new App.Views.Teams({ collection: App.teams }).render();
}
});
App.Views.Teams = Backbone.View.extend({
tagName: 'div',
events: {
"click a" : "teamClicked"
},
teamClicked: function(e){
e.preventDefault();
var team_id = e.target.id;
teamusers = new App.Models.Team({id: team_id});
collection = new App.Collections.Teams([teamusers]);
teamusers.fetch();
view = new App.Views.Users({collection: collection, el: '#usersList'});
view.render();
return false;
},
attributes: function() {
return{
class: 'span2 admin teams',
id: 'inner-content-div'
};
},
render: function(){
this.collection.each( this.addOne, this );
return this;
},
addOne: function(team){
var teamView = new App.Views.Team({model: team});
this.$el.append(teamView.render().el);
}
});
// Single Team View
App.Views.Team = Backbone.View.extend({
tagName: 'li',
template: template('allTeamsTemplate'),
attributes: function() {
return{
id: this.model.get('id')
};
},
render: function(){
this.$el.html( this.template( this.model.toJSON() ));
return this;
}
});
App.Views.Users = Backbone.View.extend({
tagName: 'ul',
render: function(){
this.collection.each( this.addOne, this );
console.log(collection);
return this;
},
addOne: function(user){
var userView = new App.Views.User({model: user });
this.$el.append(userView.render().el);
}
});
// Single User View
App.Views.User = Backbone.View.extend({
tagName: 'li',
template: template('allUsersTemplate'),
attributes: function() {
return{
id: this.model.get('id')
};
},
render: function(){
this.$el.html( this.template( this.model.toJSON() ));
return this;
}
});
TeamController.php
public function show($id)
{
return Team::find($id)->user;
}
Index.Blade.php
<div class="teamContainer" id="demo"></div>
<ul class="userContainer" id="usersList"></ul>
<script type="text/template" id="allUsersTemplate">
<p><%= id %></p>
</script>
<script type="text/template" id="allTeamsTemplate">
<a id='<%= teamNum %>' ><%= teamName %></a>
</script>
I got it working. The problem was with my event.
View.js
teamsUserList: function(e){
e.preventDefault();
var team_id = e.target.id;
var collection = new App.Collections.TeamsUsers([], {id: team_id});
collection.fetch().complete(function() {
var view= new App.Views.Users({collection: collection});
$('#usersList').append(view.render().el);
});
},
I still trying to remove (destroy) model from my collection. Data is groupedBy and rendered into accordion style. But when I click to X in my console is notice :
Uncaught Uncaught TypeError: Cannot call method 'destroy' of undefined
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.vent = _.extend({}, Backbone.Events);
})();
// !models.js
App.Models.Item = Backbone.Model.extend({});
// !collections.js
App.Collections.Items = Backbone.Collection.extend({
model: App.Models.Item,
url: 'api/items.json'
});
// !views.js
App.Views.Items = Backbone.View.extend({
el: '#items',
events: {
'click .cccc':'deleteItem',
},
deleteItem: function() {
this.model.destroy();
},
initialize: function() {
this.listenTo( this.collection, "change", this.render );
this.template = _.template( document.getElementById('productsCategoriesTemlate').innerHTML );
this.render();
this.$el.accordion({ animate: 0 });
},
getGroups : function(){
return _.groupBy(this.collection.toJSON(), 'category');
},
render: function() {
this.el.innerHTML = this.template({ data : this.getGroups() });
},
addOne: function(item) {
// ????????????
}
});
App.Views.Item = Backbone.View.extend({
deleteItem: function() {
this.model.destroy();
},
// ???????????
});
// !router.js
App.Router = Backbone.Router.extend({
routes: {
'':'index',
},
index: function() {
console.log('index page !');
},
});
new App.Router;
Backbone.history.start();
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
new App.Views.Items({ collection: App.items });
});
template :
<script id="productsCategoriesTemlate" type="text/template">
<% _.each( data, function( category, i ){ %>
<h3 class="category-name"><%= i %></h3>
<div><% _.each( category, function( item ){ %>
<li class="product"><%= item.title %><p style="float:right;" class="cccc">X</p></li>
<% }) %>
</div>
<% }) %>
</script>
Where do you instantiate Apps.Views.Items? Is this your 'collection view'? If this view is representing your collection, you have to somehow pass or reference the model on 'deleteItem'.
App.Views.Items does not represent a single model, so this.model would be incorrect.
UPDATE
You should have a separate view for each item, such as App.Views.Item, and loop through and create this view for each model in App.Views.Items' collection.
2nd UPDATE
Yeah, you are getting it. Here's some sample code I threw together (I haven't tested it, so you might have to adjust it, but it gives a good idea. The template rendering syntax might be incorrect as I don't usually do it manually).
App.Views.Items = Backbone.View.extend({
render: function() {
this.collection.each(function(model) {
var view = new App.Views.Item({ model: model });
this.$el.append(view.render().el);
});
},
});
App.Views.Item = Backbone.View.extend({
template: _.template($('#itemViewTemplate')),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
},
});
App.items = new App.Collections.Items;
App.items.fetch().then(function() {
var items = new App.Views.Items({ collection: App.items });
$('body').append(items.render().$el);
});
By the way, once you get the hang of Backbone and how it works, you should try out Marionette.js. It makes all of this kind of thing much simpler.
Have som trouble getting the code below to work.
I am trying to fire an event from a rendered sub view that has its own events object.
Is it possible to do this in an easy way?
var SubView = Backbone.View.extend({
events: {
'click .subview-item a': 'test'
},
el: '#subview',
test: function() {
console.log('please print this...');
},
initialize: function() {
this.template = '<div class="subview-item">Clickable Subview</div>'
},
render: function(){
$(this.el).html(_.template(this.template));
return this;
}
});
var MainView = Backbone.View.extend({
el: $('#content'),
initialize: function(){
this.template = '<h1>Hello</h1><div id="subview"></div>';
this.subView = new SubView();
},
render: function(){
$(this.el).html(_.template(this.template));
this.subView.render();
return this;
}
});
var mainView = new MainView({});
mainView.render();
Any ideas??
When you create your subView inside MainView's initialize, the #subview element does not yet exist in your DOM, since you have not yet rendered MainView. So a new <div> is created outside the DOM. You need to first render MainView before creating the SubView. You could do that inside the MainView's render() but the following is simpler I think:
var SubView = Backbone.View.extend({
events: {
'click .subview-item a': 'test'
},
el: '#subview',
test: function() {
console.log('please print this...');
},
initialize: function() {
this.template = _.template('<div class="subview-item">Clickable Subview</div>');
},
render: function() {
this.$el.html(this.template);
return this;
}
});
var MainView = Backbone.View.extend({
el: $('#content'),
initialize: function() {
this.template = _.template('<h1>Hello</h1><div id="subview"></div>');
},
render: function() {
this.$el.html(this.template);
return this;
}
});
var mainView = new MainView();
mainView.render();
var subView = new SubView();
subView.render();
Also took the liberty to correct a few things like using this.$el and creating the template on initialize() rather than recompiling on every render().