Convert the message number parameter into the MVC route part. Correct the result of the restart lesson button.
This commit is contained in:
parent
de71f2700e
commit
ea1d852cda
@ -1,164 +1,195 @@
|
|||||||
define(['jquery',
|
define(['jquery',
|
||||||
'underscore',
|
'underscore',
|
||||||
'libs/backbone',
|
'libs/backbone',
|
||||||
'goatApp/model/LessonContentModel',
|
'goatApp/model/LessonContentModel',
|
||||||
'goatApp/view/LessonContentView',
|
'goatApp/view/LessonContentView',
|
||||||
'goatApp/view/PlanView',
|
'goatApp/view/PlanView',
|
||||||
'goatApp/view/SourceView',
|
'goatApp/view/SourceView',
|
||||||
'goatApp/view/SolutionView',
|
'goatApp/view/SolutionView',
|
||||||
'goatApp/view/HintView',
|
'goatApp/view/HintView',
|
||||||
'goatApp/view/HelpControlsView',
|
'goatApp/view/HelpControlsView',
|
||||||
'goatApp/view/CookieView',
|
'goatApp/view/CookieView',
|
||||||
'goatApp/view/ParamView',
|
'goatApp/view/ParamView',
|
||||||
'goatApp/model/ParamModel',
|
'goatApp/model/ParamModel',
|
||||||
'goatApp/support/GoatUtils',
|
'goatApp/support/GoatUtils',
|
||||||
'goatApp/view/UserAndInfoView',
|
'goatApp/view/UserAndInfoView',
|
||||||
'goatApp/view/MenuButtonView',
|
'goatApp/view/MenuButtonView',
|
||||||
'goatApp/model/LessonInfoModel',
|
'goatApp/model/LessonInfoModel',
|
||||||
'goatApp/view/TitleView'
|
'goatApp/view/TitleView'
|
||||||
],
|
],
|
||||||
function($,
|
function($,
|
||||||
_,
|
_,
|
||||||
Backbone,
|
Backbone,
|
||||||
LessonContentModel,
|
LessonContentModel,
|
||||||
LessonContentView,
|
LessonContentView,
|
||||||
PlanView,
|
PlanView,
|
||||||
SourceView,
|
SourceView,
|
||||||
SolutionView,
|
SolutionView,
|
||||||
HintView,
|
HintView,
|
||||||
HelpControlsView,
|
HelpControlsView,
|
||||||
CookieView,
|
CookieView,
|
||||||
ParamView,
|
ParamView,
|
||||||
ParamModel,
|
ParamModel,
|
||||||
GoatUtils,
|
GoatUtils,
|
||||||
UserAndInfoView,
|
UserAndInfoView,
|
||||||
MenuButtonView,
|
MenuButtonView,
|
||||||
LessonInfoModel,
|
LessonInfoModel,
|
||||||
TitleView
|
TitleView
|
||||||
) {
|
) {
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
|
||||||
var Controller = function(options) {
|
var Controller = function(options) {
|
||||||
this.lessonContent = new LessonContentModel();
|
this.lessonContent = new LessonContentModel();
|
||||||
this.lessonView = options.lessonView;
|
this.lessonView = options.lessonView;
|
||||||
|
|
||||||
_.extend(Controller.prototype,Backbone.Events);
|
_.extend(Controller.prototype,Backbone.Events);
|
||||||
|
|
||||||
this.start = function() {
|
this.start = function() {
|
||||||
this.listenTo(this.lessonContent,'content:loaded',this.onContentLoaded);
|
this.listenTo(this.lessonContent,'content:loaded',this.onContentLoaded);
|
||||||
this.userAndInfoView = new UserAndInfoView();
|
this.userAndInfoView = new UserAndInfoView();
|
||||||
this.menuButtonView = new MenuButtonView();
|
this.menuButtonView = new MenuButtonView();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loadLesson = function(scr,menu,stage) {
|
this.loadLesson = function(scr,menu,stage,num) {
|
||||||
this.titleView = new TitleView();
|
console.log("Loading a lesson, scr: " + scr + ", menu: " + menu + ", stage: " + stage + ", num: " + num);
|
||||||
this.helpsLoaded = {};
|
this.titleView = new TitleView();
|
||||||
this.lessonContent.loadData({
|
this.helpsLoaded = {};
|
||||||
'screen': scr,
|
if (typeof(scr) == "undefined") {
|
||||||
'menu': menu,
|
scr = null;
|
||||||
'stage': stage
|
}
|
||||||
});
|
if (typeof(menu) == "undefined") {
|
||||||
this.planView = {};
|
menu = null;
|
||||||
this.solutionView = {};
|
}
|
||||||
this.sourceView = {};
|
if (typeof(stage) == "undefined") {
|
||||||
this.lessonHintView = {};
|
stage = null;
|
||||||
this.screen = scr;
|
}
|
||||||
this.menu = menu;
|
if (typeof(num) == "undefined") {
|
||||||
};
|
num = null;
|
||||||
|
}
|
||||||
|
this.lessonContent.loadData({
|
||||||
|
'scr': scr,
|
||||||
|
'menu': menu,
|
||||||
|
'stage': stage,
|
||||||
|
'num': num,
|
||||||
|
});
|
||||||
|
this.planView = {};
|
||||||
|
this.solutionView = {};
|
||||||
|
this.sourceView = {};
|
||||||
|
this.lessonHintView = {};
|
||||||
|
this.scr = scr;
|
||||||
|
this.menu = menu;
|
||||||
|
this.stage = stage;
|
||||||
|
this.num = num;
|
||||||
|
console.log("Lesson loading initiated")
|
||||||
|
};
|
||||||
|
|
||||||
this.onInfoLoaded = function() {
|
this.onInfoLoaded = function() {
|
||||||
this.helpControlsView = new HelpControlsView({
|
console.log("Lesson info loaded")
|
||||||
hasPlan:this.lessonInfoModel.get('hasPlan'),
|
this.helpControlsView = new HelpControlsView({
|
||||||
hasSolution:this.lessonInfoModel.get('hasSolution'),
|
hasPlan:this.lessonInfoModel.get('hasPlan'),
|
||||||
hasSource:this.lessonInfoModel.get('hasSource'),
|
hasSolution:this.lessonInfoModel.get('hasSolution'),
|
||||||
hasHints:(this.lessonInfoModel.get('numberHints') > 0),
|
hasSource:this.lessonInfoModel.get('hasSource'),
|
||||||
});
|
hasHints:(this.lessonInfoModel.get('numberHints') > 0),
|
||||||
|
});
|
||||||
|
|
||||||
this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps);
|
this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps);
|
||||||
this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps);
|
this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps);
|
||||||
this.listenTo(this.helpControlsView,'hints:show',this.onShowHints)
|
this.listenTo(this.helpControlsView,'hints:show',this.onShowHints)
|
||||||
this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps);
|
this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps);
|
||||||
this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson);
|
this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson);
|
||||||
|
|
||||||
this.helpControlsView.render();
|
this.helpControlsView.render();
|
||||||
|
|
||||||
this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
|
this.titleView.render(this.lessonInfoModel.get('lessonTitle'));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onContentLoaded = function(loadHelps) {
|
this.onContentLoaded = function(loadHelps) {
|
||||||
this.lessonInfoModel = new LessonInfoModel();
|
console.log("Lesson content loaded")
|
||||||
this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded);
|
this.lessonInfoModel = new LessonInfoModel();
|
||||||
|
this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded);
|
||||||
|
|
||||||
if (loadHelps) {
|
if (loadHelps) {
|
||||||
this.helpControlsView = null;
|
this.helpControlsView = null;
|
||||||
this.lessonView.model = this.lessonContent;
|
this.lessonView.model = this.lessonContent;
|
||||||
this.lessonView.render();
|
this.lessonView.render();
|
||||||
|
|
||||||
this.planView = new PlanView();
|
this.planView = new PlanView();
|
||||||
this.solutionView = new SolutionView();
|
this.solutionView = new SolutionView();
|
||||||
this.sourceView = new SourceView();
|
this.sourceView = new SourceView();
|
||||||
this.lessonHintView = new HintView();
|
this.lessonHintView = new HintView();
|
||||||
this.cookieView = new CookieView();
|
this.cookieView = new CookieView();
|
||||||
//TODO: instantiate model with values (not sure why was not working before)
|
//TODO: instantiate model with values (not sure why was not working before)
|
||||||
var paramModel = new ParamModel({});
|
var paramModel = new ParamModel({});
|
||||||
paramModel.set('screenParam',this.lessonContent.get('screenParam'));
|
paramModel.set('scrParam',this.lessonContent.get('scrParam'));
|
||||||
paramModel.set('menuParam',this.lessonContent.get('menuParam'));
|
paramModel.set('menuParam',this.lessonContent.get('menuParam'));
|
||||||
paramModel.set('stageParam',this.lessonContent.get('stageParam'));
|
paramModel.set('stageParam',this.lessonContent.get('stageParam'));
|
||||||
this.paramView = new ParamView({model:paramModel});
|
paramModel.set('numParam',this.lessonContent.get('numParam'));
|
||||||
|
this.paramView = new ParamView({model:paramModel});
|
||||||
|
|
||||||
$('.lesson-help').hide();
|
$('.lesson-help').hide();
|
||||||
}
|
}
|
||||||
this.trigger('menu:reload');
|
this.trigger('menu:reload');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.addCurHelpState = function (curHelp) {
|
this.addCurHelpState = function (curHelp) {
|
||||||
this.helpsLoaded[curHelp.helpElement] = curHelp.value;
|
this.helpsLoaded[curHelp.helpElement] = curHelp.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hideShowHelps = function(showHelp) {
|
this.hideShowHelps = function(showHelp) {
|
||||||
var showId = '#lesson-' + showHelp + '-row';
|
var showId = '#lesson-' + showHelp + '-row';
|
||||||
var contentId = '#lesson-' + showHelp + '-content';
|
var contentId = '#lesson-' + showHelp + '-content';
|
||||||
$('.lesson-help').not(showId).hide();
|
$('.lesson-help').not(showId).hide();
|
||||||
if (!showId) {
|
if (!showId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($(showId).is(':visible')) {
|
if ($(showId).is(':visible')) {
|
||||||
$(showId).hide();
|
$(showId).hide();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
//TODO: move individual .html operations into individual help views
|
//TODO: move individual .html operations into individual help views
|
||||||
switch(showHelp) {
|
switch(showHelp) {
|
||||||
case 'plan':
|
case 'plan':
|
||||||
$(contentId).html(this.planView.model.get('content'));
|
$(contentId).html(this.planView.model.get('content'));
|
||||||
break;
|
break;
|
||||||
case 'solution':
|
case 'solution':
|
||||||
$(showId).html(this.solutionView.model.get('content'));
|
$(showId).html(this.solutionView.model.get('content'));
|
||||||
break;
|
break;
|
||||||
case 'source':
|
case 'source':
|
||||||
$(contentId).html('<pre>' + this.sourceView.model.get('content') + '</pre>');
|
$(contentId).html('<pre>' + this.sourceView.model.get('content') + '</pre>');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$(showId).show();
|
$(showId).show();
|
||||||
GoatUtils.scrollToHelp()
|
GoatUtils.scrollToHelp()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onShowHints = function() {
|
this.onShowHints = function() {
|
||||||
this.lessonHintView.render();
|
this.lessonHintView.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.restartLesson = function() {
|
this.restartLesson = function() {
|
||||||
var self=this;
|
var self=this;
|
||||||
$.ajax({
|
var fragment = "attack/" + self.scr + "/" + self.menu;
|
||||||
url:'service/restartlesson.mvc',
|
console.log("Navigating to " + fragment);
|
||||||
method:'GET'
|
// Avoiding the trigger event - handle - navigate loop by
|
||||||
}).then(function() {
|
// loading the lesson explicitly (after executing the restart
|
||||||
self.loadLesson(self.screen,self.menu);
|
// servlet).
|
||||||
});
|
goatRouter.navigate(fragment);
|
||||||
};
|
// Resetting the user's lesson state (assuming a single browser
|
||||||
|
// and session per user).
|
||||||
|
$.ajax({
|
||||||
|
url:'service/restartlesson.mvc',
|
||||||
|
method:'GET'
|
||||||
|
}).done(function(text) {
|
||||||
|
console.log("Received a response from the restart servlet: '" + text + "'");
|
||||||
|
// Explicitly loading the lesson instead of triggering an
|
||||||
|
// event in goatRouter.navigate().
|
||||||
|
self.loadLesson(self.scr,self.menu);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
return Controller;
|
return Controller;
|
||||||
});
|
});
|
||||||
|
@ -1,46 +1,54 @@
|
|||||||
define(['jquery',
|
define(['jquery',
|
||||||
'underscore',
|
'underscore',
|
||||||
'backbone',
|
'backbone',
|
||||||
'goatApp/model/HTMLContentModel'],
|
'goatApp/model/HTMLContentModel'],
|
||||||
function($,
|
function($,
|
||||||
_,
|
_,
|
||||||
Backbone,
|
Backbone,
|
||||||
HTMLContentModel){
|
HTMLContentModel){
|
||||||
|
|
||||||
return HTMLContentModel.extend({
|
return HTMLContentModel.extend({
|
||||||
urlRoot:null,
|
urlRoot:null,
|
||||||
defaults: {
|
defaults: {
|
||||||
items:null,
|
items:null,
|
||||||
selectedItem:null
|
selectedItem:null
|
||||||
},
|
},
|
||||||
initialize: function (options) {
|
initialize: function (options) {
|
||||||
this.screenParam = null;
|
this.scrParam = null;
|
||||||
this.menuParam = null;
|
this.menuParam = null;
|
||||||
this.stageParam = null;
|
this.stageParam = null;
|
||||||
this.baseUrlRoot = 'attack?Screen=';
|
this.numParam = null;
|
||||||
},
|
this.baseUrlRoot = 'attack';
|
||||||
loadData: function(options) {
|
},
|
||||||
this.urlRoot = this.baseUrlRoot +options.screen + '&menu=' + options.menu + '&stage=' + options.stage;
|
loadData: function(options) {
|
||||||
this.set('menuParam',options.menu);
|
this.urlRoot = this.baseUrlRoot + "?Screen=" + options.scr + '&menu=' + options.menu;
|
||||||
this.set('screenParam',options.screen);
|
if (options.stage != null) {
|
||||||
this.set('stageParam',options.stage)
|
this.urlRoot += '&stage=' + options.stage;
|
||||||
var self=this;
|
}
|
||||||
this.fetch().then(function(data) {
|
if (options.num != null) {
|
||||||
self.setContent(data);
|
this.urlRoot += '&Num=' + options.num;
|
||||||
});
|
}
|
||||||
},
|
this.set('menuParam', options.menu);
|
||||||
|
this.set('scrParam', options.scr);
|
||||||
|
this.set('stageParam', options.stage)
|
||||||
|
this.set('numParam', options.num)
|
||||||
|
var self = this;
|
||||||
|
this.fetch().done(function(data) {
|
||||||
|
self.setContent(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setContent: function(content, loadHelps) {
|
setContent: function(content, loadHelps) {
|
||||||
if (typeof loadHelps === 'undefined') {
|
if (typeof loadHelps === 'undefined') {
|
||||||
loadHelps = true;
|
loadHelps = true;
|
||||||
}
|
}
|
||||||
this.set('content',content);
|
this.set('content',content);
|
||||||
this.trigger('content:loaded',this,loadHelps);
|
this.trigger('content:loaded',this,loadHelps);
|
||||||
},
|
},
|
||||||
|
|
||||||
fetch: function (options) {
|
fetch: function (options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options));
|
return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,9 @@ define(['jquery',
|
|||||||
var GoatAppRouter = Backbone.Router.extend({
|
var GoatAppRouter = Backbone.Router.extend({
|
||||||
routes: {
|
routes: {
|
||||||
'welcome':'welcomeRoute',
|
'welcome':'welcomeRoute',
|
||||||
'attack/:scr/:menu(/:stage)':'attackRoute',
|
'attack/:scr/:menu':'attackRoute',
|
||||||
|
'attack/:scr/:menu/:stage':'attackRoute',
|
||||||
|
'attack/:scr/:menu/*stage/:num':'attackRoute',
|
||||||
},
|
},
|
||||||
|
|
||||||
lessonController: new LessonController({
|
lessonController: new LessonController({
|
||||||
@ -35,14 +37,17 @@ define(['jquery',
|
|||||||
this.lessonController.start();
|
this.lessonController.start();
|
||||||
// this.menuController.initMenu();
|
// this.menuController.initMenu();
|
||||||
|
|
||||||
goatRouter.on('route:attackRoute', function(scr,menu,stage) {
|
goatRouter.on('route:attackRoute', function(scr,menu,stage,num) {
|
||||||
this.lessonController.loadLesson(scr,menu,stage);
|
this.lessonController.loadLesson(scr,menu,stage,num);
|
||||||
this.menuController.updateMenu(scr,menu);
|
this.menuController.updateMenu(scr,menu);
|
||||||
//update menu
|
//update menu
|
||||||
});
|
});
|
||||||
goatRouter.on('route:welcomeRoute', function() {
|
goatRouter.on('route:welcomeRoute', function() {
|
||||||
this.lessonController.loadWelcome();
|
this.lessonController.loadWelcome();
|
||||||
});
|
});
|
||||||
|
goatRouter.on("route", function(route, params) {
|
||||||
|
console.log("Got a route event: " + route + ", params: " + params);
|
||||||
|
});
|
||||||
|
|
||||||
Backbone.history.start();
|
Backbone.history.start();
|
||||||
this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu)
|
this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu)
|
||||||
@ -57,4 +62,4 @@ define(['jquery',
|
|||||||
|
|
||||||
return GoatAppRouter;
|
return GoatAppRouter;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,59 +1,64 @@
|
|||||||
//LessonContentView
|
//LessonContentView
|
||||||
define(['jquery',
|
define(['jquery',
|
||||||
'underscore',
|
'underscore',
|
||||||
'backbone',
|
'backbone',
|
||||||
'libs/jquery.form'],
|
'libs/jquery.form'],
|
||||||
function(
|
function(
|
||||||
$,
|
$,
|
||||||
_,
|
_,
|
||||||
Backbone,
|
Backbone,
|
||||||
JQueryForm) {
|
JQueryForm) {
|
||||||
return Backbone.View.extend({
|
return Backbone.View.extend({
|
||||||
el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM
|
el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM
|
||||||
|
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
this.$el.html(this.model.get('content'));
|
this.$el.html(this.model.get('content'));
|
||||||
this.makeFormsAjax();
|
this.makeFormsAjax();
|
||||||
this.ajaxifyAttackHref();
|
this.ajaxifyAttackHref();
|
||||||
$(window).scrollTop(0); //work-around til we get the scroll down sorted out
|
$(window).scrollTop(0); //work-around til we get the scroll down sorted out
|
||||||
},
|
},
|
||||||
|
|
||||||
//TODO: reimplement this in custom fashion maybe?
|
//TODO: reimplement this in custom fashion maybe?
|
||||||
makeFormsAjax: function () {
|
makeFormsAjax: function () {
|
||||||
var options = {
|
var options = {
|
||||||
success:this.reLoadView.bind(this),
|
success:this.reLoadView.bind(this),
|
||||||
url:'attack?Screen=' + this.model.get('screenParam') + '&menu=' + this.model.get('menuParam'),
|
url: this.model.urlRoot,
|
||||||
type:'GET'
|
type:'GET'
|
||||||
// $.ajax options can be used here too, for example:
|
// $.ajax options can be used here too, for example:
|
||||||
//timeout: 3000
|
//timeout: 3000
|
||||||
};
|
};
|
||||||
//hook forms //TODO: clarify form selectors later
|
//hook forms //TODO: clarify form selectors later
|
||||||
$("form").ajaxForm(options);
|
$("form").ajaxForm(options);
|
||||||
},
|
},
|
||||||
|
|
||||||
ajaxifyAttackHref: function() { // rewrite any links with hrefs point to relative attack URLs
|
ajaxifyAttackHref: function() { // rewrite any links with hrefs point to relative attack URLs
|
||||||
var self = this;
|
var self = this;
|
||||||
$.each($('a[href^="attack?"]'),function(i,el) {
|
// The current LessonAdapter#getLink() generates a hash-mark link. It will not match the mask below.
|
||||||
var url = $(el).attr('href');
|
// Besides, the new MVC code registers an event handler that will reload the lesson according to the route.
|
||||||
$(el).unbind('click').attr('href','#').attr('link',url);
|
$.each($('a[href^="attack?"]'),function(i,el) {
|
||||||
//TODO pull currentMenuId
|
var url = $(el).attr('href');
|
||||||
$(el).click(function() {
|
$(el).unbind('click').attr('href','#').attr('link',url);
|
||||||
event.preventDefault();
|
//TODO pull currentMenuId
|
||||||
var _url = $(el).attr('link');
|
$(el).click(function(event) {
|
||||||
$.get(_url, {success:self.reloadView.bind(self)});
|
event.preventDefault();
|
||||||
});
|
var _url = $(el).attr('link');
|
||||||
});
|
console.log("About to GET " + _url);
|
||||||
},
|
$.get(_url)
|
||||||
|
.done(self.reLoadView.bind(self))
|
||||||
|
.fail(function() { alert("failed to GET " + _url); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
reLoadView: function(content) {
|
reLoadView: function(content) {
|
||||||
this.model.setContent(content);
|
this.model.setContent(content);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
if (stages != null)
|
if (stages != null)
|
||||||
for (int i = 0; i < stages.length; i++) {
|
for (int i = 0; i < stages.length; i++) {
|
||||||
%>
|
%>
|
||||||
<tr><td class="pviimenudivstage"><%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%><a href="<%=lesson.getLink() + "&stage=" + (i + 1)%>">Stage <%=i + 1%>: <%=stages[i]%></a>
|
<tr><td class="pviimenudivstage"><%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%><a href="<%=lesson.getLink() + "/" + (i + 1)%>">Stage <%=i + 1%>: <%=stages[i]%></a>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
<%
|
<%
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user