I have an application that saves a user's search criteria in localStorage, where each saved search is represented as an instance of an Ember.js model:
Checklist.SavedSearch = DS.Model.extend({
id: DS.attr('string'),
filters: DS.attr('string')
});
When the "save" button is pressed, the controller creates a model instanced and creates a record for it:
Checklist.savedSearchController = Ember.ArrayController.create({
[..]
save: function(view) {
var saved_seach = Checklist.SavedSearch.createRecord({
id: 'abcd',
filters: '<json>'
});
Checklist.local_store.commit();
}
});
Checklist.local_store is an adapter I created (this is unsurprisingly where the problem probably begins) that has a basic interface that maps createRecord, updateRecord, etc. to a bunch of get/set methods that work with localStorage (loosely based on a github fork of ember-data). The adapter appears to work fine for some basic tests, particularly as findAll has no issues and returns values added manually to localStorage.
Here is the relevant method within Checklist.local_store:
createRecord: function(store, type, model) {
model.set('id', this.storage.generateId);
var item = model.toJSON({associations: true});
this.storage.setById(this.storage_method, type, id, item);
store.didCreateRecord(model, item);
}
The problem is that when createRecord is called by the controller, absolutely nothing occurs. Running it through the debugger, and logging to console, seems to show that the method isn't called at all. I imagine this is a misunderstanding on my part as to how Ember.js is supposed to work. I'd appreciate help on why this is happening.
I come from a ruby and php background, and have perhaps foolishly dived straight in to a JS framework, so any other comments on code style, structure and anything in general are welcome.
Ember Data doesn't change createRecord on the controller so it shouldn't behave any differently. It's possible that there was something related to this in the past, but it's certainly not the case anymore.
Related
Using Ember 1.13.6 and Ember Data 1.13.7, I am wondering how I can get the already loaded objects from the store without doing a call to the server (where relationship is async).
Image following model:
//page model
export default DS.Model.extend({
parent: DS.belongsTo('page', { async: false, inverse: 'subpages'}),
subpages: DS.hasMany('page',{ async: true, inverse: 'parent' }),
});
If you just call model.get('subpages'), Ember Data returns a promise + make a call to the server. Normally this is fine behaviour, but now I got a special case I just want to grab the already loaded objects.
I can't find anything about such case in the docs. The only way I found at the moment is by using private properties:
model._internalModel._relationships.initializedRelationships.subpages.canonicalState
Of course I seek a normal way to achieve this, without touching the inner code of Ember Data. So does anyone know how to achieve this?
I don't think you can do it from relationships directly, but you can peek.
this.store.peekAll('page')
Wouldn't call the backend. You could probably use a filter, they don't hit the backend:
existingSubpages: function(){
return this.store.filter('page', item => {
return item.parent === this;
});
}.property('subpages.#each')
In addition to #Kit Sunde's answer, there maybe a situation where you want some objects to have an async relation and other objects to have a sync relation.
With the ready hook this can easily be implemented:
ready() {
if(this.get('relationIsSynced')) { //whater case you got
this.set('subpages.content.relationship.isAsync', false);
}
}
I am trying to search through several thousand records for a particular record. I have loaded a User model and it has several thousand Shipments that are async. In other words I have the serializer send down shipment_ids with the user model.
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true })
});
I want to search through all those ids to see if a particular record's id is among them. However I don't want to go to the api and load each and every single record. I just want to be able to search through the shipment_ids.
I have a jsbin that showcases it loading all the records. How do I change this so it doesn't make any api calls for a shipment and still finds the specific record?
Instead of doing
this.get('shipments').any(function(shipment){
return shipment.get('id') === "10124";
there has to be a way to go through only the ids, no?
Thanks!
The solution described in "Get belongsTo ID without fetching record"
seems to work as of Ember Data 1.0.0-beta.10. Briefly, you can do the following to access the underlying
modelInstance.get('data.belongsToRelation.id');
Presumably you can also do this (though I have not tested it):
modelInstance.get('data.hasManyRelation');
Original Answer (DO NOT USE):
I have accomplished this on a belongsTo by adding a second model field for the id. My example:
App.User = DS.Model.extend({
group: DS.belongsTo('group', { async: true }),
groupId: DS.attr('number')
});
So maybe you can do this?
App.User = DS.Model.extend({
shipments: DS.hasMany('shipment', { async: true }),
shipmentIds: DS.attr('array')
});
You will need to add an array transform in app/transforms/array.js (assuming you use ember-cli)
import DS from 'ember-data';
var ArrayTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
export default ArrayTransform;
Let me know if it works as I probably will need to something similar in my app soon.
This hack is very smelly but it's the only way I have found. And it is to search with _data
this.get('content._data.shipments').any(function(shipment){
return shipment.get('id') === "10124";
});
It won't make any api calls. But there has to be a more acceptable method that won't be prone to breakage when updating Ember.
Here is an updated jsbin to show this. Does anyone have a better solution?
Thanks!
Since Ember Data 2.3 you can use this code:
// get all ids without triggering a request
var commentIds = post.hasMany('comments').ids();
See http://emberjs.com/blog/2016/01/12/ember-data-2-3-released.html for details.
I am trying to wrap my head around Backbone, more specifically how the an application flows throughout it's life. Unfortunately at my job I do not have access (or say for that matter) on how our API is structured. We have many different calls from different time periods with crazy inconsistent structure.
Overriding fetch or sync is not a problem to standaraize the return but what I run into (at the very beginning of my dive in the a Backbone application) is a how to layout the actual code.
Here is my real world example. This page is non-critical and I am trying to re-write it with Backbone. Here is the flow:
Page loads a list of genre types from a call
Clicking on a genre type loads sub genres based off of the genre type (the sub genre type requres a genre code as the parameter)
Clicking on the sub genre type loads all products with that criteria.
I can get pretty far but at some point I feel the code is getting mangled - or doesn't feel natural. Like I am shoving things in.
So my official questions is: How do I manage a Backbone app?
Here is a summary of my though process:
I created a global namespace as one should
var App = App || {};
Okay, lets start with the main application view as all examples show:
App.MainView = Backbone.View.extend({
//this loads the outer stuff
//and creates an instance of the Genre View
});
Alright pretty straightforward, I am going to need a genre model, collection, and view (this applies to sub genre as well)
App.Genre = Backbone.Model.extend();
App.Genres = Backbone.Collection.extend({
url: 'returns a list of genres',
model: App.Genre,
initialize: function() {
this.fetch();
},
fetch: function() {
var self = this;
$.ajax({
url: this.url,
success: function(response) {
**format return**
self.add(formattedArrayOfModels);
}
});
}
});
Now on to the view, the confusing part
App.GenreView = Backbone.View.extend({
el: 'element',//easy enough
tmpl: 'my handlebars template',//implementing handlebars...no problem
initialize: function() {
//this produces a collection full of genres
this.genreList = new App.Genres();
this.genreList.on('add', _.bind(this.render, this));
},
render: function() {
//rendering not a problem, pretty straight forward
}
});
Up until here I have no problems. The genre list loads and we're good to go. So, now when the user clicks a genre I want it to load a sub genre
events: {
'click a': 'getSubGenres'
},
getSubGenres: function(e) {
}
Here is my problem. In getSubGenres do I keep it local?
var subGenre = new App.SubGenreView();
Or should I make it part of the Genre view?
this.subGenre = new App.SubGenreView();
Should I somehow put it in a parent object so it can be accessed by other views? How do I control things like that?
And if I already have a collection of sub genres how do I just use the loaded collection (instead of another ajax call).
Is this the approach you would use?
couple of things before I answer,
first: the fetch function doesn't need an $ajax call since it's its job, so, you can evaluate error:function(){} and success:function(){} immediately inside fetch, but that's assuming that the URL is set correctly.
second: one thing that helped me a lot in my backbone keyboard-head-fight is the addy osmani Backbone Fundamentals which contains a very rich tutorial in pdf format.
now back to the question: from my experience, you will mostly need 'this', so it's a good habbit to get used to it, plus there is something that solves a lot of these issues if implemented correctly: backbone layoutmanager
anyway, the decision of where to place the subview, is totally a design decision in your case and depends a lot on how you structure your page and files.
about how to use the "collection" that is preloaded: I really didn't get it, because the collection you're talking about contains all the subgenres, so usually it shouldn't change even if the view changes to a certain genre view, you are still able to use it.
but still everything I said, is relative to how you structure your files, I do an app.js and a router.js and lots of other files, but the main work is always on the main two, so basically I always get access to everything.
I hope this answered your question
I'm currently evaluating Ember.js and therefore I am building a small sample app. Currently everything went quite smooth so far, but now I don't seem to be able to fix my last little problem.
When I access the app normaly via the route films, everything works as expected. The list of films is displayed. Now when I click onto a film, the details of the film are loaded via setupController hook just below the list of films. That's all just fine.
Here comes my problem: I would like to be able to access the film details directly via url, but somehow in this case another request is fired to grab the film details, with the value of undefinded. As far as I understand that is the model hook.
I can only guess, but I think it is the model hook which is beeing executed.
Can someone point me to the probably obvious mistake I'm making? And on the other hand, is the code i wrote so far "correct"? Or is there a better way of doing this?
(I am aware of the bad way I use to render the film details. I will remove the {{#each}} tag, and change the way I asign the response to the film variable.
Here the link to the sample app: http://jsbin.com/ewiN/1#/films
UPDATE
Ok, now I am getting really confused. I almost have it working, hopefully someone can point it out to me, because it's such a simple task, but it nearly seems impossible to do without knowing ember really well...
When accessing the app via url, it only works when I remove the setupController hook. But I need that hook, to load the FilmDetails on clicking onto the links to properly load the FilmDetails.
http://jsbin.com/ewiN/16#/films/tt0100669
Many thanks for the feedback!
Regards
Reto
Instead of use jQuery.getJSON, use Ember.RSVP.Promise, because internally ember use this promise api instead of jquery. I think that using both, can make inconsistencies.
return new Ember.RSVP.Promise(function(resolve, reject) {
var films = [];
jQuery.getJSON("http://www.omdbapi.com/?s=" + searchTerm, function (response) {
$.each(response.Search, function (index, value) {
films.pushObject(Kitag.Films.create({
title: value.Title,
id: value.imdbID
}));
});
}).fail(reject);
resolve(films);
});
Because we are returning a promise instead of an object, we need to use the model hook, because it is wait until the promise is resolve to render templates.
Kitag.FilmsRoute = Ember.Route.extend({
model: function() {
return Kitag.Films.getMovies('spiderman');
}
});
I have removed the Kitag.FilmRoute, because the expected:
Kitag.FilmRoute = Ember.Route.extend({
model: function(params) {
return Kitag.Film.find(params.id)
},
serialize: function (model) {
return { film_id: model.get("id") };
}
});
is the default.
This is the final result http://jsbin.com/ewiN/15/edit
I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)