How do I get Backbone to render the subView properly? - javascript

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.

Related

setting model content in back bone using underscore external template

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 });

Validate models with Backbone.js

with help of books and tutorials im trying to do my own Todo list app. My problem is that when you add ah todo my model should validate if the input come with some text before rendering. i created the validate methis but it doesnt work and i dont know how to track the error, i'll appreciate if you guys could help me finding my error
Model:
var Task = Backbone.Model.extend({
validate: function(attrs){
if(!attrs.title){
return "The task has no title";
}
}
});
var task = new Task;
Collection:
var Tasks = Backbone.Collection.extend({
model: Task
});
var tasks = new Tasks([
{
title: 'my task'
},
{
title: 'my task 1'
},
{
title: 'my task 2'
},
{
title: 'my task 3'
}
]);
Views:
var TaskView = Backbone.View.extend({
tagName: "li",
template: _.template( $('#task').html() ),
initialize: function(){
this.model.on("change", this.render, this);
},
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html( template );
return this;
},
events: {
'click .icon-checkbox': 'toggleState'
},
toggleState: function(e){
var $checkbox = $(e.target);
$checkbox.toggleClass('icon-checkbox-unchecked');
}
});
var TasksView = Backbone.View.extend({
el: '#tasks',
initialize: function(){
this.render();
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.each(this.addOne, this);
return this;
},
addOne: function(task){
var taskView = new TaskView({ model: task });
this.$el.append( taskView.render().el );
}
});
var AddTask = Backbone.View.extend({
el: '#todos',
events:{
'click #add': 'addTask'
},
addTask: function(){
var taskTitle = $('#inputTask').val();
$('#inputTask').val("");
var task = new Task( {title: taskTitle} ); // create the task model
this.collection.add(task); //add the model to the collection
}
});
var tasksView = new TasksView( {collection: tasks} );
var addTask = new AddTask( {collection: tasks} );
Edit:
I just realized that the validate methos its on ly called when you set or save info in the model, but actually what i want to do is check when the user submit a todo that if it comes with text or ini order to create and render the model.
You never actually call the validate() method. Have you looked into the Backbone Validation plugin? Works pretty slick.
https://github.com/thedersen/backbone.validation
So i came up with this solution, I am not sure if this is a good practice or not or it the view should to this but what i did is: Once you push on the add task button the addTask methos first check if the input has text or not, if it doesnt have text it just only change the placeholder to: "Todo's can not be empty" and doesnt create a model and render its view. But, if the input has text it creates the model append it to the colelction and render the view!.
Here is the new code for the view, i hope someone could tellme if this is good or if the model should validate this or any other object should to this labor.
Code:
var AddTask = Backbone.View.extend({
el: '#todos',
events:{
'click #add': 'addTask'
},
addTask: function(){
var taskTitle = $('#inputTask').val();
$('#inputTask').val(""); //clear the input
if($.trim(taskTitle) === ''){
this.displayMessage("Todo's can not be empty");
}else{
var task = new Task( {title: taskTitle} ); // create the task model
this.collection.add(task); //add the model to the collection
}
},
displayMessage: function(msg){
$('#inputTask').attr("placeholder", msg);
}
});

Getting a Paragraph that changes with dropdown using Backbone.js

I've gotten my dropdown to be populate with lessons, but then I am stuck trying to figure out how I can get the text beneath my dropdown to change based on what the user selects by using Backbone.js
I am populating a select by adding options containing my Lessons and having the title show. Now I am just stuck on where should I insert the text so that it will change based on the selection.
Here is my HTML:
<script type="text/template" id="lesson-template">
<span class="lesson-title"><%= title %></span>
//How should I insert the text?
</script>
<script type="text/template" id="library-template">
<h1> Lesson Library </h1>
<select class="lessons"></select>
</script>
Here is my JSON file where I pull the information, I want to show the title now:
[{
"title": "Intro",
"text":"Do this now"
},
{
"title": "Second",
"text":"And then this"
}]
This is what is included in my javascript file:
window.Lesson = Backbone.Model.extend({});
window.Lessons = Backbone.Collection.extend({
model: Lesson,
url: './lessons.json'
});
window.library = new Lessons();
window.LessonView = Backbone.View.extend({
tagName: 'option',
className: 'lesson',
initialize: function() {
_.bindAll(this,'render');
this.model.bind('change',this.render);
this.template = _.template($('#lesson-template').html());
},
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
window.LibraryLessonView = LessonView.extend({
});
window.LibraryView = Backbone.View.extend({
tagName: 'section',
className: 'library',
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#library-template').html());
this.collection.bind('reset', this.render);
},
render: function() {
var $lessons,
collection = this.collection;
$(this.el).html(this.template({}));
$lessons = this.$('.lessons');
this.collection.each(function(lesson) {
var view = new LibraryLessonView({
model: lesson,
collection: collection
});
$lessons.append(view.render().el);
});
return this;
}
});
First give an id to each lesson.
window.LessonView = Backbone.View.extend({
...
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
// Then put the id as the option's value
$(this.el).val(this.model.get('id'));
return this;
}
...
});
window.LibraryView = Backbone.View.extend({
...
// bind the select's onchange event to this.onSelect
events: {
'change select': 'onSelect'
},
...
...
onSelect: function(e) {
// Grab the id of the select lesson
var lessonId = $(e.target).val();
// And get the lesson data back
var lesson = _.indexBy(this.collection.toJSON(), 'id')[lessonId];
// Then you could render you lesson view with something like this :
$('#target-container').html(
_.template($('#lesson-template').html(), lesson)
);
},
...
});
You could also go for a more elaborate views / collections construct but I'd suggest you get this working first.

