Index: lams_learning/web/includes/javascript/main.js =================================================================== diff -u -rca915c79b346338ff6e0e6e311635352056154e0 -r8634f7029afb87182dfccccd53fca4b82df4a04c --- lams_learning/web/includes/javascript/main.js (.../main.js) (revision ca915c79b346338ff6e0e6e311635352056154e0) +++ lams_learning/web/includes/javascript/main.js (.../main.js) (revision 8634f7029afb87182dfccccd53fca4b82df4a04c) @@ -148,11 +148,12 @@ // white var COLOR_GATE_TEXT = "rgb(255,255,255)"; // gray -var COLOR_OPTIONAL_BACKGROUND = "rgb(153,153,153)"; +var COLOR_COMPLEX_BACKGROUND = "rgb(153,153,153)"; var paper = null; var controlFramePadding = null; var currentActivityId = null; +var isPreview = false; var activities = []; // This should be the super class for Activities, but it's hard to accomplish in JS @@ -254,7 +255,7 @@ } }, - shapeOptionalActivityContainer : function(activity) { + shapeComplexActivityContainer : function(activity) { var addDecoration = activity.addDecoration; activity.addDecoration = function(act) { // run previous addDecoration(), for example defined in Attempted Activity @@ -266,7 +267,7 @@ var square = act.paper.path("M" + (act.middle - 13) + " " + act.y + PATH_BIG_SQUARE); square.attr({ 'opacity' : 0, - 'fill' : COLOR_OPTIONAL_BACKGROUND, + 'fill' : COLOR_COMPLEX_BACKGROUND, 'cursor' : 'pointer' }); @@ -291,6 +292,35 @@ } }, + drawActivity : function(activity, quick, isLast) { + activities[activity.index] = activity; + activity.elements = activity.paper.set(); + // only now do the read drawing, add event handlers etc. + activity.shape = activity.paper.path(activity.path); + activity.shape.attr(ActivityUtils.getShapeAttributes(activity)); + activity.elements.push(activity.shape); + // label underneath the shape + var label = paper.text(activity.middle, 43 + 60 * (activity.index - 1) + activity.height, + activity.name); + activity.elements.push(label); + if (!isLast) { + // line between activities + var line = paper.path("M " + activity.middle + " " + (50 + 60 * (activity.index - 1) + activity.height) + + " v" + (90 - activity.height)); + activity.elements.push(line); + } + + if (!quick) { + activity.elements.forEach(function(elem) { + elem.attr('opacity', 0); + elem.animate({'opacity' : 1}, 1000, "linear"); + }); + } + + ActivityUtils.addDecoration(activity, null, quick); + ActivityUtils.addEffects(activity); + }, + // adds handlers to activity for mouse interactions // long method with simple actions @@ -350,8 +380,8 @@ } : null; - var click = activity.type == 'o' ? function() { - ActivityUtils.showOptionalContent(activity); + var click = activity.isComplex ? function() { + ActivityUtils.showComplexContent(activity); } : null; activity.shape.hover(mouseover, mouseout); @@ -374,97 +404,128 @@ // copy important properties and morph visible elements transform : function(sourceActivity, targetActivity) { + var gotCompleted = false; + if (sourceActivity.status != targetActivity.status) { + gotCompleted = targetActivity.status == 1; + sourceActivity.height = targetActivity.height; sourceActivity.path = targetActivity.path; sourceActivity.fill = targetActivity.fill; sourceActivity.stroke = targetActivity.stroke; + sourceActivity.url = targetActivity.url; sourceActivity.status = targetActivity.status; sourceActivity.statusTooltip = targetActivity.statusTooltip; sourceActivity.addDecoration = targetActivity.addDecoration; ActivityUtils.animate(sourceActivity, sourceActivity.decoration); } - if (sourceActivity.childActivities) { - var isCurrent = false; + var isCurrent = targetActivity.status == 0; + if (sourceActivity.childActivities) { $.each(sourceActivity.childActivities, function(childActivityIndex, childActivity){ var targetChildActivity = targetActivity.childActivities[childActivityIndex]; isCurrent |= targetChildActivity.status == 0; ActivityUtils.transform(childActivity, targetChildActivity); }); - if (isCurrent && sourceActivity.showChildren) { - // optional sequence just became current, show it - sourceActivity.showChildren(); + } + + if (isCurrent) { + ActivityUtils.showComplexContent(sourceActivity); + if (sourceActivity.toggleChildren) { + // complex sequence just became current, show it + sourceActivity.toggleChildren('open'); + } + } else if (gotCompleted) { + if (sourceActivity.isComplex) { + ActivityUtils.hideOtherComplexContent(); + } else if (sourceActivity.toggleChildren) { + sourceActivity.toggleChildren('close'); } } }, animate : function(activity, oldDecoration){ if (activity.shape) { - ActivityUtils.removeDecoration(oldDecoration, false); + ActivityUtils.addDecoration(activity, oldDecoration, false); activity.shape.animate(ActivityUtils.getShapeAttributes(activity), 2000, "linear", function() { - ActivityUtils.addDecoration(activity, false); if(!(activity instanceof OptionalActivity)){ ActivityUtils.addEffects(activity); } }); } }, - removeDecoration : function(decoration, quick){ - if (decoration) { - decoration.forEach(function(elem){ - elem.animate({'opacity' : 0}, quick ? 0 : 1000, "linear"); + // quick is for inital drawing, no nice effect is needed + addDecoration : function(activity, oldDecoration, quick){ + if (oldDecoration) { + oldDecoration.forEach(function(elem){ + if (activity.elements) { + activity.elements.exclude(elem); + } + elem.animate({'opacity' : 0}, quick ? 0 : 1000, "linear", function(){ + elem.remove(); + }); }); } - }, - - // quick is for inital drawing, no nice effect is needed - addDecoration : function(activity, quick){ - if (activity.decoration) { - activity.decoration.clear(); - activity.decoration = null; - } if (activity.addDecoration) { + var animation = Raphael.animation({'opacity' : 1}, quick ? 0 : 1000, "linear"); + activity.addDecoration(activity); activity.decoration.forEach(function(elem){ + if (activity.elements) { + activity.elements.push(elem); + } if (elem.decorationWraps) { // decoration element is bigger that activity shape, put it in background elem.toBack(); } else { elem.toFront(); } - elem.animate({'opacity' : 1}, quick ? 0 : 1000, "linear"); + elem.animate(animation.delay(oldDecoration ? 1000 : undefined)); }); } }, + // hide all Optional Activities, except for the given one + hideOtherComplexContent : function(currentOptionalContentId) { + $('div.optionalActivity').each(function(index, contentDiv){ + var content = $(contentDiv); + if (content.attr('id') != currentOptionalContentId) { + content.slideUp(currentOptionalContentId ? 'fast' : 'slow'); + } + }); + }, + // draw pop up with optional activities - showOptionalContent : function(activity) { - // hide glow if shown (IE) - ActivityUtils.removeHover(activity.shape); - $('div.optionalActivity').hide(); - if (!activity.optionalContent) { - var containerName = 'optionalActivityContent' + activity.y; - activity.optionalContent = $('
') - .attr('id', containerName) - .addClass('optionalActivity') - .css({ - // a little higher than activity, to cover it - 'top' : $(activity.shape.node).offset().top - 8, - 'height' : 27 * activity.childActivities.length - 1 - }) - .appendTo('#progressBarDiv'); + showComplexContent : function(activity) { + if (activity.isComplex) { + // hide glow if shown (IE) + ActivityUtils.removeHover(activity.shape); + ActivityUtils.hideOtherComplexContent + (activity.optionalContent ? activity.optionalContent.attr('id') : null); + + if (!activity.optionalContent) { + var containerName = 'optionalActivityContent' + activity.y; + activity.optionalContent = $('
') + .attr('id', containerName) + .addClass('optionalActivity') + .css({ + // a little higher than activity, to cover it + 'top' : $(activity.shape.node).offset().top - 8, + 'height' : 27 * activity.childActivities.length - 1 + }) + .appendTo('#progressBarDiv'); + + var optionalContentTable = $('').appendTo(activity.optionalContent); + + ActivityUtils.addChildActivitiesRows(activity, optionalContentTable, activity.optionalContent, false); + } - var optionalContentTable = $('
').appendTo(activity.optionalContent); - - ActivityUtils.addChildActivitiesRows(activity, optionalContentTable, activity.optionalContent, false); + activity.optionalContent.slideDown('slow'); } - - activity.optionalContent.slideDown(); }, addChildActivitiesRows : function(activity, parent, container, isNested) { @@ -493,7 +554,7 @@ childActivity.paper = Raphael(cellId, 145, 23); childActivity.shape = childActivity.paper.path(childActivity.path); childActivity.shape.attr(ActivityUtils.getShapeAttributes(childActivity)); - ActivityUtils.addDecoration(childActivity, true); + ActivityUtils.addDecoration(childActivity, null, true); var label = childActivity.paper.text(35, childActivity.y + 11, (isNested ? '- ' : '') + childActivity.name) @@ -507,20 +568,23 @@ container.slideUp(); } } else if (childActivity.childActivities){ - click = function(){ - // show/hide optional sequence content - var childCells = $('td[id^=' + cellId + 'child]', parent); - var isOpen = childCells.is(':visible'); - var containerHeightDelta = 27 * childCells.length; - childCells.toggle(); - container.height(container.height() + - (isOpen ? -containerHeightDelta : containerHeightDelta)); - } - childActivity.showChildren = function(){ - if (!($('td[id^=' + cellId + 'child]', parent).is(':visible'))){ - click(); + childActivity.toggleChildren = function(forceCommand){ + if (cell.is(':visible')) { + // show/hide optional sequence content + var childCells = $('td[id^=' + cellId + 'child]', parent); + var isOpen = childCells.is(':visible'); + if (!forceCommand || (isOpen ? forceCommand == 'close' : forceCommand == 'open')) { + var containerHeightDelta = 27 * childCells.length; + childCells.toggle(); + container.height(container.height() + + (isOpen ? -containerHeightDelta : containerHeightDelta)); + } } } + + click = function(){ + childActivity.toggleChildren(); + } } } var dblclick = function() { @@ -535,33 +599,94 @@ handleClicks(cell, click, dblclick); if (childActivity.childActivities) { - isCurrent = ActivityUtils.addChildActivitiesRows(childActivity, row, container, true); - if (isCurrent) { - childActivity.showChildren(); + isCurrent |= ActivityUtils.addChildActivitiesRows(childActivity, row, container, true); + if (isCurrent && childActivity.toggleChildren) { + childActivity.toggleChildren('open'); } } }); return isCurrent; + }, + + // replace single Branching activity with list of branch activities + expandBranch : function(branchIndex, branchActivities) { + // hide any boxes obstructing the view + ActivityUtils.hideOtherComplexContent(); + + var activityShift = branchActivities.length - 1; + var yShift = 60*activityShift; + var afterBranchActivity = null; + // move down existing activities that come after Branching + for (var activityIndex = activities.length - 1; activityIndex > branchIndex; activityIndex--) { + afterBranchActivity = activities[activityIndex]; + activities[activityIndex + activityShift] = afterBranchActivity; + afterBranchActivity.y += yShift; + afterBranchActivity.path = Raphael.transformPath(afterBranchActivity.path, 'T0,' + yShift); + afterBranchActivity.elements.forEach(function(elem){ + var y = elem.attr('y'); + var targetProperties = null; + // text, rectangles etc. have 'y', paths have 'path' + if (y) { + targetProperties = {'y' : elem.attr('y') + yShift}; + } else { + var path = elem.attr('path'); + targetProperties = {'path' : Raphael.transformPath(path, 'T0,' + yShift)}; + } + elem.animate(targetProperties, 2000, "linear"); + }); + } + + // remove Branching activity + activities[branchIndex].elements.forEach(function(elem) { + elem.animate({'opacity' : 0} , 2000, "linear" , function() { + elem.remove(); + }); + }); + + // create branch activities structure + for (var activityIndex = 0; activityIndex < branchActivities.length; activityIndex ++){ + var activityData = branchActivities[activityIndex]; + var activity = new Activity(paper, activityIndex + branchIndex, activityData.id, + activityData.type, activityData.name, + activityData.status, activityData.url, + activityData.childActivities); + activities[activityIndex + branchIndex] = activity; + if (activity.status == 0) { + currentActivityIndex = activityIndex; + } + } + + // draw branch activities + setTimeout(function(){ + for (var activityIndex = 0; activityIndex < branchActivities.length; activityIndex ++){ + ActivityUtils.drawActivity(activities[activityIndex + branchIndex], false, + !afterBranchActivity && activityIndex == branchActivities.length - 1); + } + }, 2000); } } // main activities -function Activity(paper, index, type, name, status, url, childActivitiesData) { +function Activity(paper, index, id, type, name, status, url, childActivitiesData) { this.paper = paper; + this.index = index; + this.id = id; this.type = type; this.name = name; this.status = status; this.url = url; + // Optional Activities, Optional Sequences or Branching in preview mode + this.isComplex = type == 'o' || (isPreview && type == 'b'); this.middle = 70; this.height = 60; // 20 is the first line segment and following activities take 60 px each this.y = 20 + this.height * index; // first draw the inner shape, then put back the realY for background gray square var finalY = this.y; - if (type == 'o') { + if (this.isComplex) { this.y += 5; } @@ -574,10 +699,10 @@ // special behaviour for optional activities - if (type == 'o') { + if (this.isComplex) { this.height = 70; this.y = finalY; - ActivityUtils.shapeOptionalActivityContainer(this); + ActivityUtils.shapeComplexActivityContainer(this); this.childActivities = [new OptionalActivity(name, status, url)]; var childActivities = this.childActivities; @@ -649,6 +774,7 @@ // if nothing changed, don't do any calculations if (!currentActivityId || result.currentActivityId != currentActivityId) { currentActivityId = result.currentActivityId; + isPreview = result.isPreview; if (!paper) { // create paper only the first time @@ -661,34 +787,53 @@ var currentActivityIndex = 0; - $.each(result.activities, function(activityIndex, activityData) { - var activity = new Activity(paper, activityIndex, activityData.type, activityData.name, - activityData.status, activityData.url, activityData.childActivities); + for (var activityIndex = 0; activityIndex < result.activities.length; activityIndex++) { + var activityData = result.activities[activityIndex]; + var activity = new Activity(paper, activityIndex, activityData.id, + activityData.type, activityData.name, + activityData.status, activityData.url, + activityData.childActivities); if (activity.status == 0) { currentActivityIndex = activityIndex; } var existingActivity = activities[activityIndex]; if (existingActivity) { - // we are refreshing existing bar, so animate if needed - ActivityUtils.transform(existingActivity, activity); - } else { - // we are drawing bar from the scratch - activities[activityIndex] = activity; - // only now do the read drawing, add event handlers etc. - activity.shape = paper.path(activity.path); - activity.shape.attr(ActivityUtils.getShapeAttributes(activity)); - ActivityUtils.addDecoration(activity, true); - ActivityUtils.addEffects(activity); - // label underneath the shape - paper.text(activity.middle, 43 + 60 * (activityIndex - 1) + activity.height, activity.name); - if (activityIndex < result.activities.length - 1) { - // line between activities - paper.path("M " + activity.middle + " "+ (50 + 60 * (activityIndex - 1) + activity.height) - + " v" + (90 - activity.height)); + // if in preview mode, always display all options, i.e. never expand + if (!isPreview && existingActivity.type == 'b' && existingActivity.id != activity.id) { + + var branchActivityId = activityIndex; + var afterBranchActivityId = activityIndex + 1 < activities.length + ? activities[activityIndex + 1].id : null; + var branchActivities = [activity]; + activityIndex++; + + while (activityIndex < result.activities.length) { + activityData = result.activities[activityIndex]; + var activity = new Activity(paper, activityIndex, activityData.id, + activityData.type, activityData.name, + activityData.status, activityData.url, + activityData.childActivities); + if (activity.id == afterBranchActivityId) { + activityIndex--; + break; + } else { + branchActivities.push(activity); + activityIndex++; + } + } + + paper.setSize(140, 60 * (activities.length + branchActivities.length - 1)); + ActivityUtils.expandBranch(branchActivityId, branchActivities); + } else { + // we are refreshing existing bar, so animate if needed + ActivityUtils.transform(existingActivity, activity); } + } else { + ActivityUtils.drawActivity(activity, true, + activityIndex == result.activities.length - 1); } - }); + } // draw support activities if they exist if (result.support && !supportSeparatorRow.is(':visible')) { @@ -702,7 +847,7 @@ activityData.name, activityData.status, activityData.url); activity.shape = supportPaper.path(activity.path); activity.shape.attr(ActivityUtils.getShapeAttributes(activity)); - ActivityUtils.addDecoration(activity, true); + ActivityUtils.addDecoration(activity, null, true); ActivityUtils.addEffects(activity); supportPaper.text(90, 24 + 33 * activityIndex, activity.name); });