How to post json to api method in backbone.js? - javascript

i have rest api based on django rest framework, that include next method of creation object, that takes the data in JSON-format on 'myapp/create_obj/' and if the data is correct object will created, otherwise it returns an error also in JSON-format.
def create_obj(request):
stream = StringIO(request.raw_post_data)
data = JSONParser().parse(stream)
serializer = ObjSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
else:
return JSONResponse(serializer.errors, status=400)
Also i tried to create a module on backbone.js, that post the input in form data to this method. Im very new to js, in particular to backbone and i bad understand how backbone works with server api. i have something like
App.module('Createobj', function(Mod, App, Backbone, Marionette, $, _) {
Mod.id = 'create-obj';
Mod.controllers = {};
Mod.Obj = Backbone.Model.extend({
defaults: {
real_ref : '',
share : ''
}
});
Mod.View = Marionette.ItemView.extend({
id: 'create-obj-page',
template: '#tpl-create-obj-page',
model: Mod.obj,
ui: {
'real_ref': 'input[name=real_ref]',
'share': 'input[name=share]',
'error': 'div.error'
},
hammerEvents: {
'tap button': 'submit:tap'
},
hammerOptions: {
tap: true
},
showError: function(message) {
this.ui.error
.text(message)
.show();
},
hideError: function() {
this.ui.error.hide();
},
});
Mod.Controller = SRClient.PageController.extend({
id: Mod.id + '.main',
ViewClass: Mod.View,
setup: function() {
this.listenTo(this.view, 'submit:tap', this.submit);
},
submit: function() {
var real_ref = this.view.ui.real_ref.val(),
share = this.view.ui.share.val();
if (!real_ref || !share) {
this.view.showError($t('create-obj.error_empty_fields'));
return;
}
App.vent.trigger('loading-screen:show', $t('app.please_wait'));
var obj = new Mod.obj({
real_ref : this.view.ui.real_ref.val(),
share : this.view.ui.share.val()
});
}});
Mod.addInitializer(function() {
Mod.Controllers = {
default: Mod.Controller
};
App.pageControllers[Mod.id] = Mod;
});
});
What i need to do, that data which i input in webform sends to 'myapp/create_obj' in json-format? Thanks!

Backbone expects a RESTful api so instead of being the endpoint an action like create_obj, REST works with Resources and with HTTP methods. In your case you could have a Model like this:
var Obj = Backbone.Model.extend({
defaults: {
real_ref : '',
share : ''
}
});
and a collection like this
var Objects = Backbone.Collection.extend({
url: 'myapp/obj',
model: Obj
});
the collection has a propetry url that specifies the server endpoint. So the operations will be
POST /myapp/obj/ for create a new item
GET /myapp/obj/:id/ if you want to retreive an specific item
GET /myapp/obj/ retreving the whole list
PUT /myapp/obj/:id/ update an item
DELETE /myapp/obj/:id/ delete an item
Tastypie is a good framework to create RESTful api with Django.

Related

Backbone js building a collection from the results returned from multiple URL's

