I have a form (on localhost) with 2 fields:
First Name (text box)
Last Name (text box)
Once the form is submitted, I need to use API - https://beta.test.com/api
The documentation says -
"POST /user will add the details to system and generates a user ID which would be returned."
After I receive user ID in response, I need to call another endpoint -
"POST /user/metadata will fetch the metadata for a previously added user."
I have to build this in backbonejs. What should be my approach? Do you have any tutorials which I can look at?
I did some code but it gave me - "Access-Control-Allow-Origin". I have checked on server and the API already has cross domain allowed for all.
Please suggest.
For a good example look at TODO app in backbone way.
I will also suggest you to read Backbone's documentation and view the source code.
It will documented so you can find all you need there, if no look into the source.
Simple implementation of your form could be achieved like this:
For interaction with API and data exchange via REST create User model:
var UserModel = Backbone.Model.extend({
urlRoot: 'your/api/path',
default: { // this will be setted when model attributes are empty
firstname: 'Default Name',
lastname: 'Default Lastname'
}
});
Form view which will render you form and will bind model's attributes to the form's elements:
var UserForm = Backbone.View.extend({
initialize: function() {
this.render();
},
el: '.form-container', // this will attach view to the DOM's element with 'form-container' class
template: _.template($('#user-form').html()),
events: {
'submit': 'onFormSubmitted',
// validation logic could be added here
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
},
onFormSubmitted: function(e) {
e.preventDefault(); // we don't need to submit the form
// get form elements here and setting on model
// saving model at the end
var firstName = this.$('input[name="firstname"]').val();
var lastName = this.$('input[name="lastName"]').val();
this.model.set({firstname: firstName, lastname: lastName});
this.model.save(); // this will make POST request to your API
}
});
And then initialize you view and pass User model.
var userForm = new UserForm({model: new UserModel()});
I have left the declaration of template for you.
There is a lot of staff for cross origin requests policy issues when using Backbone. Actually it's not the Backbone thing. Backbone uses $.ajax to interact with REST-full resources. So you just need to configure $.ajax. Look here.
Related
I have a marionette view that have a method to create a new model from a bootbox. Now i need to be able to edit the model from the bootbox, how can i I pass the current model data to the box?
This is some of my current code:
Module.Views.Chaptersx = Marionette.CompositeView.extend({
template: Module.Templates['documents/create/course/chapter/index'],
childView: Module.Views.ChapterItemx,
childViewContainer: "#chaptersCollection",
events: {
'click .chapters-create': 'create',
//'click #uploadFilesChapters': 'startUpload'
},
create: function (evt) {
console.log('create');
evt.preventDefault();
var me = this;
var box = bootbox.dialog({
show: false,
title: "Nueva Seccion",
message: Module.Templates['documents/create/course/chapter/chapterModal'],
buttons: {
success: {
label: "Guardar",
className: "btn-success",
callback: function () {
var chapterNo = $('#cn').val();
var chapterName = $('#chapterName').val();
var chapter = new Module.Models.Chapter({
chapterNo: chapterNo,
chapterName: chapterName,
});
me.collection.add(chapter);
}
}
}
});
box.on("show.bs.modal", function () {
console.log('numbers');
var number = (me.collection.size() + 1);
$('#cn').val(number);
});
box.modal('show');
},
TL;DR - use model's custom events or an event bus to pass the data.
You can reference this.model in the view, which is somewhat of a compromise (you're tying the view and the model).
You could pass the data via the event object's data property, but for that you're gonna have to extend some methods and get into backbone's nitty gritty.
Use a data- attribute on the element:
<div class="chapters-create" data-cats></div>
create: function (evt) {
var cats = $(evt.currentTarget).data('cats');
// ...
}
… which is considered bad habit by the way - you're still tying data to the DOM (or model to view, MVC speaking).
Well, I don't like either of the above, as they tend to have high coupling - I'd do it with custom events on a shared model resides at a higher level.
I don't know where the data comes from, but bottom line - shoot it in a custom event, or, better yet, use an event bus, like the one offered by marionette.js.
You need to create another view, call it EditView or something, render it, and provide the view.el as a message option to bootbox. However, the whole thing feels like a hack to me, and I think that it's better to implement a modalRegion and manage the modals yourself.
Say a user is going down a page and checking off and selecting items.
I have a Backbone model object, and each time the user selects something I want to update the object.
I have this in a separate JavaScript file that I source in my HTML:
var app = {};
var newLineup = null;
var team = document.getElementsByName('team');
app.Lineup = Backbone.Model.extend({
defaults: {
team: team,
completed: false
},
idAttribute: "ID",
initialize: function () {
console.log('Book has been intialized');
this.on("invalid", function (model, error) {
console.log("Houston, we have a problem: " + error)
});
},
constructor: function (attributes, options) {
console.log('document',document);
console.log('Book\'s constructor had been called');
Backbone.Model.apply(this, arguments);
},
validate: function (attr) {
if (attr.ID <= 0) {
return "Invalid value for ID supplied."
}
},
urlRoot: 'http://localhost:3000/api/lineups'
});
function createNewLineupInDatabase(){
newLineup = new app.Lineup({team: team, completed: false});
newLineup.save({}, {
success: function (model, respose, options) {
},
error: function (model, xhr, options) {
}
});
}
When the user first accesses the page, I will create a new lineup object by calling the above function. But how do I update that object as the user interacts with the page? Is there a better way to do this other than putting the Backbone model object at the top of my JavaScript file?
The Backbone pattern was designed to answer your question. As other respondents said, wire up a View, which takes your model as a parameter and lets you bind DOM events to the model.
That said, you don't have to use the rest of the framework. I guess you can use all the functionality Backbone provides models by handling the model yourself.
You need to worry about a couple of things.
Give you model a little encapsulation.
Set up a listener (or listeners) for your checkbox items.
Scope the model to your app
Backbone provides neat encapsulation for your model inside a View, but if you can live with it, just use your app variable which is within scope of the JavaScript file you posted.
When you're ready to instantiate your model, make it a property of app:
app.newLineup = new app.Lineup({team: team, completed: false});
It may look weird to have the instance and the constructor in the same object, but there aren't other options until you pull out the rest of Backbone.
The listener
So you have N number of checkboxes you care about. Say you give them a class, say, .options. Your listener will look like
$( ".options" ).change(function() {
if(this.checked) {
//Do stuff with your model
//You can access it from app.newLineup
} else {
}
});
Voila! Now your page is ready to talk to your model.
If there is frontend ui / any user interaction within your code it is extremely useful to create a backbone view which makes use of an events object where you can set up your event handler.
You can also link a view to a model to allow your model / your object to be updated without scope issues.
I'm developing a jQuery Backbone.js web application.
As it is in Adobe Flex, I have implemented 2 way data binding in my app for
input elements/widgets.
So, every input element/widget knows its corresponding model and model attribute name.
When the user hits tab or enter, the field value is automatically given to the model.
container.model.set(this.attrName, this.value, options); // command 1
In the other direction, when the model gets updated from the backend, the view of the
input element/widget should automatically get
updated:
container.model.bind("change:"+ this.attrName, this.updateView, this); // command 2
The problem is:
When the user hits enter and the model is automatically updated, also the "change:abc" is
triggered and this.updateView is called, not only when a new model comes from the
backend.
My solution until now was to pass an option "source: gui" when setting the model value when the user pressed enter (command 1), and to check for that in my updateView method. But I am not content with this solution anymore.
Does anybody have a better solution?
Thanks alot in advance
Wolfgang
Update:
When the option silent: true is passed, the validate method of the model is not called, so
that does not help. See Backbone.js source 0.9.2:
_validate: function(attrs, options) {
if (options.silent || !this.validate) return true;
From Backbone.js site:
A "change" event will be triggered, unless {silent: true} is passed as an option
options.silent = true;
container.model.set(this.attrName, this.value, options);
Update:
You added a new comment to your question, so I just complemented my answer to fix the new use case(validation flow) that you mentioned:
var ExtendedModel = Backbone.Model.extend({
uiChange : false,
uiSet: function (attributes, options, optional) {
this.uiChange = true;
this.set(attributes, options, optional);
this.uiChange = false;
}
});
var MyModel = ExtendedModel.extend({
});
var model = new MyModel();
model.on('change:name', function(){
console.log('this.uiChange: ', this.uiChange);
});
//simulates the server side set
model.set({name:'hello'});
//simulates the ui side set you must use it to set from UI
model.uiSet({name:'hello2'});
Two-way binding just means that:
When properties in the model get updated, so does the UI.
When UI elements get updated, the changes get propagated back to the
model.
Backbone doesn't have a "baked-in" implementation of 2 option (although you can certainly do it using event listeners)
In Backbone, we can easily achieve option 1 by binding a view's "render" method to its model's "change" event. To achieve option 2, you need to also add a change listener to the input element, and call model.set in the handler.
check (jsfiddle.net/sunnysm/Xm5eH/16)jsfiddle example with two-way binding set up in Backbone.
Backbone.ModelBinderplugin works great for providing Two-way data binding between your Backbone Views and Models. I wrote a blog post covering some essential features of this plugin Here is the direct link: http://niki4810.github.io/blog/2013/03/02/new-post/
I wanted to see what the bare bones code would be to have two-way binding with Backbone.js. This is what I came up with:
var TwoWayBoundView = Backbone.View.extend({
initialize: function(options) {
this.options = _.defaults(options || {}, this.options);
_.bindAll(this, "render");
this.model.on("change", this.render, this);
this.render();
},
events: {
"change input,textarea,select": "update"
},
// input updated
update: function(e) {
this.model.set(e.currentTarget.id, $(e.currentTarget).val());
},
// model updated...re-render
render: function(e) {
if (e){
var id = Object.keys(e.changed)[0];
$('#'+id).val(e.changed[id]);
}
else{
_.each(this.model.attributes, function(value, key){
$('#'+key).val(value);
});
}
}
});
And the usage:
var model = new Backbone.Model({ prop1: "uno 1", prop2: "dos 2", prop3: "3" });
var view = new TwoWayBoundView({
el: "#myContainer",
model: model
});
Here's a jsbin for it: http://jsbin.com/guvusal/edit?html,js,console,output
I've used libraries that do this, such as Epoxy.js (only 11k minified). And there are several others besides, which I would recommend long before using the proof of concept code above.
I would be interested in potential pitfalls and improvements that could be made with the TwoWayBoundView class above (but nothing beyond basic two-way binding please! i.e. I'm not looking for more features to add.)
demo fiddle (with problem) http://jsfiddle.net/mjmitche/UJ4HN/19/
I have a collection defined like this
var Friends = Backbone.Collection.extend({
model: Friend,
localStorage: new Backbone.LocalStorage("friends-list")
});
As far as I'm aware, that's all I need to do to get local storage to work (in addition to including it below backbone.js)
One thing I wasn't sure about, does the name "friends-list" have to correspond to a DOM element? I'm trying to save the "friends-list" so I called it that in local storage, however, localstorage doesn't seem to require passing a class or an id.
Here's a fiddle where you can see what I'm trying to do http://jsfiddle.net/mjmitche/UJ4HN/19/
On my local site, I'm adding a couple friends, refreshing the page, but the friends are not re-appearing.
Update
I've also done the following in my code on my local site
console.log(Backbone.LocalStorage);
and it's not throwing an error.
My attempt to debug
I tried this code (taken from another SO answer) in the window.AppView but nothing came up in the console.
this.collection.fetch({}, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
})
From the fine manual:
Quite simply a localStorage adapter for Backbone. It's a drop-in replacement for Backbone.Sync() to handle saving to a localStorage database.
This LocalStorage plugin is just a replacement for Backbone.Sync so you still have to save your models and fetch your collections.
Since you're not saving anything, you never put anything into your LocalStorage database. You need to save your models:
showPrompt: function() {
var friend_name = prompt("Who is your friend?");
var friend_model = new Friend({
name: friend_name
});
//Add a new friend model to our friend collection
this.collection.add(friend_model);
friend_model.save(); // <------------- This is sort of important.
},
You might want to use the success and error callbacks on that friend_model.save() too.
Since you're not fetching anything, you don't initialize your collection with whatever is in your LocalStorage database. You need to call fetch on your collection and you probably want to bind render to its "reset" event:
initialize: function() {
_.bindAll(this, 'render', 'showPrompt');
this.collection = new Friends();
this.collection.bind('add', this.render);
this.collection.bind('reset', this.render);
this.collection.fetch();
},
You'll also need to update your render to be able to render the whole collection:
render: function() {
var $list = this.$('#friends-list');
$list.empty();
this.collection.each(function(m) {
var newFriend = new FriendView({ model: m });
$list.append(newFriend.render().el);
});
$list.sortable();
return this;
}
You could make this better by moving the "add one model's view" logic to a separate method and bind that method to the collection's "add" event.
And a stripped down and fixed up version of your fiddle: http://jsfiddle.net/ambiguous/haE9K/
Say I have a View that displays a search box with a submit button.
When I click on the submit button how do i pass the value of the search box to another view ?
I tried:
In view 1, inside the submit callback : this.trigger('abc', $('#searchBox').val())
In view 2, in the initialize function: this.bind('abc', function(data){ console.log(data); })
but that does not seem to work: the custom event is fired but View 2 does not see it.
Here's a great article by Derick Bailley # LosTechies.com:
References, Routing, And The Event Aggregator: Coordinating Views In Backbone.js
This article discusses a simple solution using PubSub that is built in Backbone.JS. I agree with Derick when he mentions that views should be decoupled.
Unfortunately you can't bind this way - you will need to share a reference to view1 in view2:
var View2 = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'foo');
this.view1.bind('abc', this.foo);
},
foo: function(data) {
console.log(data);
}
});
This also means that at some point you need to set view1 on your instance of View2 so that you can bind against it.
If you don't want to pass the references around, simply bind the two views together in whatever container you are holding them in (i.e. another view or a controller):
var view1 = new View1();
var view2 = new View2();
view1.bind('abc', view2.foo);
I suggest using a PubSub framework in addition to backbone. MinPubSub is a popular choice. I use the jquery pubsub extension extracted from https://github.com/phiggins42/bloody-jquery-plugins.
Then, View 2 doesn't need a reference to View 1. To modify Andrew Hare's example, you would do:
var View2 = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'foo');
$.subscribe('abc', this.foo);
},
foo: function(data) {
console.log(data);
}
});
Then in View 1:
$.publish('abc', $('#searchBox').val());
Of course, with a pubsub system, you will probably want to use something better than "abc", perhaps instead choosing "searchBox:submit" as the topic.