Commit e5928bac by Christina Roberts

Merge pull request #8894 from edx/andya/lms-requirejs

Implement RequireJS Optimizer in the LMS
parents fbe07845 e9e5b5bd
""" """
Settings for Bok Choy tests that are used for running CMS and LMS. Settings for Bok Choy tests that are used when running Studio.
Bok Choy uses two different settings files: Bok Choy uses two different settings files:
1. test_static_optimized is used when invoking collectstatic 1. test_static_optimized is used when invoking collectstatic
2. bok_choy is used when running CMS and LMS 2. bok_choy is used when running the tests
Note: it isn't possible to have a single settings file, because Django doesn't Note: it isn't possible to have a single settings file, because Django doesn't
support both generating static assets to a directory and also serving static support both generating static assets to a directory and also serving static
......
...@@ -455,7 +455,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' ...@@ -455,7 +455,7 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
EMBARGO_SITE_REDIRECT_URL = None EMBARGO_SITE_REDIRECT_URL = None
############################### Pipeline ####################################### ############################### Pipeline #######################################
STATICFILES_STORAGE = 'cms.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage' STATICFILES_STORAGE = 'openedx.core.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage'
from openedx.core.lib.rooted_paths import rooted_glob from openedx.core.lib.rooted_paths import rooted_glob
...@@ -573,7 +573,7 @@ REQUIRE_BASE_URL = "./" ...@@ -573,7 +573,7 @@ REQUIRE_BASE_URL = "./"
# A sensible value would be 'app.build.js'. Leave blank to use the built-in default build profile. # A sensible value would be 'app.build.js'. Leave blank to use the built-in default build profile.
# Set to False to disable running the default profile (e.g. if only using it to build Standalone # Set to False to disable running the default profile (e.g. if only using it to build Standalone
# Modules) # Modules)
REQUIRE_BUILD_PROFILE = "build.js" REQUIRE_BUILD_PROFILE = "cms/js/build.js"
# The name of the require.js script used by your project, relative to REQUIRE_BASE_URL. # The name of the require.js script used by your project, relative to REQUIRE_BASE_URL.
REQUIRE_JS = "js/vendor/require.js" REQUIRE_JS = "js/vendor/require.js"
...@@ -592,6 +592,8 @@ REQUIRE_EXCLUDE = ("build.txt",) ...@@ -592,6 +592,8 @@ REQUIRE_EXCLUDE = ("build.txt",)
# It can also be a path to a custom class that subclasses require.environments.Environment and defines some "args" function that returns a list with the command arguments to execute. # It can also be a path to a custom class that subclasses require.environments.Environment and defines some "args" function that returns a list with the command arguments to execute.
REQUIRE_ENVIRONMENT = "node" REQUIRE_ENVIRONMENT = "node"
################################# TENDER ######################################
# If you want to enable Tender integration (http://tenderapp.com/), # If you want to enable Tender integration (http://tenderapp.com/),
# put in the subdomain where Tender hosts tender_widget.js. For example, # put in the subdomain where Tender hosts tender_widget.js. For example,
# if you want to use the URL https://example.tenderapp.com/tender_widget.js, # if you want to use the URL https://example.tenderapp.com/tender_widget.js,
......
...@@ -29,6 +29,9 @@ TEST_ROOT = REPO_ROOT / "test_root" # pylint: disable=no-value-for-parameter ...@@ -29,6 +29,9 @@ TEST_ROOT = REPO_ROOT / "test_root" # pylint: disable=no-value-for-parameter
# Enable debug so that static assets are served by Django # Enable debug so that static assets are served by Django
DEBUG = True DEBUG = True
# Set REQUIRE_DEBUG to false so that it behaves like production
REQUIRE_DEBUG = False
# Serve static files at /static directly from the staticfiles directory under test root. # Serve static files at /static directly from the staticfiles directory under test root.
# Note: optimized files for testing are generated with settings from test_static_optimized # Note: optimized files for testing are generated with settings from test_static_optimized
STATIC_URL = "/static/" STATIC_URL = "/static/"
......
...@@ -33,3 +33,4 @@ STATIC_ROOT = (TEST_ROOT / "staticfiles" / "cms").abspath() ...@@ -33,3 +33,4 @@ STATIC_ROOT = (TEST_ROOT / "staticfiles" / "cms").abspath()
# 1. Uglify is by far the slowest part of the build process # 1. Uglify is by far the slowest part of the build process
# 2. Having full source code makes debugging tests easier for developers # 2. Having full source code makes debugging tests easier for developers
os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none' os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none'
PIPELINE_JS_COMPRESSOR = None
(function () { (function () {
'use strict'; 'use strict';
var commonLibrariesPath = 'common/js/common_libraries';
var getModule = function (moduleName, excludeCommonDeps) { var getModule = function (moduleName, excludeCommonDeps) {
var module = { var module = {
name: moduleName name: moduleName
}; };
if (excludeCommonDeps) { if (excludeCommonDeps) {
module.exclude = ['js/factories/common_deps']; module.exclude = [commonLibrariesPath];
} }
return module; return module;
}; };
var getModulesList = function (modules) { var getModulesList = function (modules) {
var result = [getModule('js/factories/common_deps')]; var result = [getModule(commonLibrariesPath)];
return result.concat(modules.map(function (moduleName) { return result.concat(modules.map(function (moduleName) {
return getModule(moduleName, true); return getModule(moduleName, true);
})); }));
...@@ -84,6 +86,17 @@ ...@@ -84,6 +86,17 @@
'tender': 'empty:', 'tender': 'empty:',
'youtube': 'empty:' 'youtube': 'empty:'
}, },
/**
* Inline requireJS text templates.
*/
inlineText: true,
/**
* Stub out requireJS text in the optimized file, but leave available for non-optimized development use.
*/
stubModules: ["text"],
/** /**
* If shim config is used in the app during runtime, duplicate the config * If shim config is used in the app during runtime, duplicate the config
* here. Necessary if shim config is used, so that the shim's dependencies * here. Necessary if shim config is used, so that the shim's dependencies
......
define(['domReady!', 'jquery', 'backbone', 'underscore', 'gettext', 'text']);
...@@ -36,7 +36,7 @@ import json ...@@ -36,7 +36,7 @@ import json
</head> </head>
<body class="${static.dir_rtl()} <%block name='bodyclass'></%block> lang_${LANGUAGE_CODE}"> <body class="${static.dir_rtl()} <%block name='bodyclass'></%block> lang_${LANGUAGE_CODE}">
<%block name="view_notes"></%block> <%block name="view_notes"></%block>
<a class="nav-skip" href="#content">${_("Skip to main content")}</a> <a class="nav-skip" href="#content">${_("Skip to main content")}</a>
...@@ -46,7 +46,7 @@ import json ...@@ -46,7 +46,7 @@ import json
var require = {baseUrl: window.baseUrl}; var require = {baseUrl: window.baseUrl};
</script> </script>
<script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script> <script type="text/javascript" src="${static.url("js/vendor/require.js")}"></script>
<script type="text/javascript" src="${static.url("require-config.js")}"></script> <script type="text/javascript" src="${static.url("cms/js/require-config.js")}"></script>
## js templates ## js templates
<script id="system-feedback-tpl" type="text/template"> <script id="system-feedback-tpl" type="text/template">
...@@ -81,7 +81,7 @@ import json ...@@ -81,7 +81,7 @@ import json
<%block name="jsextra"></%block> <%block name="jsextra"></%block>
<script type="text/javascript"> <script type="text/javascript">
require(['js/factories/common_deps'], function () { require(['common/js/common_libraries'], function () {
require(['js/factories/base'], function () { require(['js/factories/base'], function () {
% if context_course: % if context_course:
require(['js/factories/course'], function(CourseFactory) { require(['js/factories/course'], function(CourseFactory) {
......
...@@ -51,3 +51,25 @@ except: ...@@ -51,3 +51,25 @@ except:
from django.template.loaders.filesystem import _loader from django.template.loaders.filesystem import _loader
source, template_path = _loader.load_template_source(path) source, template_path = _loader.load_template_source(path)
%>${source}</%def> %>${source}</%def>
<%def name="require_module(module_name, class_name)">
<script type="text/javascript">
(function (require) {
% if settings.REQUIRE_DEBUG:
require(['${module_name}'], function (${class_name}) {
${caller.body()}
});
% else:
## The "raw" parameter is specified to avoid the URL from being further maninpulated by
## static_replace calls (as woudl happen if require_module is used within courseware).
## Without specifying "raw", a call to static_replace would result in the MD5 hash being
## being appended more than once, causing the import to fail in production environments.
require(['${staticfiles_storage.url(module_name + ".js") + "?raw"}'], function () {
require(['${module_name}'], function (${class_name}) {
${caller.body()}
});
});
% endif
}).call(this, require || RequireJS.require);
</script>
</%def>
define(['domReady!', 'jquery', 'backbone', 'underscore', 'gettext']);
!function(e,t){"use strict";var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return"";var n="";while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?"\\s":e.source?e.source:"["+p.escapeRegExp(e)+"]"},f={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u="",a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u==="string")f.push(r[l]);else if(u==="array"){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h('[_.sprintf] property "%s" does not exist',p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!="number")throw new Error(h("[_.sprintf] expecting number but found %s",e(a)));switch(p[8]){case"b":a=a.toString(2);break;case"c":a=t.fromCharCode(a);break;case"d":a=parseInt(a,10);break;case"e":a=p[7]?a.toExponential(p[7]):a.toExponential();break;case"f":a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case"o":a=a.toString(8);break;case"s":a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case"u":a=Math.abs(a);break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?"+"+a:a,v=p[4]?p[4]=="0"?"0":p[4].charAt(1):" ",m=p[6]-t(a).length,d=p[6]?n(v,m):"",f.push(p[5]?a+d:d+a)}}return f.join("")},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error("[_.sprintf] huh?");if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:"2.3.0",isBlank:function(e){return e==null&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return e==null?"":t(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=e==null?"":t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(".{1,"+n+"}","g")):[e])},clean:function(e){return p.strip(e).replace(/\s+/g," ")},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split("")},swapCase:function(e){return e==null?"":t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?"":t(e).replace(/[&<>"']/g,function(e){return"&"+l[e]+";"})},unescapeHTML:function(e){return e==null?"":t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?"":t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join("")},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===""?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=""),e.join(t)},lines:function(e){return e==null?[]:t(e).split("\n")},reverse:function(e){return p.chars(e).reverse().join("")},startsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?"":(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?"":t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g," ")).replace(/\s/g,"")},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return e==null?"":!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,n){return e==null?"":!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp("^"+n+"+"),""))},rtrim:function(e,n){return e==null?"":!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+"+$"),""))},truncate:function(e,n,r){return e==null?"":(e=t(e),r=r||"...",n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return"";e=t(e),n=~~n,r=r!=null?t(r):"...";if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?"":t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=" ";switch(i){case"right":return s=n-e.length,e+o(r,s);case"both":return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,"right")},lrpad:function(e,t,n){return p.pad(e,t,n,"both")},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e=="")return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return"";e=e.toFixed(~~t),r=r||",";var i=e.split("."),s=i[0],o=i[1]?(n||".")+i[1]:"";return s.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+r)+o},strRight:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return"";e+="",t=t!=null?""+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||", ",n=n||" and ";var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return"";var n="ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",r="aaaaaaaaceeeeeiiiilnoooooouuuunczz",i=new RegExp(a(n),"g");return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||"-"}),p.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,t){return[t,e,t].join("")},quote:function(e){return p.surround(e,'"')},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return"";n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[--n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(module.exports=p),exports._s=p):typeof define=="function"&&define.amd?define("underscore.string",[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String); !function(e,t){"use strict";var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return"";var n="";while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?"\\s":e.source?e.source:"["+p.escapeRegExp(e)+"]"},f={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u="",a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u==="string")f.push(r[l]);else if(u==="array"){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h('[_.sprintf] property "%s" does not exist',p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!="number")throw new Error(h("[_.sprintf] expecting number but found %s",e(a)));switch(p[8]){case"b":a=a.toString(2);break;case"c":a=t.fromCharCode(a);break;case"d":a=parseInt(a,10);break;case"e":a=p[7]?a.toExponential(p[7]):a.toExponential();break;case"f":a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case"o":a=a.toString(8);break;case"s":a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case"u":a=Math.abs(a);break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?"+"+a:a,v=p[4]?p[4]=="0"?"0":p[4].charAt(1):" ",m=p[6]-t(a).length,d=p[6]?n(v,m):"",f.push(p[5]?a+d:d+a)}}return f.join("")},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error("[_.sprintf] huh?");if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:"2.3.0",isBlank:function(e){return e==null&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return e==null?"":t(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=e==null?"":t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(".{1,"+n+"}","g")):[e])},clean:function(e){return p.strip(e).replace(/\s+/g," ")},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split("")},swapCase:function(e){return e==null?"":t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?"":t(e).replace(/[&<>"']/g,function(e){return"&"+l[e]+";"})},unescapeHTML:function(e){return e==null?"":t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?"":t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join("")},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===""?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=""),e.join(t)},lines:function(e){return e==null?[]:t(e).split("\n")},reverse:function(e){return p.chars(e).reverse().join("")},startsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?"":(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?"":t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g," ")).replace(/\s/g,"")},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return e==null?"":!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,n){return e==null?"":!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp("^"+n+"+"),""))},rtrim:function(e,n){return e==null?"":!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+"+$"),""))},truncate:function(e,n,r){return e==null?"":(e=t(e),r=r||"...",n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return"";e=t(e),n=~~n,r=r!=null?t(r):"...";if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?"":t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=" ";switch(i){case"right":return s=n-e.length,e+o(r,s);case"both":return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,"right")},lrpad:function(e,t,n){return p.pad(e,t,n,"both")},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e=="")return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return"";e=e.toFixed(~~t),r=r||",";var i=e.split("."),s=i[0],o=i[1]?(n||".")+i[1]:"";return s.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+r)+o},strRight:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return"";e+="",t=t!=null?""+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||", ",n=n||" and ";var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return"";var n="ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",r="aaaaaaaaceeeeeiiiilnoooooouuuunczz",i=new RegExp(a(n),"g");return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||"-"}),p.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,t){return[t,e,t].join("")},quote:function(e){return p.surround(e,'"')},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return"";n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[--n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(module.exports=p),exports._s=p):typeof define==="function"&&define.amd?define("underscore.string",[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);
\ No newline at end of file \ No newline at end of file
...@@ -3,6 +3,7 @@ import json ...@@ -3,6 +3,7 @@ import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from student.models import anonymous_id_for_user from student.models import anonymous_id_for_user
%> %>
<%namespace name='static' file='/static_content.html'/>
<% <%
if user: if user:
params.update({'user': anonymous_id_for_user(user, None)}) params.update({'user': anonymous_id_for_user(user, None)})
...@@ -10,11 +11,7 @@ from student.models import anonymous_id_for_user ...@@ -10,11 +11,7 @@ from student.models import anonymous_id_for_user
<div id="edx-notes-wrapper-${uid}" class="edx-notes-wrapper"> <div id="edx-notes-wrapper-${uid}" class="edx-notes-wrapper">
<div class="edx-notes-wrapper-content">${content}</div> <div class="edx-notes-wrapper-content">${content}</div>
</div> </div>
<script type="text/javascript"> <%static:require_module module_name="js/edxnotes/views/notes_visibility_factory" class_name="NotesVisibilityFactory">
(function (require) { var element = document.getElementById('edx-notes-wrapper-${uid}');
require(['js/edxnotes/views/visibility_decorator'], function(EdxnotesVisibilityDecorator) { NotesVisibilityFactory.VisibilityDecorator.factory(element, ${json.dumps(params)}, ${edxnotes_visibility});
var element = document.getElementById('edx-notes-wrapper-${uid}'); </%static:require_module>
EdxnotesVisibilityDecorator.factory(element, ${json.dumps(params)}, ${edxnotes_visibility});
});
}).call(this, require || RequireJS.require);
</script>
...@@ -2,12 +2,12 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"], ...@@ -2,12 +2,12 @@ define(["jquery", "backbone", "teams/js/teams_tab_factory"],
function($, Backbone, TeamsTabFactory) { function($, Backbone, TeamsTabFactory) {
'use strict'; 'use strict';
describe("teams django app", function() { describe("Teams tab", function() {
var teamsTab; var teamsTab;
beforeEach(function() { beforeEach(function() {
setFixtures('<section class="teams-content"></section>'); setFixtures('<section class="teams-content"></section>');
teamsTab = new TeamsTabFactory({results: []}, '', 'edX/DemoX/Demo_Course'); teamsTab = new TeamsTabFactory($(".teams-content"), {results: []}, '', 'edX/DemoX/Demo_Course');
}); });
afterEach(function() { afterEach(function() {
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
define(['jquery', 'teams/js/views/teams_tab', 'teams/js/collections/topic'], define(['jquery', 'teams/js/views/teams_tab', 'teams/js/collections/topic'],
function ($, TeamsTabView, TopicCollection) { function ($, TeamsTabView, TopicCollection) {
return function (topics, topics_url, course_id) { return function (element, topics, topics_url, course_id) {
var topicCollection = new TopicCollection(topics, {url: topics_url, course_id: course_id, parse: true}); var topicCollection = new TopicCollection(topics, {url: topics_url, course_id: course_id, parse: true});
topicCollection.bootstrap(); topicCollection.bootstrap();
var view = new TeamsTabView({ var view = new TeamsTabView({
el: $('.teams-content'), el: element,
topicCollection: topicCollection topicCollection: topicCollection
}); });
view.render(); view.render();
......
...@@ -21,11 +21,7 @@ ...@@ -21,11 +21,7 @@
</div> </div>
<%block name="js_extra"> <%block name="js_extra">
<script type="text/javascript"> <%static:require_module module_name="teams/js/teams_tab_factory" class_name="TeamsTabFactory">
(function (require) { TeamsTabFactory($('.teams-content'), ${ json.dumps(topics, cls=EscapedEdxJSONEncoder) }, '${ topics_url }', '${ unicode(course.id) }');
require(['teams/js/teams_tab_factory'], function (TeamsTabFactory) { </%static:require_module>
new TeamsTabFactory(${ json.dumps(topics, cls=EscapedEdxJSONEncoder) }, '${ topics_url }', '${ unicode(course.id) }');
});
}).call(this, require || RequireJS.require);
</script>
</%block> </%block>
""" """
Settings for bok choy tests Settings for Bok Choy tests that are used when running LMS.
Bok Choy uses two different settings files:
1. test_static_optimized is used when invoking collectstatic
2. bok_choy is used when running the tests
Note: it isn't possible to have a single settings file, because Django doesn't
support both generating static assets to a directory and also serving static
from the same directory.
""" """
import os import os
...@@ -48,6 +56,20 @@ update_module_store_settings( ...@@ -48,6 +56,20 @@ update_module_store_settings(
) )
############################ STATIC FILES ############################# ############################ STATIC FILES #############################
# Enable debug so that static assets are served by Django
DEBUG = True
# Serve static files at /static directly from the staticfiles directory under test root
# Note: optimized files for testing are generated with settings from test_static_optimized
STATIC_URL = "/static/"
STATICFILES_FINDERS = (
'staticfiles.finders.FileSystemFinder',
)
STATICFILES_DIRS = (
(TEST_ROOT / "staticfiles" / "lms").abspath(),
)
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = TEST_ROOT / "uploads" MEDIA_ROOT = TEST_ROOT / "uploads"
MEDIA_URL = "/static/uploads/" MEDIA_URL = "/static/uploads/"
...@@ -74,9 +96,6 @@ OPEN_ENDED_GRADING_INTERFACE['url'] = 'http://localhost:8041/' ...@@ -74,9 +96,6 @@ OPEN_ENDED_GRADING_INTERFACE['url'] = 'http://localhost:8041/'
EDXNOTES_PUBLIC_API = 'http://localhost:8042/api/v1' EDXNOTES_PUBLIC_API = 'http://localhost:8042/api/v1'
EDXNOTES_INTERNAL_API = 'http://localhost:8042/api/v1' EDXNOTES_INTERNAL_API = 'http://localhost:8042/api/v1'
# Enable django-pipeline and staticfiles
STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath()
# Silence noisy logs # Silence noisy logs
import logging import logging
LOG_OVERRIDES = [ LOG_OVERRIDES = [
...@@ -106,9 +125,6 @@ FEATURES['ENABLE_TEAMS'] = True ...@@ -106,9 +125,6 @@ FEATURES['ENABLE_TEAMS'] = True
# Enable custom content licensing # Enable custom content licensing
FEATURES['LICENSING'] = True FEATURES['LICENSING'] = True
# Unfortunately, we need to use debug mode to serve staticfiles
DEBUG = True
########################### Entrance Exams ################################# ########################### Entrance Exams #################################
FEATURES['MILESTONES_APP'] = True FEATURES['MILESTONES_APP'] = True
FEATURES['ENTRANCE_EXAMS'] = True FEATURES['ENTRANCE_EXAMS'] = True
......
...@@ -1199,7 +1199,7 @@ X_FRAME_OPTIONS = 'ALLOW' ...@@ -1199,7 +1199,7 @@ X_FRAME_OPTIONS = 'ALLOW'
############################### Pipeline ####################################### ############################### Pipeline #######################################
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage' STATICFILES_STORAGE = 'openedx.core.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage'
from openedx.core.lib.rooted_paths import rooted_glob from openedx.core.lib.rooted_paths import rooted_glob
...@@ -1212,8 +1212,6 @@ courseware_js = ( ...@@ -1212,8 +1212,6 @@ courseware_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/modules/**/*.js')) sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/modules/**/*.js'))
) )
courseware_search_js = ['js/search/course/main.js']
# Before a student accesses courseware, we do not # Before a student accesses courseware, we do not
# need many of the JS dependencies. This includes # need many of the JS dependencies. This includes
...@@ -1231,6 +1229,8 @@ base_vendor_js = [ ...@@ -1231,6 +1229,8 @@ base_vendor_js = [
'js/vendor/underscore-min.js', 'js/vendor/underscore-min.js',
'js/vendor/require.js', 'js/vendor/require.js',
'js/RequireJS-namespace-undefine.js', 'js/RequireJS-namespace-undefine.js',
'js/vendor/URI.min.js',
'js/vendor/backbone-min.js'
] ]
main_vendor_js = base_vendor_js + [ main_vendor_js = base_vendor_js + [
...@@ -1239,13 +1239,17 @@ main_vendor_js = base_vendor_js + [ ...@@ -1239,13 +1239,17 @@ main_vendor_js = base_vendor_js + [
'js/vendor/jquery.qtip.min.js', 'js/vendor/jquery.qtip.min.js',
'js/vendor/swfobject/swfobject.js', 'js/vendor/swfobject/swfobject.js',
'js/vendor/jquery.ba-bbq.min.js', 'js/vendor/jquery.ba-bbq.min.js',
'js/vendor/URI.min.js', ]
# Common files used by both RequireJS code and non-RequireJS code
base_application_js = [
'js/src/utility.js',
'js/src/logger.js',
] ]
dashboard_js = ( dashboard_js = (
sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/dashboard/**/*.js')) sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/dashboard/**/*.js'))
) )
dashboard_search_js = ['js/search/dashboard/main.js']
discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js')) discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js'))
staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js')) staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js'))
open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js')) open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js'))
...@@ -1265,13 +1269,9 @@ student_account_js = [ ...@@ -1265,13 +1269,9 @@ student_account_js = [
'js/toggle_login_modal.js', 'js/toggle_login_modal.js',
'js/sticky_filter.js', 'js/sticky_filter.js',
'js/query-params.js', 'js/query-params.js',
'js/src/utility.js',
'js/src/accessibility_tools.js', 'js/src/accessibility_tools.js',
'js/src/ie_shim.js', 'js/src/ie_shim.js',
'js/src/string_utils.js', 'js/src/string_utils.js',
'js/student_account/enrollment.js',
'js/student_account/emailoptin.js',
'js/student_account/shoppingcart.js',
'js/student_account/models/LoginModel.js', 'js/student_account/models/LoginModel.js',
'js/student_account/models/RegisterModel.js', 'js/student_account/models/RegisterModel.js',
'js/student_account/models/PasswordResetModel.js', 'js/student_account/models/PasswordResetModel.js',
...@@ -1291,7 +1291,6 @@ verify_student_js = [ ...@@ -1291,7 +1291,6 @@ verify_student_js = [
'js/toggle_login_modal.js', 'js/toggle_login_modal.js',
'js/sticky_filter.js', 'js/sticky_filter.js',
'js/query-params.js', 'js/query-params.js',
'js/src/utility.js',
'js/src/accessibility_tools.js', 'js/src/accessibility_tools.js',
'js/src/ie_shim.js', 'js/src/ie_shim.js',
'js/src/string_utils.js', 'js/src/string_utils.js',
...@@ -1336,8 +1335,6 @@ incourse_reverify_js = [ ...@@ -1336,8 +1335,6 @@ incourse_reverify_js = [
ccx_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/ccx/**/*.js')) ccx_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/ccx/**/*.js'))
discovery_js = ['js/discovery/main.js']
certificates_web_view_js = [ certificates_web_view_js = [
'js/vendor/jquery.min.js', 'js/vendor/jquery.min.js',
'js/vendor/jquery.cookie.js', 'js/vendor/jquery.cookie.js',
...@@ -1478,20 +1475,23 @@ project_js = set(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.js')) - s ...@@ -1478,20 +1475,23 @@ project_js = set(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/**/*.js')) - s
PIPELINE_JS = { PIPELINE_JS = {
'base_application': {
'source_filenames': base_application_js,
'output_filename': 'js/lms-base-application.js',
},
'application': { 'application': {
# Application will contain all paths not in courseware_only_js # Application will contain all paths not in courseware_only_js
'source_filenames': ['js/xblock/core.js'] + sorted(common_js) + sorted(project_js) + [ 'source_filenames': ['js/xblock/core.js'] + sorted(common_js) + sorted(project_js) + base_application_js + [
'js/form.ext.js', 'js/form.ext.js',
'js/my_courses_dropdown.js', 'js/my_courses_dropdown.js',
'js/toggle_login_modal.js', 'js/toggle_login_modal.js',
'js/sticky_filter.js', 'js/sticky_filter.js',
'js/query-params.js', 'js/query-params.js',
'js/src/utility.js',
'js/src/accessibility_tools.js', 'js/src/accessibility_tools.js',
'js/src/ie_shim.js', 'js/src/ie_shim.js',
'js/src/string_utils.js', 'js/src/string_utils.js',
'js/src/logger.js',
], ],
'output_filename': 'js/lms-application.js', 'output_filename': 'js/lms-application.js',
}, },
...@@ -1499,10 +1499,6 @@ PIPELINE_JS = { ...@@ -1499,10 +1499,6 @@ PIPELINE_JS = {
'source_filenames': courseware_js, 'source_filenames': courseware_js,
'output_filename': 'js/lms-courseware.js', 'output_filename': 'js/lms-courseware.js',
}, },
'courseware_search': {
'source_filenames': courseware_search_js,
'output_filename': 'js/lms-courseware-search.js',
},
'base_vendor': { 'base_vendor': {
'source_filenames': base_vendor_js, 'source_filenames': base_vendor_js,
'output_filename': 'js/lms-base-vendor.js', 'output_filename': 'js/lms-base-vendor.js',
...@@ -1543,10 +1539,6 @@ PIPELINE_JS = { ...@@ -1543,10 +1539,6 @@ PIPELINE_JS = {
'source_filenames': dashboard_js, 'source_filenames': dashboard_js,
'output_filename': 'js/dashboard.js' 'output_filename': 'js/dashboard.js'
}, },
'dashboard_search': {
'source_filenames': dashboard_search_js,
'output_filename': 'js/dashboard-search.js',
},
'student_account': { 'student_account': {
'source_filenames': student_account_js, 'source_filenames': student_account_js,
'output_filename': 'js/student_account.js' 'output_filename': 'js/student_account.js'
...@@ -1571,18 +1563,10 @@ PIPELINE_JS = { ...@@ -1571,18 +1563,10 @@ PIPELINE_JS = {
'source_filenames': ['js/footer-edx.js'], 'source_filenames': ['js/footer-edx.js'],
'output_filename': 'js/footer-edx.js' 'output_filename': 'js/footer-edx.js'
}, },
'discovery': {
'source_filenames': discovery_js,
'output_filename': 'js/discovery.js'
},
'certificates_wv': { 'certificates_wv': {
'source_filenames': certificates_web_view_js, 'source_filenames': certificates_web_view_js,
'output_filename': 'js/certificates/web_view.js' 'output_filename': 'js/certificates/web_view.js'
}, },
'utility': {
'source_filenames': ['js/src/utility.js'],
'output_filename': 'js/utility.js'
},
'credit_wv': { 'credit_wv': {
'source_filenames': credit_web_view_js, 'source_filenames': credit_web_view_js,
'output_filename': 'js/credit/web_view.js' 'output_filename': 'js/credit/web_view.js'
...@@ -1616,7 +1600,10 @@ PIPELINE_JS_COMPRESSOR = "pipeline.compressors.uglifyjs.UglifyJSCompressor" ...@@ -1616,7 +1600,10 @@ PIPELINE_JS_COMPRESSOR = "pipeline.compressors.uglifyjs.UglifyJSCompressor"
STATICFILES_IGNORE_PATTERNS = ( STATICFILES_IGNORE_PATTERNS = (
"sass/*", "sass/*",
"coffee/*", "coffee/*.coffee",
"coffee/*/*.coffee",
"coffee/*/*/*.coffee",
"coffee/*/*/*/*.coffee",
# Symlinks used by js-test-tool # Symlinks used by js-test-tool
"xmodule_js", "xmodule_js",
...@@ -1628,6 +1615,36 @@ PIPELINE_UGLIFYJS_BINARY = 'node_modules/.bin/uglifyjs' ...@@ -1628,6 +1615,36 @@ PIPELINE_UGLIFYJS_BINARY = 'node_modules/.bin/uglifyjs'
PIPELINE_COMPILE_INPLACE = True PIPELINE_COMPILE_INPLACE = True
################################# DJANGO-REQUIRE ###############################
# The baseUrl to pass to the r.js optimizer, relative to STATIC_ROOT.
REQUIRE_BASE_URL = "./"
# The name of a build profile to use for your project, relative to REQUIRE_BASE_URL.
# A sensible value would be 'app.build.js'. Leave blank to use the built-in default build profile.
# Set to False to disable running the default profile (e.g. if only using it to build Standalone
# Modules)
REQUIRE_BUILD_PROFILE = "lms/js/build.js"
# The name of the require.js script used by your project, relative to REQUIRE_BASE_URL.
REQUIRE_JS = "js/vendor/require.js"
# A dictionary of standalone modules to build with almond.js.
REQUIRE_STANDALONE_MODULES = {}
# Whether to run django-require in debug mode.
REQUIRE_DEBUG = False
# A tuple of files to exclude from the compilation result of r.js.
REQUIRE_EXCLUDE = ("build.txt",)
# The execution environment in which to run r.js: auto, node or rhino.
# auto will autodetect the environment and make use of node if available and rhino if not.
# It can also be a path to a custom class that subclasses require.environments.Environment
# and defines some "args" function that returns a list with the command arguments to execute.
REQUIRE_ENVIRONMENT = "node"
################################# CELERY ###################################### ################################# CELERY ######################################
# Message configuration # Message configuration
......
...@@ -79,6 +79,12 @@ def should_show_debug_toolbar(_): ...@@ -79,6 +79,12 @@ def should_show_debug_toolbar(_):
########################### PIPELINE ################################# ########################### PIPELINE #################################
# # Skip RequireJS optimizer in development
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
# Whether to run django-require in debug mode.
REQUIRE_DEBUG = DEBUG
PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT) PIPELINE_SASS_ARGUMENTS = '--debug-info --require {proj_dir}/static/sass/bourbon/lib/bourbon.rb'.format(proj_dir=PROJECT_ROOT)
########################### VERIFIED CERTIFICATES ################################# ########################### VERIFIED CERTIFICATES #################################
......
...@@ -29,6 +29,9 @@ TEST_ROOT = REPO_ROOT / "test_root" # pylint: disable=no-value-for-parameter ...@@ -29,6 +29,9 @@ TEST_ROOT = REPO_ROOT / "test_root" # pylint: disable=no-value-for-parameter
# Enable debug so that static assets are served by Django # Enable debug so that static assets are served by Django
DEBUG = True DEBUG = True
# Set REQUIRE_DEBUG to false so that it behaves like production
REQUIRE_DEBUG = False
# Serve static files at /static directly from the staticfiles directory under test root. # Serve static files at /static directly from the staticfiles directory under test root.
# Note: optimized files for testing are generated with settings from test_static_optimized # Note: optimized files for testing are generated with settings from test_static_optimized
STATIC_URL = "/static/" STATIC_URL = "/static/"
......
...@@ -45,3 +45,4 @@ STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath() ...@@ -45,3 +45,4 @@ STATIC_ROOT = (TEST_ROOT / "staticfiles" / "lms").abspath()
# 1. Uglify is by far the slowest part of the build process # 1. Uglify is by far the slowest part of the build process
# 2. Having full source code makes debugging tests easier for developers # 2. Having full source code makes debugging tests easier for developers
os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none' os.environ['REQUIRE_BUILD_PROFILE_OPTIMIZE'] = 'none'
PIPELINE_JS_COMPRESSOR = None
;(function (define) { ;(function (define) {
define(['backbone', 'course_discovery_meanings'], function(Backbone, meanings) {
'use strict'; 'use strict';
return function (Collection, Form, ResultListView, FilterBarView, FacetsBarView, searchQuery) { define(['backbone', 'js/discovery/collection', 'js/discovery/form', 'js/discovery/result_list_view',
//facet types configuration - set default display names 'js/discovery/filter_bar_view', 'js/discovery/search_facets_view'],
var facetsTypes = meanings; function(Backbone, Collection, Form, ResultListView, FilterBarView, FacetsBarView) {
var collection = new Collection([]); return function (meanings, searchQuery) {
var results = new ResultListView({ collection: collection }); //facet types configuration - set default display names
var dispatcher = _.clone(Backbone.Events); var facetsTypes = meanings;
var form = new Form();
var filters = new FilterBarView();
var facetsBarView = new FacetsBarView(facetsTypes);
dispatcher.listenTo(form, 'search', function (query) { var collection = new Collection([]);
form.showLoadingIndicator(); var results = new ResultListView({ collection: collection });
filters.changeQueryFilter(query); var dispatcher = _.clone(Backbone.Events);
}); var form = new Form();
var filters = new FilterBarView();
var facetsBarView = new FacetsBarView(facetsTypes);
dispatcher.listenTo(filters, 'search', function (searchTerm, facets) { dispatcher.listenTo(form, 'search', function (query) {
collection.performSearch(searchTerm, facets); form.showLoadingIndicator();
form.showLoadingIndicator(); filters.changeQueryFilter(query);
}); });
dispatcher.listenTo(filters, 'clear', function () { dispatcher.listenTo(filters, 'search', function (searchTerm, facets) {
form.clearSearch(); collection.performSearch(searchTerm, facets);
collection.performSearch(); form.showLoadingIndicator();
filters.hideClearAllButton(); });
});
dispatcher.listenTo(results, 'next', function () { dispatcher.listenTo(filters, 'clear', function () {
collection.loadNextPage(); form.clearSearch();
form.showLoadingIndicator(); collection.performSearch();
}); filters.hideClearAllButton();
});
dispatcher.listenTo(collection, 'search', function () { dispatcher.listenTo(results, 'next', function () {
if (collection.length > 0) { collection.loadNextPage();
form.showFoundMessage(collection.totalCount); form.showLoadingIndicator();
results.render(); });
}
else {
form.showNotFoundMessage(collection.searchTerm);
}
facetsBarView.renderFacets(collection.facets);
form.hideLoadingIndicator();
});
dispatcher.listenTo(collection, 'next', function () { dispatcher.listenTo(collection, 'search', function () {
results.renderNext(); if (collection.length > 0) {
form.hideLoadingIndicator(); form.showFoundMessage(collection.totalCount);
}); results.render();
}
else {
form.showNotFoundMessage(collection.searchTerm);
}
facetsBarView.renderFacets(collection.facets);
form.hideLoadingIndicator();
});
dispatcher.listenTo(collection, 'error', function () { dispatcher.listenTo(collection, 'next', function () {
form.showErrorMessage(); results.renderNext();
form.hideLoadingIndicator(); form.hideLoadingIndicator();
}); });
dispatcher.listenTo(facetsBarView, 'addFilter', function (data) { dispatcher.listenTo(collection, 'error', function () {
filters.addFilter(data); form.showErrorMessage();
}); form.hideLoadingIndicator();
});
// kick off search on page refresh dispatcher.listenTo(facetsBarView, 'addFilter', function (data) {
form.doSearch(searchQuery); filters.addFilter(data);
});
}; // kick off search on page refresh
form.doSearch(searchQuery);
}); };
});
})(define || RequireJS.define); })(define || RequireJS.define);
RequireJS.require([
'jquery',
'backbone',
'js/discovery/app',
'js/discovery/collection',
'js/discovery/form',
'js/discovery/result_list_view',
'js/discovery/filter_bar_view',
'js/discovery/search_facets_view'
], function ($, Backbone, App, Collection, DiscoveryForm, ResultListView, FilterBarView, FacetsBarView) {
'use strict';
var app = new App(
Collection,
DiscoveryForm,
ResultListView,
FilterBarView,
FacetsBarView,
getParameterByName('search_query')
);
});
;(function (define) { ;(function (define) {
'use strict'; 'use strict';
define(['backbone', 'js/edxnotes/utils/utils', 'underscore.string'], function (Backbone, Utils) { define(['backbone', 'js/edxnotes/utils/utils', 'underscore.string'], function (Backbone, Utils, str) {
var NoteModel = Backbone.Model.extend({ var NoteModel = Backbone.Model.extend({
defaults: { defaults: {
'id': null, 'id': null,
...@@ -47,7 +47,7 @@ define(['backbone', 'js/edxnotes/utils/utils', 'underscore.string'], function (B ...@@ -47,7 +47,7 @@ define(['backbone', 'js/edxnotes/utils/utils', 'underscore.string'], function (B
var message = this.get('quote'); var message = this.get('quote');
if (!this.get('is_expanded') && this.get('show_link')) { if (!this.get('is_expanded') && this.get('show_link')) {
message = _.str.prune(message, this.textSize); message = str.prune(message, this.textSize);
} }
return message; return message;
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'underscore', 'annotator_1.2.9', 'underscore.string' 'underscore', 'annotator_1.2.9'
], function (_, Annotator) { ], function (_, Annotator) {
/** /**
* Modifies Annotator.Plugin.Store.annotationCreated to make it trigger a new * Modifies Annotator.Plugin.Store.annotationCreated to make it trigger a new
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
define([ define([
'jquery', 'underscore', 'backbone', 'gettext', 'jquery', 'underscore', 'backbone', 'gettext',
'annotator_1.2.9', 'js/edxnotes/views/visibility_decorator', 'js/utils/animation' 'annotator_1.2.9', 'js/edxnotes/views/visibility_decorator', 'js/utils/animation'
], function($, _, Backbone, gettext, Annotator, EdxnotesVisibilityDecorator) { ], function($, _, Backbone, gettext, Annotator, VisibilityDecorator) {
var ToggleNotesView = Backbone.View.extend({ var ToggleVisibilityView = Backbone.View.extend({
events: { events: {
'click .action-toggle-notes': 'toggleHandler' 'click .action-toggle-notes': 'toggleHandler'
}, },
...@@ -52,14 +52,14 @@ define([ ...@@ -52,14 +52,14 @@ define([
}, },
enableNotes: function () { enableNotes: function () {
_.each($('.edx-notes-wrapper'), EdxnotesVisibilityDecorator.enableNote); _.each($('.edx-notes-wrapper'), VisibilityDecorator.enableNote);
this.actionLink.addClass('is-active'); this.actionLink.addClass('is-active');
this.label.text(gettext('Hide notes')); this.label.text(gettext('Hide notes'));
this.actionToggleMessage.text(gettext('Notes visible')); this.actionToggleMessage.text(gettext('Notes visible'));
}, },
disableNotes: function () { disableNotes: function () {
EdxnotesVisibilityDecorator.disableNotes(); VisibilityDecorator.disableNotes();
this.actionLink.removeClass('is-active'); this.actionLink.removeClass('is-active');
this.label.text(gettext('Show notes')); this.label.text(gettext('Show notes'));
this.actionToggleMessage.text(gettext('Notes hidden')); this.actionToggleMessage.text(gettext('Notes hidden'));
...@@ -93,12 +93,16 @@ define([ ...@@ -93,12 +93,16 @@ define([
} }
}); });
return function (visibility, visibilityUrl) { return {
return new ToggleNotesView({ ToggleVisibilityView: function (visibility, visibilityUrl) {
el: $('.edx-notes-visibility').get(0), return new ToggleVisibilityView({
visibility: visibility, el: $('.edx-notes-visibility').get(0),
visibilityUrl: visibilityUrl visibility: visibility,
}); visibilityUrl: visibilityUrl
});
},
VisibilityDecorator: VisibilityDecorator
}; };
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone, CohortModel) {
'use strict'; 'use strict';
define(['backbone', 'js/groups/models/cohort'], function(Backbone, CohortModel) {
edx.groups = edx.groups || {}; var CohortCollection = Backbone.Collection.extend({
model : CohortModel,
edx.groups.CohortCollection = Backbone.Collection.extend({ comparator: "name",
model : CohortModel,
comparator: "name",
parse: function(response) { parse: function(response) {
return response.cohorts; return response.cohorts;
} }
});
return CohortCollection;
}); });
}).call(this, Backbone, edx.groups.CohortModel); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone) {
'use strict'; 'use strict';
define(['backbone'], function(Backbone) {
edx.groups = edx.groups || {}; var CohortModel = Backbone.Model.extend({
idAttribute: 'id',
edx.groups.CohortModel = Backbone.Model.extend({ defaults: {
idAttribute: 'id', name: '',
defaults: { user_count: 0,
name: '', /**
user_count: 0, * Indicates how students are added to the cohort. Will be "none" (signifying manual assignment) or
/** * "random" (indicating students are randomly assigned).
* Indicates how students are added to the cohort. Will be "none" (signifying manual assignment) or */
* "random" (indicating students are randomly assigned). assignment_type: '',
*/ /**
assignment_type: '', * If this cohort is associated with a user partition group, the ID of the user partition.
/** */
* If this cohort is associated with a user partition group, the ID of the user partition. user_partition_id: null,
*/ /**
user_partition_id: null, * If this cohort is associated with a user partition group, the ID of the group within the
/** * partition associated with user_partition_id.
* If this cohort is associated with a user partition group, the ID of the group within the */
* partition associated with user_partition_id. group_id: null
*/ }
group_id: null });
} return CohortModel;
}); });
}).call(this, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone) {
'use strict'; 'use strict';
define(['backbone'], function(Backbone) {
edx.groups = edx.groups || {}; var DiscussionTopicsSettingsModel = Backbone.Model.extend({
defaults: {
edx.groups.DiscussionTopicsSettingsModel = Backbone.Model.extend({ course_wide_discussions: {},
defaults: { inline_discussions: {}
course_wide_discussions: {}, }
inline_discussions: {} });
} return DiscussionTopicsSettingsModel;
}); });
}).call(this, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone) {
'use strict'; 'use strict';
define(['backbone'], function(Backbone) {
edx.groups = edx.groups || {}; var ContentGroupModel = Backbone.Model.extend({
idAttribute: 'id',
edx.groups.ContentGroupModel = Backbone.Model.extend({ defaults: {
idAttribute: 'id', name: '',
defaults: { user_partition_id: null
name: '', }
user_partition_id: null });
} return ContentGroupModel;
}); });
}).call(this, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone) {
'use strict'; 'use strict';
define(['backbone'], function(Backbone) {
edx.groups = edx.groups || {}; var CourseCohortSettingsModel = Backbone.Model.extend({
idAttribute: 'id',
edx.groups.CourseCohortSettingsModel = Backbone.Model.extend({ defaults: {
idAttribute: 'id', is_cohorted: false,
defaults: { cohorted_inline_discussions: [],
is_cohorted: false, cohorted_course_wide_discussions:[],
cohorted_inline_discussions: [], always_cohort_inline_discussions: true
cohorted_course_wide_discussions:[], }
always_cohort_inline_discussions: true });
} return CourseCohortSettingsModel;
}); });
}).call(this, Backbone); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function ($, _, Backbone, gettext, interpolate_text, NotificationModel, NotificationView) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'],
function ($, _, Backbone) {
edx.groups = edx.groups || {}; var CohortDiscussionConfigurationView = Backbone.View.extend({
edx.groups.CohortDiscussionConfigurationView = Backbone.View.extend({ /**
* Add/Remove the disabled attribute on given element.
* @param {object} $element - The element to disable/enable.
* @param {bool} disable - The flag to add/remove 'disabled' attribute.
*/
setDisabled: function($element, disable) {
$element.prop('disabled', disable ? 'disabled' : false);
},
/** /**
* Add/Remove the disabled attribute on given element. * Returns the cohorted discussions list.
* @param {object} $element - The element to disable/enable. * @param {string} selector - To select the discussion elements whose ids to return.
* @param {bool} disable - The flag to add/remove 'disabled' attribute. * @returns {Array} - Cohorted discussions.
*/ */
setDisabled: function($element, disable) { getCohortedDiscussions: function(selector) {
$element.prop('disabled', disable ? 'disabled' : false); var self=this,
}, cohortedDiscussions = [];
/** _.each(self.$(selector), function (topic) {
* Returns the cohorted discussions list. cohortedDiscussions.push($(topic).data('id'))
* @param {string} selector - To select the discussion elements whose ids to return. });
* @returns {Array} - Cohorted discussions. return cohortedDiscussions;
*/ },
getCohortedDiscussions: function(selector) {
var self=this,
cohortedDiscussions = [];
_.each(self.$(selector), function (topic) { /**
cohortedDiscussions.push($(topic).data('id')) * Save the cohortSettings' changed attributes to the server via PATCH method.
}); * It shows the error message(s) if any.
return cohortedDiscussions; * @param {object} $element - Messages would be shown before this element.
}, * @param {object} fieldData - Data to update on the server.
*/
saveForm: function ($element, fieldData) {
var self = this,
cohortSettingsModel = this.cohortSettings,
saveOperation = $.Deferred(),
showErrorMessage;
/** showErrorMessage = function (message, $element) {
* Save the cohortSettings' changed attributes to the server via PATCH method. self.showMessage(message, $element, 'error');
* It shows the error message(s) if any. };
* @param {object} $element - Messages would be shown before this element. this.removeNotification();
* @param {object} fieldData - Data to update on the server.
*/
saveForm: function ($element, fieldData) {
var self = this,
cohortSettingsModel = this.cohortSettings,
saveOperation = $.Deferred(),
showErrorMessage;
showErrorMessage = function (message, $element) { cohortSettingsModel.save(
self.showMessage(message, $element, 'error'); fieldData, {patch: true, wait: true}
}; ).done(function () {
this.removeNotification(); saveOperation.resolve();
}).fail(function (result) {
var errorMessage = null;
try {
var jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error;
} catch (e) {
// Ignore the exception and show the default error message instead.
}
if (!errorMessage) {
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
}
showErrorMessage(errorMessage, $element);
saveOperation.reject();
});
return saveOperation.promise();
},
cohortSettingsModel.save( /**
fieldData, {patch: true, wait: true} * Shows the notification messages before given element using the NotificationModel.
).done(function () { * @param {string} message - Text message to show.
saveOperation.resolve(); * @param {object} $element - Message would be shown before this element.
}).fail(function (result) { * @param {string} type - Type of message to show e.g. confirmation or error.
var errorMessage = null; */
try { showMessage: function (message, $element, type) {
var jsonResponse = JSON.parse(result.responseText); var model = new NotificationModel({type: type || 'confirmation', title: message});
errorMessage = jsonResponse.error; this.removeNotification();
} catch (e) { this.notification = new NotificationView({
// Ignore the exception and show the default error message instead. model: model
} });
if (!errorMessage) { $element.before(this.notification.$el);
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); this.notification.render();
},
/**
*Removes the notification messages.
*/
removeNotification: function () {
if (this.notification) {
this.notification.remove();
}
} }
showErrorMessage(errorMessage, $element);
saveOperation.reject();
});
return saveOperation.promise();
},
/**
* Shows the notification messages before given element using the NotificationModel.
* @param {string} message - Text message to show.
* @param {object} $element - Message would be shown before this element.
* @param {string} type - Type of message to show e.g. confirmation or error.
*/
showMessage: function (message, $element, type) {
var model = new NotificationModel({type: type || 'confirmation', title: message});
this.removeNotification();
this.notification = new NotificationView({
model: model
}); });
$element.before(this.notification.$el); return CohortDiscussionConfigurationView;
this.notification.render(); });
}, }).call(this, define || RequireJS.define);
/**
*Removes the notification messages.
*/
removeNotification: function () {
if (this.notification) {
this.notification.remove();
}
}
});
}).call(this, $, _, Backbone, gettext, interpolate_text, NotificationModel, NotificationView
);
var edx = edx || {}; ;(function (define) {
(function ($, _, Backbone, gettext, interpolate_text, CohortDiscussionConfigurationView) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions'],
function ($, _, Backbone, gettext, CohortDiscussionConfigurationView) {
var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({
events: {
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
},
edx.groups = edx.groups || {}; initialize: function (options) {
this.template = _.template($('#cohort-discussions-course-wide-tpl').text());
edx.groups.CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({ this.cohortSettings = options.cohortSettings;
events: { },
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
},
initialize: function (options) { render: function () {
this.template = _.template($('#cohort-discussions-course-wide-tpl').text()); this.$('.cohort-course-wide-discussions-nav').html(this.template({
this.cohortSettings = options.cohortSettings; courseWideTopics: this.getCourseWideDiscussionsHtml(
}, this.model.get('course_wide_discussions')
)
}));
this.setDisabled(this.$('.cohort-course-wide-discussions-form .action-save'), true);
},
render: function () { /**
this.$('.cohort-course-wide-discussions-nav').html(this.template({ * Returns the html list for course-wide discussion topics.
courseWideTopics: this.getCourseWideDiscussionsHtml( * @param {object} courseWideDiscussions - course-wide discussions object from server.
this.model.get('course_wide_discussions') * @returns {Array} - HTML list for course-wide discussion topics.
) */
})); getCourseWideDiscussionsHtml: function (courseWideDiscussions) {
this.setDisabled(this.$('.cohort-course-wide-discussions-form .action-save'), true); var subCategoryTemplate = _.template($('#cohort-discussions-subcategory-tpl').html()),
}, entries = courseWideDiscussions.entries,
children = courseWideDiscussions.children;
/** return _.map(children, function (name) {
* Returns the html list for course-wide discussion topics. var entry = entries[name];
* @param {object} courseWideDiscussions - course-wide discussions object from server. return subCategoryTemplate({
* @returns {Array} - HTML list for course-wide discussion topics. name: name,
*/ id: entry.id,
getCourseWideDiscussionsHtml: function (courseWideDiscussions) { is_cohorted: entry.is_cohorted,
var subCategoryTemplate = _.template($('#cohort-discussions-subcategory-tpl').html()), type: 'course-wide'
entries = courseWideDiscussions.entries, });
children = courseWideDiscussions.children; }).join('');
},
return _.map(children, function (name) { /**
var entry = entries[name]; * Enables the save button for course-wide discussions.
return subCategoryTemplate({ */
name: name, discussionCategoryStateChanged: function(event) {
id: entry.id, event.preventDefault();
is_cohorted: entry.is_cohorted, this.setDisabled(this.$('.cohort-course-wide-discussions-form .action-save'), false);
type: 'course-wide' },
});
}).join('');
},
/** /**
* Enables the save button for course-wide discussions. * Sends the cohorted_course_wide_discussions to the server and renders the view.
*/ */
discussionCategoryStateChanged: function(event) { saveCourseWideDiscussionsForm: function (event) {
event.preventDefault(); event.preventDefault();
this.setDisabled(this.$('.cohort-course-wide-discussions-form .action-save'), false);
},
/** var self = this,
* Sends the cohorted_course_wide_discussions to the server and renders the view. courseWideCohortedDiscussions = self.getCohortedDiscussions(
*/ '.check-discussion-subcategory-course-wide:checked'
saveCourseWideDiscussionsForm: function (event) { ),
event.preventDefault(); fieldData = { cohorted_course_wide_discussions: courseWideCohortedDiscussions };
var self = this, self.saveForm(self.$('.course-wide-discussion-topics'),fieldData)
courseWideCohortedDiscussions = self.getCohortedDiscussions( .done(function () {
'.check-discussion-subcategory-course-wide:checked' self.model.fetch()
), .done(function () {
fieldData = { cohorted_course_wide_discussions: courseWideCohortedDiscussions }; self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics'));
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error')
});
});
}
self.saveForm(self.$('.course-wide-discussion-topics'),fieldData) });
.done(function () { return CourseWideDiscussionsView;
self.model.fetch() });
.done(function () { }).call(this, define || RequireJS.define);
self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics'));
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error')
});
});
}
});
}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortDiscussionConfigurationView);
var edx = edx || {}; ;(function (define) {
(function ($, _, Backbone, gettext, interpolate_text, CohortDiscussionConfigurationView) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions', 'js/vendor/jquery.qubit'],
edx.groups = edx.groups || {}; function ($, _, Backbone, gettext, CohortDiscussionConfigurationView) {
var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({
edx.groups.InlineDiscussionsView = CohortDiscussionConfigurationView.extend({ events: {
events: { 'change .check-discussion-category': 'setSaveButton',
'change .check-discussion-category': 'setSaveButton', 'change .check-discussion-subcategory-inline': 'setSaveButton',
'change .check-discussion-subcategory-inline': 'setSaveButton', 'click .cohort-inline-discussions-form .action-save': 'saveInlineDiscussionsForm',
'click .cohort-inline-discussions-form .action-save': 'saveInlineDiscussionsForm', 'change .check-all-inline-discussions': 'setAllInlineDiscussions',
'change .check-all-inline-discussions': 'setAllInlineDiscussions', 'change .check-cohort-inline-discussions': 'setSomeInlineDiscussions'
'change .check-cohort-inline-discussions': 'setSomeInlineDiscussions' },
},
initialize: function (options) {
initialize: function (options) { this.template = _.template($('#cohort-discussions-inline-tpl').text());
this.template = _.template($('#cohort-discussions-inline-tpl').text()); this.cohortSettings = options.cohortSettings;
this.cohortSettings = options.cohortSettings; },
},
render: function () {
render: function () { var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions');
var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions');
this.$('.cohort-inline-discussions-nav').html(this.template({
this.$('.cohort-inline-discussions-nav').html(this.template({ inlineDiscussionTopics: this.getInlineDiscussionsHtml(this.model.get('inline_discussions')),
inlineDiscussionTopics: this.getInlineDiscussionsHtml(this.model.get('inline_discussions')), alwaysCohortInlineDiscussions:alwaysCohortInlineDiscussions
alwaysCohortInlineDiscussions:alwaysCohortInlineDiscussions }));
}));
// Provides the semantics for a nested list of tri-state checkboxes.
// Provides the semantics for a nested list of tri-state checkboxes. // When attached to a jQuery element it listens for change events to
// When attached to a jQuery element it listens for change events to // input[type=checkbox] elements, and updates the checked and indeterminate
// input[type=checkbox] elements, and updates the checked and indeterminate // based on the checked values of any checkboxes in child elements of the DOM.
// based on the checked values of any checkboxes in child elements of the DOM. this.$('ul.inline-topics').qubit();
this.$('ul.inline-topics').qubit();
this.setElementsEnabled(alwaysCohortInlineDiscussions, true);
this.setElementsEnabled(alwaysCohortInlineDiscussions, true); },
},
/**
/** * Generate html list for inline discussion topics.
* Generate html list for inline discussion topics. * @params {object} inlineDiscussions - inline discussions object from server.
* @params {object} inlineDiscussions - inline discussions object from server. * @returns {Array} - HTML for inline discussion topics.
* @returns {Array} - HTML for inline discussion topics. */
*/ getInlineDiscussionsHtml: function (inlineDiscussions) {
getInlineDiscussionsHtml: function (inlineDiscussions) { var categoryTemplate = _.template($('#cohort-discussions-category-tpl').html()),
var categoryTemplate = _.template($('#cohort-discussions-category-tpl').html()), entryTemplate = _.template($('#cohort-discussions-subcategory-tpl').html()),
entryTemplate = _.template($('#cohort-discussions-subcategory-tpl').html()), isCategoryCohorted = false,
isCategoryCohorted = false, children = inlineDiscussions.children,
children = inlineDiscussions.children, entries = inlineDiscussions.entries,
entries = inlineDiscussions.entries, subcategories = inlineDiscussions.subcategories;
subcategories = inlineDiscussions.subcategories;
return _.map(children, function (name) {
return _.map(children, function (name) { var html = '', entry;
var html = '', entry; if (entries && _.has(entries, name)) {
if (entries && _.has(entries, name)) { entry = entries[name];
entry = entries[name]; html = entryTemplate({
html = entryTemplate({ name: name,
name: name, id: entry.id,
id: entry.id, is_cohorted: entry.is_cohorted,
is_cohorted: entry.is_cohorted, type: 'inline'
type: 'inline' });
}); } else { // subcategory
} else { // subcategory html = categoryTemplate({
html = categoryTemplate({ name: name,
name: name, entries: this.getInlineDiscussionsHtml(subcategories[name]),
entries: this.getInlineDiscussionsHtml(subcategories[name]), isCategoryCohorted: isCategoryCohorted
isCategoryCohorted: isCategoryCohorted });
}); }
} return html;
return html; }, this).join('');
}, this).join(''); },
},
/**
/** * Enable/Disable the inline discussion elements.
* Enable/Disable the inline discussion elements. *
* * Disables the category and sub-category checkboxes.
* Disables the category and sub-category checkboxes. * Enables the save button.
* Enables the save button. */
*/ setAllInlineDiscussions: function(event) {
setAllInlineDiscussions: function(event) { event.preventDefault();
event.preventDefault(); this.setElementsEnabled(($(event.currentTarget).prop('checked')), false);
this.setElementsEnabled(($(event.currentTarget).prop('checked')), false); },
},
/**
/** * Enables the inline discussion elements.
* Enables the inline discussion elements. *
* * Enables the category and sub-category checkboxes.
* Enables the category and sub-category checkboxes. * Enables the save button.
* Enables the save button. */
*/ setSomeInlineDiscussions: function(event) {
setSomeInlineDiscussions: function(event) { event.preventDefault();
event.preventDefault(); this.setElementsEnabled(!($(event.currentTarget).prop('checked')), false);
this.setElementsEnabled(!($(event.currentTarget).prop('checked')), false); },
},
/**
/** * Enable/Disable the inline discussion elements.
* Enable/Disable the inline discussion elements. *
* * Enable/Disable the category and sub-category checkboxes.
* Enable/Disable the category and sub-category checkboxes. * Enable/Disable the save button.
* Enable/Disable the save button. * @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes.
* @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes. * @param {bool} enable_save_button - The flag to enable/disable the save button.
* @param {bool} enable_save_button - The flag to enable/disable the save button. */
*/ setElementsEnabled: function(enable_checkboxes, enable_save_button) {
setElementsEnabled: function(enable_checkboxes, enable_save_button) { this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes);
this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes); this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes); this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button); },
},
/**
/** * Enables the save button for inline discussions.
* Enables the save button for inline discussions. */
*/ setSaveButton: function(event) {
setSaveButton: function(event) { this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false); },
},
/**
/** * Sends the cohorted_inline_discussions to the server and renders the view.
* Sends the cohorted_inline_discussions to the server and renders the view. */
*/ saveInlineDiscussionsForm: function (event) {
saveInlineDiscussionsForm: function (event) { event.preventDefault();
event.preventDefault();
var self = this,
var self = this, cohortedInlineDiscussions = self.getCohortedDiscussions(
cohortedInlineDiscussions = self.getCohortedDiscussions( '.check-discussion-subcategory-inline:checked'
'.check-discussion-subcategory-inline:checked' ),
), fieldData= {
fieldData= { cohorted_inline_discussions: cohortedInlineDiscussions,
cohorted_inline_discussions: cohortedInlineDiscussions, always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked')
always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked') };
};
self.saveForm(self.$('.inline-discussion-topics'), fieldData)
self.saveForm(self.$('.inline-discussion-topics'), fieldData) .done(function () {
.done(function () { self.model.fetch()
self.model.fetch() .done(function () {
.done(function () { self.render();
self.render(); self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics'));
self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics')); }).fail(function() {
}).fail(function() { var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error')
self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error') });
}); });
}); }
}
});
return InlineDiscussionsView;
}); });
}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortDiscussionConfigurationView); }).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function(Backbone, _, $, gettext, ngettext, interpolate_text, CohortFormView, NotificationModel, NotificationView) {
'use strict'; 'use strict';
define(['backbone', 'underscore', 'jquery', 'gettext', 'js/groups/views/cohort_form', 'string_utils',
edx.groups = edx.groups || {}; 'js/models/notification', 'js/views/notification'],
function(Backbone, _, $, gettext, CohortFormView) {
edx.groups.CohortEditorView = Backbone.View.extend({ var CohortEditorView = Backbone.View.extend({
events : { events : {
'click .wrapper-tabs .tab': 'selectTab', 'click .wrapper-tabs .tab': 'selectTab',
'click .tab-content-settings .action-save': 'saveSettings', 'click .tab-content-settings .action-save': 'saveSettings',
'click .tab-content-settings .action-cancel': 'cancelSettings', 'click .tab-content-settings .action-cancel': 'cancelSettings',
'submit .cohort-management-group-add-form': 'addStudents' 'submit .cohort-management-group-add-form': 'addStudents'
}, },
initialize: function(options) { initialize: function(options) {
this.template = _.template($('#cohort-editor-tpl').text()); this.template = _.template($('#cohort-editor-tpl').text());
this.groupHeaderTemplate = _.template($('#cohort-group-header-tpl').text()); this.groupHeaderTemplate = _.template($('#cohort-group-header-tpl').text());
this.cohorts = options.cohorts; this.cohorts = options.cohorts;
this.contentGroups = options.contentGroups; this.contentGroups = options.contentGroups;
this.context = options.context; this.context = options.context;
}, },
// Any errors that are currently being displayed to the instructor (for example, unknown email addresses). // Any errors that are currently being displayed to the instructor (for example, unknown email addresses).
errorNotifications: null, errorNotifications: null,
// Any confirmation messages that are currently being displayed (for example, number of students added). // Any confirmation messages that are currently being displayed (for example, number of students added).
confirmationNotifications: null, confirmationNotifications: null,
render: function() { render: function() {
this.$el.html(this.template({ this.$el.html(this.template({
cohort: this.model cohort: this.model
})); }));
this.renderGroupHeader(); this.renderGroupHeader();
this.cohortFormView = new CohortFormView({ this.cohortFormView = new CohortFormView({
model: this.model, model: this.model,
contentGroups: this.contentGroups, contentGroups: this.contentGroups,
context: this.context context: this.context
});
this.cohortFormView.render();
this.$('.tab-content-settings').append(this.cohortFormView.$el);
return this;
},
renderGroupHeader: function() {
this.$('.cohort-management-group-header').html(this.groupHeaderTemplate({
cohort: this.model
}));
},
selectTab: function(event) {
var tabElement = $(event.currentTarget),
tabName = tabElement.data('tab');
event.preventDefault();
this.$('.wrapper-tabs .tab').removeClass('is-selected');
this.$('.wrapper-tabs .tab').find('span.sr').remove();
tabElement.addClass('is-selected');
tabElement.find('a').prepend('<span class="sr">' + gettext('Selected tab') + ' </span>');
this.$('.tab-content').addClass('is-hidden');
this.$('.tab-content-' + tabName).removeClass('is-hidden').focus();
},
saveSettings: function(event) {
var cohortFormView = this.cohortFormView;
var self = this;
event.preventDefault();
cohortFormView.saveForm()
.done(function() {
self.renderGroupHeader();
cohortFormView.showMessage(gettext('Saved cohort'));
});
},
cancelSettings: function(event) {
event.preventDefault();
this.render();
},
setCohort: function(cohort) {
this.model = cohort;
this.render();
},
addStudents: function(event) {
event.preventDefault();
var self = this,
cohorts = this.cohorts,
input = this.$('.cohort-management-group-add-students'),
add_url = this.model.url() + '/add',
students = input.val().trim(),
cohortId = this.model.id;
if (students.length > 0) {
$.post(
add_url, {'users': students}
).done(function(modifiedUsers) {
self.refreshCohorts().done(function() {
// Find the equivalent cohort in the new collection and select it
var cohort = cohorts.get(cohortId);
self.setCohort(cohort);
// Show the notifications
self.addNotifications(modifiedUsers);
// If an unknown user was specified then update the new input to have
// the original input's value. This is to allow the user to correct the
// value in case it was a typo.
if (modifiedUsers.unknown.length > 0) {
self.$('.cohort-management-group-add-students').val(students);
}
}); });
}).fail(function() { this.cohortFormView.render();
self.showErrorMessage(gettext('Error adding students.'), true); this.$('.tab-content-settings').append(this.cohortFormView.$el);
}); return this;
} else { },
self.showErrorMessage(gettext('Enter a username or email.'), true);
input.val(''); renderGroupHeader: function() {
} this.$('.cohort-management-group-header').html(this.groupHeaderTemplate({
}, cohort: this.model
}));
/** },
* Refresh the cohort collection to get the latest set as well as up-to-date counts.
*/ selectTab: function(event) {
refreshCohorts: function() { var tabElement = $(event.currentTarget),
return this.cohorts.fetch(); tabName = tabElement.data('tab');
}, event.preventDefault();
this.$('.wrapper-tabs .tab').removeClass('is-selected');
undelegateViewEvents: function (view) { this.$('.wrapper-tabs .tab').find('span.sr').remove();
if (view) { tabElement.addClass('is-selected');
view.undelegateEvents(); tabElement.find('a').prepend('<span class="sr">' + gettext('Selected tab') + ' </span>');
} this.$('.tab-content').addClass('is-hidden');
}, this.$('.tab-content-' + tabName).removeClass('is-hidden').focus();
},
showErrorMessage: function(message, removeConfirmations, model) {
if (removeConfirmations && this.confirmationNotifications) { saveSettings: function(event) {
this.undelegateViewEvents(this.confirmationNotifications); var cohortFormView = this.cohortFormView;
this.confirmationNotifications.$el.html(''); var self = this;
this.confirmationNotifications = null; event.preventDefault();
} cohortFormView.saveForm()
if (model === undefined) { .done(function() {
model = new NotificationModel(); self.renderGroupHeader();
} cohortFormView.showMessage(gettext('Saved cohort'));
model.set('type', 'error'); });
model.set('title', message); },
this.undelegateViewEvents(this.errorNotifications); cancelSettings: function(event) {
event.preventDefault();
this.errorNotifications = new NotificationView({ this.render();
el: this.$('.cohort-errors'), },
model: model
}); setCohort: function(cohort) {
this.errorNotifications.render(); this.model = cohort;
}, this.render();
},
addNotifications: function(modifiedUsers) {
var oldCohort, title, details, numPresent, numUsersAdded, numErrors, addStudents: function(event) {
createErrorDetails, errorActionCallback, errorModel, event.preventDefault();
errorLimit = 5; var self = this,
cohorts = this.cohorts,
// Show confirmation messages. input = this.$('.cohort-management-group-add-students'),
this.undelegateViewEvents(this.confirmationNotifications); add_url = this.model.url() + '/add',
numUsersAdded = modifiedUsers.added.length + modifiedUsers.changed.length; students = input.val().trim(),
numPresent = modifiedUsers.present.length; cohortId = this.model.id;
if (numUsersAdded > 0 || numPresent > 0) {
title = interpolate_text( if (students.length > 0) {
ngettext("{numUsersAdded} student has been added to this cohort", $.post(
"{numUsersAdded} students have been added to this cohort", numUsersAdded), add_url, {'users': students}
{numUsersAdded: numUsersAdded} ).done(function(modifiedUsers) {
); self.refreshCohorts().done(function() {
// Find the equivalent cohort in the new collection and select it
var movedByCohort = {}; var cohort = cohorts.get(cohortId);
_.each(modifiedUsers.changed, function (changedInfo) { self.setCohort(cohort);
oldCohort = changedInfo.previous_cohort;
if (oldCohort in movedByCohort) { // Show the notifications
movedByCohort[oldCohort] = movedByCohort[oldCohort] + 1; self.addNotifications(modifiedUsers);
// If an unknown user was specified then update the new input to have
// the original input's value. This is to allow the user to correct the
// value in case it was a typo.
if (modifiedUsers.unknown.length > 0) {
self.$('.cohort-management-group-add-students').val(students);
}
});
}).fail(function() {
self.showErrorMessage(gettext('Error adding students.'), true);
});
} else {
self.showErrorMessage(gettext('Enter a username or email.'), true);
input.val('');
} }
else { },
movedByCohort[oldCohort] = 1;
/**
* Refresh the cohort collection to get the latest set as well as up-to-date counts.
*/
refreshCohorts: function() {
return this.cohorts.fetch();
},
undelegateViewEvents: function (view) {
if (view) {
view.undelegateEvents();
} }
}); },
details = [];
for (oldCohort in movedByCohort) {
details.push(
interpolate_text(
ngettext("{numMoved} student was removed from {oldCohort}",
"{numMoved} students were removed from {oldCohort}", movedByCohort[oldCohort]),
{numMoved: movedByCohort[oldCohort], oldCohort: oldCohort}
)
);
}
if (numPresent > 0) {
details.push(
interpolate_text(
ngettext("{numPresent} student was already in the cohort",
"{numPresent} students were already in the cohort", numPresent),
{numPresent: numPresent}
)
);
}
this.confirmationNotifications = new NotificationView({
el: this.$('.cohort-confirmations'),
model: new NotificationModel({
type: "confirmation",
title: title,
details: details
})
});
this.confirmationNotifications.render();
}
else if (this.confirmationNotifications) {
this.confirmationNotifications.$el.html('');
this.confirmationNotifications = null;
}
// Show error messages.
this.undelegateViewEvents(this.errorNotifications);
numErrors = modifiedUsers.unknown.length;
if (numErrors > 0) {
createErrorDetails = function (unknownUsers, showAllErrors) {
var numErrors = unknownUsers.length, details = [];
for (var i = 0; i < (showAllErrors ? numErrors : Math.min(errorLimit, numErrors)); i++) { showErrorMessage: function(message, removeConfirmations, model) {
details.push(interpolate_text(gettext("Unknown user: {user}"), {user: unknownUsers[i]})); if (removeConfirmations && this.confirmationNotifications) {
this.undelegateViewEvents(this.confirmationNotifications);
this.confirmationNotifications.$el.html('');
this.confirmationNotifications = null;
} }
return details; if (model === undefined) {
}; model = new NotificationModel();
}
model.set('type', 'error');
model.set('title', message);
title = interpolate_text( this.undelegateViewEvents(this.errorNotifications);
ngettext("There was an error when trying to add students:",
"There were {numErrors} errors when trying to add students:", numErrors),
{numErrors: numErrors}
);
details = createErrorDetails(modifiedUsers.unknown, false);
errorActionCallback = function (view) { this.errorNotifications = new NotificationView({
view.model.set("actionText", null); el: this.$('.cohort-errors'),
view.model.set("details", createErrorDetails(modifiedUsers.unknown, true)); model: model
view.render(); });
}; this.errorNotifications.render();
},
addNotifications: function(modifiedUsers) {
var oldCohort, title, details, numPresent, numUsersAdded, numErrors,
createErrorDetails, errorActionCallback, errorModel,
errorLimit = 5;
// Show confirmation messages.
this.undelegateViewEvents(this.confirmationNotifications);
numUsersAdded = modifiedUsers.added.length + modifiedUsers.changed.length;
numPresent = modifiedUsers.present.length;
if (numUsersAdded > 0 || numPresent > 0) {
title = interpolate_text(
ngettext("{numUsersAdded} student has been added to this cohort",
"{numUsersAdded} students have been added to this cohort", numUsersAdded),
{numUsersAdded: numUsersAdded}
);
var movedByCohort = {};
_.each(modifiedUsers.changed, function (changedInfo) {
oldCohort = changedInfo.previous_cohort;
if (oldCohort in movedByCohort) {
movedByCohort[oldCohort] = movedByCohort[oldCohort] + 1;
}
else {
movedByCohort[oldCohort] = 1;
}
});
details = [];
for (oldCohort in movedByCohort) {
details.push(
interpolate_text(
ngettext("{numMoved} student was removed from {oldCohort}",
"{numMoved} students were removed from {oldCohort}", movedByCohort[oldCohort]),
{numMoved: movedByCohort[oldCohort], oldCohort: oldCohort}
)
);
}
if (numPresent > 0) {
details.push(
interpolate_text(
ngettext("{numPresent} student was already in the cohort",
"{numPresent} students were already in the cohort", numPresent),
{numPresent: numPresent}
)
);
}
errorModel = new NotificationModel({ this.confirmationNotifications = new NotificationView({
details: details, el: this.$('.cohort-confirmations'),
actionText: numErrors > errorLimit ? gettext("View all errors") : null, model: new NotificationModel({
actionCallback: errorActionCallback, type: "confirmation",
actionClass: 'action-expand' title: title,
}); details: details
})
});
this.confirmationNotifications.render();
}
else if (this.confirmationNotifications) {
this.confirmationNotifications.$el.html('');
this.confirmationNotifications = null;
}
this.showErrorMessage(title, false, errorModel); // Show error messages.
} this.undelegateViewEvents(this.errorNotifications);
else if (this.errorNotifications) { numErrors = modifiedUsers.unknown.length;
this.errorNotifications.$el.html(''); if (numErrors > 0) {
this.errorNotifications = null; createErrorDetails = function (unknownUsers, showAllErrors) {
} var numErrors = unknownUsers.length, details = [];
}
for (var i = 0; i < (showAllErrors ? numErrors : Math.min(errorLimit, numErrors)); i++) {
details.push(interpolate_text(gettext("Unknown user: {user}"), {user: unknownUsers[i]}));
}
return details;
};
title = interpolate_text(
ngettext("There was an error when trying to add students:",
"There were {numErrors} errors when trying to add students:", numErrors),
{numErrors: numErrors}
);
details = createErrorDetails(modifiedUsers.unknown, false);
errorActionCallback = function (view) {
view.model.set("actionText", null);
view.model.set("details", createErrorDetails(modifiedUsers.unknown, true));
view.render();
};
errorModel = new NotificationModel({
details: details,
actionText: numErrors > errorLimit ? gettext("View all errors") : null,
actionCallback: errorActionCallback,
actionClass: 'action-expand'
});
this.showErrorMessage(title, false, errorModel);
}
else if (this.errorNotifications) {
this.errorNotifications.$el.html('');
this.errorNotifications = null;
}
}
});
return CohortEditorView;
}); });
}).call(this, Backbone, _, $, gettext, ngettext, interpolate_text, edx.groups.CohortFormView, }).call(this, define || RequireJS.define);
NotificationModel, NotificationView);
var edx = edx || {}; ;(function (define) {
(function($, _, Backbone, gettext, interpolate_text, CohortModel, NotificationModel, NotificationView) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/models/cohort',
edx.groups = edx.groups || {}; 'js/models/notification', 'js/views/notification'],
function($, _, Backbone, gettext, CohortModel) {
edx.groups.CohortFormView = Backbone.View.extend({
events : { var CohortFormView = Backbone.View.extend({
'change .cohort-management-details-association-course input': 'onRadioButtonChange' events : {
}, 'change .cohort-management-details-association-course input': 'onRadioButtonChange'
},
initialize: function(options) {
this.template = _.template($('#cohort-form-tpl').text()); initialize: function(options) {
this.contentGroups = options.contentGroups; this.template = _.template($('#cohort-form-tpl').text());
this.context = options.context; this.contentGroups = options.contentGroups;
}, this.context = options.context;
},
showNotification: function(options, beforeElement) {
var model = new NotificationModel(options); showNotification: function(options, beforeElement) {
this.removeNotification(); var model = new NotificationModel(options);
this.notification = new NotificationView({ this.removeNotification();
model: model this.notification = new NotificationView({
}); model: model
beforeElement.before(this.notification.$el); });
this.notification.render(); beforeElement.before(this.notification.$el);
}, this.notification.render();
},
removeNotification: function() {
if (this.notification) { removeNotification: function() {
this.notification.remove(); if (this.notification) {
} this.notification.remove();
}, }
},
render: function() {
this.$el.html(this.template({ render: function() {
cohort: this.model, this.$el.html(this.template({
isDefaultCohort: this.isDefault(this.model.get('name')), cohort: this.model,
contentGroups: this.contentGroups, isDefaultCohort: this.isDefault(this.model.get('name')),
studioGroupConfigurationsUrl: this.context.studioGroupConfigurationsUrl contentGroups: this.contentGroups,
})); studioGroupConfigurationsUrl: this.context.studioGroupConfigurationsUrl
return this; }));
}, return this;
},
isDefault: function(name) {
var cohorts = this.model.collection; isDefault: function(name) {
if (_.isUndefined(cohorts)) { var cohorts = this.model.collection;
return false; if (_.isUndefined(cohorts)) {
} return false;
var randomModels = cohorts.where({assignment_type:'random'});
return (randomModels.length === 1) && (randomModels[0].get('name') === name);
},
onRadioButtonChange: function(event) {
var target = $(event.currentTarget),
groupsEnabled = target.val() === 'yes';
if (!groupsEnabled) {
// If the user has chosen 'no', then clear the selection by setting
// it to the first option which represents no selection.
this.$('.input-cohort-group-association').val('None');
}
// Enable the select if the user has chosen groups, else disable it
this.$('.input-cohort-group-association').prop('disabled', !groupsEnabled);
},
hasAssociatedContentGroup: function() {
return this.$('.radio-yes').prop('checked');
},
getSelectedContentGroup: function() {
var selectValue = this.$('.input-cohort-group-association').val(),
ids, groupId, userPartitionId, i, contentGroup;
if (!this.hasAssociatedContentGroup() || selectValue === 'None') {
return null;
}
ids = selectValue.split(':');
groupId = parseInt(ids[0]);
userPartitionId = parseInt(ids[1]);
for (i=0; i < this.contentGroups.length; i++) {
contentGroup = this.contentGroups[i];
if (contentGroup.get('id') === groupId && contentGroup.get('user_partition_id') === userPartitionId) {
return contentGroup;
}
}
return null;
},
getUpdatedCohortName: function() {
var cohortName = this.$('.cohort-name').val();
return cohortName ? cohortName.trim() : '';
},
getAssignmentType: function() {
return this.$('input[name="cohort-assignment-type"]:checked').val();
},
showMessage: function(message, type, details) {
this.showNotification(
{type: type || 'confirmation', title: message, details: details},
this.$('.form-fields')
);
},
validate: function(fieldData) {
var errorMessages;
errorMessages = [];
if (!fieldData.name) {
errorMessages.push(gettext('You must specify a name for the cohort'));
}
if (this.hasAssociatedContentGroup() && fieldData.group_id === null) {
if (this.$('.input-cohort-group-association').val() === 'None') {
errorMessages.push(gettext('You did not select a content group'));
} else {
// If a value was selected, then it must be for a non-existent/deleted content group
errorMessages.push(gettext('The selected content group does not exist'));
}
}
return errorMessages;
},
saveForm: function() {
var self = this,
cohort = this.model,
saveOperation = $.Deferred(),
isUpdate = !_.isUndefined(this.model.id),
fieldData, selectedContentGroup, selectedAssignmentType, errorMessages, showErrorMessage;
showErrorMessage = function(message, details) {
self.showMessage(message, 'error', details);
};
this.removeNotification();
selectedContentGroup = this.getSelectedContentGroup();
selectedAssignmentType = this.getAssignmentType();
fieldData = {
name: this.getUpdatedCohortName(),
group_id: selectedContentGroup ? selectedContentGroup.id : null,
user_partition_id: selectedContentGroup ? selectedContentGroup.get('user_partition_id') : null,
assignment_type: selectedAssignmentType
};
errorMessages = this.validate(fieldData);
if (errorMessages.length > 0) {
showErrorMessage(
isUpdate ? gettext("The cohort cannot be saved") : gettext("The cohort cannot be added"),
errorMessages
);
saveOperation.reject();
} else {
cohort.save(
fieldData, {patch: isUpdate, wait: true}
).done(function(result) {
cohort.id = result.id;
self.render(); // re-render to remove any now invalid error messages
saveOperation.resolve();
}).fail(function(result) {
var errorMessage = null;
try {
var jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error;
} catch(e) {
// Ignore the exception and show the default error message instead.
} }
if (!errorMessage) { var randomModels = cohorts.where({assignment_type:'random'});
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); return (randomModels.length === 1) && (randomModels[0].get('name') === name);
},
onRadioButtonChange: function(event) {
var target = $(event.currentTarget),
groupsEnabled = target.val() === 'yes';
if (!groupsEnabled) {
// If the user has chosen 'no', then clear the selection by setting
// it to the first option which represents no selection.
this.$('.input-cohort-group-association').val('None');
} }
showErrorMessage(errorMessage); // Enable the select if the user has chosen groups, else disable it
saveOperation.reject(); this.$('.input-cohort-group-association').prop('disabled', !groupsEnabled);
}); },
}
return saveOperation.promise(); hasAssociatedContentGroup: function() {
} return this.$('.radio-yes').prop('checked');
}); },
}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, NotificationModel, NotificationView);
getSelectedContentGroup: function() {
var selectValue = this.$('.input-cohort-group-association').val(),
ids, groupId, userPartitionId, i, contentGroup;
if (!this.hasAssociatedContentGroup() || selectValue === 'None') {
return null;
}
ids = selectValue.split(':');
groupId = parseInt(ids[0]);
userPartitionId = parseInt(ids[1]);
for (i=0; i < this.contentGroups.length; i++) {
contentGroup = this.contentGroups[i];
if (contentGroup.get('id') === groupId && contentGroup.get('user_partition_id') === userPartitionId) {
return contentGroup;
}
}
return null;
},
getUpdatedCohortName: function() {
var cohortName = this.$('.cohort-name').val();
return cohortName ? cohortName.trim() : '';
},
getAssignmentType: function() {
return this.$('input[name="cohort-assignment-type"]:checked').val();
},
showMessage: function(message, type, details) {
this.showNotification(
{type: type || 'confirmation', title: message, details: details},
this.$('.form-fields')
);
},
validate: function(fieldData) {
var errorMessages;
errorMessages = [];
if (!fieldData.name) {
errorMessages.push(gettext('You must specify a name for the cohort'));
}
if (this.hasAssociatedContentGroup() && fieldData.group_id === null) {
if (this.$('.input-cohort-group-association').val() === 'None') {
errorMessages.push(gettext('You did not select a content group'));
} else {
// If a value was selected, then it must be for a non-existent/deleted content group
errorMessages.push(gettext('The selected content group does not exist'));
}
}
return errorMessages;
},
saveForm: function() {
var self = this,
cohort = this.model,
saveOperation = $.Deferred(),
isUpdate = !_.isUndefined(this.model.id),
fieldData, selectedContentGroup, selectedAssignmentType, errorMessages, showErrorMessage;
showErrorMessage = function(message, details) {
self.showMessage(message, 'error', details);
};
this.removeNotification();
selectedContentGroup = this.getSelectedContentGroup();
selectedAssignmentType = this.getAssignmentType();
fieldData = {
name: this.getUpdatedCohortName(),
group_id: selectedContentGroup ? selectedContentGroup.id : null,
user_partition_id: selectedContentGroup ? selectedContentGroup.get('user_partition_id') : null,
assignment_type: selectedAssignmentType
};
errorMessages = this.validate(fieldData);
if (errorMessages.length > 0) {
showErrorMessage(
isUpdate ? gettext("The cohort cannot be saved") : gettext("The cohort cannot be added"),
errorMessages
);
saveOperation.reject();
} else {
cohort.save(
fieldData, {patch: isUpdate, wait: true}
).done(function(result) {
cohort.id = result.id;
self.render(); // re-render to remove any now invalid error messages
saveOperation.resolve();
}).fail(function(result) {
var errorMessage = null;
try {
var jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error;
} catch(e) {
// Ignore the exception and show the default error message instead.
}
if (!errorMessage) {
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
}
showErrorMessage(errorMessage);
saveOperation.reject();
});
}
return saveOperation.promise();
}
});
return CohortFormView;
});
}).call(this, define || RequireJS.define);
var edx = edx || {}; ;(function (define) {
(function($, _, Backbone, gettext, interpolate_text, CohortModel, CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView, NotificationModel, NotificationView, FileUploaderView,
InlineDiscussionsView, CourseWideDiscussionsView) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/models/cohort',
'js/groups/views/cohort_editor', 'js/groups/views/cohort_form',
'js/groups/views/course_cohort_settings_notification',
'js/groups/views/cohort_discussions_inline', 'js/groups/views/cohort_discussions_course_wide',
'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'],
function($, _, Backbone, gettext, CohortModel, CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView) {
var hiddenClass = 'is-hidden',
disabledClass = 'is-disabled';
var CohortsView = Backbone.View.extend({
events : {
'change .cohort-select': 'onCohortSelected',
'change .cohorts-state': 'onCohortsEnabledChanged',
'click .action-create': 'showAddCohortForm',
'click .cohort-management-add-form .action-save': 'saveAddCohortForm',
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload',
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
},
initialize: function(options) {
var model = this.model;
this.template = _.template($('#cohorts-tpl').text());
this.selectorTemplate = _.template($('#cohort-selector-tpl').text());
this.context = options.context;
this.contentGroups = options.contentGroups;
this.cohortSettings = options.cohortSettings;
model.on('sync', this.onSync, this);
// Update cohort counts when the user clicks back on the cohort management tab
// (for example, after uploading a csv file of cohort assignments and then
// checking results on data download tab).
$(this.getSectionCss('cohort_management')).click(function () {
model.fetch();
});
},
render: function() {
this.$el.html(this.template({
cohorts: this.model.models,
cohortsEnabled: this.cohortSettings.get('is_cohorted')
}));
this.onSync();
return this;
},
renderSelector: function(selectedCohort) {
this.$('.cohort-select').html(this.selectorTemplate({
cohorts: this.model.models,
selectedCohort: selectedCohort
}));
},
renderCourseCohortSettingsNotificationView: function() {
var cohortStateMessageNotificationView = new CourseCohortSettingsNotificationView({
el: $('.cohort-state-message'),
cohortEnabled: this.getCohortsEnabled()
});
cohortStateMessageNotificationView.render();
},
onSync: function(model, response, options) {
var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId),
hasCohorts = this.model.length > 0,
cohortNavElement = this.$('.cohort-management-nav'),
additionalCohortControlElement = this.$('.wrapper-cohort-supplemental'),
isModelUpdate;
isModelUpdate = function() {
// Distinguish whether this is a sync event for just one model, or if it is for
// an entire collection.
return options && options.patch && response.hasOwnProperty('user_partition_id');
};
this.hideAddCohortForm();
if (isModelUpdate()) {
// Refresh the selector in case the model's name changed
this.renderSelector(selectedCohort);
} else if (hasCohorts) {
cohortNavElement.removeClass(hiddenClass);
additionalCohortControlElement.removeClass(hiddenClass);
this.renderSelector(selectedCohort);
if (selectedCohort) {
this.showCohortEditor(selectedCohort);
}
} else {
cohortNavElement.addClass(hiddenClass);
additionalCohortControlElement.addClass(hiddenClass);
this.showNotification({
type: 'warning',
title: gettext('You currently have no cohorts configured'),
actionText: gettext('Add Cohort'),
actionClass: 'action-create',
actionIconClass: 'fa-plus'
});
}
},
var hiddenClass = 'is-hidden', getSelectedCohort: function() {
disabledClass = 'is-disabled'; var id = this.$('.cohort-select').val();
return id && this.model.get(parseInt(id));
edx.groups = edx.groups || {}; },
edx.groups.CohortsView = Backbone.View.extend({
events : {
'change .cohort-select': 'onCohortSelected',
'change .cohorts-state': 'onCohortsEnabledChanged',
'click .action-create': 'showAddCohortForm',
'click .cohort-management-add-form .action-save': 'saveAddCohortForm',
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload',
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
},
initialize: function(options) {
var model = this.model;
this.template = _.template($('#cohorts-tpl').text());
this.selectorTemplate = _.template($('#cohort-selector-tpl').text());
this.context = options.context;
this.contentGroups = options.contentGroups;
this.cohortSettings = options.cohortSettings;
model.on('sync', this.onSync, this);
// Update cohort counts when the user clicks back on the cohort management tab
// (for example, after uploading a csv file of cohort assignments and then
// checking results on data download tab).
$(this.getSectionCss('cohort_management')).click(function () {
model.fetch();
});
},
render: function() {
this.$el.html(this.template({
cohorts: this.model.models,
cohortsEnabled: this.cohortSettings.get('is_cohorted')
}));
this.onSync();
return this;
},
renderSelector: function(selectedCohort) {
this.$('.cohort-select').html(this.selectorTemplate({
cohorts: this.model.models,
selectedCohort: selectedCohort
}));
},
renderCourseCohortSettingsNotificationView: function() {
var cohortStateMessageNotificationView = new CourseCohortSettingsNotificationView({
el: $('.cohort-state-message'),
cohortEnabled: this.getCohortsEnabled()
});
cohortStateMessageNotificationView.render();
},
onSync: function(model, response, options) {
var selectedCohort = this.lastSelectedCohortId && this.model.get(this.lastSelectedCohortId),
hasCohorts = this.model.length > 0,
cohortNavElement = this.$('.cohort-management-nav'),
additionalCohortControlElement = this.$('.wrapper-cohort-supplemental'),
isModelUpdate;
isModelUpdate = function() {
// Distinguish whether this is a sync event for just one model, or if it is for
// an entire collection.
return options && options.patch && response.hasOwnProperty('user_partition_id');
};
this.hideAddCohortForm();
if (isModelUpdate()) {
// Refresh the selector in case the model's name changed
this.renderSelector(selectedCohort);
} else if (hasCohorts) {
cohortNavElement.removeClass(hiddenClass);
additionalCohortControlElement.removeClass(hiddenClass);
this.renderSelector(selectedCohort);
if (selectedCohort) {
this.showCohortEditor(selectedCohort);
}
} else {
cohortNavElement.addClass(hiddenClass);
additionalCohortControlElement.addClass(hiddenClass);
this.showNotification({
type: 'warning',
title: gettext('You currently have no cohorts configured'),
actionText: gettext('Add Cohort'),
actionClass: 'action-create',
actionIconClass: 'fa-plus'
});
}
},
getSelectedCohort: function() {
var id = this.$('.cohort-select').val();
return id && this.model.get(parseInt(id));
},
onCohortSelected: function(event) {
event.preventDefault();
var selectedCohort = this.getSelectedCohort();
this.lastSelectedCohortId = selectedCohort.get('id');
this.showCohortEditor(selectedCohort);
},
onCohortsEnabledChanged: function(event) {
event.preventDefault();
this.saveCohortSettings();
},
saveCohortSettings: function() {
var self = this,
cohortSettings,
fieldData = {is_cohorted: this.getCohortsEnabled()};
cohortSettings = this.cohortSettings;
cohortSettings.save(
fieldData, {patch: true, wait: true}
).done(function() {
self.render();
self.renderCourseCohortSettingsNotificationView();
}).fail(function(result) {
self.showNotification({
type: 'error',
title: gettext("We've encountered an error. Refresh your browser and then try again.")},
self.$('.cohorts-state-section')
);
});
},
getCohortsEnabled: function() {
return this.$('.cohorts-state').prop('checked');
},
showCohortEditor: function(cohort) {
this.removeNotification();
if (this.editor) {
this.editor.setCohort(cohort);
$('.cohort-management-group .group-header-title').focus();
} else {
this.editor = new CohortEditorView({
el: this.$('.cohort-management-group'),
model: cohort,
cohorts: this.model,
contentGroups: this.contentGroups,
context: this.context
});
this.editor.render();
$('.cohort-management-group .group-header-title').focus();
}
},
showNotification: function(options, beforeElement) {
var model = new NotificationModel(options);
this.removeNotification();
this.notification = new NotificationView({
model: model
});
if (!beforeElement) { onCohortSelected: function(event) {
beforeElement = this.$('.cohort-management-group'); event.preventDefault();
} var selectedCohort = this.getSelectedCohort();
beforeElement.before(this.notification.$el); this.lastSelectedCohortId = selectedCohort.get('id');
this.showCohortEditor(selectedCohort);
this.notification.render(); },
},
onCohortsEnabledChanged: function(event) {
removeNotification: function() { event.preventDefault();
if (this.notification) { this.saveCohortSettings();
this.notification.remove(); },
}
if (this.cohortFormView) { saveCohortSettings: function() {
this.cohortFormView.removeNotification(); var self = this,
} cohortSettings,
}, fieldData = {is_cohorted: this.getCohortsEnabled()};
cohortSettings = this.cohortSettings;
showAddCohortForm: function(event) { cohortSettings.save(
var newCohort; fieldData, {patch: true, wait: true}
event.preventDefault(); ).done(function() {
this.removeNotification(); self.render();
newCohort = new CohortModel(); self.renderCourseCohortSettingsNotificationView();
newCohort.url = this.model.url; }).fail(function(result) {
this.cohortFormView = new CohortFormView({
model: newCohort,
contentGroups: this.contentGroups,
context: this.context
});
this.cohortFormView.render();
this.$('.cohort-management-add-form').append(this.cohortFormView.$el);
this.cohortFormView.$('.cohort-name').focus();
this.setCohortEditorVisibility(false);
},
hideAddCohortForm: function() {
this.setCohortEditorVisibility(true);
if (this.cohortFormView) {
this.cohortFormView.remove();
this.cohortFormView = null;
}
},
setCohortEditorVisibility: function(showEditor) {
if (showEditor) {
this.$('.cohorts-state-section').removeClass(disabledClass).attr('aria-disabled', false);
this.$('.cohort-management-group').removeClass(hiddenClass);
this.$('.cohort-management-nav').removeClass(disabledClass).attr('aria-disabled', false);
} else {
this.$('.cohorts-state-section').addClass(disabledClass).attr('aria-disabled', true);
this.$('.cohort-management-group').addClass(hiddenClass);
this.$('.cohort-management-nav').addClass(disabledClass).attr('aria-disabled', true);
}
},
saveAddCohortForm: function(event) {
var self = this,
newCohort = this.cohortFormView.model;
event.preventDefault();
this.removeNotification();
this.cohortFormView.saveForm()
.done(function() {
self.lastSelectedCohortId = newCohort.id;
self.model.fetch().done(function() {
self.showNotification({ self.showNotification({
type: 'confirmation', type: 'error',
title: interpolate_text( title: gettext("We've encountered an error. Refresh your browser and then try again.")},
gettext('The {cohortGroupName} cohort has been created. You can manually add students to this cohort below.'), self.$('.cohorts-state-section')
{cohortGroupName: newCohort.get('name')} );
) });
},
getCohortsEnabled: function() {
return this.$('.cohorts-state').prop('checked');
},
showCohortEditor: function(cohort) {
this.removeNotification();
if (this.editor) {
this.editor.setCohort(cohort);
$('.cohort-management-group .group-header-title').focus();
} else {
this.editor = new CohortEditorView({
el: this.$('.cohort-management-group'),
model: cohort,
cohorts: this.model,
contentGroups: this.contentGroups,
context: this.context
}); });
this.editor.render();
$('.cohort-management-group .group-header-title').focus();
}
},
showNotification: function(options, beforeElement) {
var model = new NotificationModel(options);
this.removeNotification();
this.notification = new NotificationView({
model: model
});
if (!beforeElement) {
beforeElement = this.$('.cohort-management-group');
}
beforeElement.before(this.notification.$el);
this.notification.render();
},
removeNotification: function() {
if (this.notification) {
this.notification.remove();
}
if (this.cohortFormView) {
this.cohortFormView.removeNotification();
}
},
showAddCohortForm: function(event) {
var newCohort;
event.preventDefault();
this.removeNotification();
newCohort = new CohortModel();
newCohort.url = this.model.url;
this.cohortFormView = new CohortFormView({
model: newCohort,
contentGroups: this.contentGroups,
context: this.context
}); });
}); this.cohortFormView.render();
}, this.$('.cohort-management-add-form').append(this.cohortFormView.$el);
this.cohortFormView.$('.cohort-name').focus();
cancelAddCohortForm: function(event) { this.setCohortEditorVisibility(false);
event.preventDefault(); },
this.removeNotification();
this.onSync(); hideAddCohortForm: function() {
}, this.setCohortEditorVisibility(true);
if (this.cohortFormView) {
showSection: function(event) { this.cohortFormView.remove();
event.preventDefault(); this.cohortFormView = null;
var section = $(event.currentTarget).data("section"); }
$(this.getSectionCss(section)).click(); },
$(window).scrollTop(0);
}, setCohortEditorVisibility: function(showEditor) {
if (showEditor) {
showCsvUpload: function(event) { this.$('.cohorts-state-section').removeClass(disabledClass).attr('aria-disabled', false);
event.preventDefault(); this.$('.cohort-management-group').removeClass(hiddenClass);
this.$('.cohort-management-nav').removeClass(disabledClass).attr('aria-disabled', false);
$(event.currentTarget).addClass(hiddenClass); } else {
var uploadElement = this.$('.csv-upload').removeClass(hiddenClass); this.$('.cohorts-state-section').addClass(disabledClass).attr('aria-disabled', true);
this.$('.cohort-management-group').addClass(hiddenClass);
if (!this.fileUploaderView) { this.$('.cohort-management-nav').addClass(disabledClass).attr('aria-disabled', true);
this.fileUploaderView = new FileUploaderView({ }
el: uploadElement, },
title: gettext("Assign students to cohorts by uploading a CSV file."),
inputLabel: gettext("Choose a .csv file"), saveAddCohortForm: function(event) {
inputTip: gettext("Only properly formatted .csv files will be accepted."), var self = this,
submitButtonText: gettext("Upload File and Assign Students"), newCohort = this.cohortFormView.model;
extensions: ".csv", event.preventDefault();
url: this.context.uploadCohortsCsvUrl, this.removeNotification();
successNotification: function (file, event, data) { this.cohortFormView.saveForm()
var message = interpolate_text(gettext( .done(function() {
"Your file '{file}' has been uploaded. Allow a few minutes for processing." self.lastSelectedCohortId = newCohort.id;
), {file: file}); self.model.fetch().done(function() {
return new NotificationModel({ self.showNotification({
type: "confirmation", type: 'confirmation',
title: message title: interpolate_text(
gettext('The {cohortGroupName} cohort has been created. You can manually add students to this cohort below.'),
{cohortGroupName: newCohort.get('name')}
)
});
});
}); });
},
cancelAddCohortForm: function(event) {
event.preventDefault();
this.removeNotification();
this.onSync();
},
showSection: function(event) {
event.preventDefault();
var section = $(event.currentTarget).data("section");
$(this.getSectionCss(section)).click();
$(window).scrollTop(0);
},
showCsvUpload: function(event) {
event.preventDefault();
$(event.currentTarget).addClass(hiddenClass);
var uploadElement = this.$('.csv-upload').removeClass(hiddenClass);
if (!this.fileUploaderView) {
this.fileUploaderView = new FileUploaderView({
el: uploadElement,
title: gettext("Assign students to cohorts by uploading a CSV file."),
inputLabel: gettext("Choose a .csv file"),
inputTip: gettext("Only properly formatted .csv files will be accepted."),
submitButtonText: gettext("Upload File and Assign Students"),
extensions: ".csv",
url: this.context.uploadCohortsCsvUrl,
successNotification: function (file, event, data) {
var message = interpolate_text(gettext(
"Your file '{file}' has been uploaded. Allow a few minutes for processing."
), {file: file});
return new NotificationModel({
type: "confirmation",
title: message
});
}
}).render();
this.$('#file-upload-form-file').focus();
}
},
showDiscussionTopics: function(event) {
event.preventDefault();
$(event.currentTarget).addClass(hiddenClass);
var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass);
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
if(!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
} }
}).render(); },
this.$('#file-upload-form-file').focus();
} getSectionCss: function (section) {
}, return ".instructor-nav .nav-item a[data-section='" + section + "']";
showDiscussionTopics: function(event) { }
event.preventDefault(); });
return CohortsView;
$(event.currentTarget).addClass(hiddenClass);
var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass);
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
if(!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
},
getSectionCss: function (section) {
return ".instructor-nav .nav-item a[data-section='" + section + "']";
}
}); });
}).call(this, $, _, Backbone, gettext, interpolate_text, edx.groups.CohortModel, edx.groups.CohortEditorView, }).call(this, define || RequireJS.define);
edx.groups.CohortFormView, edx.groups.CourseCohortSettingsNotificationView, NotificationModel, NotificationView,
FileUploaderView, edx.groups.InlineDiscussionsView, edx.groups.CourseWideDiscussionsView);
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings', define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings',
'js/groups/models/cohort_discussions'], 'js/groups/models/cohort_discussions', 'js/groups/models/content_group'],
function($) { function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) {
return function(contentGroups, studioGroupConfigurationsUrl) { return function(contentGroups, studioGroupConfigurationsUrl) {
var contentGroupModels = $.map(contentGroups, function(group) {
return new ContentGroupModel({
id: group.id,
name: group.name,
user_partition_id: group.user_partition_id
});
});
var cohorts = new edx.groups.CohortCollection(), var cohorts = new CohortCollection(),
courseCohortSettings = new edx.groups.CourseCohortSettingsModel(), courseCohortSettings = new CourseCohortSettingsModel(),
discussionTopicsSettings = new edx.groups.DiscussionTopicsSettingsModel(); discussionTopicsSettings = new DiscussionTopicsSettingsModel();
var cohortManagementElement = $('.cohort-management'); var cohortManagementElement = $('.cohort-management');
...@@ -16,10 +23,10 @@ ...@@ -16,10 +23,10 @@
courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url'); courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url');
discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url'); discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url');
var cohortsView = new edx.groups.CohortsView({ var cohortsView = new CohortsView({
el: cohortManagementElement, el: cohortManagementElement,
model: cohorts, model: cohorts,
contentGroups: contentGroups, contentGroups: contentGroupModels,
cohortSettings: courseCohortSettings, cohortSettings: courseCohortSettings,
context: { context: {
discussionTopicsSettingsModel: discussionTopicsSettings, discussionTopicsSettingsModel: discussionTopicsSettings,
......
var edx = edx || {}; ;(function (define) {
(function($, _, Backbone, gettext) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext'], function($, _, Backbone, gettext) {
edx.groups = edx.groups || {}; var CourseCohortSettingsNotificationView = Backbone.View.extend({
initialize: function(options) {
edx.groups.CourseCohortSettingsNotificationView = Backbone.View.extend({ this.template = _.template($('#cohort-state-tpl').text());
initialize: function(options) { this.cohortEnabled = options.cohortEnabled;
this.template = _.template($('#cohort-state-tpl').text()); },
this.cohortEnabled = options.cohortEnabled;
},
render: function() { render: function() {
this.$el.html(this.template({})); this.$el.html(this.template({}));
this.showCohortStateMessage(); this.showCohortStateMessage();
return this; return this;
}, },
showCohortStateMessage: function () { showCohortStateMessage: function () {
var actionToggleMessage = this.$('.action-toggle-message'); var actionToggleMessage = this.$('.action-toggle-message');
AnimationUtil.triggerAnimation(actionToggleMessage); AnimationUtil.triggerAnimation(actionToggleMessage);
if (this.cohortEnabled) { if (this.cohortEnabled) {
actionToggleMessage.text(gettext('Cohorts Enabled')); actionToggleMessage.text(gettext('Cohorts Enabled'));
} else { } else {
actionToggleMessage.text(gettext('Cohorts Disabled')); actionToggleMessage.text(gettext('Cohorts Disabled'));
}
} }
} });
return CourseCohortSettingsNotificationView;
}); });
}).call(this, $, _, Backbone, gettext); }).call(this, define || RequireJS.define);
/*!
* mustache.js - Logic-less {{mustache}} templates with JavaScript
* http://github.com/janl/mustache.js
*/
var Mustache;
(function (exports) {
if (typeof module !== "undefined") {
module.exports = exports; // CommonJS
} else if (typeof define === "function") {
define(exports); // AMD
} else {
Mustache = exports; // <script>
}
}(function () {
var exports = {};
exports.name = "mustache.js";
exports.version = "0.5.1-dev";
exports.tags = ["{{", "}}"];
exports.parse = parse;
exports.clearCache = clearCache;
exports.compile = compile;
exports.compilePartial = compilePartial;
exports.render = render;
exports.Scanner = Scanner;
exports.Context = Context;
exports.Renderer = Renderer;
// This is here for backwards compatibility with 0.4.x.
exports.to_html = function (template, view, partials, send) {
var result = render(template, view, partials);
if (typeof send === "function") {
send(result);
} else {
return result;
}
};
var whiteRe = /\s*/;
var spaceRe = /\s+/;
var nonSpaceRe = /\S/;
var eqRe = /\s*=/;
var curlyRe = /\s*\}/;
var tagRe = /#|\^|\/|>|\{|&|=|!/;
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
function testRe(re, string) {
return RegExp.prototype.test.call(re, string);
}
function isWhitespace(string) {
return !testRe(nonSpaceRe, string);
}
var isArray = Array.isArray || function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
// OSWASP Guidelines: escape all non alphanumeric characters in ASCII space.
var jsCharsRe = /[\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\xFF\u2028\u2029]/gm;
function quote(text) {
var escaped = text.replace(jsCharsRe, function (c) {
return "\\u" + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
});
return '"' + escaped + '"';
}
function escapeRe(string) {
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
}
var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};
function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}
// Export these utility functions.
exports.isWhitespace = isWhitespace;
exports.isArray = isArray;
exports.quote = quote;
exports.escapeRe = escapeRe;
exports.escapeHtml = escapeHtml;
function Scanner(string) {
this.string = string;
this.tail = string;
this.pos = 0;
}
/**
* Returns `true` if the tail is empty (end of string).
*/
Scanner.prototype.eos = function () {
return this.tail === "";
};
/**
* Tries to match the given regular expression at the current position.
* Returns the matched text if it can match, `null` otherwise.
*/
Scanner.prototype.scan = function (re) {
var match = this.tail.match(re);
if (match && match.index === 0) {
this.tail = this.tail.substring(match[0].length);
this.pos += match[0].length;
return match[0];
}
return null;
};
/**
* Skips all text until the given regular expression can be matched. Returns
* the skipped string, which is the entire tail of this scanner if no match
* can be made.
*/
Scanner.prototype.scanUntil = function (re) {
var match, pos = this.tail.search(re);
switch (pos) {
case -1:
match = this.tail;
this.pos += this.tail.length;
this.tail = "";
break;
case 0:
match = null;
break;
default:
match = this.tail.substring(0, pos);
this.tail = this.tail.substring(pos);
this.pos += pos;
}
return match;
};
function Context(view, parent) {
this.view = view;
this.parent = parent;
this.clearCache();
}
Context.make = function (view) {
return (view instanceof Context) ? view : new Context(view);
};
Context.prototype.clearCache = function () {
this._cache = {};
};
Context.prototype.push = function (view) {
return new Context(view, this);
};
Context.prototype.lookup = function (name) {
var value = this._cache[name];
if (!value) {
if (name === ".") {
value = this.view;
} else {
var context = this;
while (context) {
if (name.indexOf(".") > 0) {
var names = name.split("."), i = 0;
value = context.view;
while (value && i < names.length) {
value = value[names[i++]];
}
} else {
value = context.view[name];
}
if (value != null) {
break;
}
context = context.parent;
}
}
this._cache[name] = value;
}
if (typeof value === "function") {
value = value.call(this.view);
}
return value;
};
function Renderer() {
this.clearCache();
}
Renderer.prototype.clearCache = function () {
this._cache = {};
this._partialCache = {};
};
Renderer.prototype.compile = function (tokens, tags) {
if (typeof tokens === "string") {
tokens = parse(tokens, tags);
}
var fn = compileTokens(tokens),
self = this;
return function (view) {
return fn(Context.make(view), self);
};
};
Renderer.prototype.compilePartial = function (name, tokens, tags) {
this._partialCache[name] = this.compile(tokens, tags);
return this._partialCache[name];
};
Renderer.prototype.render = function (template, view) {
var fn = this._cache[template];
if (!fn) {
fn = this.compile(template);
this._cache[template] = fn;
}
return fn(view);
};
Renderer.prototype._section = function (name, context, callback) {
var value = context.lookup(name);
switch (typeof value) {
case "object":
if (isArray(value)) {
var buffer = "";
for (var i = 0, len = value.length; i < len; ++i) {
buffer += callback(context.push(value[i]), this);
}
return buffer;
}
return value ? callback(context.push(value), this) : "";
case "function":
// TODO: The text should be passed to the callback plain, not rendered.
var sectionText = callback(context, this),
self = this;
var scopedRender = function (template) {
return self.render(template, context);
};
return value.call(context.view, sectionText, scopedRender) || "";
default:
if (value) {
return callback(context, this);
}
}
return "";
};
Renderer.prototype._inverted = function (name, context, callback) {
var value = context.lookup(name);
// From the spec: inverted sections may render text once based on the
// inverse value of the key. That is, they will be rendered if the key
// doesn't exist, is false, or is an empty list.
if (value == null || value === false || (isArray(value) && value.length === 0)) {
return callback(context, this);
}
return "";
};
Renderer.prototype._partial = function (name, context) {
var fn = this._partialCache[name];
if (fn) {
return fn(context, this);
}
return "";
};
Renderer.prototype._name = function (name, context, escape) {
var value = context.lookup(name);
if (typeof value === "function") {
value = value.call(context.view);
}
var string = (value == null) ? "" : String(value);
if (escape) {
return escapeHtml(string);
}
return string;
};
/**
* Low-level function that compiles the given `tokens` into a
* function that accepts two arguments: a Context and a
* Renderer. Returns the body of the function as a string if
* `returnBody` is true.
*/
function compileTokens(tokens, returnBody) {
var body = ['""'];
var token, method, escape;
for (var i = 0, len = tokens.length; i < len; ++i) {
token = tokens[i];
switch (token.type) {
case "#":
case "^":
method = (token.type === "#") ? "_section" : "_inverted";
body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
" " + compileTokens(token.tokens, true) + "\n" +
"})");
break;
case "{":
case "&":
case "name":
escape = token.type === "name" ? "true" : "false";
body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
break;
case ">":
body.push("r._partial(" + quote(token.value) + ", c)");
break;
case "text":
body.push(quote(token.value));
break;
}
}
// Convert to a string body.
body = "return " + body.join(" + ") + ";";
// Good for debugging.
// console.log(body);
if (returnBody) {
return body;
}
// For great evil!
return new Function("c, r", body);
}
function escapeTags(tags) {
if (tags.length === 2) {
return [
new RegExp(escapeRe(tags[0]) + "\\s*"),
new RegExp("\\s*" + escapeRe(tags[1]))
];
}
throw new Error("Invalid tags: " + tags.join(" "));
}
/**
* Forms the given linear array of `tokens` into a nested tree structure
* where tokens that represent a section have a "tokens" array property
* that contains all tokens that are in that section.
*/
function nestTokens(tokens) {
var tree = [];
var collector = tree;
var sections = [];
var token, section;
for (var i = 0; i < tokens.length; ++i) {
token = tokens[i];
switch (token.type) {
case "#":
case "^":
token.tokens = [];
sections.push(token);
collector.push(token);
collector = token.tokens;
break;
case "/":
if (sections.length === 0) {
throw new Error("Unopened section: " + token.value);
}
section = sections.pop();
if (section.value !== token.value) {
throw new Error("Unclosed section: " + section.value);
}
if (sections.length > 0) {
collector = sections[sections.length - 1].tokens;
} else {
collector = tree;
}
break;
default:
collector.push(token);
}
}
// Make sure there were no open sections when we're done.
section = sections.pop();
if (section) {
throw new Error("Unclosed section: " + section.value);
}
return tree;
}
/**
* Combines the values of consecutive text tokens in the given `tokens` array
* to a single token.
*/
function squashTokens(tokens) {
var lastToken;
for (var i = 0; i < tokens.length; ++i) {
var token = tokens[i];
if (lastToken && lastToken.type === "text" && token.type === "text") {
lastToken.value += token.value;
tokens.splice(i--, 1); // Remove this token from the array.
} else {
lastToken = token;
}
}
}
/**
* Breaks up the given `template` string into a tree of token objects. If
* `tags` is given here it must be an array with two string values: the
* opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
* course, the default is to use mustaches (i.e. Mustache.tags).
*/
function parse(template, tags) {
tags = tags || exports.tags;
var tagRes = escapeTags(tags);
var scanner = new Scanner(template);
var tokens = [], // Buffer to hold the tokens
spaces = [], // Indices of whitespace tokens on the current line
hasTag = false, // Is there a {{tag}} on the current line?
nonSpace = false; // Is there a non-space char on the current line?
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
var stripSpace = function () {
if (hasTag && !nonSpace) {
while (spaces.length) {
tokens.splice(spaces.pop(), 1);
}
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
};
var type, value, chr;
while (!scanner.eos()) {
value = scanner.scanUntil(tagRes[0]);
if (value) {
for (var i = 0, len = value.length; i < len; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push({type: "text", value: chr});
if (chr === "\n") {
stripSpace(); // Check for whitespace on the current line.
}
}
}
// Match the opening tag.
if (!scanner.scan(tagRes[0])) {
break;
}
hasTag = true;
type = scanner.scan(tagRe) || "name";
// Skip any whitespace between tag and value.
scanner.scan(whiteRe);
// Extract the tag value.
if (type === "=") {
value = scanner.scanUntil(eqRe);
scanner.scan(eqRe);
scanner.scanUntil(tagRes[1]);
} else if (type === "{") {
var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1]));
value = scanner.scanUntil(closeRe);
scanner.scan(curlyRe);
scanner.scanUntil(tagRes[1]);
} else {
value = scanner.scanUntil(tagRes[1]);
}
// Match the closing tag.
if (!scanner.scan(tagRes[1])) {
throw new Error("Unclosed tag at " + scanner.pos);
}
tokens.push({type: type, value: value});
if (type === "name" || type === "{" || type === "&") {
nonSpace = true;
}
// Set the tags for the next time around.
if (type === "=") {
tags = value.split(spaceRe);
tagRes = escapeTags(tags);
}
}
squashTokens(tokens);
return nestTokens(tokens);
}
// The high-level clearCache, compile, compilePartial, and render functions
// use this default renderer.
var _renderer = new Renderer();
/**
* Clears all cached templates and partials.
*/
function clearCache() {
_renderer.clearCache();
}
/**
* High-level API for compiling the given `tokens` down to a reusable
* function. If `tokens` is a string it will be parsed using the given `tags`
* before it is compiled.
*/
function compile(tokens, tags) {
return _renderer.compile(tokens, tags);
}
/**
* High-level API for compiling the `tokens` for the partial with the given
* `name` down to a reusable function. If `tokens` is a string it will be
* parsed using the given `tags` before it is compiled.
*/
function compilePartial(name, tokens, tags) {
return _renderer.compilePartial(name, tokens, tags);
}
/**
* High-level API for rendering the `template` using the given `view`. The
* optional `partials` object may be given here for convenience, but note that
* it will cause all partials to be re-compiled, thus hurting performance. Of
* course, this only matters if you're going to render the same template more
* than once. If so, it is best to call `compilePartial` before calling this
* function and to leave the `partials` argument blank.
*/
function render(template, view, partials) {
if (partials) {
for (var name in partials) {
compilePartial(name, partials[name]);
}
}
return _renderer.render(template, view);
}
return exports;
}()));
;(function (define) { ;(function (define) {
define(['backbone'], function(Backbone) {
'use strict'; 'use strict';
return function (courseId, SearchRouter, SearchForm, SearchCollection, SearchListView) { 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) {
var router = new SearchRouter(); return function (courseId) {
var form = new SearchForm();
var collection = new SearchCollection([], { courseId: courseId });
var results = new SearchListView({ collection: collection });
var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(router, 'search', function (query) { var router = new SearchRouter();
form.doSearch(query); var form = new CourseSearchForm();
}); var collection = new SearchCollection([], { courseId: courseId });
var results = new SearchResultsView({ collection: collection });
var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(form, 'search', function (query) { dispatcher.listenTo(router, 'search', function (query) {
results.showLoadingMessage(); form.doSearch(query);
collection.performSearch(query); });
router.navigate('search/' + query, { replace: true });
});
dispatcher.listenTo(form, 'clear', function () { dispatcher.listenTo(form, 'search', function (query) {
collection.cancelSearch(); results.showLoadingMessage();
results.clear(); collection.performSearch(query);
router.navigate(''); router.navigate('search/' + query, { replace: true });
}); });
dispatcher.listenTo(results, 'next', function () { dispatcher.listenTo(form, 'clear', function () {
collection.loadNextPage(); collection.cancelSearch();
}); results.clear();
router.navigate('');
});
dispatcher.listenTo(collection, 'search', function () { dispatcher.listenTo(results, 'next', function () {
results.render(); collection.loadNextPage();
}); });
dispatcher.listenTo(collection, 'next', function () { dispatcher.listenTo(collection, 'search', function () {
results.renderNext(); results.render();
}); });
dispatcher.listenTo(collection, 'error', function () { dispatcher.listenTo(collection, 'next', function () {
results.showErrorMessage(); results.renderNext();
}); });
}; dispatcher.listenTo(collection, 'error', function () {
results.showErrorMessage();
});
}); };
});
})(define || RequireJS.define); })(define || RequireJS.define);
RequireJS.require([
'jquery',
'backbone',
'js/search/course/search_app',
'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, SearchApp, SearchRouter, CourseSearchForm, SearchCollection, CourseSearchResultsView) {
'use strict';
var courseId = $('#courseware-search-results').data('courseId');
var app = new SearchApp(
courseId,
SearchRouter,
CourseSearchForm,
SearchCollection,
CourseSearchResultsView
);
Backbone.history.start();
});
;(function (define) { ;(function (define) {
define(['backbone'], function(Backbone) {
'use strict'; 'use strict';
return function (SearchRouter, SearchForm, SearchCollection, SearchListView) { 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) {
var router = new SearchRouter(); return function () {
var form = new SearchForm();
var collection = new SearchCollection([]);
var results = new SearchListView({ collection: collection });
var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(router, 'search', function (query) { var router = new SearchRouter();
form.doSearch(query); var form = new SearchForm();
}); var collection = new SearchCollection([]);
var results = new SearchListView({ collection: collection });
var dispatcher = _.clone(Backbone.Events);
dispatcher.listenTo(form, 'search', function (query) { dispatcher.listenTo(router, 'search', function (query) {
results.showLoadingMessage(); form.doSearch(query);
collection.performSearch(query); });
router.navigate('search/' + query, { replace: true });
});
dispatcher.listenTo(form, 'clear', function () { dispatcher.listenTo(form, 'search', function (query) {
collection.cancelSearch(); results.showLoadingMessage();
results.clear(); collection.performSearch(query);
router.navigate(''); router.navigate('search/' + query, { replace: true });
}); });
dispatcher.listenTo(results, 'next', function () { dispatcher.listenTo(form, 'clear', function () {
collection.loadNextPage(); collection.cancelSearch();
}); results.clear();
router.navigate('');
});
dispatcher.listenTo(results, 'reset', function () { dispatcher.listenTo(results, 'next', function () {
form.resetSearchForm(); collection.loadNextPage();
}); });
dispatcher.listenTo(collection, 'search', function () { dispatcher.listenTo(results, 'reset', function () {
results.render(); form.resetSearchForm();
}); });
dispatcher.listenTo(collection, 'next', function () { dispatcher.listenTo(collection, 'search', function () {
results.renderNext(); results.render();
}); });
dispatcher.listenTo(collection, 'error', function () { dispatcher.listenTo(collection, 'next', function () {
results.showErrorMessage(); results.renderNext();
}); });
}; dispatcher.listenTo(collection, 'error', function () {
results.showErrorMessage();
});
}); };
});
})(define || RequireJS.define); })(define || RequireJS.define);
RequireJS.require([
'backbone',
'js/search/dashboard/search_app',
'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, SearchApp, SearchRouter, DashSearchForm, SearchCollection, DashSearchResultsView) {
'use strict';
var app = new SearchApp(
SearchRouter,
DashSearchForm,
SearchCollection,
DashSearchResultsView
);
Backbone.history.start();
});
define({
org: {
name: 'Organization',
terms: {
edX1: "edX_1"
}
},
modes: {
name: 'Course Type',
terms: {
honor: 'Honor',
verified: 'Verified'
}
},
language: {
en: 'English',
hr: 'Croatian'
}
});
...@@ -4,7 +4,7 @@ define([ ...@@ -4,7 +4,7 @@ define([
'logger', 'logger',
'common/js/spec_helpers/ajax_helpers', 'common/js/spec_helpers/ajax_helpers',
'common/js/spec_helpers/template_helpers', 'common/js/spec_helpers/template_helpers',
'js/discovery/app', 'js/discovery/discovery_factory',
'js/discovery/collection', 'js/discovery/collection',
'js/discovery/form', 'js/discovery/form',
'js/discovery/result', 'js/discovery/result',
...@@ -14,16 +14,14 @@ define([ ...@@ -14,16 +14,14 @@ define([
'js/discovery/filters', 'js/discovery/filters',
'js/discovery/filter_bar_view', 'js/discovery/filter_bar_view',
'js/discovery/filter_view', 'js/discovery/filter_view',
'js/discovery/search_facets_view', 'js/discovery/search_facets_view'
'js/discovery/facet_view',
'js/discovery/facets_view'
], function( ], function(
$, $,
Backbone, Backbone,
Logger, Logger,
AjaxHelpers, AjaxHelpers,
TemplateHelpers, TemplateHelpers,
App, DiscoveryFactory,
Collection, Collection,
DiscoveryForm, DiscoveryForm,
ResultItem, ResultItem,
...@@ -33,9 +31,7 @@ define([ ...@@ -33,9 +31,7 @@ define([
FiltersCollection, FiltersCollection,
FiltersBarView, FiltersBarView,
FilterView, FilterView,
SearchFacetView, SearchFacetView
FacetView,
FacetsView
) { ) {
'use strict'; 'use strict';
...@@ -113,126 +109,116 @@ define([ ...@@ -113,126 +109,116 @@ define([
var SEARCH_FILTER = {"type": "search_string", "query": "search3"}; var SEARCH_FILTER = {"type": "search_string", "query": "search3"};
describe('Collection', function () { describe('Course Discovery', function () {
beforeEach(function () { describe('Collection', function () {
this.collection = new Collection();
this.onSearch = jasmine.createSpy('onSearch'); beforeEach(function () {
this.collection.on('search', this.onSearch); this.collection = new Collection();
this.onNext = jasmine.createSpy('onNext'); this.onSearch = jasmine.createSpy('onSearch');
this.collection.on('next', this.onNext); this.collection.on('search', this.onSearch);
this.onError = jasmine.createSpy('onError'); this.onNext = jasmine.createSpy('onNext');
this.collection.on('error', this.onError); this.collection.on('next', this.onNext);
});
it('sends a request and parses the json result', function () {
var requests = AjaxHelpers.requests(this);
this.collection.performSearch('search string');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect(this.onSearch).toHaveBeenCalled();
expect(this.collection.totalCount).toEqual(365);
expect(this.collection.latestModels()[0].attributes).toEqual(JSON_RESPONSE.results[0].data);
expect(this.collection.page).toEqual(0);
});
it('handles errors', function () { this.onError = jasmine.createSpy('onError');
var requests = AjaxHelpers.requests(this); this.collection.on('error', this.onError);
this.collection.performSearch('search string');
AjaxHelpers.respondWithError(requests);
expect(this.onSearch).not.toHaveBeenCalled();
expect(this.onError).toHaveBeenCalled();
this.collection.loadNextPage();
AjaxHelpers.respondWithError(requests);
expect(this.onSearch).not.toHaveBeenCalled();
expect(this.onError).toHaveBeenCalled();
});
it('loads next page', function () {
var requests = AjaxHelpers.requests(this);
var response = { total: 35, results: [] };
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
expect(this.onNext).toHaveBeenCalled();
expect(this.onError).not.toHaveBeenCalled();
});
it('sends correct paging parameters', function () {
var requests = AjaxHelpers.requests(this);
var response = { total: 52, results: [] };
this.collection.performSearch('search string');
AjaxHelpers.respondWithJson(requests, response);
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
spyOn($, 'ajax');
this.collection.loadNextPage();
expect($.ajax.mostRecentCall.args[0].url).toEqual(this.collection.url);
expect($.ajax.mostRecentCall.args[0].data).toEqual({
search_string : 'search string',
page_size : this.collection.pageSize,
page_index : 2
}); });
});
it('has next page', function () { it('sends a request and parses the json result', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
var response = { total: 35, access_denied_count: 5, results: [] }; this.collection.performSearch('search string');
this.collection.performSearch('search string'); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
AjaxHelpers.respondWithJson(requests, response); expect(this.onSearch).toHaveBeenCalled();
expect(this.collection.hasNextPage()).toEqual(true); expect(this.collection.totalCount).toEqual(365);
this.collection.loadNextPage(); expect(this.collection.latestModels()[0].attributes).toEqual(JSON_RESPONSE.results[0].data);
AjaxHelpers.respondWithJson(requests, response); expect(this.collection.page).toEqual(0);
expect(this.collection.hasNextPage()).toEqual(false); });
});
it('resets state when performing new search', function () { it('handles errors', function () {
this.collection.add(new ResultItem()); var requests = AjaxHelpers.requests(this);
expect(this.collection.length).toEqual(1); this.collection.performSearch('search string');
this.collection.performSearch('search string'); AjaxHelpers.respondWithError(requests);
expect(this.collection.length).toEqual(0); expect(this.onSearch).not.toHaveBeenCalled();
expect(this.collection.page).toEqual(0); expect(this.onError).toHaveBeenCalled();
expect(this.collection.totalCount).toEqual(0); this.collection.loadNextPage();
expect(this.collection.latestModelsCount).toEqual(0); AjaxHelpers.respondWithError(requests);
}); expect(this.onSearch).not.toHaveBeenCalled();
expect(this.onError).toHaveBeenCalled();
});
}); it('loads next page', function () {
var requests = AjaxHelpers.requests(this);
var response = { total: 35, results: [] };
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
expect(this.onNext).toHaveBeenCalled();
expect(this.onError).not.toHaveBeenCalled();
});
it('sends correct paging parameters', function () {
var requests = AjaxHelpers.requests(this);
var response = { total: 52, results: [] };
this.collection.performSearch('search string');
AjaxHelpers.respondWithJson(requests, response);
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
spyOn($, 'ajax');
this.collection.loadNextPage();
expect($.ajax.mostRecentCall.args[0].url).toEqual(this.collection.url);
expect($.ajax.mostRecentCall.args[0].data).toEqual({
search_string : 'search string',
page_size : this.collection.pageSize,
page_index : 2
});
});
describe('ResultItem', function () { it('has next page', function () {
var requests = AjaxHelpers.requests(this);
var response = { total: 35, access_denied_count: 5, results: [] };
this.collection.performSearch('search string');
AjaxHelpers.respondWithJson(requests, response);
expect(this.collection.hasNextPage()).toEqual(true);
this.collection.loadNextPage();
AjaxHelpers.respondWithJson(requests, response);
expect(this.collection.hasNextPage()).toEqual(false);
});
beforeEach(function () { it('resets state when performing new search', function () {
this.result = new ResultItem(); this.collection.add(new ResultItem());
}); expect(this.collection.length).toEqual(1);
this.collection.performSearch('search string');
expect(this.collection.length).toEqual(0);
expect(this.collection.page).toEqual(0);
expect(this.collection.totalCount).toEqual(0);
expect(this.collection.latestModelsCount).toEqual(0);
});
it('has properties', function () {
expect(this.result.get('modes')).toBeDefined();
expect(this.result.get('course')).toBeDefined();
expect(this.result.get('enrollment_start')).toBeDefined();
expect(this.result.get('number')).toBeDefined();
expect(this.result.get('content')).toEqual({
display_name: '',
number: '',
overview: ''
});
expect(this.result.get('start')).toBeDefined();
expect(this.result.get('image_url')).toBeDefined();
expect(this.result.get('org')).toBeDefined();
expect(this.result.get('id')).toBeDefined();
}); });
});
describe('ResultItem', function () {
describe('ResultItemView', function () { beforeEach(function () {
this.result = new ResultItem();
});
beforeEach(function () { it('has properties', function () {
TemplateHelpers.installTemplate('templates/discovery/result_item'); expect(this.result.get('modes')).toBeDefined();
this.item = new ResultItemView({ expect(this.result.get('course')).toBeDefined();
model: new ResultItem(JSON_RESPONSE.results[0].data) expect(this.result.get('enrollment_start')).toBeDefined();
expect(this.result.get('number')).toBeDefined();
expect(this.result.get('content')).toEqual({
display_name: '',
number: '',
overview: ''
});
expect(this.result.get('start')).toBeDefined();
expect(this.result.get('image_url')).toBeDefined();
expect(this.result.get('org')).toBeDefined();
expect(this.result.get('id')).toBeDefined();
}); });
});
it('renders correctly', function () { it('renders correctly', function () {
var data = this.item.model.attributes; var data = this.item.model.attributes;
...@@ -246,350 +232,383 @@ define([ ...@@ -246,350 +232,383 @@ define([
expect(this.item.$el.find('.course-date')).toContainHtml('Jan 01, 1970'); expect(this.item.$el.find('.course-date')).toContainHtml('Jan 01, 1970');
}); });
});
describe('ResultItemView', function () {
describe('DiscoveryForm', function () { beforeEach(function () {
TemplateHelpers.installTemplate('templates/discovery/result_item');
this.item = new ResultItemView({
model: new ResultItem(JSON_RESPONSE.results[0].data)
});
});
beforeEach(function () { it('renders correctly', function () {
loadFixtures('js/fixtures/discovery.html'); var data = this.item.model.attributes;
this.form = new DiscoveryForm(); this.item.render();
this.onSearch = jasmine.createSpy('onSearch'); expect(this.item.$el).toContainHtml(data.content.display_name);
this.form.on('search', this.onSearch); expect(this.item.$el).toContain('a[href="/courses/' + data.course + '/info"]');
}); expect(this.item.$el).toContain('img[src="' + data.image_url + '"]');
expect(this.item.$el.find('.course-name')).toContainHtml(data.org);
expect(this.item.$el.find('.course-name')).toContainHtml(data.content.number);
expect(this.item.$el.find('.course-name')).toContainHtml(data.content.display_name);
expect(this.item.$el.find('.course-date')).toContainHtml('Jan 01, 1970');
});
it('trims input string', function () {
var term = ' search string ';
$('.discovery-input').val(term);
$('form').trigger('submit');
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
}); });
it('handles calls to doSearch', function () {
var term = ' search string ';
$('.discovery-input').val(term);
this.form.doSearch(term);
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
expect($('.discovery-input').val()).toEqual(term);
expect($('#discovery-message')).toBeEmpty();
});
it('clears search', function () { describe('DiscoveryForm', function () {
$('.discovery-input').val('somethig');
this.form.clearSearch();
expect($('.discovery-input').val()).toEqual('');
});
it('shows/hides loading indicator', function () { beforeEach(function () {
this.form.showLoadingIndicator(); loadFixtures('js/fixtures/discovery.html');
expect($('#loading-indicator')).not.toHaveClass('hidden'); this.form = new DiscoveryForm();
this.form.hideLoadingIndicator(); this.onSearch = jasmine.createSpy('onSearch');
expect($('#loading-indicator')).toHaveClass('hidden'); this.form.on('search', this.onSearch);
}); });
it('shows messages', function () { it('trims input string', function () {
this.form.showNotFoundMessage(); var term = ' search string ';
expect($('#discovery-message')).not.toBeEmpty(); $('.discovery-input').val(term);
this.form.showErrorMessage(); $('form').trigger('submit');
expect($('#discovery-message')).not.toBeEmpty(); expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
}); });
}); it('handles calls to doSearch', function () {
var term = ' search string ';
$('.discovery-input').val(term);
this.form.doSearch(term);
expect(this.onSearch).toHaveBeenCalledWith($.trim(term));
expect($('.discovery-input').val()).toEqual(term);
expect($('#discovery-message')).toBeEmpty();
});
describe('FilterBarView', function () { it('clears search', function () {
beforeEach(function () { $('.discovery-input').val('somethig');
loadFixtures('js/fixtures/discovery.html'); this.form.clearSearch();
TemplateHelpers.installTemplates( expect($('.discovery-input').val()).toEqual('');
['templates/discovery/filter_bar', });
'templates/discovery/filter']
);
this.filterBar = new FiltersBarView();
this.onClear = jasmine.createSpy('onClear');
this.filterBar.on('clear', this.onClear);
});
it('view searches for sent facet object', function () { it('shows/hides loading indicator', function () {
expect(this.filterBar.$el.length).toBe(1); this.form.showLoadingIndicator();
this.filterBar.addFilter(FACET_LIST[0]); expect($('#loading-indicator')).not.toHaveClass('hidden');
expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible(); this.form.hideLoadingIndicator();
}); expect($('#loading-indicator')).toHaveClass('hidden');
});
it('view searches for entered search string', function () { it('shows messages', function () {
spyOn(this.filterBar, 'addFilter').andCallThrough(); this.form.showNotFoundMessage();
expect(this.filterBar.$el.length).toBe(1); expect($('#discovery-message')).not.toBeEmpty();
this.filterBar.changeQueryFilter(SEARCH_FILTER.query); this.form.showErrorMessage();
expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible(); expect($('#discovery-message')).not.toBeEmpty();
expect(this.filterBar.addFilter).toHaveBeenCalledWith(SEARCH_FILTER); });
});
it('model cleans view on destruction correctly', function () {
this.filterBar.addFilter(SEARCH_FILTER);
var model = this.filterBar.collection.findWhere(SEARCH_FILTER);
expect(this.filterBar.$el.find('.active-filter').length).toBe(1);
model.cleanModelView();
expect(this.filterBar.$el.find('.active-filter').length).toBe(0);
}); });
it('view removes all filters and hides bar if clear all', function () { describe('FilterBarView', function () {
spyOn(this.filterBar, 'clearAll').andCallThrough(); beforeEach(function () {
this.filterBar.delegateEvents(); loadFixtures('js/fixtures/discovery.html');
this.filterBar.addFilter(SEARCH_FILTER); TemplateHelpers.installTemplates(
var clearAll = this.filterBar.$el.find('#clear-all-filters'); ['templates/discovery/filter_bar',
expect(clearAll).toBeVisible(); 'templates/discovery/filter']
clearAll.trigger('click'); );
expect(this.filterBar.clearAll).toHaveBeenCalled(); this.filterBar = new FiltersBarView();
expect(this.onClear).toHaveBeenCalled(); this.onClear = jasmine.createSpy('onClear');
}); this.filterBar.on('clear', this.onClear);
});
it('view hides bar if all filters removed', function () { it('view searches for sent facet object', function () {
spyOn(this.filterBar, 'clearFilter').andCallThrough(); expect(this.filterBar.$el.length).toBe(1);
this.filterBar.delegateEvents(); this.filterBar.addFilter(FACET_LIST[0]);
this.filterBar.addFilter(SEARCH_FILTER); expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible();
var clearAll = this.filterBar.$el.find('#clear-all-filters'); });
expect(clearAll).toBeVisible();
var filter = this.filterBar.$el.find('li .discovery-button');
filter.trigger('click');
expect(this.filterBar.clearFilter).toHaveBeenCalled();
expect(this.onClear).toHaveBeenCalled();
});
it('view changes query filter', function () { it('view searches for entered search string', function () {
this.filterBar.addFilter(SEARCH_FILTER); spyOn(this.filterBar, 'addFilter').andCallThrough();
var filter = $(this.filterBar.$el.find('li .discovery-button')[0]); expect(this.filterBar.$el.length).toBe(1);
expect(filter.text().trim()).toBe(SEARCH_FILTER.query); this.filterBar.changeQueryFilter(SEARCH_FILTER.query);
// Have to explicitly remove model because events not dispatched expect(this.filterBar.$el.find('#clear-all-filters')).toBeVisible();
var model = this.filterBar.collection.findWhere(SEARCH_FILTER); expect(this.filterBar.addFilter).toHaveBeenCalledWith(SEARCH_FILTER);
model.cleanModelView(); });
this.filterBar.changeQueryFilter(SEARCH_FILTER.query + '2');
filter = $(this.filterBar.$el.find('li .discovery-button')[0]);
expect(filter.text().trim()).toBe(SEARCH_FILTER.query + '2');
});
it('view returns correct search term', function () { it('model cleans view on destruction correctly', function () {
this.filterBar.addFilter(SEARCH_FILTER); this.filterBar.addFilter(SEARCH_FILTER);
expect(this.filterBar.getSearchTerm()).toBe(SEARCH_FILTER.query); var model = this.filterBar.collection.findWhere(SEARCH_FILTER);
}); expect(this.filterBar.$el.find('.active-filter').length).toBe(1);
model.cleanModelView();
expect(this.filterBar.$el.find('.active-filter').length).toBe(0);
});
}); it('view removes all filters and hides bar if clear all', function () {
spyOn(this.filterBar, 'clearAll').andCallThrough();
this.filterBar.delegateEvents();
this.filterBar.addFilter(SEARCH_FILTER);
var clearAll = this.filterBar.$el.find('#clear-all-filters');
expect(clearAll).toBeVisible();
clearAll.trigger('click');
expect(this.filterBar.clearAll).toHaveBeenCalled();
expect(this.onClear).toHaveBeenCalled();
});
describe('SearchFacetView', function () { it('view hides bar if all filters removed', function () {
beforeEach(function () { spyOn(this.filterBar, 'clearFilter').andCallThrough();
loadFixtures('js/fixtures/discovery.html'); this.filterBar.delegateEvents();
TemplateHelpers.installTemplates([ this.filterBar.addFilter(SEARCH_FILTER);
'templates/discovery/search_facet', var clearAll = this.filterBar.$el.find('#clear-all-filters');
'templates/discovery/search_facets_section', expect(clearAll).toBeVisible();
'templates/discovery/search_facets_list', var filter = this.filterBar.$el.find('li .discovery-button');
'templates/discovery/more_less_links' filter.trigger('click');
]); expect(this.filterBar.clearFilter).toHaveBeenCalled();
var facetsTypes = {org: 'Organization', modes: 'Course Type'}; expect(this.onClear).toHaveBeenCalled();
this.searchFacetView = new SearchFacetView(facetsTypes); });
this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
this.onAddFilter = jasmine.createSpy('onAddFilter');
this.searchFacetView.on('addFilter', this.onAddFilter);
});
it('view expands more content on show more click', function () { it('view changes query filter', function () {
var $showMore = this.searchFacetView.$el.find('.show-more'); this.filterBar.addFilter(SEARCH_FILTER);
var $showLess = this.searchFacetView.$el.find('.show-less'); var filter = $(this.filterBar.$el.find('li .discovery-button')[0]);
var $ul = $showMore.parent('div').siblings('ul'); expect(filter.text().trim()).toBe(SEARCH_FILTER.query);
expect($showMore).not.toHaveClass('hidden'); // Have to explicitly remove model because events not dispatched
expect($showLess).toHaveClass('hidden'); var model = this.filterBar.collection.findWhere(SEARCH_FILTER);
expect($ul).toHaveClass('collapse'); model.cleanModelView();
$showMore.trigger('click'); this.filterBar.changeQueryFilter(SEARCH_FILTER.query + '2');
expect($showMore).toHaveClass('hidden'); filter = $(this.filterBar.$el.find('li .discovery-button')[0]);
expect($showLess).not.toHaveClass('hidden'); expect(filter.text().trim()).toBe(SEARCH_FILTER.query + '2');
expect($ul).not.toHaveClass('collapse'); });
});
it('view collapses content on show less click', function () { it('view returns correct search term', function () {
var $showMore = this.searchFacetView.$el.find('.show-more'); this.filterBar.addFilter(SEARCH_FILTER);
var $showLess = this.searchFacetView.$el.find('.show-less'); expect(this.filterBar.getSearchTerm()).toBe(SEARCH_FILTER.query);
var $ul = $showMore.parent('div').siblings('ul'); });
$showMore.trigger('click');
expect($showMore).toHaveClass('hidden');
expect($showLess).not.toHaveClass('hidden');
expect($ul).not.toHaveClass('collapse');
$showLess.trigger('click');
expect($showMore).not.toHaveClass('hidden');
expect($showLess).toHaveClass('hidden');
expect($ul).toHaveClass('collapse');
});
it('view triggers addFilter event if facet is clicked', function () {
this.searchFacetView.delegateEvents();
var $facetLink = this.searchFacetView.$el.find('li [data-value="edX1"]');
var $facet = $facetLink.parent('li');
$facet.trigger('click');
expect(this.onAddFilter).toHaveBeenCalledWith(
{
type: $facet.data('facet'),
query: $facetLink.data('value'),
name : $facetLink.data('text')
}
);
}); });
it('re-render facets on second click', function () { describe('SearchFacetView', function () {
// First search beforeEach(function () {
this.searchFacetView.delegateEvents(); loadFixtures('js/fixtures/discovery.html');
this.searchFacetView.renderFacets(JSON_RESPONSE.facets); TemplateHelpers.installTemplates([
expect(this.searchFacetView.facetViews.length).toBe(2); 'templates/discovery/search_facet',
// Setup spy 'templates/discovery/search_facets_section',
var customView = this.searchFacetView.facetViews[0]; 'templates/discovery/search_facets_list',
spyOn(customView, 'remove').andCallThrough(); 'templates/discovery/more_less_links'
// Second search ]);
this.searchFacetView.renderFacets(JSON_RESPONSE.facets); var facetsTypes = {org: 'Organization', modes: 'Course Type'};
expect(this.searchFacetView.facetViews.length).toBe(2); this.searchFacetView = new SearchFacetView(facetsTypes);
expect(customView.remove).toHaveBeenCalled(); this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
}); this.onAddFilter = jasmine.createSpy('onAddFilter');
this.searchFacetView.on('addFilter', this.onAddFilter);
});
}); it('view expands more content on show more click', function () {
var $showMore = this.searchFacetView.$el.find('.show-more');
var $showLess = this.searchFacetView.$el.find('.show-less');
var $ul = $showMore.parent('div').siblings('ul');
expect($showMore).not.toHaveClass('hidden');
expect($showLess).toHaveClass('hidden');
expect($ul).toHaveClass('collapse');
$showMore.trigger('click');
expect($showMore).toHaveClass('hidden');
expect($showLess).not.toHaveClass('hidden');
expect($ul).not.toHaveClass('collapse');
});
describe('ResultListView', function () { it('view collapses content on show less click', function () {
var $showMore = this.searchFacetView.$el.find('.show-more');
var $showLess = this.searchFacetView.$el.find('.show-less');
var $ul = $showMore.parent('div').siblings('ul');
$showMore.trigger('click');
expect($showMore).toHaveClass('hidden');
expect($showLess).not.toHaveClass('hidden');
expect($ul).not.toHaveClass('collapse');
$showLess.trigger('click');
expect($showMore).not.toHaveClass('hidden');
expect($showLess).toHaveClass('hidden');
expect($ul).toHaveClass('collapse');
});
beforeEach(function () { it('view triggers addFilter event if facet is clicked', function () {
jasmine.Clock.useMock(); this.searchFacetView.delegateEvents();
loadFixtures('js/fixtures/discovery.html'); var $facetLink = this.searchFacetView.$el.find('li [data-value="edX1"]');
TemplateHelpers.installTemplate('templates/discovery/result_item'); var $facet = $facetLink.parent('li');
var collection = new Collection([JSON_RESPONSE.results[0].data]); $facet.trigger('click');
collection.latestModelsCount = 1; expect(this.onAddFilter).toHaveBeenCalledWith(
this.view = new ResultListView({ collection: collection }); {
}); type: $facet.data('facet'),
query: $facetLink.data('value'),
name : $facetLink.data('text')
}
);
});
it('renders search results', function () { it('re-render facets on second click', function () {
this.view.render(); // First search
expect($('.courses-listing article').length).toEqual(1); this.searchFacetView.delegateEvents();
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course'); this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
this.view.renderNext(); expect(this.searchFacetView.facetViews.length).toBe(2);
expect($('.courses-listing article').length).toEqual(2); // Setup spy
}); var customView = this.searchFacetView.facetViews[0];
spyOn(customView, 'remove').andCallThrough();
// Second search
this.searchFacetView.renderFacets(JSON_RESPONSE.facets);
expect(this.searchFacetView.facetViews.length).toBe(2);
expect(customView.remove).toHaveBeenCalled();
});
it('scrolling triggers an event for next page', function () {
this.onNext = jasmine.createSpy('onNext');
this.view.on('next', this.onNext);
spyOn(this.view.collection, 'hasNextPage').andCallFake(function () {
return true;
});
this.view.render();
window.scroll(0, $(document).height());
$(window).trigger('scroll');
jasmine.Clock.tick(500);
expect(this.onNext).toHaveBeenCalled();
// should not be triggered again (while it is loading)
$(window).trigger('scroll');
jasmine.Clock.tick(500);
expect(this.onNext.calls.length).toEqual(1);
}); });
}); describe('ResultListView', function () {
beforeEach(function () {
jasmine.Clock.useMock();
loadFixtures('js/fixtures/discovery.html');
TemplateHelpers.installTemplate('templates/discovery/result_item');
var collection = new Collection([JSON_RESPONSE.results[0].data]);
collection.latestModelsCount = 1;
this.view = new ResultListView({ collection: collection });
});
describe('Discovery App', function () { it('renders search results', function () {
this.view.render();
beforeEach(function () { expect($('.courses-listing article').length).toEqual(1);
loadFixtures('js/fixtures/discovery.html'); expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
TemplateHelpers.installTemplates([ this.view.renderNext();
'templates/discovery/result_item', expect($('.courses-listing article').length).toEqual(2);
'templates/discovery/filter', });
'templates/discovery/filter_bar',
'templates/discovery/search_facet',
'templates/discovery/search_facets_section',
'templates/discovery/search_facets_list',
'templates/discovery/more_less_links'
]);
this.app = new App(
Collection,
DiscoveryForm,
ResultListView,
FiltersBarView,
SearchFacetView
);
});
it('performs search', function () { it('scrolling triggers an event for next page', function () {
var requests = AjaxHelpers.requests(this); this.onNext = jasmine.createSpy('onNext');
$('.discovery-input').val('test'); this.view.on('next', this.onNext);
$('.discovery-submit').trigger('click'); spyOn(this.view.collection, 'hasNextPage').andCallFake(function () {
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); return true;
expect($('.courses-listing article').length).toEqual(1); });
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course'); this.view.render();
expect($('.active-filter').length).toBe(1); window.scroll(0, $(document).height());
}); $(window).trigger('scroll');
jasmine.Clock.tick(500);
expect(this.onNext).toHaveBeenCalled();
// should not be triggered again (while it is loading)
$(window).trigger('scroll');
jasmine.Clock.tick(500);
expect(this.onNext.calls.length).toEqual(1);
});
it('loads more', function () {
var requests = AjaxHelpers.requests(this);
jasmine.Clock.useMock();
$('.discovery-input').val('test');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.courses-listing article').length).toEqual(1);
expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
window.scroll(0, $(document).height());
$(window).trigger('scroll');
jasmine.Clock.tick(500);
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.courses-listing article').length).toEqual(2);
}); });
it('displays not found message', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('asdfasdf');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, {});
expect($('#discovery-message')).not.toBeEmpty();
expect($('.courses-listing')).toBeEmpty();
});
it('displays error message', function () { describe('Discovery App', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('asdfasdf'); beforeEach(function () {
$('.discovery-submit').trigger('click'); loadFixtures('js/fixtures/discovery.html');
AjaxHelpers.respondWithError(requests, 404); TemplateHelpers.installTemplates([
expect($('#discovery-message')).not.toBeEmpty(); 'templates/discovery/result_item',
expect($('.courses-listing')).toBeEmpty(); 'templates/discovery/filter',
}); 'templates/discovery/filter_bar',
'templates/discovery/search_facet',
'templates/discovery/search_facets_section',
'templates/discovery/search_facets_list',
'templates/discovery/more_less_links'
]);
DiscoveryFactory(
{
org: {
name: 'Organization',
terms: {
edX1: "edX_1"
}
},
modes: {
name: 'Course Type',
terms: {
honor: 'Honor',
verified: 'Verified'
}
},
language: {
en: 'English',
hr: 'Croatian'
}
}
);
});
it('check filters and bar removed on clear all', function () { it('performs search', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test'); $('.discovery-input').val('test');
$('.discovery-submit').trigger('click'); $('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.active-filter').length).toBe(1); expect($('.courses-listing article').length).toEqual(1);
expect($('#filter-bar')).not.toHaveClass('hidden'); expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
$('#clear-all-filters').trigger('click'); expect($('.active-filter').length).toBe(1);
expect($('.active-filter').length).toBe(0); });
expect($('#filter-bar')).toHaveClass('hidden');
});
it('check filters and bar removed on last filter cleared', function () { it('loads more', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test'); jasmine.Clock.useMock();
$('.discovery-submit').trigger('click'); $('.discovery-input').val('test');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); $('.discovery-submit').trigger('click');
expect($('.active-filter').length).toBe(1); AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
var $filter = $('.active-filter'); expect($('.courses-listing article').length).toEqual(1);
$filter.find('.discovery-button').trigger('click'); expect($('.courses-listing .course-title')).toContainHtml('edX Demonstration Course');
expect($('.active-filter').length).toBe(0); window.scroll(0, $(document).height());
}); $(window).trigger('scroll');
jasmine.Clock.tick(500);
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.courses-listing article').length).toEqual(2);
});
it('filter results by named facet', function () { it('displays not found message', function () {
var requests = AjaxHelpers.requests(this); var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test'); $('.discovery-input').val('asdfasdf');
$('.discovery-submit').trigger('click'); $('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE); AjaxHelpers.respondWithJson(requests, {});
expect($('.active-filter').length).toBe(1); expect($('#discovery-message')).not.toBeEmpty();
var $facetLink = $('.search-facets li [data-value="edX1"]'); expect($('.courses-listing')).toBeEmpty();
var $facet = $facetLink.parent('li'); });
$facet.trigger('click');
expect($('.active-filter').length).toBe(2);
expect($('.active-filter [data-value="edX1"]').length).toBe(1);
expect($('.active-filter [data-value="edX1"]').text().trim()).toBe("edX_1");
});
}); it('displays error message', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('asdfasdf');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithError(requests, 404);
expect($('#discovery-message')).not.toBeEmpty();
expect($('.courses-listing')).toBeEmpty();
});
it('check filters and bar removed on clear all', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.active-filter').length).toBe(1);
expect($('#filter-bar')).not.toHaveClass('hidden');
$('#clear-all-filters').trigger('click');
expect($('.active-filter').length).toBe(0);
expect($('#filter-bar')).toHaveClass('hidden');
});
it('check filters and bar removed on last filter cleared', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.active-filter').length).toBe(1);
var $filter = $('.active-filter');
$filter.find('.discovery-button').trigger('click');
expect($('.active-filter').length).toBe(0);
});
it('filter results by named facet', function () {
var requests = AjaxHelpers.requests(this);
$('.discovery-input').val('test');
$('.discovery-submit').trigger('click');
AjaxHelpers.respondWithJson(requests, JSON_RESPONSE);
expect($('.active-filter').length).toBe(1);
var $facetLink = $('.search-facets li [data-value="edX1"]');
var $facet = $facetLink.parent('li');
$facet.trigger('click');
expect($('.active-filter').length).toBe(2);
expect($('.active-filter [data-value="edX1"]').length).toBe(1);
expect($('.active-filter [data-value="edX1"]').text().trim()).toBe("edX_1");
});
});
});
}); });
define([ define([
'jquery', 'annotator_1.2.9', 'common/js/spec_helpers/ajax_helpers', 'js/edxnotes/views/visibility_decorator', 'jquery', 'underscore', 'annotator_1.2.9', 'common/js/spec_helpers/ajax_helpers',
'js/edxnotes/views/toggle_notes_factory', 'js/spec/edxnotes/helpers', 'js/edxnotes/views/notes_visibility_factory', 'js/spec/edxnotes/helpers',
'js/spec/edxnotes/custom_matchers', 'jasmine-jquery' 'js/spec/edxnotes/custom_matchers', 'jasmine-jquery'
], function( ], function(
$, Annotator, AjaxHelpers, VisibilityDecorator, ToggleNotesFactory, Helpers, $, _, Annotator, AjaxHelpers, NotesVisibilityFactory, Helpers, customMatchers
customMatchers
) { ) {
'use strict'; 'use strict';
describe('EdxNotes ToggleNotesFactory', function() { describe('EdxNotes ToggleNotesFactory', function() {
...@@ -23,13 +22,13 @@ define([ ...@@ -23,13 +22,13 @@ define([
'js/fixtures/edxnotes/edxnotes_wrapper.html', 'js/fixtures/edxnotes/edxnotes_wrapper.html',
'js/fixtures/edxnotes/toggle_notes.html' 'js/fixtures/edxnotes/toggle_notes.html'
); );
VisibilityDecorator.factory( NotesVisibilityFactory.VisibilityDecorator.factory(
document.getElementById('edx-notes-wrapper-123'), params, true document.getElementById('edx-notes-wrapper-123'), params, true
); );
VisibilityDecorator.factory( NotesVisibilityFactory.VisibilityDecorator.factory(
document.getElementById('edx-notes-wrapper-456'), params, true document.getElementById('edx-notes-wrapper-456'), params, true
); );
this.toggleNotes = ToggleNotesFactory(true, '/test_url'); this.toggleNotes = NotesVisibilityFactory.ToggleVisibilityView(true, '/test_url');
this.button = $('.action-toggle-notes'); this.button = $('.action-toggle-notes');
this.label = this.button.find('.utility-control-label'); this.label = this.button.find('.utility-control-label');
this.toggleMessage = $('.action-toggle-message'); this.toggleMessage = $('.action-toggle-message');
...@@ -37,7 +36,7 @@ define([ ...@@ -37,7 +36,7 @@ define([
}); });
afterEach(function () { afterEach(function () {
VisibilityDecorator._setVisibility(null); NotesVisibilityFactory.VisibilityDecorator._setVisibility(null);
_.invoke(Annotator._instances, 'destroy'); _.invoke(Annotator._instances, 'destroy');
$('.annotator-notice').remove(); $('.annotator-notice').remove();
}); });
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
requirejs.config({ requirejs.config({
paths: { paths: {
'gettext': 'xmodule_js/common_static/js/test/i18n', 'gettext': 'xmodule_js/common_static/js/test/i18n',
'mustache': 'xmodule_js/common_static/js/vendor/mustache',
'codemirror': 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror', 'codemirror': 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror',
'jquery': 'xmodule_js/common_static/js/vendor/jquery.min', 'jquery': 'xmodule_js/common_static/js/vendor/jquery.min',
'jquery.ui': 'xmodule_js/common_static/js/vendor/jquery-ui.min', 'jquery.ui': 'xmodule_js/common_static/js/vendor/jquery-ui.min',
...@@ -61,7 +60,6 @@ ...@@ -61,7 +60,6 @@
// Manually specify LMS files that are not converted to RequireJS // Manually specify LMS files that are not converted to RequireJS
'history': 'js/vendor/history', 'history': 'js/vendor/history',
'js/mustache': 'js/mustache',
'js/staff_debug_actions': 'js/staff_debug_actions', 'js/staff_debug_actions': 'js/staff_debug_actions',
'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit', 'js/vendor/jquery.qubit': 'js/vendor/jquery.qubit',
...@@ -69,18 +67,6 @@ ...@@ -69,18 +67,6 @@
'js/models/notification': 'js/models/notification', 'js/models/notification': 'js/models/notification',
'js/views/file_uploader': 'js/views/file_uploader', 'js/views/file_uploader': 'js/views/file_uploader',
'js/views/notification': 'js/views/notification', 'js/views/notification': 'js/views/notification',
'js/groups/models/cohort': 'js/groups/models/cohort',
'js/groups/models/content_group': 'js/groups/models/content_group',
'js/groups/models/course_cohort_settings': 'js/groups/models/course_cohort_settings',
'js/groups/models/cohort_discussions': 'js/groups/models/cohort_discussions',
'js/groups/views/cohort_discussions': 'js/groups/views/cohort_discussions',
'js/groups/views/cohort_discussions_course_wide': 'js/groups/views/cohort_discussions_course_wide',
'js/groups/views/cohort_discussions_inline': 'js/groups/views/cohort_discussions_inline',
'js/groups/views/course_cohort_settings_notification': 'js/groups/views/course_cohort_settings_notification',
'js/groups/collections/cohort': 'js/groups/collections/cohort',
'js/groups/views/cohort_editor': 'js/groups/views/cohort_editor',
'js/groups/views/cohort_form': 'js/groups/views/cohort_form',
'js/groups/views/cohorts': 'js/groups/views/cohorts',
'js/student_account/account': 'js/student_account/account', 'js/student_account/account': 'js/student_account/account',
'js/student_account/views/FormView': 'js/student_account/views/FormView', 'js/student_account/views/FormView': 'js/student_account/views/FormView',
'js/student_account/models/LoginModel': 'js/student_account/models/LoginModel', 'js/student_account/models/LoginModel': 'js/student_account/models/LoginModel',
...@@ -98,9 +84,7 @@ ...@@ -98,9 +84,7 @@
'js/ccx/schedule': 'js/ccx/schedule', 'js/ccx/schedule': 'js/ccx/schedule',
// edxnotes // edxnotes
'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min', 'annotator_1.2.9': 'xmodule_js/common_static/js/vendor/edxnotes/annotator-full.min'
'course_discovery_meanings': 'js/spec/discovery/course_discovery_meanings'
}, },
shim: { shim: {
'gettext': { 'gettext': {
...@@ -303,63 +287,6 @@ ...@@ -303,63 +287,6 @@
exports: 'edx.instructor_dashboard.ecommerce.ExpiryCouponView', exports: 'edx.instructor_dashboard.ecommerce.ExpiryCouponView',
deps: ['backbone', 'jquery', 'underscore'] deps: ['backbone', 'jquery', 'underscore']
}, },
'js/groups/models/cohort': {
exports: 'edx.groups.CohortModel',
deps: ['backbone']
},
'js/groups/models/content_group': {
exports: 'edx.groups.ContentGroupModel',
deps: ['backbone']
},
'js/groups/models/course_cohort_settings': {
exports: 'edx.groups.CourseCohortSettingsModel',
deps: ['backbone']
},
'js/groups/models/cohort_discussions': {
exports: 'edx.groups.DiscussionTopicsSettingsModel',
deps: ['backbone']
},
'js/groups/views/cohort_discussions': {
exports: 'edx.groups.CohortDiscussionConfigurationView',
deps: ['backbone']
},
'js/groups/views/cohort_discussions_course_wide': {
exports: 'edx.groups.CourseWideDiscussionsView',
deps: ['backbone', 'js/groups/views/cohort_discussions']
},
'js/groups/views/cohort_discussions_inline': {
exports: 'edx.groups.InlineDiscussionsView',
deps: ['backbone', 'js/groups/views/cohort_discussions', 'js/vendor/jquery.qubit']
},
'js/groups/views/course_cohort_settings_notification': {
exports: 'edx.groups.CourseCohortSettingsNotificationView',
deps: ['backbone']
},
'js/groups/collections/cohort': {
exports: 'edx.groups.CohortCollection',
deps: ['backbone', 'js/groups/models/cohort']
},
'js/groups/views/cohort_form': {
exports: 'edx.groups.CohortFormView',
deps: [
'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification',
'string_utils'
]
},
'js/groups/views/cohort_editor': {
exports: 'edx.groups.CohortEditorView',
deps: [
'backbone', 'jquery', 'underscore', 'js/views/notification', 'js/models/notification',
'string_utils', 'js/groups/views/cohort_form'
]
},
'js/groups/views/cohorts': {
exports: 'edx.groups.CohortsView',
deps: [
'jquery', 'underscore', 'backbone', 'gettext', 'string_utils', 'js/groups/views/cohort_editor',
'js/views/notification', 'js/models/notification', 'js/views/file_uploader'
]
},
'js/models/notification': { 'js/models/notification': {
exports: 'NotificationModel', exports: 'NotificationModel',
deps: ['backbone'] deps: ['backbone']
...@@ -375,18 +302,6 @@ ...@@ -375,18 +302,6 @@
'js/models/notification', 'jquery.fileupload' 'js/models/notification', 'jquery.fileupload'
] ]
}, },
'js/student_account/enrollment': {
exports: 'edx.student.account.EnrollmentInterface',
deps: ['jquery', 'jquery.cookie']
},
'js/student_account/emailoptin': {
exports: 'edx.student.account.EmailOptInInterface',
deps: ['jquery', 'jquery.cookie']
},
'js/student_account/shoppingcart': {
exports: 'edx.student.account.ShoppingCartInterface',
deps: ['jquery', 'jquery.cookie', 'underscore']
},
// Student account registration/login // Student account registration/login
// Loaded explicitly until these are converted to RequireJS // Loaded explicitly until these are converted to RequireJS
'js/student_account/views/FormView': { 'js/student_account/views/FormView': {
...@@ -665,7 +580,7 @@ ...@@ -665,7 +580,7 @@
'lms/include/js/spec/edxnotes/views/tabs/course_structure_spec.js', 'lms/include/js/spec/edxnotes/views/tabs/course_structure_spec.js',
'lms/include/js/spec/edxnotes/views/tabs/tags_spec.js', 'lms/include/js/spec/edxnotes/views/tabs/tags_spec.js',
'lms/include/js/spec/edxnotes/views/visibility_decorator_spec.js', 'lms/include/js/spec/edxnotes/views/visibility_decorator_spec.js',
'lms/include/js/spec/edxnotes/views/toggle_notes_factory_spec.js', 'lms/include/js/spec/edxnotes/views/notes_visibility_factory_spec.js',
'lms/include/js/spec/edxnotes/models/tab_spec.js', 'lms/include/js/spec/edxnotes/models/tab_spec.js',
'lms/include/js/spec/edxnotes/models/note_spec.js', 'lms/include/js/spec/edxnotes/models/note_spec.js',
'lms/include/js/spec/edxnotes/plugins/accessibility_spec.js', 'lms/include/js/spec/edxnotes/plugins/accessibility_spec.js',
......
...@@ -13,8 +13,8 @@ define([ ...@@ -13,8 +13,8 @@ define([
'js/search/dashboard/views/search_form', 'js/search/dashboard/views/search_form',
'js/search/course/views/search_results_view', 'js/search/course/views/search_results_view',
'js/search/dashboard/views/search_results_view', 'js/search/dashboard/views/search_results_view',
'js/search/course/search_app', 'js/search/course/course_search_factory',
'js/search/dashboard/search_app' 'js/search/dashboard/dashboard_search_factory'
], function( ], function(
$, $,
Sinon, Sinon,
...@@ -30,8 +30,8 @@ define([ ...@@ -30,8 +30,8 @@ define([
DashSearchForm, DashSearchForm,
CourseSearchResultsView, CourseSearchResultsView,
DashSearchResultsView, DashSearchResultsView,
CourseSearchApp, CourseSearchFactory,
DashSearchApp DashboardSearchFactory
) { ) {
'use strict'; 'use strict';
...@@ -681,13 +681,7 @@ define([ ...@@ -681,13 +681,7 @@ define([
this.server = Sinon.fakeServer.create(); this.server = Sinon.fakeServer.create();
var courseId = 'a/b/c'; var courseId = 'a/b/c';
this.app = new CourseSearchApp( CourseSearchFactory(courseId);
courseId,
SearchRouter,
CourseSearchForm,
SearchCollection,
CourseSearchResultsView
);
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
this.$contentElement = $('#course-content'); this.$contentElement = $('#course-content');
this.$searchResults = $('#courseware-search-results'); this.$searchResults = $('#courseware-search-results');
...@@ -718,12 +712,7 @@ define([ ...@@ -718,12 +712,7 @@ define([
loadTemplates.call(this); loadTemplates.call(this);
this.server = Sinon.fakeServer.create(); this.server = Sinon.fakeServer.create();
this.app = new DashSearchApp( DashboardSearchFactory();
SearchRouter,
DashSearchForm,
SearchCollection,
DashSearchResultsView
);
spyOn(Backbone.history, 'navigate'); spyOn(Backbone.history, 'navigate');
this.$contentElement = $('#my-courses'); this.$contentElement = $('#my-courses');
......
...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/emailoptin'], ...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/emailoptin'],
function( AjaxHelpers, EmailOptInInterface ) { function( AjaxHelpers, EmailOptInInterface ) {
'use strict'; 'use strict';
describe( 'edx.student.account.EmailOptInInterface', function() { describe( 'EmailOptInInterface', function() {
var COURSE_KEY = 'edX/DemoX/Fall', var COURSE_KEY = 'edX/DemoX/Fall',
EMAIL_OPT_IN = 'True', EMAIL_OPT_IN = 'True',
......
...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/enrollment'], ...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/enrollment'],
function( AjaxHelpers, EnrollmentInterface ) { function( AjaxHelpers, EnrollmentInterface ) {
'use strict'; 'use strict';
describe( 'edx.student.account.EnrollmentInterface', function() { describe( 'EnrollmentInterface', function() {
var COURSE_KEY = 'edX/DemoX/Fall', var COURSE_KEY = 'edX/DemoX/Fall',
ENROLL_URL = '/api/commerce/v0/baskets/', ENROLL_URL = '/api/commerce/v0/baskets/',
......
...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/shoppingcart' ...@@ -2,7 +2,7 @@ define(['common/js/spec_helpers/ajax_helpers', 'js/student_account/shoppingcart'
function(AjaxHelpers, ShoppingCartInterface) { function(AjaxHelpers, ShoppingCartInterface) {
'use strict'; 'use strict';
describe( 'edx.student.account.ShoppingCartInterface', function() { describe( 'ShoppingCartInterface', function() {
var COURSE_KEY = "edX/DemoX/Fall", var COURSE_KEY = "edX/DemoX/Fall",
ADD_COURSE_URL = "/shoppingcart/add/course/edX/DemoX/Fall/", ADD_COURSE_URL = "/shoppingcart/add/course/edX/DemoX/Fall/",
......
var edx = edx || {}; ;(function (define) {
(function($) {
'use strict'; 'use strict';
define(['jquery', 'jquery.cookie'], function($) {
edx.student = edx.student || {}; var EmailOptInInterface = {
edx.student.account = edx.student.account || {};
edx.student.account.EmailOptInInterface = { urls: {
emailOptInUrl: '/user_api/v1/preferences/email_opt_in/'
},
urls: { headers: {
emailOptInUrl: '/user_api/v1/preferences/email_opt_in/' 'X-CSRFToken': $.cookie('csrftoken')
}, },
headers: { /**
'X-CSRFToken': $.cookie('csrftoken') * Set the email opt in setting for the organization associated
}, * with this course.
* @param {string} courseKey Slash-separated course key.
* @param {string} emailOptIn The preference to opt in or out of organization emails.
*/
setPreference: function( courseKey, emailOptIn ) {
return $.ajax({
url: this.urls.emailOptInUrl,
type: 'POST',
data: {course_id: courseKey, email_opt_in: emailOptIn},
headers: this.headers
});
}
};
/** return EmailOptInInterface;
* Set the email opt in setting for the organization associated });
* with this course. }).call(this, define || RequireJS.define);
* @param {string} courseKey Slash-separated course key.
* @param {string} emailOptIn The preference to opt in or out of organization emails.
*/
setPreference: function( courseKey, emailOptIn ) {
return $.ajax({
url: this.urls.emailOptInUrl,
type: 'POST',
data: {course_id: courseKey, email_opt_in: emailOptIn},
headers: this.headers
});
}
};
})(jQuery);
var edx = edx || {}; ;(function (define) {
(function($) {
'use strict'; 'use strict';
define(['jquery', 'jquery.cookie'], function($) {
edx.student = edx.student || {}; var EnrollmentInterface = {
edx.student.account = edx.student.account || {};
edx.student.account.EnrollmentInterface = {
urls: { urls: {
baskets: '/api/commerce/v0/baskets/', baskets: '/api/commerce/v0/baskets/',
}, },
headers: { headers: {
'X-CSRFToken': $.cookie('csrftoken') 'X-CSRFToken': $.cookie('csrftoken')
}, },
/** /**
* Enroll a user in a course, then redirect the user. * Enroll a user in a course, then redirect the user.
* @param {string} courseKey Slash-separated course key. * @param {string} courseKey Slash-separated course key.
* @param {string} redirectUrl The URL to redirect to once enrollment completes. * @param {string} redirectUrl The URL to redirect to once enrollment completes.
*/ */
enroll: function( courseKey, redirectUrl ) { enroll: function( courseKey, redirectUrl ) {
var data_obj = {course_id: courseKey}, var data_obj = {course_id: courseKey},
data = JSON.stringify(data_obj); data = JSON.stringify(data_obj);
$.ajax({ $.ajax({
url: this.urls.baskets, url: this.urls.baskets,
type: 'POST', type: 'POST',
contentType: 'application/json; charset=utf-8', contentType: 'application/json; charset=utf-8',
data: data, data: data,
headers: this.headers, headers: this.headers,
context: this context: this
}) }).fail(function( jqXHR ) {
.fail(function( jqXHR ) { var responseData = JSON.parse(jqXHR.responseText);
var responseData = JSON.parse(jqXHR.responseText); if ( jqXHR.status === 403 && responseData.user_message_url ) {
if ( jqXHR.status === 403 && responseData.user_message_url ) { // Check if we've been blocked from the course
// Check if we've been blocked from the course // because of country access rules.
// because of country access rules. // If so, redirect to a page explaining to the user
// If so, redirect to a page explaining to the user // why they were blocked.
// why they were blocked. this.redirect( responseData.user_message_url );
this.redirect( responseData.user_message_url ); } else {
} else { // Otherwise, redirect the user to the next page.
// Otherwise, redirect the user to the next page. if ( redirectUrl ) {
this.redirect( redirectUrl );
}
}
}).done(function() {
// If we successfully enrolled, redirect the user
// to the next page (usually the student dashboard or payment flow)
if ( redirectUrl ) { if ( redirectUrl ) {
this.redirect( redirectUrl ); this.redirect( redirectUrl );
} }
} });
}) },
.done(function() {
// If we successfully enrolled, redirect the user /**
// to the next page (usually the student dashboard or payment flow) * Redirect to a URL. Mainly useful for mocking out in tests.
if ( redirectUrl ) { * @param {string} url The URL to redirect to.
this.redirect( redirectUrl ); */
} redirect: function(url) {
}); window.location.href = url;
}, }
};
/** return EnrollmentInterface;
* Redirect to a URL. Mainly useful for mocking out in tests. });
* @param {string} url The URL to redirect to. }).call(this, define || RequireJS.define);
*/
redirect: function(url) {
window.location.href = url;
}
};
})(jQuery);
/** /**
* Use the shopping cart to purchase courses. * Use the shopping cart to purchase courses.
*/ */
;(function (define) {
var edx = edx || {};
(function($) {
'use strict'; 'use strict';
define(['jquery', 'jquery.cookie'], function($) {
edx.student = edx.student || {};
edx.student.account = edx.student.account || {}; var ShoppingCartInterface = {
urls: {
edx.student.account.ShoppingCartInterface = { viewCart: "/shoppingcart/",
addCourse: "/shoppingcart/add/course/"
urls: { },
viewCart: "/shoppingcart/",
addCourse: "/shoppingcart/add/course/" headers: {
}, 'X-CSRFToken': $.cookie('csrftoken')
},
headers: {
'X-CSRFToken': $.cookie('csrftoken') /**
}, * Add a course to a cart, then redirect to the view cart page.
* @param {string} courseId The slash-separated course ID to add to the cart.
/** */
* Add a course to a cart, then redirect to the view cart page. addCourseToCart: function( courseId ) {
* @param {string} courseId The slash-separated course ID to add to the cart. $.ajax({
*/ url: this.urls.addCourse + courseId + "/",
addCourseToCart: function( courseId ) { type: 'POST',
$.ajax({ data: {},
url: this.urls.addCourse + courseId + "/", headers: this.headers,
type: 'POST', context: this
data: {}, }).always(function() {
headers: this.headers, this.redirect( this.urls.viewCart );
context: this });
}).always(function() { },
this.redirect( this.urls.viewCart );
}); /**
}, * Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
/** */
* Redirect to a URL. Mainly useful for mocking out in tests. redirect: function( url ) {
* @param {string} url The URL to redirect to. window.location.href = url;
*/ }
redirect: function( url ) { };
window.location.href = url;
} return ShoppingCartInterface;
}; });
}).call(this, define || RequireJS.define);
})(jQuery);
...@@ -26,18 +26,14 @@ ...@@ -26,18 +26,14 @@
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'jquery',
'underscore', 'underscore',
'backbone', 'backbone',
'gettext', 'gettext',
'js/student_account/emailoptin', 'js/student_account/emailoptin',
'js/student_account/enrollment', 'js/student_account/enrollment',
'js/student_account/shoppingcart' 'js/student_account/shoppingcart'
], function (_, Backbone, gettext, emailOptInInterface, enrollmentInterface, shoppingCartInterface) { ], function ($, _, Backbone, gettext, emailOptInInterface, enrollmentInterface, shoppingCartInterface) {
// These are not yet converted to requireJS:
var edx = window.edx || {};
emailOptInInterface = emailOptInInterface || edx.student.account.EmailOptInInterface;
enrollmentInterface = enrollmentInterface || edx.student.account.EnrollmentInterface;
shoppingCartInterface = shoppingCartInterface || edx.student.account.ShoppingCartInterface;
var FinishAuthView = Backbone.View.extend({ var FinishAuthView = Backbone.View.extend({
el: '#finish-auth-status', el: '#finish-auth-status',
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'backbone', 'js/mustache', 'js/views/fields' 'gettext', 'jquery', 'underscore', 'backbone', 'js/views/fields'
], function (gettext, $, _, Backbone, RequireMustache, FieldViews) { ], function (gettext, $, _, Backbone, FieldViews) {
var AccountSettingsFieldViews = {}; var AccountSettingsFieldViews = {};
......
(function (define) {
'use strict';
define("js/student_account/views/finish_auth_factory",
['jquery', 'underscore', 'backbone', 'js/student_account/views/FinishAuthView', 'utility'],
function ($, _, Backbone, FinishAuthView) {
return function() {
var view = new FinishAuthView({});
view.render();
};
}
);
}).call(this, define || RequireJS.define);
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
'js/student_profile/views/learner_profile_fields', 'js/student_profile/views/learner_profile_fields',
'js/student_profile/views/learner_profile_view', 'js/student_profile/views/learner_profile_view',
'js/student_account/views/account_settings_fields', 'js/student_account/views/account_settings_fields',
'js/views/message_banner' 'js/views/message_banner',
'string_utils'
], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView, ], function (gettext, $, _, Backbone, Logger, AccountSettingsModel, AccountPreferencesModel, FieldsView,
LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageBannerView) { LearnerProfileFieldsView, LearnerProfileView, AccountSettingsFieldViews, MessageBannerView) {
......
;(function (define, undefined) { ;(function (define, undefined) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'backbone', 'js/mustache', 'backbone-super' 'gettext', 'jquery', 'underscore', 'backbone', 'backbone-super', 'jquery.fileupload'
], function (gettext, $, _, Backbone, RequireMustache) { ], function (gettext, $, _, Backbone) {
var Mustache = window.Mustache || RequireMustache;
var messageRevertDelay = 6000; var messageRevertDelay = 6000;
var FieldViews = {}; var FieldViews = {};
...@@ -127,7 +125,7 @@ ...@@ -127,7 +125,7 @@
if (xhr.status === 400) { if (xhr.status === 400) {
try { try {
var errors = JSON.parse(xhr.responseText), var errors = JSON.parse(xhr.responseText),
validationErrorMessage = Mustache.escapeHtml( validationErrorMessage = _.escape(
errors.field_errors[this.options.valueAttribute].user_message errors.field_errors[this.options.valueAttribute].user_message
), ),
message = this.indicators.validationError + validationErrorMessage; message = this.indicators.validationError + validationErrorMessage;
...@@ -253,7 +251,7 @@ ...@@ -253,7 +251,7 @@
}, },
updateValueInField: function () { updateValueInField: function () {
this.$('.u-field-value input').val(Mustache.escapeHtml(this.modelValue())); this.$('.u-field-value input').val(_.escape(this.modelValue()));
} }
}); });
...@@ -290,7 +288,7 @@ ...@@ -290,7 +288,7 @@
updateValueInField: function () { updateValueInField: function () {
var value = (_.isUndefined(this.modelValue()) || _.isNull(this.modelValue())) ? '' : this.modelValue(); var value = (_.isUndefined(this.modelValue()) || _.isNull(this.modelValue())) ? '' : this.modelValue();
this.$('.u-field-value input').val(Mustache.escapeHtml(value)); this.$('.u-field-value input').val(_.escape(value));
}, },
saveValue: function () { saveValue: function () {
...@@ -375,7 +373,7 @@ ...@@ -375,7 +373,7 @@
value = this.options.placeholderValue || ''; value = this.options.placeholderValue || '';
} }
this.$('.u-field-value').attr('aria-label', this.options.title); this.$('.u-field-value').attr('aria-label', this.options.title);
this.$('.u-field-value-readonly').html(Mustache.escapeHtml(value)); this.$('.u-field-value-readonly').html(_.escape(value));
if (this.mode === 'display') { if (this.mode === 'display') {
this.updateDisplayModeClass(); this.updateDisplayModeClass();
......
(function () {
'use strict';
var getModulesList = function (modules) {
return modules.map(function (moduleName) {
return { name: moduleName };
});
};
var jsOptimize = process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE !== undefined ?
process.env.REQUIRE_BUILD_PROFILE_OPTIMIZE : 'uglify2';
return {
namespace: "RequireJS",
/**
* List the modules that will be optimized. All their immediate and deep
* dependencies will be included in the module's file when the build is
* done.
*/
modules: getModulesList([
'js/discovery/discovery_factory',
'js/edxnotes/views/notes_visibility_factory',
'js/edxnotes/views/page_factory',
'js/groups/views/cohorts_dashboard_factory',
'js/search/course/course_search_factory',
'js/search/dashboard/dashboard_search_factory',
'js/student_account/views/account_settings_factory',
'js/student_account/views/finish_auth_factory',
'js/student_profile/views/learner_profile_factory',
'teams/js/teams_tab_factory'
]),
/**
* By default all the configuration for optimization happens from the command
* line or by properties in the config file, and configuration that was
* passed to requirejs as part of the app's runtime "main" JS file is *not*
* considered. However, if you prefer the "main" JS file configuration
* to be read for the build so that you do not have to duplicate the values
* in a separate configuration, set this property to the location of that
* main JS file. The first requirejs({}), require({}), requirejs.config({}),
* or require.config({}) call found in that file will be used.
* As of 2.1.10, mainConfigFile can be an array of values, with the last
* value's config take precedence over previous values in the array.
*/
mainConfigFile: 'require-config.js',
/**
* Set paths for modules. If relative paths, set relative to baseUrl above.
* If a special value of "empty:" is used for the path value, then that
* acts like mapping the path to an empty file. It allows the optimizer to
* resolve the dependency to path, but then does not include it in the output.
* Useful to map module names that are to resources on a CDN or other
* http: URL when running in the browser and during an optimization that
* file should be skipped because it has no dependencies.
*/
paths: {
'gettext': 'empty:',
'coffee/src/ajax_prefix': 'empty:',
'jquery': 'empty:',
'jquery.cookie': 'empty:',
'jquery.url': 'empty:',
'backbone': 'empty:',
'underscore': 'empty:',
'logger': 'empty:',
'utility': 'empty:',
'URI': 'empty:',
},
/**
* Inline requireJS text templates.
*/
inlineText: true,
/**
* Stub out requireJS text in the optimized file, but leave available for non-optimized development use.
*/
stubModules: ["text"],
/**
* If shim config is used in the app during runtime, duplicate the config
* here. Necessary if shim config is used, so that the shim's dependencies
* are included in the build. Using "mainConfigFile" is a better way to
* pass this information though, so that it is only listed in one place.
* However, if mainConfigFile is not an option, the shim config can be
* inlined in the build config.
*/
shim: {},
/**
* Introduced in 2.1.2: If using "dir" for an output directory, normally the
* optimize setting is used to optimize the build bundles (the "modules"
* section of the config) and any other JS file in the directory. However, if
* the non-build bundle JS files will not be loaded after a build, you can
* skip the optimization of those files, to speed up builds. Set this value
* to true if you want to skip optimizing those other non-build bundle JS
* files.
*/
skipDirOptimize: true,
/**
* When the optimizer copies files from the source location to the
* destination directory, it will skip directories and files that start
* with a ".". If you want to copy .directories or certain .files, for
* instance if you keep some packages in a .packages directory, or copy
* over .htaccess files, you can set this to null. If you want to change
* the exclusion rules, change it to a different regexp. If the regexp
* matches, it means the directory will be excluded. This used to be
* called dirExclusionRegExp before the 1.0.2 release.
* As of 1.0.3, this value can also be a string that is converted to a
* RegExp via new RegExp().
*/
fileExclusionRegExp: /^\.|spec|spec_helpers/,
/**
* Allow CSS optimizations. Allowed values:
* - "standard": @import inlining and removal of comments, unnecessary
* whitespace and line returns.
* Removing line returns may have problems in IE, depending on the type
* of CSS.
* - "standard.keepLines": like "standard" but keeps line returns.
* - "none": skip CSS optimizations.
* - "standard.keepComments": keeps the file comments, but removes line
* returns. (r.js 1.0.8+)
* - "standard.keepComments.keepLines": keeps the file comments and line
* returns. (r.js 1.0.8+)
* - "standard.keepWhitespace": like "standard" but keeps unnecessary whitespace.
*/
optimizeCss: 'none',
/**
* How to optimize all the JS files in the build output directory.
* Right now only the following values are supported:
* - "uglify": Uses UglifyJS to minify the code.
* - "uglify2": Uses UglifyJS2.
* - "closure": Uses Google's Closure Compiler in simple optimization
* mode to minify the code. Only available if REQUIRE_ENVIRONMENT is "rhino" (the default).
* - "none": No minification will be done.
*/
optimize: jsOptimize,
/**
* Sets the logging level. It is a number:
* TRACE: 0,
* INFO: 1,
* WARN: 2,
* ERROR: 3,
* SILENT: 4
* Default is 0.
*/
logLevel: 1
};
} ())
;(function (require, define) { ;(function (require, define) {
var paths = {}, config;
// jquery, underscore, gettext, URI, tinymce, or jquery.tinymce may already // We do not wish to bundle common libraries (that may also be used by non-RequireJS code on the page
// have been loaded and we do not want to load them a second time. Check if // into the optimized files. Therefore load these libraries through script tags and explicitly define them.
// it is the case and use the global var instead. // Note that when the optimizer executes this code, window will not be defined.
if (window.jQuery) { if (window) {
define("jquery", [], function() {return window.jQuery;}); var defineDependency = function (globalVariable, name, noShim) {
} else { if (window[globalVariable]) {
paths.jquery = "js/vendor/jquery.min"; if (noShim) {
} define(name, {});
if (window._) { }
define("underscore", [], function() {return window._;}); else {
} else { define(name, [], function() {return window[globalVariable];});
paths.jquery = "js/vendor/underscore-min"; }
} }
if (window.gettext) { else {
define("gettext", [], function() {return window.gettext;}); console.error("Expected library to be included on page, but not found on window object: " + name);
} else { }
paths.gettext = "/i18n"; };
} defineDependency("jQuery", "jquery");
if (window.Logger) { defineDependency("_", "underscore");
define("logger", [], function() {return window.Logger;}); defineDependency("gettext", "gettext");
} else { defineDependency("Logger", "logger");
paths.logger = "js/src/logger"; defineDependency("URI", "URI");
} defineDependency("Backbone", "backbone");
if (window.URI) { // utility.js adds two functions to the window object, but does not return anything
define("URI", [], function() {return window.URI;}); defineDependency("isExternal", "utility", true);
} else {
paths.URI = "js/vendor/URI.min";
}
if (window.tinymce) {
define('tinymce', [], function() {return window.tinymce;});
} else {
paths.tinymce = "js/vendor/tinymce/js/tinymce/tinymce.full.min";
}
if (window.jquery && window.jquery.tinymce) {
define("jquery.tinymce", [], function() {return window.jquery.tinymce;});
} else {
paths.tinymce = "js/vendor/tinymce/js/tinymce/jquery.tinymce.min";
} }
config = { require.config({
// NOTE: baseUrl has been previously set in lms/static/templates/main.html // NOTE: baseUrl has been previously set in lms/templates/main.html
waitSeconds: 60, waitSeconds: 60,
paths: { paths: {
"gettext": "/i18n",
"annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min", "annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min",
"date": "js/vendor/date", "date": "js/vendor/date",
"text": 'js/vendor/requirejs/text', "text": "js/vendor/requirejs/text",
"logger": "js/src/logger",
"backbone": "js/vendor/backbone-min", "backbone": "js/vendor/backbone-min",
"backbone-super": "js/vendor/backbone-super", "backbone-super": "js/vendor/backbone-super",
"backbone.paginator": "js/vendor/backbone.paginator.min", "backbone.paginator": "js/vendor/backbone.paginator.min",
"underscore": "js/vendor/underscore-min",
"underscore.string": "js/vendor/underscore.string.min", "underscore.string": "js/vendor/underscore.string.min",
"jquery": "js/vendor/jquery.min",
"jquery.cookie": "js/vendor/jquery.cookie",
"jquery.url": "js/vendor/url.min",
"jquery.ui": "js/vendor/jquery-ui.min",
"jquery.iframe-transport": "js/vendor/jQuery-File-Upload/js/jquery.iframe-transport",
"jquery.fileupload": "js/vendor/jQuery-File-Upload/js/jquery.fileupload",
"URI": "js/vendor/URI.min",
"string_utils": "js/src/string_utils",
"utility": "js/src/utility",
// Files needed by OVA // Files needed by OVA
"annotator": "js/vendor/ova/annotator-full", "annotator": "js/vendor/ova/annotator-full",
"annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth", "annotator-harvardx": "js/vendor/ova/annotator-full-firebase-auth",
"video.dev": "js/vendor/ova/video.dev", "video.dev": "js/vendor/ova/video.dev",
"vjs.youtube": 'js/vendor/ova/vjs.youtube', "vjs.youtube": "js/vendor/ova/vjs.youtube",
"rangeslider": 'js/vendor/ova/rangeslider', "rangeslider": "js/vendor/ova/rangeslider",
"share-annotator": 'js/vendor/ova/share-annotator', "share-annotator": "js/vendor/ova/share-annotator",
"richText-annotator": 'js/vendor/ova/richText-annotator', "richText-annotator": "js/vendor/ova/richText-annotator",
"reply-annotator": 'js/vendor/ova/reply-annotator', "reply-annotator": "js/vendor/ova/reply-annotator",
"grouping-annotator": 'js/vendor/ova/grouping-annotator', "grouping-annotator": "js/vendor/ova/grouping-annotator",
"tags-annotator": 'js/vendor/ova/tags-annotator', "tags-annotator": "js/vendor/ova/tags-annotator",
"diacritic-annotator": 'js/vendor/ova/diacritic-annotator', "diacritic-annotator": "js/vendor/ova/diacritic-annotator",
"flagging-annotator": 'js/vendor/ova/flagging-annotator', "flagging-annotator": "js/vendor/ova/flagging-annotator",
"jquery-Watch": 'js/vendor/ova/jquery-Watch', "jquery-Watch": "js/vendor/ova/jquery-Watch",
"openseadragon": 'js/vendor/ova/openseadragon', "openseadragon": "js/vendor/ova/openseadragon",
"osda": 'js/vendor/ova/OpenSeaDragonAnnotation', "osda": "js/vendor/ova/OpenSeaDragonAnnotation",
"ova": 'js/vendor/ova/ova', "ova": "js/vendor/ova/ova",
"catch": 'js/vendor/ova/catch/js/catch', "catch": "js/vendor/ova/catch/js/catch",
"handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2' "handlebars": "js/vendor/ova/catch/js/handlebars-1.1.2",
"tinymce": "js/vendor/tinymce/js/tinymce/tinymce.full.min",
"jquery.tinymce": "js/vendor/tinymce/js/tinymce/jquery.tinymce.min"
// end of files needed by OVA // end of files needed by OVA
}, },
shim: { shim: {
"gettext": {
exports: "gettext"
},
"annotator_1.2.9": { "annotator_1.2.9": {
deps: ["jquery"], deps: ["jquery"],
exports: "Annotator" exports: "Annotator"
...@@ -83,6 +88,22 @@ ...@@ -83,6 +88,22 @@
"jquery": { "jquery": {
exports: "$" exports: "$"
}, },
"jquery.cookie": {
deps: ["jquery"],
exports: "jQuery.fn.cookie"
},
"jquery.url": {
deps: ["jquery"],
exports: "jQuery.url"
},
"jquery.fileupload": {
deps: ["jquery.ui", "jquery.iframe-transport"],
exports: "jQuery.fn.fileupload"
},
"jquery.tinymce": {
deps: ["jquery", "tinymce"],
exports: "jQuery.fn.tinymce"
},
"underscore": { "underscore": {
exports: "_" exports: "_"
}, },
...@@ -97,8 +118,9 @@ ...@@ -97,8 +118,9 @@
"backbone-super": { "backbone-super": {
deps: ["backbone"] deps: ["backbone"]
}, },
"logger": { "string_utils": {
exports: "Logger" deps: ["underscore"],
exports: "interpolate_text"
}, },
// Needed by OVA // Needed by OVA
"video.dev": { "video.dev": {
...@@ -153,15 +175,11 @@ ...@@ -153,15 +175,11 @@
"grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars", "grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars",
"URI" "URI"
] ]
},
"tinymce": {
exports: "tinymce"
} }
// End of needed by OVA // End of needed by OVA
} }
}; });
for (var key in paths) {
if ({}.hasOwnProperty.call(paths, key)) {
config.paths[key] = paths[key];
}
}
require.config(config);
}).call(this, require || RequireJS.require, define || RequireJS.define); }).call(this, require || RequireJS.require, define || RequireJS.define);
...@@ -19,9 +19,7 @@ from django.utils.translation import ugettext as _ ...@@ -19,9 +19,7 @@ from django.utils.translation import ugettext as _
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script> <script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script>
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<script src="${static.url('js/commerce/views/receipt_view.js')}"></script> <script src="${static.url('js/commerce/views/receipt_view.js')}"></script>
</%block> </%block>
......
...@@ -2,33 +2,27 @@ ...@@ -2,33 +2,27 @@
import json import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from microsite_configuration import microsite from microsite_configuration import microsite
from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
%> %>
<%inherit file="../main.html" /> <%inherit file="../main.html" />
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
<%block name="header_extras"> <%block name="header_extras">
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
% for template_name in ["result_item", "filter_bar", "filter", "search_facets_list", "search_facets_section", "search_facet", "more_less_links"]: % for template_name in ["result_item", "filter_bar", "filter", "search_facets_list", "search_facets_section", "search_facet", "more_less_links"]:
<script type="text/template" id="${template_name}-tpl"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="discovery/${template_name}.underscore" /> <%static:include path="discovery/${template_name}.underscore" />
</script> </script>
% endfor % endfor
<script type="text/javascript">;(function (define) {{ <%static:require_module module_name="js/discovery/discovery_factory" class_name="DiscoveryFactory">
define('course_discovery_meanings', function() {{ DiscoveryFactory(
'use strict'; ${json.dumps(course_discovery_meanings, cls=EscapedEdxJSONEncoder)},
return ${json.dumps(course_discovery_meanings)}; getParameterByName('search_query')
}}); );
}})(define || RequireJS.define); </%static:require_module>
</script>
% endif
</%block>
<%block name="js_extra">
% if settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
<%static:js group='discovery'/>
% endif
</%block> </%block>
% endif
<%block name="pagetitle">${_("Courses")}</%block> <%block name="pagetitle">${_("Courses")}</%block>
<% <%
......
...@@ -66,7 +66,10 @@ ${page_title_breadcrumbs(course_name())} ...@@ -66,7 +66,10 @@ ${page_title_breadcrumbs(course_name())}
<%static:js group='courseware'/> <%static:js group='courseware'/>
<%static:js group='discussion'/> <%static:js group='discussion'/>
% if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'): % if settings.FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
<%static:js group='courseware_search'/> <%static:require_module module_name="js/search/course/course_search_factory" class_name="CourseSearchFactory">
var courseId = $('#courseware-search-results').data('courseId');
CourseSearchFactory(courseId);
</%static:require_module>
% endif % endif
<%include file="../discussion/_js_body_dependencies.html" /> <%include file="../discussion/_js_body_dependencies.html" />
......
...@@ -45,7 +45,9 @@ from django.core.urlresolvers import reverse ...@@ -45,7 +45,9 @@ from django.core.urlresolvers import reverse
}); });
</script> </script>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): % if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:js group='dashboard_search'/> <%static:require_module module_name="js/search/dashboard/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory();
</%static:require_module>
% endif % endif
</%block> </%block>
......
...@@ -9,9 +9,7 @@ ...@@ -9,9 +9,7 @@
<script type="text/javascript" src="${static.url('js/jquery.autocomplete.js')}"></script> <script type="text/javascript" src="${static.url('js/jquery.autocomplete.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/jquery.timeago.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/jquery.timeago.js')}"></script>
<script type="text/javascript" src="${static.url('js/src/jquery.timeago.locale.js')}"></script> <script type="text/javascript" src="${static.url('js/src/jquery.timeago.locale.js')}"></script>
<script type="text/javascript" src="${static.url('js/mustache.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/mustache.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/URI.min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/src/tooltip_manager.js')}"></script> <script type="text/javascript" src="${static.url('js/src/tooltip_manager.js')}"></script>
<link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css"> <link href="${static.url('css/vendor/jquery.autocomplete.css')}" rel="stylesheet" type="text/css">
...@@ -102,17 +102,14 @@ import json ...@@ -102,17 +102,14 @@ import json
</script> </script>
% endfor % endfor
</%block> </%block>
<%block name="js_extra">
% if notes: % if notes:
<script type="text/javascript"> <%block name="js_extra">
(function (require) { <%static:require_module module_name="js/edxnotes/views/page_factory" class_name="NotesPageFactory">
require(['js/edxnotes/views/page_factory'], function (NotesPageFactory) { NotesPageFactory({
var pageView = new NotesPageFactory({ notesList: ${notes if notes is not None else []},
notesList: ${notes}, debugMode: ${debug}
debugMode: ${debug} });
}); </%static:require_module>
}); </%block>
}).call(this, require || RequireJS.require);
</script>
% endif % endif
</%block>
...@@ -3,6 +3,7 @@ import json ...@@ -3,6 +3,7 @@ import json
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
%> %>
<%namespace name='static' file='/static_content.html'/>
<%page args="course"/> <%page args="course"/>
<% <%
...@@ -20,10 +21,7 @@ from django.core.urlresolvers import reverse ...@@ -20,10 +21,7 @@ from django.core.urlresolvers import reverse
% endif % endif
</button> </button>
</div> </div>
<script type="text/javascript">
(function (require) { <%static:require_module module_name="js/edxnotes/views/notes_visibility_factory" class_name="NotesVisibilityFactory">
require(['js/edxnotes/views/toggle_notes_factory'], function(ToggleNotesFactory) { NotesVisibilityFactory.ToggleVisibilityView(${json.dumps(edxnotes_visibility)}, '${edxnotes_visibility_url}');
ToggleNotesFactory(${json.dumps(edxnotes_visibility)}, '${edxnotes_visibility_url}'); </%static:require_module>
});
}).call(this, require || RequireJS.require);
</script>
<%page args="section_data"/> <%page args="section_data"/>
<%namespace name='static' file='../../static_content.html'/>
<%! <%!
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from courseware.courses import get_studio_url from courseware.courses import get_studio_url
...@@ -16,32 +17,23 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_ ...@@ -16,32 +17,23 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
</div> </div>
<%block name="headextra"> <%block name="js_extra">
<%static:require_module module_name="js/groups/views/cohorts_dashboard_factory" class_name="CohortsFactory">
<% <%
cohorted_user_partition = get_cohorted_user_partition(course.id) cohorted_user_partition = get_cohorted_user_partition(course.id)
content_groups = cohorted_user_partition.groups if cohorted_user_partition else [] content_groups = cohorted_user_partition.groups if cohorted_user_partition else []
%> %>
<script type="text/javascript"> var cohortUserPartitionId = ${cohorted_user_partition.id if cohorted_user_partition else 'null'},
$(document).ready(function() { contentGroups = [
% for content_group in content_groups:
var cohortUserPartitionId = ${cohorted_user_partition.id if cohorted_user_partition else 'null'}, {
contentGroups = [ id: ${content_group.id},
% for content_group in content_groups: name: "${content_group.name | h}",
new edx.groups.ContentGroupModel({ user_partition_id: cohortUserPartitionId
id: ${content_group.id}, },
name: "${content_group.name | h}", % endfor
user_partition_id: cohortUserPartitionId ];
}), CohortsFactory(contentGroups, '${get_studio_url(course, 'group_configurations') | h}');
% endfor </%static:require_module>
];
(function (require) {
require(['js/groups/views/cohorts_dashboard_factory'], function (CohortsFactory) {
CohortsFactory(contentGroups, '${get_studio_url(course, 'group_configurations') | h}');
});
}).call(this, require || RequireJS.require);
});
</script>
</%block> </%block>
<div class="cohort-state-message"></div> <div class="cohort-state-message"></div>
...@@ -34,7 +34,6 @@ from django.core.urlresolvers import reverse ...@@ -34,7 +34,6 @@ from django.core.urlresolvers import reverse
window.Range.prototype = { }; window.Range.prototype = { };
} }
</script> </script>
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/mustache.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/mustache.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script>
...@@ -57,22 +56,9 @@ from django.core.urlresolvers import reverse ...@@ -57,22 +56,9 @@ from django.core.urlresolvers import reverse
<%static:js group='application'/> <%static:js group='application'/>
## Backbone classes declared explicitly until RequireJS is supported ## Backbone classes declared explicitly until RequireJS is supported
<script type="text/javascript" src="${static.url('js/instructor_dashboard/cohort_management.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/notification.js')}"></script> <script type="text/javascript" src="${static.url('js/models/notification.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/notification.js')}"></script> <script type="text/javascript" src="${static.url('js/views/notification.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script> <script type="text/javascript" src="${static.url('js/views/file_uploader.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/cohort.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/content_group.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/course_cohort_settings.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/collections/cohort.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/course_cohort_settings_notification.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/models/cohort_discussions.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_discussions.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_discussions_course_wide.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_discussions_inline.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_form.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohort_editor.js')}"></script>
<script type="text/javascript" src="${static.url('js/groups/views/cohorts.js')}"></script>
<script type="text/javascript" src="${static.url('js/utils/animation.js')}"></script> <script type="text/javascript" src="${static.url('js/utils/animation.js')}"></script>
</%block> </%block>
......
...@@ -57,7 +57,7 @@ from branding import api as branding_api ...@@ -57,7 +57,7 @@ from branding import api as branding_api
</script> </script>
% endif % endif
<script type="text/javascript" src="/jsi18n/"></script> <script type="text/javascript" src="/i18n.js"></script>
<link rel="icon" type="image/x-icon" href="${static.url(microsite.get_value('favicon_path', settings.FAVICON_PATH))}" /> <link rel="icon" type="image/x-icon" href="${static.url(microsite.get_value('favicon_path', settings.FAVICON_PATH))}" />
...@@ -66,26 +66,22 @@ from branding import api as branding_api ...@@ -66,26 +66,22 @@ from branding import api as branding_api
% if disable_courseware_js: % if disable_courseware_js:
<%static:js group='base_vendor'/> <%static:js group='base_vendor'/>
<%static:js group='base_application'/>
% else: % else:
<%static:js group='main_vendor'/> <%static:js group='main_vendor'/>
<%static:js group='application'/>
<%static:js group='module-js'/>
% endif % endif
<script> <script>
window.baseUrl = "${settings.STATIC_URL}"; window.baseUrl = "${settings.STATIC_URL}";
(function (require) { (function (require) {
% if settings.DEBUG is True:
## Using what amounts to a random number in the Development environment for cache-busting
var urlArgs = "bust=" + (new Date()).getTime();
% else:
var urlArgs = "v=${settings.EDX_PLATFORM_REVISION}";
% endif
require.config({ require.config({
baseUrl: baseUrl, baseUrl: window.baseUrl
urlArgs: urlArgs
}); });
}).call(this, require || RequireJS.require); }).call(this, require || RequireJS.require);
</script> </script>
<script type="text/javascript" src="${static.url("require-config-lms.js")}"></script> <script type="text/javascript" src="${static.url("lms/js/require-config.js")}"></script>
<%block name="headextra"/> <%block name="headextra"/>
...@@ -160,11 +156,6 @@ from branding import api as branding_api ...@@ -160,11 +156,6 @@ from branding import api as branding_api
</div> </div>
% endif % endif
% if not disable_courseware_js:
<%static:js group='application'/>
<%static:js group='module-js'/>
% endif
<%block name="js_extra"/> <%block name="js_extra"/>
<script type="text/javascript" src="${static.url('js/vendor/noreferrer.js')}" charset="utf-8"></script> <script type="text/javascript" src="${static.url('js/vendor/noreferrer.js')}" charset="utf-8"></script>
</body> </body>
......
...@@ -37,21 +37,16 @@ from microsite_configuration import microsite ...@@ -37,21 +37,16 @@ from microsite_configuration import microsite
<%block name="headextra"> <%block name="headextra">
<%static:css group='style-course'/> <%static:css group='style-course'/>
</%block>
<script> <%block name="js_extra">
(function (require) { <%static:require_module module_name="js/student_account/views/account_settings_factory" class_name="AccountSettingsFactory">
require(['js/student_account/views/account_settings_factory'], function(setupAccountSettingsSection) { var fieldsData = ${ json.dumps(fields) };
var authData = ${ json.dumps(auth) };
var fieldsData = ${ json.dumps(fields) }; var platformName = ${ json.dumps(microsite.get_value('platform_name', settings.PLATFORM_NAME))};
var authData = ${ json.dumps(auth) };
var platformName = ${ json.dumps(microsite.get_value('platform_name', settings.PLATFORM_NAME))};
setupAccountSettingsSection(
fieldsData, authData, '${user_accounts_api_url}', '${user_preferences_api_url}', ${user.id},
platformName
);
});
}).call(this, require || RequireJS.require);
</script>
AccountSettingsFactory(
fieldsData, authData, '${user_accounts_api_url}', '${user_preferences_api_url}', ${user.id}, platformName
);
</%static:require_module>
</%block> </%block>
...@@ -4,38 +4,10 @@ ...@@ -4,38 +4,10 @@
<%block name="pagetitle">${_("Please Wait")}</%block> <%block name="pagetitle">${_("Please Wait")}</%block>
<%block name="js_extra">
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<%static:js group='utility'/>
</%block>
<%block name="headextra"> <%block name="headextra">
<%static:require_module module_name="js/student_account/views/finish_auth_factory" class_name="FinishAuthFactory">
<script> FinishAuthFactory();
(function (require, define) { </%static:require_module>
'use strict';
define("js/student_account/views/finish_auth_factory",
[
'jquery', 'underscore', 'backbone',
'js/student_account/views/FinishAuthView'
],
function ($, _, Backbone, FinishAuthView) {
return function() {
var view = new FinishAuthView({});
view.render();
};
}
);
require(["js/student_account/views/finish_auth_factory"],
function (factory) {
factory();
}
);
}).call(this, require || RequireJS.require, define || RequireJS.define);
</script>
</%block> </%block>
<div class="finish-auth"> <div class="finish-auth">
......
...@@ -6,10 +6,7 @@ ...@@ -6,10 +6,7 @@
<%block name="pagetitle">${_("Sign in or Register")}</%block> <%block name="pagetitle">${_("Sign in or Register")}</%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/vendor/url.min.js')}"></script>
<script src="${static.url('js/vendor/history.js')}"></script> <script src="${static.url('js/vendor/history.js')}"></script>
<%static:js group='student_account'/> <%static:js group='student_account'/>
</%block> </%block>
......
...@@ -34,16 +34,11 @@ from openedx.core.lib.json_utils import EscapedEdxJSONEncoder ...@@ -34,16 +34,11 @@ from openedx.core.lib.json_utils import EscapedEdxJSONEncoder
</div> </div>
<%block name="headextra"> <%block name="headextra">
<%static:css group='style-course'/> <%static:css group='style-course'/>
</%block>
<script type="text/javascript" src="${static.url('js/vendor/jQuery-File-Upload/js/jquery.fileupload.js')}"></script> <%block name="js_extra">
<%static:require_module module_name="js/student_profile/views/learner_profile_factory" class_name="LearnerProfileFactory">
<script> var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
(function (require) { LearnerProfileFactory(options);
require(['js/student_profile/views/learner_profile_factory'], function(setupLearnerProfile) { </%static:require_module>
var options = ${ json.dumps(data, cls=EscapedEdxJSONEncoder) };
setupLearnerProfile(options);
});
}).call(this, require || RequireJS.require);
</script>
</%block> </%block>
...@@ -18,9 +18,7 @@ from django.utils.translation import ugettext as _ ...@@ -18,9 +18,7 @@ from django.utils.translation import ugettext as _
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='incourse_reverify'/> <%static:js group='incourse_reverify'/>
</%block> </%block>
......
...@@ -35,9 +35,7 @@ from verify_student.views import PayAndVerifyView ...@@ -35,9 +35,7 @@ from verify_student.views import PayAndVerifyView
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='verify_student'/> <%static:js group='verify_student'/>
</%block> </%block>
......
...@@ -16,9 +16,7 @@ from django.utils.translation import ugettext as _ ...@@ -16,9 +16,7 @@ from django.utils.translation import ugettext as _
% endfor % endfor
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/vendor/underscore-min.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/vendor/backbone-min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<%static:js group='reverify'/> <%static:js group='reverify'/>
</%block> </%block>
......
...@@ -137,7 +137,7 @@ js_info_dict = { ...@@ -137,7 +137,7 @@ js_info_dict = {
urlpatterns += ( urlpatterns += (
# Serve catalog of localized strings to be rendered by Javascript # Serve catalog of localized strings to be rendered by Javascript
url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict), url(r'^i18n.js$', 'django.views.i18n.javascript_catalog', js_info_dict),
) )
# sysadmin dashboard, to see what courses are loaded, to delete & load courses # sysadmin dashboard, to see what courses are loaded, to delete & load courses
......
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