I have a model that looks like this:
var BasicModel = Backbone.Model.extend({
defaults: {
a: '',
b: '',
c: '',
d: '',
e: ''
},
idAttribute: "f",
parse: function (data) {
return data;
},
initialize: function () {
console.log('Intialized');
},
constructor: function (attributes, options) {
Backbone.Model.apply(this, arguments);
}
});
Collections like this:
var BasicCollection = Backbone.Collection.extend({
model: BasicModel,
url: urlCode
});
var ACollection = BasicCollection.extend({
parse: function (data) {
return data.a.b.c.d;
}
});
var aCollection = new ACollection ();
And Views like this:
var BasicView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#basic-status-template').html()),
render: function () {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
var BasicsView = Backbone.View.extend({
initialize: function () {
this.render();
},
});
This is how the collection fetch looks (Which builds the views):
aCollection.fetch({
success: function () {
// View
var aView = BasicsView.extend({
el: '#foobar #table-body',
render: function () {
this.$el.html('');
aCollection.each(function (model) {
var x = new BasicView({
model: model
});
this.$el.append(x.render().el);
}.bind(this));
return this;
}
});
var app = new aView();
}
});
But now I face a problem when trying to add another piece of detail to the tables that the views will populate. One of the columns will require data that will come from a seperate url. But I still want it to be part of the same process.
Is there are way to form a collection from the result of two URL's. (i.e. a, b, d and e come from URL 1, and c comes from URL 2)?
This way all I would need to change was the template and it should all work the same. Instead of having to alter a load of other stuff as well.
Thanks.
You have few options:
Update the endpoint to send required data. This is the proper way to do it. Collection should Ideally have single endpoint
Send a seperate AJAX request to get data from one URL before fetching collection, then in collection's parse method add the data to the response fetched from collection's URL
Do something like:
$.when(collection.fetch(), collection.fetchExtraData())
.done(()=> { /* create view here */ });
fetchExtraData here is a custom function that sends extra request and updates collection properly with the data. This way both requests are sent simultaneously. You need to make sure parse doesn't reset the data from other endpoint.

Backbone view/template fails to load from REST

I have properly coded a simple REST api and several backbone models. My parent model is called Topic and child model called Questions.
I'm trying to call a get method on the REST api and display the received Topic object to the user in a presentable manner. I am receiving the json (can be seen in the network tab on Chrome), but it is not getting sent to the view correctly.
Model:
var Topic = Backbone.Model.extend({
urlRoot: ROOT + '/topic',
idAttribute: 'topicId',
initialize: function () {
this.questions = new Questions([], {parent: this});
},
toJSON: function () {
var json = Backbone.Model.prototype.toJSON.call(this);
json.questions = this.questions.toJSON();
return json;
}
});
var Topics = Backbone.Collection.extend({
model: Topic,
url: ROOT + 'topic',
parse: function (response) {
return response.results;
}
})
REST URL:
http://localhost/Project/index.php/rest/resource/topic/
Backbone View: This is where I think the error is...(console log below prints an empty object)
var TopicListView = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var topics = new Topics();
topics.fetch({
success: function (topics) {
console.log(topics);
var template = _.template($('#topic-list-template').html(), {topics: topics.models});
that.$el.html(template);
}
})
}
});
Using the above functions:
var topic = new Topic();
topic.fetch();
topicListView = new TopicListView();
var Router = Backbone.Router.extend({
routes: {
"": "home"
}
});
var router = new Router;
// render topic list for 'home'
router.on('route:home', function () {
topicListView.render();
});
Edit: Solution: Overriding the parse function in the collection proved to be the error. I wonder why...
The argument topics in your success handler is shadowing the variable topics.
The argument contains the parsed JSON response, not the Backbone Collection. You don't need that, so you can remove the argument.
The reference to topics will now be to your Collection, so topics.models will have the value you expect.
topics.fetch({
success: function () { // argument removed here so `topics` is no longer shadowed
var template = _.template($('#topic-list-template').html(), { topics: topics.models });
that.$el.html(template);
}
})

Ember Data belongsTo async relationship omitted from createRecord() save() serialization

