Reputation: 75
Environment:
Ember.VERSION = 1.7.1
DS.VERSION = 1.0.0-beta.14.1
Problem: I'm trying to run some code when the view is finally rendered on initial application load, the problem is the model is not yet loaded because the client fetches it from the server.
so the flow (I guess) that happens is something like this:
I want to run some code (bootstrap init of tooltips) after step 3 is completed, but I dont really sure how to.
just to point out, after the model is fetched from server when I leave and return to the same route everything is works fine (because the model returns from the store and not from server)
what I tried so far:
How can catch event after template is rendered at EmberJS?
App.ApartmentsView = Ember.View.extend({
didInsertElement: function () {
$('[data-toggle="tooltip"]').tooltip();
}
});
this didnt work, because at that point the model length is 0
listening to model change:
App.ApartmentsRoute = Ember.Route.extend({
model: function () {
return this.store.filter('apartment').then(function (model) {
console.log(model);
return model;
});
}
});
Again, at this point the model.content.length = 0
I guess at this point the store just creates the model and only at a later point updates it.
I've also tried listening to the arrayContentDidChange
property in the controller, but at this point the model is not rendered yet, I can use a timeout but I think its a bad solution.
UPDATE1: here is the template:
<script type="text/x-handlebars" data-template-name="apartments" id="apartments">
.
.
{{#each }}
<tr>
<td>{{rating}}</td>
<td>{{price}}</td>
<td>{{street}} {{building}} {{city}}</td>
<td class="text-right hidden-print">
<button type="button" class="btn btn-default" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="Navigate" {{ action 'navigate' }}>
<span class="glyphicon glyphicon-transfer" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="Center Apartment" {{ action 'centerApartment' }}>
<span class="glyphicon glyphicon-screenshot" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="NOT WORKING" {{ action 'setTime' }}>
<span class="glyphicon glyphicon-time" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="NOT WORKING" {{ action 'share' }}>
<span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span>
</button>
{{#link-to 'apartment' this classNames="btn btn-default"}}<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>{{/link-to}}
<div class="btn btn-default" data-toggle="tooltip" data-placement="top" title="Delete Apartment" {{action 'deleteApartment' }}>X</div>
</td>
</tr>
{{/each}}
.
.
</script>
Any suggestions?
Upvotes: 1
Views: 445
Reputation: 9330
As @torazaburo already mentioned, the issue was in the filter function. Simply
model : function(){
return this.store.filter('apartment', {}, function(item){
return true;
});
}
would work for you. Obviously, this is analogous to this.store.find('apartment')
Actually this is a simple hack (including empty object as query) to get data from server before filter.
But, to make sure that tooltips get initialized on newly added elements, much better approach is mentioned by @torazaburo
Upvotes: 0
Reputation:
Your description of the lifecycle is incorrect. It should be:
Ember enters the route.
Route's model
hook is invoked.
The returned value from the model
hook is resolved.
Proceed with the transition, including rendering the template and calling its didInsertElement
hook.
The problem is that your model hook is returning a promise from the filter, but at that point, as you've found, there are no records in the store yet, and thus nothing to filter. What I am missing from your question is when and how you do expect the apartments to be loaded from the server. filter
will not do that for you. For our purposes, let's assume you have a routine, returning a promise, called getApartments
, that ensures that the necessary models are loaded from the server. Perhaps it is something as simple as
getApartments: function() {
return this.store.find('apartments');
}
Then replace your model
hook with something like this:
model: function () {
return this.getApartments() .
then(function(model) {
return this.store.filter('apartment', filterfunc);
})
;
}
Now Ember will wait until the promise from getApartments
is resolved, then use the filtered result as its model, and then render, so the element you want should be available for manipulation in your didInsertElement
hook.
However, this will still fail when additional apartments are added to the store later, because didInsertElement
has already run when the element was added. To solve, this problem, I suggest moving your logic for rendering individual apartments into a component. This is probably a good way to factor your code anyway.
components/show-apartment/template.hbs
<tr>
<td>{{content.rating}}</td>
...
<button type="button" class="btn btn-default" aria-label="Left Align" data-toggle="tooltip" data-placement="top" title="Navigate" {{ action 'navigate' content}}>
...
</tr>
Then, in the component's JS, initialize the tooltip in didInsertElement
:
components/show-apartment/component.js
export default Ember.Component.extend({
didInsertElement: function() {
$(this).find('[data-toggle="tooltip"]').tooltip();
}
});
Then in your top-level template, use the new component to display each apartment:
{{#each apartment in model}}
{{show-apartment content=apartment navigate=navigate}}
{{/each}}
Now, each time a new apartment comes into the store, the filtered model will reflect it, the {{#each}}
will generate a new entry, a new component will be created and rendered, and the didInsertElement
logic on that component be run to initialize that particular tooltip.
By the way, your model
hook is calling store.filter
with no filter function. What are you trying to accomplish with that?
Using isLoaded
is a hack and a code smell, not necessary in the land of promises.
Upvotes: 2
Reputation: 1414
If you want something to render only after its model is loaded then I guess you can check for isLoaded
key in the template. Something like this
template.hbs
{{#if model.isLoaded}}
All the HTML stuff that will be shown only if model is loaded.
{{/if}}
Upvotes: 0