I'm using the default RESTAdapter with the ActiveModelAdapter, and I want to include a JSON object in a particular model.
eg:
App.Game = DS.Model.extend(
name: attr('string')
options: attr('raw') # This should be a JSON object
)
After reading ember-data/TRANSITION.md.
I've used the same transformer from the example:
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
When I've tried to create an Game instance model and save it, the options attribute in the POST data was "null"(string type).
App.GamesController = Ember.ObjectController.extend(
actions:
add_new: ->
game = this.get('model')
game.set('options', {max_time: 15, max_rounds: 5})
game.save()
)
What am I missing here?
Probably you need to register your transform:
App = Ember.Application.create();
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
App.initializer({
name: "raw-transform",
initialize: function(container, application) {
application.register('transform:raw', App.RawTransform);
}
});
I hope it helps
Related
I want to pass a param by the router
I have been trying :
Router.route('/someURL/:id', {
name: 'someTemplate',
data: function() {
var myData = someCollection.findOne({_id:this.params.id});
myData.someParam = true;
return myData;
}
});
Router.route('/anotherURL', {
name: 'someTemplate',
data: function() {
return {someParam:false};
}
});
but it doesn't work
This is my error:
Error: Handler with name 'someTemplate' already exists.
How can i solve it.?
Note: I need this "someParam"
The name is a name for the route not the template you want to use. Route names are a unique identifier per route much like the url and can be used to call the route without using the full url. You want something like:
Router.route('/someURL/:id', {
name: 'someName',
template: 'someTemplate',
data: function() {
var myData = someCollection.findOne({_id:this.params.id});
myData.someParam = true;
return myData;
}
});
Router.route('/anotherURL', {
name: 'someOtherName',
template: 'someTemplate',
data: function() {
return {someParam:false};
}
});
I'm having a problem where my backbone model isn't parsing something correctly. Here is the listing.js:
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings/",
images: function() {
this._images = this._images || new SpendYourSavings.Collections.Images([], { listing: this });
return this._images;
},
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], { listing: this });
return this._reviews;
},
shop: function() {
this._shop = this._shop || new SpendYourSavings.Models.Shop([], { listing: this });
return this._shop;
},
parse: function(data) {
if(data.images) {
this.images().set(data.images, { parse: true });
delete data.images;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.shop) {
this.shop().set(data.shop, { parse: true });
delete data.shop;
}
return data;
}
});
Images and reviews work, but shop doesn't quite work. It sets the attributes of shop correctly, but it doesn't set the image properly.
Here is the shop.js:
SpendYourSavings.Models.Shop = Backbone.Model.extend({
urlRoot: "/api/shops",
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], {});
return this._reviews;
},
listings: function() {
this._listings = this._listings || new SpendYourSavings.Collections.Listings([], {});
return this._listings;
},
user: function() {
this._user = this._user || new SpendYourSavings.Models.User([], {});
return this._user;
},
image: function() {
this._image = this._image || new SpendYourSavings.Models.Image([], {});
return this._image
},
parse: function(data) {
console.log("shop parse data: " + data);
debugger
if(data.listings) {
this.listings().set(data.listings, { parse: true });
delete data.listings;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.user) {
this.user().set(data.user, { parse: true });
delete data.user;
}
if(data.image) {
debugger
this.image().set(data.image, { parse: true });
delete data.image;
}
return data
}
});
The parse function in the shop.js never even when I receive a shop in the listing.js parse function! shop.image() doesn't get set to an image model properly, so I have to call something wonky like shop.get('image').url to get the url.
Presumably, the reason you're memoizing the image model in the shop is to maintain listeners and keep a single instance of that model around.
Collection#set takes a parse option that tells it to call parse on all the models that were set on the collection. Model#set is the method called immediately after calling parse using the attributes returned from parse.
In this case, we want to call #set on the associated shop model using the parsed attributes. So first lets call parse. It should look something like this:
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings",
images: function() {
this._images = this._images || new SpendYourSavings.Collections.Images([], { listing: this });
return this._images;
},
reviews: function() {
this._reviews = this._reviews || new SpendYourSavings.Collections.Reviews([], { listing: this });
return this._reviews;
},
shop: function() {
// Notice the first argument is an object when initializing models.
this._shop = this._shop || new SpendYourSavings.Models.Shop({}, { listing: this });
return this._shop;
},
parse: function(data) {
if(data.images) {
this.images().set(data.images, { parse: true });
delete data.images;
}
if(data.reviews) {
this.reviews().set(data.reviews, { parse: true });
delete data.reviews;
}
if(data.shop) {
var shopParams = this.shop().parse(data.shop);
this.shop().set(shopParams);
delete data.shop;
}
return data;
}
}
});
Your issue is that parse: true on set only really applies to collections.
These lines
this.images().set(data.images, { parse: true });
this.reviews().set(data.reviews, { parse: true });
work, because you are saying "add whole new models from this JSON".
This line
this.image().set(data.image, { parse: true });
however, is trying to say, parse these params, and set values, but that is weird on a model. Should it literally only parse the attributes that were passed in? Should it merge the attributes that the model already has? What if there were dependencies between the things already in the model and the things being parsed?
Instead, you might try restructuring your top-level parsing, e.g
SpendYourSavings.Models.Listing = Backbone.Model.extend({
urlRoot: "api/listings/",
images: function() {
return this.get('images');
},
reviews: function() {
return this.get('reviews');
},
shop: function() {
return this.get('shop');
},
parse: function(data) {
if (data.images){
data.images = new SpendYourSavings.Collections.Images(data.images, { listing: this, parse: true});
}
if (data.reviews){
data.reviews = new SpendYourSavings.Collections.Reviews(data.reviews, { listing: this, parse: true});
}
if (data.shop){
data.shop = new SpendYourSavings.Models.Shop(data.shop, { listing: this, parse: true});
}
return data;
}
});
I have a backbone collection:
var user = new Backbone.Collection.extend({
url: '/user',
parse: function (response) {
return response.lunch;
return response.dinner;
}
});
which returns a json like this:
[
{
lunch: [{
appetizer : 'bagel',
maincourse: 'steak',
desert: 'sweets'
}]
},
{
dinner: [{
appetizer : 'chips',
main: 'rice',
desert: 'sweets'
}]
}
]
I want to combine both response.lunch and response.dinner and have a common collection: I tried:
parse: function (response) {
var collection1 = response.lunch;
var collection2 = response.dinner;
return collection1.add(collection2.toJSON(), {silent : true});
}
But it doesnot work. Also how do i do a each function to override all main with maincourse? I tried:
this.collection.each(function(model) {
var a = model.get("main");
model.set({a: "maincourse"});
}
Any help would be appreciated. Thanks!
I'm guessing that you want to merge lunch and dinner so that your collection ends up with { appetizer : 'bagel', ... } and { appetizer : 'chips', ... } inside it. If so, then simply concat the two arrays together:
parse: function(response) {
return response.lunch.concat(response.dinner);
}
If you want to rename all the main attributes to maincourse then you'd want to use get to pull out the mains, unset to remove them, and then set to put them back in with the new name:
var maincourse = model.get('main');
model.unset('main', { silent: true });
model.set('maincourse', maincourse, { silent: true });
or just edit attributes directly:
model.attributes.maincourse = model.attributes.main;
delete model.attributes.main;
or better, just rename the attribute in your parse method.
I am currently trying to render out this json object in a ul. I'd like to be able to cycle through the GamesList and get the games and their attributes in a list. I've kinda hit a wall where I am not entirely sure how to accomplish this. Still very new to backbone so any help would be greatly appreciated.
JSON Object:
{
"GamesList":[
{
"Date":"2013/07/02",
"Games":[
{
"Id":"3252",
"Time":"12:10 AM"
}
]
},
{
"Date":"2013/07/02",
"Games":[
{
"Id":"3252",
"Time":"12:10 AM"
}
]
},
{
"Date":"2013/07/02",
"Games":[
{
"Id":"3252",
"Time":"12:10 AM"
}
]
}
]
}
App Structure:
App.Models.Game = Backbone.Model.extend({
defaults: {
GamesList: ''
}
});
App.Collections.Game = Backbone.Collection.extend({
model: App.Models.Game,
url: 'path/to/json',
parse: function (response) {
return response;
}
});
App.Views.Games = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new App.Collections.Game();
this.listenTo(this.collection, 'reset', this.render, this);
this.collection.fetch();
},
render: function () {
//filter through all items in a collection
this.collection.each(function (game) {
var gameView = new App.Views.Game({
model: game
});
this.$el.append(gameView.render().el);
}, this)
return this;
}
});
App.Views.Game = Backbone.View.extend({
tagName: 'li',
template: _.template($('#gameTemplate').html()),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var gameCollection = new App.Collections.Game();
gameCollection.fetch({
data: {
collection_id: 25
},
success: function (data, textStatus, jqXHR) {
console.log(data);
console.log(textStatus);
console.log(jqXHR);
console.log('success');
},
error: function () {
alert('Oh noes! Something went wrong!')
}
});
var gamesView = new App.Views.Games({
collection: gameCollection
});
$(document.body).append(gamesView.render().el);
It looks like your JSON object is not inlined with Backbone.Collection...
as you declared App.Collections.Game has url /path/to/json which means the json that needs to return is a list... without the GamesList that is seen in your JSON
EDIT:
You can use the parse function in your Games Collection to fix the json retrieved from your server
parse:function(response){
return response.GamesList;
}
Important:
Please note that your json objects that are fetched from the server should have ID. Backbone will 'think' these models are new and will create them upon save...
I'm seeing a little confusion in it. Let's proceed step by step:
--------- AFTER COMMENT ---------
You can set your model as:
defaults: {
Date:'',
Games:''
}
then modifying your parse function as
parse: function (response)
{
var _this = this;
_.map(response, function(obj) {
_this.add(obj)
});
}
This way you add each single item in the collection as your model expect.
Another problem I'm seeing is that you're creating and fetching the collection twice:
...
this.collection = new App.Collections.Game();
this.listenTo(this.collection, 'reset', this.render, this);
this.collection.fetch();
...
and then
var gameCollection = new App.Collections.Game();
...
gameCollection.fetch({
data: {
....
...
var gamesView = new App.Views.Games({
collection: gameCollection
});
I'm relatively new to Backbone and though I know the general idea of how to use it, my learning has been rapid and I'm probably missing some key elements.
So I have a collection that contains an attribute called "type" which can be article, book, video, class. I have the view rendering and everything but I need to be able to filter the collection when links are clicked.
My question is - how can I get it to filter down the collection and still be able to refilter the original collection when I click on another type?
Here's the gist of my code, I simplified it for easy reading:
var TagsView = Backbone.View.extend({
initialize: function(query) {
this.collection = new TagsCollection([], {query: self.apiQuery} );
this.collection.on('sync', function() {
self.render();
});
this.collection.on('reset', this.render, this);
},
render: function() {
//renders the template just fine
},
filter: function() {
//filtered does work correctly the first time I click on it but not the second.
var filtered = this.collection.where({'type':filter});
this.collection.reset(filtered);
}
});
update: I managed to get this working. I ended up triggering a filter event.
var TagsCollection = Backbone.Collection.extend({
initialize: function(model, options) {
this.query = options.query;
this.fetch();
},
url: function() {
return '/api/assets?tag=' + this.query;
},
filterBy: function(filter) {
filtered = this.filter(function(asset) {
return asset.get('type') == filter;
});
this.trigger('filter');
return new TagsCollection(filtered, {query: this.query});
},
model: AssetModel
});
And then in my view, I added some stuff to render my new collection.
var TagsView = Backbone.View.extend({
initialize: function(query) {
this.collection = new TagsCollection([], {query: self.apiQuery} );
this.collection.on('sync', function() {
self.render();
});
this.collection.on('filter sync', this.filterTemplate, this);
this.collection.on('reset', this.render, this);
},
render: function() {
//renders the template just fine
},
filterCollection: function(target) {
var filter = $(target).text().toLowerCase().slice(0,-1);
if (filter != 'al') {
var filtered = this.collection.filterBy(filter);
} else {
this.render();
}
},
filterTemplate: function() {
filterResults = new TagsCollection(filtered, {query: self.apiQuery});
console.log(filterResults);
$('.asset').remove();
filterResults.each(function(asset,index) {
dust.render('dust/academy-card', asset.toJSON(), function(error,output) {
self.$el.append(output);
});
});
},
});
The reason it's not working a second time is because you're deleting the models that don't match your filter when you call reset. That's normal behaviour for the reset function.
Instead of rendering with the view's main collection, try using a second collection just for rendering which represents the filtered data of the original base collection. So your view MIGHT look something like:
var TagsView = Backbone.View.extend({
filter: null,
events: {
'click .filter-button': 'filter'
},
initialize: function (query) {
this.baseCollection = new TagsCollection([], {query: self.apiQuery} );
this.baseCollection.on('reset sync', this.filterCollection, this);
this.collection = new Backbone.Collection;
this.collection.on('reset', this.render, this);
},
render: function () {
var self = this,
data = this.collection.toJSON();
// This renders all models in the one template
dust.render('some-template', data, function (error, output) {
self.$el.append(output);
});
},
filter: function (e) {
// Grab filter from data attribute or however else you prefer
this.filter = $(e.currentTarget).attr('data-filter');
this.filterCollection();
},
filterCollection: function () {
var filtered;
if (this.filter) {
filtered = this.baseCollection.where({'type': this.filter});
} else {
filtered = this.baseCollection.models;
}
this.collection.reset(filtered);
}
});
To remove any filters, set a button with class filter-button to have an empty data-filter attribute. collection will then be reset with all of baseCollection's models
Here's a better answer to this. Instead of making it so complicated, you can just use the where method. Here's my replacement solution for the question above.
filterby: function(type) {
return type === 'all' ? this : new BaseCollection(this.where({type: type});
});
You can try using comparator function of your Collection.
http://backbonejs.org/#Collection-comparator
Basically its is like sorting your collection.