Edit 11/16/14: Version Information
DEBUG: Ember : 1.7.0 ember-1.7.0.js:14463
DEBUG: Ember Data : 1.0.0-beta.10+canary.30d6bf849b ember-1.7.0.js:14463
DEBUG: Handlebars : 1.1.2 ember-1.7.0.js:14463
DEBUG: jQuery : 1.10.2
I'm beating my head against a wall trying to do something that I think should be fairly straightforward with ember and ember-data, but I haven't had any luck so far.
Essentially, I want to use server data to populate a <select> dropdown menu. When the form is submitted, a model should be created based on the data the user chooses to select. The model is then saved with ember data and forwarded to the server with the following format:
{
"File": {
"fileName":"the_name.txt",
"filePath":"/the/path",
"typeId": 13,
"versionId": 2
}
}
The problem is, the typeId and versionId are left out when the model relationship is defined as async like so:
App.File = DS.Model.extend({
type: DS.belongsTo('type', {async: true}),
version: DS.belongsTo('version', {async: true}),
fileName: DS.attr('string'),
filePath: DS.attr('string')
});
The part that is confusing me, and probably where my mistakes lie, is the controller:
App.FilesNewController = Ember.ObjectController.extend({
needs: ['files'],
uploadError: false,
// These properties will be given by the binding in the view to the
//<select> inputs.
selectedType: null,
selectedVersion: null,
files: Ember.computed.alias('controllers.files'),
actions: {
createFile: function() {
this.createFileHelper();
}
},
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path'
});
var gotDependencies = function(values) {
//////////////////////////////////////
// This only works when async: false
file.set('type', values[0])
.set('version', values[1]);
//////////////////////////////////////
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this);
Ember.RSVP.all([
selectedType,
selectedVersion
]).then(gotDependencies);
}
});
When async is set to false, ember handles createRecord().save() POST requests correctly.
When async is true, ember handles GET requests perfectly with multiple requests, but does NOT add the belongsTo relationships to the file JSON during createRecord().save(). Only the basic properties are serialized:
{"File":{"fileName":"the_name.txt","filePath":"/the/path"}}
I realize this question has been asked before but I have not found a satisfactory answer thus far and I have not found anything that suits my needs. So, how do I get the belongsTo relationship to serialize properly?
Just to be sure that everything is here, I will add the custom serialization I have so far:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeIntoHash: function(data, type, record, options) {
var root = Ember.String.capitalize(type.typeKey);
data[root] = this.serialize(record, options);
},
keyForRelationship: function(key, type){
if (type === 'belongsTo') {
key += "Id";
}
if (type === 'hasMany') {
key += "Ids";
}
return key;
}
});
App.FileSerializer = App.ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
type: { serialize: 'id' },
version: { serialize: 'id' }
}
});
And a select:
{{ view Ember.Select
contentBinding="controller.files.versions"
optionValuePath="content"
optionLabelPath="content.versionStr"
valueBinding="controller.selectedVersion"
id="selectVersion"
classNames="form-control"
prompt="-- Select Version --"}}
If necessary I will append the other routes and controllers (FilesRoute, FilesController, VersionsRoute, TypesRoute)
EDIT 11/16/14
I have a working solution (hack?) that I found based on information in two relevant threads:
1) How should async belongsTo relationships be serialized?
2) Does async belongsTo support related model assignment?
Essentially, all I had to do was move the Ember.RSVP.all() to after a get() on the properties:
createFileHelper: function() {
var selectedType = this.get('selectedType');
var selectedVersion = this.get('selectedVersion');
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
file.set('type', values[0])
.set('version', values[1]);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
file.save().then(onSuccess, onFail);
}.bind(this));
}
So I needed to get() the properties that were belongsTo relationships before I save the model. I don't know is whether this is a bug or not. Maybe someone with more knowledge about emberjs can help shed some light on that.
See the question for more details, but the generic answer that I worked for me when saving a model with a belongsTo relationship (and you specifically need that relationship to be serialized) is to call .get() on the properties and then save() them in then().
It boils down to this:
var file = this.store.createRecord('file', {
fileName: 'the_name.txt',
filePath: '/the/path',
type: null,
version: null
});
// belongsTo set() here
file.set('type', selectedType)
.set('version', selectedVersion);
Ember.RSVP.all([
file.get('type'),
file.get('version')
]).then(function(values) {
var onSuccess = function() {
this.transitionToRoute('files');
}.bind(this);
var onFail = function() {
alert("failure");
this.set('uploadError', true);
}.bind(this);
// Save inside then() after I call get() on promises
file.save().then(onSuccess, onFail);
}.bind(this));

Fetch BackboneJS model from id (I think)

