Convert webpackified factories to es6 modules with global exports, and remove the invoke_page_factory pattern

This commit is contained in:
Calen Pennington
2018-03-22 16:14:51 -04:00
parent 12a082cf83
commit ee6360543b
28 changed files with 219 additions and 303 deletions

View File

@@ -26,14 +26,12 @@
'js/factories/group_configurations',
'js/certificates/factories/certificates_page_factory',
'js/factories/index',
'js/factories/library',
'js/factories/manage_users',
'js/factories/outline',
'js/factories/register',
'js/factories/settings',
'js/factories/settings_advanced',
'js/factories/settings_graders',
'js/factories/textbooks',
'js/factories/videos_index',
]),
/**

View File

@@ -1,3 +1,5 @@
// We can't convert this to an es6 module until all factories that use it have been converted out
// of RequireJS
define(['js/base', 'cms/js/main', 'js/src/logger', 'datepair', 'accessibility',
'ieshim', 'tooltip_manager', 'lang_edx', 'js/models/course'],
function() {

View File

@@ -1,21 +1,26 @@
define([
'jquery', 'underscore', 'js/models/xblock_container_info', 'js/views/pages/container',
'js/collections/component_template', 'xmodule', 'cms/js/main',
'xblock/cms.runtime.v1'
],
function($, _, XBlockContainerInfo, ContainerPage, ComponentTemplates, xmoduleLoader) {
'use strict';
return function(componentTemplates, XBlockInfoJson, action, options) {
var main_options = {
el: $('#content'),
model: new XBlockContainerInfo(XBlockInfoJson, {parse: true}),
action: action,
templates: new ComponentTemplates(componentTemplates, {parse: true})
};
import * as $ from 'jquery';
import * as _ from 'underscore';
import * as XBlockContainerInfo from 'js/models/xblock_container_info';
import * as ContainerPage from 'js/views/pages/container';
import * as ComponentTemplates from 'js/collections/component_template';
import * as xmoduleLoader from 'xmodule';
import './base';
import 'cms/js/main';
import 'xblock/cms.runtime.v1';
xmoduleLoader.done(function() {
var view = new ContainerPage(_.extend(main_options, options));
view.render();
});
'use strict';
export default function ContainerFactory(componentTemplates, XBlockInfoJson, action, options) {
var main_options = {
el: $('#content'),
model: new XBlockContainerInfo(XBlockInfoJson, {parse: true}),
action: action,
templates: new ComponentTemplates(componentTemplates, {parse: true})
};
});
xmoduleLoader.done(function() {
var view = new ContainerPage(_.extend(main_options, options));
view.render();
});
};
export {ContainerFactory}

View File

@@ -0,0 +1,3 @@
import * as ContextCourse from 'js/models/course';
export {ContextCourse}

View File

@@ -1,23 +1,28 @@
define([
'jquery', 'underscore', 'js/models/xblock_info', 'js/views/pages/paged_container',
'js/views/library_container', 'js/collections/component_template', 'xmodule', 'cms/js/main',
'xblock/cms.runtime.v1'
],
function($, _, XBlockInfo, PagedContainerPage, LibraryContainerView, ComponentTemplates, xmoduleLoader) {
'use strict';
return function(componentTemplates, XBlockInfoJson, options) {
var main_options = {
el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
templates: new ComponentTemplates(componentTemplates, {parse: true}),
action: 'view',
viewClass: LibraryContainerView,
canEdit: true
};
import * as $ from 'jquery';
import * as _ from 'underscore';
import * as XBlockInfo from 'js/models/xblock_info';
import * as PagedContainerPage from 'js/views/pages/paged_container';
import * as LibraryContainerView from 'js/views/library_container';
import * as ComponentTemplates from 'js/collections/component_template';
import * as xmoduleLoader from 'xmodule';
import 'cms/js/main';
import 'xblock/cms.runtime.v1';
xmoduleLoader.done(function() {
var view = new PagedContainerPage(_.extend(main_options, options));
view.render();
});
'use strict';
export default function LibraryFactory(componentTemplates, XBlockInfoJson, options) {
var main_options = {
el: $('#content'),
model: new XBlockInfo(XBlockInfoJson, {parse: true}),
templates: new ComponentTemplates(componentTemplates, {parse: true}),
action: 'view',
viewClass: LibraryContainerView,
canEdit: true
};
});
xmoduleLoader.done(function() {
var view = new PagedContainerPage(_.extend(main_options, options));
view.render();
});
};
export {LibraryFactory}

View File

@@ -1,57 +1,62 @@
define(['jquery.cookie', 'utility', 'common/js/components/utils/view_utils'], function(cookie, utility, ViewUtils) {
'use strict';
return function LoginFactory(homepageURL) {
function postJSON(url, data, callback) {
$.ajax({
type: 'POST',
url: url,
dataType: 'json',
data: data,
success: callback
});
}
import * as cookie from 'jquery.cookie';
import * as utility from 'utility';
import * as ViewUtils from 'common/js/components/utils/view_utils';
// Clear the login error message when credentials are edited
$('input#email').on('input', function() {
$('#login_error').removeClass('is-shown');
'use strict';
export default function LoginFactory(homepageURL) {
function postJSON(url, data, callback) {
$.ajax({
type: 'POST',
url: url,
dataType: 'json',
data: data,
success: callback
});
}
$('input#password').on('input', function() {
$('#login_error').removeClass('is-shown');
});
// Clear the login error message when credentials are edited
$('input#email').on('input', function() {
$('#login_error').removeClass('is-shown');
});
$('form#login_form').submit(function(event) {
event.preventDefault();
var $submitButton = $('#submit'),
deferred = new $.Deferred(),
promise = deferred.promise();
ViewUtils.disableElementWhileRunning($submitButton, function() { return promise; });
var submit_data = $('#login_form').serialize();
$('input#password').on('input', function() {
$('#login_error').removeClass('is-shown');
});
postJSON('/login_post', submit_data, function(json) {
if (json.success) {
var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1 && !isExternal(next[1])) {
ViewUtils.redirect(next[1]);
} else {
ViewUtils.redirect(homepageURL);
}
} else if ($('#login_error').length === 0) {
$('#login_form').prepend(
'<div id="login_error" class="message message-status error">' +
json.value +
'</span></div>'
);
$('#login_error').addClass('is-shown');
deferred.resolve();
$('form#login_form').submit(function(event) {
event.preventDefault();
var $submitButton = $('#submit'),
deferred = new $.Deferred(),
promise = deferred.promise();
ViewUtils.disableElementWhileRunning($submitButton, function() { return promise; });
var submit_data = $('#login_form').serialize();
postJSON('/login_post', submit_data, function(json) {
if (json.success) {
var next = /next=([^&]*)/g.exec(decodeURIComponent(window.location.search));
if (next && next.length > 1 && !isExternal(next[1])) {
ViewUtils.redirect(next[1]);
} else {
$('#login_error')
.stop()
.addClass('is-shown')
.html(json.value);
deferred.resolve();
ViewUtils.redirect(homepageURL);
}
});
} else if ($('#login_error').length === 0) {
$('#login_form').prepend(
'<div id="login_error" class="message message-status error">' +
json.value +
'</span></div>'
);
$('#login_error').addClass('is-shown');
deferred.resolve();
} else {
$('#login_error')
.stop()
.addClass('is-shown')
.html(json.value);
deferred.resolve();
}
});
};
});
});
};
export {LoginFactory}

View File

@@ -1,20 +1,23 @@
define([
'gettext', 'js/models/section', 'js/collections/textbook', 'js/views/list_textbooks'
], function(gettext, Section, TextbookCollection, ListTextbooksView) {
'use strict';
return function(textbooksJson) {
var textbooks = new TextbookCollection(textbooksJson, {parse: true}),
tbView = new ListTextbooksView({collection: textbooks});
import * as gettext from 'gettext';
import * as Section from 'js/models/section';
import * as TextbookCollection from 'js/collections/textbook';
import * as ListTextbooksView from 'js/views/list_textbooks';
$('.content-primary').append(tbView.render().el);
$('.nav-actions .new-button').click(function(event) {
tbView.addOne(event);
});
$(window).on('beforeunload', function() {
var dirty = textbooks.find(function(textbook) { return textbook.isDirty(); });
if (dirty) {
return gettext('You have unsaved changes. Do you really want to leave this page?');
}
});
};
});
'use strict';
export default function TextbooksFactory(textbooksJson) {
var textbooks = new TextbookCollection(textbooksJson, {parse: true}),
tbView = new ListTextbooksView({collection: textbooks});
$('.content-primary').append(tbView.render().el);
$('.nav-actions .new-button').click(function(event) {
tbView.addOne(event);
});
$(window).on('beforeunload', function() {
var dirty = textbooks.find(function(textbook) { return textbook.isDirty(); });
if (dirty) {
return gettext('You have unsaved changes. Do you really want to leave this page?');
}
});
};
export {TextbooksFactory}

View File

@@ -1,19 +1,22 @@
define(['js/views/xblock_validation', 'js/models/xblock_validation'],
function(XBlockValidationView, XBlockValidationModel) {
'use strict';
return function(validationMessages, hasEditingUrl, isRoot, isUnit, validationEle) {
var model, response;
if (hasEditingUrl && !isRoot) {
validationMessages.showSummaryOnly = true;
}
response = validationMessages;
response.isUnit = isUnit;
import * as XBlockValidationView from 'js/views/xblock_validation';
import * as XBlockValidationModel from 'js/models/xblock_validation';
model = new XBlockValidationModel(response, {parse: true});
'use strict';
export default function XBlockValidationFactory(validationMessages, hasEditingUrl, isRoot, isUnit, validationEle) {
var model, response;
if (!model.get('empty')) {
new XBlockValidationView({el: validationEle, model: model, root: isRoot}).render();
}
};
});
if (hasEditingUrl && !isRoot) {
validationMessages.showSummaryOnly = true;
}
response = validationMessages;
response.isUnit = isUnit;
model = new XBlockValidationModel(response, {parse: true});
if (!model.get('empty')) {
new XBlockValidationView({el: validationEle, model: model, root: isRoot}).render();
}
};
export {XBlockValidationFactory}

View File

@@ -1,8 +0,0 @@
define(
['js/factories/container', 'common/js/utils/page_factory', 'js/factories/base', 'js/pages/course'],
function(ContainerFactory, invokePageFactory) {
'use strict';
invokePageFactory('ContainerFactory', ContainerFactory);
}
);

View File

@@ -1,8 +0,0 @@
define(
['js/models/course'],
function(ContextCourse) {
'use strict';
window.course = new ContextCourse(window.pageFactoryArguments.ContextCourse[0]);
}
);

View File

@@ -1,8 +0,0 @@
define(
['js/factories/library', 'common/js/utils/page_factory', 'js/factories/base'],
function(LibraryFactory, invokePageFactory) {
'use strict';
invokePageFactory('LibraryFactory', LibraryFactory);
}
);

View File

@@ -1,8 +0,0 @@
define(
['js/factories/login', 'common/js/utils/page_factory', 'js/factories/base'],
function(LoginFactory, invokePageFactory) {
'use strict';
invokePageFactory('LoginFactory', LoginFactory);
}
);

View File

@@ -1,7 +0,0 @@
define(
['js/factories/textbooks', 'common/js/utils/page_factory', 'js/factories/base', 'js/pages/course'],
function(TextbooksFactory, invokePageFactory) {
'use strict';
invokePageFactory('TextbooksFactory', TextbooksFactory);
}
);

View File

@@ -1,8 +0,0 @@
define(
['js/factories/xblock_validation', 'common/js/utils/page_factory'],
function(XBlockValidationFactory, invokePageFactory) {
'use strict';
invokePageFactory('XBlockValidationFactory', XBlockValidationFactory);
}
);

View File

@@ -1,39 +1,39 @@
define(['domReady', 'jquery', 'jquery.smoothScroll'],
function(domReady, $) {
'use strict';
import * as domReady from 'domReady';
import * as $ from 'jquery';
import 'jquery.smoothScroll';
var toggleSock = function(e) {
e.preventDefault();
'use strict';
var $btnShowSockLabel = $(this).find('.copy-show');
var $btnHideSockLabel = $(this).find('.copy-hide');
var $sock = $('.wrapper-sock');
var $sockContent = $sock.find('.wrapper-inner');
var toggleSock = function(e) {
e.preventDefault();
if ($sock.hasClass('is-shown')) {
$sock.removeClass('is-shown');
$sockContent.hide('fast');
$btnHideSockLabel.removeClass('is-shown').addClass('is-hidden');
$btnShowSockLabel.removeClass('is-hidden').addClass('is-shown');
} else {
$sock.addClass('is-shown');
$sockContent.show('fast');
$btnHideSockLabel.removeClass('is-hidden').addClass('is-shown');
$btnShowSockLabel.removeClass('is-shown').addClass('is-hidden');
}
var $btnShowSockLabel = $(this).find('.copy-show');
var $btnHideSockLabel = $(this).find('.copy-hide');
var $sock = $('.wrapper-sock');
var $sockContent = $sock.find('.wrapper-inner');
$.smoothScroll({
offset: -200,
easing: 'swing',
speed: 1000,
scrollElement: null,
scrollTarget: $sock
});
};
domReady(function() {
// toggling footer additional support
$('.cta-show-sock').bind('click', toggleSock);
});
if ($sock.hasClass('is-shown')) {
$sock.removeClass('is-shown');
$sockContent.hide('fast');
$btnHideSockLabel.removeClass('is-shown').addClass('is-hidden');
$btnShowSockLabel.removeClass('is-hidden').addClass('is-shown');
} else {
$sock.addClass('is-shown');
$sockContent.show('fast');
$btnHideSockLabel.removeClass('is-hidden').addClass('is-shown');
$btnShowSockLabel.removeClass('is-shown').addClass('is-hidden');
}
);
$.smoothScroll({
offset: -200,
easing: 'swing',
speed: 1000,
scrollElement: null,
scrollTarget: $sock
});
};
domReady(function() {
// toggling footer additional support
$('.cta-show-sock').bind('click', toggleSock);
});

View File

@@ -125,12 +125,9 @@ from openedx.core.release import RELEASE_LINE
<%block name="jsextra"></%block>
% if context_course:
<%static:webpack entry="js/factories/context_course"/>
<script type="text/javascript">
if (typeof window.pageFactoryArguments == "undefined") {
window.pageFactoryArguments = {};
}
window.pageFactoryArguments['ContextCourse'] = {
window.course = new ContextCourse({
id: "${context_course.id | n, js_escaped_string}",
name: "${context_course.display_name_with_default | n, js_escaped_string}",
url_name: "${context_course.location.block_id | n, js_escaped_string}",
@@ -139,21 +136,16 @@ from openedx.core.release import RELEASE_LINE
display_course_number: "${context_course.display_coursenumber | n, js_escaped_string}",
revision: "${context_course.location.branch | n, js_escaped_string}",
self_paced: ${ context_course.self_paced | n, dump_js_escaped_json }
}
});
</script>
% endif
% if user.is_authenticated:
<%static:invoke_page_bundle page_name='js/sock'/>
<%static:webpack entry='js/sock'/>
% endif
<%block name='page_bundle'>
<script type="text/javascript">
require(['js/factories/base'], function () {
require(['js/models/course'], function(Course) {
% if context_course:
window.course = new Course(window.pageFactoryArguments['ContextCourse']);
% endif
<%block name='requirejs'></%block>
});
<%block name='requirejs'></%block>
});
</script>
</%block>

View File

@@ -55,7 +55,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
</%block>
<%block name="page_bundle">
<%static:invoke_page_bundle page_name="js/pages/login" class_name="LoginFactory">
"${reverse('homepage') | n, js_escaped_string}"
</%static:invoke_page_bundle>
<%static:webpack entry="js/factories/login">
LoginFactory("${reverse('homepage') | n, js_escaped_string}");
</%static:webpack>
</%block>

View File

@@ -27,12 +27,14 @@ block_is_unit = is_unit(xblock)
</script>
</%block>
<%static:webpack page_name="js/pages/xblock_validation" class_name="XBlockValidationFactory">
${messages | n, dump_js_escaped_json},
${bool(xblock_url) | n, dump_js_escaped_json}, // xblock_url will be None or a string
${bool(is_root) | n, dump_js_escaped_json}, // is_root will be None or a boolean
${bool(block_is_unit) | n, dump_js_escaped_json}, // block_is_unit will be None or a boolean
$('div.xblock-validation-messages[data-locator="${xblock.location | n, js_escaped_string}"]')
<%static:webpack entry="js/factories/xblock_validation">
XBlockValidationFactory(
${messages | n, dump_js_escaped_json},
${bool(xblock_url) | n, dump_js_escaped_json}, // xblock_url will be None or a string
${bool(is_root) | n, dump_js_escaped_json}, // is_root will be None or a boolean
${bool(block_is_unit) | n, dump_js_escaped_json}, // block_is_unit will be None or a boolean
$('div.xblock-validation-messages[data-locator="${xblock.location | n, js_escaped_string}"]')
);
</%static:webpack>
% if isinstance(xblock, (XModule, XModuleDescriptor)):

View File

@@ -28,9 +28,9 @@ CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
</%block>
<%block name="page_bundle">
<%static:invoke_page_bundle page_name="js/pages/textbooks" class_name="TextbooksFactory">
${textbooks | n, dump_js_escaped_json}
</%static:invoke_page_bundle>
<%static:webpack entry="js/factories/textbooks">
TextbooksFactory(${textbooks | n, dump_js_escaped_json});
</%static:webpack>
</%block>
<%block name="content">

View File

@@ -164,31 +164,6 @@ source, template_path = Loader(engine).load_template_source(path)
</script>
</%def>
<%def name="invoke_page_bundle(page_name, class_name=None)">
<%doc>
Loads Javascript onto your page synchronously.
Uses RequireJS in development and a plain script tag in production.
The body of the tag should be a comma-separated list of arguments
to be passed to the page factory specified by the class_name argument.
</%doc>
<%
body = capture(caller.body)
%>
% if class_name:
<script type="text/javascript">
if (typeof pageFactoryArguments == "undefined") {
var pageFactoryArguments = {};
}
% if body:
pageFactoryArguments['${class_name | n, js_escaped_string}'] = [${ body | n, decode.utf8 }];
% else:
pageFactoryArguments['${class_name | n, js_escaped_string}'] = [];
% endif
</script>
% endif
<%self:webpack entry="${page_name}"/>
</%def>
<%def name="require_module(module_name, class_name)">
<%doc>
Loads Javascript onto your page synchronously.

View File

@@ -48,7 +48,7 @@ REQUIREJS_WAIT = {
# Dashboard
re.compile(r'^Studio Home \|'): [
"js/sock", "gettext", "js/base",
"gettext", "js/base",
"jquery.ui", "cms/js/main", "underscore"],
# Pages
@@ -66,12 +66,10 @@ def wait(seconds):
@world.absorb
def wait_for_js_to_load():
requirements = None
for test, req in REQUIREJS_WAIT.items():
if test.search(world.browser.title):
requirements = req
world.wait_for_requirejs(req)
break
world.wait_for_requirejs(requirements)
# Selenium's `execute_async_script` function pauses Selenium's execution

View File

@@ -1,27 +0,0 @@
define([], function() {
'use strict';
return function invokePageFactory(name, factory) {
var args;
if (typeof window.pageFactoryArguments === 'undefined') {
throw Error(
'window.pageFactoryArguments must be initialized before calling invokePageFactory(' +
name +
'). Use the <%static:invoke_page_bundle> template tag.'
);
}
args = window.pageFactoryArguments[name];
if (typeof args === 'undefined') {
throw Error(
'window.pageFactoryArguments["' +
name +
'"] must be initialized before calling invokePageFactory(' +
name +
'). Use the <%static:invoke_page_bundle> template tag.'
);
}
factory.apply(null, window.pageFactoryArguments[name]);
};
});

View File

@@ -1,7 +1,7 @@
(function() {
'use strict';
XBlock.Runtime.v1 = (function() {
this.XBlock.Runtime.v1 = (function() {
function v1() {
var _this = this;
this.childMap = function() {

View File

@@ -53,7 +53,7 @@ from openedx.core.djangolib.js_utils import js_escaped_string
</%block>
<%block name="page_bundle">
<%static:invoke_page_bundle page_name="js/pages/login" class_name="LoginFactory">
"${reverse('homepage') | n, js_escaped_string}"
</%static:invoke_page_bundle>
<%static:webpack entry="js/factories/login">
LoginFactory("${reverse('homepage') | n, js_escaped_string}");
</%static:webpack>
</%block>

2
package-lock.json generated
View File

@@ -8219,7 +8219,7 @@
"redux": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
"integrity": "sha1-BrcxIyFZAdJdBlvjQusCa8HIU3s=",
"requires": {
"lodash": "4.17.5",
"lodash-es": "4.17.6",

View File

@@ -1370,8 +1370,6 @@ class MakoTemplateLinter(BaseLinter):
</script> | # script tag end
<%static:require_module(_async)?.*?> | # require js script tag start (optionally the _async version)
</%static:require_module(_async)?> | # require js script tag end (optionally the _async version)
<%static:invoke_page_bundle.*?> | # require js script tag start
</%static:invoke_page_bundle> | # require js script tag end
<%static:webpack.*?> | # webpack script tag start
</%static:webpack> | # webpack script tag end
<%static:studiofrontend.*?> | # studiofrontend script tag start

View File

@@ -52,7 +52,7 @@ from django.utils.translation import ugettext as _
</%block>
<%block name="page_bundle">
<%static:invoke_page_bundle page_name="js/pages/login" class_name="LoginFactory">
"${reverse('homepage') | n, js_escaped_string}"
</%static:invoke_page_bundle>
<%static:webpack entry="js/factories/login">
LoginFactory("${reverse('homepage') | n, js_escaped_string}");
</%static:webpack>
</%block>

View File

@@ -30,11 +30,12 @@ module.exports = {
// Studio
Import: './cms/static/js/features/import/factories/import.js',
CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx',
'js/pages/login': './cms/static/js/pages/login.js',
'js/pages/textbooks': './cms/static/js/pages/textbooks.js',
'js/pages/container': './cms/static/js/pages/container.js',
'js/pages/library': './cms/static/js/pages/library.js',
'js/pages/xblock_validation': './cms/static/js/pages/xblock_validation.js',
'js/factories/login': './cms/static/js/factories/login.js',
'js/factories/textbooks': './cms/static/js/factories/textbooks.js',
'js/factories/container': './cms/static/js/factories/container.js',
'js/factories/context_course': './cms/static/js/factories/context_course.js',
'js/factories/library': './cms/static/js/factories/library.js',
'js/factories/xblock_validation': './cms/static/js/factories/xblock_validation.js',
'js/sock': './cms/static/js/sock.js',
// LMS