so I am working on an XMPP client with Ember.js. Since my data is coming from XMPP I wanted to create my own models and found this nice tutorial: http://eviltrout.com/2013/03/23/ember-without-data.html and the small example application emberreddit.
The setup should be pretty simple. I just extend Ember.Object and implement a find function which either creates or returns the object:
App.Conversation = Ember.Object.extend({
messages: [],
talkingPartner: null,
init: function(){
this._super();
console.log("Init called for App.Conversation");
//Binding for XMPP client event
$.subscribe('message.client.im', _.bind(this._onMessage, this));
},
//Private Callbacks
_onMessage: function(event, message){
console.log("Received message");
this.find(message.jid).messages.pushObject(message);
}
});
App.Conversation = Ember.Object.reopenClass({
store: {},
find: function(id){
if(!this.store[id]){
this.store[id] = App.Conversation.create();
}
return this.store[id];
}
});
This follows roughly the code from here. It works okay but init is never called. If I create the object not using find it works. So I am a little confused.
As far as I know store should be the same for all instances of
App.Conversation. Is that correct? Also, if that is true I have to
move messages and talkingPartner to init and set them via
this.set('message'), don't I.
Why is ìnit not called when App.Conversateion.create() is called in App.Conversation.find(id). Can anyone explain why? I found that Ember.js sometimes behaves a little bit different than one expects at first.
You need to change this:
App.Conversation = Ember.Object.reopenClass({
To this:
App.Conversation.reopenClass({
You code is reopening Ember.Object itself, and the completely overwriting the definition of App.Conversation.
Here's a working jsFiddle based on your code.
Related
I am still very new to the world of Ember, and I'm still trying to understand EmberJS and Ember Data (latest version). In my previous (non-Ember) Node app, I included a library that handled all my REST calls to where my data was stored. It set up the connection to the server and handled all the error handling and parsing into a nice and tidy JSON object, and even handled multiple calls to the server in case the response was too big for one call. I can fetch individual records, but if I wanted to fetch a bunch of records, all I had to do was initialize the library object ('myObj') and call myObj.fetchAll(config) to initiate the fetch. Then I just have to wait on several events.
Example
myObj.on('record', function() { // Each record is an event }
myObj.on('error', function () { ...}
myObj.on('end', function () { // After the last record is retrieved }}
I would very much still like to use this library in Ember, but I have no idea how to go about setting it up. I haven't been able to find any examples of creating my own Adapter (is that the right terminology) that would allow me to do this.
Is this something I can do with Ember, or is it not recommended?
I would strongly suggest you use ember-data before attempting something non-standard as you're learning. Virtually all the documentation, and help will specifically be about ember-data. This is a good starting point: http://guides.emberjs.com/v1.13.0/models/
It's perfectly possible to use your own models and use a custom rest interface. You initiate your myObj.fetchAll(config) call on the router. If it's waiting for an event, return a promise and resolve it when the event returns. I don't know anything about your library but it would look something like:
export default Ember.Route.extend({
model() {
return Ember.RSVP.Promise(function(resolve){
var records = [];
myObj.on("record", (record) => {
records.pushObject(record);
});
myObj.on("end", () => {
resolve(records);
});
myObj.fetchAll(ENV.config);
});
}
});
In imperfect contrast, this is how you glue things together from your adapter to your template normally in ember:
Configuring a REST endpoint:
export default DS.RESTAdapter.extend({
host: 'https://api.example.com'
});
Defining a model:
export default Model.extend({
name: attr('string')
});
Fetching data in your route:
export default Ember.Route.extend({
model() {
return this.store.findAll('person');
}
});
Rendering the data:
{{#each model as |person|}}
{{person.name}}
{{/each}}
It's all pretty straight forward if you stick to the default way of doing things.
When my path /map/:id finds no value via this.store.find('location', route.id), I'd like to redirect to another page instead of receiving an "adapter's response did not have any data" error. It seems to stop processing before it even gets to the controller.
I thought the best way to do this was to extend DS.FixtureAdapter or to return a proxy object until this.store.find resolves. I read the documentation and it said to extend DS.FixtureAdapter via find or findMany hooks, among others. When I tried, none of the events seemed to fire, and I can't figure out an appropriate alternative return object. What am I doing wrong?
this.store.find() returns a promise. Promise resolution has 2 outcomes: 1. good and 2. bad. You can pass in 2 functions into the then() method to tell a promise what to do in each scenario.
So, let's say you are looking for a record and it's not there (bad outcome), you can tell ember to transition to another route.
App.DudeRoute = Ember.Route.extend({
model: function() {
var route = this;
return this.store.find('dude', 5).then(
function(dude){
return dude;
},
function(error){
route.transitionTo('nomansland');
});
}
});
Also note that you need to create a route variable, because just using this inside the bad scenario won't work, since this gets a new context.
Working example here
I've recently been porting a number of Ember apps I maintain to RC 8 and ran into this.
Before the router facelift landed I would sometimes manage control flow via promises returned by Ember Data find calls.
For example:
SomeRoute = Ember.Route.extend({
model: function(params) {
var resolve = function(model) { return model; };
var route = this;
var reject = function() { this.transitionTo('someOtherRoute'); };
return SomeModel.find(params.some_model_id).then(resolve, reject);
}
...
});
With the recent changes, it is now possible to handle errors created in model callbacks via the error action:
SomeRoute = Ember.Route.extend({
// note: model callback no longer needed--default suffices
actions: {
error: function(reason, transition) {
// check the reason object to determine how/if to handle this error
this.transitionTo('someOtherRoute');
}
}
...
});
I much prefer the latter approach as it makes the code easier to read and better separates concerns.
This works well in most cases but I encountered an issue in an app that uses nested routes. I've included a simplified example followed by a jsbin that demonstrates the issue.
Lets say we want to show Articles that belong to Authors and the URLs look like: /authors/:author_slug/articles/:article_slug. We want to redirect to a Not Found page when someone tries to view an article that doesn't exist.
When managing control flow in the model callback as above, you can browse to /authors/some_author/articles/some_invalid_slug and be redirected to /authors/some_author/articles/not_found as expected.
However, if the redirect to the Not Found page is instead managed via the error action, the parent context is lost at some point and you end up at /authors/undefined/articles/not_found.
You can see this in the following jsbins:
http://jsbin.com/eJOXifo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/eJOXifo/1#/authors/schneier/articles/not_found)
http://jsbin.com/oNaWelo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/oNaWelo/1#/authors/undefined/articles/not_found)
Does anyone know why this happens or how to avoid it?
Notes:
I know this doesn't have anything to do with Ember Data. However, implementing something equivalent without Ember Data just makes the example more complicated without adding anything
There are a few small hacks to make Ember Data work as expected in jsbin:
I'm preloading the parent model to avoid having to load it from anywhere.
I'm not doing anything special to provide data for the child model. The app just makes a request to http://jsbin.com/articles/12345. This actually returns a 200 but bombs anyway because the response is html. An API that correctly returns a 404 response gives the same behvaiour.
I remember a while ago reading about some service that could be used to build fake API responses for use with services like jsfiddle or jsbin. If anyone knows what it is please comment.
Your right that the parent context is getting lost. The trick is to extract that context from the transition and pass it as an argument when calling transitionTo. So:
App.ArticlesArticleRoute = Em.Route.extend({
actions: {
error: function(reason, transition) {
console.log('in error handler');
this.transitionTo('articles.notFound', transition.resolvedModels.authors);
}
}
});
With this change, visiting the url:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/my-fake-article
will redirect to:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/not_found
I have probably tried a gazillion ways and nothing is efficient. My last attempt - which works a little well but with an ugly tradeoff was this:
App.UsersRoute = Em.Route.extend({
model: function() {
return App.User.find({}).then(function(response) {
return response;
});
}
});
the problem with this - which I'd love to know is its making a synchronous call. My HTML/DOM won't finish loading until this returns.
Another thing I'd love to know is if I omit the empty object {} from the find - the promise function actually gets called immediately. I promise!
Now other methods I've tried are the following which all have flaws:
observing content.lastObject.isLoaded on a controller
implementing arrayContentDidChange from Ember.ArrayController - obviously this gets triggerd multiple times as the array is getting filled.
-
I have probably tried a gazillion ways and nothing is efficient.
I don't know if you tried hooking into the afterModel function of a route which is an addition added not very long ago and available in rc6:
App.UsersRoute = Em.Route.extend({
afterModel: function(users, transition) {
console.log(users.get('length'));
}
});
See here for more info on the hooks beforeModel and afterModel.
I've also put togheter a jsbin to play around.
Hope it helps.
You can read about it here https://github.com/emberjs/data/pull/735
There is a question like yours Ember-data RecordArray isLoaded Status.
Also you can try to observe isUpdating to be false instead of isLoading
I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.