From eb0c10293171894f6c7bf811dfca724399254ecd Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Thu, 23 May 2013 17:42:57 -0400
Subject: [PATCH] Migration logic from SearchView to a controller, where it
 should be.

---
 .../controllers/search_controller.js          |  89 ++++++++++++++
 .../discourse/templates/header.js.handlebars  |   2 +-
 .../discourse/templates/search.js.handlebars  |  48 ++++----
 .../views/search/search_results_type_view.js  |  13 +-
 .../discourse/views/search/search_view.js     | 114 +-----------------
 lib/search.rb                                 |   1 -
 6 files changed, 124 insertions(+), 143 deletions(-)
 create mode 100644 app/assets/javascripts/discourse/controllers/search_controller.js

diff --git a/app/assets/javascripts/discourse/controllers/search_controller.js b/app/assets/javascripts/discourse/controllers/search_controller.js
new file mode 100644
index 000000000..5f9fe23b1
--- /dev/null
+++ b/app/assets/javascripts/discourse/controllers/search_controller.js
@@ -0,0 +1,89 @@
+/**
+  Support for searching
+
+  @class SearchController
+  @extends Discourse.Controller
+  @namespace Discourse
+  @module Discourse
+**/
+Discourse.SearchController = Em.ArrayController.extend(Discourse.Presence, {
+
+  // If we need to perform another search
+  newSearchNeeded: function() {
+    this.set('noResults', false);
+    var term = this.get('term');
+    if (term && term.length >= Discourse.SiteSettings.min_search_term_length) {
+      this.set('loading', true);
+      this.searchTerm(term, this.get('typeFilter'));
+    } else {
+      this.set('content', Em.A());
+    }
+    this.set('selectedIndex', 0);
+  }.observes('term', 'typeFilter'),
+
+  searchTerm: Discourse.debouncePromise(function(term, typeFilter) {
+    var searchController = this;
+    this.set('count', 0);
+    return Discourse.Search.forTerm(term, typeFilter).then(function(results) {
+      searchController.set('results', results);
+      if (results) {
+        searchController.set('noResults', results.length === 0);
+
+        // Make it easy to find the results by type
+        var results_hashed = {};
+        results.forEach(function(r) { results_hashed[r.type] = r });
+
+        // Default order
+        var order = ['topic', 'category', 'user'];
+        results = order.map(function(o) { return results_hashed[o] }).without(void 0);
+
+        var index = 0;
+        results.forEach(function(r) {
+          r.results.each(function(item) {
+            item.index = index++;
+          });
+        });
+        searchController.set('count', index);
+        searchController.set('content', results);
+      }
+
+      searchController.set('loading', false);
+    });
+  }, 300),
+
+  showCancelFilter: function() {
+    if (this.get('loading')) return false;
+    return this.present('typeFilter');
+  }.property('typeFilter', 'loading'),
+
+  termChanged: function() {
+    this.cancelType();
+  }.observes('term'),
+
+  moreOfType: function(type) {
+    this.set('typeFilter', type);
+  },
+
+  cancelType: function() {
+    this.set('typeFilter', null);
+  },
+
+  moveUp: function() {
+    if (this.get('selectedIndex') === 0) return;
+    this.set('selectedIndex', this.get('selectedIndex') - 1);
+  },
+
+  moveDown: function() {
+    if (this.get('resultCount') === (this.get('selectedIndex') + 1)) return;
+    this.set('selectedIndex', this.get('selectedIndex') + 1);
+  },
+
+  select: function() {
+    if (this.get('loading')) return;
+    var href = $('#search-dropdown li.selected a').prop('href');
+    if (href) {
+      Discourse.URL.routeTo(href);
+    }
+  }
+
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/templates/header.js.handlebars b/app/assets/javascripts/discourse/templates/header.js.handlebars
index 515ba31b7..02f31ce43 100644
--- a/app/assets/javascripts/discourse/templates/header.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/header.js.handlebars
@@ -46,7 +46,7 @@
         </li>
       </ul>
 
-      {{view Discourse.SearchView currentUserBinding="view.currentUser"}}
+      {{render search}}
 
       <section class='d-dropdown' id='notifications-dropdown'>
         {{#if view.notifications}}
diff --git a/app/assets/javascripts/discourse/templates/search.js.handlebars b/app/assets/javascripts/discourse/templates/search.js.handlebars
index 73dbefa3c..17ce55b86 100644
--- a/app/assets/javascripts/discourse/templates/search.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/search.js.handlebars
@@ -1,28 +1,26 @@
-{{textField value=view.term placeholderBinding="view.searchPlaceholder"}}
-{{#with view}}
-  {{#unless loading}}
-    {{#unless noResults}}
-      {{#each content}}
-        <ul>
-          <li class='heading'>
-            {{name}}
-            {{#if more}}
-              <a href='#' class='filter' {{action moreOfType type target="view" bubbles=false}}>{{i18n show_more}}</a>
-            {{else}}
-              {{#if view.showCancelFilter}}
-                <a href='#' class='filter' {{action cancelType target="view" bubbles=false}}><i class='icon icon-remove-sign'></i></a>
-              {{/if}}
+{{textField value=term placeholderKey="search.placeholder"}}
+{{#unless loading}}
+  {{#unless noResults}}
+    {{#each resultType in content}}
+      <ul>
+        <li class='heading'>
+          {{resultType.name}}
+          {{#if resultType.more}}
+            <a href='#' class='filter' {{action moreOfType resultType.type bubbles=false}}>{{i18n show_more}}</a>
+          {{else}}
+            {{#if showCancelFilter}}
+              <a href='#' class='filter' {{action cancelType bubbles=false}}><i class='icon icon-remove-sign'></i></a>
             {{/if}}
-          </li>
-          {{view Discourse.SearchResultsTypeView typeBinding="type" contentBinding="results"}}
-        </ul>
-      {{/each}}
-    {{else}}
-      <div class='no-results'>
-        {{i18n search.no_results}}
-      </div>
-    {{/unless}}
+          {{/if}}
+        </li>
+        {{view Discourse.SearchResultsTypeView typeBinding="resultType.type" contentBinding="resultType.results"}}
+      </ul>
+    {{/each}}
   {{else}}
-  <div class='searching'><i class='icon-spinner icon-spin'></i></div>
+    <div class='no-results'>
+      {{i18n search.no_results}}
+    </div>
   {{/unless}}
-{{/with}}
+{{else}}
+<div class='searching'><i class='icon-spinner icon-spin'></i></div>
+{{/unless}}
diff --git a/app/assets/javascripts/discourse/views/search/search_results_type_view.js b/app/assets/javascripts/discourse/views/search/search_results_type_view.js
index 9118744a8..d6db3b50b 100644
--- a/app/assets/javascripts/discourse/views/search/search_results_type_view.js
+++ b/app/assets/javascripts/discourse/views/search/search_results_type_view.js
@@ -10,18 +10,17 @@ Discourse.SearchResultsTypeView = Ember.CollectionView.extend({
   tagName: 'ul',
   itemViewClass: Ember.View.extend({
     tagName: 'li',
-    classNameBindings: ['selectedClass', 'parentView.type'],
-    selectedIndexBinding: 'parentView.parentView.selectedIndex',
+    classNameBindings: ['selectedClass'],
 
-    templateName: (function() {
+    templateName: function() {
       return "search/" + (this.get('parentView.type')) + "_result";
-    }).property('parentView.type'),
+    }.property('parentView.type'),
 
     // Is this row currently selected by the keyboard?
-    selectedClass: (function() {
-      if (this.get('content.index') === this.get('selectedIndex')) return 'selected';
+    selectedClass: function() {
+      if (this.get('content.index') === this.get('controller.selectedIndex')) return 'selected';
       return null;
-    }).property('selectedIndex')
+    }.property('controller.selectedIndex')
 
   })
 });
diff --git a/app/assets/javascripts/discourse/views/search/search_view.js b/app/assets/javascripts/discourse/views/search/search_view.js
index 44ed5350f..f03f87671 100644
--- a/app/assets/javascripts/discourse/views/search/search_view.js
+++ b/app/assets/javascripts/discourse/views/search/search_view.js
@@ -14,125 +14,21 @@ Discourse.SearchView = Discourse.View.extend({
 
   didInsertElement: function() {
     // Delegate ESC to the composer
-    var _this = this;
+    var controller = this.get('controller');
     return $('body').on('keydown.search', function(e) {
       if ($('#search-dropdown').is(':visible')) {
         switch (e.which) {
           case 13:
-            return _this.select();
+            return controller.select();
           case 38:
-            return _this.moveUp();
+            return controller.moveUp();
           case 40:
-            return _this.moveDown();
+            return controller.moveDown();
         }
       }
     });
-  },
-
-  searchPlaceholder: function() {
-    return Em.String.i18n("search.placeholder");
-  }.property(),
-
-  // If we need to perform another search
-  newSearchNeeded: function() {
-    this.set('noResults', false);
-    var term = this.get('term');
-    if (term && term.length >= Discourse.SiteSettings.min_search_term_length) {
-      this.set('loading', true);
-      this.searchTerm(term, this.get('typeFilter'));
-    } else {
-      this.set('results', null);
-    }
-    return this.set('selectedIndex', 0);
-  }.observes('term', 'typeFilter'),
-
-  searchTerm: Discourse.debouncePromise(function(term, typeFilter) {
-    var searchView = this;
-    return Discourse.Search.forTerm(term, typeFilter).then(function(results) {
-      searchView.set('results', results);
-    });
-  }, 300),
-
-  showCancelFilter: function() {
-    if (this.get('loading')) return false;
-    return this.present('typeFilter');
-  }.property('typeFilter', 'loading'),
-
-  termChanged: function() {
-    return this.cancelType();
-  }.observes('term'),
-
-  // We can re-order them based on the context
-  content: function() {
-    var index, order, path, results, results_hashed;
-    if (results = this.get('results')) {
-      // Make it easy to find the results by type
-      results_hashed = {};
-      results.each(function(r) {
-        results_hashed[r.type] = r;
-      });
-      path = Discourse.get('router.currentState.path');
-      // Default order
-      order = ['topic', 'category', 'user'];
-      results = (order.map(function(o) {
-        return results_hashed[o];
-      })).without(void 0);
-      index = 0;
-      results.each(function(result) {
-        return result.results.each(function(item) {
-          item.index = index++;
-        });
-      });
-    }
-    return results;
-  }.property('results'),
-
-  updateProgress: function() {
-    var results;
-    if (results = this.get('results')) {
-      this.set('noResults', results.length === 0);
-    }
-    return this.set('loading', false);
-  }.observes('results'),
-
-  resultCount: function() {
-    var count;
-    if (this.blank('content')) return 0;
-    count = 0;
-    this.get('content').each(function(result) {
-      count += result.results.length;
-    });
-    return count;
-  }.property('content'),
-
-  moreOfType: function(type) {
-    this.set('typeFilter', type);
-    return false;
-  },
-
-  cancelType: function() {
-    this.set('typeFilter', null);
-    return false;
-  },
-
-  moveUp: function() {
-    if (this.get('selectedIndex') === 0) return;
-    return this.set('selectedIndex', this.get('selectedIndex') - 1);
-  },
-
-  moveDown: function() {
-    if (this.get('resultCount') === (this.get('selectedIndex') + 1)) return;
-    return this.set('selectedIndex', this.get('selectedIndex') + 1);
-  },
-
-  select: function() {
-    if (this.get('loading')) return;
-    var href = $('#search-dropdown li.selected a').prop('href');
-    if (href) {
-      Discourse.URL.routeTo(href);
-    }
-    return false;
   }
+
 });
 
 
diff --git a/lib/search.rb b/lib/search.rb
index bc9da30f0..660233f23 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -28,7 +28,6 @@ class Search
   end
 
   def initialize(term, opts=nil)
-
     if term.present?
       @term = term.to_s
       @original_term = PG::Connection.escape_string(@term)