First attempt to remove JSP and move to Thymeleaf and update to Spring Boot. The Thymeleaf templates can be loaded as snippets which makes it more easy to move away from ECS and create normal HTML pages for a lesson.

This commit is contained in:
Nanne Baars
2016-04-05 08:19:50 +02:00
parent 7f91671c8f
commit ecc8cb391b
186 changed files with 14439 additions and 13920 deletions

View File

@ -0,0 +1,201 @@
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',
'goatApp/model/LessonProgressModel',
'goatApp/view/LessonProgressView'
],
function($,
_,
Backbone,
LessonContentModel,
LessonContentView,
PlanView,
SourceView,
SolutionView,
HintView,
HelpControlsView,
CookieView,
ParamView,
ParamModel,
GoatUtils,
UserAndInfoView,
MenuButtonView,
LessonInfoModel,
TitleView,
LessonProgressModel,
LessonProgressView
) {
'use strict'
var Controller = function(options) {
this.lessonContent = new LessonContentModel();
this.lessonProgressModel = new LessonProgressModel();
this.lessonProgressView = new LessonProgressView(this.lessonProgressModel);
this.lessonView = options.lessonView;
_.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.loadLesson = function(scr,menu,stage,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.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.titleView.render(this.lessonInfoModel.get('lessonTitle'));
};
this.onContentLoaded = function(loadHelps) {
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('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');
this.lessonProgressModel.completed();
};
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;
}
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('<pre>' + this.sourceView.model.get('content') + '</pre>');
break;
}
$(showId).show();
GoatUtils.scrollToHelp()
}
};
this.onShowHints = function() {
this.lessonHintView.render();
};
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;
});

View File

@ -0,0 +1,33 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/view/MenuView'
],
function($,
_,
Backbone,
MenuView) {
Controller = function(options){
_.extend(Controller.prototype,Backbone.Events);
options = options || {};
this.menuView = options.menuView;
this.titleView = options.titleView;
// this.initMenu = function() {
// this.listenTo(this.menuView,'lesson:click',this.renderTitle);
// }
this.updateMenu = function(){
this.menuView.updateMenu();
},
//TODO: move title rendering into lessonContent/View pipeline once data can support it
this.renderTitle = function(title) {
this.titleView.render(title);
}
};
return Controller;
});

View File

@ -0,0 +1,12 @@
define(['jquery','underscore','backbone','goatApp/view/GoatRouter'],
function($,_,Backbone,Router){
'use strict'
//var goatRouter = new Router();
return {
initApp: function() {
//TODO: add query/ability to load from where they left off
var goatRouter = new Router();
goatRouter.init();
}
};
});

View File

@ -0,0 +1,13 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/CookieModel'],
function($,
_,
Backbone,
CookieModel) {
return Backbone.Collection.extend({
url:'service/cookie.mvc',
model:CookieModel
});
});

View File

@ -0,0 +1,9 @@
define(['jquery',
'underscore',
'backbone'],
function($,
_,
Backbone) {
return Backbone.Model.extend({
});
});

View File

@ -0,0 +1,25 @@
define(['jquery',
'underscore',
'backbone'],
function($,_,Backbone) {
return Backbone.Model.extend({
//url:'service/lessonplan.mvc',
fetch: function (options) {
options = options || {};
return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options));
},
loadData: function() {
var self=this;
this.fetch().then(function(data) {
self.setContent(data);
});
},
setContent: function(content) {
this.set('content',content);
this.checkNullModel();
this.trigger('loaded');
}
});
});

View File

@ -0,0 +1,32 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HintModel'],
function($,
_,
Backbone,
HintModel) {
return Backbone.Collection.extend({
model: HintModel,
url:'service/hint.mvc',
initialize: function () {
var self = this;
this.fetch().then(function (data) {
this.models = data;
self.onDataLoaded();
});
},
onDataLoaded:function() {
this.trigger('loaded');
},
checkNullModel:function() {
if (this.models[0].indexOf('There are no hints defined.') > -1) {
this.reset([]);
//return this.models;
}
}
});
});

