Updating loaded models with JS - ember.js

To explain my exact use-case:
So I have a news-model with a 'created_at' field. Displaying all the dates with an ArrayController works fine and as expected. But what I want to do now, is converting these dates into fuzzy times with http://pragmaticly.github.io/smart-time-ago/ (or http://timeago.yarp.com/).
The problem here is, I need to call $('.timeago').timeago('refresh'); once the timestamps are loaded and displayed. That seems to work fine as long as I navigate within the ember-app. But when I refresh a site the plugin somehow can't convert it.
So I suppose the command doesn't get called at the right time from the app.
I am doing that in the following two ways right now:
in the View:
didInsertElement: function(){
$('body').timeago('refresh');
}
and in the Controller:
updateFuzzyTime: function(){
$('body').timeago('refresh');
}.observes('content')
When I do it in either place with the setTimeout() command set to 1000ms it obviously does work.
So how can I get ember to perform that command when a model is fully loaded and displayed on a refresh?
(In another project I had a similar issue and there I used the rerender() command which did work, but in this use-case I simply can not do that)
Edit: Just to post it here as well. The plugin looks at the datetime-attribute of a time element. So my hbs code looks like this:
<time class="timeago" {{bindAttr datetime="date"}}>{{date}}</time>
And I suppose the reason why most hooks don't work is because even though the DOM is rendered I suppose Ember hasn't updated the attribute yet.

Try calling it in an afterRender callback. That usually executes after everything else.
init: function() {
this._super();
Ember.run.scheduleOnce('afterRender', this, 'updateFuzzyTime');
}
updateFuzzyTime: function() {
$('body').timeago('refresh');
}

Related

Ember link-to gets active class for the wrong transition

This is one of those Ember issues that I'm unable to replicate anywhere but my project. The visual effect of the problem is that the active class on a link-to is one transition behind. I'll click a link and the link that goes to the page I was just on is highlighted with the active class.
I've started digging into the link-to component code to figure out how active is computed. But it is based on _routing.currentState and I'm not sure what that is. The currentState, and other bits of info, are passed to the routing's isActiveForRoute which then calls the routerState's isActiveIntent. And that function calls another isActiveIntent and compares some more things together. All this seems like a large easter egg hunt for something (the root of my problem) that is probably not in Ember's code anyways.
I feel like the following snippet sums up the problem I'm having. The targetRouteName is the route that is being directed to by the link. _routing.currentRouteName seems to be pointing to the route the browser is currently looking at. The fact these match makes me feel like the link should be active, but the active function returns false.
> link.get('targetRouteName')
"parentRoute.pageA.index”
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index”
> link.get('active')
false
For reference this is after finding the link via the Chrome extension and showing all components. I then did link = $E.
For the wrong link (the one that does get the active class) I get:
> link.get('targetRouteName')
"parentRoute.pageB.index"
> link.get('_routing.currentRouteName')
"parentRoute.pageA.index"
> link.get('active')
"active"
Additional Raw Information
The routes I'm dealing with are nested. But it is a pretty standard nesting, very much like the one I have in my ember-twiddle (e.g. page-a, page-b, page-c).
There is a model hook on the parent route and on the indexs of the children routes. But the children routes reference (this.modelFor(...)) the parent.
My template is referencing the .index of those routes. They are standard link-to components. They do not include model information.
I'm running Ember-cli 1.13.8, Ember 2.0.0, and Ember Data 2.0.0-beta.1.
What I have tried so far
Upgrading to 1.13.0
Moving the file structure to pods
Removing the functions in my authentication route which a lot of these routes inherit from.
Upgrading to 2.0.0
Trying to remove/add .index on my routes
Tried replicating on ember-twiddle
Doing ember init with ember-cli to see if my router or application setup was different from the standard layout and it doesn't differ in any significant way.
Adding model information to one of the links, that didn't change anything and since it didn't call the model hooks it messed up the view.
Asked on the slack channel
Please Help
I've had this issue for a couple weeks now and I'm not sure where else to look. I'd love any suggestions on how I can resolve this.
Update
This ended up getting fixed in 2.1.0.
This is common problem when you mess around with willTransition router action. For example,
IMS.ResultDetailsEditRoute = Ember.Route.extend({
actions: {
willTransition: function() {
this.controller.clearForm();
}
}
});
In this code snipped willTransition called controller's method "clearForm()" which no longer exists. For some reason, Ember doesn't throw an error, but it causes the problem that #RyanJM explained.
I have run into something similar when using a component with a nav. Here was my approach:
I added a controller (I know, you should be steering away form these, but I needed to). My controller:
import Ember from 'ember';
const {
Controller,
inject
} = Ember;
export default Controller.extend({
application: inject.controller(),
});
Then, in my template, I could pass application to my component.
{{account/account-icon-nav currentRouteName=application.currentRouteName}}
In my component, I set set up a function to test my current route names:
import Ember from 'ember';
const {
Component,
computed,
get
} = Ember;
const activeParentRoute = function(dependentKey, parentRouteName) {
return computed(dependentKey, {
get() {
return get(this, dependentKey).indexOf(parentRouteName) > -1;
}
});
};
export default Component.extend({
isYourProfile: activeParentRoute('currentRouteName', 'account.your-profile'),
isYourActivity: activeParentRoute('currentRouteName', 'account.your-activity'),
isYourGoals: activeParentRoute('currentRouteName', 'account.your-goals')
});
Then bind the active class yourself:
<div class="icon-nav md-hidden">
{{link-to "" "account.your-profile" classBinding=":profile isYourProfile:active" title="Your Life"}}
{{link-to "" "account.your-activity" classBinding=":activity isYourActivity:active" title="Your Money"}}
{{link-to "" "account.your-goals" classBinding=":goals isYourGoals:active" title="Your Goals"}}
</div>
I know this is a bit different since we are doing it within a component, but I hope it helps. You can bind these classes yourself by passing the application around.

