title: How to construct a google like search interface to a Solr service using Ember
body: <p>I'm new to Ember, and trying to implement a Google-like search interface to a Solr API. i.e. I would like for the results to update automatically on the page as you type. </p>
  
  <p>My first thought was to create a reusable TextSearch CollectionView, with two child views SearchInput, and SearchResults. </p>
  
  <p>Typing into the SearchInput would trigger an event, the event would be caught by the TextSearch CollectionView, which would update the SearchResult child element. </p>
  
  <p>I got the events generated ok, but the TextSearch ContainerView is not able to intercept the event using either actions, functions, or event manager. I can, however, intercept the event in the IndexController for some reason, but I need to have it handled in a reusable TextSearch CollectionView instead (I think). </p>
  
  <p>Finally, I am not able to update the view in the SearchResults (when I try handling the event in the IndexController), and the SearchResult model is not firing on page load. </p>
  
  <p>I'm quite new at Ember, so I'm sure I'm doing something silly here. It's been a bit of a long road to get to this point.  </p>
  
  <p>Any advice would be greatly appreciated!</p>
  
  <p>Pastebin:
  <a href="http://jsbin.com/ezomOkO/3/edit" rel="nofollow">http://jsbin.com/ezomOkO/3/edit</a></p>
  
  <p>HTML:</p>
  
  <pre><code>&lt;script type="text/x-handlebars"&gt;
    &lt;h2&gt;Welcome to Ember.js&lt;/h2&gt;
    {{outlet}}
  &lt;/script&gt;
  
  &lt;script type="text/x-handlebars" data-template-name="index"&gt;
    {{view App.TextSearchView }}
  &lt;/script&gt;
  
  &lt;script type="text/x-handlebars" data-template-name="textSearch"&gt;
    {{view App.SearchInputView }}
    {{view App.SearchResultsView }}
  &lt;/script&gt;
  
  &lt;script type="text/x-handlebars" data-template-name="searchInput"&gt;
    {{input type="text" value=query size="50"}}
  &lt;/script&gt;  
  
  &lt;script type="text/x-handlebars" data-template-name="searchResults" &gt;
    &lt;ul&gt;
      {{#each concept}}
        &lt;li&gt;{{title}}&lt;/li&gt;
      {{else}}
        Sorry, nobody is here. 
      {{/each}}
    &lt;/ul&gt;
  </code></pre>
  
  <p></p>
  
  <p>Javascript:</p>
  
  <pre><code>App = Ember.Application.create();
  
  App.ApplicationController = Ember.Controller.extend({
    appName: 'Snomed Search'
  });
  
  App.Router.map(function() {
    this.route("index", {path: "/"});
  });
  
  // INDEX
  App.IndexRoute = Ember.Route.extend({
  });
  App.IndexController = Ember.Controller.extend({
  });
  
  //TEXT SEARCH
  App.TextSearchView = Ember.View.extend({
    actions:{
      search: function(search) {
        this.get('controllers.searchResults').set('model', App.TextSearch.find(search));
        return false;
      }
    },
    needs: "searchResults",
    templateName: 'textSearch'
  });
  
  // SEARCH INPUT
  App.SearchInputController  = Ember.Controller.extend({  
    query: 'Family'
  });
  
  App.SearchInputView = Ember.View.extend({
    templateName: 'searchInput',
    keyUp: function(evt) {
      this.get('controller').send('search', this.get('controller.query'));
    }
  });
  
  // SEARCH RESULTS
  App.SearchResultsController  = Ember.Controller.extend({
    model: function(){
      return App.TextSearch.find('Family');
    },
    afterModel: function(posts, transitions){
      alert('model');
    },
    isPublic: true
  });
  
  App.SearchResultsView = Ember.View.extend({
    templateName: 'searchResults'
  });
  
  
  App.SearchResults = Ember.Object.extend({
    total: 0,
    start: 0,
    concepts: Ember.A()
  });
  
  App.Concept = Ember.Object.extend({
    id: null,
    title: null,
    active: null,
    effectiveTime: null
  });
  
  App.TextSearch = Ember.Object.extend({});
  
  App.TextSearch.reopenClass({
    find: function(searchString){
      return Ember.Deferred.promise(function(p) {
        p.resolve($.getJSON("http://solr.sparklingideas.co.uk/solr/concept/select?q=title:" + searchString + "&amp;wt=json&amp;indent=true")
          .then(function(solr) {
              var returned = App.SearchResults.create();
              returned.total = solr.response.numFound;
              returned.start = solr.response.start;
              solr.response.docs.forEach(function (doc) {
                var concept = App.Concept.create();
                concept.id = doc.id;
                concept.title = doc.title;
                concept.active = doc.active;
                concept.effectiveTime= doc.effectiveTime;
                returned.concepts.push(concept);
              });
              return returned;
          }) //then
        );//resolve
      });//deferred promise
    }//find
  });//reopen
  </code></pre>
  
  <p><br/></p>
  
  <h2>Update</h2>
  
  <p>Many thanks for your help, Jeremy, that was all the information I needed. Brilliant. </p>
  
  <p>Here are some of the lessons learned:</p>
  
  <ol>
  <li><p>You have to wait for a JSON promise to resolve before using the results, like this: </p>
  
  <pre><code>search: function(search) {
    var results = App.TextSearch.find(search);
    var _this = this;
    // results is a jquery promise, wait for it to resolve
    // Ember can't resolve it automatically
    results.then(function(results){
      _this.get('controllers.searchResults').set('model', results);
    });
    return false;
  }
  </code></pre></li>
  <li><p>I was not aware of the difference between view, render, and controller (Its not all that well documented, but a google search will tell you). But how do these relate to the 'partial' helper? Not sure.</p></li>
  <li><p>By adding '&amp;json.wrf=?' to the end of a Solr URL, you can avoid the call being blocked in your browser because of XSS security. </p></li>
  <li><p>I got very confused by the ember docuementation as to how to reference the model from a view. This is the correct way (for this example):</p>
  
  <pre><code>&lt;script type="text/x-handlebars" data-template-name="searchResults" &gt;
    Total : {{model.total}}
    &lt;ul&gt;
      {{#each model.concepts}}
        &lt;li&gt;{{title}}&lt;/li&gt;
      {{else}}
        Sorry, nobody is here. 
      {{/each}}
    &lt;/ul&gt;
  &lt;/script&gt;
  </code></pre></li>
  <li><p>My events were not getting caught in the view controllers, because by using {{view}} helper in the template, there are no controllers(!). Use {{render}} helpers instead.</p></li>
  </ol>
  
  <p>Just one minor correction to a comment in the proposed solution, for future readers:</p>
  
  <pre><code>    App.SearchResultsController  = Ember.Controller.extend({
        // This controller could be removed.
        // Ember will auto generate one for you.
      });
  </code></pre>
  
  <p>Actually, removing this controller causes a Javascript error: </p>
  
  <pre><code>Assertion failed: &lt;App.TextSearchController:ember242&gt; needs controller:searchResults but it does not exist 
  </code></pre>
  
  <p>because of the needs attribute of the SearchInputController. In the final example, though, I ended up removing the dependency on the searchResults from the input controller instead.</p>
  
  <p>I believe Ember now requires event handlers to be wrapped in the 'actions: {}' parameter, like this:</p>
  
  <pre><code>    actions:{
        search: function(search) {
          var results = App.TextSearchController.find(search);
          var _this = this;
          // results is a jquery promise, wait for it to resolve
          // Ember can't resolve it automatically
          results.then(function(results){
            _this.get('controllers.searchResults').set('model', results);
          });
          return false;
        }
      }
  </code></pre>
  
  <p><br/>
  <em>PLEASE NOTE:</em> </p>
  
  <p>JSBin uses EmbeJS RC6, which is a couple of months old. The above change to wrapping event handlers in an 'action' attribute was seemingly introduced after RC6. IF you are using the latest version of Ember (as you should), THEN your (eventing) code will NOT work in JSBin (using the default Ember library). However, if you DON'T use the action wrapper, THEN your code will not work in your browser. Hum... See github.com/emberjs/ember.js/releases</p>
  
  <p>In the JSBin example below, I have therefore removed the 'action:' tag from the event handler, so the app will run.</p>
  
  <p>I also made some small design changes: </p>
  
  <ul>
  <li>I moved the search event handler from the InputController to the
  containing TextSearchController instead, in order to decouple the
  InputController from the SearchResultsController.  </li>
  <li>I also removed the
  initial search on page load, so the user starts with a blank slate. </li>
  <li>Finally, I removed the TextSearch Object, and moved the solr
  integration to the TextSearchController instead.</li>
  </ul>
  
  <p>The final version can be found here:
  <a href="http://jsbin.com/ezExeCI/1/" rel="nofollow">http://jsbin.com/ezExeCI/1/</a></p>
  
  <p>Your help is greatly appreciated, Jeremy. I couldn't have done it without you. I'll make sure to pay it forward. </p>
  
  <p>Many thanks!</p>