I am using Backbone version 0.9.2,
jquery version 1.7.2 and
Backbone.Validation v0.5.2.
My html look like (1), the backbone.view (2) and the backbone.model (3).
It seems that my implementation does not work at all anymore.
When I click on submit button, it performs a POST request without making any form validation.
Any ideas why and how can I fix this problem?
Please see the comments in my code.
Thanks.
(1)
<form method="POST" class="form1">
<div class="control-group">
<label for="reason" class="control-label">Reason</label>
<div class="controls">
<textarea id="reason" name="reason" required="required" /></textarea>
</div>
</div>
<!-- other codes -->
</form>
(2)
var myView = Backbone.View.extend({
initialize: function () {
this.model = new MyModel();
this.model.bind('validated:invalid', function(model, attrs) {
console.log('validated:invalid', model, attrs); // It does not work
});
this.model.bind('validated:valid', function(model) {
console.log('validated:valid', model); // It does not work
});
},
events: {
'click [data-tid="submit"]': 'submitForm'
},
submitForm: function (event) {
event.preventDefault();
this.model.set(data);
this.model.save({
success: function () {
// some code
}
});
}
(3)
var MyModel = Backbone.Model.extend({
urlRoot: 'someUrl',
validation: {
reason: {
required: true,
msg: 'Reason is required'
}
}
});
You need to call Backbone.Validation.bind(this) after you created the model in initialize.
Hope this helps!
you can use form validation. I think you need to listen to the submit event.
Backbone works as they should.
events: {
'submit': 'submit'
}
Related
What I have:
I have a edit function in which I rerender my view;
MyView = Backbone.View.extend({
edit: function (view) {
......
view.render();
}
});
What a problem:
There is an usecase in which during edit function view can be closed, so I MUST NOT invoke view.render() at the end of edit function.
Question:
How to check if view has already been closed inside edit function? Something like:
MyView = Backbone.View.extend({
edit: function (view) {
......
if (!view.isClosed())
view.render();
}
});
For #net.uk.sweet:
I use Bootstrap X-Editable. With help of it I can modify test fields. To modify text field I just need to click on it, change value, and then click outside of it (outside of text field). In such case method success will be invoked.
textEditor: function(view) {
$(this).editable({
type: 'textarea',
mode: 'inline',
onblur: 'submit',
showbuttons: false,
inputclass: 'edit-comments-text-input',
validate: function(value) {
if (!value.trim()) {
return 'Can not be empty!';
}
},
success: function(response, newValue){
//modify the comment
comment.text = newValue.trim();
//rerender
if (!view.isClosed()) //This line is what I need, but view hasn't isClosed method ((
view.render();
}
});
}
Also it is worth to say that user can close view by clicking on close button or by clicking outside of view.
Problem use case:
User click on x-editable field
Change text
Click close button
What happens in such case:
Two actions:
View is closed
success method, which invoke view.render(), BUT MUST NOT!
Summary:
I need to check inside of my success method if view has been closed.
Coarse solution:
Seems I find some solition, not the best one, of course.
if ($(view.el).hasClass('in'))
view.render();
MyView = Backbone.View.extend({
initialize : function() {
this.setClosed(false);
},
setClosed : function(booleanValue) {
this.closed = booleanValue;
},
getClosed : function() {
return this.closed;
},
edit: function () {
var view = this;
//......
if (!view.getClosed()) {
view.render();
}
}
});
My backbone.js form has a single textfield (no submit button). I need to capture submit event (using enter key) in the view. Below is the sample code. Somehow the submit method is not called on pressing enter. Instead the form goes for a reload.
Script :
var FormView = Backbone.View.extend({
el: '#form',
events: {
"submit": "submit",
},
initialize: function () {
console.log("initialize");
},
submit: function (e) {
e.preventDefault();
console.log("submit");
}
});
new FormView();
HTML :
<form id="form">
<input type="text"/>
</form>
Add this to your Backbone view:
events: {
'submit form': 'submit'
}
Also, note that in your HTML, the form action has to be defined.
If you don't have the action defined, then do this:
events: {
'keyup': 'processKey'
}
processKey: function(e) {
if(e.which === 13) // enter key
this.submit();
}
If you're having trouble capturing submit events ensure that your 'el' property is defined in your view.
var myView = Backbone.View.extend({
el: $(body),
events: {
"submit form": "doMethod"
},
doMethod: function(e) {
e.preventDefault();
console.log('form fired');
}
});
I am creating form something like this using backbone, underscore and backbone.stickit libraries. My screen most look somewhat like this .
form html
<form id="main">
<div>
<label for="words">words: <input name="words" type="text"/></label>
</div>
<div>
<label for="type">type: <input name="type" type="text"/></label>
</div>
<div>
<input id="okButton" class="btn" value="save"/>
</div>
backbone model/view
var app = {
Model: Backbone.Model.extend({}),
View: Backbone.View.extend({
initialize: function() {
this.setElement($('#main'));
},
bindings: {
'input[name=words]': 'words',
'input[name=type]': 'type',
'input#okButton': {
attributes: [{
name: 'disabled',
observe:['type','words'],
onGet: 'okButtonDisabled'
}],
}
},
okButtonDisabled: function(words) {
return words[0].length > 0 ? null : true;
},
addDisabledClass: function(words) {
return words[0].length > 0 ? "" : "disabled";
},
render: function() {
this.stickit();
}
})
};
When user enter some value in type field, the "save" button gets enabled, otherwise it is disabled.
The above examples binds only type field. it does not act for "words" fields.
Thing I am working on
The jsfiddle for the form I am working on is here.
form html
<form id="main">
<div>
<label for="words">words: <input name="words" type="text"/></label>
</div>
<div>
<label for="type">type: <input name="type" type="text"/></label>
</div>
</form>
model/view code
var app = {
Model: Backbone.Model.extend({}),
View: Backbone.View.extend({
initialize: function() {
this.setElement($('#main'));
},
bindings: {
'input[name=words]': 'words',
'input[name=type]': 'type',
'input#okButton': {
attributes: [{
name: 'disabled',
observe:['type','words'],
onGet: 'okButtonDisabled'
}],
}
},
okButtonDisabled: function(words) {
return words[0].length > 0 ? null : true;
},
addDisabledClass: function(words) {
return words[0].length > 0 ? "" : "disabled";
},
render: function() {
this.stickit();
}
})
};
var model = new app.Model({
words: "apple",
type: "fruit"
});
var view = new app.View({
model: model
});
view.render();
What I am trying to do is:
set initially the "save" button as disabled.
If user makes any changes to any of form field, it must enable "save" button.
But, if user enters something in any field so that "save" button is enabled, and again make the field same as original , the "save" button must be disabled again. i.e. editing "apple" to "apppleee" so that "save" is enabled, and reverse "appleee" back to "apple" to disable "save" button again.
Thanks for help in advance.
I would suggest adding some logic (function) to your model to check an objects value against its' default. Then, in your okButtonDisabled method, call the model method to check for default values. Here is an example from the code you have supplied:
var app = {
Model: Backbone.Model.extend({
defaults: {
words: "apple",
type: "fruit"
},
isDefault: function(key) {
return this.get(key) === this.defaults[key];
}
}),
View: Backbone.View.extend({
initialize: function() {
this.setElement($('#main'));
},
bindings: {
'input[name=words]': 'words',
'input[name=type]': 'type',
'input#okButton': {
attributes: [{
name: 'disabled',
observe:['type','words'],
onGet: 'okButtonDisabled'
}],
}
},
okButtonDisabled: function(words) {
return (!this.model.isDefault("words") ||
!this.model.isDefault("type")) ? null : true;
},
addDisabledClass: function(words) {
console.log("is this neccessary?");
return (!this.model.isDefault("words") ||
!this.model.isDefault("type")) ? "" : "disabled";
},
render: function() {
this.stickit();
}
})
};
var view = new app.View({model:new app.Model()});
view.render();
Modified JSFiddle
If you have many fields, you could change the isDefault method to accept an array of keys. Then, loop through the keys and return false if ANY key does not match its' default. Something like:
Model: Backbone.Model.extend({
isDefault: function(keys) {
for (var i = 0; i<keys.length; i++) {
if (this.get(keys[i]) !== this.defaults[keys[i]]) {
return false;
}
}
return true;
}
});
View: Backbone.View.extend({
okButtonDisabled: function(words) {
return !this.model.isDefault(["words", "type"]) ? null : true;
}
});
EDIT:
If you can assume that ALL attributes in the Model are associated with a form input, then you could iterate through each attribute and check for a difference. Something like this:
Model: Backbone.Model.extend({
isDefault: function(keys) {
for (var key in this.attributes) {
if (this.get(key) !== this.defaults[key]) {
return false;
}
}
return true;
}
});
View: Backbone.View.extend({
okButtonDisabled: function(words) {
return !this.model.isDefault() ? null : true;
}
});
Updated Abstracted JSFiddle
"set initially the "save" button as disabled."
This can be done with HTML markup or in the initialize method of your view (or such).
"If user makes any changes to any of form field, it must enable "save" button.
But, if user enters something in any field so that "save" button is enabled, and again make the field same as original , the "save" button must be disabled again. i.e. editing "apple" to "apppleee" so that "save" is enabled, and reverse "appleee" back to "apple" to disable "save" button again."
How to enable/disable save button of backbone form-view when user changes form content should give you a solution for that part as well.
Basically, you store your model's original state like myView._model = myView.model.toJSON(). When your model change, you compare this.model.toJSON() and this._model (with the help of _.isEqual or just checking some attributes) and enable/disable your button accordingly.
I have the following backbone.js view shown below. It was working fine, and now the click event does not execute when the form button is clicked. No errors in console, or any other obvious signs of why is shown.
window.FormOptionView = Backbone.View.extend({
template: _.template($('#test-option').html()),
render: function (eventName) {
$('#test1').append(this.template(this.model.toJSON()));
$('#test2').append(this.template(this.model.toJSON()));
return this;
}
});
window.AddTestView = Backbone.View.extend({
template: _.template($('#test-add').html()),
initialize: function () {
this.collection.bind("reset", this.render, this);
this.render();
},
events: {
"click button": "addTest"
},
addTest: function(event){
event.preventDefault();
var test = new Test({
test1: {
firstName: $("#test1 option:selected").val(),
lastName: $("#test1Score").val()
},
test2: {
firstName: $("#test2 option:selected").val(),
lastName: $("#test2Score").val()
}
});
test.add(result);
app.navigate("tests", true);
},
render: function (eventName) {
$('#content').html(this.template());
_.each(this.collection.models, function (test) {
this.renderSelectBox(test);
}, this);
return this;
},
renderSelectBox: function (item) {
var optionView = new FormOptionView({
model: item
});
$(this.el).append(optionView.render().el);
}
});
The corrosponding HTML
<script type="text/template" id="test-option">
<option><%= firstName %> <%= lastName %></option>
</script>
<script type="text/template" id="test-add">
<form id="addTest" action="#">
<label for="test1">Test1:</label>
<select id="test1">
<option></option>
</select>
<label for="test1Score">Score:</label>
<input id="test1Score" type="number" />
<label for="test2">Test 2:</label>
<select id="test2">
<option></option>
</select>
<label for="test2Score">Score:</label>
<input id="test2Score" type="number" />
<button id="add">Add</button>
</form>
</script>
Without seeing the corresponding HTML, my best guess is that your <button> is outside the scope of your view. Click events are only attached to children of the root element of your View.
Just found the views el tag corresponded to a bunch of empty <div>s. Not to sure why but a simple el: $('#content') managed to fix the problem. Thanks
Your problem, as you've already discovered, is that your view wasn't handling its el properly.
When your view is rendered, Backbone hooks up the events by using jQuery's delegate on the view's this.el.
If you don't explicitly set the el (through the constructor as new V({ el: ... }), in the view's definition as el: ..., or by calling setElement), then the el will be constructed using various view properties as noted in the documentation; the default el is just a simple <div>. This is where your empty <div>s are coming from.
You were doing this:
render: function (eventName) {
$('#content').html(this.template());
_.each(this.collection.models, function (test) {
this.renderSelectBox(test);
}, this);
return this;
}
and somewhere else you were probably doing a simple add_test_view.render(). The result is that the event delegate is bound to the view's el (an empty <div>) and that el is never added to the DOM; if you have delegate bound to something that isn't in the DOM then you never get any events.
If you say el: '#content' in your view definition, then Backbone will use #content as your el and your view would look more like this:
el: '#content',
//...
render: function (eventName) {
this.$el.html(this.template());
this.collection.each(function(test) {
this.renderSelectBox(test);
}, this);
return this;
},
renderSelectBox: function (item) {
var optionView = new FormOptionView({
model: item
});
this.$el.append(optionView.render().el);
}
Notice that I switch to this.collection.each, Backbone collections have various Underscore methods mixed in so you don't have to access collection.models manually; I've also switched to this.$el since view already have a cached jQuery'd version of this.el.
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.