1
votes

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:

  1. Ember renders the view, requests the model
  2. Model returns a promise but view is rendered anyhow
  3. After AJAX is finished model is updated and view is re rendered

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?

3
As far as I know Ember will wait until the promise to resolve before render template. Are you sure tooltip exists within the ApartmentsView and not in any childViews. If it does exist in childView, you need to wait until childViews finished rendering, or schedule initialization code within Ember.run.scheduleOnce('afterRender', this, function() { // Yet more things });code-jaff
hi jaff, thanks for the response, I've added the view code, I'm with you I also thought Ember will wait, but it doesn't, so either I'm doing something wrong, or I've found a bug :/ I've added the view template, is it considered child view? also tried adding your code in the view inside the didInsertElement, but same results. its as if the DS returns an empty array for the model so the view can render and updates it async, maybe I'm missing something regarding the DS..Nir Cohen

3 Answers

2
votes

Your description of the lifecycle is incorrect. It should be:

  1. Ember enters the route.

  2. Route's model hook is invoked.

  3. The returned value from the model hook is resolved.

  4. 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.

0
votes

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}}
0
votes

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

DEMO