Model not updating on form submit, using Backbone + Stickit - backbone.js

I'm trying to use the backbone.stickit library to bind my form input to the model but can't seem to get the model to update correctly.
The keyup event appears to work correctly, i can see the value change if i use the "onSet" callback to display it:
bindings: {
'#firstName': {
observe: 'firstName',
onSet: function(val, options) {
$('#output').html(val);
}
}
}
Here is my code (Run it on jsfiddle):
HTML
<div id="view">
<form name="form" id="form">
<input id="firstName" type="text"/>
<input type="submit" id="submit"/>
</form>
<div id="output"></div>
</div>
JavaScript
var app = {
Model: Backbone.Model.extend({
firstName: 'test'
}),
View: Backbone.View.extend({
el: "#view",
initialize: function(){
this.model = new app.Model();
this.render();
},
bindings: {
'#firstName': 'firstName'
},
render: function(){
this.$el.html( this.template );
this.stickit();
},
events: {
"submit #form": "submitForm"
},
submitForm: function(e) {
e.preventDefault();
$('#output').html('output:'+this.model.firstName);
}
})
};
var view = new app.View();

The way of getting a model attribute is usally not by accessing the attribute name as an object property, the way you did it this.model.firstName. personally I know a very few cases of such implemntation. The so called right way to do that is by using get method:
this.model.get("firstName").
This will return the current model value.
I usually define getters and setters for each model I use, so I would do the following:
getFirstName: function(){
return this.get("firstName");
}
Just looks better and more "easy on the eyes" :) (but totally not a must)
Here's an update of your fiddle: http://jsfiddle.net/srhfvs8h/1/

Related

Backbone - Cannot get model on click using localstorage