Timing Issues with Layouting via didInsertElement view function

I encounter ugly timing problems (race conditions) with putting code which carries out layout based on height calculations with jQuery in didInsertElement().
For example, I calculate the height of a header via $('header.someClass').outerHeight(true); then I use the result to offset the content area from the top. If I render the view completely new via reloading the whole page it works (60px in my example) but if I navigate to the view from another one, it fails because the wrong height is returned (6px in my example).
To prove that it is a timing issue: If I wrap the code in:
Em.run.later(function() {
...do layout
}, 50);
It works.
I consider this a serious issue because there are not other hooks in Ember, I can attach to.
Instead you should schedule your jQuery logic to run after the rendering:
App.YourView = Ember.View.extend({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, function(){
// perform your jQuery logic here
});
}
});
Find more infos and explanations in my blog.
It sounds like you might have a bit of a misunderstanding of how ember works.
didInsertElement is fired when that particular view is injected into the dom, not when all of the elements are in the dom. Note: If the model behind it changes, ember won't fire didInsertElement again, because it wasn't reinserted into the dom.
If it works when you delay, then it sounds like your logic for calculating the size of something is depending on something that may not be there yet.
Feel free to show an example of it using emberjs.jsbin.com.

Detect model/URL change for Ember Route

My EmberJS application has a ProjectRoute (/project/:project_id) and a corresponding ProjectController. When viewing a particular project, users can edit its properties, and I'd like the project to automatically be saved when the user stops looking at it.
Currently, what I'm doing is something like this:
Application.ProjectRoute = Ember.Route.extend({
...
exit: function() {
this.get('controller').saveProject();
}
...
});
This works when the user simply closest the project view. However, if the user simply switches to viewing a different project (e.g. goes directly from /project/1 to /project/2), the same route is used (it just uses a different model), and exit is not called.
What I need is a way to detect this transition and call the saveProject function before it happens. Any ideas?
I "solved" it by adding the following to my controller:
Application.ProjectController = Ember.ObjectController.extend({
...
saveOnChange: function() {
var previousProject = this.get('target.projectToSave');
if (previousProject && previousProject.get('isDirty')) {
Application.store.commit();
}
this.set('target.projectToSave', this.get('content'));
}.observes('content')
...
});
This seems to work well. Note that I'm storing the projectToSave property in the route (target) as opposed to the controller, because the controller gets wiped every time the model changes. It feels a little weird/hacky to store this in the route, but the only alternative I could think of was to store it in ApplicationController, which seemed overly broad.