Loading initial data in Backbone.js

I'm new to backbone.js and MVC so apologise if this is a silly question...
I have been experimenting with some of the backbone.js tutorials out there and am trying to work out how to load an initial set of data onto the page.
If anyone could point me in the right direction or show me the what I'm missing below, it would be greatly appreciated!
Thanks!
The code is below or at: http://jsfiddle.net/kiwi/kgVgY/1/
The HTML:
Add list item
The JS:
(function($) {
Backbone.sync = function(method, model, success, error) {
success();
}
var Item = Backbone.Model.extend({
defaults: {
createdOn: 'Date',
createdBy: 'Name'
}
});
var List = Backbone.Collection.extend({
model: Item
});
// ------------
// ItemView
// ------------
var ItemView = Backbone.View.extend({
tagName: 'li',
// name of tag to be created
events: {
'click span.delete': 'remove'
},
// `initialize()` now binds model change/removal to the corresponding handlers below.
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
},
// `render()` now includes two extra `span`s corresponding to the actions swap and delete.
render: function() {
$(this.el).html('<span">' + this.model.get('planStartDate') + ' ' + this.model.get('planActivity') + '</span> <span class="delete">[delete]</span>');
return this; // for chainable calls, like .render().el
},
// `unrender()`: Makes Model remove itself from the DOM.
unrender: function() {
$(this.el).remove();
},
// `remove()`: We use the method `destroy()` to remove a model from its collection.
remove: function() {
this.model.destroy();
}
});
// ------------
// ListView
// ------------
var ListView = Backbone.View.extend({
el: $('body'),
// el attaches to existing element
events: {
'click button#add': 'addItem'
},
initialize: function() {
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.render();
},
render: function() {
_(this.collection.models).each(function(item) { // in case collection is not empty
appendItem(item);
}, this);
},
addItem: function() {
var item = new Item();
var planStartDate = $('#planStartDate').val();
var planActivity = $('#planActivity').val();
item.set({
planStartDate: planStartDate,
planActivity: planActivity
});
this.collection.add(item);
},
appendItem: function(item) {
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
}
});
var listView = new ListView();
})(jQuery);
Thanks.
Here's the modified example: http://jsfiddle.net/kgVgY/2/
You create the collection first with the data you want
var list = new List([{
createdOn: 'Jan',
createdBy: 'John',
planStartDate: "dfd",
planActivity: "dfdfd"
}]);
and then pass the collection to the view you want
var listView = new ListView({collection: list});
That's about all you had wrong in this code. Few minor unrelated notes:
You were using _(this.collection.models).each. Backbone collections use underscore to expose all those functions on themselves, so that is equivalent to this.collection.each
You don't really need the "unrender" method on the ItemView but since you aren't using that I'm guessing you're using it for debugging.

In a Backbone app what is work of model, views and collections by convention

I am right now developing a dead simple application using backbonejs mvc javascript library.Just to show how simple it is here is the html
Sample Html
<div class="container">
<div>
<label>
Name:
<input id="txtName" type="text" title="Type your name please" /></label>
<button id="btnSubmit" type="button" value="Submit">
Submit</button>
</div>
<div id="names">
</div>
</div>
Operation
Here's all i want the application to do.
User Types a name(or some alphanumeric string) and clicks submit.
The value(which is what they call model i suppose) after validation will be sent to restful service.
Service will return the same string along with status of database save operation.
I am now confused as to where the click event will handled(is it in the model?), after that where should the render method be called?(is it in views). Below you will find the script i had managed up until now
Model.js
//store a global reference to model
window["model"] = Backbone.Model.extend({
defaults: { name: "Type your name"
},
validate: function () {
}
});
View.js
//store a global reference to view
window["view"] = Backbone.View.extend({});
nothing in the view to say :(
Application.js
//when every thing is ready kick of the application
$(document).ready(function () {
var modelInstance = new model();
});
so do i add click event to the button in application.js or model.js.which is the convention/best practice?
Will it be possible for me to render the names collection returned from service into #names and status of the database insertion into another div somewhere up above the container? Can the view render both views?
So, here is a disclaimer: If this is all your app is doing, then Backbone is way too much ceremony. When the app gets more complex with more moving parts, Backbone becomes pretty amazing.
That being said, I have created a jsFiddle to highlight how you might use Backbone for what you want to do: http://jsfiddle.net/BrianGenisio/CZwnk/4/
Basically, you have a Person model and a People collection to hold the Person models. You then have three views: NewPersonView which is the form for entering your data, which is responsible for adding new items to the People collection. Then you have a PeopleView view which is responsible for showing People collection, which will watch the collection and show any additions. Finally, is the PersonView which is responsible for showing an individual Person model.
When it all comes together, it looks a little something like this:
HTML:
<script id="new_person_template" type="text/template">
<label>
Name:
<input id="txtName" type="text" title="Type your name please" />
</label>
<button id="btnSubmit" type="button" value="Submit">Submit</button>
</script>
<script id="person_template" type="text/template">
Hello, my name is <%= name %>
</script>
<div id="container">
</div>
JavaScript
window.app = {};
window.app.Person = Backbone.Model.extend({
defaults: { name: "Type your name" },
validate: function () { }
});
window.app.People = Backbone.Collection.extend({
model: window.app.Person
});
window.app.NewPersonView = Backbone.View.extend({
template: _.template($("#new_person_template").html()),
initialize: function () {
_.bindAll(this, 'submit');
},
events:
{
"click #btnSubmit": "submit"
},
render: function () {
$(this.el).html(this.template());
return this;
},
submit: function (x, y, z) {
// If you want this to go up to the server and sync, do this instead:
// this.model.create({name: $("#txtName").val()});
// but since we don't really have a server right now, we'll just create and add
var person = new window.app.Person({name: $("#txtName").val()});
this.model.add(person);
}
});
window.app.PersonView = Backbone.View.extend({
tagname: "li",
template: _.template($("#person_template").html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
window.app.PeopleView = Backbone.View.extend({
tagName: "ul",
initialize: function() {
_.bindAll(this, "render", "renderPerson");
this.model.bind("add", this.renderPerson);
},
render: function() {
console.log("rendering");
this.model.each(this.renderPerson);
return this;
},
renderPerson: function(person) {
var personView = new window.app.PersonView({model: person});
$(this.el).append(personView.render().el);
}
});
$(document).ready(function () {
var people = new window.app.People();
var newPersonView = new window.app.NewPersonView({model: people});
var peopleView = new window.app.PeopleView({model: people});
$("#container").html(newPersonView.render().el);
$("#container").append(peopleView.render().el);
});
i would do the click in the view
you would need 2 views in my eyes, 1 is an appview that holds your entire app
the other is just a view for each usermodel you render out in your list .
here is your user model
var User = Backbone.Model.extend({
defaults: {
id: 0,
name: 'no-name'
}
});
here is your collection
var Users = Backbone.Collection.extend({
model: User,
create : function(model, options) {
var coll = this;
options || (options = {});
if (!(model instanceof Backbone.Model)) {
model = new this.model(model, {collection: coll});
} else {
model.collection = coll;
}
var success = function(savedModel, resp) {
coll.add(savedModel);
if (options.success) options.success(nextModel, resp);
// fire success event
coll.trigger("savesuccess", savedModel);
};
var error = function(resp) {
if(options.error) options.error(resp);
// fire error event
coll.trigger("saveerror");
}
return model.save(null, {success : success, error : error});
}
});
so here is a possible user view
var UserRow = Backbone.View.extend({
tagName: "li",
initialize: function(){
_.bindAll(this, 'render');
this.model.bind('change', this.render);
},
className: "user",
render: function() {
var user = this.model;
$(this.el).html(user.get('name'));
return this;
}
});
here is your appView
var AppView = Backbone.View.extend({
el: 'body',
events: {
'click button#btnSubmit': 'addUser'
},
initialize: function(){
this.collection = new Users();
this.collection.bind('add', this.appendUser);
this.collection.bind('savesuccess', this.saveSuccess);
this.collection.bind('saveerror', this.saveError);
this.counter = 0;
this.render();
},
addUser: function(){
this.counter++;
var user = new User();
user.set({
id: user.get('id') + this.counter,
name: $('#txtName', this.el).val()
});
this.collection.add(user);
},
appendUser: function(user){
var userRow = new UserRow({
model: user
});
$('ul#names', this.el).append(userRow.render().el);
},
saveSuccess: function(user){
alert('successfully saved, you can append something to the AppView DOM to show that saving was successful ...');
},
saveError: function(){
alert('failed to save, you can append something to the AppView Dom to show that saving failed, or remove the item again ...');
}
});
here you initialize it all
var App = new AppView();
as you can see, rendering the items itself does not happen after saving it in the database, it is added to the collection, the appView binds to the add event of the collection and appends the new user to your list.
the CRUD server connection works automatically, due to using the create function on the collection.
the idea here is that you don't need to user backbone events, trigger an event from your collections successful save or error save, and let any view bind to that event to make something happen on the screen when save was a success or not.

Categories

Resources