Backbone.js app not rendering view - javascript

I found backbone.js a couple of days ago, and i found out its a pretty code tool for javascript development though my javascript skill aren't great.
However after reading the documentation, i decided to code a simple contact app.
I save the contact data on browser localstorage.
This is code
// Source Code for my contacts app
$(function() {
//Contact Model
Contact = Backbone.Model.extend({
//Contact Defaults
defaults : {
first_name : 'First Name',
last_name : 'Last Name',
phone : 'Phone Number'
},
//Constructor(intialize)
//Ensuring each contact has a first_name,last_name,phone
intialize: function(){
if(!this.get("first_name")) {
this.set({"first_name":this.defaults.first_name});
}
if(!this.get("last_name")) {
this.set({"last_name":this.defaults.last_name});
}
if(!this.get("phone")) {
this.set({"phone":this.defaults.phone});
}
}
});
//Contact Collection
//The collection is backed by localstorage
ContactList = Backbone.Collection.extend({
//Model
model : Contact,
//Save all contacts in localstorage under the namespace of "contacts"
localStorage: new Store("contacts")
});
//Create global collection of Contacts
Contacts = new ContactList;
//Contact View
ContactView = Backbone.View.extend({
tagName : "li",
template: _.template($("#item_template").html()),
events : {
"click span.contact-delete": "delete_contact"
},
intialize: function(){
this.bind('change',this.render,this);
this.bind('destroy',this.remove,this);
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
setContent: function() {
var first_name = this.model.get("first_name");
var last_name = this.model.get("last_name");
var phone = this.model.get("phone");
var name = first_name+" "+last_name;
this.$('.contact-name').html(name);
this.$('.contact-phone').html(phone);
},
remove: function() {
$(this.el).remove();
},
delete_contact: function() {
this.model.destroy();
}
});
//The Application
AppView = Backbone.View.extend({
el: $("#contact-app"),
events : {
"click #new-contact #save-button": "createContact"
},
intialize: function() {
Contacts.bind("add", this.addOne, this);
Contacts.bind("reset", this.addAll, this);
Contacts.fetch();
},
// Add a single contact item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(contact) {
var view = new ContactView({model: contact});
this.$("#contact-list").append(view.render().el);
},
// Add all items in the **Contacts** collection at once.
addAll: function() {
Contacts.each(this.addOne);
},
// Generate the attributes for a new Contact item.
newAttributes: function() {
return {
first_name : this.$('#first_name').val(),
last_name : this.$('#last_name').val(),
phone : this.$('#phone').val()
};
},
createContact: function() {
Contacts.create(this.newAttributes());
//Reset Form
this.$('#first_name').val('');
this.$('#last_name').val('');
this.$('#phone').val('');
}
});
// Finally,kick things off by creating the **App**.
var App = new AppView;
});
And this is my html source
<div id="contact-app">
<div class="title">
<h1>Contacts App</h1>
</div>
<div class="content">
<div id="new-contact">
<input name="first_name" placeholder="First Name" type="text" id="first_name"/>
<input name="last_name" placeholder="Last Name" type="text" id="last_name" />
<input name="phone" placeholder="Phone Number" type="text" id="phone" />
<button id="save-button">Create Contact</button>
</div>
<div id="contacts">
<ul id="contact-list">
</ul>
</div>
<div id="contact-stats"></div>
</div>
</div>
<script type="text/template" id="item_template">
<div class="contact">
<div class="contact-name"></div>
<div class="contact-phone"><div>
<span class="contact-delete"></span>
</div>
</script>
The contact data gets saved in the local storage, which i can see via firebug but the view is not updated. Am new to backbone.js.
What is the problem, there are no javascript errors.

