Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent re-rendering Ember view if model data hasn't changed

I'm trying to create an auto-complete feature, which grabs movies from the Trakt API and renders the top 3 results onto the page. I get new data every time the "query" text (from the input) is changed. However, Ember re-renders the page even when those 3 models stay the same. How do I prevent this? Is there a way to add some sort of check to see whether the movie.title and movie.year is different within the Handlebars template? Or a check within the controller that will only set a new model if it is different?

Example:

If I type "how" as my query, the page will render:

  • How to Train Your Dragon (2010)
  • How the Grinch Stole Christmas (2000)
  • How the Grinch Stole Christmas! (1966)

If I add a 't', so that my query becomes "how t", the page will re-render the same content, creating an unnecessary flash of the data being gone and the same data reappearing again:

  • How to Train Your Dragon (2010)
  • How the Grinch Stole Christmas (2000)
  • How the Grinch Stole Christmas! (1966)

Code:

HTML

<script type="text/x-handlebars" data-template-name="application">
    <div class="hero-unit">
    <h1>App</h1>
    {{outlet}}
    </div>
</script>
<script type="text/x-handlebars" data-template-name="index">
    {{input value=query}}
    <div class="prompt">
      {{prompt}}
    </div>

    {{#if moviesAvailable}}
      {{#each movie in movies}}
        <li>{{movie.title}} ({{movie.year}})</li>
      {{/each}}
    {{/if}}
</script>

Javascript (a change in "query" in the IndexController triggers a change in "query" in the MovieController, which prompts it to set a new model)

var App;

App = Ember.Application.create();

App.Movie = DS.Model.extend({
  title: DS.attr('string'),
  year: DS.attr('number'),
  fanart: DS.attr('string'),
  poster: DS.attr('string'),
  overview: DS.attr('string')
});

App.IndexController = Ember.ArrayController.extend({
  needs: "movies",
  movies: Ember.computed.alias("controllers.movies"),
  query: '',
  prompt: '',
  moviesAvailable: false,
  queryChanged: (function() {
    var length;
    length = this.get('query').length;
    if (length === 0) {
      this.set('moviesAvailable', false);
      return this.set('prompt', 'Please type two more characters.');
    } else if (length === 1) {
      this.set('moviesAvailable', false);
      return this.set('prompt', 'Please type one more character.');
    } else {
      this.set('moviesAvailable', true);
      this.set('prompt', '');
      return this.get('movies').set('query', this.get('query'));
    }
  }).observes('query')
});

App.MoviesController = Ember.ArrayController.extend({
  queryChanged: (function() {
    var newmodel;
    newmodel = this.store.find('movie', {
      query: this.get('query'),
      limit: 3
    });
    return this.set('model', newmodel);
  }).observes('query')
});


App.MovieAdapter = DS.RESTAdapter.extend({
  host: 'http://api.trakt.tv/search/movies.json',
  namespace: '5aa5a25cfc06d869c136725b2b29d645',
  ajaxOptions: function(url, type, hash) {
    hash = this._super(url, type, hash);
    hash.dataType = 'jsonp';
    return hash;
  }
});

App.MovieSerializer = DS.RESTSerializer.extend({
  normalizeHash: {
    movie: function(hash) {
      hash.id = hash.tmdb_id;
      hash.fanart = hash.images.fanart;
      hash.poster = hash.images.poster;
      delete hash.images;
      return hash;
    }
  },
  normalizePayload: function(payload) {
    var json;
    json = {
      "movie": payload
    };
    return json;
  }
});
like image 763
Jacquerie Avatar asked Feb 06 '26 10:02

Jacquerie


1 Answers

The problem is they are similar results, but not the same collection.

['a', 'b', 'c'] === ['a', 'b', 'c'] is false. They are two different collections that may contain the same values, and appear similar, but they are different instances etc.

Example: http://emberjs.jsbin.com/OxIDiVU/698/edit

If you wanted, you could roll your own collection, and manually update the records which would avoid the entire collection disappearing and reappearing when they are the same items. It's still going to destroy when they are different though.

  watchInput: function(){
    //model here is just an []
    var model = this.get('model');

    this.store.find('color', {
      limit:10
    }).then(function(results){
      var len = results.get('length'),
          curItem,
          newItem;

      for(var i = 0;i<len;i++){
        curItem = model.objectAt(i);
        newItem = results.objectAt(i);
        if(curItem!=newItem){
          if(!curItem){
            model.pushObject(newItem);
          } else{
            model.replace(i,1,[newItem]);
          }

        }
      }
      // maybe remove additional items if it only returns 1, or 2 or even 0 for that matter.
    })
  }.observes('stuff'),

Example: http://emberjs.jsbin.com/OxIDiVU/697/edit

Also, just for the fun of it, depending on how fast your server is etc, you might want to debounce the change request to alleviate a million requests when the user is typing t, th, the...

watchInput: function(){
    Ember.run.debounce(this, this.slowFetch, 400);
  }.observes('stuff'),

http://emberjs.jsbin.com/OxIDiVU/721/edit

P.S. try lowering the debounce rate for fun, to see how often it fires when you type

like image 56
Kingpin2k Avatar answered Feb 09 '26 00:02

Kingpin2k



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!