diff --git a/webgoat-container/src/main/webapp/js/goatApp/controller/LessonController.js b/webgoat-container/src/main/webapp/js/goatApp/controller/LessonController.js index 1d14179f5..e26d7bc0e 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/controller/LessonController.js +++ b/webgoat-container/src/main/webapp/js/goatApp/controller/LessonController.js @@ -1,164 +1,195 @@ define(['jquery', - 'underscore', - 'libs/backbone', - 'goatApp/model/LessonContentModel', - 'goatApp/view/LessonContentView', - 'goatApp/view/PlanView', - 'goatApp/view/SourceView', - 'goatApp/view/SolutionView', - 'goatApp/view/HintView', - 'goatApp/view/HelpControlsView', - 'goatApp/view/CookieView', - 'goatApp/view/ParamView', - 'goatApp/model/ParamModel', - 'goatApp/support/GoatUtils', - 'goatApp/view/UserAndInfoView', - 'goatApp/view/MenuButtonView', - 'goatApp/model/LessonInfoModel', - 'goatApp/view/TitleView' - ], - function($, - _, - Backbone, - LessonContentModel, - LessonContentView, - PlanView, - SourceView, - SolutionView, - HintView, - HelpControlsView, - CookieView, - ParamView, - ParamModel, - GoatUtils, - UserAndInfoView, - MenuButtonView, - LessonInfoModel, - TitleView - ) { - 'use strict' - - - var Controller = function(options) { - this.lessonContent = new LessonContentModel(); - this.lessonView = options.lessonView; + 'underscore', + 'libs/backbone', + 'goatApp/model/LessonContentModel', + 'goatApp/view/LessonContentView', + 'goatApp/view/PlanView', + 'goatApp/view/SourceView', + 'goatApp/view/SolutionView', + 'goatApp/view/HintView', + 'goatApp/view/HelpControlsView', + 'goatApp/view/CookieView', + 'goatApp/view/ParamView', + 'goatApp/model/ParamModel', + 'goatApp/support/GoatUtils', + 'goatApp/view/UserAndInfoView', + 'goatApp/view/MenuButtonView', + 'goatApp/model/LessonInfoModel', + 'goatApp/view/TitleView' + ], + function($, + _, + Backbone, + LessonContentModel, + LessonContentView, + PlanView, + SourceView, + SolutionView, + HintView, + HelpControlsView, + CookieView, + ParamView, + ParamModel, + GoatUtils, + UserAndInfoView, + MenuButtonView, + LessonInfoModel, + TitleView + ) { + 'use strict' + + + var Controller = function(options) { + this.lessonContent = new LessonContentModel(); + this.lessonView = options.lessonView; - _.extend(Controller.prototype,Backbone.Events); + _.extend(Controller.prototype,Backbone.Events); - this.start = function() { - this.listenTo(this.lessonContent,'content:loaded',this.onContentLoaded); - this.userAndInfoView = new UserAndInfoView(); - this.menuButtonView = new MenuButtonView(); - }; + this.start = function() { + this.listenTo(this.lessonContent,'content:loaded',this.onContentLoaded); + this.userAndInfoView = new UserAndInfoView(); + this.menuButtonView = new MenuButtonView(); + }; - this.loadLesson = function(scr,menu,stage) { - this.titleView = new TitleView(); - this.helpsLoaded = {}; - this.lessonContent.loadData({ - 'screen': scr, - 'menu': menu, - 'stage': stage - }); - this.planView = {}; - this.solutionView = {}; - this.sourceView = {}; - this.lessonHintView = {}; - this.screen = scr; - this.menu = menu; - }; + this.loadLesson = function(scr,menu,stage,num) { + console.log("Loading a lesson, scr: " + scr + ", menu: " + menu + ", stage: " + stage + ", num: " + num); + this.titleView = new TitleView(); + this.helpsLoaded = {}; + if (typeof(scr) == "undefined") { + scr = null; + } + if (typeof(menu) == "undefined") { + menu = null; + } + if (typeof(stage) == "undefined") { + stage = null; + } + 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.helpControlsView = new HelpControlsView({ - hasPlan:this.lessonInfoModel.get('hasPlan'), - hasSolution:this.lessonInfoModel.get('hasSolution'), - hasSource:this.lessonInfoModel.get('hasSource'), - hasHints:(this.lessonInfoModel.get('numberHints') > 0), - }); + this.onInfoLoaded = function() { + console.log("Lesson info loaded") + this.helpControlsView = new HelpControlsView({ + hasPlan:this.lessonInfoModel.get('hasPlan'), + hasSolution:this.lessonInfoModel.get('hasSolution'), + hasSource:this.lessonInfoModel.get('hasSource'), + hasHints:(this.lessonInfoModel.get('numberHints') > 0), + }); - this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'hints:show',this.onShowHints) - this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps); - this.listenTo(this.helpControlsView,'lesson:restart',this.restartLesson); + this.listenTo(this.helpControlsView,'plan:show',this.hideShowHelps); + this.listenTo(this.helpControlsView,'solution:show',this.hideShowHelps); + this.listenTo(this.helpControlsView,'hints:show',this.onShowHints) + this.listenTo(this.helpControlsView,'source:show',this.hideShowHelps); + 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.lessonInfoModel = new LessonInfoModel(); - this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded); + this.onContentLoaded = function(loadHelps) { + console.log("Lesson content loaded") + this.lessonInfoModel = new LessonInfoModel(); + this.listenTo(this.lessonInfoModel,'info:loaded',this.onInfoLoaded); - if (loadHelps) { - this.helpControlsView = null; - this.lessonView.model = this.lessonContent; - this.lessonView.render(); - - this.planView = new PlanView(); - this.solutionView = new SolutionView(); - this.sourceView = new SourceView(); - this.lessonHintView = new HintView(); - this.cookieView = new CookieView(); - //TODO: instantiate model with values (not sure why was not working before) - var paramModel = new ParamModel({}); - paramModel.set('screenParam',this.lessonContent.get('screenParam')); - paramModel.set('menuParam',this.lessonContent.get('menuParam')); - paramModel.set('stageParam',this.lessonContent.get('stageParam')); - this.paramView = new ParamView({model:paramModel}); + if (loadHelps) { + this.helpControlsView = null; + this.lessonView.model = this.lessonContent; + this.lessonView.render(); + + this.planView = new PlanView(); + this.solutionView = new SolutionView(); + this.sourceView = new SourceView(); + this.lessonHintView = new HintView(); + this.cookieView = new CookieView(); + //TODO: instantiate model with values (not sure why was not working before) + var paramModel = new ParamModel({}); + paramModel.set('scrParam',this.lessonContent.get('scrParam')); + paramModel.set('menuParam',this.lessonContent.get('menuParam')); + paramModel.set('stageParam',this.lessonContent.get('stageParam')); + paramModel.set('numParam',this.lessonContent.get('numParam')); + this.paramView = new ParamView({model:paramModel}); - $('.lesson-help').hide(); - } - this.trigger('menu:reload'); - }; + $('.lesson-help').hide(); + } + this.trigger('menu:reload'); + }; - this.addCurHelpState = function (curHelp) { - this.helpsLoaded[curHelp.helpElement] = curHelp.value; - }; + this.addCurHelpState = function (curHelp) { + this.helpsLoaded[curHelp.helpElement] = curHelp.value; + }; - this.hideShowHelps = function(showHelp) { - var showId = '#lesson-' + showHelp + '-row'; - var contentId = '#lesson-' + showHelp + '-content'; - $('.lesson-help').not(showId).hide(); - if (!showId) { - return; - } + this.hideShowHelps = function(showHelp) { + var showId = '#lesson-' + showHelp + '-row'; + var contentId = '#lesson-' + showHelp + '-content'; + $('.lesson-help').not(showId).hide(); + if (!showId) { + return; + } - if ($(showId).is(':visible')) { - $(showId).hide(); - return; - } else { - //TODO: move individual .html operations into individual help views - switch(showHelp) { - case 'plan': - $(contentId).html(this.planView.model.get('content')); - break; - case 'solution': - $(showId).html(this.solutionView.model.get('content')); - break; - case 'source': - $(contentId).html('
' + this.sourceView.model.get('content') + '
'); - break; - } - $(showId).show(); - GoatUtils.scrollToHelp() - } - }; + if ($(showId).is(':visible')) { + $(showId).hide(); + return; + } else { + //TODO: move individual .html operations into individual help views + switch(showHelp) { + case 'plan': + $(contentId).html(this.planView.model.get('content')); + break; + case 'solution': + $(showId).html(this.solutionView.model.get('content')); + break; + case 'source': + $(contentId).html('
' + this.sourceView.model.get('content') + '
'); + break; + } + $(showId).show(); + GoatUtils.scrollToHelp() + } + }; - this.onShowHints = function() { - this.lessonHintView.render(); - }; + this.onShowHints = function() { + this.lessonHintView.render(); + }; - this.restartLesson = function() { - var self=this; - $.ajax({ - url:'service/restartlesson.mvc', - method:'GET' - }).then(function() { - self.loadLesson(self.screen,self.menu); - }); - }; + this.restartLesson = function() { + var self=this; + var fragment = "attack/" + self.scr + "/" + self.menu; + console.log("Navigating to " + fragment); + // Avoiding the trigger event - handle - navigate loop by + // loading the lesson explicitly (after executing the restart + // 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; -}); \ No newline at end of file + }; + return Controller; +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js b/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js index e588447b2..d4923640d 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js +++ b/webgoat-container/src/main/webapp/js/goatApp/model/LessonContentModel.js @@ -1,46 +1,54 @@ define(['jquery', - 'underscore', - 'backbone', - 'goatApp/model/HTMLContentModel'], - function($, - _, - Backbone, - HTMLContentModel){ + 'underscore', + 'backbone', + 'goatApp/model/HTMLContentModel'], + function($, + _, + Backbone, + HTMLContentModel){ - return HTMLContentModel.extend({ - urlRoot:null, - defaults: { - items:null, - selectedItem:null - }, - initialize: function (options) { - this.screenParam = null; - this.menuParam = null; - this.stageParam = null; - this.baseUrlRoot = 'attack?Screen='; - }, - loadData: function(options) { - this.urlRoot = this.baseUrlRoot +options.screen + '&menu=' + options.menu + '&stage=' + options.stage; - this.set('menuParam',options.menu); - this.set('screenParam',options.screen); - this.set('stageParam',options.stage) - var self=this; - this.fetch().then(function(data) { - self.setContent(data); - }); - }, + return HTMLContentModel.extend({ + urlRoot:null, + defaults: { + items:null, + selectedItem:null + }, + initialize: function (options) { + this.scrParam = null; + this.menuParam = null; + this.stageParam = null; + this.numParam = null; + this.baseUrlRoot = 'attack'; + }, + loadData: function(options) { + this.urlRoot = this.baseUrlRoot + "?Screen=" + options.scr + '&menu=' + options.menu; + if (options.stage != null) { + this.urlRoot += '&stage=' + options.stage; + } + if (options.num != null) { + 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) { - if (typeof loadHelps === 'undefined') { - loadHelps = true; - } - this.set('content',content); - this.trigger('content:loaded',this,loadHelps); - }, + setContent: function(content, loadHelps) { + if (typeof loadHelps === 'undefined') { + loadHelps = true; + } + this.set('content',content); + this.trigger('content:loaded',this,loadHelps); + }, - fetch: function (options) { - options = options || {}; - return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options)); - } - }); -}); \ No newline at end of file + fetch: function (options) { + options = options || {}; + return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options)); + } + }); +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js b/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js index 9b1b20717..b95167123 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js +++ b/webgoat-container/src/main/webapp/js/goatApp/view/GoatRouter.js @@ -19,7 +19,9 @@ define(['jquery', var GoatAppRouter = Backbone.Router.extend({ routes: { '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({ @@ -35,14 +37,17 @@ define(['jquery', this.lessonController.start(); // this.menuController.initMenu(); - goatRouter.on('route:attackRoute', function(scr,menu,stage) { - this.lessonController.loadLesson(scr,menu,stage); + goatRouter.on('route:attackRoute', function(scr,menu,stage,num) { + this.lessonController.loadLesson(scr,menu,stage,num); this.menuController.updateMenu(scr,menu); //update menu }); goatRouter.on('route:welcomeRoute', function() { this.lessonController.loadWelcome(); }); + goatRouter.on("route", function(route, params) { + console.log("Got a route event: " + route + ", params: " + params); + }); Backbone.history.start(); this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu) @@ -57,4 +62,4 @@ define(['jquery', return GoatAppRouter; -}); \ No newline at end of file +}); diff --git a/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js b/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js index 94d5176bc..d51bf5946 100644 --- a/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js +++ b/webgoat-container/src/main/webapp/js/goatApp/view/LessonContentView.js @@ -1,59 +1,64 @@ //LessonContentView define(['jquery', - 'underscore', - 'backbone', - 'libs/jquery.form'], - function( - $, - _, - Backbone, - JQueryForm) { - return Backbone.View.extend({ - el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM + 'underscore', + 'backbone', + 'libs/jquery.form'], + function( + $, + _, + Backbone, + JQueryForm) { + return Backbone.View.extend({ + el:'#lesson-content-wrapper', //TODO << get this fixed up in DOM - initialize: function(options) { - options = options || {}; - }, + initialize: function(options) { + options = options || {}; + }, - render: function() { - this.$el.html(this.model.get('content')); - this.makeFormsAjax(); - this.ajaxifyAttackHref(); - $(window).scrollTop(0); //work-around til we get the scroll down sorted out - }, - - //TODO: reimplement this in custom fashion maybe? - makeFormsAjax: function () { - var options = { - success:this.reLoadView.bind(this), - url:'attack?Screen=' + this.model.get('screenParam') + '&menu=' + this.model.get('menuParam'), - type:'GET' - // $.ajax options can be used here too, for example: - //timeout: 3000 - }; - //hook forms //TODO: clarify form selectors later - $("form").ajaxForm(options); + render: function() { + this.$el.html(this.model.get('content')); + this.makeFormsAjax(); + this.ajaxifyAttackHref(); + $(window).scrollTop(0); //work-around til we get the scroll down sorted out + }, + + //TODO: reimplement this in custom fashion maybe? + makeFormsAjax: function () { + var options = { + success:this.reLoadView.bind(this), + url: this.model.urlRoot, + type:'GET' + // $.ajax options can be used here too, for example: + //timeout: 3000 + }; + //hook forms //TODO: clarify form selectors later + $("form").ajaxForm(options); }, ajaxifyAttackHref: function() { // rewrite any links with hrefs point to relative attack URLs - var self = this; - $.each($('a[href^="attack?"]'),function(i,el) { - var url = $(el).attr('href'); - $(el).unbind('click').attr('href','#').attr('link',url); - //TODO pull currentMenuId - $(el).click(function() { - event.preventDefault(); - var _url = $(el).attr('link'); - $.get(_url, {success:self.reloadView.bind(self)}); - }); - }); - }, + var self = this; + // The current LessonAdapter#getLink() generates a hash-mark link. It will not match the mask below. + // Besides, the new MVC code registers an event handler that will reload the lesson according to the route. + $.each($('a[href^="attack?"]'),function(i,el) { + var url = $(el).attr('href'); + $(el).unbind('click').attr('href','#').attr('link',url); + //TODO pull currentMenuId + $(el).click(function(event) { + 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) { - this.model.setContent(content); - this.render(); + this.model.setContent(content); + this.render(); } - }); + }); - -}); \ No newline at end of file + +}); diff --git a/webgoat-container/src/main/webapp/main.jsp b/webgoat-container/src/main/webapp/main.jsp index eeb9c9e06..7d21b84ca 100644 --- a/webgoat-container/src/main/webapp/main.jsp +++ b/webgoat-container/src/main/webapp/main.jsp @@ -104,7 +104,7 @@ if (stages != null) for (int i = 0; i < stages.length; i++) { %> - <%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%>Stage <%=i + 1%>: <%=stages[i]%> + <%=(rla.isStageComplete(webSession, stages[i]) ? lessonComplete : "")%>">Stage <%=i + 1%>: <%=stages[i]%> <% }