View File

@ -0,0 +1,10 @@
define(['jquery',
'underscore',
'backbone'],
function($,
_,
Backbone,
HTMLContentModel) {
return Backbone.Model.extend({
});
});

View File

@ -0,0 +1,54 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HTMLContentModel'],
function($,
_,
Backbone,
HTMLContentModel){
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);
},
fetch: function (options) {
options = options || {};
return Backbone.Model.prototype.fetch.call(this, _.extend({ dataType: "html"}, options));
}
});
});

View File

@ -0,0 +1,20 @@
define(['jquery',
'underscore',
'backbone'],
function($,
_,
Backbone){
return Backbone.Model.extend({
url:'service/lessoninfo.mvc',
initialize: function (options) {
this.fetch().then(this.infoLoaded.bind(this));
},
infoLoaded: function(data) {
this.trigger('info:loaded',this,data);
}
});
});

View File

@ -0,0 +1,17 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HTMLContentModel'],
function($,
_,
Backbone,
HTMLContentModel) {
return HTMLContentModel.extend({
url:'service/lessonplan.mvc',
checkNullModel: function() {
if (this.get('content').indexOf('Could not find lesson plan for') > -1) {
this.set('content',null);
}
}
});
});

View File

@ -0,0 +1,29 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/MenuModel'],
function($,_,Backbone,MenuModel) {
return Backbone.Collection.extend({
model: MenuModel,
url:'service/lessonmenu.mvc',
initialize: function () {
var self = this;
this.fetch();
},
onDataLoaded: function() {
this.trigger('menuData:loaded');
},
fetch: function() {
var self=this;
Backbone.Collection.prototype.fetch.apply(this,arguments).then(
function(data) {
this.models = data;
self.onDataLoaded();
}
);
}
});
});

View File

@ -0,0 +1,32 @@
//var goatApp = goatApp || {};
define(['jquery','underscore','backbone'], function($,_,Backbone) {
var menuData = Backbone.Model.extend({
urlRoot:'/webgoat/service/lessonmenu.mvc',
defaults: {
items:null,
selectedItem:null
},
initialize: function () {
var self = this;
this.fetch().then(function(menuItems){
menuItems = goatUtils.enhanceMenuData(menuItems,this.selectedItem);
this.setDataItems(menuItems);
});
},
update: function() {
var self = this;
this.fetch().then(function(menuItems) {
menuItems = goatUtils.enhanceMenuData(menuItems,this.selectedItem);
self.setDataItems(menuItems);
});
},
setDataItems: function (data) {
this.items = data;
}
});
});

View File

@ -0,0 +1,10 @@
define(['jquery',
'underscore',
'backbone'],
function($,_,Backbone) {
return Backbone.Model.extend({
});
});

View File

@ -0,0 +1,12 @@
define([
'backbone'],
function(
Backbone) {
return Backbone.Model.extend({
initialize: function(options) {
for (var key in options) {
this.set(key, options.key);
}
}
});
});

View File

@ -0,0 +1,18 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HTMLContentModel'],
function($,
_,
Backbone,
HTMLContentModel) {
return HTMLContentModel.extend({
url:'service/solution.mvc',
checkNullModel: function() {
if (this.get('content').indexOf('Could not find the solution file or solution file does not exist') === 0) {
this.set('content',null);
}
}
});
});

View File

@ -0,0 +1,19 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HTMLContentModel'],
function($,
_,
Backbone,
HTMLContentModel) {
return HTMLContentModel.extend({
url:'service/source.mvc',
checkNullModel: function () {
//TODO: move this function into HTMLContentModel and make the string a property of this 'child' model
if (this.get('content').indexOf("Could not find the source file or") > -1) {
this.set('content',null);
}
}
});
});

View File

