Commit 0096c80a by Andy Armstrong Committed by Diana Huang

Refactor course search into openedx/features

parent 77212379
......@@ -2230,6 +2230,7 @@ INSTALLED_APPS = (
# Features
'openedx.features.course_bookmarks',
'openedx.features.course_experience',
'openedx.features.course_search',
'openedx.features.enterprise_support',
)
......
../../openedx/features/course_search/static/course_search
\ No newline at end of file
(function(define) {
define([
'js/search/base/views/search_form'
], function(SearchForm) {
'use strict';
return SearchForm.extend({
el: '#courseware-search-bar'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_item_view'
], function(SearchItemView) {
'use strict';
return SearchItemView.extend({
templateId: '#course_search_item-tpl'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_form'
], function(SearchForm) {
'use strict';
return SearchForm.extend({
el: '#dashboard-search-bar'
});
});
})(define || RequireJS.define);
(function(define) {
define([
'js/search/base/views/search_item_view'
], function(SearchItemView) {
'use strict';
return SearchItemView.extend({
templateId: '#dashboard_search_item-tpl'
});
});
})(define || RequireJS.define);
......@@ -28,6 +28,7 @@ var options = {
sourceFiles: [
{pattern: 'coffee/src/**/!(*spec).js'},
{pattern: 'course_bookmarks/**/!(*spec).js'},
{pattern: 'course_search/**/!(*spec).js'},
{pattern: 'discussion/js/**/!(*spec).js'},
{pattern: 'js/**/!(*spec|djangojs).js'},
{pattern: 'lms/js/**/!(*spec).js'},
......
......@@ -19,6 +19,8 @@
*/
modules: getModulesList([
'course_bookmarks/js/course_bookmarks_factory',
'course_search/js/course_search_factory',
'course_search/js/dashboard_search_factory',
'discussion/js/discussion_board_factory',
'discussion/js/discussion_profile_page_factory',
'js/api_admin/catalog_preview_factory',
......@@ -32,8 +34,6 @@
'js/header_factory',
'js/learner_dashboard/program_details_factory',
'js/learner_dashboard/program_list_factory',
'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory',
'js/student_account/logistration_factory',
'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory',
......
......@@ -676,6 +676,7 @@
'course_bookmarks/js/spec/bookmark_button_view_spec.js',
'course_bookmarks/js/spec/bookmarks_list_view_spec.js',
'course_bookmarks/js/spec/course_bookmarks_factory_spec.js',
'course_search/js/spec/search_spec.js',
'discussion/js/spec/discussion_board_factory_spec.js',
'discussion/js/spec/discussion_profile_page_factory_spec.js',
'discussion/js/spec/discussion_board_view_spec.js',
......@@ -749,7 +750,6 @@
'js/spec/markdown_editor_spec.js',
'js/spec/dateutil_factory_spec.js',
'js/spec/navigation_spec.js',
'js/spec/search/search_spec.js',
'js/spec/shoppingcart/shoppingcart_spec.js',
'js/spec/staff_debug_actions_spec.js',
'js/spec/student_account/access_spec.js',
......
......@@ -37,14 +37,6 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
</script>
% endfor
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
% for template_name in ["course_search_item", "course_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
% endif
% if include_special_exams is not UNDEFINED and include_special_exams:
% for template_name in ["proctored-exam-status"]:
<script type="text/template" id="${template_name}-tpl">
......@@ -81,7 +73,7 @@ from openedx.features.course_experience import course_home_page_title, UNIFIED_C
<%include file="/mathjax_include.html" args="disable_fast_preview=True"/>
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
<%static:require_module module_name="js/search/course/course_search_factory" class_name="CourseSearchFactory">
<%static:require_module module_name="course_search/js/course_search_factory" class_name="CourseSearchFactory">
var courseId = $('.courseware-results').data('courseId');
CourseSearchFactory(courseId);
</%static:require_module>
......
......@@ -28,12 +28,6 @@ from openedx.core.djangolib.markup import HTML, Text
<%static:include path="dashboard/${template_name}.underscore" />
</script>
% endfor
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="js_extra">
......@@ -49,7 +43,7 @@ from openedx.core.djangolib.markup import HTML, Text
});
</script>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
<%static:require_module module_name="course_search/js/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory();
</%static:require_module>
% endif
......
"""
Defines URLs for the course experience.
Defines URLs for course bookmarks.
"""
from django.conf.urls import url
......
(function(define) {
define([
'backbone',
'js/search/base/models/search_result'
], function(Backbone, SearchResult) {
'use strict';
define([
'underscore',
'backbone',
'course_search/js/models/search_result'
], function(_, Backbone, SearchResult) {
return Backbone.Collection.extend({
model: SearchResult,
......@@ -26,7 +27,9 @@
},
performSearch: function(searchTerm) {
this.fetchXhr && this.fetchXhr.abort();
if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.searchTerm = searchTerm || '';
this.resetState();
this.fetchXhr = this.fetch({
......@@ -36,17 +39,19 @@
page_index: 0
},
type: 'POST',
success: function(self, xhr) {
success: function(self) {
self.trigger('search');
},
error: function(self, xhr) {
error: function(self) {
self.trigger('error');
}
});
},
loadNextPage: function() {
this.fetchXhr && this.fetchXhr.abort();
if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.fetchXhr = this.fetch({
data: {
search_string: this.searchTerm,
......@@ -54,11 +59,11 @@
page_index: this.page + 1
},
type: 'POST',
success: function(self, xhr) {
self.page += 1;
success: function(self) {
self.page += 1; // eslint-disable-line no-param-reassign
self.trigger('next');
},
error: function(self, xhr) {
error: function(self) {
self.trigger('error');
},
add: true,
......@@ -68,7 +73,9 @@
},
cancelSearch: function() {
this.fetchXhr && this.fetchXhr.abort();
if (this.fetchXhr) {
this.fetchXhr.abort();
}
this.resetState();
},
......@@ -101,4 +108,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
'use strict';
define(['backbone', 'js/search/base/routers/search_router', 'js/search/course/views/search_form',
'js/search/base/collections/search_collection', 'js/search/course/views/search_results_view'],
function(Backbone, SearchRouter, CourseSearchForm, SearchCollection, SearchResultsView) {
define([
'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
'course_search/js/collections/search_collection', 'course_search/js/views/course_search_results_view'
],
function(_, Backbone, SearchRouter, CourseSearchForm, SearchCollection, CourseSearchResultsView) {
return function(courseId) {
var router = new SearchRouter();
var form = new CourseSearchForm();
var collection = new SearchCollection([], {courseId: courseId});
var results = new SearchResultsView({collection: collection});
var results = new CourseSearchResultsView({collection: collection});
var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(router, 'search', function(query) {
......@@ -44,4 +46,4 @@
});
};
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
'use strict';
define(['backbone', 'js/search/base/routers/search_router', 'js/search/dashboard/views/search_form',
'js/search/base/collections/search_collection', 'js/search/dashboard/views/search_results_view'],
function(Backbone, SearchRouter, SearchForm, SearchCollection, SearchListView) {
define([
'underscore', 'backbone', 'course_search/js/search_router', 'course_search/js/views/search_form',
'course_search/js/collections/search_collection', 'course_search/js/views/dashboard_search_results_view'
],
function(_, Backbone, SearchRouter, SearchForm, SearchCollection, SearchListView) {
return function() {
var router = new SearchRouter();
var form = new SearchForm();
var form = new SearchForm({
el: $('#dashboard-search-bar')
});
var collection = new SearchCollection([]);
var results = new SearchListView({collection: collection});
var dispatcher = _.clone(Backbone.Events);
......@@ -48,4 +52,4 @@
});
};
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
define(['backbone'], function(Backbone) {
'use strict';
define(['backbone'], function(Backbone) {
return Backbone.Model.extend({
defaults: {
location: [],
......@@ -11,4 +11,4 @@
}
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
define(['backbone'], function(Backbone) {
'use strict';
define(['backbone'], function(Backbone) {
return Backbone.Router.extend({
routes: {
'search/:query': 'search'
......@@ -11,4 +11,4 @@
}
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
define([
'js/search/base/views/search_results_view',
'js/search/course/views/search_item_view'
], function(SearchResultsView, CourseSearchItemView) {
'use strict';
define([
'course_search/js/views/search_results_view',
'text!course_search/templates/course_search_results.underscore',
'text!course_search/templates/course_search_item.underscore'
], function(
SearchResultsView,
courseSearchResultsTemplate,
courseSearchItemTemplate
) {
return SearchResultsView.extend({
el: '.courseware-results',
contentElement: '#course-content',
coursewareResultsWrapperElement: '.courseware-results-wrapper',
resultsTemplateId: '#course_search_results-tpl',
loadingTemplateId: '#search_loading-tpl',
errorTemplateId: '#search_error-tpl',
resultsTemplate: courseSearchResultsTemplate,
itemTemplate: courseSearchItemTemplate,
events: {
'click .search-load-next': 'loadNext'
},
SearchItemView: CourseSearchItemView,
clear: function() {
SearchResultsView.prototype.clear.call(this);
......@@ -31,4 +34,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
define([
'js/search/base/views/search_results_view',
'js/search/dashboard/views/search_item_view'
], function(SearchResultsView, DashSearchItemView) {
'use strict';
define([
'course_search/js/views/search_results_view',
'text!course_search/templates/dashboard_search_results.underscore',
'text!course_search/templates/dashboard_search_item.underscore'
], function(
SearchResultsView,
dashboardSearchResultsTemplate,
dashboardSearchItemTemplate
) {
return SearchResultsView.extend({
el: '#dashboard-search-results',
contentElement: '#my-courses, #profile-sidebar',
resultsTemplateId: '#dashboard_search_results-tpl',
loadingTemplateId: '#search_loading-tpl',
errorTemplateId: '#search_error-tpl',
resultsTemplate: dashboardSearchResultsTemplate,
itemTemplate: dashboardSearchItemTemplate,
events: {
'click .search-load-next': 'loadNext',
'click .search-back-to-courses': 'backToCourses'
},
SearchItemView: DashSearchItemView,
backToCourses: function() {
this.clear();
......@@ -26,4 +28,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
define(['jquery', 'backbone'], function($, Backbone) {
'use strict';
define(['jquery', 'backbone'], function($, Backbone) {
return Backbone.View.extend({
el: '',
......@@ -22,19 +22,17 @@
},
doSearch: function(term) {
var trimmed;
if (term) {
this.$searchField.val(term);
}
else {
term = this.$searchField.val();
trimmed = term.trim();
this.$searchField.val(trimmed);
} else {
trimmed = this.$searchField.val().trim();
}
var trimmed = $.trim(term);
if (trimmed) {
this.setActiveStyle();
this.trigger('search', trimmed);
}
else {
} else {
this.clearSearch();
}
},
......@@ -63,4 +61,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
'use strict';
define([
'jquery',
'underscore',
'backbone',
'gettext',
'logger'
], function($, _, Backbone, gettext, Logger) {
'use strict';
'logger',
'edx-ui-toolkit/js/utils/html-utils'
], function($, _, Backbone, gettext, Logger, HtmlUtils) {
return Backbone.View.extend({
tagName: 'li',
templateId: '',
className: 'search-results-item',
attributes: {
'role': 'region',
role: 'region',
'aria-label': 'search result'
},
events: {
'click': 'logSearchItem'
click: 'logSearchItem'
},
initialize: function() {
this.tpl = _.template($(this.templateId).html());
initialize: function(options) {
this.template = options.template;
},
render: function() {
var data = _.clone(this.model.attributes);
// Drop the preview text and result type if the search term is found
// in the title/location in the course hierarchy
if (this.model.get('content_type') === 'Sequence') {
data.excerpt = '';
data.content_type = '';
}
this.$el.html(this.tpl(data));
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.template)(data));
return this;
},
......@@ -47,7 +48,6 @@
},
logSearchItem: function(event) {
event.preventDefault();
var self = this;
var target = this.model.id;
var link = this.model.get('url');
......@@ -56,10 +56,13 @@
var pageSize = collection.pageSize;
var searchTerm = collection.searchTerm;
var index = collection.indexOf(this.model);
event.preventDefault();
Logger.log('edx.course.search.result_selected', {
'search_term': searchTerm,
'result_position': (page * pageSize + index),
'result_link': target
search_term: searchTerm,
result_position: (page * pageSize) + index,
result_link: target
}).always(function() {
self.redirect(link);
});
......@@ -67,4 +70,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
(function(define) {
'use strict';
define([
'jquery',
'underscore',
'backbone',
'gettext'
], function($, _, Backbone, gettext) {
'use strict';
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'course_search/js/views/search_item_view',
'text!course_search/templates/search_loading.underscore',
'text!course_search/templates/search_error.underscore'
], function($, _, Backbone, HtmlUtils, StringUtils, SearchItemView, searchLoadingTemplate, searchErrorTemplate) {
return Backbone.View.extend({
// these should be defined by subclasses
el: '',
contentElement: '',
resultsTemplateId: '',
loadingTemplateId: '',
errorTemplateId: '',
resultsTemplate: null,
itemTemplate: null,
loadingTemplate: searchLoadingTemplate,
errorTemplate: searchErrorTemplate,
events: {},
spinner: '.search-load-next .icon',
SearchItemView: function() {},
initialize: function() {
this.$contentElement = $(this.contentElement);
this.resultsTemplate = _.template($(this.resultsTemplateId).html());
this.loadingTemplate = _.template($(this.loadingTemplateId).html());
this.errorTemplate = _.template($(this.errorTemplateId).html());
},
render: function() {
this.$el.html(this.resultsTemplate({
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.resultsTemplate)({
totalCount: this.collection.totalCount,
totalCountMsg: this.totalCountMsg(),
pageSize: this.collection.pageSize,
......@@ -43,7 +44,7 @@
// total count may have changed
this.$el.find('.search-count').text(this.totalCountMsg());
this.renderItems();
if (! this.collection.hasNextPage()) {
if (!this.collection.hasNextPage()) {
this.$el.find('.search-load-next').remove();
}
this.$el.find(this.spinner).hide();
......@@ -52,15 +53,20 @@
renderItems: function() {
var latest = this.collection.latestModels();
var items = latest.map(function(result) {
var item = new this.SearchItemView({model: result});
var item = new SearchItemView({
model: result,
template: this.itemTemplate
});
return item.render().el;
}, this);
this.$el.find('ol').append(items);
},
totalCountMsg: function() {
var fmt = ngettext('%s result', '%s results', this.collection.totalCount);
return interpolate(fmt, [this.collection.totalCount]);
var fmt = ngettext('{total_results} result', '{total_results} results', this.collection.totalCount);
return StringUtils.interpolate(fmt, {
total_results: this.collection.totalCount
});
},
clear: function() {
......@@ -75,12 +81,12 @@
showLoadingMessage: function() {
this.doCleanup();
this.$el.html(this.loadingTemplate());
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.loadingTemplate)());
this.showResults();
},
showErrorMessage: function() {
this.$el.html(this.errorTemplate());
HtmlUtils.setHtml(this.$el, HtmlUtils.template(this.errorTemplate)());
this.showResults();
},
......@@ -94,7 +100,9 @@
},
loadNext: function(event) {
event && event.preventDefault();
if (event) {
event.preventDefault();
}
this.$el.find(this.spinner).show();
this.trigger('next');
return false;
......@@ -102,4 +110,4 @@
});
});
})(define || RequireJS.define);
}(define || RequireJS.define));
......@@ -29,12 +29,6 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
<%static:include path="dashboard/${template_name}.underscore" />
</script>
% endfor
% for template_name in ["dashboard_search_item", "dashboard_search_results", "search_loading", "search_error"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="search/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="js_extra">
......@@ -50,7 +44,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
});
</script>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
<%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory();
</%static:require_module>
% endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment