How do I use backbone model events in views? - backbone.js

I am using thedersen's Backbone.validation plugin to provide validation.
The validation itself works so far and gets executed when I submit the form. I only don't success about letting the view listen on validation errors.
The howto says that you can listen on validation events with:
model.bind('validated:invalid', function(model, attrs) {
// do something
});
Source: http://thedersen.com/backbone.validation/#events/validated
define(['jquery', 'underscore', 'backbone', "models/security/user", 'text!templates/security/registration.html'], function($, _, Backbone, SecurityUserModel, Template){
var SecurityRegistrationView;
SecurityRegistrationView = Backbone.View.extend({
initialize: function(){
this.model = new SecurityUserModel();
this.render();
Backbone.Validation.bind(this);
},
render: function(){
$(this.el).append(Template);
},
events: {
"submit form": "submit"
, "validated:valid": "valid"
, "validated:invalid": "invalid"
},
submit: function(e){
e.preventDefault();
var username, email, password;
username = $("#_user_username").val();
email = $("#_user_email").val();
password = $("#_user_password").val();
this.model.set('username', username);
this.model.set('email', email);
this.model.set('password', password);
this.model.validate();
console.log(this.model.validate());
},
invalid: function(){
$("input").addClass("inputError");
alert(" ");
},
valid: function(){
alert(" ");
this.model.save(function(){
// server validation callback...
});
}
});
return SecurityRegistrationView;
});
So how can I use the model related event in a form?

The events validated:valid and validated:invalid are not DOM-events, but Backbone events. The Backbone.View events hash is meant for handling DOM-events (source) whereas the model.bind is used to tap onto events triggered by the particular model.
So remove these lines from the events-hash:
events: {
"submit form": "submit", // LEAVE THIS AS IT IS A DOM EVENT
"validated:valid": "valid", // REMOVE THIS
"validated:invalid": "invalid" // REMOVE THIS
}
and add this to your initialize function
initialize: function(){
this.model = new SecurityUserModel();
this.render();
Backbone.Validation.bind(this);
this.model.bind('validated:valid', this.valid); // NEW
this.model.bind('validated:invalid', this.invalid); // NEW
}
Now you are tapping into the model events the correct way.
Hope this helps!

Related

Getting view to update on save using Backbone.js

I am learning Backbone.js and as a trial project I am creating a little WordPress user management application. So far my code shows a listing of all WordPress users and it has a form which enables you to add new users to the application.
This all works fine however when you add a new user the listing of users doesn't update automatically, you need to refresh the page to see the new user added which isn't ideal and defeats one of the benefits of Backbone.js!
I have a model for a user and then a collection which compiles all the users. I have a view which outputs the users into a ul and I have a view which renders the form. How do I make my code work so when the .save method is called the view which contains the users updates with the new user? Or is there another way to approach this?
//define the model which sets the defaults for each user
var UserModel = Backbone.Model.extend({
defaults: {
"username": "",
"first_name": "",
"last_name": "",
"email": "",
"password": "",
},
initialize: function(){
},
urlRoot: 'http://localhost/development/wp-json/wp/v2/users'
});
//define the base URL for ajax calls
var baseURL = 'http://localhost/development/wp-json/wp/v2/';
//function to define username and password
function authenticationDetails(){
var user = "myUserName";
var pass = "myPassword";
var token = btoa(user+':'+pass);
return 'Basic ' + token;
}
//add basic authorisation header to all API requests
Backbone.$.ajaxSetup({
headers: {'Authorization':authenticationDetails()}
});
//create a collection which returns the data
var UsersCollection = Backbone.Collection.extend(
{
model: UserModel,
// Url to request when fetch() is called
url: baseURL + 'users?context=edit',
parse: function(response) {
return response;
},
initialize: function(){
}
});
// Define the View
UserView = Backbone.View.extend({
model: UserModel,
initialize: function() {
// create a collection
this.collection = new UsersCollection;
// Fetch the collection and call render() method
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
},
// Use an external template
template: _.template($('#UserTemplate').html()),
render: function() {
// Fill the html with the template and the collection
$(this.el).html(this.template({ users: this.collection.toJSON() }));
return this;
},
});
var userListing = new UserView({
// define the el where the view will render
el: $('#user-listing')
});
NewUserFormView = Backbone.View.extend({
initialize: function() {
this.render();
},
// Use an external template
template: _.template($('#NewUserTemplate').html()),
render: function() {
// Fill the html with the template and the collection
$(this.el).html(this.template());
return this;
},
events: {
'click .create-user':'addNewUser'
},
addNewUser: function(){
var newFirstName = $('.first-name').val();
var newLastName = $('.last-name').val();
var newEmail = $('.email').val();
var newPassword = $('.password').val();
var newUserName = newFirstName.toLowerCase();
var myNewUser = new UserModel({username:newUserName,first_name:newFirstName,last_name:newLastName,email:newEmail,password:newPassword});
console.log(myNewUser);
myNewUser.save({}, {
success: function (model, respose, options) {
console.log("The model has been saved to the server");
},
error: function (model, xhr, options) {
console.log("Something went wrong while saving the model");
}
});
}
});
var userForm = new NewUserFormView({
// define the el where the view will render
el: $('#new-user-form')
});
All backbone objects (models, collections, views) throw events, some of which would be relevant to what you want. Models throw change events when their .set methods are used, and Collections throw add or update events... a complete list is here.
Once you know which events are already being thrown, you can listen to them and react. For example, use listenTo - in your view's initialize, you can add:
this.listenTo(this.collection, 'add', this.render);
That will cause your view to rerender whenever a model is added to your collection. You can also cause models, collections, whatever, to throw custom events using trigger from anywhere in the code.
EDIT: For the specific case of getting your user listing view to rerender when a new user is added using the form, here are the steps you can take... In the initialize method of your UserView, after the initialize the collection, add:
this.listenTo(this.collection, 'add', this.render);
Then in your form view... assuming you want to wait until the save is complete on your server, in the addNewUser method, in the success callback of your save, add:
userlisting.collection.add(model);
This will work, since the instance of your UserView is in the global scope. Hope this one works for you!

