Migration logic from SearchView to a controller, where it should be.

This commit is contained in:
Robin Ward 2013-05-23 17:42:57 -04:00
parent 9d0e830786
commit eb0c102931
6 changed files with 124 additions and 143 deletions

View File

@ -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);
}
}
});

View File

@ -46,7 +46,7 @@
</li>
</ul>
{{view Discourse.SearchView currentUserBinding="view.currentUser"}}
{{render search}}
<section class='d-dropdown' id='notifications-dropdown'>
{{#if view.notifications}}

View File

@ -1,20 +1,19 @@
{{textField value=view.term placeholderBinding="view.searchPlaceholder"}}
{{#with view}}
{{textField value=term placeholderKey="search.placeholder"}}
{{#unless loading}}
{{#unless noResults}}
{{#each content}}
{{#each resultType in content}}
<ul>
<li class='heading'>
{{name}}
{{#if more}}
<a href='#' class='filter' {{action moreOfType type target="view" bubbles=false}}>{{i18n show_more}}</a>
{{resultType.name}}
{{#if resultType.more}}
<a href='#' class='filter' {{action moreOfType resultType.type 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 showCancelFilter}}
<a href='#' class='filter' {{action cancelType bubbles=false}}><i class='icon icon-remove-sign'></i></a>
{{/if}}
{{/if}}
</li>
{{view Discourse.SearchResultsTypeView typeBinding="type" contentBinding="results"}}
{{view Discourse.SearchResultsTypeView typeBinding="resultType.type" contentBinding="resultType.results"}}
</ul>
{{/each}}
{{else}}
@ -25,4 +24,3 @@
{{else}}
<div class='searching'><i class='icon-spinner icon-spin'></i></div>
{{/unless}}
{{/with}}

View File

@ -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')
})
});

View File

@ -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;
}
});

View File

@ -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)