Try using "add" instead of 'create' for adding models to the collection (I don't think the 'add' event is being fired by the 'create' method).
Instead of
Contacts.create(this.newAttributes());
Use
Contacts.add(this.newAttributes());
To save the model to local storage you can call the save method
addOne: function(contact) {
var view = new ContactView({model: contact});
contact.save();
this.$("#contact-list").append(view.render().el);
},
EDIT:
Another thing check the spelling of your "intialize" method i think it should be "initialize".
Here's a jsFiddle, I'm not saving it to localStorage in the jsfiddle, but that should work by you.

On the model, the defaults should take care of the default values, the initialize functions are probably not needed; someone correct me on this if i'm wrong.
On your ContactView, you may have to change your render line to this in your initialize method:
this.model.bind('change', _.bind(this.render, this));

Related

Backbone View showing then disappearing

I have a view created with Backbone.js and inserted into a div element - it shows for about a second and then disappears.
Here is my code for the view:
var addPlayerView = Backbone.View.extend({
tagName: "div",
model: Player,
id: 'addPlayerDiv',
initialize: function() {
console.log('addPlayerView has been created');
},
render: function (){
this.$el.html('<p>show this puppy</p>');
return this;
}
});
here is the model:
var Player = Backbone.Model.extend({
defaults: {
ID: "",
firstName: '',
lastName: ''
},
idAttribute: "ID"
});
and here is the HTML:
<form onsubmit="addNewPlayer();">
<input type="submit" value="Add New Player New"/>
</form>
<p>
<div id="addPlayerDiv"></div>
</p>
<script>
function addNewPlayer() {
var player = new Player({});
var newPlayerView = new addPlayerView({el: $("#addPlayerDiv"), model: player});
newPlayerView.render();
};
</script>
The addNewPlayer() function is being called correctly and the newPlayerView is rendering on the page, but only for a second, then it disappears on the page.
No idea what to do. Anyone have this problem before?
You need cancel the default action (in our case onsubmit tries send data to server)
<form onsubmit="addNewPlayer(); return false;">
or
<form onsubmit="addNewPlayer(event);">
function addNewPlayer(e) {
e.preventDefault();
.....
}
Example

Backbone JS: Issue related to click event and POST request

I am new to Backbone and just finished a simple get request. Trying to implement a simple POST request.
Use case:
When user clicks on Transfer button input field values will be sent to a REST API as a JSON object.
<div id="transfer">
<input type="text" placeholder="From Address" id="fromAddress" />
<input type="text" placeholder="To Address" id="toAddress" />
<input type="text" placeholder="Amount" id="dollars" />
<input type="button" id="button" value="Transfer"/>
</div>
Issue 1
First problem is that what will go into the Backbone View.
My Backbone View:
var TransferView = Backbone.View.extend({
events: {
"click #button": "sendMoney"
},
sendMoney: function() {
alert();
console.log($("#fromAddress").val());
//this.model.transferMoney($("#fromAddress").val(),
$("#toAddress").val(), $("#dollars").val());
}
});
var transferView = new TransferView();
transferView.render();
When I click on button nothing happens. What is the issue here?
Issue 2
Backbone Model looks like this.
var Money = Backbone.Model.extend({
url: 'http://localhost:3000/sendMoney',
defaults: {
fromAddress: "",
toAddress: "",
amount: ""
},
transferMoney: function(request, response) {
//get field values?
this.save();
}
});
var transferMoney = new Money();
Flow didn't reach the model yet, but I am not sure how would I fetch fromAddress, toAddress and amount values from req? How would I pass the request parameters in JSON format to REST service?
Note: Can not use form here. It is more like a ajax request.
The issues with your view are :
you're missing a template
you have a syntax error in $("#toAddress").val(), $("#dollars").val());
Since the view doesn't have a template, nothing gets displayed and therefore there is no "#button" to attach events to. Also, don't forget you'll typicallly need to provide a Money model instance to the view, so that you can then set attributes on it.
To pass value from the form, just use the val() method.
And to send data to the API, you just need to save: Backbone does the rest.
Basically, what you probably want to have is something like
var TransferView = Backbone.View.extend({
events: {
"click #button": "sendMoney"
},
sendMoney: function() {
this.model.save({
fromAddress: $("#fromAddress").val(),
toAddress: $("#toAddress").val(),
amount: $("#dollars").val(),
});
}
});
And to instanciate the view:
var money = new Money();
var transferView = new TransferView({ model: money });
It would probably be a good investment of your time to read a few Backbone tutorials, such as http://coenraets.org/blog/2011/12/backbone-js-wine-cellar-tutorial-part-1-getting-started/
To delegate saving to a model method, do something like:
var Money = Backbone.Model.extend({
url: 'http://localhost:3000/sendMoney',
defaults: {
fromAddress: "",
toAddress: "",
amount: ""
},
transferMoney: function(attributes) {
this.save(attributes);
}
});
var TransferView = Backbone.View.extend({
events: {
"click #button": "sendMoney"
},
sendMoney: function() {
this.model.transferMoney({
fromAddress: $("#fromAddress").val(),
toAddress: $("#toAddress").val(),
amount: $("#dollars").val(),
});
}
});

How to enable/disable save button of backbone form-view when user changes form content