@ -0,0 +1,90 @@
define(['jquery',
'underscore',
'backbone',
'libs/jquery.form'
],
function($,
_,
Backbone,
JQueryForm) {
var goatUtils = {
makeId: function(lessonName) {
//var id =
return lessonName.replace(/\s|\(|\)|\!|\:|\;|\@|\#|\$|\%|\^|\&|\*/g, '');
},
addMenuClasses: function(arr) {
for (var i = 0; i < arr.length; i++) {
var menuItem = arr[i];
//console.log(menuItem);
if (menuItem.type && menuItem.type === 'CATEGORY') {
menuItem.class = 'fa-angle-right pull-right';
}
}
return arr;
},
// debugFormSubmission: false,
// pre-submit callback
showRequest: function(formData, jqForm, options) {
if (GoatUtils.debugFormSubmission) {
// formData is an array; here we use $.param to convert it to a string to display it
// but the form plugin does this for you automatically when it submits the data
var queryString = $.param(formData);
// jqForm is a jQuery object encapsulating the form element. To access the
// DOM element for the form do this:
// var formElement = jqForm[0];
alert('About to submit: \n\n' + queryString);
}
// here we could return false to prevent the form from being submitted;
// returning anything other than false will allow the form submit to continue
return true;
},
displayButton: function(id, show) {
if ($('#' + id)) {
if (show) {
$('#' + id).show();
} else {
}
}
},
showLessonCookiesAndParams: function() {
$.get(goatConstants.cookieService, {}, function(reply) {
$("#lesson_cookies").html(reply);
}, "html");
},
scrollToHelp: function() {
$('#leftside-navigation').height($('#main-content').height() + 15)
var target = $('#lesson-helps-wrapper');
this.scrollEasy(target);
},
scrollToTop: function() {
$('.lessonHelp').hide();
var target = $('#container');
this.scrollEasy(target);
},
scrollEasy: function(target) {
$('html,body').animate({
scrollTop: target.offset().top
}, 1000);
},
highlightCurrentLessonMenu: function(id) {
//TODO: move selectors in first two lines into goatConstants
$('ul li.selected').removeClass(goatConstants.selectedMenuClass)
$('ul li.selected a.selected').removeClass(goatConstants.selectedMenuClass)
$('#' + id).addClass(goatConstants.selectedMenuClass);
$('#' + id).parent().addClass(goatConstants.selectedMenuClass);
},
};
return goatUtils;
});

View File

@ -0,0 +1,39 @@
//goatConstants
var goatConstants = {
getClasses: function() {
return {
categoryClass:'fa-angle-right pull-right',
lessonCompleteClass:'glyphicon glyphicon-check lessonComplete',
selectedMenuClass:'selected',
keepOpenClass:'keepOpen'
};
},
getServices: function() {
return {
lessonService: 'service/lessonmenu.mvc',
cookieService: 'service/cookie.mvc', //cookies_widget.mvc
hintService: 'service/hint.mvc',
sourceService: 'service/source.mvc',
solutionService: 'service/solution.mvc',
lessonPlanService: 'service/lessonplan.mvc',
menuService: 'service/lessonmenu.mvc',
lessonTitleService: 'service/lessontitle.mvc',
restartLessonService: 'service/restartlesson.mvc'
}
},
getMessages: function() {
return {
notFound: 'Could not find',
noHints: 'There are no hints defined.',
noSourcePulled: 'No source was retrieved for this lesson'
}
},
getDOMContainers:function() {
return {
lessonMenu: '#menu-container'
}
}
};

View File

@ -0,0 +1,34 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/CookieCollection'],
function($,
_,
Backbone,
CookieCollection) {
return Backbone.View.extend({
el:'#cookies-view',
initialize: function() {
this.collection = new CookieCollection();
this.listenTo(this.collection,'reset',this.render)
this.collection.fetch({reset:true});
},
render: function() {
this.$el.html('')
var cookieTable;
this.collection.each(function(model) {
cookieTable = $('<table>',{'class':'cookie-table table-striped table-nonfluid'});
_.each(model.keys(), function(attribute) {
var newRow = $('<tr>');
newRow.append($('<th>',{text:_.escape(attribute)}))
newRow.append($('<td>',{text:_.escape(model.get(attribute))}));
cookieTable.append(newRow);
});
});
this.$el.append($('<h4>',{text:'Cookie/s'}));
this.$el.append(cookieTable);
}
});
});

View File

@ -0,0 +1,67 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/controller/LessonController',
'goatApp/controller/MenuController',
'goatApp/view/LessonContentView',
'goatApp/view/MenuView',
'goatApp/view/DeveloperControlsView'
], function ($,
_,
Backbone,
LessonController,
MenuController,
LessonContentView,
MenuView,
DeveloperControlsView) {
var lessonView = new LessonContentView();
var menuView = new MenuView();
var developerControlsView = new DeveloperControlsView();
var GoatAppRouter = Backbone.Router.extend({
routes: {
'welcome':'welcomeRoute',
'attack/:scr/:menu':'attackRoute',
'attack/:scr/:menu/:stage':'attackRoute',
'attack/:scr/:menu/*stage/:num':'attackRoute',
},
lessonController: new LessonController({
lessonView: lessonView
}),
menuController: new MenuController({
menuView: menuView
}),
init:function() {
goatRouter = new GoatAppRouter();
this.lessonController.start();
// this.menuController.initMenu();
goatRouter.on('route:attackRoute', function(scr,menu,stage,num) {
this.lessonController.loadLesson(scr,menu,stage,num);
this.menuController.updateMenu(scr,menu);
});
goatRouter.on('route:welcomeRoute', function() {
this.lessonController.loadWelcome();
});
goatRouter.on("route", function(route, params) {
});
Backbone.history.start();
this.listenTo(this.lessonController, 'menu:reload',this.reloadMenu)
},
reloadMenu: function (curLesson) {
this.menuController.updateMenu();
}
});
return GoatAppRouter;
});

View File

@ -0,0 +1,62 @@
define(['jquery',
'underscore',
'backbone'],
function($,_,Backbone) {
return Backbone.View.extend({
el:'#help-controls', //Check this
initialize: function (options) {
if (!options) {
return;
}
this.hasPlan = options.hasPlan;
this.hasSolution = options.hasSolution;
this.hasSource = options.hasSource;
this.hasHints = options.hasHints;
},
render:function(title) {
//this.$el.html();
// if still showing, hide
$('#show-source-button').hide();
$('#show-solution-button').hide();
$('#show-plan-button').hide();
$('#show-hints-button').hide();
if (this.hasSource) {
this.$el.find('#show-source-button').unbind().on('click',_.bind(this.showSource,this)).show();
}
if (this.hasSolution) {
this.$el.find('#show-solution-button').unbind().on('click',_.bind(this.showSolution,this)).show();
}
if (this.hasPlan) {
this.$el.find('#show-plan-button').unbind().on('click',_.bind(this.showPlan,this)).show();
}
if (this.hasHints) {
this.$el.find('#show-hints-button').unbind().on('click',_.bind(this.showHints,this)).show();
}
this.$el.find('#restart-lesson-button').unbind().on('click',_.bind(this.restartLesson,this)).show();
//this.$el.append(this.helpButtons.restartLesson);
},
showSource: function() {
this.trigger('source:show','source');
},
showSolution: function() {
this.trigger('solution:show','solution');
},
showPlan: function() {
this.trigger('plan:show','plan');
},
showHints: function() {
this.trigger('hints:show','hints');
},
restartLesson: function() {
this.trigger('lesson:restart');
}
});
});

View File

@ -0,0 +1,23 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/SourceModel'],
function($,
_,
Backbone,
SourceModel) {
return Backbone.View.extend({
el:'#lessonHelpWrapper .lessonHelp.lessonPlan', //Check this
initialize: function() {
this.model = new SourceModel();
this.listenTo(this.model,'loaded',this.onModelLoaded);
this.model.loadData();
},
render:function(title) {
},
onModelLoaded: function() {
this.trigger(this.loadedMessage,this.helpElement);
}
});
});

View File

@ -0,0 +1,77 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/HintCollection'],
function($,
_,
Backbone,
HintCollection) {
return Backbone.View.extend({
el:'#lesson-hint-container',
events: {
"click #show-next-hint": "showNextHint",
"click #show-prev-hint": "showPrevHint"
},
initialize: function() {
this.curHint=0;
this.collection = new HintCollection();
this.listenTo(this.collection,'loaded',this.onModelLoaded);
this.hideHints();
},
render:function() {
if (this.$el.is(':visible')) {
this.$el.hide(350);
} else {
this.$el.show(350);
}
if (this.collection.length > 0) {
this.hideShowPrevNextButtons();
}
this.displayHint(this.curHint);
},
onModelLoaded: function() {
this.trigger('hints:loaded',{'helpElement':'hints','value':true})
},
hideHints: function() {
if (this.$el.is(':visible')) {
this.$el.hide(350);
}
},
showNextHint: function() {
this.curHint = (this.curHint < this.collection.length -1) ? this.curHint+1 : this.curHint;
this.hideShowPrevNextButtons();
this.displayHint(this.curHint);
},
showPrevHint: function() {
this.curHint = (this.curHint > 0) ? this.curHint-1 : this.curHint;
this.hideShowPrevNextButtons();
this.displayHint(this.curHint);
},
displayHint: function(curHint) {
this.$el.find('#lesson-hint-content').html(this.collection.models[curHint].get('hint'));
},
hideShowPrevNextButtons: function() {
if (this.curHint === this.collection.length -1) {
this.$el.find('#show-next-hint').css('visibility','hidden');
} else {
this.$el.find('#show-next-hint').css('visibility','visible');
}
if (this.curHint === 0) {
this.$el.find('#show-prev-hint').css('visibility','hidden');
} else {
this.$el.find('#show-prev-hint').css('visibility','visible');
}
}
});
});

View File

@ -0,0 +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
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: 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;
// 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();
}
});
});

View File

@ -0,0 +1,27 @@
//
//UserAndInfoView
define(['jquery',
'underscore',
'backbone'],
function($,
_,
Backbone) {
return Backbone.View.extend({
el:'#toggle-menu', //Check this,
initialize: function() {
this.$el.on('click',this.toggleMenu);
},
toggleMenu: function() {
//left
if (!$('.sidebarRight').hasClass('.sidebar-toggle-right')) {
$('.sidebarRight').removeClass('sidebar-toggle-right');
$('.main-content-wrapper').removeClass('main-content-toggle-right');
}
$('.sidebar').toggleClass('sidebar-toggle');
$('.main-content-wrapper').toggleClass('main-content-toggle-left');
//e.stopPropagation();
}
});
});

View File

@ -0,0 +1,30 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/support/GoatUtils',
'goatApp/view/MenuItemView'],
function(
$,
_,
Backbone,
GoatUtils,
MenuItemView) {
return Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.items = options.items;
},
render: function() {
var viewItems = [];
for (var i=0;i<this.items.length;i++) {
var listItem = $('<li>',{text:this.items[i].name});
//viewItems
viewItems.push(listItem);
}
return viewItems;
}
});
});

View File

@ -0,0 +1,14 @@
define(['jquery',
'underscore',
'backbone'], function($,_,Backbone) {
return Backbone.View.extend({
initialize: function(options) {
options = options || {};
}
});
});

View File

