Refactor course search into openedx/features
This commit is contained in:
committed by
Diana Huang
parent
7721237989
commit
0096c80a13
@@ -2230,6 +2230,7 @@ INSTALLED_APPS = (
|
||||
# Features
|
||||
'openedx.features.course_bookmarks',
|
||||
'openedx.features.course_experience',
|
||||
'openedx.features.course_search',
|
||||
'openedx.features.enterprise_support',
|
||||
)
|
||||
|
||||
|
||||
1
lms/static/course_search
Symbolic link
1
lms/static/course_search
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openedx/features/course_search/static/course_search
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_form'
|
||||
], function(SearchForm) {
|
||||
'use strict';
|
||||
|
||||
return SearchForm.extend({
|
||||
el: '#courseware-search-bar'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,11 +0,0 @@
|
||||
(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);
|
||||
@@ -1,11 +0,0 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_form'
|
||||
], function(SearchForm) {
|
||||
'use strict';
|
||||
|
||||
return SearchForm.extend({
|
||||
el: '#dashboard-search-bar'
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
@@ -1,11 +0,0 @@
|
||||
(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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Defines URLs for the course experience.
|
||||
Defines URLs for course bookmarks.
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'backbone',
|
||||
'js/search/base/models/search_result'
|
||||
], function(Backbone, SearchResult) {
|
||||
'use strict';
|
||||
'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));
|
||||
@@ -1,14 +1,16 @@
|
||||
(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));
|
||||
@@ -1,12 +1,16 @@
|
||||
(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));
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(define) {
|
||||
define(['backbone'], function(Backbone) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define(['backbone'], function(Backbone) {
|
||||
return Backbone.Model.extend({
|
||||
defaults: {
|
||||
location: [],
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(define) {
|
||||
define(['backbone'], function(Backbone) {
|
||||
'use strict';
|
||||
'use strict';
|
||||
|
||||
define(['backbone'], function(Backbone) {
|
||||
return Backbone.Router.extend({
|
||||
routes: {
|
||||
'search/:query': 'search'
|
||||
@@ -11,4 +11,4 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
})(define || RequireJS.define);
|
||||
}(define || RequireJS.define));
|
||||
@@ -5,17 +5,16 @@ define([
|
||||
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
|
||||
'common/js/spec_helpers/page_helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/search/base/models/search_result',
|
||||
'js/search/base/collections/search_collection',
|
||||
'js/search/base/routers/search_router',
|
||||
'js/search/course/views/search_item_view',
|
||||
'js/search/dashboard/views/search_item_view',
|
||||
'js/search/course/views/search_form',
|
||||
'js/search/dashboard/views/search_form',
|
||||
'js/search/course/views/search_results_view',
|
||||
'js/search/dashboard/views/search_results_view',
|
||||
'js/search/course/course_search_factory',
|
||||
'js/search/dashboard/dashboard_search_factory'
|
||||
'course_search/js/models/search_result',
|
||||
'course_search/js/collections/search_collection',
|
||||
'course_search/js/search_router',
|
||||
'course_search/js/views/search_form',
|
||||
'course_search/js/views/search_item_view',
|
||||
'course_search/js/views/course_search_results_view',
|
||||
'course_search/js/views/dashboard_search_results_view',
|
||||
'course_search/js/course_search_factory',
|
||||
'course_search/js/dashboard_search_factory',
|
||||
'text!course_search/templates/course_search_item.underscore'
|
||||
], function(
|
||||
$,
|
||||
Backbone,
|
||||
@@ -26,14 +25,13 @@ define([
|
||||
SearchResult,
|
||||
SearchCollection,
|
||||
SearchRouter,
|
||||
CourseSearchItemView,
|
||||
DashSearchItemView,
|
||||
CourseSearchForm,
|
||||
DashSearchForm,
|
||||
SearchForm,
|
||||
SearchItemView,
|
||||
CourseSearchResultsView,
|
||||
DashSearchResultsView,
|
||||
CourseSearchFactory,
|
||||
DashboardSearchFactory
|
||||
DashboardSearchFactory,
|
||||
courseSearchItemTemplate
|
||||
) {
|
||||
'use strict';
|
||||
|
||||
@@ -86,7 +84,6 @@ define([
|
||||
|
||||
it('sends a request and parses the json result', function() {
|
||||
var requests = AjaxHelpers.requests(this);
|
||||
this.collection.performSearch('search string');
|
||||
var response = {
|
||||
total: 2,
|
||||
access_denied_count: 1,
|
||||
@@ -99,6 +96,7 @@ define([
|
||||
}
|
||||
}]
|
||||
};
|
||||
this.collection.performSearch('search string');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
|
||||
expect(this.onSearch).toHaveBeenCalled();
|
||||
@@ -221,12 +219,7 @@ define([
|
||||
|
||||
|
||||
describe('SearchItemView', function() {
|
||||
function beforeEachHelper(SearchItemView) {
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item'
|
||||
]);
|
||||
|
||||
beforeEach(function() {
|
||||
this.model = new SearchResult({
|
||||
location: ['section', 'subsection', 'unit'],
|
||||
content_type: 'Video',
|
||||
@@ -243,31 +236,37 @@ define([
|
||||
url: 'path/to/content'
|
||||
});
|
||||
|
||||
this.item = new SearchItemView({model: this.model});
|
||||
this.item = new SearchItemView({
|
||||
model: this.model,
|
||||
template: courseSearchItemTemplate
|
||||
});
|
||||
this.item.render();
|
||||
this.seqItem = new SearchItemView({model: this.seqModel});
|
||||
this.seqItem = new SearchItemView({
|
||||
model: this.seqModel,
|
||||
template: courseSearchItemTemplate
|
||||
});
|
||||
this.seqItem.render();
|
||||
}
|
||||
});
|
||||
|
||||
function rendersItem() {
|
||||
it('rendersItem', function() {
|
||||
expect(this.item.$el).toHaveAttr('role', 'region');
|
||||
expect(this.item.$el).toHaveAttr('aria-label', 'search result');
|
||||
expect(this.item.$el).toContainElement('a[href="' + this.model.get('url') + '"]');
|
||||
expect(this.item.$el.find('.result-type')).toContainHtml(this.model.get('content_type'));
|
||||
expect(this.item.$el.find('.result-excerpt')).toContainHtml(this.model.get('excerpt'));
|
||||
expect(this.item.$el.find('.result-location')).toContainHtml('section ▸ subsection ▸ unit');
|
||||
}
|
||||
});
|
||||
|
||||
function rendersSequentialItem() {
|
||||
it('rendersSequentialItem', function() {
|
||||
expect(this.seqItem.$el).toHaveAttr('role', 'region');
|
||||
expect(this.seqItem.$el).toHaveAttr('aria-label', 'search result');
|
||||
expect(this.seqItem.$el).toContainElement('a[href="' + this.seqModel.get('url') + '"]');
|
||||
expect(this.seqItem.$el.find('.result-type')).toBeEmpty();
|
||||
expect(this.seqItem.$el.find('.result-excerpt')).toBeEmpty();
|
||||
expect(this.seqItem.$el.find('.result-location')).toContainHtml('section ▸ subsection');
|
||||
}
|
||||
});
|
||||
|
||||
function logsSearchItemViewEvent() {
|
||||
it('logsSearchItemViewEvent', function() {
|
||||
this.model.collection = new SearchCollection([this.model], {course_id: 'edx101'});
|
||||
this.item.render();
|
||||
// Mock the redirect call
|
||||
@@ -277,27 +276,6 @@ define([
|
||||
expect(this.item.redirect).toHaveBeenCalled();
|
||||
this.item.$el.trigger('click');
|
||||
expect(this.item.redirect).toHaveBeenCalled();
|
||||
}
|
||||
|
||||
describe('CourseSearchItemView', function() {
|
||||
beforeEach(function() {
|
||||
beforeEachHelper.call(this, CourseSearchItemView);
|
||||
});
|
||||
it('renders items correctly', rendersItem);
|
||||
it('renders Sequence items correctly', rendersSequentialItem);
|
||||
it('logs view event', logsSearchItemViewEvent);
|
||||
});
|
||||
|
||||
describe('DashSearchItemView', function() {
|
||||
beforeEach(function() {
|
||||
beforeEachHelper.call(this, DashSearchItemView);
|
||||
});
|
||||
it('renders items correctly', rendersItem);
|
||||
it('renders Sequence items correctly', rendersSequentialItem);
|
||||
it('displays course name in breadcrumbs', function() {
|
||||
expect(this.seqItem.$el.find('.result-course-name')).toContainHtml(this.model.get('course_name'));
|
||||
});
|
||||
it('logs view event', logsSearchItemViewEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -315,7 +293,7 @@ define([
|
||||
$('.search-field').val(term);
|
||||
this.form.doSearch(term);
|
||||
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
|
||||
expect($('.search-field').val()).toEqual(term);
|
||||
expect($('.search-field').val()).toEqual(term.trim());
|
||||
expect($('.search-field')).toHaveClass('is-active');
|
||||
expect($('.search-button')).toBeHidden();
|
||||
expect($('.cancel-button')).toBeVisible();
|
||||
@@ -350,26 +328,12 @@ define([
|
||||
expect($('.search-button')).toBeVisible();
|
||||
}
|
||||
|
||||
describe('CourseSearchForm', function() {
|
||||
describe('SearchForm', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/course_search_form.html');
|
||||
this.form = new CourseSearchForm();
|
||||
this.onClear = jasmine.createSpy('onClear');
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('clear', this.onClear);
|
||||
this.form.on('search', this.onSearch);
|
||||
});
|
||||
it('trims input string', trimsInputString);
|
||||
it('handles calls to doSearch', doesSearch);
|
||||
it('triggers a search event and changes to active state', triggersSearchEvent);
|
||||
it('clears search when clicking on cancel button', clearsSearchOnCancel);
|
||||
it('clears search when search box is empty', clearsSearchOnEmpty);
|
||||
});
|
||||
|
||||
describe('DashSearchForm', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/dashboard_search_form.html');
|
||||
this.form = new DashSearchForm();
|
||||
loadFixtures('course_search/fixtures/course_search_form.html');
|
||||
this.form = new SearchForm({
|
||||
el: '#courseware-search-bar'
|
||||
});
|
||||
this.onClear = jasmine.createSpy('onClear');
|
||||
this.onSearch = jasmine.createSpy('onSearch');
|
||||
this.form.on('clear', this.onClear);
|
||||
@@ -401,7 +365,9 @@ define([
|
||||
|
||||
function returnsToContent() {
|
||||
this.resultsView.clear();
|
||||
expect(this.resultsView.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.resultsView.$contentElement).toHaveCss({
|
||||
display: this.contentElementDisplayValue
|
||||
});
|
||||
expect(this.resultsView.$el).toBeHidden();
|
||||
expect(this.resultsView.$el).toBeEmpty();
|
||||
}
|
||||
@@ -467,28 +433,14 @@ define([
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
|
||||
this.resultsView.loadNext();
|
||||
// toBeVisible does not work with inline
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({'display': 'inline'});
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toHaveCss({
|
||||
display: 'inline'
|
||||
});
|
||||
this.resultsView.renderNext();
|
||||
expect(this.resultsView.$el.find('a.search-load-next .icon')).toBeHidden();
|
||||
}
|
||||
|
||||
function beforeEachHelper(SearchResultsView) {
|
||||
appendSetFixtures(
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>' +
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses" tabindex="-1"></section>'
|
||||
);
|
||||
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item',
|
||||
'templates/search/course_search_results',
|
||||
'templates/search/dashboard_search_results',
|
||||
'templates/search/search_loading',
|
||||
'templates/search/search_error'
|
||||
]);
|
||||
|
||||
var MockCollection = Backbone.Collection.extend({
|
||||
hasNextPage: function() {},
|
||||
latestModelsCount: 0,
|
||||
@@ -497,6 +449,14 @@ define([
|
||||
return SearchCollection.prototype.latestModels.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
appendSetFixtures(
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>' +
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses" tabindex="-1"></section>'
|
||||
);
|
||||
|
||||
this.collection = new MockCollection();
|
||||
this.resultsView = new SearchResultsView({collection: this.collection});
|
||||
}
|
||||
@@ -599,13 +559,13 @@ define([
|
||||
$('.cancel-button').trigger('click');
|
||||
AjaxHelpers.skipResetRequest(requests);
|
||||
// there should be no results
|
||||
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
|
||||
expect(this.$searchResults).toBeHidden();
|
||||
}
|
||||
|
||||
function clearsResults() {
|
||||
$('.cancel-button').trigger('click');
|
||||
expect(this.$contentElement).toHaveCss({'display': this.contentElementDisplayValue});
|
||||
expect(this.$contentElement).toHaveCss({display: this.contentElementDisplayValue});
|
||||
expect(this.$searchResults).toBeHidden();
|
||||
}
|
||||
|
||||
@@ -624,13 +584,14 @@ define([
|
||||
}
|
||||
}]
|
||||
};
|
||||
var body;
|
||||
$('.search-field').val('query');
|
||||
$('.search-button').trigger('click');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
expect(this.$searchResults.find('li').length).toEqual(1);
|
||||
expect($('.search-load-next')).toBeVisible();
|
||||
$('.search-load-next').trigger('click');
|
||||
var body = requests[1].requestBody;
|
||||
body = requests[1].requestBody;
|
||||
expect(body).toContain('search_string=query');
|
||||
expect(body).toContain('page_index=1');
|
||||
AjaxHelpers.respondWithJson(requests, response);
|
||||
@@ -644,27 +605,14 @@ define([
|
||||
expect(requests[0].requestBody).toContain('search_string=query');
|
||||
}
|
||||
|
||||
function loadTemplates() {
|
||||
TemplateHelpers.installTemplates([
|
||||
'templates/search/course_search_item',
|
||||
'templates/search/dashboard_search_item',
|
||||
'templates/search/search_loading',
|
||||
'templates/search/search_error',
|
||||
'templates/search/course_search_results',
|
||||
'templates/search/dashboard_search_results'
|
||||
]);
|
||||
}
|
||||
|
||||
describe('CourseSearchApp', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/course_search_form.html');
|
||||
var courseId = 'a/b/c';
|
||||
loadFixtures('course_search/fixtures/course_search_form.html');
|
||||
appendSetFixtures(
|
||||
'<div class="courseware-results"></div>' +
|
||||
'<section id="course-content"></section>'
|
||||
);
|
||||
loadTemplates.call(this);
|
||||
|
||||
var courseId = 'a/b/c';
|
||||
CourseSearchFactory(courseId);
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
this.$contentElement = $('#course-content');
|
||||
@@ -688,12 +636,11 @@ define([
|
||||
|
||||
describe('DashSearchApp', function() {
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/search/dashboard_search_form.html');
|
||||
loadFixtures('course_search/fixtures/dashboard_search_form.html');
|
||||
appendSetFixtures(
|
||||
'<section id="dashboard-search-results"></section>' +
|
||||
'<section id="my-courses" tabindex="-1"></section>'
|
||||
);
|
||||
loadTemplates.call(this);
|
||||
DashboardSearchFactory();
|
||||
|
||||
spyOn(Backbone.history, 'navigate');
|
||||
@@ -1,22 +1,25 @@
|
||||
(function(define) {
|
||||
define([
|
||||
'js/search/base/views/search_results_view',
|
||||
'js/search/course/views/search_item_view'
|
||||
], function(SearchResultsView, CourseSearchItemView) {
|
||||
'use strict';
|
||||
'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));
|
||||
@@ -1,22 +1,24 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'js/search/base/views/search_results_view',
|
||||
'js/search/dashboard/views/search_item_view'
|
||||
], function(SearchResultsView, DashSearchItemView) {
|
||||
'use strict';
|
||||
|
||||
'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));
|
||||
@@ -1,7 +1,7 @@
|
||||
(function(define) {
|
||||
define(['jquery', 'backbone'], function($, Backbone) {
|
||||
'use strict';
|
||||
'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);
|
||||
trimmed = term.trim();
|
||||
this.$searchField.val(trimmed);
|
||||
} else {
|
||||
trimmed = this.$searchField.val().trim();
|
||||
}
|
||||
else {
|
||||
term = this.$searchField.val();
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -1,40 +1,41 @@
|
||||
(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
|
||||
|
||||
// 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));
|
||||
@@ -1,33 +1,34 @@
|
||||
(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
|
||||
// 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,
|
||||
@@ -40,10 +41,10 @@
|
||||
},
|
||||
|
||||
renderNext: function() {
|
||||
// total count may have changed
|
||||
// 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,26 +81,28 @@
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
doCleanup: function() {
|
||||
// Empty any loading/error message and empty the el
|
||||
// Bookmarks share the same container element, So we are doing
|
||||
// this to ensure that elements are in clean/initial state
|
||||
// Empty any loading/error message and empty the el
|
||||
// Bookmarks share the same container element, So we are doing
|
||||
// this to ensure that elements are in clean/initial state
|
||||
$('#loading-message').html('');
|
||||
$('#error-message').html('');
|
||||
this.$el.html('');
|
||||
},
|
||||
|
||||
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));
|
||||
0
openedx/features/course_search/views/__init__.py
Normal file
0
openedx/features/course_search/views/__init__.py
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user