I'm trying to setup a little app in backbone where I can add items to a list and, when I click them, they'll be deleted. I've managed to add items to the list but when using model.destroy() nothing happens.
When I console.log the click event on the list models I get:
child {cid: "c0", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
for any item I click.
Code is below:
Html:
<h1>INDEX!</h1>
<form class="add-form">
<input type="text" name="name"/>
<hr />
<button type="submit" class="btn">Submit</button>
</form>
<h2>LIST STUFF</h2>
<ul class="blah">
{{#each indexCollection}}
<li class="li-class">{{name}}</li>
{{/each}}
</ul>
Javascript:
//Local Storage
App.Storage.Local = new Backbone.LocalStorage('localIndexList1-backbone');
//Index Model
App.Models.IndexModel = Backbone.Model.extend({
localStorage: App.Storage.Local,
defualts:{
name:''
},
urlRoot: '/'
});
//Index Collection
App.Collections.IndexCollection = Backbone.Collection.extend({
localStorage: App.Storage.Local,
model: App.Models.IndexModel,
initialize: function(){
console.log('Collection initialised');
},
url: '/'
});
//View for H1 and input form
App.Views.IndexView = Backbone.View.extend({
el: '.page',
events:{
'submit .add-form' : 'addNew',
'click' : 'deleteMe'
},
initialize: function(){
console.log('IndexView initialised');
},
addNew: function(ev){
// ev.preventDefault();
var submitEntry = $(ev.currentTarget).serializeObject();
var newEntry = new App.Models.IndexModel();
newEntry.save(submitEntry, {
success: function(newEntry){
// router.navigate('', {trigger: true});
console.log('SUCESSS!!!!!!!!!');
}
});
},
deleteMe: function(){
console.log(this.model);
//Whatever I put here will not work
}
});
//View for list
App.Views.ListView = Backbone.View.extend({
el: '.page',
initialize: function(){
console.log('ListView initialised');
},
template: Handlebars.compile($('#list').html()),
render: function(){
this.$el.html(this.template);
var that = this;
var indexCollection = new App.Collections.IndexCollection();
indexCollection.fetch({
success:function(indexCollection){
that.$el.html(that.template({indexCollection: indexCollection.toJSON()}));
}
});
}
});
Would anyone be able to help letting me know where I am going wrong?
Thanks!
Where are you creating one IndexView for each of your collection models? You should have an item view, configure its model to be one IndexModel, and move your delete code to that particular view. When you do that, you should also call remove in this item view.
This is why something like Backbone.Marionette helps a lot. Just throw in a CollectionView and you're done.
Think of it like this:
"list view" -> has a collection
"item view" -> has a single model
Anything you need to on the collection level (like adding a new one, re-loading, whatever), do it on your list view. Anything you need on model level (editing, saving, deleting), do it on your item view.

why backbone view is not getting rendered

this is below is code which i am using for simple task add , view is not getting render, i am not able to find the mistake ,
<!doctype html>
<html lang="en">
<head>
<title>Calculator</title>
<link rel="stylesheet" href="styles/bootstrap/css/bootstrap.min.css">
<script src="js/libs/jquery-1.9.1.min.js"></script>
<script src="js/libs/underscore-min.js"></script>
<script src="js/libs/backbone-min.js"></script>
<script type="text/template" id="display-template">
<div class="row">
<div class="span4">
<%=content%>
</div>
</div>
</script>
<script language="javascript">
var cdate;
var tasks={};
var app = app || {};
// App view responsible for rendering app
app.TaskView = Backbone.View.extend({
el: $('#tasks'),
template: _.template($('#display-template').html()),
initialize: function () {
this.render();
},
render: function () {
console.log("render called");
console.log(this.template());
this.$el.html(this.template());
}
});
app.task = Backbone.Model.extend({
defaults:{
content:null
}
});
app.bUsers = Backbone.Collection.extend({
model : app.task,
initialize: function(models, args) {
this.bind('add', this.renderone);
this.bind('remove', this.destroy); },
renderone:function(user){
console.log(user);
var view = new app.TaskView({model: user});
},
destroy:function(user){
$(user.view.el).remove();
}
});
app.Users = new app.bUsers();
$(document).ready(function() {
cdate=new Date();
$("#cdate").html(new Date());
$("#pre").click(function(){
cdate=new Date(cdate.getTime()-(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#next").click(function(){
cdate=new Date(cdate.getTime()+(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#submit").click(function(){
if(tasks[cdate]==undefined) tasks[cdate]=[];
tasks[cdate].push($("#task").val());
// app.appView = new app.TaskView({
// model: new app.task({"content":$("#task").val()})
// });
var data ={"content":$("#task").val()};
app.Users.add(data);
});
});
</script>
</head>
<body>
<a id="pre" href="#">Prev</a>
<div id="cdate"></div>
<a id="next" href="#">Next</a>
<input type="text" id="task" ></input>
<input type="button" value="submit" id="submit" ></input>
<div id="tasks"></div>
</body>
Oye, you've got a few problems.
To answer your specific question, your render method of your view should take your view's model instance and get something from this.model.toJSON() it to get at its data to pass to the template method (toJSON really returns "JSONable" objects).
But that's not all.
Besides a few html issues, you also have stylistic problems.
Collections generally should not be concerned with views, only data (*). Views should be concerned with with collections and models. Collections communicate to views via event binding, which I see you are doing. However, for reuse purposes, you may have more than one combination views that might want to listen to events in the collection. By setting up the event binding in the collection, you've effectively limited your collection for only one use.
Views can do alot. There's not much reason to manually add DOM event handlers when you can code the view to do it for you.
I haven't written Backbone in a little while (not by choice!), but generally found it was a good idea to have a view dedicated to the collection, and then have a separate model view that the collection view might create or destroy based upon whatever events took place.
Here's a bit of a cleanup of your code to give you a starting example:
http://jsfiddle.net/jfcox/SmPNv/
HTML:
<a id="pre" href="#">Prev</a>
<div id="cdate"> </div>
<a id="next" href="#">Next</a>
<input type="text" id="task" />
<input type="button" value="add" id="submit" />
<div id="tasks"></div>
Backbone definitions:
var defs = defs || {};
//first define the data handlers
defs.Task = Backbone.Model.extend({
defaults: function () {
return {
content: null,
addDate: (new Date()).toString()
};
}
});
defs.Users = Backbone.Collection.extend({
model: defs.Task
});
// App view responsible for rendering app
defs.SingleTaskView = Backbone.View.extend({
//since we can't control where the js is loaded, go ahead and make the template inline for jsFiddle demo.
tagName: 'div',
template: _.template('<div class="row"> <div class="span4"><%=content%></div> <em><%=addDate%></em> <button class="remove"> remove</remove> </div>'),
events: {
"click button.remove": "remove"
},
initialize: function (opts) {
this.model.on('change', this.render, this);
},
render: function () {
console.log("render called");
var modelBare = this.model.toJSON();
return this.$el.html(this.template(modelBare));
},
remove: function () {
//removes from local collection, does not delete on server
//for that, you'd want `this.model.destroy`
this.collection.remove(this.model);
//removes this view's element.
this.$el.remove();
}
})
defs.TasksView = Backbone.View.extend({
el: 'body',
events: {
"click #pre": "doPrevious",
"click #next ": "doNext",
"click #submit ": "doSubmit"
},
cdate: null,
initialize: function (opts) {
this.cdate = new Date();
this.render();
this.collection.on('add', this.renderone, this);
},
render: function () {
$("#cdate").html(this.cdate.toString());
},
doPrevious: function () {
this.cdate = new Date(this.cdate.getTime() - (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doNext: function () {
this.cdate = new Date(this.cdate.getTime() + (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doSubmit: function () {
var data = {
"content": $("#task").val()
};
this.collection.add([data]);
},
renderone: function (userModel) {
var view = new defs.SingleTaskView({
model: userModel,
collection: this.collection
});
this.$el.find('#tasks').append(view.render());
}
});
The application, itself.
var app = app || {};
app.users = new defs.Users();
(function ($) {
$(document).ready(function () {
app.usersview = new defs.TasksView({
collection: app.users
});
});
})(jQuery);
(*) This is a guideline, not an absolute rule, of course. If you think a collection might work as some sort of workflow manager, etc, that might be fine, but that's an advanced topic.
Edit: I included the template inline, partially for reasons that I don't trust jsFiddle with inline "text" scripts. I'm not recommending any way to handle that, just that's how I did it here.

simple backbone events not firing

I'm playing around with backbone.js for the first time, but can't get the events to fire properly. Can somebody explain what I'm doing wrong?
Much appreciated!
in app.js loaded at the bottom of my html:
var Discussion = Backbone.Model.extend({
defaults: {
id: null,
title: 'New discussion'
},
urlRoot: '/api/discussion'
});
var DiscussionCollection = Backbone.Collection.extend({
model: Discussion,
url: '/api/discussion'
});
var DiscussionView = Backbone.View.extend({
events: {
'click .btnCreateDiscussion': 'create',
'keypress #discussion_title': 'create'
},
initialize: function(){
//this.$el = $("#form_discussion");
this.template = _.template( $('#discussion-template').html() );
},
render: function(){
console.log("rendering");
return this;
},
create: function(){
console.log('creating a new discussion')
}
});
var discussionView = new DiscussionView({ el: $("#form_discussion"), model: Discussion });
html:
<form action="" id="form_discussion" method="post">
<label for="discussion_title">Discussion Title</label>
<input type="text" id="discussion_title" name="discussion_title" />
<input class="btnCreateDiscussion" type="button" value="Create Discussion">
<script type="text/template" id="discussion-template">
<h1><%= title %></h1>
</script>
It seems to work fine: http://jsfiddle.net/Jbahx/. (check your backbone & underscore versions, and make sure the DOM is initialized)
About what you're doing wrong though:
model: Discussion when instantiating your view. You have to give the view an instance of a model, not a class. If you give the view a model (optional), it's generally because you want to represent the data of a particular instance.
Your render method is never called, but it's useless at the moment so that's not that big a problem.
this.template = _.template( $('#discussion-template').html() ); in the initialize method. Put this as a property of the view when extending so it'll be put in the prototype of your view (even if it seems to be a singleton here): template: _.template( $('#discussion-template').html() ),.
The problem was jQuery. The most recent 1.x release didn't work, but using the most recent 2.x release fixes the problem. It would be useful if anyone could explain why we should only use 2.x in this case?
First of all, you must call Backbone.View.prototype.initialize in your overriden method to let Backbone initialize event listeners:
initialize: function(){
//this.$el = $("#form_discussion");
this.template = _.template( $('#discussion-template').html() );
Backbone.View.prototype.initialize.call(this)
},
Second, render view in initialize - it isn't best practice. Use for this separate render method.

How can I bind datasourse of bootstrap typeahead in my backbone view?

I have a backbone view. I want to set datasource of bootstrap typeahead from server in every change occur in the search text field. How can I do this?? This is my Search view
window.SearchByChoiceView = Backbone.View.extend({
initialize: function () {
this.model = new SearchByChoiceModel();
this.render();
},
render: function () {
$(this.el).empty().html(this.template(this.model.toJSON()));
return this;
},
});
window.SearchByChoiceModel = Backbone.Model.extend({
initialize: function () {
},
defaults: {
id: 1,
SearchKeyword: "",
"tags": new Array()
}
});
And this is my search field:
<input type="text" required placeholder="search" class="input-span3" id="SearchKeyword" title= "Search Keyword" name="SearchKeyword" value="<%= SearchKeyword %>" style="margin: 0 auto;" data-provide="typeahead" data-items="4">
Please anyone help. I am a begginer with backbone. May be it is easy but it is very hard to handle for me. I have tried a lot.
I believe you can do this:
$( '.typeahead' ).typeahead( {
source : typeAhead.people.pluck( 'name' )
} );
});
Where "typeAhead.people" is your collection instance. In your case
myApp.myCollection.pluck( 'SearchKeyword' )
JSBin:
http://jsbin.com/upigej/1/edit

display fetched result of backbonejs model

I'm trying to show single model fetch result in html form
Here is my backbone.js part:
window.Category = Backbone.Model.extend({
urlRoot : "../myWs/category/"
});
window.CategoryView = Backbone.View.extend({
el : $('#category_details'),
template : _.template($('#category-details').html()),
initialize : function() {
this.render();
},
render : function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var category = new Category({
id : "067e6162-3b6f-4ae2-a171-240000000000"
});
var vategoryView = new CategoryView({
model : category
});
category.fetch();
What I am doing is:
Create a backbone model Category
Creating a backbone view CategoryView
Fetch data from rest web service which returns a JSON data object.
Display the fetched data in the div "#category-details".
On browsers , I can see that the "fetch()" method works, because I can see my JSON object returned
Here is HTML code:
<div id="category_details">details:</div>
<script type="text/template" id="category-details">
<label>Id:</label>
<input id="id" name="id" type="text" disabled />
<label>Name:</label>
<input type="text" id="name" name="name" value="<%= name %>"/>
</script>
The problem is that data is not shown in html.
How can I display data in html?
Your view doesn't handle the changes on the model, which implies that the view is not re-rendered when model.fetch completes.
Try binding the change event from the model:
window.CategoryView = Backbone.View.extend({
el : $('#category_details'),
template : _.template($('#category-details').html()),
initialize : function() {
this.render();
this.model.on('change', this.render, this);
},
render : function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
And a demo http://jsfiddle.net/Hzm5Y/
Note that Underscore dislikes being asked to render undefined variables, you probably should add defaults to your model:
window.Category = Backbone.Model.extend({
urlRoot : "../myWs/category/",
defaults: {
name: ''
}
});

Resources