@ -0,0 +1,135 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/MenuCollection',
'goatApp/view/MenuItemView',
'goatApp/support/GoatUtils'],
function(
$,
_,
Backbone,
MenuCollection,
MenuItemView,
GoatUtils) {
return Backbone.View.extend({
el:'#menu-container',
//TODO: set template
initialize: function() {
this.collection = new MenuCollection();
this.addSpinner();
this.listenTo(this.collection,'menuData:loaded',this.render);
// this.listenTo(this,'menu:click',this.accordionMenu);
this.curLessonLinkId = '';
},
addSpinner: function() {
//<i class="fa fa-spinner fa-spin"></i>
this.$el.append($('<i>',{class:'fa fa-3x fa-spinner fa-spin'}));
},
removeSpinner: function() {
this.$el.find('i.fa-spinner').remove();
},
// rendering top level menu
render: function (){
//for now, just brute force
//TODO: refactor into sub-views/components
this.removeSpinner();
var items, catItems, stages;
items = this.collection.models; // top level items
var menuMarkup = '';
var menuUl = $('<ul>',{class:'nano-content'});
for(var i=0;i<items.length;i++) { //CATEGORY LEVEL
var catId, category, catLink, catArrow, catLinkText, lessonName, stageName;
catId = GoatUtils.makeId(items[i].get('name'));
category = $('<li>',{class:'sub-menu ng-scope'});
catLink = $('<a>',{'category':catId});
catArrow = $('<i>',{class:'fa fa-angle-right pull-right'});
catLinkText = $('<span>',{text:items[i].get('name')});
catLink.append(catArrow);
catLink.append(catLinkText);
var self = this;
catLink.click(_.bind(this.expandCategory,this,catId));
category.append(catLink);
// lesson level (first children level)
//var lessons = new MenuItemView({items:items[i].get('children')}).render();
var lessons=items[i].get('children');
if (lessons) {
var categoryLessonList = $('<ul>',{class:'slideDown lessonsAndStages',id:catId}); //keepOpen
for (var j=0; j < lessons.length;j++) {
var lessonItem = $('<li>',{class:'lesson'});
var lessonName = lessons[j].name;
var lessonId = catId + '-' + GoatUtils.makeId(lessonName);
if (this.curLessonLinkId === lessonId) {
lessonItem.addClass('selected');
}
var lessonLink = $('<a>',{href:lessons[j].link,text:lessonName,id:lessonId});
lessonLink.click(_.bind(this.onLessonClick,this,lessonId));
lessonItem.append(lessonLink);
//check for lab/stages
categoryLessonList.append(lessonItem);
if (lessons[j].complete) {
lessonItem.append($('<span>',{class:'glyphicon glyphicon-check lesson-complete'}));
}
var stages = lessons[j].children;
for (k=0; k < stages.length; k++) {
var stageItem = $('<li>',{class:'stage'});
var stageName = stages[k].name;
var stageId = lessonId + '-stage' + k;
if (this.curLessonLinkId === stageId) {
stageItem.addClass('selected');
}
var stageLink = $('<a>',{href:stages[k].link,text:stageName,id:stageId});
stageLink.click(_.bind(this.onLessonClick,this,stageId));
stageItem.append(stageLink);
categoryLessonList.append(stageItem);
if (stages[k].complete) {
stageItem.append($('<span>',{class:'glyphicon glyphicon-check lesson-complete'}));
}
}
}
category.append(categoryLessonList);
}
menuUl.append(category);
}
this.$el.html(menuUl);
//if we need to keep a menu open
if (this.openMenu) {
$('#'+this.openMenu).show();
}
},
updateMenu: function() {
//for now ...
this.collection.fetch();
},
onLessonClick: function (elementId) {
$('#'+this.curLessonLinkId).removeClass('selected');
//update
$('#'+elementId).addClass('selected');
this.curLessonLinkId = elementId;
},
expandCategory: function (id) {
if (id) {
this.accordionMenu(id);
}
},
accordionMenu: function(id) {
if (this.openMenu !== id) {
this.$el.find('#' + this.openMenu).slideUp(200);
this.$el.find('#' + id).slideDown(300);
this.openMenu = id;
} else { //it's open
this.$el.find('#' + id).slideUp(300).attr('isOpen', 0);
this.openMenu = null;
return;
}
}
});
});

