I am trying to get a Collection to render in a Backbone View, but it won't populate out the HTML. It will console log after the fetch, but does not console log inside of the _.each. Any clues? I am new to Backbone and am looking for assistance with populating based on REST calls. It works with hard data (inline entries from an array), but seems to trip up on the REST.
<script>
var featuredArticle = Backbone.Model.extend({
defaults: {
id: '',
headLine: '',
snippet: '',
fullStory: '',
location: '',
nsfw: '',
category: '',
relatedArticleIds: '',
hasVideoPlaceholder: '',
numberOfImages: ''
}
});
var featuredArticles = Backbone.Collection.extend({
model: featuredArticle,
url: 'http://myrestendpoint'
});
var articleView = Backbone.View.extend({
tagName: 'ul',
render: function () {
var that = this;
var getfeaturedArticles = new featuredArticles();
getfeaturedArticles.fetch();
console.log(getfeaturedArticles);
_.each(that.collection, function (value) {
console.log(value.headLine);
$(that.el).append('<li>' + value.headLine + '</li>');
})
return that;
}
});
var articles = new articleView({collection: featuredArticles});
$("body").append(articles.render().el);
</script>
While creating articleView instance, you need to pass an instance of collection not the variable containing the definition.
Hence change :
var articles = new articleView({collection: featuredArticles});
to
var articles = new articleView({collection: new featuredArticles});
In articleView's render method, use the collection for fetch.
Change :
var getfeaturedArticles = new featuredArticles();
getfeaturedArticles.fetch();
console.log(getfeaturedArticles);
to
this.collection.fetch();
console.log(this.collection);
Related
I wonder if someone can help to find what's wrong in this case. I get "Uncaught ReferenceError: text is not defined" in line 6 app.js:
((__t=( text ))==null?'':_.escape(__t))+
driver.js:
var Marionette = require('backbone.marionette');
var TodoView = require('./views/layout');
var initialData = {
items: [
{assignee: 'Scott', text: 'Write a book about Marionette'},
{assignee: 'Andrew', text: 'Do some coding'}
]
};
var App = new Marionette.Application({
onStart: function(options) {
var todo = new TodoView({
collection: new Backbone.Collection(options.initialData.items),
model: new ToDoModel()
});
todo.render();
todo.triggerMethod('show');
}
});
App.start({initialData: initialData});
views/layout.js
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var ToDoModel = require('../models/todo');
var FormView = require('./form');
var ListView = require('./list');
var Layout = Marionette.View.extend({
el: '#app-hook',
template: require('../templates/layout.html'),
regions: {
form: '.form',
list: '.list'
},
collectionEvents: {
add: 'itemAdded'
},
onShow: function() {
var formView = new FormView({model: this.model});
var listView = new ListView({collection: this.collection});
this.showChildView('form', formView);
this.showChildView('list', listView);
},
onChildviewAddTodoItem: function(child) {
this.model.set({
assignee: child.ui.assignee.val(),
text: child.ui.text.val()
}, {validate: true});
var items = this.model.pick('assignee', 'text');
this.collection.add(items);
},
itemAdded: function() {
this.model.set({
assignee: '',
text: ''
});
}
});
module.exports = Layout;
todoitem.html
<%- item.text %> — <%- item.assignee %>
Any can me explain why text is not defined?
check your ToDoModel for a typo, the Backbone Model field should be "defaults" not "default", while parsing for a template Marionette view looks for "defaults" field:
https://marionettejs.com/docs/master/template.html#rendering-a-model
so the ToDoModel code should go like this:
...
var ToDo = Backbone.Model.extend({
defaults: {
assignee: '',
text: ''
},
...
You should take a look at the Marionnette's ItemView documentation which explain how to render a template with custom data.
var my_template_html = '<div><%= args.name %></div>'
var MyView = Marionette.ItemView.extend({
template : function(serialized_model) {
var name = serialized_model.name;
return _.template(my_template_html)({
name : name,
some_custom_attribute : some_custom_key
});
}
});
new MyView().render();
Note that using a template function allows passing custom arguments
into the .template function and allows for more control over how the
.template function is called.
With the code you provided at the moment, I can't help.
Marionette calls 'serializeModel' before passing the context to 'template'. So, if you have a backbone.model like
{
.
.
.
attributes: {
text: 'someText',
asignee: 'someAsignee'
}
.
.
}
your template will receive
{
text: 'someText',
assignee: 'someAsignee'
}
I have worked with handlebars but not underscore exactly. There {{this.text}} and {{this.assignee}} works like a charm in the template. So, try this.text or text in place of item.text, see if that works
I am relatively new to Backbone.js and having difficulty rendering a subView. I have subViews in other parts of the app working properly, but I cant even render simple text in this one.
View:
Feeduni.Views.UnifeedShow = Backbone.View.extend({
template: JST['unifeeds/show'],
tagName: "section",
className: "unifeed-show",
render: function() {
var content = this.template({ unifeed: this.model });
this.$el.html(content);
var subView;
var that = this;
this.model.stories().each(function(stories) {
subView = new Feeduni.Views.StoriesShow({ model: stories });
that.subViews.push(subView);
that.$el.find(".show-content").append(subView.render().$el);
});
return this;
},
});
Subview:
Feeduni.Views.StoriesShow = Backbone.View.extend({
template: JST['stories/show'],
tagName: "div",
className: 'stories-show',
render: function() {
this.$el.text("Nothing shows up here");
return this;
},
});
Model:
Feeduni.Models.Unifeed = Backbone.Model.extend({
urlRoot: "/api/uninews",
stories: function() {
this._stories = this._stories || new Feeduni.Subsets.StoriesSub([], {
parentCollection: Feeduni.all_unifeeds
});
return this._stories;
},
});
The text "Nothing shows up here" should be displaying in the "show content" element, but all I get is this:
<section class="unifeed-show">
<article class="show-content">
</article>
</section>
Below is a slight modification of your code showing a working main view managing some sub-views.
var UnifeedShow = Backbone.View.extend({
// I've hard-coded the template here just for a sample
template: _.template("Feed: <%= feedName %><br/> <ul class='show-content'></ul>"),
className: "unifeed-show",
initialize: function () {
// Create an array to store our sub-views
this.subViews = [];
},
render: function () {
var content = this.template(this.model.toJSON());
this.$el.html(content);
var subView;
var that = this;
var subViewContent = this.$el.find(".show-content");
this.model.stories().each(function (story) {
var subView = new StoryShow({
model: story
});
this.subViews.push(subView);
subViewContent.append(subView.render().$el);
}, this);
return this;
}
});
var StoryShow = Backbone.View.extend({
tagName: 'li',
// This template will show the title
template: _.template('Title: <%= title %>'),
className: 'stories-show',
render: function () {
var content = this.template(this.model.toJSON());
this.$el.html(content);
return this;
},
});
var Unifeed = Backbone.Model.extend({
stories: function () {
// I'm just returning the value set on this model as a collection;
// You may need to do something different.
return new Backbone.Collection(this.get('stories'));
}
});
// ================================
// Code below is creating the model & view, then rendering
// ================================
// Create our model
var feed = new Unifeed();
// Put some data in the model so we have something to show
feed.set('feedName', 'A Sample Feed');
feed.set('stories', [{
title: "Story #1",
id: 1
}, {
title: "Story #2",
id: 5
}]);
// Create our main view
var mainView = new UnifeedShow({
model: feed,
el: $('#main')
});
// Render it, which should render the sub-views
mainView.render();
Here's a working JSFiddle:
https://jsfiddle.net/pwagener/7o9k5d6j/7/
Note that while this manual sort of sub-view management works OK, you'll be better off using something like a Marionette LayoutView to help manage parent and sub-views. It builds good best practices for this sort of thing without you needing to do it yourself.
Have fun!
The subview is named Feeduni.Views.StoriesShow but in your main view you are instantiating new Feeduni.Views.StoryShow. Name them consistently and see if you still have problems.
this is my backbone code ,where iam using a underscore template,it is working fine now i am trying to make that template external and load from there
this is my sample backbone code
Backbone Application
<div class="list"></div>
<script id="personTemplate" type="text/template">
<strong><%= name %></strong> (<%= age %>) - <%= occupation %>
</script>
<script type="text/javascript">
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest User',
age: 23,
occupation: 'worker'
}
});
var PersonView = Backbone.View.extend({
tagName: 'li',
template: _.template( $('#personTemplate').html()),
initialize: function(){
this.render();
},
render: function(){
this.$el.html( this.template(this.model.toJSON()));
}
});
var person = new Person; // a person object created...
//person.set('name', 'abhi');
var personView = new PersonView({ model: person });
personView.el // ---->; You can call this method and it will display the view..
$(document.body).html(personView.el);
</script>
</body>
</html>
what is really need is that i will place this template <strong><%= name %></strong> (<%= age %>) - <%= occupation %> in a external file. i dont know much about its extension,i think it can be a html file and load the template from there ,i researched about it and find some sample code and tried some thing like this
<script type="text/javascript">
_.mixin({templateFromUrl: function (url, data, settings) {
var templateHtml = "";
this.cache = this.cache || {};
if (this.cache[url]) {
templateHtml = this.cache[url];
alert(templateHtml);
} else {
$.ajax({
url: url,
method: "GET",
async: false,
success: function(data) {
templateHtml = data;
alert(templateHtml);
}
});
this.cache[url] = templateHtml;
}
return _.template(templateHtml, data, settings);
}});
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest User',
age: 23,
occupation: 'worker'
}
});
var PersonView = Backbone.View.extend({
tagName: 'li',
template:_.templateFromUrl("templatefile1.html", {"name": "value","age":10}) ,
// template:_.templateFromUrl("templatefile1.html", this.model.toJSON()) ,
//template: _.template( $('#personTemplate').html()),
initialize: function(){
this.render();
},
render: function(){
alert(this.template);
this.$el.html( this.template);
}
});
var person = new Person; // a person object created...
//person.set('name', 'abhi');
var personView = new PersonView({ model: person });
personView.el // ---->; You can call this method and it will display the view..
$(document.body).html(personView.el);
</script>
but when i am calling teemplate like this template:_.templateFromUrl("templatefile1.html", {"name": "value","age":10}) ,
it is working
but when i trying to pass the pass model to that template like this
template:_.templateFromUrl("templatefile1.html", this.model.toJSON())
i am getting exception like
Uncaught TypeError: Cannot read property 'toJSON' of undefined
how can i resolve this issue
You're saying something like this:
var PersonView = Backbone.View.extend({
//...
template: _.templateFromUrl("templatefile1.html", this.model.toJSON()),
//...
});
What do you think this is in that context? It certainly won't be an instance of your view since no instances have been made yet. this is going to be window at the level and window won't have a useful model property. You need to defer that call until you have an instance of your view created with a model property.
You could make template a method:
template: function() {
return _.templateFromUrl('templatefile1.html', this.model.toJSON());
}
and then call it at the appropriate time:
render: function(){
this.$el.html(this.template());
return this; // <----- Standard practise so you can x.append(v.render().el)
}
I'd recommend against using async:false with $.ajax though, that's a nasty thing to do to people. Instead, you should hand templateFromUrl a callback function that it can call when the template has come in from the server.
If you're going to be doing this sort of thing a lot then you could add a template function to a base view:
var BaseView = Backbone.View.extend({
template: function() {
// This assumes that you have exactly one of `this.model` and
// `this.collection`, you can do other things here if that doesn't
// work in your world.
var data_source = this.model || this.collection;
return _.templateFromUrl(this.template_name, data_source.toJSON());
}
});
and then use it your PersonView:
var PersonView = BaseView.extend({
template_name: 'templatefile1.html',
render: function() {
this.$el.html(this.template());
return this;
},
//...
});
You need to make the model reusable, and define a url:
var PersonModel = Backbone.Model.extend({
defaults: {
name: 'Untitled',
age: 0,
occupation: null,
},
url: "/somefile.json"
});
Create a listener for model changes inside the view, and fetch the model:
initialize: function(options){
this.listenTo(this.model, "change", this.render);
this.model.fetch();
}
Create a new model, then pass it to a new view:
var person = new PersonModel();
var personView = new PersonView({ model: person });
I have created a very basic backbone app, to understand how it works.
In the router, I just wanna display just 1 model, i.e. a user, not the whole collection, by passing an id in the url, how to do that?
For example, I'd like to do someapp.com/app/#user/2, and this would display just user no2 details.
Please see my work in jsfiddle
// router
var ViewsRouter = Backbone.Router.extend({
routes: {
'': 'viewOne',
'one': 'viewOne',
'two': 'viewTwo',
'user/:id': 'user'
},
viewOne: function() {
var view = new TheViewOne({ model: new TheModel });
},
viewTwo: function() {
var view = new UserView({ model: new TheModel });
},
user: function(id) {
// how to get just 1 user with the corresponding id passed as argument
// and display it???
}
});
Many thanks.
https://github.com/coding-idiot/BackboneCRUD
I've written a complete Backbone CRUD with no backend stuff for beginners. Below is the part where we get the user from the collection and show/render it.
View
var UserEditView = Backbone.View.extend({
render: function(options) {
if (options && options.id) {
var template = _.template($("#user-edit-template").html(), {
user: userCollection.get(options.id)
});
this.$el.html(template);
} else {
var template = _.template($("#user-edit-template").html(), {
user: null
});
// console.log(template);
this.$el.html(template);
}
return this;
},
Router
router.on('route:editUser', function(id) {
console.log("Show edit user view : " + id);
var userEditView = new UserEditView({
el: '.content'
});
userEditView.render({
id: id
});
});
Update
Particularly, sticking to your code, the router will look something like this :
user: function(id) {
var view = new UserView({ model: userCollection.get(id) });
}
I'm running into a problem maintaining my collection. First, I load attendees into a collection via fetch. This loads existing attendees from the database in to the collection. I also have a button which allows a user to add new attendees. When an attendee is manually entered it seems to wipe out the models loaded into the collection via fetch and starts fresh. All manually added attendees now populate the collection; however, i would like both the fetch loaded and manually added attendees to populate this list.
var InviteeView = Backbone.View.extend({
tagName: "tr",
initialize: function() {
this.collection = new InviteeJSONList();
_.bindAll(this, 'render','appendItem','remove','saveInvitee');
},
events: {
"click .removeInvitee":"remove",
"click .saveInvitee":"saveInvitee"
},
render: function() {
var source = $("#invitee-template").html();
var template = Handlebars.compile(source);
var context = inviteeListJSON.attributes['json'];
var html=template(context);
$(this.el).html(html);
return this;
},
appendItem: function() {
$("#attendees").append(this.render().el);
},
remove: function() {
$(this.el).remove();
},
saveInvitee: function() {
var value = $(this.el).find('select').val();
var model = this.collection.attributes['json']['invitees'];
model = model.filter(function(attributes) {return attributes.encrypted_id==value});
var attendee = new Attendee({
user_id: model[0]['id'],
meeting_id: '<?=$mid?>',
status: 'Uncomfirmed',
first_name: model[0]['first_name'],
last_name: model[0]['last_name'],
email: model[0]['email'],
user_uuid: model[0]['encrypted_id'],
unavailable_dates: model[0]['unavailable_dates']
});
attendeeView.addAttendeeItem(attendee.attributes)
this.remove();
}
});
var AttendeeList = Backbone.Collection.extend({
model: Attendee,
url: '<?=$baseURL?>api/index.php/attendees/<?=$mid?>×tamp=<?=$timestamp?>&userid=<?=$userid?>&apikey=<?=$apikey?>',
parse: function(response) {
if(response!="No History") {
$.each(response['attendees'], function(key, value) {
attendeeView.addAttendeeItem(value);
});
$('.loading_attendees').hide();
}
else {
$('.loading_attendees').html("No attendees exists for this meeting.");
}
}
});
var AttendeeView = Backbone.View.extend({
el: $('body'),
initialize: function() {
_.bindAll(this, 'render','fetchAttendees', 'appendItem', 'addAttendeeItem');
this.counter=0;
this.collection = new AttendeeList();
this.collection.bind('add', this.appendItem);
this.fetchAttendees();
},
events: {
"click #addInvitee":"appendInvitees",
},
appendInvitees: function() {
var inviteeView = new InviteeView();
inviteeView.appendItem();
},
render: function() {
},
fetchAttendees: function() {
this.collection.fetch({
success: function(model, response) {
},
error: function(model, response) {
$('#loading_attendees').html("An error has occurred.");
}
});
},
appendItem: function(item) {
var attendeeItemView = new AttendeeItemView({
model: item
});
$("#attendees").append(attendeeItemView.render().el);
attendeeItemView.updateAttendeeStatusSelect();
},
addAttendeeItem: function(data) {
this.counter++;
var attendee = new Attendee({
id: data['id'],
user_id: data['user_id'],
meeting_id: data['id'],
status: data['status'],
comments: data['comments'],
attended: data['datetime'],
first_name: data['first_name'],
last_name: data['last_name'],
email: data['email'],
counter: this.counter,
user_uuid: data['user_uuid'],
unavailable_dates: data['unavailable_dates']
});
this.collection.add(attendee);
},
});
After the collection (2 items loaded from REST API) is loaded via fetch():
console.log(this.collection.models) outputs:
[d]
[d,d]
Then when I manually add an attendee via a button the collection seems to reset:
console.log(this.collection.models) outputs:
[d]
Good that it's working, as there are many ways to go. I probably would have structured it a bit differently to leverage the Backbone methods that instantiate modes, but working code is the real goal, so these are just my thoughts:
Rather than actually instantiate the Models in the Collection parse() method, merely have parse return an array of data objects from which Backbone would instantiate the models, and trigger a
Rather than call fetch for the Collection inside AttendeeView, but outside the View class
Either have AttendeeView represent the view for a single attendee, or name it AttendeeListView and have it render the list
For instance:
AttendeeList = Backbone.Collection.extend({
...
parse: function(response) {
// create an array of objects from which the models can be parsed
var rawItems = [];
$.each(response['attendees'], function(key, value) {
rawItems.push({
id: data['id'],
user_id: data['user_id'],
meeting_id: data['id'],
status: data['status'],
comments: data['comments'],
attended: data['datetime'],
first_name: data['first_name'],
last_name: data['last_name'],
email: data['email'],
counter: this.counter,
user_uuid: data['user_uuid'],
unavailable_dates: data['unavailable_dates']
});
});
return rawItems;
},
...
}
and then either use the success/failure call backs:
AttendeeList.fetch( onListFetchSuccess , onListFetchFail );
or listen for the reset event that gets triggered:
AttendeeList.on('reset', createAttendeeListView );
(I didn't actually edit and run the code, this is just an outline)
I ended up resolving the issue by removing the url parameter and parse function out of the collection and into the view. Now everything works as expected.