I have form which gets data from backbone model. When the form is shown, they have initially value set to backbone model. Now, if any field is edited, i want to enable "save" button immediately as soon as any changes is made to field. However, if you change field value and again change it to original, it should again disable the "save" button that allows to save model.I want to achieve as one shown in this jsfiddle :http://jsfiddle.net/qaf4M/2/
I am using backbone.js and backbone.stick (http://nytimes.github.io/backbone.stickit/) to bind model to template.
I create view as follows with model as parameter
RegionManager.show(new app.myView({
model : new app.myModel(
{id: 1})
}));
MY model value is something like this:
{
"id":1, "name:"a" , "age":21
}
The view is as follows:
myView = Backbone.View.extend({
template: _.template( $("#template").html() ),
events: {
"click #save" : "update",
},
bindings: {
'#id': 'id',
'#name': 'name',
'#age': 'age'
},
initialize: function () {
if(this.model){
this.model.fetch();
},
render: function () {
this.$el.html( this.template );
this.stickit(); //used library http://nytimes.github.io/backbone.stickit/
Backbone.Validation.bind(this);
},
update: function() {
this.model.save (
{success: this.success_callback, error: this.error_callback});
},
success_callback: function (model, response) {
},
error_callback: function (model, response) {
alert('error.');
}
});
My template look like
<script type="text/template" id="template">
<form id="myForm " >
<fieldset>
<label>Name</label>
<input type="text" id="name" name="name" />
<label>Age</label>
<input type="text" id="age" name="age" />
<input type="hidden" id="id" name="id"/>
</fieldset>
<a id="save" disabled >Save Changes</a>
</form>
I am confused where should i bind event and how or what is proper way to know the user has chagne some value and accordingly disable button when there is no cahnge in form and enable when change has been made.
A simple solution would be to make your view listen to your model's changes:
initialize: function () {
if(this.model){
this.model.fetch({
success: function(model, resp) {
this.model._attributes = resp; // create a copy of the original attributes
this.listenTo(this.model, 'change', function() {
// when the model changes
// maybe write some function to compare both
if(_.isEqual(this.model._attributes, this.model.toJSON())) {
// disable
}
else {
// able
}
});
}
});
}
So, when the data comes back from the server, you create a copy, and then listen to your model's changes. If the new attributes equal the original, disable the button.

Ember bindings firing when not wanted, and not firing when required

Hey I'm having two different issues in my ember app, both of which involve bindings.
First, I have a binding firing when I don't want it to. Basically what I'm trying to achieve (I'm building a survey creator front-end app) is that when any text is entered into the 'name' field of a question, I want to add a new question object, which will render out another blank question at the end of the list of questions that the user is adding. This has the effect of there always being a new question, so an add question button is not required. The binding is working, and a new object is being added: however, since the binding is from the newest question object, the binding is triggered again when the new object is created, which in turn creates a new object, which triggers the binding again....which obviously eventually crashes the browser. I've tried using the Ember._suspendObserver function, but there isn't a lot of documentation on this, and I think I'm using it wrong - anyhow it isn't suspending the observer or pausing the binding. The observer in the code is around line 27 (contentsNameObserver)
The other issue I'm having -- I have a selection drop down box which selects what type of question the user wants (single answer, multi-choice, etc.) but the binding between the select box and the {{#each}} helper which renders the kind of question isn't triggering. I'm using the Ember.Select view helper, so there shouldn't be any issues with using get/set to fire the binding. I'm using a computed property to return an array of fields for the question type based on the value of the question type id. The computed property is in line 13 (App.SurveyContent.types), and the template templates/step3. Quick heads up that this app may be extended for more than surveys, hence 'questions' are often referred to in the code as 'content'.
I'm pretty new to ember (this is my first real app) so my code most likely has a lot of issues outside of these problems...so any comments on how I've structured my app would be hugely appreciated as well!
Javascript ember app:
App = Ember.Application.create({
rootElement: '#emberContainer'
});
App.SurveyContent = Ember.Object.extend({
name: "",
content_type: 1,
content_pos: 1,
hash: Em.A([]),
types: function() {
alert("redraw");
return App.ContentTypes[this.content_type-1].hash;
}.property()
});
App.Surveys = Ember.Object.create({
name: null,
start: $.datepicker.formatDate('mm/dd/yy' , new Date()),
end: $.datepicker.formatDate('mm/dd/yy' , new Date()),
themeID: 0,
contents: [App.SurveyContent.create()], //Pushing an instance of App.SurveyContent onto this
contentsNameObserver: function() {
context = this;
console.log("entering");
Em._suspendObserver(App.Surveys, "contents.lastObject.name", false, false, function() {
console.log("suspend handler");
context.contents.pushObject(App.SurveyContent.create());
})
}.observes("contents.lastObject.name")
});
App.ContentTypes = [
Ember.Object.create({name: 'Text question', id:1, hash: [Ember.Object.create({name: 'Question', help: 'Enter the question here', type: 'text'})]}),
Ember.Object.create({name: 'Multichoice question', id:2, hash: [Ember.Object.create({name: 'Question', help: 'Enter the question here', type: 'text'}),
Ember.Object.create({name: 'Answer', help: 'Enter possible answers here', type: 'text', multiple: true})]})
];
App.ViewTypeConvention = Ember.Mixin.create({
viewType: function() {
console.log(this);
return Em.get("Ember.TextField");
}.property().cacheable()
});
App.CRMData = Ember.Object.extend();
App.CRMData.reopenClass ({
crm_data: [],
org_data: [],
org_display_data: [],
loadData: function() {
context = this;
context.crm_data = [];
$.getJSON ("ajax/crm_data", function(data) {
data.forEach(function(crm) {
context.crm_data.pushObject(App.CRMData.create({id: crm.crm_id, name: crm.crm_name}));
crm.orgs.forEach(function(org) {
context.org_data.pushObject(App.CRMData.create({id: org.org_id, name: org.org_name, crm_id: crm.crm_id}));
}, context)
}, context)
context.updateOrganisations(5);
});
return this.crm_data;
},
updateOrganisations: function(crm_id) {
context = this;
this.org_display_data.clear();
console.log("clearing the buffer")
console.log(this.org_display_data)
context.org_data.forEach(function(org) {
if(org.crm_id == crm_id) {
context.org_display_data.pushObject(App.CRMData.create({id: org.id, name: org.name}));
}
}, context)
}
});
App.DateField = Ember.TextField.extend({
attributeBindings: ['id', 'class']
});
App.CRMSelect = Ember.Select.extend({
attributeBindings: ['id'],
change: function(evt) {
console.log(evt)
App.CRMData.updateOrganisations($('#crm').val())
}
});
App.ApplicationController = Ember.Controller.extend();
App.Step1Controller = Ember.ArrayController.extend({});
App.Step2Controller = Ember.ArrayController.extend({});
App.Step2Controller = Ember.ArrayController.extend({});
App.ApplicationView = Ember.View.extend({
templateName: 'app'
});
App.Step0View = Ember.View.extend ({
templateName: 'templates/step0'
});
App.Step1View = Ember.View.extend ({
templateName: 'templates/step1'
});
App.Step2View = Ember.View.extend ({
templateName: 'templates/step2',
didInsertElement: function() {
$( ".jquery-ui-datepicker" ).datepicker();
}
});
App.Step3View = Ember.View.extend ({
templateName: 'templates/step3',
});
App.Router = Em.Router.extend ({
enableLogging: true,
root: Em.Route.extend ({
showstep1: Ember.Route.transitionTo('step1'),
showstep2: Ember.Route.transitionTo('step2'),
showstep3: Ember.Route.transitionTo('step3'),
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('applicationController').connectOutlet( 'step0');
}
}),
step1: Ember.Route.extend ({
route: 'step1',
connectOutlets: function(router){
router.get('applicationController').connectOutlet( 'step1', App.CRMData.loadData());
}
}),
step2: Ember.Route.extend ({
route: 'step2',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('step2')
},
}),
step3: Ember.Route.extend ({
route: 'step3',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('step3')
},
})
})
});
Ember.LOG_BINDINGS=true;
App.LOG_BINDINGS = true;
App.ContentTypes.forEach(function(object) {
object.hash.forEach(function(hash) {
hash.reopen(App.ViewTypeConvention);
}, this);
}, this);
Html templates (I've got these in haml, so this is just a representation of the important ones)
<script type="text/x-handlebars" data-template-name="app">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="templates/step3">
<h1> Add content to {{App.Surveys.name}} </h1>
<br>
<div id = "accordion2" class = "accordion">
{{#each content in App.Surveys.contents}}
<div class="accordion-group">
<div class = "accordion-heading">
<a class = "accordion-toggle" data-parent = "#accordion2" data-toggle = "collapse" href = "#collapseOne">
{{content.name}}
</a>
</div>
<div id = "collapseOne" class = "accordion-body collapse in">
{{view Ember.TextField valueBinding="content.name" class="txtName"}}
<form class = "form-horizontal">
<div class = "accordion-inner">
<div class = "control-group">
<label class = "control-label" for ="organisation">
Content Type
<div class = "controls">
{{view Ember.Select contentBinding="App.ContentTypes" optionValuePath="content.id" optionLabelPath="content.name" valueBinding="content.content_type"}}
</div>
</div>
</div>
{{#each item in content.types }}
<div class = "control-group" >
<label class = "control-label" for = "organisation">
{{item.name}}
<div class = "controls">
{{view item.viewType }}
</div>
{{/each}}
</div>
</form>
</div>
{{/each}}
</div>
</div>
<br>
<div class = "btn" {:_action => 'showstep3'}> Next Step > </div>
</script>
I've solved the first issue, although I didn't get the suspendObserver property working I used an if statement to check the previous element, removing the infinite loop.
contentsNameObserver: function() {
context = this;
if(this.get('contents.lastObject').name) {
context.contents.pushObject(App.SurveyContent.create());
}
}.observes("contents.lastObject.name")
Any comments on how to get the _suspendObserver handler working would be appreciated though, it is something that should work but I'm doing something wrong
I've created a stripped down jsfiddle at http://jsfiddle.net/reubenposthuma/sHPv4/
It is set up to go straight to the problem step, step 3, so that I don't need to include all the previous templates.
I'm still stuck on the issue of the binding not firing though. The behaviour I'm expecting is that when the 'Content Type' dropdown box is changed, the text box underneath should change, it should re-render with two text boxes.
I realise this is an old question, but there is no documenation and precious little information I could find searching either, hence sharing what I found worked here.
What I found worked was to call Ember._suspendObserver as follows:
somePropertyDidChange: function(key) {
var that = this;
Ember._suspendObserver(this, key, null,
'somePropertyDidChange', function() {
// do stuff which would normally cause feedback loops
that.set('some.property', 'immune to feedback');
});
}.observes('some.property');
You can also use the multiple observer variant as follows:
somePropertiesDidChange: function(key) {
var that = this;
Ember._suspendObservers(this, ['some.property', 'another.property'],
null, 'somePropertiesDidChange', function() {
// do stuff which would normally cause feedback loops
that.set('some.property', 'immune to feedback');
that.set('another.property', 'also immune to feedback');
});
}.observes('some.property', 'another.property');
In my exact use case I actually called Ember._suspendObservers from an Ember.run.once() function which was setup by the observer since I wanted to make sure a number of dependant properties had settled before doing calculations which in turn would mutate some of those properties.

Adding ID and Class on template dynamically using backbone views

I want to add a ID and CLASS attribute to my view template.
this is what i tried but failed.
$("#login").html( _.template( LoginTemplate ) );
this.loginmodel = new LoginModel();
this.loginview = new LoginView({
el: $("#loginContainer"),
model: this.loginmodel,
className: "test"
});
<div id="loginContainer">
<div id="loginForm">
<input class="user-input formContainerInput" id="username" placeholder="Username" required="required"/>
<input class="user-input formContainerInput" id="password" type="password" placeholder="Password" required="required"/>
<a class="connection-btn" data-role="button">Connection</a>
<a class="login-btn" data-role="button">Log In</a>
</div>
I want to assign id and class using the views and not on the html itself. How will i do it?
update
attemp #1
loginview: function(){
$("#login").html( _.template( LoginTemplate ) );
this.loginmodel = new LoginModel();
this.loginview = new LoginView({
id: "#loginContainer",
model: this.loginmodel,
attributes:{ id:'Test', class: "myClass otherClass" }
});
},
it even display an error in aptana on the "class" part.
even tried it on the main view since the code above was the parent view.
var LoginView = Backbone.View.extend({
events: {
"click .login-btn": "Login",
"click .connection-btn": 'Connect',
},
initialize: function(){
//some code here
},
attributes: {
id:"test"
}
});
What about using View.attributes.
You can specify something like:
attributes: {
id: "myId",
class: "myClass otherClass"
}
I didn't try but maybe you also can use functions to make it even more dynamic:
attributes: {
id: function(){ return "element-" + this.id; },
class: "myClass otherClass"
}
Beware this only affects to the view.el DOM element.. not any of its children.
Updated
The above solution only works when the View.el is anonymous.
So, the only solution I see can work in your concrete scenario is to manipulate the View.el directly by JavaScript in the initialize() like this:
initialize: function(){
this.$el.attr( "id", "my-id" );
this.$el.attr( "class", "myclass1 myclass2" );
},
Check the jsfiddle for three different scenarios manipulating the View.el attributes.

Categories