Backbone.js model trigger change only if model.on called again

It's my first post here so please be nice ;) I'm trying to create a Backbone+requireJS sample app and I have stumbled upon a strange problem. In my views initialize function I bind to models change event by
this.model.on("change", this.change());
Event is triggered correctly when data is loaded and all is fine. In my view I also bind to a click event. On click I try to change one of the properties and I was hoping for a change event, but it never came.
I was trying some stuff recommended here on stackoverflow (here, here, and there) but with no luck.
In my guts I feel it has to do with event binding. When I tried to bind again to models on change event inside click callback it started to work. So now I sit here and I'm, hm, confused. Could someone shed some light on this issue?
My View:
define(
[
'jquery',
'underscore',
'backbone',
'text!templates/news/newsListItem.html'
],
function($, _, Backbone, newsListItemTemplate)
{
var NewsListItemViewModel = Backbone.View.extend({
tagName: 'li',
events: {
"click a" : "onLinkClick"
},
initialize: function(){
this.model.on("change", this.changed());
},
render: function()
{
this.$el.html(_.template(newsListItemTemplate, this.model.toJSON()));
},
changed: function()
{
this.render();
console.log("changed");
},
//GUI functions
onLinkClick: function(e)
{
console.log("click!");
this.model.toggle();
this.model.on("change", this.changed());
}
});
var init = function(config){
return new NewsListItemViewModel(config);
}
return {
init : init
}
}
);
My Model:
define(
['jquery', 'underscore', 'backbone'],
function($, _, Backbone){
var NewsListItemModel = Backbone.Model.extend({
toggle: function() {
this.set('done', !this.get('done'));
this.trigger('change', this);
}
});
var init = function(data)
{
return new NewsListItemModel(data);
}
return {
init: init,
getClass: NewsListItemModel
};
}
);
Thanks for any input :)
First, you should use a function as event handler - not its result.
Hence, change the line into this:
this.model.on("change", this.changed.bind(this));
As it stands now, you actually fire this.changed() function just once - and assign its result (which is undefined, as the function doesn't have return statement) to be the model's change event handler.
Second, you shouldn't rebind this handler in onLinkClick: once bound, it'll stay here. It looks like it's more appropriate to trigger this event instead:
onLinkClick: function(e)
{
console.log("click!");
this.$el.toggle();
this.model.trigger('change');
}

Backbone - My model always keeps an id and make a put instead of a post

Sorry for my title, it could not be relevant. I'll try to explain better in this post :)
I understand my problem and I find a solution but I'm not sur this is the good way to do it, so I would like some advise.
My workflow:
I have a form where the user can enter an id.
I make a validation (empty field etc.) and call an API when he click on submit.
if the id exist, I register all the information in my database and send a response with an User Object(Symfony2 + fosRestBunble)
My problem:
When I click for the first time on the form, it works well and a new user his create. But when I try to register a second user, he makes a PUT request because of the id send with the previous user object.
I understand why when I see my code, because I initialize my user model in the intialize function of my view. (before it was outside)
Here is my view:
define(['backbone',
'views/notification/form',
'models/form',
'text!templates/user-form.tpl'],
function(Backbone,
NotificationForm,
User,
FormTpl) {
var UserForm = Backbone.View.extend({
el: "#user-form",
template: _.template(FormTpl),
initialize: function() {
this.model = new User();
this.model.on("invalid",this.showErrors, this);
this.model.on('request',this.ajaxStart, this);
this.model.on('sync',this.ajaxComplete, this);
},
events: {
submit: 'register'
},
render: function() {
this.$el.html(this.template());
},
register: function(e) {
e.preventDefault();
var attrs = {
counter: 0,
created: new Date(),
updated: new Date(),
stackexchangeId: $('#stackId').val(),
site: $('#site').find(":selected").text()
};
var self = this;
this.model.save(attrs,
{
wait:true,
success: function(model, response) {
console.log('success ajax');
console.log(model);
console.log(response);
self.collection.add(model);
//self.collection.add(new User(response));
var form = { id:'#user', messages: 'User has been registered' };
var success = new NotificationForm({ type: 'success', form: form} );
success.closeSuccessNotification();
},
error: function(model, xhr, options) {
self.ajaxComplete();
if(xhr.status == '500') {
var form = { id:'#user', messages: 'Connection StackExchange API failed' };
} else {
var response = JSON.parse(xhr.responseText);
var form = { id:'#user', messages: response.users };
}
new NotificationForm({ type: 'warning', form: form} );
}
}
);
},
showErrors: function(errors) {
var form = { id:'#user', messages: errors.validationError };
new NotificationForm({ type: 'error', form: form} );
},
ajaxStart: function() {
this.$('#spinner-register').addClass('spinner-anim');
},
ajaxComplete: function() {
this.$('#spinner-register').removeClass('spinner-anim');
}
});
return UserForm;
So when I click a second time my model is the same and the id is here.
I have found a solution but I'm not sur this is a good one because I have to move my event from the initialize function.
So I create that :
test: function(model) {
model.on("invalid",this.showErrors, this);
model.on('request',this.ajaxStart, this);
model.on('sync',this.ajaxComplete, this);
},
and in register I make that:
register: function(e) {
this.model = new User();
this.test(this.model);
e.preventDefault();
etc.
}
It works fine but I totally remove the initialize function, it doesn't sound very good. I would like to keep the initialize like in my first example and to always have a new User model.
Thanks :)
You can use model.clear() to make the model as fresh as new, then on save() the POST request will be sent.
...
this.model.clear();
this.model.save(attrs,
...

backbonejs fire event after parse is complete

I have this collection with an over-riden parse method. I want a method in my view to be called when the collection is finished with parse
This collection will be calling sync and so parse only once.
I tried this.collection.on("reset", this.more, this); but that doesn't work.
more: function() {
var users = this.collection.slice( this.index, this.index + this.load_once), that = this;
this.index = this.index + this.load_once;
_.each( users, function( user ){
that.addOne( user );
});
},
addOne: function( user ){
var view = new UserView({model: user});
this.$("#user_list").append(view.render().el);
}
The reset method will be triggered when {reset: true} option is passed to the fetch. You can listen to the add and sync that will fire this method. Also use this.listenTo bind the events in a cleaner manner.
initialize: function() {
... some other code
this.listenTo(this.collection, "add sync", this.more);
this.collection.fetch();
}

Event handling between views

Ok I have a layout like the one in this pic:
The table in the upper part of the screen is made by:
MessageListView
define(['backbone','collections/messages','views/message'], function(Backbone, MessageCollection, MessageView) {
var MessageListView = Backbone.View.extend({
el: '#messagesContainer',
initialize: function() {
this.collection = new MessageCollection();
this.collection.fetch({reset:true});
this.listenTo( this.collection, 'reset', this.render );
this.table = this.$el.find("table tbody");
this.render();
},
render: function() {
this.collection.each( function(message, index) {
this.renderMessage(message, index);
}, this);
},
renderMessage: function(message, index) {
var view = new MessageView({
model:message,
className: (index % 2 == 0) ? "even" : "odd"
});
this.table.append( view.render().el );
}
});
return MessageListView;
});
MessageView
define(['backbone','models/message'], function(Backbone, MessageCollection, MessageView) {
var MessageView = Backbone.View.extend({
template: _.template( $("#messageTemplate").html() ),
render: function() {
this.setElement( this.template(this.model.toJSON()) );
return this;
},
events:{
'click':'select'
},
select: function() {
// WHAT TO DO HERE?
}
});
return MessageView;
});
AppView
define(['backbone','views/messages'], function(Backbone, MessageList) {
var App = Backbone.View.extend({
initialize: function() {
new MessageList();
}
});
return App;
});
I will soon add a new view (maybe "PreviewView") in the lower part of the screen.
I want to make something happen inside the "PreviewView" when user clicks a row.
For example, it could be interesting to display other model's attributes (details, e.g.) inside the PreviewView.
What is the best practice?
holding a reference to PreviewView inside each MessageView ?
triggering events inside select method, and listening to them using on() inside the preview view.
using a transient "selected" attribute in my model, and make PreviewView listen to collection "change" events?
Thank you, if you need more details tell me please, I'll edit the question.
Not sure about the best practice but I found this solution trivial to implement. I created a global messaging object, bus, whatever:
window.App = {};
window.App.vent = _.extend({}, Backbone.Events);
You have to register the "triggerable" functions of PreviewView on the previously created event bus (according to your example, this should be in the PreviewView):
initialize: function () {
App.vent.on('PreviewView.show', this.show, this);
}
Now you should be able to trigger any of registered events from anywhere within your application by calling: App.vent.trigger. For example when the user click on a row you will have something similar:
App.vent.trigger('PreviewView.show');
in case if you have to send and object along with the triggered event use:
App.vent.trigger('PreviewView.show', data);

Resources