I'm responsible to make a cache-layer, that uses local storage, that is between the server and the client to lower the bandwidth and server load.
I have two models, 'model' and 'modelContent'. These are connected to each other with an ID.
model is populated as it should be with all the parameters that are in the model.
modelContent is not populated when its content is fully fetched from the server.
I want to wait until the modelContent has it's attribute 'body' fully populated and then add it to the cache. Right now modelContent's 'body'-attribute is just an empty string (which is the default value).
I have created a base-model that model and modelContent inherits from that contains an override for sync for the read-method. The reason for this is that I read http://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps and that is the way I want it to work but with two models that belong together.
I'm very new at BackboneJS and Grails (which the project uses as well) so I'm hoping someone can point me in the right direction.
My base-model:
var basic_model = Backbone.Model.extend({
sync: function(method, model, options) {
if ( method === 'read' ) {
uid = this.get("UID");
if ( uid ) {
var success = options.success;
options.success = function(resp, status, xhr) {
console.log(resp);
}
} else {
Backbone.sync(method, model, options);
}
} else {
Backbone.sync(method, model, options);
}
}
modelContent:
var MailContentItem = basic_model.extend({
urlRoot: project_webroot +'modelContent',
idAttribute: "UID",
defaults: function() {
return _.extend({}, basic_model.prototype.defaults, {
isHTML: false,
body: ""
});
}
});

Backbone: how to load a Collection in the view (inc require)

I am trying to load data from an API into a view. However the data doesn't turn up in my view.
I tried getting the collection information in de router, as well as in the model.
However the date won't even console.log the data. Let alone that I can load the data into the view.
I am using require to load the JavaScript files. Can you have a look and see what I am doing wrong here?
I do see this console.log:
console.log("People Collection is initialized");
And I can also see the page loaded and the json. But not the console.log of the data in the url function... In fact I get this error in the console:
Error: A "url" property or function must be specified
In the Backbone Router:
var OF = OF || {};
OF.AdminRouter = Backbone.Router.extend({
routes: {
"users": "goToUsers",
"users/*other": "goToUsers"
},
goToUsers: function() {
require(['./models/users', './views/users_view', './views/menu_view', './collections/user_collection'], function(UsersMdl, UsersView, MenuView, UsersCollection) {
OF.usersView = new OF.UsersView;
OF.usersView.render();
});
}
});
The Collection:
var OF = OF || {};
OF.UsersCollection = Backbone.Collection.extend({
initialize: function() {
console.log("People Collection is initialized");
},
url: function() {
var that = this;
var sendObj = {
"admin": OF.login.attributes.admin,
"session": OF.login.attributes.session
};
$.ajax({
url: 'php/api/users/',
type: "POST",
dataType: "json",
data: sendObj,
success: function(data) {
console.log(data);
},
error: function(data) {
console.log("ERR: " + data);
}
});
},
model: OF.UsersMdl
});
The Model:
var OF = OF || {};
OF.UsersMdl = Backbone.Model.extend({
default: {
username: '',
homefoldersize: '',
email: ''
},
initialize: function(){
//on change functions can be done here
OF.usersCollection = new OF.UsersCollection();
OF.usersCollection.fetch();
},
result: {
success: false,
message: ''
},
validate: function(att) {
}
});
The View:
var OF = OF || {};
OF.UsersView = Backbone.View.extend({
el: '#content',
remove: function() {
this.$el.empty();
this.stopListening();
return this;
},
initialize: function() {
//set the new address variable.
OF.usersMdl = OF.usersMdl || new OF.UsersMdl();
},
render: function() {
/*
//first check if you are allowed to see this page
if (!OF.login || !OF.login.isValid()) {
OF.router.navigate('login', {trigger: true});
return;
}
*/
//save this in that
var that = this;
//when importing of login page (and fill it with info) is done
$.when(OF.template.get('users-usersField', function(data) {
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
var compiled = template(OF.usersMdl.attributes);
//now place the page
that.$el.html(compiled);
//then start the menu
})).then(function(){
setTimeout(function(){
OF.menuView = new OF.MenuView;
OF.menuView.render();
}, 100);
});
$('#logout').show();
}
});
Thanks.
It seems to call the initialize of the collection twice and then continues to call the json function.
In your model's initialization you have
OF.usersCollection = new OF.UsersCollection();
OF.usersCollection.fetch();
But when you fetch your collection, it's going to initialize models for every result it gets back ... which will then trigger fresh collection fetches.
You don't need to create collections for your models inside your models, especially if the model is being created by the collection. Whenever you add a model to a collection (including when the collection creates the model after a fetch) the collection will associate itself with the model.
The general order of things should be:
You define a url function on your collection which returns the URL where you can get the (raw JSON) models of that collection.
You instantiate that collection, and then call fetch on the instance
The collection makes an AJAX call (which you can affect by overriding fetch or sync) and gets back the raw JSON for all of the models.
The collection instantiates new models for each result it gets back; those models are automatically added to the collection, and their .collection is automatically set to the collection.
Once OF.usersCollection.fetch().done(function() {... you can have your views start doing things, as your collection should now be all set.

Categories