View File

@ -0,0 +1,36 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/ParamModel'],
function($,
_,
Backbone,
ParamModel) {
return Backbone.View.extend({
el:'#params-view',
initialize: function(options) {
this.model = options.model;
if (options.model) {
this.listenTo(this.model,'change',this.render);
}
this.render();
},
render: function() {
this.$el.html('');
var paramsTable = $('<table>',{'class':'param-table table-striped table-nonfluid'});
var self = this;
_.each(this.model.keys(), function(attribute) {
var attributeLabel = attribute.replace(/Param/,'');
var newRow = $('<tr>');
newRow.append($('<th>',{text:_.escape(attributeLabel)}))
newRow.append($('<td>',{text:_.escape(self.model.get(attribute))}));
paramsTable.append(newRow);
});
this.$el.append($('<h4>',{text:'Parameters'}));
this.$el.append(paramsTable);
}
});
});

View File

@ -0,0 +1,25 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/LessonPlanModel'],
function($,
_,
Backbone,
LessonPlanModel) {
return Backbone.View.extend({
el:'#lessonHelpWrapper .lessonHelp.lessonPlan', //Check this
initialize: function() {
this.model = new LessonPlanModel();
this.listenTo(this.model,'loaded',this.onModelLoaded);
this.model.loadData();
},
render:function(title) {
},
onModelLoaded: function() {
this.trigger('plan:loaded',{'helpElement':'plan','value':true});
}
});
});

View File

@ -0,0 +1,22 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/SolutionModel'],
//TODO: create a base 'HelpView class'
function($,_,Backbone,SolutionModel) {
return Backbone.View.extend({
el:'#lessonHelpWrapper .lessonHelp.lessonSolution', //Check this
initialize: function() {
this.model = new SolutionModel();
this.listenTo(this.model,'loaded',this.onModelLoaded);
this.model.loadData();
//TODO: handle error cases
},
render:function(title) {
},
onModelLoaded: function() {
this.trigger('solution:loaded',{'helpElement':'solution','value':true});
}
});
});

View File

@ -0,0 +1,21 @@
define(['jquery',
'underscore',
'backbone',
'goatApp/model/SourceModel',
'goatApp/view/HelpView'],
function($,
_,
Backbone,
SourceModel,
HelpView) {
return HelpView.extend({
helpElement:{'helpElement':'source','value':true},
loadedMessage:'source:loaded',
el:'#lessonHelpWrapper .lessonHelp.lessonPlan', //Check this
initialize: function() {
this.model = new SourceModel();
this.listenTo(this.model,'loaded',this.onModelLoaded);
this.model.loadData();
}
});
});

View File

@ -0,0 +1,13 @@
define(['jquery',
'underscore',
'backbone'],
function($,_,Backbone) {
return Backbone.View.extend({
el:'#header #lesson-title-wrapper',
render:function(title) {
var lessonTitleEl = $('<h1>',{id:'lesson-title',text:title});
this.$el.html(lessonTitleEl);
}
});
});

View File

@ -0,0 +1,45 @@
//UserAndInfoView
define(['jquery',
'underscore',
'backbone'],
function($,
_,
Backbone) {
return Backbone.View.extend({
el:'#user-and-info-nav', //Check this,
events: {
'click #about-button': 'showAboutModal',
'click #user-menu': 'showUserMenu'
},
initialize: function() {
},
render:function(title) {
},
showUserMenu: function() {
var menu = this.$el.find('.dropdown-menu');
if (menu.is(':visible')) {
menu.hide(200);
} else {
menu.show(400);
/*menu.on('mouseout', function() {
$('#user-and-info-nav .dropdown-menu').hide(200);
});*/
}
},
showAboutModal: function() {
$('#about-modal').show(400);
$('#about-modal div.modal-header button.close, #about-modal div.modal-footer button').unbind('click').on('click', function() {
$('#about-modal').hide(200);
});
}
});
});