posting data with backbone form post - backbone.js

i am unable to get form post to work. below is snippet for form view with event handling but I am unable to see form data printed in console from getFormData. I am not sure if this is right approach to form handling but was just trying it out reading stuff from net.
define(['backbone', 'handlebars', 'jquery', 'events', 'models/article'], function(Backbone, Handlebars, $, Events, Article) {
var ArticleFormView = Backbone.View.extend({
events: {
"submit": "createArticle"
},
tagName: "form",
id: "article-form",
className: "articleform",
initialize: function() {
this.model = new Article();
this.render();
},
render: function() {
var template = $("#createarticletemplate").html();
console.log("template=" + template);
var compiled = Handlebars.compile(template);
var html = compiled(this.model.attributes);//passed when we do new View()
console.log("compiled template=" + template);
this.$el.html(html);
return this;
},
createArticle: function(e) {
e.preventDefault();
console.log("createArticle event happened" + $(this.el).parent().html());
var data = this.getFormData( $(this.el).parent() );
console.log(JSON.stringify(data));
this.model.save(data, {
success: function(model, response, options) {
console.log("create article success");
Events.trigger("router:navigate", "#");
},
error: function(model, response, options) {
return console.log("create article failure:" + response.responseText);
}
});
},
//Auxiliar function
getFormData: function(form) {
console.log(form);
var unindexed_array = form.serializeArray();
console.log(unindexed_array.length);
var indexed_array = {};
$.map(unindexed_array, function(n, i){
console.log("array:" + n);
indexed_array[n['name']] = n['value'];
});
return indexed_array;
},
}); //artifleformview
return ArticleFormView;
});
snippet of routes/index.js
exports.articles.createone = function(req, res) {
console.log(req.body);
//res.json(req.body);
db.articles.insert(req.body);
}
article.js model:
define(['backbone'], function(Backbone) {
var Article = Backbone.Model.extend({
url: "/article",
idAttribute: "_id"
});
return Article;
});
console log:
createArticle event happened<form id="article-form" class="articleform"><label>title </label><input type="text" name="title"><p></p><label>body </label><input type="text" name="body"><p></p><label>category </label><input type="text" name="category"><p></p><input type="submit" value="create article"></form> articleform.js:27
[div.form, prevObject: m.fn.init[1], context: form#article-form.articleform, jquery: "1.11.1", constructor: function, selector: ""…]
articleform.js:44
0 articleform.js:46
{} articleform.js:29
create article success articleform.js:33
index called on router
Adding createArticle error log:
POST http://localhost:3000/article net::ERR_EMPTY_RESPONSE jquery.js:4
send jquery.js:4
m.extend.ajax jquery.js:4
e.ajax backbone.js:1
e.sync backbone.js:1
i.extend.sync backbone.js:1
i.extend.save backbone.js:1
Backbone.View.extend.createArticle articleform.js:33
m.event.dispatch jquery.js:3
r.handle
If some one wants more code let me know I can put all code here but its too many files so better would be some link I attach if needed.
Above log for error prints in console thought the article is getting persisted fine.
Regards,
Miten.

Looks like you need to replace
var data = this.getFormData( $(this.el).parent() );
with
var data = this.getFormData(this.$('form'));
or
var data = this.getFormData(this.$el.closest('form'));

Related

Backbone doesn't remove View when Model removed

There's a remove method on EventView. When I click on the remove button, the Event (Model) should be removed so as the EventView.
With the following code, I can remove the Model from mongodb by clicking on the remove button. But the Model View won't remove itself until I refresh the page.
I am using express, EJS and mongodb for this demo.
app.js // with express routes settings
events.init = function(req, res) { res.render('index') };
events.all = function(req, res) {
db.event.find({}, function(err, event) {
if (err) return;
res.json(event);
});
}
events.delete = function (req, res) {
var Id = db.ObjectId(req.params.id);
db.event.remove({
"_id": Id
});
}
app.get('/', events.init);
app.get('/events', events.all);
app.del('/events/:id', events.delete);
client.js // Backbone Model, Collection and View setup
var Event = Backbone.Model.extend({
idAttribute: "_id"
});
var EventCollection = Backbone.Collection.extend({
model: Event,
url: "/events"
});
var EventView = Backbone.View.extend({
events: {
"click .remove": "remove"
},
initialize: function () {
this.listenTo(this.model, 'destroy', this.remove);
},
remove: function (e) {
this.model.destroy();
},
render: function () {
var html = new EJS({url: '/partials/event-field.ejs'}).render(this.model);
this.$el.html(html);
return this
}
});
var EventCollectionView = Backbone.View.extend({
render: function () {
this.collection.each(function(event){
var eventView = new EventView({ model: event });
this.$el.append(eventView.render().$el);
}, this);
return this
}
});
init.js // Called on page load
$(function () {
var collection = new EventCollection();
collection.fetch({
success: function(data){
var collectionView = new EventCollectionView({ collection: data})
$('.upcoming .list-group').append(collectionView.render().$el);
}
});
});
Somehow I found out how to make it work.
I renamed the remove method to destroy and changed the destroy method like so.
events: {
"click .remove": "destroy"
},
initialize: function () {
this.listenTo(this.model, 'destroy', this.destroy);
},
destroy: function () {
this.remove();
this.unbind();
this.model.destroy();
},

Adding model to a collection after save method in backbone

I am using the save method when my data is submitted. On success callback of the save method, the collection should be updated with the model which i have saved since i want to get the id of the model from my server. My code is as below
var app = app || {};
app.AllDoneView = Backbone.View.extend({
el: '#frmAddDone',
events:{
'click #addDone':'addDone'
},
addDone: function(e ) {
e.preventDefault();
var formData = {
doneHeading: this.$("#doneHeading").val(),
doneDescription: this.$("#doneDescription").val(),
};
var donemodel = new app.Done();
donemodel.save(formData,
{
success :function(data){
/*my problem is here how do i listen to collection event add that has been
instantiated in intialize property to call renderDone . My tried code is
var donecollection = new app.AllDone();
donecollection.add(donemodel);
and my response from server is
[{id:145, doneHeading:heading , doneDescription:description,
submission_date:2014-08-27 03:20:12}]
*/
},
error: function(data){
console.log('error');
},
});
},
initialize: function() {
this.collection = new app.AllDone();
this.collection.fetch({
error: function () {
console.log("error!!");
},
success: function (collection) {
console.log("no error");
}
});
this.listenTo( this.collection, 'add', this.renderDone );
},
renderDone: function( item ) {
var doneView = new app.DoneView({
model: item
});
this.$el.append( doneView.render().el );
}
});
Collection is
var app = app || {};
app.AllDone = Backbone.Collection.extend({
url: './api',
model: app.Done,
});
Model is
var app = app || {};
app.Done = Backbone.Model.extend({
url: "./insert_done",
});
View is
var app = app || {};
app.DoneView = Backbone.View.extend({
template: _.template( $( '#doneTemplate' ).html() ),
render: function() {
function
this.$el.html( this.template( this.model.attributes ) );
return this;
}
});
In your success callback you create an entirely new collection, which doesn't have any listeners registered. This is the reason why the renderDone isn't triggered.
The model you receive from the server should be added to the collection which is attached directly to your view, this.collection:
var self = this,
donemodel = new app.Done();
donemodel.save(formData, {
success :function(data){
// this is the collection you created in initialize
self.collection.add(donemodel);
},
error: function(data){
console.log('error');
}
});

Backbone.js model.save() fire a "too much recursion" error in underscore

I've got a problem trying to use backbone on saving my Model from a form. Here I want my my view to actually be an editing form:
(function() {
'use strict';
var YachtEditor = {};
window.YachtEditor = YachtEditor;
var template = function(name) {
return Mustache.compile($('#' + name + 'Template').html());
};
YachtEditor.Tank = Backbone.Model.extend({
defaults : {
dCapacity : "",
sType : ""
}
});
YachtEditor.Tanks = Backbone.Collection.extend({
// url: "/rest/tanks",
localStorage: new Store("tanks"),
model : YachtEditor.Tank
});
YachtEditor.TankView = Backbone.View.extend({
template: template("tank"),
events: {
'click .save' : 'save',
'click .remove' : 'remove'
},
initialize: function() {
console.log("initialize tank View :");
console.log(this.model.get("id"));
},
render: function() {
this.$el.html(this.template(this));
return this;
},
save: function() {
console.log('change');
var self = this;
var values = {
sType: self.$("#sType").val(),
dCapacity: self.$("#dCapacity").val()
};
console.log("dCapacity : " + values.dCapacity);
console.log("sType : " + values.sType);
this.model.save(values);
},
remove: function() {
this.model.destroy();
},
dCapacity : function() {
return this.model.get("dCapacity");
},
sType : function() {
return this.model.get("sType");
}
});
YachtEditor.TanksView = Backbone.View.extend({
el: $("div.tankZone"),
template: template("tanks"),
events: {
"click .add" : "addTank",
"click .clear" : "clear"
},
initialize: function() {
this.tanks = new YachtEditor.Tanks();
// this.tanks.on('all', this.render, this);
this.tanks.fetch();
this.render();
},
render: function() {
this.$el.html(this.template(this));
this.tanks.each(this.renderTank, this);
return this;
},
renderTank: function(tank) {
var view = new YachtEditor.TankView({model: tank});
$(".tanks").append(view.render().el);
return this;
},
addTank: function() {
this.tanks.create({});
this.render();
},
clear: function() {
this.tanks.each(function(tank) {
tank.destroy();
});
this.render();
}
});
...
})();
Here is the mustache template i use for each tank
<script id="tankTemplate" type="text/x-mustache-template">
<div class="tankView">
<h1>Tank</h1>
<select id="sType" value="{{ sType }}">
#for(option <- Tank.Type.values().toList) {
<option>#option.toString</option>
}
</select>
<input id="dCapacity" type="text" value="{{ dCapacity }}">
<button class="destroy">x</button>
</div>
</script>
My problem here is that this.model.save() triggers a 'too much recursion' in underscore. js. (chrome is displaying an error also.
Here is the call stack on error:
_.extend
_.clone
_.extend.toJSON
_.extend.save
_.extend.update
Backbone.sync
_.extend.sync
_.extend.save
YachtEditor.TankView.Backbone.View.extend.save
st.event.dispatch
y.handle
I suspect the save to recall the blur event but i cannot find a way to explicit it... Maybe I'm not using backbone as i should?
My problem, aside of some pointed out by Yurui Ray Zhang (thank you), was that I was using a backbone-localstorage.js from an example I found here : git://github.com/ngauthier/intro-to-backbone-js.git
The "too much recursion error" stopped to appear as soon a I replaced it with a storage I found here : https://github.com/jeromegn/Backbone.localStorage
a few things. you defined your tank model as
app.Tank = ...
but in your collection you are referencing it as:
model : YachtEditor.Tank
and in your view, you are trying to assign elements before they are rendered on the page:
this.input = {}
this.input.sType = this.$("#sType");
this.input.dCapacity = this.$("#dCapacity");
I'm not sure how your view is rendered to the page, some people, like me, like to use render() to render the template directly to the page:
render: function() {
this.$el.html(this.template(this));
//done, you should be able to see the form on the page now.
},
some others, will use something else to insert the el, eg:
//in another view
tankView.render().$el.appendTo('body');
but either way, if you want to cache your elements, you need to do it after they are rendered to the page, not in initialize.
//this method is only called after render() is called!
cacheElements: function() {
this.input = {}
this.input.sType = this.$("#sType");
this.input.dCapacity = this.$("#dCapacity");
}
I'd suggest, first, try to fix this things, and then, try to add some console log or debuggers in your readForm method to see if the values are grabbed correctly:
readForm: function() {
var input = this.input;
console.log(input.sType.val());
console.log(input.dCapacity.val());
this.model.save({
sType: input.sType.val(),
dCapacity: input.dCapacity.val()
});
},

Backbone.js Uncaught ReferenceError: x is not defined

I am getting Uncaught ReferenceError: _auditNumber is not defined error while trying to bind my model to the view using backbone.js and underscore.js
<script id="searchTemplate" type="text/template">
<div class="span4">
<p>"<%= _auditNumber %>"</p>
</div>
<div class="span4">
<p>"<%= _aic %>"</p>
</script>
Collection
//Collection
var AuditsCollection = Backbone.Collection.extend({
initialize: function() {
this.on('add', this.render);
},
render: function() {
_.each(this.models, function (item) {
var _auditView = new AuditView({
model: item
});
$("#audits").append(_auditView.render().el);
});
},
});
Model
var Audit = Backbone.Model.extend({
url: function () {
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
data.forEach(function (auditItem) {
var auditsCollection = new AuditsCollection();
auditsCollection.add(JSON.stringify(auditItem));
});
}
});
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
template: $("#searchTemplate").html(),
render: function () {
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
I know I am missing something simple, any help is appreciated.
2 problems (at least - you're kind of off in the weeds given how many backbone tutorials there are).
Your model URL is returning a list of results. That's what collections are for. Your model should fetch a single record and the parse method has to return the model's attribute data. If you stick with the tutorials, you won't need a custom url function and you won't need a custom parse function at all.
var Audit = Backbone.Model.extend({
url: function () {
//This needs to be a url like /audits/42 for a single record
return myUrl;
},
defaults: {
_auditNumber: "",
_aic: "",
},
parse: function (data) {
//this needs to return an object
return data[0];
}
});
You aren't passing a valid data object to your template function.
// Sub View
var AuditView = Backbone.View.extend({
className: 'row-fluid',
//compile template string into function once
template: _.template($("#searchTemplate").html()),
render: function () {
//render template into unique HTML each time
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});

Backbone.js and Bootstrap Typeahead - rendering after async fetch

I took the base of this code from a gist. It initially worked perfectly when I first fetch()ed the collection and then in render() called tw-bootstap's .typeahead().
However, I have put in a keypress event to try and restrict the size of the data returned by fetch(). The collection data is returned and it is filtered through prepData() fine and arrives at render(). The typeahead is not working, however at that stage. It may be that the backbone event is overriding render at that point?
// typeahead on the numbers
var Bootstrap = {};
Bootstrap.Typeahead = Backbone.View.extend({
el: '#autocompleteN',
tagName: 'input',
attributes: {"data-provide": "typeahead"},
initialize: function(options){
if(!this.collection) {
return null;
}
//this.collection.on("reset", this.prepData, this);
},
events: {
"keypress": "setSearch"
},
setSearch: _.throttle(function(e) {
var that=this;
var d = e.currentTarget.value;
// strip spaces and remove non-numerics
d = d.replace(/ /g,'');
d = d.replace(/[^0-9]/g, '');
// if it's longer than 2, call a fetch;
if(d.length > 2) {
$.when( app.searchNums.fetch({url: 'api/index.php/search/num/'+d}) ).then(function() {
//console.dir("success");
that.prepData();
});
}
}, 1000),
prepData: function() {
//console.dir("prepData called");
var prepare = _.pluck(this.collection.models, 'attributes');
this.property = this.options.property || _.keys(prepare[0])[0];
this.items = this.options.items;
this.data = _.pluck(prepare, this.property);
this.render();
},
render: function() {
var that = this;
that.$el.typeahead({
source: that.data,
//source: ['PHP', 'MySQL', 'SQL', 'PostgreSQL', 'HTML', 'CSS', 'HTML5', 'CSS3', 'JSON'],
items: that.items,
onselect: function( data ) {
// render the results view here
}
});
return this;
}
});
var bui = new Bootstrap.Typeahead({
collection: app.searchNums,
items: 5
});
Why dont you just set minLength on the typeahead, it looks like that is what you are trying to do?

Resources