I am working with backbone at the moment, I run a fetch on a new model, and get a response from the server, the model I am fetching should have other models and collections within it, the JSON returned supports this and it looks something like this,
{
"id" : 230,
"name" : "A project name",
"briefversion":{
"id":199,
"project_id":230,
"version_number":1,
"name":"Version 1",
"created_at":"2015-05-14 10:22:29",
"updated_at":"2015-05-14 10:22:29",
"briefversionsections":[{
"id":947,
"briefversion_id":199,
"position":1,
"name":"Overview",
"content":"<p>A general description of the project and some background information, also worth including some context, where is the work going to be used? Billboards, online, showroom etc</p><div><img src="//www.sketchup.com/images/case_study/architecture/robertson_walsh_3.jpg"/></div>",
"created_at":"2015-05-14 10:22:29",
"updated_at":"2015-05-14 10:22:29",
"briefsectonattachments":{}
}, {
"id":948,
"briefversion_id":199,
"position":2,
"name":"Scope of work",
"content":"<p>A list of the deliverables, e.g.</p><ul><li>An exterior view</li><li>An interior view</li><li>An animation</li><li>A website</li></ul>",
"created_at":"2015-05-14 10:22:29",
"updated_at":"2015-05-14 10:22:29",
"briefsectonattachments":{}
},{
"id":949,
"briefversion_id":199,
"position":3,
"name":"Target market",
"content":"<p>ASCribe who the work is to appeal to, what are the demographics and end user types.</p>",
"created_at":"2015-05-14 10:22:29",
"updated_at":"2015-05-14 10:22:29",
"briefsectonattachments":{
}
}]
},
"organisations":{
"id":55,
"name":"Jaguar",
"uri_hash":"jaguar",
"slug":"S336e056",
"information":"",
"type":"organisation",
"currency":"USD",
"notifications":"0",
"add_all":"0",
"created_at":"-0001-11-30 00:00:00",
"updated_at":"2015-05-20 09:16:21",
"users":[
{
"id":111,
"email":"xxxxxxxx#gmail.com",
"first_name":"Matty",
"last_name":"Brook",
"display_name":"mattybrook",
"initials":"MB",
"remember_me":null,
"active":"1",
"invite_code":null,
"forgotten_code":null,
"cost_visible":0,
"login_type":"normal",
"api_token":null,
"created_at":"2015-03-16 15:49:58",
"updated_at":"2015-05-15 13:12:45",
"deleted_at":null,
"pivot":{
"organisation_id":55,
"user_id":111,
"is_admin":"0"
}
}
}
So after the fetch, how can I make sure that briefversion becomes a model and within that briefversionsections within that becomes a collection, similarly how do I make sure that the users attribute of the organisation object also become a collection?
You will need to override parse to handle getting the JSON from your server in the right format. Once that's done you can then instantiate your collections for some of the properties in the initialize method.
For example
initialize: function () {
this.briefversionsections = new Backbone.Collection(this.briefversionsections);
this.users = new Backbone.Collection(this.users);
},
parse: function (response, options) {
var myModel = response.briefversion;
myModel.users= response.organisations.users
return myModel;
}
Related
I am using MongoDB in my Sails setup to store my collections.
I am making a POST request through an API, whose request body looks like this :
**THE REQUEST BODY**
{
"name":"Martin",
"phone":"5447874787",
"comment":"This is a comment",
"campaign":{
"campaign_id":123454587,
"name":"Digital Marketing",
"category":"Marketing",
"sub_category":"Digital",
"product_name":"no product",
"product_id":5417
}
}
This record is to be stored in my mongodb collection named "leads". My "Leads" model looks like this:
module.exports = {
schema: true,
attributes:{
name:{
required:true,
type:"string",
unique: true
},
phone:{
type:"string"
},
comment:{
type:"string"
},
campaign:{
model: 'lead_campaign_detail',
// via: "lead_model"
}
}
};
The associated "Lead_campaign_detail" model is as following
module.exports = {
attributes:{
campaign_id:{
required:true,
type:'integer'
},
name:{
type:"string"
},
category:{
type:"string"
},
sub_category:{
type:"string"
},
product_name:{
type:"string"
},
product_id:{
type:"integer"
}
}
};
And My controller handler is:
create_lead_api:function(req, res){
Leads.create(params, function(err, resp){
if(err) return res.negotiate(err);
else return res.json(resp);
})
},
WHAT I HAVE TRIED
1> I tried using .native(). It creates a new record BUT it does not care about the attributes in the model "Leads" or the other one. It just saves as the request is.
2> Tried creating using the .create(). It does not stores the nested values of index "campaign" in the request: the stored value looks like this:
{ **IN MY DB it looks like**
"name": "martin",
"phone": "5447874787",
"comment": "This is a comment",
"campaign": "579083f049cb6ad522a6dd3c",
"id": "579083f049cb6ad522a6dd3d"
}
I WANT TO
1> Store the record in the same format as the requested am sending.
2> Make use of the waterline ORM function .create() to achieve the same if possible.
3> .native() is also in option if my Model-attributes:{} can be used to bring in between before storing the record. I want to store only values defined in attributes:{ }
Any help is appreciated, Google is failing me.
Thanks
If you want the document in 'leads' collection to look like your request body, then you should use:
campaign: {
type: "json"
}
in your 'Leads' model, rather than linking to 'lead_campaign_detail' collection. You can then do away with the 'lead_campaign_detail' model.
By saying model: 'lead_campaign_detail' you are essentially linking the "Leads" document to "lead_campaign_detail", See Waterline One-way association and populate() for more details.
I am trying to render a view for a collection using Backbone.View. But I cannot render the comment view to show the individual comments as a list inside the comments view. Only comments form is rendered. when visited the url of the collection from the address bar the below array is returned as I have written it on the server side code with express.
What can be the issue here I cannot manage to fix? It seems very natural to achieve it with this code, but it is certain that I am missing something. General issue is I am stuck at such detailed points although I can learn a say mvc framework, Backbone, Node, express etc.
CommentsView:
var CommentsView = Backbone.View.extend({
initialize: function (options) {
this.post = options.post;
this.collection = new Comments({post: this.post});//injecting collection from this views options, was injected to this view form router.
this.collection.on('add', this.addComments, this);
},
addComments: function (comment) {
this.$el.append(new CommentView({ model: comment }).render().el);
},
render: function () {
this.$el.append("<h2> Comments </h2>");
this.$el.append(new CommentFormView({post: this.post}).render().el);
return this;
}
});
This is the array returned when the url of collection is visited form the address bar:
[
{
"_id": "547e36df0ca19b2c1a6af910",
"postId": "547d00e30ca19b2c1a6af90b",
"name": "comment one",
"date": "Mon 21 Oct 2014",
"text": "gibberjabber"
}
]
Router method when the route to the comments of the related post is routed to:
comments: function(_id) {
var csv = new CommentsView({ post: this.posts.findWhere( {_id: _id} ) });
this.main.html(csv.render().el);
}
I think it could have something to do with your constructor function for this.collection. When creating a Collection, you should pass in the array as the first parameter and object literal with the options as the second (if you didn't define it when creating the collection class. What I'm thinking is that the "add" event on the collection isn't getting fired so the comments are not being rendered.
var Comments = Backbone.Collection.extend({
model: Post
});
this.collection = new Comments(posts)
I'm guessing that posts is just an array of models
Below is the current code structure I have in place for a collection that I have manually constructed. I have a json file on my server which I am now trying to load in and basically remove the manual one and construct a collection based on that data. Was wondering what would I possibly need to change below to my code to help accommodate this.
var Game = Backbone.Model.extend({
defaults: {
name: 'John Doe',
age: 30,
occupation: 'worker'
}
});
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json',
parse: function(response) {
return response;
}
});
var GamesView = Backbone.View.extend({
tagName: 'ul',
render: function() {
//filter through all items in a collection
this.collection.each(function(game){
var gameView = new GameView({model: game});
this.$el.append(gameView.render().el);
}, this)
return this;
}
});
var GameView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#gameTemplate').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var gameCollection = new GameCollection([
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
},
{
name: 'John Doe',
age: 30,
occupation: 'worker'
}
]);
var gamesView = new GamesView({collection: gameCollection});
$(document.body).append(gamesView.render().el);
This is one of the many things to love about Backbone. I don't know what you are using for your backend, but you state that you have a json file on your server, hopefully a json file full of the models that should be in your collection. And now here is the magic code (drumroll please..):
var GameCollection = Backbone.Collection.extend({
model: Game,
url: 'path/to/json/on/external/server',
});
var gameCollection = new GameCollection();
gameCollection.fetch();
Not much to it, right? Of course there are several options you can add or change to a fetch, so check out the docs here: http://backbonejs.org/#Collection-fetch. Backbone uses jQuery.ajax() be default, so check out the docs here to see all of the options: http://api.jquery.com/jQuery.ajax/
You shouldn't need the custom parse in your collection unless your models on the server don't match your backbone models.
Things to know:
fetch is asynchronous. It takes time to talk to the server, and the rest of your javascript will move on and complete. You will probably need to at least add a callback function to the success option, which will be called when fetch is finished, and it is good to add something to error as well, in case something goes wrong. You can add data as a query string so that your backend can use it using the data option, the data has to be an object. Here is an example:
gameCollection.fetch({
data: {collection_id: 25},
success: function(){
renderCollection(); // some callback to do stuff with the collection you made
},
error: function(){
alert("Oh noes! Something went wrong!")
}
});
fetch should receive data as JSON, so your url should either exclusive return JSON or be set up to detect an AJAX request and respond to it with JSON.
Firstly you need to fetch it from server as RustyToms said. And the other consideration is how to force the collection view to render itself again once data collected from server, as muistooshort commented.
If you manipulating fetch or sync you'll need to do it multiple times when there are more than one collection in app.
Doing such is native with Marionette, but in plain Backbone you can mimic the method of Marionette's CollectionView and do such:
//For the collection view
var GamesView = Backbone.View.extend({
initialize: function({
this.listenTo(this.collection, 'reset', this.render, this);
});
// Others
});
Then, when collection data fetched from server, the collection will trigger a reset event, the collection view noticed this event and render itself again.
For more than one collections, you can extract the code into a parent object in app and inherit from that.
var App.CollectionView = Backbone.View.extent({
initialize: //code as above
});
var GamesView = App.CollectionView.extend({
//Your code without initialize
});
I know this is a bit old at this point, but wanted to answer for anyone else stuck on this.
The code seems to come from the tutorial found here: http://codebeerstartups.com/2012/12/a-complete-guide-for-learning-backbone-js/
I too re-purposed the demo app found in that tutorial and had trouble rendering using external data.
The first thing is that the data itself needs to be converted to valid JSON or else you'll get a .parse() error.
SyntaxError: JSON.parse: expected property name or '}' at line 3 column 9 of the JSON data
or
error: SyntaxError: Unexpected token n
In your data source file, object properties need to be surrounded by quotes. It should look something like this:
[
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
},
{
"name": "John Doe",
"age": 30,
"occupation": "worker"
}
]
Secondly, once it's clear the external data is loading, we need to get it to render. I solved this (perhaps ungracefully) by moving the render() command into the success function of your gameCollection.fetch().
gameCollection.fetch({
success: function(collection, response, options) {
console.log('Success!! Yay!!');
$(document.body).append(gamesView.render().el);
},
error: function(collection, response, options) {
console.log('Oh, no!');
// Display some errors that might be useful
console.error('gameCollection.fetch error: ', options.errorThrown);
}
});
There are certainly better ways to accomplish this, but this method directly converts the code learned in the tutorial into something that works with external data.
I'm currently trying to put something together with ember + emberdata + router + asp.net web api. Most of it seem to work, however I stuck in an error message I get when ember-data tries to findAll through the adapter for my models.
In my backend I have a model like this (C#):
public class Genre {
[Key]
public int Id { get; set; }
[Required]
[StringLength(50, MinimumLength=3)]
public string Name { get; set; }
}
Which in my app I represent it like this using ember-data:
App.Genre = DS.Model.extend({
id: DS.attr("number"),
name: DS.attr("string")
}).reopenClass({
url: 'api/genre'
});
I have also a Store defined in my App using the RESTAdapter like so:
App.store = DS.Store.create({
revision: 4,
adapter: DS.RESTAdapter.create({
bulkCommit: false
})
});
And the store is used in my controller as below:
App.GenreController = Ember.ArrayController.extend({
content: App.store.findAll(App.Genre),
selectedGenre: null
});
The router is defined as
App.router = Em.Router.create({
enableLogging: true,
location: 'hash',
root: Ember.Route.extend({
//...
genre: Em.Route.extend({
route: '/genre',
index: Ember.Route.extend({
connectOutlets: function (router, context) {
router.get('applicationController').connectOutlet('genre');
}
})
}),
//...
})
})
When I run my application, I get the following message for every object that has this same structure:
Uncaught Error: assertion failed: Your server returned a hash with the
key 0 but you have no mappings
For reference, here's the json the service is returning:
[
{
"id": 1,
"name": "Action"
},
{
"id": 2,
"name": "Drama"
},
{
"id": 3,
"name": "Comedy"
},
{
"id": 4,
"name": "Romance"
}
]
I cannot tell exactly what the problem is and since the assertion is mentioning that I need mapping, I'd like to know:
What this mapping is and how to use it.
Since the returned json is an array, should I be using a different type of controller in my app ,or is there anything I should know about when working with this type of json in ember-data? or should I change the JsonFormatter options in the server?
Any help is welcome.
I can definitely add more information if you feel this isn't enough to understand the problem.
EDIT: I've changed a few things in my backend and now my findAll() equivalent action in the server serializes the the output as the following json:
{
"genres": [
{ "id": 1, "name": "Action" },
{ "id": 2, "name": "Drama" },
{ "id": 3, "name": "Comedy" },
{ "id": 4, "name": "Romance" }
]
}
But I still can't get it to populate my models in the client and my error message has changed to this:
Uncaught Error: assertion failed: Your server returned a hash with the
key genres but you have no mappings
Not sure what else I might be doing wrong.
The method that throws this exception is sideload and checks for the mappings like this:
sideload: function (store, type, json, root) {
var sideloadedType, mappings, loaded = {};
loaded[root] = true;
for (var prop in json) {
if (!json.hasOwnProperty(prop)) { continue; }
if (prop === root) { continue; }
sideloadedType = type.typeForAssociation(prop);
if (!sideloadedType) {
mappings = get(this, 'mappings');
Ember.assert("Your server returned a hash with the key " + prop + " but you have no mappings", !!mappings);
//...
This call sideloadedType = type.typeForAssociation(prop); returns undefined and then I get the error message. The method typeForAssociation() checks for the for 'associationsByName' key which returns an empty Ember.Map.
Still no solution for this at the moment.
By the way...
My action is now like this:
// GET api/genres
public object GetGenres() {
return new { genres = context.Genres.AsQueryable() };
}
// GET api/genres
//[Queryable]
//public IQueryable<Genre> GetGenres()
//{
// return context.Genres.AsQueryable();
//}
I had to remove the original implementation which gets serialized by json.NET as I could not find config options to produce a json output as Ember-Data expects ( as in {resource_name : [json, json,...]}). Side effect of this is that I've lost built-in OData support, but I'd like to keep it. Does anyone know how could I configure it to produce different json for a collection?
The mapping can be defined in the DS.RESTAdapter. I think you could try to define something like this:
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.create({
bulkCommit: true,
mappings: {
genres: App.Genre
},
// you can also define plurals, if there is a unregular plural
// usually, RESTAdapter simply add a 's' for plurals.
// for example at work we have to define something like this
plurals: {
business_process: 'business_processes'
//else it tries to fetch business_processs
}
}),
revision: 4
});
Hope this resolves your problem.
Update:
At this time, this is not well documented, I don't remember if we found it by ourself reading the code, or perhaps Tom Dale pointed on it.
Anyway, here is the point for plurals
For the mappings, I think we were driven by the same error as you, and either we tried, either Tom teached us about this.
The RESTAdapter expects the returned JSON to be of the form:
{
"genres": [{
"id": 1,
"name": "action"
},{
"id": 2,
"name": "Drama"
}]
}
The tests are a good source of documentation, see https://github.com/emberjs/data/blob/master/packages/ember-data/tests/unit/rest_adapter_test.js#L315-329
I'm using Ember Data rev. 11 and it seems that the plurals config in DS.RESTAdapter.create never works. I looked into the codes and found a solution as following:
App.Adapter = DS.RESTAdapter.extend({
bulkCommit: false
})
App.Adapter.configure('plurals', {
series: 'series'
})
I have a backbone setup for Workflows. There are multiple workflows/models visible on the page at any given time. If a workflow is not started yet (i.e. not in the database), it is displayed as a New model. Having trouble with passing the fetched model to the view. I'm only passing the attributes rather than the collection it seems? Anyone have any ideas?
When I fetch all workflows from the database, it returns a JSON array of workflows progress.
[{
"ref_id": 41959,
"parent_ref_id": 35,
"fk_workflow_id": 2,
"stage": 1,
"status_max": 4,
"updated": "2012-05-14 17:30:46"
}, {
"ref_id": 41960,
"parent_ref_id": 35,
"fk_workflow_id": 3,
"stage": 3,
"status_max": 4,
"updated": "2012-05-14 12:30:48"
}]
When the page is loaded, all workflows are created as "new models", because we don't know if they've been started yet. Once the fetch has returned the models that have been started, I have to pass the appropriate model to the view. I'm having trouble figuring out how to pass the model properly (i.e. I can pass the attributes, but it seems after that, I don't have access to the collection.create functions etc?)
var self = this;
this.workflows = new Workflow.WorkflowCollection();
this.workflows.fetch({
data:{
ref_id: REF_ID
},
success: function(model, response) {
for(var i = 0, ln = model.models.length; i< ln;i++) {
self.switchWorkflow(model.models[i].get("fk_workflow_id"), new Workflow(model.models[i].attributes));
}
}
});
This is the only way I can figure backbone knows how many models it needs to setup (short of initiating each of the workflows for each customer - ~1million rows)
setupWorkflowSpaces: function(model) {
for(var i =0; i<Workflow.FileWorkflows.length;i++) {
this.switchWorkflow(Workflow.FileWorkflows[i], false);
}
},
switchWorkflow: function(workflow_id, model) {
if(!model)
model = new Workflow;
switch(workflow_id) {
case 1:
this.applicationProcessView(model);
break;
case 2:
this.kitView(model);
break;
case 3:
this.posView(model);
break;
default:
alert('wut');
break;
}
},
kitView: function(model) {
console.log(model);
model.set("fk_workflow_id", 2);
if(this.kit) this.kit.close();
this.kit = new Workflow.PostageView({
model: model
});
},
Try defining the model property in your Backbone Collection:
var Workflow.WorkflowCollection = Backbone.Collection.extend({
model: Workflow
});
If defined, the raw attributes coming back from your server will be converted into a Workflow model.
Then you won't have to iterate through the collection that is returned via the success callback in fetch. All the added models will be in your collection and you can use the .create() method to continue adding more.
After fetch you can iterate through the updated collection using WorflowCollection.each. In the loop you can run your switch statement to assign the each model to an appropriate view.
I hope this helps.