emberjs "loading screen" at the beginning

I don't know, if you have seen this demo-app yet: http://www.johnpapa.net/hottowel/ but once you start it, you see a really nice loading screen at the beginning like you would in any bigger desktop application/game.
So I haven't had the chance to go through the code properly myself, but I have started recently with Emberjs and I have the feeling that loading all the js-code for the whole SPA that I am building could be in the seconds area.
My question now, how would such a loading screen be possible with emberjs?
Or would there be a better way to go about that? (I somehow don't think requirejs would be a solution, though I could be wrong)
I'd like to contribute an alternate answer to this. By default, ready fires when the DOM is ready, and it may take some time to render your application after that, resulting in (possibly) a few seconds of blank page. For my application, using didInsertElement on ApplicationView was the best solution.
Example:
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
$("#loading").remove();
}
});
Please note that Ember also offers the ability to defer application readiness, see the code for more information.
Maybe it's my lazy way of doing things, but I just solved this by adding a no-ember class to my div.loading and in my CSS I added
.ember-application .no-ember {
display: none;
}
(Ember automatically adds the ember-application to the body.)
This way, you could also add CSS3 animations to transition away from the loading screen.
you can do something like this:
App = Ember.Application.create({
ready: function () {
$("#loader").remove();
}
});
in your body you set something like this
<img src="img/loading.gif" id="loader">
Alternative to using didInsertElement, the willInsertElement is a better event to perform the loading div removal since it will be removed from the body tag "before" the application template is rendered inside it and eliminates the "flicker" effect ( unless using absolute positioning of the loading div ).
Example:
App.ApplicationView = Ember.View.extend({
willInsertElement: function() {
$("#loading").remove();
}
});
Ember has an automagic loading view logic.
By simply setting App.LoadingView and its template, Ember will show that view while application loads.
This feature is likely to change in next release, in favor of a nested loading route feature which looks promising. See below:
Draft documentation
Feature proposal and discussion
In Ember 2.0 there is no more View layer, but you can do the same with initializers:
App.initializer({
name: 'splash-screen-remover',
initialize: function(application) {
$('#loading').remove();
},
});

Emberjs - actions get triggered 3 times on a single click

In my app using Emberjs, every action gets triggered 3 times on a single click.
For example with the following template and view:
Template:
<button {{action "removeFoo"}}>remove</button>
View with click handler:
listsView = Ember.View.create({
templateName: 'lists',
removeFoo: function(event) {
event.preventDefault();
console.log(new Date().valueOf());
}
})
I get the following 3 outputs in the console:
1333634360209
1333634360215
1333634360217
Does anybody know what's causing this or what's the best approach to debug the problem?
The actual problem was, that the Ember app was part of a Rails application which had two other Ember apps already. Those Ember apps didn't have a rootElement specified. Adding a rootElement for every Ember app fixed the problem.
I'm not sure why it's being called multiple times, but are you intentionally overriding Ember.View#remove? If so you'll probably want to call this._super() so that it destroys the element etc...
Here's the definition of it in the source:
https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js#L770
If that wasn't your intention, you might want to call your action something else and see if resolves the problem.
Using the latest Ember.js version 0.9.6 works fine, see http://jsfiddle.net/pangratz666/BxccU/