Index: lams_central/src/java/org/lamsfoundation/lams/authoring/web/AuthoringAction.java =================================================================== RCS file: /usr/local/cvsroot/lams_central/src/java/org/lamsfoundation/lams/authoring/web/AuthoringAction.java,v diff -u -r1.49 -r1.50 --- lams_central/src/java/org/lamsfoundation/lams/authoring/web/AuthoringAction.java 20 May 2014 09:40:46 -0000 1.49 +++ lams_central/src/java/org/lamsfoundation/lams/authoring/web/AuthoringAction.java 2 Jun 2014 07:27:16 -0000 1.50 @@ -84,7 +84,8 @@ * @author Manpreet Minhas * * @struts.action path = "/authoring/author" parameter = "method" validate = "false" - * @struts:action-forward name="openAutoring" path="/author2.jsp" + * @struts:action-forward name="openAutoring" path="/authoring/authoring.jsp" + * @struts:action-forward name="svgGenerator" path="/authoring/svgGenerator.jsp" */ public class AuthoringAction extends LamsDispatchAction { @@ -123,7 +124,30 @@ request.setAttribute("access", gson.toJson(accessList)); return mapping.findForward("openAutoring"); } + + public ActionForward generateSVG(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + request.setAttribute("tools", getLearningDesignService().getToolDTOs(true, request.getRemoteUser())); + + return mapping.findForward("svgGenerator"); + } + + public ActionForward exportSequence(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + String type = request.getParameter("type"); + + if ("image".equalsIgnoreCase(type)){ + String image = request.getParameter("image"); + String name = request.getParameter("name"); + name = FileUtil.encodeFilenameForDownload(request, name); + response.setContentType("application/x-download"); + response.setHeader("Content-Disposition", "attachment;filename=" + name); + } + + return null; + } + /** * Output the supplied WDDX packet. If the request parameter USE_JSP_OUTPUT is set, then it sets the session * attribute "parameterName" to the wddx packet string. If USE_JSP_OUTPUT is not set, then the packet is written out Fisheye: Tag 1.38 refers to a dead (removed) revision in file `lams_central/web/author2.jsp'. Fisheye: No comparison available. Pass `N' to diff? Index: lams_central/web/authoring/authoring.jsp =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/authoring/authoring.jsp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_central/web/authoring/authoring.jsp 2 Jun 2014 07:27:16 -0000 1.1 @@ -0,0 +1,761 @@ +<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8"%> + +<%@ taglib uri="tags-lams" prefix="lams"%> +<%@ taglib uri="tags-core" prefix="c"%> +<%@ taglib uri="tags-fmt" prefix="fmt"%> + + + + + Flashless Authoring + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ New +
+
+
+
+ Open +
+
 
+
+
    +
  • Import
  • +
+
+
+
+
+ Save +
+
 
+
+
    +
  • Save as
  • +
  • Export +
      +
    • Sequence LAMS
    • +
    • Sequence IMS
    • +
    • Image PNG
    • +
    • Image SVG
    • +
    +
  • +
+
+
+ Copy +
+
+ Paste +
+
+ Transition +
+
+
+
+ Optional +
+
 
+
+
    +
  • Activity
  • +
  • Support
  • +
+
+
+
+
+ Flow +
+
 
+
+
    +
  • Gate
  • +
  • Branch
  • +
+
+
+ Group +
+
+
+
+ Annotate +
+
 
+
+
    +
  • Label
  • +
  • Region
  • +
+
+
+ Arrange +
+
+ Preview +
+ +
+ + + + + + +
+
+ +
+ + + +
+
+
+
+
+
+
+ Untitled + + +
+
+
Description: + +
+
+ +
+
+
+
+
+ + + + +
+ + + + + + + + +
+
+
+
+ + +
+
+
Recently used sequences
+
+ <%-- This will be moved to dialog's button pane --%> +
+ Title: +
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ Mappings +
+
+
+
+
+ + + + +
+ + + + + + + + + +
+ Title: + + +
+ Default? + + +
+
+ +
+ + + + + + + + + +
+ Title: + + +
+ Grouping: + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Title: + + +
+ Grouping type: + + +
+ Number of groups: + + + +
+ Number of learners: + + + +
+ Equal group sizes? + + +
+ View learners before selection? + + +
+
Name Groups
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Title: + + +
+ Description: + + +
+ Type: + + +
+ Input (Tool): + + +
+ Delay: + + days + + hours + + minutes +
+ Since learner finished previous activity? + + +
+
Create conditions
+
+
Map gate conditions
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Title: + + +
+ Branching type: + + +
+ Grouping: + + +
+ Input (Tool): + + +
+
Create conditions
+
+
Match conditions to branches
+
+
Match Groups to Branches
+
+ Min sequences: + + +
+ Max sequences: + + +
+
+ + +
+ + + + + + + + + +
+ Title: + + +
+ Grouping: + + +
+
+ + +
+ + + + + + + + + + + + + +
+ Title: + + +
+ Min activities: + + +
+ Max activities: + + +
+
+ + +
+ + + + + + + + + +
+ Title: + + +
+ Color: + + +
+
+ + +
+ + + + + +
+ Title: + + +
+
+ + + +
+ + +
+ +
+ From: To: +
+ Add +
+
+
Name:
+
    +
    + + + + + +
    NameCondition
    +
    + + + +
    + Click here to download the image. +
    + + + +
    + Please wait for the download.
    Close the dialog when the download is finished.
    + +
    + +
    \ No newline at end of file Index: lams_central/web/authoring/svgGenerator.jsp =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/authoring/svgGenerator.jsp,v diff -u --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lams_central/web/authoring/svgGenerator.jsp 2 Jun 2014 07:27:16 -0000 1.1 @@ -0,0 +1,64 @@ +<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8"%> + +<%@ taglib uri="tags-lams" prefix="lams"%> +<%@ taglib uri="tags-core" prefix="c"%> +<%@ taglib uri="tags-fmt" prefix="fmt"%> + + + + + SVG Generator + + + + + + + + + + +
    + +
    + + + +
    +
    +
    +
    + +
    + +
    \ No newline at end of file Index: lams_central/web/css/authoring.css =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/css/authoring.css,v diff -u -r1.26 -r1.27 --- lams_central/web/css/authoring.css 26 May 2014 10:11:54 -0000 1.26 +++ lams_central/web/css/authoring.css 2 Jun 2014 07:27:16 -0000 1.27 @@ -66,6 +66,25 @@ } +div.exportDialog { + text-align: center; + font-size: 17px; + font-weight: bold; +} + +div.exportDialog a { + text-decoration: none; +} + +div.exportDialog iframe { + display: none; +} + +div#exportLDDialog { + cursor: pointer; +} + + div#ldStoreDialog > table { width: 100%; height: 682px; @@ -259,8 +278,10 @@ font-size: 11px; } -div#toolbar .split-ui-button + ul { +div#toolbar .split-ui-button + ul, div#toolbar .split-ui-button + ul ul { font-size: 11px; + font-weight: bold; + list-style: none; color: #2E6E9E; z-index: 1100; } @@ -274,6 +295,10 @@ background-color: #dfeffc; } +div#toolbar .split-ui-button + ul ul { + white-space: nowrap; +} + table#authoringTable { table-layout: fixed; width: 100%; Index: lams_central/web/includes/javascript/authoring/authoringActivity.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringActivity.js,v diff -u -r1.33 -r1.34 --- lams_central/web/includes/javascript/authoring/authoringActivity.js 27 May 2014 11:54:41 -0000 1.33 +++ lams_central/web/includes/javascript/authoring/authoringActivity.js 2 Jun 2014 07:27:16 -0000 1.34 @@ -1,84 +1,94 @@ /** - * This file contains methods for Activity manipulation on canvas. + * This file contains methods for Activity definition and manipulation on canvas. */ -var ActivityLib = { +/** + * Stores different Activity types structures. + */ +var ActivityDefs = { /** - * Constructor for a Transition + * Either branching (start) or converge (end) point. */ - Transition : function(id, uiid, fromActivity, toActivity, title) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.fromActivity = fromActivity; - this.toActivity = toActivity; - if (title) { - // only branches have titles - this.title = title; - this.loadPropertiesDialogContent = PropertyLib.transitionProperties; + BranchingEdgeActivity : function(id, uiid, x, y, title, branchingType, branchingActivity) { + this.transitions = { + 'from' : [], + 'to' : [] + }; + + if (branchingActivity) { + // branchingActivity already exists, so this is the converge point + this.isStart = false; + branchingActivity.end = this; + } else { + // this is the branching point + this.isStart = true; + branchingActivity = new ActivityDefs.BranchingActivity(id, uiid, this); + branchingActivity.branchingType = branchingType || 'chosen'; + branchingActivity.title = title || LABELS.DEFAULT_BRANCHING_TITLE; } + this.branchingActivity = branchingActivity; - this.draw = ActivityLib.draw.transition; - this.draw(); + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.branchingProperties; + } - // set up references in activities and the transition - fromActivity.transitions.from.push(this); - toActivity.transitions.to.push(this); + this.draw = ActivityDraw.branching; + this.draw(x, y); }, /** - * Constructor for a Tool Activity. + * Represents a set of branches. It is not displayed on canvas, but holds all the vital data. */ - ToolActivity : function(id, uiid, toolContentID, toolID, learningLibraryID, authorURL, - x, y, title, supportsOutputs) { + BranchingActivity : function(id, uiid, branchingEdgeStart) { this.id = +id || null; this.uiid = +uiid || ++layout.ld.maxUIID; - this.toolContentID = toolContentID; - this.toolID = +toolID; - this.learningLibraryID = +learningLibraryID; - this.authorURL = authorURL; - this.title = title; - this.supportsOutputs = supportsOutputs; - this.transitions = { - 'from' : [], - 'to' : [] - }; + this.start = branchingEdgeStart; + this.branches = []; + // mapping between groups and branches, if applicable + this.groupsToBranches = []; + // mapping between tool output and branches, if applicable + this.conditionsToBranches = []; - this.loadPropertiesDialogContent = PropertyLib.toolProperties; - - this.draw = ActivityLib.draw.tool; - this.draw(x, y); + this.minOptions = 0; + this.maxOptions = 0; }, - + + /** - * Constructor for a Grouping Activity. + * Represents a subsequence of activities. It is not displayed on canvas, but is the parent activity for its children. */ - GroupingActivity : function(id, uiid, x, y, title, groupingID, groupingUIID, groupingType, groupDivide, - groupCount, learnerCount, equalSizes, viewLearners, groups) { + BranchActivity : function(id, uiid, title, branchingActivity, transitionFrom, defaultBranch) { this.id = +id || null; - this.groupingID = +groupingID || null; - this.groupingUIID = +groupingUIID || ++layout.ld.maxUIID; this.uiid = +uiid || ++layout.ld.maxUIID; - this.title = title || LABELS.DEFAULT_GROUPING_TITLE; - this.groupingType = groupingType || 'random'; - this.groupDivide = groupDivide || 'groups'; - this.groupCount = +groupCount || 2; - this.learnerCount = +learnerCount || 1; - this.equalSizes = equalSizes || false; - this.viewLearners = viewLearners || false; - this.groups = groups || PropertyLib.fillNameAndUIIDList(this.groupCount, [], 'name', LABELS.DEFAULT_GROUP_PREFIX); - this.transitions = { - 'from' : [], - 'to' : [] - }; + this.title = title || (LABELS.DEFAULT_BRANCH_PREFIX + (branchingActivity.branches.length + 1)); + this.branchingActivity = branchingActivity; + this.transitionFrom = transitionFrom; + if (defaultBranch) { + this.defaultBranch = true; + // there can be only one default branch + $.each(branchingActivity.branches, function(){ + this.defaultBranch = false; + }); + } + }, + + + /** + * Constructor for a Floating Activity. + */ + FloatingActivity : function(id, uiid, x, y) { + DecorationDefs.Container.call(this, id, uiid, LABELS.SUPPORT_ACTIVITY_TITLE); - this.loadPropertiesDialogContent = PropertyLib.groupingProperties; - - this.draw = ActivityLib.draw.grouping; + this.draw = ActivityDraw.floatingActivity; this.draw(x, y); + + // there can only be one Floating Activity container + layout.floatingActivity = this; }, - + + /** * Constructor for a Gate Activity. */ @@ -104,82 +114,74 @@ 'to' : [] }; - this.loadPropertiesDialogContent = PropertyLib.gateProperties; - - this.draw = ActivityLib.draw.gate; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.gateProperties; + } + + this.draw = ActivityDraw.gate; this.draw(x, y); }, /** - * Either branching or converge point. + * Constructor for a Grouping Activity. */ - BranchingEdgeActivity : function(id, uiid, x, y, title, branchingType, branchingActivity) { + GroupingActivity : function(id, uiid, x, y, title, groupingID, groupingUIID, groupingType, groupDivide, + groupCount, learnerCount, equalSizes, viewLearners, groups) { + this.id = +id || null; + this.groupingID = +groupingID || null; + this.groupingUIID = +groupingUIID || ++layout.ld.maxUIID; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.title = title || LABELS.DEFAULT_GROUPING_TITLE; + this.groupingType = groupingType || 'random'; + this.groupDivide = groupDivide || 'groups'; + this.groupCount = +groupCount || layout.conf.defaultGroupingGroupCount; + this.learnerCount = +learnerCount || layout.conf.defaultGroupingLearnerCount; + this.equalSizes = equalSizes || false; + this.viewLearners = viewLearners || false; + // either groups are already defined or create them with default names + this.groups = groups || PropertyLib.fillNameAndUIIDList(this.groupCount, [], 'name', LABELS.DEFAULT_GROUP_PREFIX); this.transitions = { 'from' : [], 'to' : [] }; - if (branchingActivity) { - // branchingActivity already exists, so this is the converge point - this.isStart = false; - branchingActivity.end = this; - } else { - // this is the branching point - this.isStart = true; - branchingActivity = new ActivityLib.BranchingActivity(id, uiid, this); - branchingActivity.branchingType = branchingType || 'chosen'; - branchingActivity.title = title || LABELS.DEFAULT_BRANCHING_TITLE; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.groupingProperties; } - this.branchingActivity = branchingActivity; - - this.loadPropertiesDialogContent = PropertyLib.branchingProperties; - - this.draw = ActivityLib.draw.branching; + this.draw = ActivityDraw.grouping; this.draw(x, y); }, /** - * Represents a set of branches. It is not displayed on canvas. + * Constructor for an Optional Activity. */ - BranchingActivity : function(id, uiid, branchingEdgeStart) { + OptionalActivity : function(id, uiid, x, y, title, minOptions, maxOptions) { + DecorationDefs.Container.call(this, id, uiid, title || LABELS.DEFAULT_OPTIONAL_ACTIVITY_TITLE); + this.id = +id || null; this.uiid = +uiid || ++layout.ld.maxUIID; - this.start = branchingEdgeStart; - this.branches = []; - // mapping between groups and branches, if applicable - this.groupsToBranches = []; - // mapping between tool output and branches, if applicable - this.conditionsToBranches = []; + this.minOptions = minOptions || 0; + this.maxOptions = maxOptions || 0; + this.transitions = { + 'from' : [], + 'to' : [] + }; - this.minOptions = 0; - this.maxOptions = 0; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.optionalActivityProperties; + } + this.draw = ActivityDraw.optionalActivity; + this.draw(x, y); }, /** - * Represents a subsequence of activities. It is not displayed on canvas. + * Constructor for a Parallel (double) Activity */ - BranchActivity : function(id, uiid, title, branchingActivity, transitionFrom, defaultBranch) { - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - this.title = title || (LABELS.DEFAULT_BRANCH_PREFIX + (branchingActivity.branches.length + 1)); - this.branchingActivity = branchingActivity; - this.transitionFrom = transitionFrom; - this.defaultBranch = defaultBranch; - if (defaultBranch) { - this.defaultBranch = true; - // there can be only one default branch - $.each(branchingActivity.branches, function(){ - this.defaultBranch = false; - }); - } - }, - - - ParallelActivity : function(id, uiid, learningLibraryID, x, y, title, childActivities){ - DecorationLib.Container.call(this, id, uiid, title); + ParallelActivity : function(id, uiid, learningLibraryID, x, y, title, childActivityDefs){ + DecorationDefs.Container.call(this, id, uiid, title); this.id = +id || null; this.uiid = +uiid || ++layout.ld.maxUIID; @@ -188,589 +190,443 @@ 'from' : [], 'to' : [] }; - if (childActivities){ - this.childActivities = childActivities; + if (childActivityDefs){ + this.childActivityDefs = childActivityDefs; } - this.loadPropertiesDialogContent = PropertyLib.parallelProperties; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.parallelProperties; + } - this.draw = ActivityLib.draw.parallelActivity; + this.draw = ActivityDraw.parallelActivity; this.draw(x, y); }, + /** - * Constructor for an Optional Activity. + * Constructor for a Tool Activity. */ - OptionalActivity : function(id, uiid, x, y, title, minOptions, maxOptions) { - DecorationLib.Container.call(this, id, uiid, title || LABELS.DEFAULT_OPTIONAL_ACTIVITY_TITLE); - + ToolActivity : function(id, uiid, toolContentID, toolID, learningLibraryID, authorURL, x, y, title) { this.id = +id || null; this.uiid = +uiid || ++layout.ld.maxUIID; - this.minOptions = minOptions || 0; - this.maxOptions = maxOptions || 0; + this.toolContentID = toolContentID; + this.toolID = +toolID; + this.learningLibraryID = +learningLibraryID; + this.authorURL = authorURL; + this.title = title; this.transitions = { 'from' : [], 'to' : [] }; - this.loadPropertiesDialogContent = PropertyLib.optionalActivityProperties; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.toolProperties; + } - this.draw = ActivityLib.draw.optionalActivity; + this.draw = ActivityDraw.tool; this.draw(x, y); }, /** - * Constructor for a Floating Activity. + * Constructor for a Transition */ - FloatingActivity : function(id, uiid, x, y) { - DecorationLib.Container.call(this, id, uiid, LABELS.SUPPORT_ACTIVITY_TITLE); + Transition : function(id, uiid, fromActivity, toActivity, title) { + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + this.fromActivity = fromActivity; + this.toActivity = toActivity; + if (title) { + // only branches have titles + this.title = title; + + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.transitionProperties; + } + } - this.draw = ActivityLib.draw.floatingActivity; - this.draw(x, y); + this.draw = ActivityDraw.transition; + this.draw(); - // there can only be one - layout.floatingActivity = this; - }, + // set up references in edge activities + fromActivity.transitions.from.push(this); + toActivity.transitions.to.push(this); + } +}, + + +/** + * Mehtods for drawing various kinds of activities. + * They are not defined in constructors so there is a static reference, + * not a separate definition for each object instance. + */ +ActivityDraw = { - /** - * Mehtods for drawing various kinds of activities. - * They are not defined in constructors so there is always a reference to an existing method, - * not a separate definition for each object instance. + * Draws a Branching Activity */ - draw : { + branching : function(x, y) { + if (x == undefined || y == undefined) { + // just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; + } - transition : function() { - // clear previous canvas elements - if (this.items) { - this.items.remove(); - } - - // calculate middle points of each activity - var points = ActivityLib.findTransitionPoints(this.fromActivity, this.toActivity); - - // create transition SVG elements - paper.setStart(); - paper.path(Raphael.format('M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)) - .attr({ - 'stroke' : layout.colors.transition, - 'stroke-width' : 2 - }); - - // draw the arrow and turn it in the same direction as the line - var angle = 90 + Math.atan2(points.endY - points.startY, points.endX - points.startX) * 180 / Math.PI, - arrowPath = Raphael.transformPath(Raphael.format('M {0} {1}' + layout.defs.transArrow, points.middleX, points.middleY), - Raphael.format('R {0} {1} {2}', angle, points.middleX, points.middleY)); - paper.path(arrowPath) - .attr({ - 'stroke' : layout.colors.transition, - 'fill' : layout.colors.transition - }); - if (this.title) { - // adjust X & Y depending on the angle, so the label does not overlap with the transition; - // angle is -90 <= a <= 270 - paper.text(points.middleX + ((angle > -45 && angle < 45) || (angle > 135 && angle < 225) ? 20 : 0), - points.middleY + ((angle > 45 && angle < 135) || angle > 225 || angle < 45 ? -20 : 0), - this.title) - .attr('text-anchor', 'start'); - } - this.items = paper.setFinish(); - - this.items.toBack(); - this.items.attr('cursor', 'pointer'); - this.items.data('parentObject', this); - this.items.mousedown(HandlerLib.transitionMousedownHandler); - this.items.click(HandlerLib.itemClickHandler); - }, + if (this.items) { + this.items.remove(); + } + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1} a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0', x, y + 8)) + .attr({ + 'fill' : this.isStart ? layout.colors.branchingEdgeStart + : layout.colors.branchingEdgeEnd + }); - tool : function(x, y) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - // create activity SVG elements - this.items = paper.set(); - var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.activity, x, y)) - .attr({ - 'fill' : layout.colors.activity[layout.toolMetadata[this.learningLibraryID].activityCategoryID] - }); - - this.items.shape = shape; - this.items.push(shape); - - if (this.grouping) { - ActivityLib.addGroupingEffect(this); - } - - // check for icon in the library - this.items.push(paper.image(layout.toolMetadata[this.learningLibraryID].iconPath, x + 47, y + 2, 30, 30)); - this.items.push(paper.text(x + 62, y + 40, ActivityLib.shortenActivityTitle(this.title)) - .attr({ - 'fill' : layout.colors.activityText - })); - - ActivityLib.activityHandlersInit(this); - }, + var title = this.branchingActivity.title; + paper.text(x + 8, y + 27, title + '\n' + (this.isStart ? LABELS.BRANCHING_START_SUFFIX + : LABELS.BRANCHING_END_SUFFIX)) + .attr(layout.defaultTextAttributes); + this.items = paper.setFinish(); + this.items.shape = shape; - grouping : function(x, y) { - if (x == undefined || y == undefined) { - // just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - // create activity SVG elements - paper.setStart(); - var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.activity, x, y)) - .attr({ - 'fill' : layout.colors.grouping - }); - - paper.image('../images/grouping.gif', x + 47, y + 2, 30, 30); - paper.text(x + 62, y + 40, ActivityLib.shortenActivityTitle(this.title)); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws a Floating (support) Activity container + */ + floatingActivity : function(x, y, ignoredParam1, ignoredParam2, childActivityDefs) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; + } + // either check what children are on canvas or use the priovided parameter + if (childActivityDefs) { + this.childActivityDefs = childActivityDefs; + } - gate : function(x, y) { - if (x == undefined || y == undefined) { - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } + if (this.childActivityDefs && this.childActivityDefs.length > 0) { + // draw one by one, horizontally + var activityX = x + layout.conf.containerActivityPadding, + allElements = paper.set(), + floatingActivity = this, + box = this.items.shape.getBBox(); + $.each(this.childActivityDefs, function(orderID){ + this.parentActivity = floatingActivity; + this.orderID = orderID; + var childBox = this.items.shape.getBBox(); + this.draw(activityX, y + Math.max(layout.conf.containerActivityPadding + 10, (box.height - childBox.height)/2)); + childBox = this.items.shape.getBBox(); + activityX = childBox.x2 + layout.conf.containerActivityChildrenPadding; + allElements.push(this.items.shape); + }); + // area containing all drawn child activities + box = allElements.getBBox(); - // create activity SVG elements - paper.setStart(); - var shape = paper.path(Raphael.format('M {0} {1}' + layout.defs.gate, x + 8, y)) - .attr({ - 'fill' : layout.colors.gate - }); - - paper.text(x + 15, y + 14, LABELS.GATE_ACTIVITY_LABEL) - .attr({ - 'font-size' : 9, - 'font' : 'sans-serif', - 'stroke' : layout.colors.gateText - }); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, + this.drawContainer(x, y, + box.x2 + layout.conf.containerActivityPadding, + box.y2 + layout.conf.containerActivityPadding, + layout.colors.optionalActivity); + } else { + this.drawContainer(x, y, + x + layout.conf.containerActivityEmptyWidth, + y + layout.conf.containerActivityEmptyHeight, + layout.colors.optionalActivity); + } + this.items.data('parentObject', this); + }, + + + /** + * Draws a Gate activity + */ + gate : function(x, y) { + if (x == undefined || y == undefined) { + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; + } + + if (this.items) { + this.items.remove(); + } - branching : function(x, y) { - if (x == undefined || y == undefined) { - // just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - if (this.items) { - this.items.remove(); - } - - // create activity SVG elements - paper.setStart(); - var shape = paper.path(Raphael.format('M {0} {1}' - + (this.isStart ? layout.defs.branchingEdgeStart : layout.defs.branchingEdgeEnd), - x, y + 8)) - .attr({ - 'fill' : this.isStart ? layout.colors.branchingEdgeStart - : layout.colors.branchingEdgeEnd - }); - - var title = this.branchingActivity.title; - paper.text(x + 8, y + 27, title + '\n' + (this.isStart ? LABELS.BRANCHING_START_SUFFIX - : LABELS.BRANCHING_END_SUFFIX)) - .attr({ - 'font-size' : 9, - 'font' : 'sans-serif' - }); - - this.items = paper.setFinish(); - this.items.shape = shape; - - ActivityLib.activityHandlersInit(this); - }, + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1} l-8 8 v14 l8 8 h14 l8 -8 v-14 l-8 -8 z', x + 8, y)) + .attr({ + 'fill' : layout.colors.gate + }); + paper.text(x + 15, y + 14, LABELS.GATE_ACTIVITY_LABEL) + .attr(layout.defaultTextAttributes) + .attr('stroke', layout.colors.gateText); - parallelActivity : function(x, y) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, vertically - var activityY = y + 30, - allElements = paper.set(), - optionalActivity = this; - $.each(this.childActivities, function(orderID){ - this.parentActivity = optionalActivity; - this.orderID = orderID + 1; - this.draw(x + 20, activityY); - activityY = this.items.shape.getBBox().y2 + 10; - allElements.push(this.items.shape); - }); - // area containing all drawn child activities - var box = allElements.getBBox(); - - this.drawContainer(x, y, box.x2 + 20, box.y2 + 20, layout.colors.optionalActivity); - } else { - this.drawContainer(x, y, x + 50, y + 70, layout.colors.optionalActivity); - } - - // allow transition drawing and other activity behaviour - this.items.shape.unmousedown().mousedown(HandlerLib.activityMousedownHandler); - - this.items.data('parentObject', this); - }, + this.items = paper.setFinish(); + this.items.shape = shape; + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws a Gropuing activity + */ + grouping : function(x, y) { + if (x == undefined || y == undefined) { + // just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; + } + + if (this.items) { + this.items.remove(); + } - optionalActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - // either check what children are on canvas or use the priovided parameter - if (childActivities) { - this.childActivities = childActivities; - } - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, vertically - var activityY = y + 30, - allElements = paper.set(), - optionalActivity = this, - box = this.items.shape.getBBox(); - $.each(this.childActivities, function(orderID){ - this.parentActivity = optionalActivity; - this.orderID = orderID + 1; - var childBox = this.items.shape.getBBox(); - this.draw(x + Math.max(20, (box.width - childBox.width)/2), activityY); - childBox = this.items.shape.getBBox(); - activityY = childBox.y2 + 10; - allElements.push(this.items.shape); - }); - // area containing all drawn child activities - box = allElements.getBBox(); - - this.drawContainer(x, y, box.x2 + 20, box.y2 + 20, layout.colors.optionalActivity); - } else { - this.drawContainer(x, y, x + 50, y + 70, layout.colors.optionalActivity); - } - - // allow transition drawing and other activity behaviour - this.items.shape.unmousedown().mousedown(HandlerLib.activityMousedownHandler); - - this.items.data('parentObject', this); - }, + // create activity SVG elements + paper.setStart(); + var shape = paper.path(Raphael.format('M {0} {1} h 125 v 50 h -125 z', x, y)) + .attr({ + 'fill' : layout.colors.grouping + }); + paper.image('../images/grouping.gif', x + 47, y + 2, 30, 30); + paper.text(x + 62, y + 40, ActivityLib.shortenActivityTitle(this.title)); - floatingActivity : function(x, y, ignoredParam1, ignoredParam2, childActivities) { - if (x == undefined || y == undefined) { - // if no new coordinates are given, just redraw the activity - x = this.items.shape.getBBox().x; - y = this.items.shape.getBBox().y; - } - - // either check what children are on canvas or use the priovided parameter - if (childActivities) { - this.childActivities = childActivities; - } - - if (this.childActivities && this.childActivities.length > 0) { - // draw one by one, horizontally - var activityX = x + 20, - allElements = paper.set(), - floatingActivity = this, - box = this.items.shape.getBBox(); - $.each(this.childActivities, function(orderID){ - this.parentActivity = floatingActivity; - this.orderID = orderID; - var childBox = this.items.shape.getBBox(); - this.draw(activityX, y + Math.max(30, (box.height - childBox.height)/2)); - childBox = this.items.shape.getBBox(); - activityX = childBox.x2 + 10; - allElements.push(this.items.shape); - }); - // area containing all drawn child activities - box = allElements.getBBox(); - - this.drawContainer(x, y, box.x2 + 20, box.y2 + 20, layout.colors.optionalActivity); - } else { - this.drawContainer(x, y, x + 50, y + 70, layout.colors.optionalActivity); - } - - this.items.data('parentObject', this); - } + this.items = paper.setFinish(); + this.items.shape = shape; + + ActivityLib.activityHandlersInit(this); }, /** - * Make a new activity fully functional on canvas. + * Draws an Optional Activity container */ - activityHandlersInit : function(activity) { - // set all the handlers - activity.items - .data('parentObject', activity) - .mousedown(HandlerLib.activityMousedownHandler) - .click(HandlerLib.itemClickHandler) - .dblclick(HandlerLib.activityDblclickHandler) - .attr({ - 'cursor' : 'pointer' + optionalActivity : function(x, y, ignoredParam1, ignoredParam2, childActivityDefs) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; + } + + // either check what children are on canvas or use the priovided parameter + if (childActivityDefs) { + this.childActivityDefs = childActivityDefs; + } + + if (this.childActivityDefs && this.childActivityDefs.length > 0) { + // draw one by one, vertically + var activityY = y + containerActivityPadding + 10, + allElements = paper.set(), + optionalActivity = this, + box = this.items.shape.getBBox(); + $.each(this.childActivityDefs, function(orderID){ + this.parentActivity = optionalActivity; + this.orderID = orderID + 1; + var childBox = this.items.shape.getBBox(); + this.draw(x + Math.max(containerActivityPadding, (box.width - childBox.width)/2), activityY); + childBox = this.items.shape.getBBox(); + activityY = childBox.y2 + containerActivityChildrenPadding; + allElements.push(this.items.shape); }); + // area containing all drawn child activities + box = allElements.getBBox(); - if (activity instanceof ActivityLib.BranchingEdgeActivity - && activity.branchingActivity.end) { - // highligh branching edges on hover - activity.branchingActivity.start.items.hover(HandlerLib.branchingEdgeMouseoverHandler, - HandlerLib.branchingEdgeMouseoutHandler); - activity.branchingActivity.end.items.hover(HandlerLib.branchingEdgeMouseoverHandler, - HandlerLib.branchingEdgeMouseoutHandler); - } - }, - + this.drawContainer(x, y, + box.x2 + layout.conf.containerActivityPadding, + box.y2 + layout.conf.containerActivityPadding, + layout.colors.optionalActivity); + } else { + this.drawContainer(x, y, + x + containerActivityEmptyWidth, + y + containerActivityEmptyHeight, + layout.colors.optionalActivity); + } + + if (!isReadOnlyMode){ + // allow transition drawing and other activity behaviour + this.items.shape.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); + } + + this.items.data('parentObject', this); + }, + /** - * Deletes the given activity. + * Draws a Parallel (double) Activity container */ - removeActivity : function(activity, forceRemove) { - var coreActivity = activity.branchingActivity || this; - if (!forceRemove && activity instanceof ActivityLib.BranchingEdgeActivity){ - // user removes one of the branching edges, so remove the whole activity - if (confirm(LABELS.REMOVE_ACTIVITY_CONFIRM)){ - var otherEdge = activity.isStart ? coreActivity.end - : coreActivity.start; - ActivityLib.removeActivity(otherEdge, true); - } else { - return; - } + parallelActivity : function(x, y) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; } - if (activity instanceof ActivityLib.FloatingActivity) { - layout.floatingActivity = null; - // re-enable the button - $('#floatingActivityButton').attr('disabled', null) - .css('opacity', 1); - } else { - // remove the transitions - // need to use slice() to copy the array as it gets modified in removeTransition() - $.each(activity.transitions.from.slice(), function() { - ActivityLib.removeTransition(this); + if (this.childActivityDefs && this.childActivityDefs.length > 0) { + // draw one by one, vertically + var activityY = y + layout.conf.containerActivityPadding + 10, + allElements = paper.set(), + optionalActivity = this; + $.each(this.childActivityDefs, function(orderID){ + this.parentActivity = optionalActivity; + this.orderID = orderID + 1; + this.draw(x + layout.conf.containerActivityPadding, activityY); + activityY = this.items.shape.getBBox().y2 + layout.conf.containerActivityChildrenPadding; + allElements.push(this.items.shape); }); - $.each(activity.transitions.to.slice(), function() { - ActivityLib.removeTransition(this); - }); + // area containing all drawn child activities + var box = allElements.getBBox(); - // remove the activity from reference tables - layout.activities.splice(layout.activities.indexOf(activity), 1); - if (layout.items.copiedActivity = activity) { - layout.items.copiedActivity = null; - } - - // find references of this activity as grouping or input - $.each(layout.activities, function(){ - if (activity == coreActivity.grouping) { - coreActivity.grouping = null; - this.draw(); - } else if (activity == coreActivity.input) { - coreActivity.input = null; - } - }); + this.drawContainer(x, y, + box.x2 + layout.conf.containerActivityPadding, + box.y2 + layout.conf.containerActivityPadding, + layout.colors.optionalActivity); + } else { + this.drawContainer(x, y, + x + layout.conf.containerActivityEmptyWidth, + y + layout.conf.containerActivityEmptyHeight, + layout.colors.optionalActivity); } - // remove the activity from parent activity - if (activity.parentActivity && activity.parentActivity instanceof DecorationLib.Container) { - activity.parentActivity.childActivities.splice(activity.parentActivity.childActivities.indexOf(activity), 1); + if (!isReadOnlyMode){ + // allow transition drawing and other activity behaviour + this.items.shape.unmousedown().mousedown(HandlerActivityLib.activityMousedownHandler); } - // remove child activities - if (activity instanceof DecorationLib.Container) { - $.each(activity.childActivities, function(){ - ActivityLib.removeActivity(this); - }); - } - - // visually remove the activity - activity.items.remove(); + this.items.data('parentObject', this); }, /** - * Draws a transition between two activities. + * Draws a Tool activity */ - addTransition : function(fromActivity, toActivity, redraw, id, uiid, branchData) { - if (toActivity.parentActivity && toActivity.parentActivity instanceof DecorationLib.Container){ - toActivity = toActivity.parentActivity; + tool : function(x, y) { + if (x == undefined || y == undefined) { + // if no new coordinates are given, just redraw the activity + x = this.items.shape.getBBox().x; + y = this.items.shape.getBBox().y; } - if (fromActivity.parentActivity && fromActivity.parentActivity instanceof DecorationLib.Container){ - fromActivity = fromActivity.parentActivity; - } - if (toActivity instanceof ActivityLib.FloatingActivity - || fromActivity instanceof ActivityLib.FloatingActivity){ - return; - } - // only converge points are allowed to have few inbound transitions - if (!redraw - && toActivity.transitions.to.length > 0 - && !(toActivity instanceof ActivityLib.BranchingEdgeActivity && !toActivity.isStart)) { - alert(LABELS.TRANSITION_TO_EXISTS_ERROR); - return; + if (this.items) { + this.items.remove(); } - - // user chose to create outbound transition from an activity that already has one - if (!redraw - && fromActivity.transitions.from.length > 0 - && !(fromActivity instanceof ActivityLib.BranchingEdgeActivity && fromActivity.isStart) - && !(toActivity instanceof ActivityLib.BranchingEdgeActivity && toActivity.isStart)) { - if (confirm(LABELS.BRANCHING_CREATE_CONFIRM)) { - ActivityLib.addBranching(fromActivity, toActivity); - } - return; - } - // branchData can be either existing branch or a title for the new branch - var branch = branchData && branchData instanceof ActivityLib.BranchActivity ? branchData : null, - transition = null; - // remove the existing transition - $.each(fromActivity.transitions.from, function(index) { - if (this.toActivity == toActivity) { - id = this.id; - uiid = this.uiid; - transition = this; - if (!branch){ - branch = this.branch; - } + // create activity SVG elements + this.items = paper.set(); + var shape = paper.path(Raphael.format('M {0} {1} h 125 v 50 h -125 z', x, y)) + // activity colour depends on its category ID + .attr({ + 'fill' : layout.colors.activity[layout.toolMetadata[this.learningLibraryID].activityCategoryID] + }); - return false; - } - }); + this.items.shape = shape; + this.items.push(shape); - if (!branch && fromActivity instanceof ActivityLib.BranchingEdgeActivity && fromActivity.isStart) { - $.each(fromActivity.branchingActivity.branches, function(){ - if (branchData == this.title) { - branch = this; - return false; - } - }); - if (!branch) { - // create a new branch - branch = new ActivityLib.BranchActivity(null, null, branchData, fromActivity.branchingActivity, false); - } + if (this.grouping) { + ActivityLib.addGroupingEffect(this); } + // check for icon in the library + this.items.push(paper.image(layout.toolMetadata[this.learningLibraryID].iconPath, x + 47, y + 2, 30, 30)); + this.items.push(paper.text(x + 62, y + 40, ActivityLib.shortenActivityTitle(this.title)) + .attr(layout.defaultTextAttributes) + .attr('fill', layout.colors.activityText)); - if (transition) { - ActivityLib.removeTransition(transition, redraw); + ActivityLib.activityHandlersInit(this); + }, + + + /** + * Draws a Transition + */ + transition : function() { + // clear previous canvas elements + if (this.items) { + this.items.remove(); } - transition = new ActivityLib.Transition(id, uiid, fromActivity, toActivity, - branch ? branch.title : null); + // calculate middle points of each activity + var points = ActivityLib.findTransitionPoints(this.fromActivity, this.toActivity); + + // create transition SVG elements + paper.setStart(); + paper.path(Raphael.format('M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)) + .attr({ + 'stroke' : layout.colors.transition, + 'stroke-width' : 2 + }); - if (branch) { - // set the corresponding branch (again) - branch.transitionFrom = transition; - transition.branch = branch; - fromActivity.branchingActivity.branches.push(branch); - if (fromActivity.branchingActivity.branches.length == 1) { - branch.defaultBranch = true; - } + // draw the arrow and turn it in the same direction as the line + var angle = 90 + Math.atan2(points.endY - points.startY, points.endX - points.startX) * 180 / Math.PI, + arrowPath = Raphael.transformPath(Raphael.format('M {0} {1} l 10 15 a 25 25 0 0 0 -20 0 z', + points.middleX, points.middleY), + Raphael.format('R {0} {1} {2}', angle, points.middleX, points.middleY)); + paper.path(arrowPath) + .attr({ + 'stroke' : layout.colors.transition, + 'fill' : layout.colors.transition + }); + if (this.title) { + // adjust X & Y depending on the angle, so the label does not overlap with the transition; + // angle in Javascript is -90 <= a <= 270 + paper.text(points.middleX + ((angle > -45 && angle < 45) || (angle > 135 && angle < 225) ? 20 : 0), + points.middleY + ((angle > 45 && angle < 135) || angle > 225 || angle < 45 ? -20 : 0), + this.title) + .attr('text-anchor', 'start'); } + this.items = paper.setFinish(); + + this.items.toBack(); + this.items.data('parentObject', this); - setModified(true); - return transition; - }, - - + if (!isReadOnlyMode){ + this.items.attr('cursor', 'pointer'); + this.items.mousedown(HandlerTransitionLib.transitionMousedownHandler); + this.items.click(HandlerLib.itemClickHandler); + } + } +}, + + + +/** + * Contains utility methods for Activity manipulation. + */ +ActivityLib = { + /** - * Removes the given transition. + * Make a new activity fully functional on canvas. */ - removeTransition : function(transition, redraw) { - // find the transition and remove it - var transitions = transition.fromActivity.transitions.from; - transitions.splice(transitions.indexOf(transition), 1); - transitions = transition.toActivity.transitions.to; - transitions.splice(transitions.indexOf(transition), 1); + activityHandlersInit : function(activity) { + activity.items.data('parentObject', activity); - if (transition.branch) { - // remove corresponding branch - var branches = transition.branch.branchingActivity.branches; - branches.splice(branches.indexOf(transition.branch), 1); + if (!isReadOnlyMode) { + // set all the handlers + activity.items.mousedown(HandlerActivityLib.activityMousedownHandler) + .click(HandlerLib.itemClickHandler) + .dblclick(HandlerActivityLib.activityDblclickHandler) + .attr({ + 'cursor' : 'pointer' + }); - if (transition.branch.defaultBranch && branches.length > 0) { - // reset the first branch as the default one - branches[0].defaultBranch = true; + if (activity instanceof ActivityDefs.BranchingEdgeActivity + && activity.branchingActivity.end) { + // highligh branching edges on hover + activity.branchingActivity.start.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, + HandlerActivityLib.branchingEdgeMouseoutHandler); + activity.branchingActivity.end.items.hover(HandlerActivityLib.branchingEdgeMouseoverHandler, + HandlerActivityLib.branchingEdgeMouseoutHandler); } } - - if (!redraw){ - // remove grouping or input references if chain was broken by the removed transition - $.each(layout.activities, function(){ - var coreActivity = this.branchingActivity || this; - if (coreActivity.grouping || coreActivity.input) { - var candidate = this.branchingActivity ? coreActivity.start : this, - groupingFound = false, - inputFound = false; - do { - if (candidate.transitions && candidate.transitions.to.length > 0) { - candidate = candidate.transitions.to[0].fromActivity; - } else if (candidate.branchingActivity && !candidate.isStart) { - candidate = candidate.branchingActivity.start; - } else if (!candidate.branchingActivity && candidate.parentActivity) { - candidate = candidate.parentActivity; - } else { - candidate = null; - } - - if (coreActivity.grouping == candidate) { - groupingFound = true; - } - if (coreActivity.input == candidate) { - inputFound = true; - } - } while (candidate != null); - - if (!groupingFound) { - coreActivity.grouping = null; - this.draw(); - } - if (!inputFound) { - coreActivity.input = null; - } - } - }); - } - - transition.items.remove(); - setModified(true); }, + /** @@ -789,7 +645,7 @@ convergeActivity1 = convergeActivity1.transitions.from[0].toActivity; }; - if (toActivity2 instanceof ActivityLib.BranchingEdgeActivity && toActivity2.isStart) { + if (toActivity2 instanceof ActivityDefs.BranchingEdgeActivity && toActivity2.isStart) { // there is already a branching activity, reuse existing items branchingEdgeStart = toActivity2; branchingEdgeEnd = toActivity2.branchingActivity.end; @@ -802,7 +658,7 @@ branchPoints2 = ActivityLib.findTransitionPoints(fromActivity, toActivity2), branchEdgeStartX = branchPoints1.middleX + (branchPoints2.middleX - branchPoints1.middleX)/2, branchEdgeStartY = branchPoints1.middleY + (branchPoints2.middleY - branchPoints1.middleY)/2, - branchingEdgeStart = new ActivityLib.BranchingEdgeActivity(null, null, branchEdgeStartX, + branchingEdgeStart = new ActivityDefs.BranchingEdgeActivity(null, null, branchEdgeStartX, branchEdgeStartY, null, null, null); layout.activities.push(branchingEdgeStart); @@ -812,7 +668,7 @@ }; var convergePoints = ActivityLib.findTransitionPoints(convergeActivity1, convergeActivity2), - branchingEdgeEnd = new ActivityLib.BranchingEdgeActivity(null, null, convergePoints.middleX, + branchingEdgeEnd = new ActivityDefs.BranchingEdgeActivity(null, null, convergePoints.middleX, convergePoints.middleY, null, null, branchingEdgeStart.branchingActivity); layout.activities.push(branchingEdgeEnd); @@ -824,18 +680,46 @@ ActivityLib.addTransition(branchingEdgeStart, toActivity1); ActivityLib.addTransition(convergeActivity1, branchingEdgeEnd); - setModified(true); + GeneralLib.setModified(true); }, + /** - * Highlights the selected canvas element. + * Adds visual grouping effect on an activity. */ + addGroupingEffect : function(activity) { + // do not draw twice if it already exists + if (!activity.items.groupingEffect) { + var shape = activity.items.shape, + activityBox = shape.getBBox(); + + activity.items.groupingEffect = paper.rect( + activityBox.x + layout.conf.groupingEffectPadding, + activityBox.y + layout.conf.groupingEffectPadding, + activityBox.width, + activityBox.height) + .attr({ + 'fill' : shape.attr('fill') + }); + + shape.toFront(); + activity.items.push(activity.items.groupingEffect); + + // this is needed, for some reason, otherwise the activity can not be selected + HandlerLib.resetCanvasMode(true); + } + }, + + + /** + * Adds visual select effect around an activity. + */ addSelectEffect : function (object, markSelected) { // do not draw twice if (!object.items.selectEffect) { // different effects for different types of objects - if (object instanceof DecorationLib.Region) { + if (object instanceof DecorationDefs.Region) { object.items.shape.attr({ 'stroke' : layout.colors.selectEffect, 'stroke-dasharray' : '-' @@ -845,17 +729,17 @@ object.items.selectEffect = true; // also select encapsulated activities - var childActivities = DecorationLib.getChildActivities(object.items.shape); - if (childActivities.length > 0) { + var childActivityDefs = DecorationLib.getChildActivityDefs(object.items.shape); + if (childActivityDefs.length > 0) { object.items.fitButton.show(); - $.each(childActivities, function(){ - if (!this.parentActivity || !(this.parentActivity instanceof DecorationLib.Container)) { + $.each(childActivityDefs, function(){ + if (!this.parentActivity || !(this.parentActivity instanceof DecorationDefs.Container)) { ActivityLib.addSelectEffect(this, false); } }); } - } else if (object instanceof ActivityLib.Transition) { + } else if (object instanceof ActivityDefs.Transition) { // show only if Transition is selectable, i.e. is a branch, has a title if (object.loadPropertiesDialogContent) { object.items.attr({ @@ -866,15 +750,15 @@ object.items.selectEffect = true; } } else { - // this goes for Activities and Labels + // this goes for ActivityDefs and Labels var box = object.items.getBBox(); // a simple rectange a bit wider than the actual activity boundaries object.items.selectEffect = paper.rect( - box.x - 7, - box.y - 7, - box.width + 14, - box.height + 14) + box.x - layout.conf.selectEffectPadding, + box.y - layout.conf.selectEffectPadding, + box.width + 2*layout.conf.selectEffectPadding, + box.height + 2*layout.conf.selectEffectPadding) .attr({ 'stroke' : layout.colors.selectEffect, 'stroke-dasharray' : '-' @@ -884,7 +768,7 @@ // make it officially marked? if (markSelected && object.items.selectEffect){ - layout.items.selectedObject = object; + layout.selectedObject = object; // show the properties dialog for the selected object if (object.loadPropertiesDialogContent) { PropertyLib.openPropertiesDialog(object); @@ -894,78 +778,144 @@ }, - removeSelectEffect : function(object) { - // remove the effect from the given object or the selected one, whatever it is - if (!object) { - object = layout.items.selectedObject; + /** + * Draws a transition between two activities. + */ + addTransition : function(fromActivity, toActivity, redraw, id, uiid, branchData) { + // if a child activity was detected, use the parent activity as the target + if (toActivity.parentActivity && toActivity.parentActivity instanceof DecorationDefs.Container){ + toActivity = toActivity.parentActivity; } + if (fromActivity.parentActivity && fromActivity.parentActivity instanceof DecorationDefs.Container){ + fromActivity = fromActivity.parentActivity; + } + // no transitions to/from support activities + if (toActivity instanceof ActivityDefs.FloatingActivity + || fromActivity instanceof ActivityDefs.FloatingActivity){ + return; + } - if (object) { - if (object.items.selectEffect) { - // different effects for different types of objects - if (object instanceof DecorationLib.Region) { - object.items.shape.attr({ - 'stroke' : 'black', - 'stroke-dasharray' : '' - }); - object.items.fitButton.hide(); - object.items.resizeButton.hide(); - - var childActivities = DecorationLib.getChildActivities(object.items.shape); - $.each(childActivities, function(){ - ActivityLib.removeSelectEffect(this); - }); - } else if (object instanceof ActivityLib.Transition) { - // just redraw the transition, it's easier - object.draw(); - } else { - object.items.selectEffect.remove(); + // only converge points are allowed to have few inbound transitions + if (!redraw + && toActivity.transitions.to.length > 0 + && !(toActivity instanceof ActivityDefs.BranchingEdgeActivity && !toActivity.isStart)) { + alert(LABELS.TRANSITION_TO_EXISTS_ERROR); + return; + } + + // user chose to create outbound transition from an activity that already has one + if (!redraw + && fromActivity.transitions.from.length > 0 + && !(fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart) + && !(toActivity instanceof ActivityDefs.BranchingEdgeActivity && toActivity.isStart)) { + if (confirm(LABELS.BRANCHING_CREATE_CONFIRM)) { + ActivityLib.addBranching(fromActivity, toActivity); + } + return; + } + + // branchData can be either an existing branch or a title for the new branch + var branch = branchData && branchData instanceof ActivityDefs.BranchActivity ? branchData : null, + transition = null; + // remove the existing transition + $.each(fromActivity.transitions.from, function(index) { + if (this.toActivity == toActivity) { + id = this.id; + uiid = this.uiid; + transition = this; + if (!branch){ + branch = this.branch; } - object.items.selectEffect = null; + + return false; } - - // no selected activity = no properties dialog - layout.items.propertiesDialog.dialog('close'); - layout.items.selectedObject = null; + }); + + if (!branch && fromActivity instanceof ActivityDefs.BranchingEdgeActivity && fromActivity.isStart) { + // if a title was provided, try to find the branch based on this information + $.each(fromActivity.branchingActivity.branches, function(){ + if (branchData == this.title) { + branch = this; + return false; + } + }); + if (!branch) { + // create a new branch + branch = new ActivityDefs.BranchActivity(null, null, branchData, fromActivity.branchingActivity, false); + } } + + + if (transition) { + ActivityLib.removeTransition(transition, redraw); + } + + // finally add the new transition + transition = new ActivityDefs.Transition(id, uiid, fromActivity, toActivity, + branch ? branch.title : null); + + if (branch) { + // set the corresponding branch (again) + branch.transitionFrom = transition; + transition.branch = branch; + fromActivity.branchingActivity.branches.push(branch); + if (fromActivity.branchingActivity.branches.length == 1) { + branch.defaultBranch = true; + } + } + + GeneralLib.setModified(true); + return transition; }, + - /** - * Adds visual grouping effect on an activity. + * Calculates start, middle and end points of a line between two activities. */ - addGroupingEffect : function(activity) { - if (!activity.items.groupingEffect) { - var shape = activity.items.shape, - activityBox = shape.getBBox(); + findTransitionPoints : function(fromActivity, toActivity) { + var fromActivityBox = fromActivity.items.shape.getBBox(), + toActivityBox = toActivity.items.shape.getBBox(), - activity.items.groupingEffect = paper.rect( - activityBox.x + 5, - activityBox.y + 5, - activityBox.width, - activityBox.height) - .attr({ - 'fill' : shape.attr('fill') - }); - - shape.toFront(); - activity.items.push(activity.items.groupingEffect); - - // this is needed, for some reason, otherwise the activity can not be selected - HandlerLib.resetCanvasMode(true); + // find points in the middle of each activity + points = { + 'startX' : fromActivityBox.x + fromActivityBox.width / 2, + 'startY' : fromActivityBox.y + fromActivityBox.height / 2, + 'endX' : toActivityBox.x + toActivityBox.width / 2, + 'endY' : toActivityBox.y + toActivityBox.height / 2 + }, + + // find intersection points of the temporary transition + tempTransition = Raphael.parsePathString(Raphael.format( + 'M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)), + fromIntersect = Raphael.pathIntersection(tempTransition, fromActivity.items.shape.attr('path')), + toIntersect = Raphael.pathIntersection(tempTransition, toActivity.items.shape.attr('path')); + + // find points on borders of activities, if they exist + if (fromIntersect.length > 0) { + points.startX = fromIntersect[0].x; + points.startY = fromIntersect[0].y; } + if (toIntersect.length > 0) { + points.endX = toIntersect[0].x; + points.endY = toIntersect[0].y; + } + // middle point of the transition + points.middleX = points.startX + (points.endX - points.startX)/2; + points.middleY = points.startY + (points.endY - points.startY)/2; + + return points; }, /** * Drop the dragged activity on the canvas. */ dropActivity : function(activity, x, y) { - if (!(activity instanceof ActivityLib.OptionalActivity || activity instanceof ActivityLib.FloatingActivity)) { + if (!(activity instanceof ActivityDefs.OptionalActivity || activity instanceof ActivityDefs.FloatingActivity)) { // check if it was removed from an Optional or Floating Activity - if (activity.parentActivity && activity.parentActivity instanceof DecorationLib.Container) { - var childActivities = DecorationLib.getChildActivities(activity.parentActivity.items.shape); - if ($.inArray(activity, childActivities) == -1) { + if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { + var childActivityDefs = DecorationLib.getChildActivityDefs(activity.parentActivity.items.shape); + if ($.inArray(activity, childActivityDefs) == -1) { activity.parentActivity.draw(); ActivityLib.redrawTransitions(activity.parentActivity); activity.parentActivity = null; @@ -978,15 +928,15 @@ ? layout.floatingActivity : null; if (!container) { $.each(layout.activities, function(){ - if (this instanceof ActivityLib.OptionalActivity + if (this instanceof ActivityDefs.OptionalActivity && Raphael.isPointInsideBBox(this.items.getBBox(),x,y)) { container = this; return false; } }); } if (container) { - if ($.inArray(activity, container.childActivities) == -1) { + if ($.inArray(activity, container.childActivityDefs) == -1) { $.each(activity.transitions.from, function(){ ActivityLib.removeTransition(this); }); @@ -997,8 +947,8 @@ // for properties dialog to reload ActivityLib.removeSelectEffect(container); - container.childActivities.push(activity); - container.draw(null, null, null, null, childActivities); + container.childActivityDefs.push(activity); + container.draw(null, null, null, null, childActivityDefs); ActivityLib.redrawTransitions(container); } } @@ -1011,22 +961,10 @@ this.draw(); }); - setModified(true); + GeneralLib.setModified(true); }, - - redrawTransitions : function(activity) { - if (activity.transitions) { - $.each(activity.transitions.from.slice(), function(){ - ActivityLib.addTransition(activity, this.toActivity, true); - }); - $.each(activity.transitions.to.slice(), function(){ - ActivityLib.addTransition(this.fromActivity, activity, true); - }); - } - }, - - + /** * Open separate window with activity authoring on double click. */ @@ -1059,50 +997,209 @@ window.open(activity.authorURL, 'activityAuthoring' + activity.id, "HEIGHT=800,WIDTH=1024,resizable=yes,scrollbars=yes,status=false," + "menubar=no,toolbar=no"); - setModified(true); + GeneralLib.setModified(true); } }, /** - * Calculates start, middle and end points of a line between two activities. + * Draw each of activity's inboud and outbound transitions again. */ - findTransitionPoints : function(fromActivity, toActivity) { - var fromActivityBox = fromActivity.items.shape.getBBox(), - toActivityBox = toActivity.items.shape.getBBox(), + redrawTransitions : function(activity) { + if (activity.transitions) { + $.each(activity.transitions.from.slice(), function(){ + ActivityLib.addTransition(activity, this.toActivity, true); + }); + $.each(activity.transitions.to.slice(), function(){ + ActivityLib.addTransition(this.fromActivity, activity, true); + }); + } + }, + + + /** + * Deletes the given activity. + */ + removeActivity : function(activity, forceRemove) { + var coreActivity = activity.branchingActivity || this; + if (!forceRemove && activity instanceof ActivityDefs.BranchingEdgeActivity){ + // user removes one of the branching edges, so remove the whole activity + if (confirm(LABELS.REMOVE_ACTIVITY_CONFIRM)){ + var otherEdge = activity.isStart ? coreActivity.end + : coreActivity.start; + ActivityLib.removeActivity(otherEdge, true); + } else { + return; + } + } + + if (activity instanceof ActivityDefs.FloatingActivity) { + layout.floatingActivity = null; + // re-enable the button, as the only possible Floating Activity is gone now + $('#floatingActivityButton').attr('disabled', null) + .css('opacity', 1); + } else { + // remove the transitions + // need to use slice() to copy the array as it gets modified in removeTransition() + $.each(activity.transitions.from.slice(), function() { + ActivityLib.removeTransition(this); + }); + $.each(activity.transitions.to.slice(), function() { + ActivityLib.removeTransition(this); + }); - // find points in the middle of each activity - points = { - 'startX' : fromActivityBox.x + fromActivityBox.width / 2, - 'startY' : fromActivityBox.y + fromActivityBox.height / 2, - 'endX' : toActivityBox.x + toActivityBox.width / 2, - 'endY' : toActivityBox.y + toActivityBox.height / 2 - }, + // remove the activity from reference tables + layout.activities.splice(layout.activities.indexOf(activity), 1); + if (layout.copiedActivity = activity) { + layout.copiedActivity = null; + } - // find intersection points of the temporary transition - tempTransition = Raphael.parsePathString(Raphael.format( - 'M {0} {1} L {2} {3}', points.startX, points.startY, points.endX, points.endY)), - fromIntersect = Raphael.pathIntersection(tempTransition, fromActivity.items.shape.attr('path')), - toIntersect = Raphael.pathIntersection(tempTransition, toActivity.items.shape.attr('path')); + // find references of this activity as grouping or input + $.each(layout.activities, function(){ + if (activity == coreActivity.grouping) { + coreActivity.grouping = null; + this.draw(); + } else if (activity == coreActivity.input) { + coreActivity.input = null; + } + }); + } - // find points on borders of activities, if they exist - if (fromIntersect.length > 0) { - points.startX = fromIntersect[0].x; - points.startY = fromIntersect[0].y; + // remove the activity from parent activity + if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container) { + activity.parentActivity.childActivityDefs.splice(activity.parentActivity.childActivityDefs.indexOf(activity), 1); } - if (toIntersect.length > 0) { - points.endX = toIntersect[0].x; - points.endY = toIntersect[0].y; + + // remove child activities + if (activity instanceof DecorationDefs.Container) { + $.each(activity.childActivityDefs, function(){ + ActivityLib.removeActivity(this); + }); } - // middle point of the transition - points.middleX = points.startX + (points.endX - points.startX)/2; - points.middleY = points.startY + (points.endY - points.startY)/2; - return points; + // visually remove the activity + activity.items.remove(); }, /** + * Deselects an activity/transition/annotation + */ + removeSelectEffect : function(object) { + // remove the effect from the given object or the selected one, whatever it is + if (!object) { + object = layout.selectedObject; + } + + if (object) { + if (object.items.selectEffect) { + // different effects for different types of objects + if (object instanceof DecorationDefs.Region) { + object.items.shape.attr({ + 'stroke' : 'black', + 'stroke-dasharray' : '' + }); + object.items.fitButton.hide(); + object.items.resizeButton.hide(); + + var childActivityDefs = DecorationLib.getChildActivityDefs(object.items.shape); + $.each(childActivityDefs, function(){ + ActivityLib.removeSelectEffect(this); + }); + } else if (object instanceof ActivityDefs.Transition) { + // just redraw the transition, it's easier + object.draw(); + } else { + object.items.selectEffect.remove(); + } + object.items.selectEffect = null; + } + + // no selected activity = no properties dialog + layout.propertiesDialog.dialog('close'); + layout.selectedObject = null; + } + }, + + + /** + * Removes the given transition. + */ + removeTransition : function(transition, redraw) { + // find the transition and remove it + var transitions = transition.fromActivity.transitions.from; + transitions.splice(transitions.indexOf(transition), 1); + transitions = transition.toActivity.transitions.to; + transitions.splice(transitions.indexOf(transition), 1); + + if (transition.branch) { + // remove corresponding branch + var branches = transition.branch.branchingActivity.branches; + branches.splice(branches.indexOf(transition.branch), 1); + + if (transition.branch.defaultBranch && branches.length > 0) { + // reset the first branch as the default one + branches[0].defaultBranch = true; + } + } + + // redraw means that the transition will be drawn again in just a moment + // so do not do any structural changes + if (!redraw){ + // remove grouping or input references if chain was broken by the removed transition + $.each(layout.activities, function(){ + var coreActivity = this.branchingActivity || this; + if (coreActivity.grouping || coreActivity.input) { + var candidate = this.branchingActivity ? coreActivity.start : this, + groupingFound = false, + inputFound = false; + do { + if (candidate.transitions && candidate.transitions.to.length > 0) { + candidate = candidate.transitions.to[0].fromActivity; + } else if (candidate.branchingActivity && !candidate.isStart) { + candidate = candidate.branchingActivity.start; + } else if (!candidate.branchingActivity && candidate.parentActivity) { + candidate = candidate.parentActivity; + } else { + candidate = null; + } + + if (coreActivity.grouping == candidate) { + groupingFound = true; + } + if (coreActivity.input == candidate) { + inputFound = true; + } + } while (candidate != null); + + if (!groupingFound) { + coreActivity.grouping = null; + this.draw(); + } + if (!inputFound) { + coreActivity.input = null; + } + } + }); + } + + transition.items.remove(); + GeneralLib.setModified(true); + }, + + + /** + * Reduce length of activity's title so it fits in its SVG shape. + */ + shortenActivityTitle : function(title) { + if (title.length > 18) { + title = title.substring(0, 17) + '...'; + } + return title; + }, + + + /** * Crawles through branches setting their lengths and finding the longest one. */ updateBranchesLength : function(branchingActivity) { @@ -1111,7 +1208,7 @@ // include the first activity var branchLength = 1, activity = this.transitionFrom.toActivity; - if (activity instanceof ActivityLib.BranchingEdgeActivity + if (activity instanceof ActivityDefs.BranchingEdgeActivity && branchingActivity == activity.branchingActivity){ // branch with no activities return true; @@ -1120,7 +1217,7 @@ while (activity.transitions.from.length > 0) { activity = activity.transitions.from[0].toActivity; // check if reached the end of branch - if (activity instanceof ActivityLib.BranchingEdgeActivity) { + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { break; } else { branchLength++; @@ -1133,25 +1230,5 @@ }); branchingActivity.longestBranchLength = longestBranchLength; - }, - - - /** - * Reduce length of activity's title so it fits in its SVG shape. - */ - shortenActivityTitle : function(title) { - if (title.length > 18) { - title = title.substring(0, 17) + '...'; - } - return title; - }, - - - /** - * Get real coordinates on paper, based on event coordinates. - */ - translateEventOnCanvas : function(event) { - return [event.pageX + canvas.scrollLeft() - canvas.offset().left, - event.pageY + canvas.scrollTop() - canvas.offset().top]; } }; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringDecoration.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringDecoration.js,v diff -u -r1.7 -r1.8 --- lams_central/web/includes/javascript/authoring/authoringDecoration.js 27 May 2014 11:54:41 -0000 1.7 +++ lams_central/web/includes/javascript/authoring/authoringDecoration.js 2 Jun 2014 07:27:16 -0000 1.8 @@ -1,136 +1,184 @@ /** - * This file contains methods for Decoration manipulation on canvas. + * This file contains methods for Decoration (annotations, containers) manipulation on canvas. */ -var DecorationLib = { + +/** + * Stores different Decoration types structures. + */ +var DecorationDefs = { + /** + * Abstract class for Region, Optional and Floating ActivityDefs. + */ + Container : function(id, uiid, title) { + this.id = +id || null; + this.uiid = +uiid || (layout.ld ? ++layout.ld.maxUIID : null); + this.title = title; + this.childActivityDefs = []; - /** - * Abstract class for Region, Optional and Floating Activities. - */ - Container : function(id, uiid, title) { - this.id = +id || null; - this.uiid = +uiid || (layout.ld ? ++layout.ld.maxUIID : null); - this.title = title; - this.childActivities = []; - - this.drawContainer = DecorationLib.methods.container.draw; - this.fit = DecorationLib.methods.container.fit; - }, + this.drawContainer = DecorationDefs.methods.container.draw; + this.fit = DecorationDefs.methods.container.fit; + }, + + + /** + * Constructor for label annotation. + */ + Label : function(id, uiid, x, y, title){ + this.id = +id || null; + this.uiid = +uiid || ++layout.ld.maxUIID; + // set a default title, if none provided + this.title = title || LABELS.DEFAULT_ANNOTATION_LABEL_TITLE; - /** - * Constructor for region annotation. - */ - Region : function(id, uiid, x, y, x2, y2, title, color) { - DecorationLib.Container.call(this, id, uiid, title); - // we don't use it for region - this.childActivities = null; - - this.draw = DecorationLib.methods.region.draw; - this.loadPropertiesDialogContent = PropertyLib.regionProperties; - - this.draw(x, y, x2, y2, color); - }, + this.draw = DecorationDefs.methods.label.draw; + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.labelProperties; + } - /** - * Constructor for label annotation. - */ - Label : function(id, uiid, x, y, title){ - this.id = +id || null; - this.uiid = +uiid || ++layout.ld.maxUIID; - // set a default title, if none provided - this.title = title || LABELS.DEFAULT_ANNOTATION_LABEL_TITLE; - - this.draw = DecorationLib.methods.label.draw; - this.loadPropertiesDialogContent = PropertyLib.labelProperties; - - this.draw(x, y); - }, + this.draw(x, y); + }, + + + /** + * Constructor for region annotation. + */ + Region : function(id, uiid, x, y, x2, y2, title, color) { + DecorationDefs.Container.call(this, id, uiid, title); + // we don't use it for region + this.childActivityDefs = null; + this.draw = DecorationDefs.methods.region.draw; - methods : { - container : { - - draw : function(x, y, x2, y2, color){ - // check for new coordinates or just take them from the existing shape - var box = this.items ? this.items.shape.getBBox() : null, - x = x ? x : box.x, - y = y ? y : box.y, - // take into account minimal size of rectangle - x2 = x2 ? Math.max(x2, x + 20) : x + box.width, - y2 = y2 ? Math.max(y2, y + 20) : y + box.height, - color = color ? color : this.items.shape.attr('fill'); + if (!isReadOnlyMode){ + this.loadPropertiesDialogContent = PropertyDefs.regionProperties; + } - if (box) { - this.items.remove(); + this.draw(x, y, x2, y2, color); + }, + + + methods : { + container : { + + draw : function(x, y, x2, y2, color){ + // check for new coordinates or just take them from the existing shape + var box = this.items ? this.items.shape.getBBox() : null, + x = x ? x : box.x, + y = y ? y : box.y, + // take into account minimal size of rectangle + x2 = x2 ? Math.max(x2, x + layout.conf.regionEmptyWidth) : x + box.width, + y2 = y2 ? Math.max(y2, y + layout.conf.regionEmptyHeight) : y + box.height, + color = color ? color : this.items.shape.attr('fill'); + + if (box) { + this.items.remove(); + } + + // the label + this.items = paper.set(); + if (this.title) { + var label = paper.text(x + 7, y + 10, this.title) + .attr(layout.defaultTextAttributes) + .attr('text-anchor', 'start') + .toBack(); + if (!isReadOnlyMode){ + label.attr('cursor', 'pointer'); } + this.items.push(label); - // the label - this.items = paper.set(); - if (this.title) { - var label = paper.text(x + 7, y + 10, this.title) - .attr({ - 'text-anchor' : 'start', - 'cursor' : 'pointer' - }) - .toBack(); - this.items.push(label); - - // make sure title fits - x2 = Math.max(x2, label.getBBox().x2 + 5); - } - - // the rectangle - this.items.shape = paper.path('M {0} {1} L {2} {1} L {2} {3} L {0} {3} z', x, y, x2, y2) - .attr({ - 'fill' : color, - 'cursor' : 'pointer' - }) - .toBack(); - this.items.push(this.items.shape); - - this.items.mousedown(HandlerLib.containerMousedownHandler) + // make sure title fits + x2 = Math.max(x2, label.getBBox().x2 + 5); + } + + // the rectangle + this.items.shape = paper.path('M {0} {1} L {2} {1} L {2} {3} L {0} {3} z', x, y, x2, y2) + .attr('fill', color) + .toBack(); + this.items.push(this.items.shape); + + if (!isReadOnlyMode){ + this.items.shape.attr('cursor', 'pointer'); + this.items.mousedown(HandlerDecorationLib.containerMousedownHandler) .click(HandlerLib.itemClickHandler); - }, - - /** - * Adjust the annotation so it envelops its child activities and nothing more. - */ - fit : function() { - var childActivities = DecorationLib.getChildActivities(this.items.shape); - if (childActivities.length == 0) { - return; - } - - ActivityLib.removeSelectEffect(this); - - var allElements = paper.set(); - $.each(childActivities, function(){ - allElements.push(this.items.shape); - }); - // big rectangle enveloping all child activities - var box = allElements.getBBox(); - - // add some padding - this.draw(box.x - 20, box.y - 20, box.x2 + 20, box.y2 + 20); } - }, + }, + /** - * Region methods + * Adjust the annotation so it envelops its child activities and nothing more. */ - region : { - draw : function(x, y, x2, y2, color){ - this.drawContainer(x, y, x2, y2, color); - - var box = this.items.shape.getBBox(); - + fit : function() { + var childActivityDefs = DecorationLib.getChildActivityDefs(this.items.shape); + if (childActivityDefs.length == 0) { + return; + } + + ActivityLib.removeSelectEffect(this); + + var allElements = paper.set(); + $.each(childActivityDefs, function(){ + allElements.push(this.items.shape); + }); + // big rectangle enveloping all child activities + var box = allElements.getBBox(); + + // add some padding + this.draw(box.x - layout.conf.containerActivityPadding, + box.y - layout.conf.containerActivityPadding, + box.x2 + layout.conf.containerActivityPadding, + box.y2 + layout.conf.containerActivityPadding); + } + }, + + + /** + * Label methods + */ + label : { + draw : function(x, y) { + var x = x ? x : this.items.shape.getBBox().x, + // the Y coordinate has to be adjusted; + // it is not perfect and not really cross-browser compliant... + y = y ? y : this.items.shape.getBBox().y + 6; + + if (this.items) { + this.items.remove(); + } + + this.items = paper.set(); + this.items.shape = paper.text(x, y, this.title) + .attr(layout.defaultTextAttributes) + .attr('text-anchor', 'start'); + if (!isReadOnlyMode){ + this.items.shape.attr('cursor', 'pointer'); + this.items.shape.mousedown(HandlerDecorationLib.labelMousedownHandler) + .click(HandlerLib.itemClickHandler); + } + + this.items.push(this.items.shape); + + this.items.data('parentObject', this); + } + }, + + + /** + * Region methods + */ + region : { + draw : function(x, y, x2, y2, color){ + this.drawContainer(x, y, x2, y2, color); + + var box = this.items.shape.getBBox(); + + if (!isReadOnlyMode){ this.items.fitButton = paper.circle(box.x2 - 10, box.y + 10, 5) .attr({ 'stroke' : null, 'fill' : 'blue', 'cursor' : 'pointer', - 'title' : 'Fit' + 'title' : LABELS.REGION_FIT_BUTTON_TOOLTIP }) .click(function(event){ event.stopImmediatePropagation(); @@ -143,108 +191,96 @@ this.items.resizeButton = paper.path(Raphael.format('M {0} {1} v {2} h -{2} z', box.x2, box.y2 - 15, 15)) - .attr({ - 'stroke' : null, - 'fill' : 'blue', - 'cursor' : 'se-resize' - }) - .mousedown(HandlerLib.resizeRegionStartHandler) - .hide(); - this.items.push(this.items.resizeButton); + .attr({ + 'stroke' : null, + 'fill' : 'blue', + 'cursor' : 'se-resize' + }); - this.items.data('parentObject', this); + this.items.resizeButton.mousedown(HandlerDecorationLib.resizeRegionStartHandler) + .hide(); + + this.items.push(this.items.resizeButton); } - }, - - - /** - * Label methods - */ - label : { - draw : function(x, y) { - var x = x ? x : this.items.shape.getBBox().x, - // the Y coordinate has to be adjusted; - // it is not perfect and not really cross-browser compliant... - y = y ? y : this.items.shape.getBBox().y + 6; - - if (this.items) { - this.items.remove(); - } - - this.items = paper.set(); - this.items.shape = paper.text(x, y, this.title) - .attr({ - 'text-anchor' : 'start', - 'cursor' : 'pointer' - }) - .mousedown(HandlerLib.labelMousedownHandler) - .click(HandlerLib.itemClickHandler); - this.items.push(this.items.shape); - - this.items.data('parentObject', this); - } + + this.items.data('parentObject', this); } - }, + } + } +}, + + + +/** + * Contains utility methods for Decoration manipulation. + */ +DecorationLib = { + + /** + * Adds a string on the canvas + */ + addLabel : function(x, y, title) { + var label = new DecorationDefs.Label(null, null, x, y, title); + layout.labels.push(label); + GeneralLib.setModified(true); + return label; + }, - - addRegion : function(x, y, x2, y2, title, color) { - var region = new DecorationLib.Region(null, null, - x, y, x2, y2, title, color ? color : layout.colors.annotation); - layout.regions.push(region); - setModified(true); - return region; - }, - - - removeRegion : function(region) { - layout.regions.splice(layout.regions.indexOf(region), 1); - region.items.remove(); - setModified(true); - }, - - - /** - * Get activities enveloped by given container - */ - getChildActivities : function(shape){ - var result = []; - $.each(layout.activities, function(){ - if (shape != this.items.shape) { - var activityBox = this.items.shape.getBBox(), - shapeBox = shape.getBBox(); - - if (Raphael.isPointInsideBBox(shapeBox,activityBox.x, activityBox.y) - && Raphael.isPointInsideBBox(shapeBox, activityBox.x2, activityBox.y2)) { - result.push(this); - } + + /** + * Adds a coloured rectange on the canvas + */ + addRegion : function(x, y, x2, y2, title, color) { + var region = new DecorationDefs.Region(null, null, + x, y, x2, y2, title, color ? color : layout.colors.annotation); + layout.regions.push(region); + GeneralLib.setModified(true); + return region; + }, + + + /** + * Get activities enveloped by given container + */ + getChildActivityDefs : function(shape){ + var result = []; + $.each(layout.activities, function(){ + if (shape != this.items.shape) { + var activityBox = this.items.shape.getBBox(), + shapeBox = shape.getBBox(); + + if (Raphael.isPointInsideBBox(shapeBox,activityBox.x, activityBox.y) + && Raphael.isPointInsideBBox(shapeBox, activityBox.x2, activityBox.y2)) { + result.push(this); } - }); - - var parentObject = shape.data('parentObject'); - if (parentObject && !(parentObject instanceof DecorationLib.Region)) { - parentObject.childActivities = result; } - return result; - }, + }); - - addLabel : function(x, y, title) { - var label = new DecorationLib.Label(null, null, x, y, title); - layout.labels.push(label); - setModified(true); - return label; - }, - - - removeLabel : function(label) { - layout.labels.splice(layout.labels.indexOf(label), 1); - label.items.remove(); - setModified(true); + var parentObject = shape.data('parentObject'); + // store the result in the shape's object + if (parentObject && !(parentObject instanceof DecorationDefs.Region)) { + parentObject.childActivityDefs = result; } + return result; + }, + + + removeLabel : function(label) { + layout.labels.splice(layout.labels.indexOf(label), 1); + label.items.remove(); + GeneralLib.setModified(true); + }, + + + removeRegion : function(region) { + layout.regions.splice(layout.regions.indexOf(region), 1); + region.items.remove(); + GeneralLib.setModified(true); + } }; // set prototype hierarchy -DecorationLib.Region.prototype = new DecorationLib.Container; -ActivityLib.ParallelActivity.prototype = new DecorationLib.Container; -ActivityLib.OptionalActivity.prototype = new DecorationLib.Container; -ActivityLib.FloatingActivity.prototype = new DecorationLib.Container; \ No newline at end of file +DecorationDefs.Region.prototype = new DecorationDefs.Container; +ActivityDefs.ParallelActivity.prototype = new DecorationDefs.Container; +ActivityDefs.OptionalActivity.prototype = new DecorationDefs.Container; +ActivityDefs.FloatingActivity.prototype = new DecorationDefs.Container; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringGeneral.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringGeneral.js,v diff -u -r1.42 -r1.43 --- lams_central/web/includes/javascript/authoring/authoringGeneral.js 27 May 2014 11:54:41 -0000 1.42 +++ lams_central/web/includes/javascript/authoring/authoringGeneral.js 2 Jun 2014 07:27:16 -0000 1.43 @@ -1,1861 +1,2247 @@ /** - * This file contains global methods for Authoring. + * This file contains main methods for Authoring. */ -// few publicly visible variables -var paper = null, - canvas = null, - nameValidator = /^[^<>^*@%$]*$/i, - numberValidator = /^[\d\.]+$/, - -// configuration and storage of various elements - layout = { - 'drawMode' : false, - 'modified' : false, - // 'isZoomed' : false, - 'activities' : null, - 'floatingActivity' : null, - 'regions' : null, - 'labels' : null, - 'items' : { - 'bin' : null, - - 'selectedObject' : null, - 'copiedActivity' : null, - - 'propertiesDialog' : null, - 'infoDialog' : null, - 'groupNamingDialog' : null, - 'groupsToBranchesMappingDialog' : null - }, - 'dialogs' : [], - 'toolMetadata': { - 'grouping' : { - 'iconPath' : '../images/grouping.gif' - }, - 'gate' : { - 'iconPath' : '../images/stop.gif' - } - }, - 'conf' : { - 'propertiesDialogDimOpacity' : 0.3, - 'propertiesDialogDimThreshold' : 100, - 'propertiesDialogDimThrottle' : 100, - 'dragStartThreshold' : 300, - 'arrangeHorizontalSpace' : 200, - 'arrangeVerticalSpace' : 100, - 'arrangeHorizontalPadding' : 35, - 'arrangeVerticalPadding' : 50 - }, - 'defs' : { - 'activity' : ' h 125 v 50 h -125 z', - 'branchingEdgeStart' : ' a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0', - 'branchingEdgeEnd' : ' a 8 8 0 1 0 16 0 a 8 8 0 1 0 -16 0', - 'transArrow' : ' l 10 15 a 25 25 0 0 0 -20 0 z', - 'gate' : ' l-8 8 v14 l8 8 h14 l8 -8 v-14 l-8 -8 z', - 'bin' : 'M 0 0 h -50 l 10 50 h 30 z' - }, - 'colors' : { - 'activity' : ['','#d0defd','#fffccb','#ece9f7','#fdf1d3','#FFFFFF','#e9f9c0'], - 'offlineActivity' : 'black', - 'activityText' : 'black', - 'offlineActivityText' : 'white', - 'grouping' : '#A9C8FD', - 'gate' : 'red', - 'gateText' : 'white', - 'branchingEdgeStart' : 'green', - 'branchingEdgeEnd' : 'red', - 'branchingEdgeMatch' : 'blue', - 'transition' : 'rgb(119,126,157)', - 'binActive' : 'red', - 'selectEffect' : 'blue', - 'annotation' : '#FFFF00', - 'annotationPalette' : ['FFFF00', '00FFFF', '8A2BE2', '7FFF00', '6495ED', - 'FFF8DC', 'FF8C00', '00BFFF', 'DCDCDC', 'ADD8E6', '20B2AA', - 'B0C4DE', 'FFE4E1', 'FF4500', 'EE82EE'], - 'optionalActivity' : 'rgb(194,213,254)' - } -}; - /** - * Initialises the whole Authoring window. + * Initialises each part of the Authoring window. */ $(document).ready(function() { canvas = $('#canvas'); - initLayout(); - initTemplates(); - PropertyLib.init(); - MenuLib.init(); + GeneralInitLib.initTemplates(); + if (!isReadOnlyMode) { + // in read-only mode (SVG generator), some parts are not necessary and not loaded + GeneralInitLib.initLayout(); + PropertyLib.init(); + MenuLib.init(); + } - MenuLib.newLearningDesign(true, true); - layout.ld.contentFolderID = initContentFolderID; - - window.onbeforeunload = function(){ - if (layout.modified && - (layout.activities.length > 0 - || layout.regions.length > 0 - || layout.labels.length > 0 - || layout.floatingActivity)) { - return LABELS.NAVIGATE_AWAY_CONFIRM; - } - }; + GeneralLib.newLearningDesign(true, true); + if (!isReadOnlyMode) { + layout.ld.contentFolderID = initContentFolderID; + } + if (initLearningDesignID) { + GeneralLib.openLearningDesign(+initLearningDesignID); + } }); /** - * Draw boxes with Tool Activity templates. + * A few publicly visible variables . */ -function initTemplates(){ - $('.template').each(function(){ - var learningLibraryID = +$(this).attr('learningLibraryId'), - activityCategoryID = +$(this).attr('activityCategoryId'), - parallelChildActivities = null; - - if (activityCategoryID == 5) { - var childToolIds = $(this).attr('childToolIds').split(','); - $.each(childToolIds, function(){ - var childToolId = this.trim(); - if (childToolId) { - parallelChildActivity = $('.template[toolId=' + childToolId + ']'); - if (parallelChildActivities) { - parallelChildActivities = parallelChildActivities.add(parallelChildActivity); - } else { - parallelChildActivities = parallelChildActivity; - } - } - }); - } - - var iconPath = $('img', this).attr('src'); - // register tool properties so they are later easily accessible - layout.toolMetadata[learningLibraryID] = { - 'iconPath' : iconPath, - 'supportsOutputs' : $(this).attr('supportsOutputs'), - 'activityCategoryID' : activityCategoryID, - 'parallelChildActivities' : parallelChildActivities - }; - - - if (/\.svg$/i.test(iconPath)){ - $.ajax({ - url : iconPath, - dataType : 'text', - success : function(response) { - layout.toolMetadata[learningLibraryID].iconCode = response.substring(response.indexOf(' 12){ - toolName.css('padding-top', '8px'); - } - - // allow dragging a template to canvas - $(this).draggable({ - 'containment' : '#authoringTable', - 'revert' : 'invalid', - 'distance' : 20, - 'scroll' : false, - 'scope' : 'template', - 'helper' : function(event){ - // build a simple helper - var helper = $(this).clone().css({ - 'width' : '135px', - 'border' : 'thin black solid', - 'z-index' : 1, - 'cursor' : 'move' - }); - - // Chrome does not render name of the tool correctly in the helper - // so just remove it - helper.children('div').remove(); - return helper; + + // The Raphael Paper object +var paper = null, + // container for the paper + canvas = null, + + // configuration and storage of various elements + layout = { + // draw mode prevents some handlers (click, mouseover etc.) from triggering + 'drawMode' : false, + // should the sequence be saved before exiting? + 'modified' : false, + // list of all dialogs, so they can be easily closed all at once + 'dialogs' : [], + // icons for special activities + 'toolMetadata': { + 'gate' : { + 'iconPath' : '../images/stop.gif' + }, + 'grouping' : { + 'iconPath' : '../images/grouping.gif' } - }); - }); + }, + // graphics contants + 'conf' : { + 'arrangeHorizontalSpace' : 200, + 'arrangeVerticalSpace' : 100, + 'arrangeHorizontalPadding' : 35, + 'arrangeVerticalPadding' : 50, + + 'dragStartThreshold' : 300, + + 'propertiesDialogDimOpacity' : 0.3, + 'propertiesDialogDimThreshold' : 100, + 'propertiesDialogDimThrottle' : 100, + + 'defaultGroupingGroupCount' : 2, + 'defaultGroupingLearnerCount' : 1, + + 'containerActivityEmptyWidth' : 50, + 'containerActivityEmptyHeight' : 70, + 'containerActivityPadding' : 20, + 'containerActivityChildrenPadding' : 10, + 'regionEmptyWidth' : 20, + 'regionEmptyHeight' : 20, + + 'groupingEffectPadding' : 5, + 'selectEffectPadding' : 7 + }, + + 'colors' : { + // each activity type has its own colour + 'activity' : ['','#d0defd','#fffccb','#ece9f7','#fdf1d3','#FFFFFF','#e9f9c0'], + 'activityText' : 'black', + // default region colour + 'annotation' : '#FFFF00', + // region colours to choose from + 'annotationPalette' : ['FFFF00', '00FFFF', '8A2BE2', '7FFF00', '6495ED', + 'FFF8DC', 'FF8C00', '00BFFF', 'DCDCDC', 'ADD8E6', '20B2AA', + 'B0C4DE', 'FFE4E1', 'FF4500', 'EE82EE'], + // when mouse hover rubbish bin + 'binActive' : 'red', + 'branchingEdgeStart' : 'green', + 'branchingEdgeEnd' : 'red', + // highlight branching edges on mouse hover + 'branchingEdgeMatch' : 'blue', + 'gate' : 'red', + 'gateText' : 'white', + 'grouping' : '#A9C8FD', + 'optionalActivity' : 'rgb(194,213,254)', + // dashed border around a selected activity + 'selectEffect' : 'blue', + 'transition' : 'rgb(119,126,157)' + }, - // allow dropping templates to canvas - canvas.droppable({ - 'tolerance' : 'touch', - 'scope' : 'template', - 'drop' : function (event, draggable) { - // get rid of helper div - $(draggable.helper).remove(); - - // calculate the position and create an instance of the tool activity - var learningLibraryID = +draggable.draggable.attr('learningLibraryId'), - toolID = +draggable.draggable.attr('toolId'), - activityCategoryID = +draggable.draggable.attr('activityCategoryId'), - x = draggable.offset.left + canvas.scrollLeft() - canvas.offset().left, - y = draggable.offset.top + canvas.scrollTop() - canvas.offset().top, - label = $('div', draggable.draggable).text(), - activity = null, - translatedEvent = ActivityLib.translateEventOnCanvas(event), - eventX = translatedEvent[0], - eventY = translatedEvent[1]; - - if (activityCategoryID == 5) { - // construct child activities out of previously referenced HTML templates - var childActivities = []; - layout.toolMetadata[learningLibraryID].parallelChildActivities.each(function(){ - var childLearningLibraryID = +$(this).attr('learningLibraryId'), - childToolID = +$(this).attr('toolId'), - toolLabel = $('div', this).text(), - childActivity = new ActivityLib.ToolActivity(null, null, null, - childToolID, childLearningLibraryID, null, x, y, toolLabel); - - layout.activities.push(childActivity); - childActivities.push(childActivity); - }); - - activity = new ActivityLib.ParallelActivity(null, null, learningLibraryID, x, y, label, childActivities); - } else { - activity = new ActivityLib.ToolActivity(null, null, null, toolID, learningLibraryID, null, x, y, label); - } - - layout.activities.push(activity); - HandlerLib.dropObject(activity); - ActivityLib.dropActivity(activity, eventX, eventY); - } - }); -} + 'defaultTextAttributes' : { + 'font-size' : 10, + 'font' : 'sans-serif' + } +}, /** - * Initialises various Authoring widgets. + * Contains methods for Authoring window initialisation which are run only once at the beginning */ -function initLayout() { - $('#tabs').tabs({ - 'activate' : function(){ - // close all opened dialogs - $.each(layout.dialogs, function() { - this.dialog('close'); - }); - } - }); - $('#tabs .ui-tabs-nav, #tabs .ui-tabs-nav > *') - .removeClass( "ui-corner-all ui-corner-top" ) - .addClass( "ui-corner-bottom" ); - $('#tabs .ui-tabs-nav').appendTo('#tabs'); - - // buttons shared by both load and save dialogs - var sharedButtons = [ - { - 'text' : 'Cancel', - 'tabIndex' : -1, - 'click' : function() { - $(this).dialog('close'); - } - }, - - // creates a new folder - { - 'class' : 'leftDialogButton', - 'text' : LABELS.NEW_FOLDER_BUTTON, - 'tabIndex' : -1, - 'click' : function(){ - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - // hightlighted sequence/folder in the tree - ldNode = tree.getHighlightedNode(); - if (!ldNode) { - return; - } - - var title = prompt(LABELS.NEW_FOLDER_TITLE_PROMPT); - // skip if no name was provided - if (!title) { - return; - } - if (!nameValidator.test(title)) { - alert(LABELS.TITLE_VALIDATION_ERROR); - return; - } +GeneralInitLib = { + /** + * Draw boxes with Tool Activity templates in the panel on the left. + */ + initTemplates : function(){ + $('.template').each(function(){ + var learningLibraryID = +$(this).attr('learningLibraryId'), + activityCategoryID = +$(this).attr('activityCategoryId'), + parallelChildActivityDefs = null; - var parentFolder = ldNode.data.learningDesignId ? ldNode.parent : ldNode; - $.each(parentFolder.children, function(){ - if (this.label == title) { - alert(LABELS.FOLDER_EXISTS_ERROR); - title = null; - return false; - } - }); - if (!title) { - return; + if (activityCategoryID == 5) { + var childToolIds = $(this).attr('childToolIds').split(','); + $.each(childToolIds, function(){ + var childToolId = this.trim(); + if (childToolId) { + parallelChildActivity = $('.template[toolId=' + childToolId + ']'); + if (parallelChildActivityDefs) { + parallelChildActivityDefs = parallelChildActivityDefs.add(parallelChildActivity); + } else { + parallelChildActivityDefs = parallelChildActivity; + } + } + }); } + var iconPath = $('img', this).attr('src'); + // register tool properties so they are later easily accessible + layout.toolMetadata[learningLibraryID] = { + 'iconPath' : iconPath, + 'supportsOutputs' : $(this).attr('supportsOutputs'), + 'activityCategoryID' : activityCategoryID, + 'parallelChildActivityDefs' : parallelChildActivityDefs + }; - $.ajax({ - cache : false, - async : false, - url : LAMS_URL + "workspace.do", - dataType : 'text', - data : { - 'method' : 'createFolderForFlash', - 'name' : title, - 'parentFolderID' : parentFolder.data.folderID - }, - success : function(response) { - // still process WDDX packet, to be changed when we get rid of Flash Authoring - var messageIndex = response.indexOf("folderID'>") + 18, - folderID = response.substring(messageIndex, response.indexOf('<', messageIndex)); - - if (!numberValidator.test(folderID)) { - // error - var messageIndex = response.indexOf("messageValue'>") + 22, - message = response.substring(messageIndex, response.indexOf('<', messageIndex)); - alert(message); - return; + + if (/\.svg$/i.test(iconPath)){ + $.ajax({ + url : iconPath, + dataType : 'text', + success : function(response) { + layout.toolMetadata[learningLibraryID].iconCode = response.substring(response.indexOf(' 12){ + toolName.css('padding-top', '8px'); } - }); - } - }, - - // copy sequence or folder - { - 'class' : 'leftDialogButton', - 'text' : LABELS.COPY_BUTTON, - 'tabIndex' : -1, - 'click' : function(){ - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - // hightlighted sequence/folder in the tree - ldNode = tree.getHighlightedNode(), - isFolder = ldNode && !ldNode.data.learningDesignId; - if (!ldNode) { - return; - } - - dialog.dialog('option', 'copiedResource', { - 'isFolder' : isFolder, - 'resourceID' : isFolder ? ldNode.data.folderID : ldNode.data.learningDesignId - }); - } - }, - - // pastes sequence or folder - { - 'class' : 'leftDialogButton', - 'text' : LABELS.PASTE_BUTTON, - 'tabIndex' : -1, - 'click' : function(){ - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - // hightlighted sequence/folder in the tree - ldNode = tree.getHighlightedNode(), - folderNode = ldNode ? (ldNode.data.learningDesignId ? ldNode.parent : ldNode) : null, - copiedResource = dialog.dialog('option','copiedResource'); - if (!folderNode || !copiedResource) { - return; - } - - $.ajax({ - cache : false, - async : false, - url : LAMS_URL + "workspace.do", - dataType : 'text', - data : { - 'method' : 'copyResource', - 'targetFolderID' : folderNode.data.folderID, - 'resourceID' : copiedResource.resourceID, - 'resourceType' : copiedResource.isFolder ? 'Folder' : 'LearningDesign' - }, - success : function(response) { - // still process WDDX packed, to be changed when we get rid of Flash Authoring - var messageIndex = response.indexOf("messageValue'>") + 22, - message = response.substring(messageIndex, response.indexOf('<', messageIndex)); - if (!numberValidator.test(message)) { - // error - alert(message); - return; + // allow dragging a template to canvas + $(this).draggable({ + 'containment' : '#authoringTable', + 'revert' : 'invalid', + 'distance' : 20, + 'scroll' : false, + 'scope' : 'template', + 'helper' : function(event){ + // build a simple helper + var helper = $(this).clone().css({ + 'width' : '135px', + 'border' : 'thin black solid', + 'z-index' : 1, + 'cursor' : 'move' + }); + + // Chrome does not render name of the tool correctly in the helper + // so just remove it + helper.children('div').remove(); + return helper; } - - tree.removeChildren(folderNode); - folderNode.expand(); - } + }); + } + }); + + if (!isReadOnlyMode) { + // allow dropping templates to canvas + canvas.droppable({ + 'tolerance' : 'touch', + 'scope' : 'template', + 'drop' : function (event, draggable) { + // get rid of helper div + $(draggable.helper).remove(); + + // calculate the position and create an instance of the tool activity + var learningLibraryID = +draggable.draggable.attr('learningLibraryId'), + toolID = +draggable.draggable.attr('toolId'), + activityCategoryID = +draggable.draggable.attr('activityCategoryId'), + x = draggable.offset.left + canvas.scrollLeft() - canvas.offset().left, + y = draggable.offset.top + canvas.scrollTop() - canvas.offset().top, + label = $('div', draggable.draggable).text(), + activity = null, + translatedEvent = GeneralLib.translateEventOnCanvas(event), + eventX = translatedEvent[0], + eventY = translatedEvent[1]; + + if (activityCategoryID == 5) { + // construct child activities out of previously referenced HTML templates + var childActivityDefs = []; + layout.toolMetadata[learningLibraryID].parallelChildActivityDefs.each(function(){ + var childLearningLibraryID = +$(this).attr('learningLibraryId'), + childToolID = +$(this).attr('toolId'), + toolLabel = $('div', this).text(), + childActivity = new ActivityDefs.ToolActivity(null, null, null, + childToolID, childLearningLibraryID, null, x, y, toolLabel); + + layout.activities.push(childActivity); + childActivityDefs.push(childActivity); + }); + + activity = new ActivityDefs.ParallelActivity(null, null, learningLibraryID, x, y, label, childActivityDefs); + } else { + activity = new ActivityDefs.ToolActivity(null, null, null, toolID, learningLibraryID, null, x, y, label); + } + + layout.activities.push(activity); + HandlerLib.dropObject(activity); + ActivityLib.dropActivity(activity, eventX, eventY); + } }); } }, - - // removes sequence or folder - { - 'class' : 'leftDialogButton', - 'text' : LABELS.DELETE_BUTTON, - 'tabIndex' : -1, - 'click' : function(){ - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - // hightlighted sequence/folder in the tree - ldNode = tree.getHighlightedNode(), - isFolder = ldNode && !ldNode.data.learningDesignId; - if (!ldNode || !confirm(LABELS.DELETE_NODE_CONFIRM + (isFolder ? LABELS.FOLDER : LABELS.SEQUENCE) + '?')) { - return; - } - - $.ajax({ - cache : false, - async : false, - url : LAMS_URL + "workspace.do", - dataType : 'text', - data : { - 'method' : 'deleteResource', - 'resourceID' : isFolder? ldNode.data.folderID : ldNode.data.learningDesignId, - 'resourceType' : isFolder ? 'Folder' : 'LearningDesign' - }, - success : function(response) { - // still process WDDX packed, to be changed when we get rid of Flash Authoring - var messageIndex = response.indexOf("messageValue'>") + 22, - message = response.substring(messageIndex, response.indexOf('<', messageIndex)); - if (!/^.*:\d+$/.test(message)) { - // error - alert(message); - return; + + + /** + * Initialises various Authoring widgets. + */ + initLayout : function(){ + // buttons shared by both load and save dialogs + var sharedButtons = [ + { + 'text' : 'Cancel', + 'tabIndex' : -1, + 'click' : function() { + $(this).dialog('close'); + } + }, + + // creates a new folder + { + 'class' : 'leftDialogButton', + 'text' : LABELS.NEW_FOLDER_BUTTON, + 'tabIndex' : -1, + 'click' : function(){ + var dialog = $(this), + tree = dialog.dialog('option', 'ldTree'), + // hightlighted sequence/folder in the tree + ldNode = tree.getHighlightedNode(); + if (!ldNode) { + return; + } + + var title = prompt(LABELS.NEW_FOLDER_TITLE_PROMPT); + // skip if no name was provided + if (!title) { + return; + } + if (!GeneralLib.nameValidator.test(title)) { + alert(LABELS.TITLE_VALIDATION_ERROR); + return; + } + + var parentFolder = ldNode.data.learningDesignId ? ldNode.parent : ldNode; + $.each(parentFolder.children, function(){ + if (this.label == title) { + alert(LABELS.FOLDER_EXISTS_ERROR); + title = null; + return false; } - - var parentFolder = ldNode.parent; - tree.removeChildren(parentFolder); - parentFolder.expand(); + }); + if (!title) { + return; } - }); - } - }, - - - // renames sequence or folder - { - 'class' : 'leftDialogButton', - 'text' : LABELS.RENAME_BUTTON, - 'tabIndex' : -1, - 'click' : function(){ - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - // hightlighted sequence/folder in the tree - ldNode = tree.getHighlightedNode(), - isFolder = ldNode && !ldNode.data.learningDesignId; - if (!ldNode) { - return; - } - var title = prompt(LABELS.RENAME_TITLE_PROMPT + (isFolder ? LABELS.FOLDER : LABELS.SEQUENCE) - + ' "' + ldNode.label + '"'); - - // skip if no name or the same name was provided - if (!title || ldNode.label == title) { - return; + + + $.ajax({ + cache : false, + async : false, + url : LAMS_URL + "workspace.do", + dataType : 'text', + data : { + 'method' : 'createFolderForFlash', + 'name' : title, + 'parentFolderID' : parentFolder.data.folderID + }, + success : function(response) { + // still process WDDX packet, to be changed when we get rid of Flash Authoring + var messageIndex = response.indexOf("folderID'>") + 18, + folderID = response.substring(messageIndex, response.indexOf('<', messageIndex)); + + if (!GeneralLib.numberValidator.test(folderID)) { + // error + var messageIndex = response.indexOf("messageValue'>") + 22, + message = response.substring(messageIndex, response.indexOf('<', messageIndex)); + alert(message); + return; + } + + tree.removeChildren(parentFolder); + parentFolder.expand(); + } + }); } - if (!nameValidator.test(title)) { - alert(LABELS.TITLE_VALIDATION_ERROR); - return; - } - - $.each(ldNode.parent.children, function(){ - if (this.label == title && (isFolder == (this.data.folderID != null))) { - alert(isFolder ? LABELS.FOLDER_EXISTS_ERROR : LABELS.SEQUENCE_EXISTS_ERROR); - title = null; - return false; - } - }); - if (!title) { - return; + }, + + // copy sequence or folder + { + 'class' : 'leftDialogButton', + 'text' : LABELS.COPY_BUTTON, + 'tabIndex' : -1, + 'click' : function(){ + var dialog = $(this), + tree = dialog.dialog('option', 'ldTree'), + // hightlighted sequence/folder in the tree + ldNode = tree.getHighlightedNode(), + isFolder = ldNode && !ldNode.data.learningDesignId; + if (!ldNode) { + return; + } + + dialog.dialog('option', 'copiedResource', { + 'isFolder' : isFolder, + 'resourceID' : isFolder ? ldNode.data.folderID : ldNode.data.learningDesignId + }); } - - $.ajax({ - cache : false, - async : false, - url : LAMS_URL + "workspace.do", - dataType : 'text', - data : { - 'method' : 'renameResourceJSON', - 'name' : title, - 'resourceID' : isFolder? ldNode.data.folderID : ldNode.data.learningDesignId, - 'resourceType' : isFolder ? 'Folder' : 'LearningDesign' - }, - success : function(response) { - // still process WDDX packed, to be changed when we get rid of Flash Authoring - var messageIndex = response.indexOf("messageValue'>") + 22, - message = response.substring(messageIndex, response.indexOf('<', messageIndex)); - if (message != title) { - // error - alert(message); - return; + }, + + // pastes sequence or folder + { + 'class' : 'leftDialogButton', + 'text' : LABELS.PASTE_BUTTON, + 'tabIndex' : -1, + 'click' : function(){ + var dialog = $(this), + tree = dialog.dialog('option', 'ldTree'), + // hightlighted sequence/folder in the tree + ldNode = tree.getHighlightedNode(), + folderNode = ldNode ? (ldNode.data.learningDesignId ? ldNode.parent : ldNode) : null, + copiedResource = dialog.dialog('option','copiedResource'); + + if (!folderNode || !copiedResource) { + return; + } + + $.ajax({ + cache : false, + async : false, + url : LAMS_URL + "workspace.do", + dataType : 'text', + data : { + 'method' : 'copyResource', + 'targetFolderID' : folderNode.data.folderID, + 'resourceID' : copiedResource.resourceID, + 'resourceType' : copiedResource.isFolder ? 'Folder' : 'LearningDesign' + }, + success : function(response) { + // still process WDDX packed, to be changed when we get rid of Flash Authoring + var messageIndex = response.indexOf("messageValue'>") + 22, + message = response.substring(messageIndex, response.indexOf('<', messageIndex)); + if (!GeneralLib.numberValidator.test(message)) { + // error + alert(message); + return; + } + + tree.removeChildren(folderNode); + folderNode.expand(); } - if (isFolder) { - ldNode.label = title; - ldNode.getLabelEl().innerHTML = title; - } else { - // refresh all opened folders in the tree - var folders = tree.getRoot().children; - if (folders) { - $.each(folders, function(){ - var expanded = this.expanded; - tree.removeChildren(this); - if (expanded) { - this.expand(); - } - }); - } - - // fetch access list again - updateAccess(null, true); - } - } - }); - } - }], - - // initalise Learning Design load/save dialog - ldStoreDialog = $('#ldStoreDialog').dialog({ - 'autoOpen' : false, - 'position' : { - 'my' : 'left top', - 'at' : 'left top', - 'of' : 'body' + }); + } }, - 'resizable' : false, - 'width' : 1240, - 'height' : 785, - 'draggable' : false, - 'buttonsLoad' : sharedButtons.concat([ - { - 'id' : 'openLdStoreButton', - 'class' : 'defaultFocus', - 'text' : LABELS.OPEN_BUTTON, - 'click' : function() { - var dialog = $(this), - tree = dialog.dialog('option', 'ldTree'), - ldNode = tree.getHighlightedNode(), - learningDesignID = ldNode ? ldNode.data.learningDesignId : null; - - if (!learningDesignID) { - learningDesignID = +$('#ldStoreDialogAccessCell > div.selected').attr('learningDesignId'); - } - - // no LD was chosen - if (!learningDesignID) { - alert(LABELS.SEQUENCE_NOT_SELECTED_ERROR); - return; - } - - dialog.dialog('close'); - openLearningDesign(learningDesignID); + + // removes sequence or folder + { + 'class' : 'leftDialogButton', + 'text' : LABELS.DELETE_BUTTON, + 'tabIndex' : -1, + 'click' : function(){ + var dialog = $(this), + tree = dialog.dialog('option', 'ldTree'), + // hightlighted sequence/folder in the tree + ldNode = tree.getHighlightedNode(), + isFolder = ldNode && !ldNode.data.learningDesignId; + if (!ldNode || !confirm(LABELS.DELETE_NODE_CONFIRM + (isFolder ? LABELS.FOLDER : LABELS.SEQUENCE) + '?')) { + return; + } + + $.ajax({ + cache : false, + async : false, + url : LAMS_URL + "workspace.do", + dataType : 'text', + data : { + 'method' : 'deleteResource', + 'resourceID' : isFolder? ldNode.data.folderID : ldNode.data.learningDesignId, + 'resourceType' : isFolder ? 'Folder' : 'LearningDesign' + }, + success : function(response) { + // still process WDDX packed, to be changed when we get rid of Flash Authoring + var messageIndex = response.indexOf("messageValue'>") + 22, + message = response.substring(messageIndex, response.indexOf('<', messageIndex)); + if (!/^.*:\d+$/.test(message)) { + // error + alert(message); + return; } - } - ]), + + var parentFolder = ldNode.parent; + tree.removeChildren(parentFolder); + parentFolder.expand(); + } + }); + } + }, - 'buttonsSave' : sharedButtons.concat([ + + // renames sequence or folder + { + 'class' : 'leftDialogButton', + 'text' : LABELS.RENAME_BUTTON, + 'tabIndex' : -1, + 'click' : function(){ + var dialog = $(this), + tree = dialog.dialog('option', 'ldTree'), + // hightlighted sequence/folder in the tree + ldNode = tree.getHighlightedNode(), + isFolder = ldNode && !ldNode.data.learningDesignId; + if (!ldNode) { + return; + } + var title = prompt(LABELS.RENAME_TITLE_PROMPT + (isFolder ? LABELS.FOLDER : LABELS.SEQUENCE) + + ' "' + ldNode.label + '"'); + + // skip if no name or the same name was provided + if (!title || ldNode.label == title) { + return; + } + if (!GeneralLib.nameValidator.test(title)) { + alert(LABELS.TITLE_VALIDATION_ERROR); + return; + } + + $.each(ldNode.parent.children, function(){ + if (this.label == title && (isFolder == (this.data.folderID != null))) { + alert(isFolder ? LABELS.FOLDER_EXISTS_ERROR : LABELS.SEQUENCE_EXISTS_ERROR); + title = null; + return false; + } + }); + if (!title) { + return; + } + + $.ajax({ + cache : false, + async : false, + url : LAMS_URL + "workspace.do", + dataType : 'text', + data : { + 'method' : 'renameResourceJSON', + 'name' : title, + 'resourceID' : isFolder? ldNode.data.folderID : ldNode.data.learningDesignId, + 'resourceType' : isFolder ? 'Folder' : 'LearningDesign' + }, + success : function(response) { + // still process WDDX packed, to be changed when we get rid of Flash Authoring + var messageIndex = response.indexOf("messageValue'>") + 22, + message = response.substring(messageIndex, response.indexOf('<', messageIndex)); + if (message != title) { + // error + alert(message); + return; + } + if (isFolder) { + ldNode.label = title; + ldNode.getLabelEl().innerHTML = title; + } else { + // refresh all opened folders in the tree + var folders = tree.getRoot().children; + if (folders) { + $.each(folders, function(){ + var expanded = this.expanded; + tree.removeChildren(this); + if (expanded) { + this.expand(); + } + }); + } + + // fetch access list again + GeneralLib.updateAccess(null, true); + } + } + }); + } + }], + + // initalise Learning Design load/save dialog + ldStoreDialog = $('#ldStoreDialog').dialog({ + 'autoOpen' : false, + 'position' : { + 'my' : 'left top', + 'at' : 'left top', + 'of' : 'body' + }, + 'resizable' : false, + 'width' : 1240, + 'height' : 785, + 'draggable' : false, + 'buttonsLoad' : sharedButtons.concat([ { + 'id' : 'openLdStoreButton', 'class' : 'defaultFocus', - 'text' : LABELS.SAVE_BUTTON, - 'click' : function() { + 'text' : LABELS.OPEN_BUTTON, + 'click' : function() { var dialog = $(this), - container = dialog.closest('.ui-dialog'), - title = $('#ldStoreDialogNameField', container).val().trim(); - if (!title) { - alert(LABELS.SAVE_SEQUENCE_TITLE_PROMPT); - return; - } - - if (!nameValidator.test(title)) { - alert(LABELS.TITLE_VALIDATION_ERROR); - return; - } - - var learningDesignID = null, - folderNode = null, - folderID = null, tree = dialog.dialog('option', 'ldTree'), - node = tree.getHighlightedNode(); - if (node) { - // get folder from LD tree - folderNode = node.data.learningDesignId ? node.parent : node; - folderID = folderNode.data.folderID; - } else { - // get data from "recently used sequences" list - var selectedAccess = $('#ldStoreDialogAccessCell > div.selected'); - // if title was altered, do not consider this an overwrite - if (selectedAccess.length > 0 && title == selectedAccess.text()) { - learningDesignID = +selectedAccess.attr('learningDesignId'); - folderID = +selectedAccess.attr('folderID'); - - var folders = tree.getRoot().children; - if (folders) { - $.each(folders, function(){ - if (folderID == this.data.folderID) { - this.highlight(); - folderNode = this; - return false; - } - }); - } - } - } + ldNode = tree.getHighlightedNode(), + learningDesignID = ldNode ? ldNode.data.learningDesignId : null; - if (!folderID) { - // although an existing sequence can be highlighted - alert(LABELS.FOLDER_NOT_SELECTED_ERROR); - return; + if (!learningDesignID) { + learningDesignID = +$('#ldStoreDialogAccessCell > div.selected').attr('learningDesignId'); } - // if a node is highlighted but user modified the title, - // it is considered a new sequence - // otherwise check if there is no other sequence with the same name - if (folderNode && folderNode.children) { - $.each(folderNode.children, function(){ - if (this.label == title) { - this.highlight(); - learningDesignID = this.data.learningDesignId; - return false; - } - }); - } - if (learningDesignID - && !confirm(LABELS.SEQUENCE_OVERWRITE_CONFIRM)) { + // no LD was chosen + if (!learningDesignID) { + alert(LABELS.SEQUENCE_NOT_SELECTED_ERROR); return; } - - var result = saveLearningDesign(folderID, learningDesignID, title); - if (result) { - dialog.dialog('close'); - } + + dialog.dialog('close'); + GeneralLib.openLearningDesign(learningDesignID); } } - ]), - 'open' : function(){ - showLearningDesignThumbnail(); + ]), - $('#leftDialogButtonContainer').remove(); - var nameContainer = $('#ldStoreDialogNameContainer'), - leftButtonContainer = $('
    ').attr('id','leftDialogButtonContainer'); - $('input', nameContainer).val(null); - $(this).siblings('.ui-dialog-buttonpane').append(leftButtonContainer).append(nameContainer); - $('#ldStoreDialogNameField', nameContainer).focus(); + 'buttonsSave' : sharedButtons.concat([ + { + 'class' : 'defaultFocus', + 'text' : LABELS.SAVE_BUTTON, + 'click' : function() { + var dialog = $(this), + container = dialog.closest('.ui-dialog'), + title = $('#ldStoreDialogNameField', container).val().trim(); + if (!title) { + alert(LABELS.SAVE_SEQUENCE_TITLE_PROMPT); + return; + } + + if (!GeneralLib.nameValidator.test(title)) { + alert(LABELS.TITLE_VALIDATION_ERROR); + return; + } + + var learningDesignID = null, + folderNode = null, + folderID = null, + tree = dialog.dialog('option', 'ldTree'), + node = tree.getHighlightedNode(); + if (node) { + // get folder from LD tree + folderNode = node.data.learningDesignId ? node.parent : node; + folderID = folderNode.data.folderID; + } else { + // get data from "recently used sequences" list + var selectedAccess = $('#ldStoreDialogAccessCell > div.selected'); + // if title was altered, do not consider this an overwrite + if (selectedAccess.length > 0 && title == selectedAccess.text()) { + learningDesignID = +selectedAccess.attr('learningDesignId'); + folderID = +selectedAccess.attr('folderID'); + + var folders = tree.getRoot().children; + if (folders) { + $.each(folders, function(){ + if (folderID == this.data.folderID) { + this.highlight(); + folderNode = this; + return false; + } + }); + } + } + } + + if (!folderID) { + // although an existing sequence can be highlighted + alert(LABELS.FOLDER_NOT_SELECTED_ERROR); + return; + } + + // if a node is highlighted but user modified the title, + // it is considered a new sequence + // otherwise check if there is no other sequence with the same name + if (folderNode && folderNode.children) { + $.each(folderNode.children, function(){ + if (this.label == title) { + this.highlight(); + learningDesignID = this.data.learningDesignId; + return false; + } + }); + } + if (learningDesignID + && !confirm(LABELS.SEQUENCE_OVERWRITE_CONFIRM)) { + return; + } + var result = GeneralLib.saveLearningDesign(folderID, learningDesignID, title); + if (result) { + dialog.dialog('close'); + } + } + } + ]), + 'open' : function(){ + GeneralLib.showLearningDesignThumbnail(); + + $('#leftDialogButtonContainer').remove(); + var nameContainer = $('#ldStoreDialogNameContainer'), + leftButtonContainer = $('
    ').attr('id','leftDialogButtonContainer'); + $('input', nameContainer).val(null); + $(this).siblings('.ui-dialog-buttonpane').append(leftButtonContainer).append(nameContainer); + $('#ldStoreDialogNameField', nameContainer).focus(); + + $('.leftDialogButton') + .attr('disabled', 'disabled') + .button('option', 'disabled', true) + .appendTo(leftButtonContainer); + $('.defaultFocus').focus(); + + $(this).dialog('option', 'copiedResource', null); + } + }); + + layout.dialogs.push(ldStoreDialog); + + $('#ldScreenshotAuthor', ldStoreDialog).load(function(){ + // hide "loading" animation + $('#ldStoreDialog .ldChoiceDependentCanvasElement').css('display', 'none'); + // show the thumbnail + $(this).css('display', 'inline'); + }); + + // there should be no focus, just highlight + YAHOO.widget.TreeView.FOCUS_CLASS_NAME = null; + var tree = new YAHOO.widget.TreeView('ldStoreDialogTree'); + // store the tree in the dialog's data + ldStoreDialog.dialog('option', 'ldTree', tree); + // make folder contents load dynamically on open + tree.setDynamicLoad(function(node, callback){ + // load subfolder contents + var childNodeData = MenuLib.getFolderContents(node.data.folderID); + if (childNodeData) { + $.each(childNodeData, function(){ + // create and add a leaf + new YAHOO.widget.TextNode(this, node); + }); + } + + // required by YUI + callback(); + }); + tree.singleNodeHighlight = true; + tree.subscribe('clickEvent', function(event){ + var dialog = $(this.getEl()).closest('.ui-dialog'), + isSaveDialog = dialog.hasClass('ldStoreDialogSave'); + $('.leftDialogButton') - .attr('disabled', 'disabled') - .button('option', 'disabled', true) - .appendTo(leftButtonContainer); - $('.defaultFocus').focus(); + .attr('disabled', event.node.highlightState > 0 ? 'disabled' : null) + .button('option', 'disabled', event.node.highlightState > 0); + + if (!isSaveDialog && !event.node.data.learningDesignId){ + // it is a folder in load sequence dialog, hightlight but stop processing + return true; + } - $(this).dialog('option', 'copiedResource', null); + var learningDesignID = event.node.highlightState == 0 ? +event.node.data.learningDesignId : null, + title = isSaveDialog && learningDesignID ? event.node.label : null; + + GeneralLib.showLearningDesignThumbnail(learningDesignID, title); + }); + tree.subscribe('clickEvent', tree.onEventToggleHighlight); + + GeneralLib.updateAccess(initAccess); + + // initialise a small info dialog + layout.infoDialog = $('
    ').attr('id', 'infoDialog').dialog({ + 'autoOpen' : false, + 'width' : 290, + 'minHeight' : 'auto', + 'show' : 'fold', + 'hide' : 'fold', + 'draggable' : false, + 'dialogClass': 'dialog-no-title', + 'position' : { + my: "right top", + at: "right top+5px", + of: '#canvas' + } + }); + + layout.dialogs.push(layout.infoDialog); + + window.onbeforeunload = function(){ + if (layout.modified && + (layout.activities.length > 0 + || layout.regions.length > 0 + || layout.labels.length > 0 + || layout.floatingActivity)) { + return LABELS.NAVIGATE_AWAY_CONFIRM; + } + }; + } +}, + + + +/** + * Contains main methods for Authoring window management + */ +GeneralLib = { + // regex validators for checking user entered strings + nameValidator : /^[^<>^*@%$]*$/i, + numberValidator : /^[\d\.]+$/, + + + /** + * Sorts activities on canvas. + */ + arrangeActivities : function(){ + if ((layout.regions.length > 0 || layout.labels.length > 0) + && !isReadOnlyMode && !confirm(LABELS.ARRANGE_CONFIRM)) { + return; } - }); - - layout.dialogs.push(ldStoreDialog); - - $('#ldScreenshotAuthor', ldStoreDialog).load(function(){ - // hide "loading" animation - $('#ldStoreDialog .ldChoiceDependentCanvasElement').css('display', 'none'); - // show the thumbnail - $(this).css('display', 'inline'); - }); - - // there should be no focus, just highlight - YAHOO.widget.TreeView.FOCUS_CLASS_NAME = null; - var tree = new YAHOO.widget.TreeView('ldStoreDialogTree'); - // store the tree in the dialog's data - ldStoreDialog.dialog('option', 'ldTree', tree); - // make folder contents load dynamically on open - tree.setDynamicLoad(function(node, callback){ - // load subfolder contents - var childNodeData = MenuLib.getFolderContents(node.data.folderID); - if (childNodeData) { - $.each(childNodeData, function(){ - // create and add a leaf - new YAHOO.widget.TextNode(this, node); - }); + + if (layout.activities.length == 0) { + // no activities, nothing to do + return; } - // required by YUI - callback(); - }); - tree.singleNodeHighlight = true; - tree.subscribe('clickEvent', function(event){ - var dialog = $(this.getEl()).closest('.ui-dialog'), - isSaveDialog = dialog.hasClass('ldStoreDialogSave'); + if (!isReadOnlyMode) { + // just to refresh the state of canvas + HandlerLib.resetCanvasMode(true); + } + + // activities are arranged in a grid + var row = 0, + // for special cases when row needs to shifted more + forceRowY = null, + column = 0, + // check how many columns current paper can hold + maxColumns = Math.floor((paper.width - layout.conf.arrangeHorizontalPadding) + / layout.conf.arrangeHorizontalSpace), + // the initial max length of subsequences is limited by paper space + subsequenceMaxLength = maxColumns, + // a shallow copy of activities array without inner activities + activitiesCopy = [], + // just to speed up processing when there are only activities with no transitions left + onlyDetachedLeft = false; - $('.leftDialogButton') - .attr('disabled', event.node.highlightState > 0 ? 'disabled' : null) - .button('option', 'disabled', event.node.highlightState > 0); - - if (!isSaveDialog && !event.node.data.learningDesignId){ - // it is a folder in load sequence dialog, hightlight but stop processing - return true; + $.each(layout.activities, function(){ + if (!this.parentActivity || !(this.parentActivity instanceof DecorationDefs.Container)){ + activitiesCopy.push(this); + } + }); + + // branches will not be broken into few rows; if they are long, paper will be resized + // find the longes branch to find the new paper size + $.each(layout.activities, function(){ + if (this instanceof ActivityDefs.BranchingEdgeActivity && this.isStart) { + // refresh branching metadata + ActivityLib.updateBranchesLength(this.branchingActivity); + // add start and end edges to the result + var longestBranchLength = this.branchingActivity.longestBranchLength + 2; + if (longestBranchLength > subsequenceMaxLength) { + subsequenceMaxLength = longestBranchLength; + } + } + }); + + // check how many child activities are in Floating Activity, if any + if (layout.floatingActivity && layout.floatingActivity.childActivityDefs.length > subsequenceMaxLength) { + subsequenceMaxLength = childActivityDefs.length; } - var learningDesignID = event.node.highlightState == 0 ? +event.node.data.learningDesignId : null, - title = isSaveDialog && learningDesignID ? event.node.label : null; + // resize paper horizontally, if needed + if (subsequenceMaxLength > maxColumns) { + maxColumns = subsequenceMaxLength; + GeneralLib.resizePaper(layout.conf.arrangeHorizontalPadding + + maxColumns * layout.conf.arrangeHorizontalSpace, + paper.height); + } - showLearningDesignThumbnail(learningDesignID, title); - }); - tree.subscribe('clickEvent', tree.onEventToggleHighlight); - - updateAccess(initAccess); + // main loop; iterate over whatever is left in the array + while (activitiesCopy.length > 0) { + // look for activities with transitions first; detached ones go to the very end + var activity = null; + if (!onlyDetachedLeft) { + $.each(activitiesCopy, function(){ + if (this.transitions.to.length > 0) { + activity = this; + while (activity.transitions.to.length > 0) { + // check if previous activity was not drawn already + // it can happen for branching edges + var activityLookup = activity.transitions.to[0].fromActivity; + if (activitiesCopy.indexOf(activityLookup) == -1) { + break; + } + activity = activityLookup; + }; + return false; + } + }); + } - // initialise a small info dialog - layout.items.infoDialog = $('
    ').attr('id', 'infoDialog').dialog({ - 'autoOpen' : false, - 'width' : 290, - 'minHeight' : 'auto', - 'show' : 'fold', - 'hide' : 'fold', - 'draggable' : false, - 'dialogClass': 'dialog-no-title', - 'position' : { - my: "right top", - at: "right top+5px", - of: '#canvas' - } - }); + if (!activity) { + // no activities with transitions left, take first detached one + onlyDetachedLeft = true; + activity = activitiesCopy[0]; + } + + // markers for complex activity processing + var complex = null; + + // crawl through a sequence of activities + while (activity) { + if (activity instanceof ActivityDefs.BranchingEdgeActivity) { + if (activity.isStart) { + // draw branching edges straight away and remove them from normall processing + var branchingActivity = activity.branchingActivity, + start = branchingActivity.start, + end = branchingActivity.end, + complex = { + end : end + }, + // can the whole branching fit in current canvas width? + branchingFits = column + branchingActivity.longestBranchLength + 2 <= maxColumns; + if (!branchingFits) { + // start branching from the left side of canvas + row++; + if (forceRowY) { + while (forceRowY > layout.conf.arrangeVerticalPadding + 10 + row * layout.conf.arrangeVerticalSpace) { + row++; + } + forceRowY = null; + } + column = 0; + } + // store the column of converge point + end.column = column + branchingActivity.longestBranchLength + 1; + + complex.branchingRow = row + Math.floor(branchingActivity.branches.length / 2); + // edge points go to middle of rows with branches + var startX = layout.conf.arrangeHorizontalPadding + + column * layout.conf.arrangeHorizontalSpace + 54, + edgeY = layout.conf.arrangeVerticalPadding + + complex.branchingRow * layout.conf.arrangeVerticalSpace + 17, + endX = layout.conf.arrangeHorizontalPadding + + end.column * layout.conf.arrangeHorizontalSpace + 54; + + activitiesCopy.splice(activitiesCopy.indexOf(start), 1); + activitiesCopy.splice(activitiesCopy.indexOf(end), 1); + + // start point goes to very left, end goes wherever the longes branch ends + start.draw(startX, edgeY); + end.draw(endX, edgeY); - layout.dialogs.push(layout.items.infoDialog); -} + complex.branchingColumn = column; + column++; + $.each(branchingActivity.branches, function(){ + if (this.transitionFrom.toActivity == branchingActivity.end) { + complex.emptyBranch = this; + return false; + } + }); + + if (branchingActivity.branches.length > (complex.emptyBranch ? 1 : 0)) { + // set up branch drawing + // skip the first branch if it is the empty one + complex.branchIndex = + complex.emptyBranch == branchingActivity.branches[0] ? 1 : 0; + // next activity for normal processing will be first one from the first branch + activity = branchingActivity.branches[complex.branchIndex].transitionFrom.toActivity; + continue; + } else { + // no branches, nothing to do, carry on with normal activity processing + activity = complex.end; + activity.column = null; + complex = null; + } + } + } else { + // it is a simple activity, so redraw it + var x = layout.conf.arrangeHorizontalPadding + column * layout.conf.arrangeHorizontalSpace, + y = layout.conf.arrangeVerticalPadding + row * layout.conf.arrangeVerticalSpace; + + if (activity instanceof ActivityDefs.GateActivity) { + // adjust placement for gate activity, so it's in the middle of its cell + x += 57; + y += 10; + } else if (activity instanceof ActivityDefs.OptionalActivity){ + x -= 20; + } + + activity.draw(x, y); + // remove the activity so we do not process it twice + activitiesCopy.splice(activitiesCopy.indexOf(activity), 1); + + // learn where a tall Optional Activity has its end + // and later start drawing activities lower than in the next row + if (activity instanceof DecorationDefs.Container && activity.childActivityDefs.length > 1) { + var activityEndY = activity.items.shape.getBBox().y2; + if (!forceRowY || activityEndY > forceRowY) { + forceRowY = activityEndY; + } + } + } + + // find the next row and column + column = (column + 1) % maxColumns; + if (column == 0) { + row++; + // if an Optional Activity forced next activities to be drawn lower than usual + if (forceRowY) { + while (forceRowY > layout.conf.arrangeVerticalPadding + 10 + row * layout.conf.arrangeVerticalSpace) { + row++; + } + forceRowY = null; + } + } + + // does the activity has further activities? + if (activity.transitions.from.length > 0) { + activity = activity.transitions.from[0].toActivity; + } else { + activity = null; + } + + if (complex && (!activity || activity == complex.end)) { + // end of branch + complex.branchIndex++; -/** - * Replace current canvas contents with the loaded sequence. - */ -function openLearningDesign(learningDesignID) { - // get LD details - $.ajax({ - cache : false, - url : LAMS_URL + "authoring/author.do", - dataType : 'json', - data : { - 'method' : 'openLearningDesign', - 'learningDesignID': learningDesignID - }, - success : function(response) { - if (!response) { - alert(LABELS.SEQUENCE_LOAD_ERROR); - return; + var branches = complex.end.branchingActivity.branches; + if (branches.length > complex.branchIndex) { + if (branches[complex.branchIndex] == complex.emptyBranch) { + // skip the empty branch + complex.branchIndex++; + } + } + + if (branches.length > complex.branchIndex) { + // there is another branch to process + activity = branches[complex.branchIndex].transitionFrom.toActivity; + // go back to left side of canvas and draw next branch + row++; + if (complex.emptyBranch && complex.branchingRow == row) { + row++; + } + + column = complex.branchingColumn + 1; + } else { + + // no more branches, return to normal activity processing + activity = complex.end.transitions.from.length == 0 ? + null : complex.end.transitions.from[0].toActivity; + column = (complex.end.column + 1) % maxColumns; + if (column == 0) { + row++; + } + if (row < complex.branchingRow) { + row = complex.branchingRow; + } + complex.end.column = null; + complex = null; + } + } + + if (!activity || activitiesCopy.indexOf(activity) == -1) { + // next activity was already processed, so stop crawling + break; + } + }; + }; + + if (layout.floatingActivity) { + if (column > 0) { + // if the last activity was in the last column, there is no need for another row + row++; + column = 0; } + var x = layout.conf.arrangeHorizontalPadding, + y = layout.conf.arrangeVerticalPadding - 30 + row * layout.conf.arrangeVerticalSpace; - var ld = response.ld; - - // remove existing activities - MenuLib.newLearningDesign(true, true); + layout.floatingActivity.draw(x, y); + } + + // redraw transitions one by one + $.each(layout.activities, function(){ + $.each(this.transitions.from.slice(), function(){ + ActivityLib.addTransition(this.fromActivity, this.toActivity, true); + }); + }); + + GeneralLib.resizePaper(); + GeneralLib.setModified(true); + }, + + + /** + * Escapes HTML tags to prevent XSS injection. + */ + escapeHtml : function(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + }, + + + /** + * Removes existing activities and prepares canvas for a new sequence. + */ + newLearningDesign : function(force, soft){ + // force means that user should not be asked for confirmation. + if (!force && (layout.activities.length > 0 + || layout.regions.length > 0 + || layout.labels.length > 0 + || layout.floatingActivity) + && !confirm(LABELS.CLEAR_CANVAS_CONFIRM)){ + return; + } + + $('#ldDescriptionDetails').slideUp(); + + // soft means that data is manually reset, instead of simply reloading the page. + if (soft) { layout.ld = { - 'learningDesignID' : learningDesignID, - 'folderID' : ld.workspaceFolderID, - 'contentFolderID' : ld.contentFolderID, - 'title' : ld.title, - 'maxUIID' : 0 + 'maxUIID' : 0 }; + layout.activities = []; + layout.regions = []; + layout.labels = []; + layout.floatingActivity = null; - $('#ldDescriptionFieldTitle').html(escapeHtml(ld.title)); - CKEDITOR.instances['ldDescriptionFieldDescription'].setData(ld.description); + if (!isReadOnlyMode) { + $('#ldDescriptionFieldTitle').text('Untitled'); + CKEDITOR.instances['ldDescriptionFieldDescription'].setData(null); + GeneralLib.setModified(true); + } - var arrangeNeeded = false, - branchToBranching = {}, - // helper for finding last activity in a branch - branchToActivities = {}; + if (paper) { + paper.clear(); + } else { + // need to set size right away for Chrome + paper = Raphael('canvas', canvas.width() - 5, canvas.height() - 5); + } - // create visual representation of the loaded activities - $.each(ld.activities, function() { - var activityData = this, - activity = null; + GeneralLib.resizePaper(); + } else { + // do not prompt again + window.onbeforeunload = null; + // full window reload so new content ID gets generated + document.location.href = LAMS_URL + 'authoring/author.do?method=openAuthoring'; + } + }, + + + /** + * Replace current canvas contents with the loaded sequence. + */ + openLearningDesign : function(learningDesignID) { + // get LD details + $.ajax({ + cache : false, + url : LAMS_URL + "authoring/author.do", + dataType : 'json', + data : { + 'method' : 'openLearningDesign', + 'learningDesignID': learningDesignID + }, + success : function(response) { + if (!response) { + if (!isReadOnlyMode) { + alert(LABELS.SEQUENCE_LOAD_ERROR); + } + return; + } - // find max uiid so newly created elements have, unique ones - if (activityData.activityUIID && layout.ld.maxUIID < activityData.activityUIID) { - layout.ld.maxUIID = activityData.activityUIID; + var ld = response.ld; + + // remove existing activities + GeneralLib.newLearningDesign(true, true); + layout.ld = { + 'learningDesignID' : learningDesignID, + 'folderID' : ld.workspaceFolderID, + 'contentFolderID' : ld.contentFolderID, + 'title' : ld.title, + 'maxUIID' : 0 + }; + + if (!isReadOnlyMode) { + $('#ldDescriptionFieldTitle').html(GeneralLib.escapeHtml(ld.title)); + CKEDITOR.instances['ldDescriptionFieldDescription'].setData(ld.description); } - - switch(activityData.activityTypeID) { - // Tool Activity - case 1 : - activity = new ActivityLib.ToolActivity( - activityData.activityID, - activityData.activityUIID, - activityData.toolContentID, - activityData.toolID, - activityData.learningLibraryID, - LAMS_URL + activityData.authoringURL - + '?toolContentID=' + activityData.toolContentID - + '&contentFolderID=' + layout.ld.contentFolderID, - activityData.xCoord ? activityData.xCoord : 1, - activityData.yCoord ? activityData.yCoord : 1, - activityData.activityTitle, - activityData.supportsOutputs); - // for later reference - activityData.activity = activity; - break; + + var arrangeNeeded = false, + branchToBranching = {}, + // helper for finding last activity in a branch + branchToActivityDefs = {}; + + // create visual representation of the loaded activities + $.each(ld.activities, function() { + var activityData = this, + activity = null; - // Grouping Activity - case 2 : - // find extra metadata for this grouping - $.each(ld.groupings, function(){ - var groupingData = this; - if (groupingData.groupingID == activityData.createGroupingID) { - var groupingType = null, - groups = []; - - // translate backend grouping type to human readable for better understanding - switch(groupingData.groupingTypeID) { - case 2: - groupingType = 'monitor'; - break; - case 4: - groupingType = 'learner'; - break; - default: - groupingType = 'random'; - break; - }; - // get groups names - $.each(groupingData.groups, function(){ - groups.push({ - 'name' : this.groupName, - 'id' : this.groupID, - 'uiid' : this.groupUIID - }); - }); - - // sort groups by asceding UIID - groups.sort(function(a,b) { - return a.uiid - b.uiid; - }); - - activity = new ActivityLib.GroupingActivity( - activityData.activityID, - activityData.activityUIID, - activityData.xCoord, - activityData.yCoord, - activityData.activityTitle, - groupingData.groupingID, - groupingData.groupingUIID, - groupingType, - groupingData.learnersPerGroup ? 'learners' : 'groups', - groupingData.numberOfGroups, - groupingData.learnersPerGroup, - groupingData.equalNumberOfLearnersPerGroup, - groupingData.viewStudentsBeforeSelection, - groups); - return false; - } - }) - break; - - // Gate Activity - case 3: var gateType = 'sync'; - case 4: var gateType = gateType || 'schedule'; - case 5: var gateType = gateType || 'permission'; - case 14: - var gateType = gateType || 'condition'; - activity = new ActivityLib.GateActivity( - activityData.activityID, - activityData.activityUIID, - activityData.xCoord, - activityData.yCoord, - activityData.activityTitle, - activityData.description, - gateType, - activityData.gateStartTimeOffset, - activityData.gateActivityCompletionBased); - break; + // find max uiid so newly created elements have, unique ones + if (activityData.activityUIID && layout.ld.maxUIID < activityData.activityUIID) { + layout.ld.maxUIID = activityData.activityUIID; + } - // Parallel Activity - case 6: - activity = new ActivityLib.ParallelActivity( - activityData.activityID, - activityData.activityUIID, - activityData.learningLibraryID, - activityData.xCoord, - activityData.yCoord, - activityData.activityTitle); - break; + switch(activityData.activityTypeID) { + // Tool Activity + case 1 : + activity = new ActivityDefs.ToolActivity( + activityData.activityID, + activityData.activityUIID, + activityData.toolContentID, + activityData.toolID, + activityData.learningLibraryID, + LAMS_URL + activityData.authoringURL + + '?toolContentID=' + activityData.toolContentID + + '&contentFolderID=' + layout.ld.contentFolderID, + activityData.xCoord ? activityData.xCoord : 1, + activityData.yCoord ? activityData.yCoord : 1, + activityData.activityTitle); + // for later reference + activityData.activity = activity; + break; - // Optional Activity - case 7: - activity = new ActivityLib.OptionalActivity( + // Grouping Activity + case 2 : + // find extra metadata for this grouping + $.each(ld.groupings, function(){ + var groupingData = this; + if (groupingData.groupingID == activityData.createGroupingID) { + var groupingType = null, + groups = []; + + // translate backend grouping type to human readable for better understanding + switch(groupingData.groupingTypeID) { + case 2: + groupingType = 'monitor'; + break; + case 4: + groupingType = 'learner'; + break; + default: + groupingType = 'random'; + break; + }; + // get groups names + $.each(groupingData.groups, function(){ + groups.push({ + 'name' : this.groupName, + 'id' : this.groupID, + 'uiid' : this.groupUIID + }); + }); + + // sort groups by asceding UIID + groups.sort(function(a,b) { + return a.uiid - b.uiid; + }); + + activity = new ActivityDefs.GroupingActivity( + activityData.activityID, + activityData.activityUIID, + activityData.xCoord, + activityData.yCoord, + activityData.activityTitle, + groupingData.groupingID, + groupingData.groupingUIID, + groupingType, + groupingData.learnersPerGroup ? 'learners' : 'groups', + groupingData.numberOfGroups, + groupingData.learnersPerGroup, + groupingData.equalNumberOfLearnersPerGroup, + groupingData.viewStudentsBeforeSelection, + groups); + return false; + } + }) + break; + + // Gate Activity + case 3: var gateType = 'sync'; + case 4: var gateType = gateType || 'schedule'; + case 5: var gateType = gateType || 'permission'; + case 14: + var gateType = gateType || 'condition'; + activity = new ActivityDefs.GateActivity( activityData.activityID, activityData.activityUIID, activityData.xCoord, activityData.yCoord, activityData.activityTitle, - activityData.minOptions, - activityData.maxOptions); - break; - - // Branching Activity - case 10: var branchingType = 'chosen'; - case 11: var branchingType = branchingType || 'group'; - case 12: var branchingType = branchingType || 'tool'; - case 13: - // draw both edge points straight away and mark the whole canvas for auto reaarange - arrangeNeeded = true; - var branchingType = branchingType || 'optional', - branchingEdge = new ActivityLib.BranchingEdgeActivity(activityData.activityID, + activityData.description, + gateType, + activityData.gateStartTimeOffset, + activityData.gateActivityCompletionBased); + break; + + // Parallel Activity + case 6: + activity = new ActivityDefs.ParallelActivity( + activityData.activityID, activityData.activityUIID, - 0, 0, + activityData.learningLibraryID, + activityData.xCoord, + activityData.yCoord, + activityData.activityTitle); + break; + + // Optional Activity + case 7: + activity = new ActivityDefs.OptionalActivity( + activityData.activityID, + activityData.activityUIID, + activityData.xCoord, + activityData.yCoord, activityData.activityTitle, - branchingType); - layout.activities.push(branchingEdge); - // for later reference - activityData.activity = branchingEdge; - - branchingEdge = new ActivityLib.BranchingEdgeActivity( - null, null, 0, 0, null, null, branchingEdge.branchingActivity); - layout.activities.push(branchingEdge); - - branchingEdge.branchingActivity.defaultActivityUIID = activityData.defaultActivityUIID; - if (branchingType == 'optional'){ - branchingEdge.branchingActivity.minOptions = activityData.minOptions; - branchingEdge.branchingActivity.maxOptions = activityData.maxOptions; - } + activityData.minOptions, + activityData.maxOptions); + break; + + // Branching Activity + case 10: var branchingType = 'chosen'; + case 11: var branchingType = branchingType || 'group'; + case 12: var branchingType = branchingType || 'tool'; + case 13: + // draw both edge points straight away and mark the whole canvas for auto reaarange + arrangeNeeded = true; + var branchingType = branchingType || 'optional', + branchingEdge = new ActivityDefs.BranchingEdgeActivity(activityData.activityID, + activityData.activityUIID, + 0, 0, + activityData.activityTitle, + branchingType); + layout.activities.push(branchingEdge); + // for later reference + activityData.activity = branchingEdge; + + branchingEdge = new ActivityDefs.BranchingEdgeActivity( + null, null, 0, 0, null, null, branchingEdge.branchingActivity); + layout.activities.push(branchingEdge); + + branchingEdge.branchingActivity.defaultActivityUIID = activityData.defaultActivityUIID; + if (branchingType == 'optional'){ + branchingEdge.branchingActivity.minOptions = activityData.minOptions; + branchingEdge.branchingActivity.maxOptions = activityData.maxOptions; + } - break; + break; + + // Branch (i.e. Sequence Activity) + case 8: + var branches = branchToBranching[activityData.parentActivityID]; + if (!branches) { + branches = branchToBranching[activityData.parentActivityID] = []; + } + branches.push(new ActivityDefs.BranchActivity(activityData.activityID, + activityData.activityUIID, + activityData.activityTitle)); + + var branchData = branchToActivityDefs[activityData.activityID]; + if (!branchData) { + branchData = branchToActivityDefs[activityData.activityID] = {}; + } + branchData.stopAfterActivity = activityData.stopAfterActivity; + + break; + + // Support (Floating) activity + case 15: + activity = new ActivityDefs.FloatingActivity( + activityData.activityID, + activityData.activityUIID, + activityData.xCoord, + activityData.yCoord); + break; + } - // Branch (i.e. Sequence Activity) - case 8: - var branches = branchToBranching[activityData.parentActivityID]; - if (!branches) { - branches = branchToBranching[activityData.parentActivityID] = []; + + if (!activity) { + // activity type not supported yet + return true; + } + + if (!(activity instanceof ActivityDefs.FloatingActivity)) { + layout.activities.push(activity); + } + + // store information about the branch the activity belongs to + if (activityData.parentActivityID) { + var branchData = branchToActivityDefs[activityData.parentActivityID]; + if (branchData) { + if (!branchData.lastActivityOrderID || activityData.orderID > branchData.lastActivityOrderID) { + // is it the last activity in the branch? + branchData.lastActivityOrderID = activityData.orderID; + branchData.lastActivity = activity; + } + } else { + branchData = branchToActivityDefs[activityData.parentActivityID] = { + 'lastActivityOrderID' : activityData.orderID, + 'lastActivity' : activity + }; } - branches.push(new ActivityLib.BranchActivity(activityData.activityID, - activityData.activityUIID, - activityData.activityTitle)); - var branchData = branchToActivities[activityData.activityID]; - if (!branchData) { - branchData = branchToActivities[activityData.activityID] = {}; + if (activityData.orderID == 1) { + // is it the first activity in the branch + branchData.firstActivity = activity; } - branchData.stopAfterActivity = activityData.stopAfterActivity; - - break; - - // Support (Floating) activity - case 15: - activity = new ActivityLib.FloatingActivity( - activityData.activityID, - activityData.activityUIID, - activityData.xCoord, - activityData.yCoord); - break; - } + } + }); + // assign previously extracted branches to branching activities + $.each(branchToBranching, function(branchingID, branches){ + $.each(layout.activities, function(){ + if (this instanceof ActivityDefs.BranchingEdgeActivity + && this.branchingActivity.id == branchingID){ + var branchingActivity = this.branchingActivity; + branchingActivity.branches = branches; + var defaultBranchSet = false; + $.each(branches, function(){ + this.branchingActivity = branchingActivity; + if (branchingActivity.defaultActivityUIID == this.uiid && !defaultBranchSet){ + this.defaultBranch = true; + defaultBranchSet = true; + } + }); + + branchingActivity.defaultActivityUIID = null; + if (!defaultBranchSet && branches.length > 0) { + branches[0].defaultBranch = true; + } + return false; + } + }); + }); - if (!activity) { - // activity type not supported yet - return true; - } - if (!(activity instanceof ActivityLib.FloatingActivity)) { - layout.activities.push(activity); - } - - // store information about the branch the activity belongs to - if (activityData.parentActivityID) { - var branchData = branchToActivities[activityData.parentActivityID]; - if (branchData) { - if (!branchData.lastActivityOrderID || activityData.orderID > branchData.lastActivityOrderID) { - // is it the last activity in the branch? - branchData.lastActivityOrderID = activityData.orderID; - branchData.lastActivity = activity; - } - } else { - branchData = branchToActivities[activityData.parentActivityID] = { - 'lastActivityOrderID' : activityData.orderID, - 'lastActivity' : activity - }; - } + // apply existing groupings and parent-child references to activities + $.each(ld.activities, function(){ + var activityData = this, + activity = this.activity; - if (activityData.orderID == 1) { - // is it the first activity in the branch - branchData.firstActivity = activity; - } - } - }); - - // assign previously extracted branches to branching activities - $.each(branchToBranching, function(branchingID, branches){ - $.each(layout.activities, function(){ - if (this instanceof ActivityLib.BranchingEdgeActivity - && this.branchingActivity.id == branchingID){ - var branchingActivity = this.branchingActivity; - branchingActivity.branches = branches; - var defaultBranchSet = false; - $.each(branches, function(){ - this.branchingActivity = branchingActivity; - if (branchingActivity.defaultActivityUIID == this.uiid && !defaultBranchSet){ - this.defaultBranch = true; - defaultBranchSet = true; + if (activity) { + if (activityData.applyGrouping) { + var groupedActivity = activityData.activity; + + $.each(layout.activities, function(){ + if (this instanceof ActivityDefs.GroupingActivity + && this.groupingID == activityData.groupingID) { + // add reference and redraw the grouped activity + if (groupedActivity instanceof ActivityDefs.BranchingEdgeActivity) { + groupedActivity.branchingActivity.grouping = this; + } else { + groupedActivity.grouping = this; + groupedActivity.draw(); + } + return false; + } + }); + } + + if (layout.floatingActivity && layout.floatingActivity.id == activityData.parentActivityID) { + // add a Tool Activity as a Floating Activity element + if (!layout.floatingActivity.childActivityDefs) { + layout.floatingActivity.childActivityDefs = []; } - }); + layout.floatingActivity.childActivityDefs.push(activity); + activity.parentActivity = layout.floatingActivity; + if (!arrangeNeeded){ + // if no auto re-arrange will be done, just redraw the container with its child activities + layout.floatingActivity.draw(); + } + } - branchingActivity.defaultActivityUIID = null; - if (!defaultBranchSet && branches.length > 0) { - branches[0].defaultBranch = true; + // find Optional/Parallel Activity + if (activityData.parentActivityID && !activity.parentActivity) { + $.each(layout.activities, function(){ + if (activityData.parentActivityID == this.id + && (this instanceof ActivityDefs.ParallelActivity + || this instanceof ActivityDefs.OptionalActivity)) { + // add a Tool Activity as a Optional/Parallel Activity element + if (!this.childActivityDefs) { + this.childActivityDefs = []; + } + this.childActivityDefs.push(activity); + activity.parentActivity = this; + if (!arrangeNeeded) { + // if no auto re-arrange will be done, just redraw the container with its child activities + this.draw(); + } + + // stop iteration + return false; + } + }); } - return false; } }); - }); - - - // apply existing groupings and parent-child references to activities - $.each(ld.activities, function(){ - var activityData = this, - activity = this.activity; - if (activity) { - if (activityData.applyGrouping) { - var groupedActivity = activityData.activity; - - $.each(layout.activities, function(){ - if (this instanceof ActivityLib.GroupingActivity - && this.groupingID == activityData.groupingID) { - // add reference and redraw the grouped activity - if (groupedActivity instanceof ActivityLib.BranchingEdgeActivity) { - groupedActivity.branchingActivity.grouping = this; - } else { - groupedActivity.grouping = this; - groupedActivity.draw(); + // apply group -> branch mappings + $.each(ld.branchMappings, function(){ + var entry = this, + input = null, + group = null, + branch = null, + gate = null; + $.each(layout.activities, function(){ + // is it the branch we're looking for? + if (this instanceof ActivityDefs.BranchingEdgeActivity && this.isStart) { + $.each(this.branchingActivity.branches, function(){ + if (entry.sequenceActivityUIID == this.uiid) { + branch = this; + return false; } - return false; - } - }); - } + }); + // is it the grouping we're looking for? + } else if (this instanceof ActivityDefs.GroupingActivity) { + $.each(this.groups, function(){ + if (entry.groupUIID == this.uiid) { + group = this; + return false; + } + }); + } + // is it the gate we're looking for + else if (this instanceof ActivityDefs.GateActivity && entry.gateActivityUIID == this.uiid) { + gate = this; + } else if (entry.condition && entry.condition.toolActivityUIID == this.uiid) { + input = this; + } + + // found both, no need to continue iteration + if ((gate || branch) && (input || group)) { + return false; + } + }); - if (layout.floatingActivity && layout.floatingActivity.id == activityData.parentActivityID) { - // add a Tool Activity as a Floating Activity element - if (!layout.floatingActivity.childActivities) { - layout.floatingActivity.childActivities = []; + if (group) { + if (branch) { + branch.branchingActivity.groupsToBranches.push({ + 'id' : entry.entryID, + 'uiid' : entry.entryUIID, + 'group' : group, + 'branch' : branch + }); } - layout.floatingActivity.childActivities.push(activity); - activity.parentActivity = layout.floatingActivity; - if (!arrangeNeeded){ - // if no auto re-arrange will be done, just redraw the container with its child activities - layout.floatingActivity.draw(); - } + } else if (input) { + if (branch) { + branch.branchingActivity.input = input; + branch.branchingActivity.conditionsToBranches.push({ + 'id' : entry.entryID, + 'uiid' : entry.entryUIID, + 'condition' : entry.condition, + 'branch' : branch + }); + } else if (gate) { + gate.input = input; + gate.conditionsToBranches.push({ + 'id' : entry.entryID, + 'uiid' : entry.entryUIID, + 'condition' : entry.condition, + 'branch' : entry.gateOpenWhenConditionMet ? 'open' : 'closed' + }); + } } - - // find Optional/Parallel Activity - if (activityData.parentActivityID && !activity.parentActivity) { - $.each(layout.activities, function(){ - if (activityData.parentActivityID == this.id - && (this instanceof ActivityLib.ParallelActivity - || this instanceof ActivityLib.OptionalActivity)) { - // add a Tool Activity as a Optional/Parallel Activity element - if (!this.childActivities) { - this.childActivities = []; - } - this.childActivities.push(activity); - activity.parentActivity = this; - if (!arrangeNeeded) { - // if no auto re-arrange will be done, just redraw the container with its child activities - this.draw(); - } - - // stop iteration - return false; - } - }); - } - } - }); - - // apply group -> branch mappings - $.each(ld.branchMappings, function(){ - var entry = this, - input = null, - group = null, - branch = null, - gate = null; + }); + + // draw starting and ending transitions in branches $.each(layout.activities, function(){ - // is it the branch we're looking for? - if (this instanceof ActivityLib.BranchingEdgeActivity && this.isStart) { - $.each(this.branchingActivity.branches, function(){ - if (entry.sequenceActivityUIID == this.uiid) { - branch = this; - return false; + if (this instanceof ActivityDefs.BranchingEdgeActivity && this.isStart) { + var branchingActivity = this.branchingActivity, + branches = branchingActivity.branches.slice(); + branchingActivity.branches = []; + + $.each(branches, function(){ + var branch = this, + branchData = branchToActivityDefs[branch.id]; + + // add reference to the transition inside branch + ActivityLib.addTransition(branchingActivity.start, + branchData.firstActivity || branchingActivity.end, + true, null, null, branch); + if (branchData.lastActivity && !branchData.stopAfterActivity) { + ActivityLib.addTransition(branchData.lastActivity, branchingActivity.end, true); } }); - // is it the grouping we're looking for? - } else if (this instanceof ActivityLib.GroupingActivity) { - $.each(this.groups, function(){ - if (entry.groupUIID == this.uiid) { - group = this; - return false; - } - }); } - // is it the gate we're looking for - else if (this instanceof ActivityLib.GateActivity && entry.gateActivityUIID == this.uiid) { - gate = this; - } else if (entry.condition && entry.condition.toolActivityUIID == this.uiid) { - input = this; - } - - // found both, no need to continue iteration - if ((gate || branch) && (input || group)) { - return false; - } }); - if (group) { - if (branch) { - branch.branchingActivity.groupsToBranches.push({ - 'id' : entry.entryID, - 'uiid' : entry.entryUIID, - 'group' : group, - 'branch' : branch - }); - } - } else if (input) { - if (branch) { - branch.branchingActivity.input = input; - branch.branchingActivity.conditionsToBranches.push({ - 'id' : entry.entryID, - 'uiid' : entry.entryUIID, - 'condition' : entry.condition, - 'branch' : branch - }); - } else if (gate) { - gate.input = input; - gate.conditionsToBranches.push({ - 'id' : entry.entryID, - 'uiid' : entry.entryUIID, - 'condition' : entry.condition, - 'branch' : entry.gateOpenWhenConditionMet ? 'open' : 'closed' - }); - } - } - }); - - // draw starting and ending transitions in branches - $.each(layout.activities, function(){ - if (this instanceof ActivityLib.BranchingEdgeActivity && this.isStart) { - var branchingActivity = this.branchingActivity, - branches = branchingActivity.branches.slice(); - branchingActivity.branches = []; + + // draw plain transitions + $.each(ld.transitions, function(){ + var transition = this, + fromActivity = null, + toActivity = null; - $.each(branches, function(){ - var branch = this, - branchData = branchToActivities[branch.id]; + // find which activities the transition belongs to + $.each(layout.activities, function(){ + var activity = this, + isBranching = activity instanceof ActivityDefs.BranchingEdgeActivity; - // add reference to the transition inside branch - ActivityLib.addTransition(branchingActivity.start, - branchData.firstActivity || branchingActivity.end, - true, null, null, branch); - if (branchData.lastActivity && !branchData.stopAfterActivity) { - ActivityLib.addTransition(branchData.lastActivity, branchingActivity.end, true); + // check if transition IDs match either a plain activity or a complex one + if (isBranching ? !activity.isStart && transition.fromActivityID == activity.branchingActivity.id + : transition.fromActivityID == activity.id) { + fromActivity = activity; + } else if (isBranching ? activity.isStart && transition.toActivityID == activity.branchingActivity.id + : transition.toActivityID == activity.id) { + toActivity = activity; } + + // found both transition ends, draw it and stop the iteration + if (fromActivity && toActivity) { + ActivityLib.addTransition(fromActivity, toActivity, true, + transition.transitionID, transition.transitionUIID); + return false; + } }); - } - }); - - - // draw plain transitions - $.each(ld.transitions, function(){ - var transition = this, - fromActivity = null, - toActivity = null; + }); - // find which activities the transition belongs to - $.each(layout.activities, function(){ - var activity = this, - isBranching = activity instanceof ActivityLib.BranchingEdgeActivity; - - // check if transition IDs match either a plain activity or a complex one - if (isBranching ? !activity.isStart && transition.fromActivityID == activity.branchingActivity.id - : transition.fromActivityID == activity.id) { - fromActivity = activity; - } else if (isBranching ? activity.isStart && transition.toActivityID == activity.branchingActivity.id - : transition.toActivityID == activity.id) { - toActivity = activity; + $.each(ld.annotations, function(){ + var isRegion = this.endXcoord; + if (isRegion) { + DecorationDefs.addRegion(this.xcoord, this.ycoord, this.endXcoord, this.endYcoord, + this. title, this.color); + } else { + DecorationDefs.addLabel(this.xcoord, this.ycoord, this.title); } - - // found both transition ends, draw it and stop the iteration - if (fromActivity && toActivity) { - ActivityLib.addTransition(fromActivity, toActivity, true, - transition.transitionID, transition.transitionUIID); - return false; - } }); - }); - - $.each(ld.annotations, function(){ - var isRegion = this.endXcoord; - if (isRegion) { - DecorationLib.addRegion(this.xcoord, this.ycoord, this.endXcoord, this.endYcoord, - this. title, this.color); + + + if (arrangeNeeded) { + GeneralLib.arrangeActivities(); } else { - DecorationLib.addLabel(this.xcoord, this.ycoord, this.title); + GeneralLib.resizePaper(); } - }); - - - if (arrangeNeeded) { - MenuLib.arrangeActivities(); - } else { - resizePaper(); - } - - setModified(false); - updateAccess(response.access); - - if (!ld.validDesign) { - var dialog = layout.items.infoDialog.html(LABELS.SEQUENCE_NOT_VALID); - dialog.dialog('open'); - setTimeout(function(){ - dialog.text('').dialog('close'); - }, 5000); - } - } - }); -} - - -/** - * Stores the sequece in database. - */ -function saveLearningDesign(folderID, learningDesignID, title) { - var activities = [], - transitions = [], - groupings = [], - branchMappings = [], - annotations = [], - layoutActivities = [], - // trim the - title = title.trim(), - description = CKEDITOR.instances['ldDescriptionFieldDescription'].getData(), - // final success/failure of the save - result = false, - error = null; - - $.each(layout.activities, function(){ - if (this.parentActivity && (this.parentActivity instanceof ActivityLib.BranchingActivity - || this.parentActivity instanceof ActivityLib.BranchActivity)){ - // remove previously set parent activities as they will be re-set from the start - this.parentActivity = null; - this.orderID = null; - } - }); - - $.each(layout.activities, function(){ - // add all branch activities for iteration and saving - if (this instanceof ActivityLib.BranchingEdgeActivity){ - if (this.isStart){ - var branchingActivity = this.branchingActivity; - branchingActivity.defaultActivityUIID = null; - layoutActivities.push(branchingActivity); + GeneralLib.setModified(false); + GeneralLib.updateAccess(response.access); - $.each(branchingActivity.branches, function(branchOrderID){ - if (!branchingActivity.defaultActivityUIID && this.defaultBranch) { - branchingActivity.defaultActivityUIID = this.uiid; - } - this.defaultActivityUIID = null; - this.orderID = branchOrderID + 1; - this.parentActivity = branchingActivity; - this.stopAfterActivity = false; - layoutActivities.push(this); + if (!ld.validDesign && !isReadOnlyMode) { + var dialog = layout.infoDialog.html(LABELS.SEQUENCE_NOT_VALID); + dialog.dialog('open'); - var childActivity = this.transitionFrom.toActivity, - orderID = 1; - while (!(childActivity instanceof ActivityLib.BranchingEdgeActivity - && !childActivity.isStart)) { - childActivity.parentActivity = this; - childActivity.orderID = orderID; - if (orderID == 1){ - this.defaultActivityUIID = childActivity.uiid; - } - orderID++; - - if (childActivity.transitions.from.length == 0) { - this.stopAfterActivity = true; - break; - } - childActivity = childActivity.transitions.from[0].toActivity; - } - }); - - if (!branchingActivity.defaultActivityUIID && branchingActivity.branches.length > 0) { - branchingActivity.defaultActivityUIID = branchingActivity.branches[0].uiid; - branchingActivity.branches[0].defaultBranch = true; + setTimeout(function(){ + dialog.text('').dialog('close'); + }, 5000); } } - } else { - layoutActivities.push(this); - } - }); + }); + }, - if (layout.floatingActivity){ - layoutActivities.push(layout.floatingActivity); - } - $.each(layoutActivities, function(){ - var activity = this, - activityBox = activity.items ? activity.items.shape.getBBox() : null, - x = activityBox ? parseInt(activityBox.x) : null, - y = activityBox ? parseInt(activityBox.y) : null, - activityTypeID = null, - activityCategoryID = activity instanceof ActivityLib.ToolActivity ? - layout.toolMetadata[activity.learningLibraryID].activityCategoryID : - activity instanceof ActivityLib.ParallelActivity ? 5 : 1, - iconPath = null, - isGrouped = activity.grouping ? true : false, - parentActivityID = activity.parentActivity ? activity.parentActivity.id : null; - - if (activity.toolID) { - activityTypeID = 1; - // find out what is the icon for tool acitivty - var templateIcon = $('.template[learningLibraryId=' + activity.learningLibraryID +'] img'); - if (templateIcon.width() > 0) { - iconPath = layout.toolMetadata[activity.learningLibraryID].iconPath; - } + /** + * Sets new paper dimensions and moves some static elements. + */ + resizePaper : function(width, height) { + if (!paper) { + return; } - // translate activity type to back-end understandable - else if (activity instanceof ActivityLib.GroupingActivity){ - activityTypeID = 2; - - // create a list of groupings - var groups = [], - groupingType = null; - $.each(activity.groups, function(groupIndex, group){ - groups.push({ - 'groupName' : group.name, - 'groupID' : group.id, - 'groupUIID' : group.uiid, - 'orderID' : groupIndex - }); + + if (!width || !height) { + var width = 0, + height = 0; + $.each(layout.activities, function(){ + // find new dimensions of paper + var activityBox = this.items.shape.getBBox(); + if (activityBox.x2 + 30 > width) { + width = activityBox.x2 + 30; + } + if (activityBox.y2 + 30 > height) { + height = activityBox.y2 + 30; + } }); - - switch(activity.groupingType) { - case 'random' : - groupingType = 1; - break; - case 'monitor' : - groupingType = 2; - break; - case 'learner' : - groupingType = 4; - break; + } + + // -20 so Chrome does not create unnecessary scrollbars when dropping a tool template to canvas + // +50 so there is space for rubbish bin + width = Math.max(width, canvas.width()) - 20; + height = Math.max(height + (isReadOnlyMode ? 20 : 50), canvas.height()) - 20; + + paper.setSize(width, height); + $('#templateContainer').height($('#ldDescriptionDiv').height() + + $('#canvas').height() - 10); + + if (!isReadOnlyMode){ + if (layout.bin) { + layout.bin.remove(); } - groupings.push({ - 'groupingUIID' : activity.groupingUIID, - 'groupingTypeID' : groupingType, - 'learnersPerGroup' : activity.learnerCount, - 'equalNumberOfLearnersPerGroup' : activity.equalSizes, - 'viewStudentsBeforeSelection' : activity.viewLearners, - 'maxNumberOfGroups' : activity.groupCount, - 'numberOfGroups' : groups.length, - 'groups' : groups - }); + // draw rubbish bin on canvas + layout.bin = paper.path(Raphael.format('M {0} {1} h -50 l 10 50 h 30 z', width - 5, height - 50)); + // so it can be found when SVG code gets cloned + $(layout.bin.node).attr('id', 'rubbishBin'); - } else if (activity instanceof ActivityLib.GateActivity){ - switch(activity.gateType) { - case 'sync' : activityTypeID = 3; break; - case 'schedule' : activityTypeID = 4; break; - case 'permission' : activityTypeID = 5; break; - case 'condition' : - activityTypeID = 14; - - if (activity.input) { - $.each(activity.conditionsToBranches, function(index){ - if (!this.branch) { - return true; - } - - var condition = this.condition; - if (condition) { - condition.orderID = index + 1; - if (condition.exactMatchValue) { - condition.startValue = condition.endValue = null; - } - } - - branchMappings.push({ - 'entryID' : this.id, - 'entryUIID' : this.uiid, - 'gateActivityUIID' : activity.uiid, - 'gateOpenWhenConditionMet' : this.branch == 'open', - 'condition' : condition - }); - }); - } - - break; + HandlerLib.resetCanvasMode(true); + } + }, + + + /** + * Stores the sequece in database. + */ + saveLearningDesign : function(folderID, learningDesignID, title) { + var activities = [], + transitions = [], + groupings = [], + branchMappings = [], + annotations = [], + layoutActivityDefs = [], + // trim the + title = title.trim(), + description = CKEDITOR.instances['ldDescriptionFieldDescription'].getData(), + // final success/failure of the save + result = false, + error = null; + + $.each(layout.activities, function(){ + if (this.parentActivity && (this.parentActivity instanceof ActivityDefs.BranchingActivity + || this.parentActivity instanceof ActivityDefs.BranchActivity)){ + // remove previously set parent activities as they will be re-set from the start + this.parentActivity = null; + this.orderID = null; } - } else if (activity instanceof ActivityLib.ParallelActivity) { - activityTypeID = 6; - } else if (activity instanceof ActivityLib.OptionalActivity) { - activityTypeID = 7; - } else if (activity instanceof ActivityLib.BranchingActivity) { - activityBox = activity.start.items.shape.getBBox(); - - switch(activity.branchingType) { - case 'chosen' : activityTypeID = 10; break; - case 'group' : - activityTypeID = 11; - var branchMappingCopy = activity.groupsToBranches.slice(), - branchMapping = activity.groupsToBranches = []; - // no break, so fall to 'tool' - case 'tool' : - activityTypeID = activityTypeID || 12; + }); + + $.each(layout.activities, function(){ + // add all branch activities for iteration and saving + if (this instanceof ActivityDefs.BranchingEdgeActivity){ + if (this.isStart){ + var branchingActivity = this.branchingActivity; + branchingActivity.defaultActivityUIID = null; + layoutActivityDefs.push(branchingActivity); + + $.each(branchingActivity.branches, function(branchOrderID){ + if (!branchingActivity.defaultActivityUIID && this.defaultBranch) { + branchingActivity.defaultActivityUIID = this.uiid; + } + this.defaultActivityUIID = null; + this.orderID = branchOrderID + 1; + this.parentActivity = branchingActivity; + this.stopAfterActivity = false; + layoutActivityDefs.push(this); - if (activity.defaultActivityUIID && (activityTypeID == 11 || activity.input)) { - var branchMappingCopy = branchMappingCopy || activity.conditionsToBranches.slice(), - // yes, yes, a lousy construction - branchMapping = branchMapping || (activity.conditionsToBranches = []); - - $.each(branchMappingCopy, function(index){ - if (activity.branches.indexOf(this.branch) == -1){ - return true; + var childActivity = this.transitionFrom.toActivity, + orderID = 1; + while (!(childActivity instanceof ActivityDefs.BranchingEdgeActivity + && !childActivity.isStart)) { + childActivity.parentActivity = this; + childActivity.orderID = orderID; + if (orderID == 1){ + this.defaultActivityUIID = childActivity.uiid; } - if (this.group && activity.grouping.groups.indexOf(this.group) == -1) { - return true; - } + orderID++; - var condition = this.condition; - if (condition) { - condition.orderID = index + 1; - if (condition.exactMatchValue) { - condition.startValue = condition.endValue = null; - } + if (childActivity.transitions.from.length == 0) { + this.stopAfterActivity = true; + break; } - - branchMappings.push({ - 'entryID' : this.id, - 'entryUIID' : this.uiid, - 'branchingActivityUIID': this.branch.branchingActivity.uiid, - 'sequenceActivityUIID' : this.branch.uiid, - 'groupUIID' : this.group ? this.group.uiid : null, - 'condition' : condition - }); - - branchMapping.push(this); - }); - } + childActivity = childActivity.transitions.from[0].toActivity; + } + }); - break; - case 'optional' : activityTypeID = 13; break; + if (!branchingActivity.defaultActivityUIID && branchingActivity.branches.length > 0) { + branchingActivity.defaultActivityUIID = branchingActivity.branches[0].uiid; + branchingActivity.branches[0].defaultBranch = true; + } + } + } else { + layoutActivityDefs.push(this); } - } else if (activity instanceof ActivityLib.BranchActivity){ - activityTypeID = 8; - } else if (activity instanceof ActivityLib.FloatingActivity){ - activityTypeID = 15; - } + }); - if (activity.parentActivity && activity.parentActivity instanceof DecorationLib.Container){ - // positions are relative to parent container - var activityBox = activity.parentActivity.items.getBBox(); - x -= activityBox.x; - y -= activityBox.y; + if (layout.floatingActivity){ + layoutActivityDefs.push(layout.floatingActivity); } - // add activity - activities.push({ - 'activityID' : activity.id, - 'activityUIID' : activity.uiid, - 'toolID' : activity.toolID, - 'learningLibraryID' : activity.learningLibraryID, - 'toolContentID' : activity.toolContentID || activity.toolID, - 'stopAfterActivity' : false, - 'groupingSupportType' : 2, - 'applyGrouping' : isGrouped, - 'groupingUIID' : isGrouped ? activity.grouping.groupingUIID : null, - 'createGroupingUIID' : activity instanceof ActivityLib.GroupingActivity ? activity.groupingUIID : null, - 'parentActivityID' : activity.parentActivity ? activity.parentActivity.id : null, - 'parentUIID' : activity.parentActivity ? activity.parentActivity.uiid : null, - 'libraryActivityUIImage' : iconPath, - 'xCoord' : x, - 'yCoord' : y, - 'activityTitle' : activity.title, - 'description' : activity.description, - 'activityCategoryID' : activityCategoryID, - 'activityTypeID' : activityTypeID, - 'orderID' : activity.orderID, - 'defaultActivityUIID' : activity.defaultActivityUIID, - 'gateStartTimeOffset' : activity.gateType == 'schedule' ? - activity.offsetDay*24*60 + activity.offsetHour*60 + activity.offsetMinute : null, - 'gateActivityCompletionBased' : activity.gateActivityCompletionBased, - 'gateActivityLevelID' : activity instanceof ActivityLib.GateActivity ? 1 : null, - 'minOptions' : activity.minOptions, - 'maxOptions' : activity.maxOptions, - 'stopAfterActivity' : activity.stopAfterActivity ? true : false, - 'toolActivityUIID' : activity.input ? activity.input.uiid : null, + $.each(layoutActivityDefs, function(){ + var activity = this, + activityBox = activity.items ? activity.items.shape.getBBox() : null, + x = activityBox ? parseInt(activityBox.x) : null, + y = activityBox ? parseInt(activityBox.y) : null, + activityTypeID = null, + activityCategoryID = activity instanceof ActivityDefs.ToolActivity ? + layout.toolMetadata[activity.learningLibraryID].activityCategoryID : + activity instanceof ActivityDefs.ParallelActivity ? 5 : 1, + iconPath = null, + isGrouped = activity.grouping ? true : false, + parentActivityID = activity.parentActivity ? activity.parentActivity.id : null; - 'gradebookToolOutputDefinitionName' : null, - 'helpText' : null - }); - - var activityTransitions = activity instanceof ActivityLib.BranchingActivity ? - activity.end.transitions : activity.transitions; - - if (activityTransitions) { - // iterate over transitions and create a list - $.each(activityTransitions.from, function(){ - var transition = this, - toActivity = transition.toActivity; - if (toActivity instanceof ActivityLib.BranchingEdgeActivity) { - if (toActivity.isStart) { - toActivity = toActivity.branchingActivity; - } else { - // skip transition from last activity in branch to branching edge marker - return true; - } + if (activity.toolID) { + activityTypeID = 1; + // find out what is the icon for tool acitivty + var templateIcon = $('.template[learningLibraryId=' + activity.learningLibraryID +'] img'); + if (templateIcon.width() > 0) { + iconPath = layout.toolMetadata[activity.learningLibraryID].iconPath; } + } + // translate activity type to back-end understandable + else if (activity instanceof ActivityDefs.GroupingActivity){ + activityTypeID = 2; - transitions.push({ - 'transitionID' : transition.id, - 'transitionUIID' : transition.uiid, - 'fromUIID' : activity.uiid, - 'toUIID' : toActivity.uiid, - 'transitionType' : 0 + // create a list of groupings + var groups = [], + groupingType = null; + $.each(activity.groups, function(groupIndex, group){ + groups.push({ + 'groupName' : group.name, + 'groupID' : group.id, + 'groupUIID' : group.uiid, + 'orderID' : groupIndex + }); }); - }); - } - }); - - if (error) { - alert(error); - return false; - } - - // iterate over labels and regions - $.each(layout.labels.concat(layout.regions), function(){ - var box = this.items.shape.getBBox(), - isRegion = this instanceof DecorationLib.Region; - - annotations.push({ - 'id' : this.id, - 'annotationUIID' : this.uiid, - 'title' : this.title, - 'xCoord' : box.x, - 'yCoord' : box.y, - 'endXCoord' : isRegion ? box.x2 : null, - 'endYCoord' : isRegion ? box.y2 : null, - 'color' : isRegion ? this.items.shape.attr('fill') : null - }); - }); - - - // serialise the sequence - var ld = { - // it is null if it is a new sequence - 'learningDesignID' : learningDesignID, - 'workspaceFolderID' : folderID, - 'copyTypeID' : 1, - 'originalUserID' : null, - 'title' : title, - 'description' : description, - 'maxID' : layout.ld.maxUIID, - 'readOnly' : false, - 'editOverrideLock' : false, - 'dateReadOnly' : null, - 'version' : null, - 'contentFolderID' : layout.ld.contentFolderID, - 'saveMode' : layout.ld.learningDesignID - && layout.ld.learningDesignID != learningDesignID - ? 1 : 0, - 'originalLearningDesignID' : null, - - 'activities' : activities, - 'transitions' : transitions, - 'groupings' : groupings, - 'branchMappings' : branchMappings, - 'annotations' : annotations, - - 'helpText' : null, - 'duration' : null, - 'licenseID' : null, - 'licenseText' : null - }; - - // get LD details - $.ajax({ - type : 'POST', - cache : false, - async : false, - url : LAMS_URL + "authoring/author.do", - dataType : 'json', - data : { - 'method' : 'saveLearningDesign', - 'ld' : JSON.stringify(ld) - }, - success : function(response) { - layout.ld.folderID = folderID; - layout.ld.title = title; - - // check if there were any validation errors - if (response.validation.length > 0) { - var message = LABELS.SEQUENCE_VALIDATION_ISSUES + '\n'; - $.each(response.validation, function() { - var uiid = this.UIID, - title = ''; - if (uiid) { - // find which activity is the error about - $.each(layout.activities, function(){ - if (uiid == this.uiid) { - title = this.title + ': '; - } - }); - } - message += title + this.message + '\n'; - }); - alert(message); - } - - // if save (even partially) was successful - if (response.ld) { - // assing the database-generated values - layout.ld.learningDesignID = response.ld.learningDesignID; - if (!layout.ld.contentFolderID) { - layout.ld.contentFolderID = response.ld.contentFolderID; + switch(activity.groupingType) { + case 'random' : + groupingType = 1; + break; + case 'monitor' : + groupingType = 2; + break; + case 'learner' : + groupingType = 4; + break; } - $('#ldDescriptionFieldTitle').text(title); - $('#ldDescriptionFieldDescription').text(description); - // assign database-generated properties to activities - $.each(response.ld.activities, function() { - var updatedActivity = this; - $.each(layout.activities, function(){ - var isBranching = this instanceof ActivityLib.BranchingEdgeActivity, - found = false; - if (isBranching && !this.isStart) { - return true; - } - - if (isBranching) { - if (updatedActivity.activityUIID == this.branchingActivity.uiid){ - this.branchingActivity.id = updatedActivity.activityID; - found = true; - } else { - $.each(this.branchingActivity.branches, function(){ - if (updatedActivity.activityUIID == this.branchingActivity.uiid){ - this.id = updatedActivity.activityID; + groupings.push({ + 'groupingUIID' : activity.groupingUIID, + 'groupingTypeID' : groupingType, + 'learnersPerGroup' : activity.learnerCount, + 'equalNumberOfLearnersPerGroup' : activity.equalSizes, + 'viewStudentsBeforeSelection' : activity.viewLearners, + 'maxNumberOfGroups' : activity.groupCount, + 'numberOfGroups' : groups.length, + 'groups' : groups + }); + + } else if (activity instanceof ActivityDefs.GateActivity){ + switch(activity.gateType) { + case 'sync' : activityTypeID = 3; break; + case 'schedule' : activityTypeID = 4; break; + case 'permission' : activityTypeID = 5; break; + case 'condition' : + activityTypeID = 14; + + if (activity.input) { + $.each(activity.conditionsToBranches, function(index){ + if (!this.branch) { + return true; + } + + var condition = this.condition; + if (condition) { + condition.orderID = index + 1; + if (condition.exactMatchValue) { + condition.startValue = condition.endValue = null; } + } + + branchMappings.push({ + 'entryID' : this.id, + 'entryUIID' : this.uiid, + 'gateActivityUIID' : activity.uiid, + 'gateOpenWhenConditionMet' : this.branch == 'open', + 'condition' : condition }); - } - } else if (updatedActivity.activityUIID == this.uiid) { - this.id = updatedActivity.activityID; - this.toolContentID = updatedActivity.toolContentID; - found = true; + }); } - // update transition IDs - $.each(this.transitions.from, function(){ - var existingTransition = this; - $.each(response.ld.transitions, function(){ - if (existingTransition.uiid == +this.transitionUIID) { - existingTransition.id = +this.transitionID || null; - return false; + break; + } + } else if (activity instanceof ActivityDefs.ParallelActivity) { + activityTypeID = 6; + } else if (activity instanceof ActivityDefs.OptionalActivity) { + activityTypeID = 7; + } else if (activity instanceof ActivityDefs.BranchingActivity) { + activityBox = activity.start.items.shape.getBBox(); + + switch(activity.branchingType) { + case 'chosen' : activityTypeID = 10; break; + case 'group' : + activityTypeID = 11; + var branchMappingCopy = activity.groupsToBranches.slice(), + branchMapping = activity.groupsToBranches = []; + // no break, so fall to 'tool' + case 'tool' : + activityTypeID = activityTypeID || 12; + + if (activity.defaultActivityUIID && (activityTypeID == 11 || activity.input)) { + var branchMappingCopy = branchMappingCopy || activity.conditionsToBranches.slice(), + // yes, yes, a lousy construction + branchMapping = branchMapping || (activity.conditionsToBranches = []); + + $.each(branchMappingCopy, function(index){ + if (activity.branches.indexOf(this.branch) == -1){ + return true; } + if (this.group && activity.grouping.groups.indexOf(this.group) == -1) { + return true; + } + + var condition = this.condition; + if (condition) { + condition.orderID = index + 1; + if (condition.exactMatchValue) { + condition.startValue = condition.endValue = null; + } + } + + branchMappings.push({ + 'entryID' : this.id, + 'entryUIID' : this.uiid, + 'branchingActivityUIID': this.branch.branchingActivity.uiid, + 'sequenceActivityUIID' : this.branch.uiid, + 'groupUIID' : this.group ? this.group.uiid : null, + 'condition' : condition + }); + + branchMapping.push(this); }); - }); + } - return !found; - }); - }); - - if (layout.floatingActivity) { - layout.floatingActivity.id = response.floatingActivityID; + break; + case 'optional' : activityTypeID = 13; break; } + } else if (activity instanceof ActivityDefs.BranchActivity){ + activityTypeID = 8; + } else if (activity instanceof ActivityDefs.FloatingActivity){ + activityTypeID = 15; + } + + if (activity.parentActivity && activity.parentActivity instanceof DecorationDefs.Container){ + // positions are relative to parent container + var activityBox = activity.parentActivity.items.getBBox(); + x -= activityBox.x; + y -= activityBox.y; + } + + // add activity + activities.push({ + 'activityID' : activity.id, + 'activityUIID' : activity.uiid, + 'toolID' : activity.toolID, + 'learningLibraryID' : activity.learningLibraryID, + 'toolContentID' : activity.toolContentID || activity.toolID, + 'stopAfterActivity' : false, + 'groupingSupportType' : 2, + 'applyGrouping' : isGrouped, + 'groupingUIID' : isGrouped ? activity.grouping.groupingUIID : null, + 'createGroupingUIID' : activity instanceof ActivityDefs.GroupingActivity ? activity.groupingUIID : null, + 'parentActivityID' : activity.parentActivity ? activity.parentActivity.id : null, + 'parentUIID' : activity.parentActivity ? activity.parentActivity.uiid : null, + 'libraryActivityUIImage' : iconPath, + 'xCoord' : x, + 'yCoord' : y, + 'activityTitle' : activity.title, + 'description' : activity.description, + 'activityCategoryID' : activityCategoryID, + 'activityTypeID' : activityTypeID, + 'orderID' : activity.orderID, + 'defaultActivityUIID' : activity.defaultActivityUIID, + 'gateStartTimeOffset' : activity.gateType == 'schedule' ? + activity.offsetDay*24*60 + activity.offsetHour*60 + activity.offsetMinute : null, + 'gateActivityCompletionBased' : activity.gateActivityCompletionBased, + 'gateActivityLevelID' : activity instanceof ActivityDefs.GateActivity ? 1 : null, + 'minOptions' : activity.minOptions, + 'maxOptions' : activity.maxOptions, + 'stopAfterActivity' : activity.stopAfterActivity ? true : false, + 'toolActivityUIID' : activity.input ? activity.input.uiid : null, - // update annotation IDs - $.each(response.ld.annotations, function(){ - var updatedAnnotation = this; - $.each(layout.labels.concat(layout.regions), function(){ - if (this.uiid == updatedAnnotation.annotationUIID) { - this.id = updatedAnnotation.uid; - return false; + 'gradebookToolOutputDefinitionName' : null, + 'helpText' : null + }); + + var activityTransitions = activity instanceof ActivityDefs.BranchingActivity ? + activity.end.transitions : activity.transitions; + + if (activityTransitions) { + // iterate over transitions and create a list + $.each(activityTransitions.from, function(){ + var transition = this, + toActivity = transition.toActivity; + if (toActivity instanceof ActivityDefs.BranchingEdgeActivity) { + if (toActivity.isStart) { + toActivity = toActivity.branchingActivity; + } else { + // skip transition from last activity in branch to branching edge marker + return true; } + } + + transitions.push({ + 'transitionID' : transition.id, + 'transitionUIID' : transition.uiid, + 'fromUIID' : activity.uiid, + 'toUIID' : toActivity.uiid, + 'transitionType' : 0 }); }); - - if (response.validation.length == 0) { - alert(LABELS.SAVE_SUCCESSFUL); - } - - result = true; - setModified(false); } - - updateAccess(response.access); - }, - error : function(){ - alert(LABELS.SEQUENCE_SAVE_ERROR); + }); + + if (error) { + alert(error); + return false; } - }); - - return result; -} - - -/** - * Sets new paper dimensions and moves some static elements. - */ -function resizePaper(width, height) { - if (!paper) { - return; - } - - if (!width || !height) { - var width = 0, - height = 0; - $.each(layout.activities, function(){ - // find new dimensions of paper - var activityBox = this.items.shape.getBBox(); - if (activityBox.x2 + 30 > width) { - width = activityBox.x2 + 30; - } - if (activityBox.y2 + 30 > height) { - height = activityBox.y2 + 30; - } + + // iterate over labels and regions + $.each(layout.labels.concat(layout.regions), function(){ + var box = this.items.shape.getBBox(), + isRegion = this instanceof DecorationDefs.Region; + + annotations.push({ + 'id' : this.id, + 'annotationUIID' : this.uiid, + 'title' : this.title, + 'xCoord' : box.x, + 'yCoord' : box.y, + 'endXCoord' : isRegion ? box.x2 : null, + 'endYCoord' : isRegion ? box.y2 : null, + 'color' : isRegion ? this.items.shape.attr('fill') : null + }); }); - } - - // -20 so Chrome does not create unnecessary scrollbars when dropping a tool template to canvas - // +50 so there is space for rubbish bin - width = Math.max(width, canvas.width()) - 20; - height = Math.max(height + 50, canvas.height()) - 20; - - paper.setSize(width, height); - $('#templateContainer').height($('#ldDescriptionDiv').height() - + $('#canvas').height() - 10); - - if (layout.items.bin) { - layout.items.bin.remove(); - } - - // draw rubbish bin on canvas - var binPath = Raphael.parsePathString(layout.defs.bin); - binPath = Raphael.transformPath(binPath, Raphael.format('t {0} {1}', width - 5, height - 50)); - layout.items.bin = paper.path(binPath); - $(layout.items.bin.node).attr('id', 'rubbishBin'); - - HandlerLib.resetCanvasMode(true); -} - -/** - * Tells that current sequence was modified and not saved. - */ -function setModified(modified) { - layout.modified = modified; - if (modified) { - $('#previewButton').attr('disabled', 'disabled') - .button('option', 'disabled', true); - $('#ldDescriptionFieldModified').text('*'); - } else { - $('#previewButton').attr('disabled', null) - .button('option', 'disabled', false); - - $('#ldDescriptionFieldModified').text(''); - } - - if (modified || layout.activities.length == 0) { - $('#exportButton').attr('disabled', 'disabled') - .css('opacity', 0.2); - } else { - $('#exportButton').attr('disabled', null) - .css('opacity', 1); - } -} - - -/** - * Escapes HTML tags to prevent XSS injection. - */ -function escapeHtml(unsafe) { - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - - -function updateAccess(access, fetchIfEmpty){ - if (fetchIfEmpty && !access) { + + // serialise the sequence + var ld = { + // it is null if it is a new sequence + 'learningDesignID' : learningDesignID, + 'workspaceFolderID' : folderID, + 'copyTypeID' : 1, + 'originalUserID' : null, + 'title' : title, + 'description' : description, + 'maxID' : layout.ld.maxUIID, + 'readOnly' : false, + 'editOverrideLock' : false, + 'dateReadOnly' : null, + 'version' : null, + 'contentFolderID' : layout.ld.contentFolderID, + 'saveMode' : layout.ld.learningDesignID + && layout.ld.learningDesignID != learningDesignID + ? 1 : 0, + 'originalLearningDesignID' : null, + + 'activities' : activities, + 'transitions' : transitions, + 'groupings' : groupings, + 'branchMappings' : branchMappings, + 'annotations' : annotations, + + 'helpText' : null, + 'duration' : null, + 'licenseID' : null, + 'licenseText' : null + }; + + // get LD details $.ajax({ + type : 'POST', cache : false, async : false, url : LAMS_URL + "authoring/author.do", dataType : 'json', data : { - 'method' : 'getLearningDesignAccess' + 'method' : 'saveLearningDesign', + 'ld' : JSON.stringify(ld) }, success : function(response) { - access = response; - } - }); - } - - if (access) { - var accessCell = $('#ldStoreDialogAccessCell'); - accessCell.children('div.access').remove(); - $.each(access, function(){ - $('
    ').addClass('access') - .attr({ - 'learningDesignId' : this.learningDesignId, - 'folderID' : this.workspaceFolderId - }) - .text(this.title) - .appendTo(accessCell) - .click(function(){ - var accessEntry = $(this); - if (accessEntry.hasClass('selected')) { - return; + layout.ld.folderID = folderID; + layout.ld.title = title; + + // check if there were any validation errors + if (response.validation.length > 0) { + var message = LABELS.SEQUENCE_VALIDATION_ISSUES + '\n'; + $.each(response.validation, function() { + var uiid = this.UIID, + title = ''; + if (uiid) { + // find which activity is the error about + $.each(layout.activities, function(){ + if (uiid == this.uiid) { + title = this.title + ': '; + } + }); + } + message += title + this.message + '\n'; + }); + + alert(message); + } + + // if save (even partially) was successful + if (response.ld) { + // assing the database-generated values + layout.ld.learningDesignID = response.ld.learningDesignID; + if (!layout.ld.contentFolderID) { + layout.ld.contentFolderID = response.ld.contentFolderID; + } + $('#ldDescriptionFieldTitle').text(title); + $('#ldDescriptionFieldDescription').text(description); + + // assign database-generated properties to activities + $.each(response.ld.activities, function() { + var updatedActivity = this; + $.each(layout.activities, function(){ + var isBranching = this instanceof ActivityDefs.BranchingEdgeActivity, + found = false; + if (isBranching && !this.isStart) { + return true; } - var dialog = accessEntry.closest('.ui-dialog'), - isSaveDialog = dialog.hasClass('ldStoreDialogSave'), - learningDesignID = +accessEntry.attr('learningDesignId'), - title = isSaveDialog ? accessEntry.text() : null; - - showLearningDesignThumbnail(learningDesignID, title); + if (isBranching) { + if (updatedActivity.activityUIID == this.branchingActivity.uiid){ + this.branchingActivity.id = updatedActivity.activityID; + found = true; + } else { + $.each(this.branchingActivity.branches, function(){ + if (updatedActivity.activityUIID == this.branchingActivity.uiid){ + this.id = updatedActivity.activityID; + } + }); + } + } else if (updatedActivity.activityUIID == this.uiid) { + this.id = updatedActivity.activityID; + this.toolContentID = updatedActivity.toolContentID; + found = true; + } + + // update transition IDs + $.each(this.transitions.from, function(){ + var existingTransition = this; + $.each(response.ld.transitions, function(){ + if (existingTransition.uiid == +this.transitionUIID) { + existingTransition.id = +this.transitionID || null; + return false; + } + }); + }); + + return !found; }); + }); + + if (layout.floatingActivity) { + layout.floatingActivity.id = response.floatingActivityID; + } + + // update annotation IDs + $.each(response.ld.annotations, function(){ + var updatedAnnotation = this; + $.each(layout.labels.concat(layout.regions), function(){ + if (this.uiid == updatedAnnotation.annotationUIID) { + this.id = updatedAnnotation.uid; + return false; + } + }); + }); + + if (response.validation.length == 0) { + alert(LABELS.SAVE_SUCCESSFUL); + } + + result = true; + GeneralLib.setModified(false); + } + + GeneralLib.updateAccess(response.access); + }, + error : function(){ + alert(LABELS.SEQUENCE_SAVE_ERROR); + } }); - } -} + + return result; + }, + + /** + * Tells that current sequence was modified and not saved. + */ + setModified : function(modified) { + if (isReadOnlyMode){ + return; + } + layout.modified = modified; + var activitiesExist = layout.activities.length > 0; + if (modified || !activitiesExist) { + $('#previewButton').attr('disabled', 'disabled') + .button('option', 'disabled', true); + $('.exportSequenceButton').attr('disabled', 'disabled') + .css('opacity', 0.2); + $('#ldDescriptionFieldModified').text('*'); + } else { + $('#previewButton').attr('disabled', null) + .button('option', 'disabled', false); + $('.exportSequenceButton').attr('disabled', null) + .css('opacity', 1); + $('#ldDescriptionFieldModified').text(''); + } + + if (activitiesExist) { + $('.exportImageButton').attr('disabled', null) + .css('opacity', 1); + } else { + $('.exportImageButton').attr('disabled', 'disabled') + .css('opacity', 0.2); + } + }, -function showLearningDesignThumbnail(learningDesignID, title) { - // display "loading" animation and finally LD thumbnail - $('.ldChoiceDependentCanvasElement').css('display', 'none'); - if (learningDesignID) { - var dialogContent = $('#ldStoreDialog'); - $('#ldScreenshotLoading', dialogContent).css('display', 'inline'); - // get the image of the chosen LD and prevent caching - $('#ldScreenshotAuthor', dialogContent) - .attr('src', LD_THUMBNAIL_URL_BASE + learningDesignID + '&_=' + new Date().getTime()) - .css({ - 'width' : 'auto', - 'height' : 'auto' + + /** + * Displays sequence image in Open/Save dialog. + */ + showLearningDesignThumbnail : function(learningDesignID, title) { + // display "loading" animation and finally LD thumbnail + $('.ldChoiceDependentCanvasElement').css('display', 'none'); + if (learningDesignID) { + var dialogContent = $('#ldStoreDialog'); + $('#ldScreenshotLoading', dialogContent).css('display', 'inline'); + // get the image of the chosen LD and prevent caching + $('#ldScreenshotAuthor', dialogContent) + .attr('src', LD_THUMBNAIL_URL_BASE + learningDesignID + '&_=' + new Date().getTime()) + .css({ + 'width' : 'auto', + 'height' : 'auto' + }); + if (title) { + // copy title of the highligthed sequence to title field + $('#ldStoreDialogNameField').val(title).focus(); + } + + var tree = $('#ldStoreDialog').dialog('option', 'ldTree'), + ldNode = tree.getHighlightedNode(); + // no LD was chosen + if (ldNode && learningDesignID != ldNode.data.learningDesignId) { + ldNode.unhighlight(true); + } + } + + $('#ldStoreDialogAccessCell > div.access', dialogContent).each(function(){ + var access = $(this); + if (+access.attr('learningDesignId') == learningDesignID){ + access.addClass('selected'); + } else { + access.removeClass('selected'); + } }); - if (title) { - // copy title of the highligthed sequence to title field - $('#ldStoreDialogNameField').val(title).focus(); + }, + + + /** + * Get real coordinates on paper, based on event coordinates. + */ + translateEventOnCanvas : function(event) { + return [event.pageX + canvas.scrollLeft() - canvas.offset().left, + event.pageY + canvas.scrollTop() - canvas.offset().top]; + }, + + + /** + * Fills "Recently used sequences" box in Open/Save dialog. + */ + updateAccess : function(access, fetchIfEmpty){ + if (isReadOnlyMode) { + return; } - var tree = $('#ldStoreDialog').dialog('option', 'ldTree'), - ldNode = tree.getHighlightedNode(); - // no LD was chosen - if (ldNode && learningDesignID != ldNode.data.learningDesignId) { - ldNode.unhighlight(true); + if (fetchIfEmpty && !access) { + $.ajax({ + cache : false, + async : false, + url : LAMS_URL + "authoring/author.do", + dataType : 'json', + data : { + 'method' : 'getLearningDesignAccess' + }, + success : function(response) { + access = response; + } + }); } - } - - $('#ldStoreDialogAccessCell > div.access', dialogContent).each(function(){ - var access = $(this); - if (+access.attr('learningDesignId') == learningDesignID){ - access.addClass('selected'); - } else { - access.removeClass('selected'); + + if (access) { + var accessCell = $('#ldStoreDialogAccessCell'); + accessCell.children('div.access').remove(); + $.each(access, function(){ + $('
    ').addClass('access') + .attr({ + 'learningDesignId' : this.learningDesignId, + 'folderID' : this.workspaceFolderId + }) + .text(this.title) + .appendTo(accessCell) + .click(function(){ + var accessEntry = $(this); + if (accessEntry.hasClass('selected')) { + return; + } + + var dialog = accessEntry.closest('.ui-dialog'), + isSaveDialog = dialog.hasClass('ldStoreDialogSave'), + learningDesignID = +accessEntry.attr('learningDesignId'), + title = isSaveDialog ? accessEntry.text() : null; + + GeneralLib.showLearningDesignThumbnail(learningDesignID, title); + }); + }); } - }); -} \ No newline at end of file + } +}; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringHandler.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringHandler.js,v diff -u -r1.14 -r1.15 --- lams_central/web/includes/javascript/authoring/authoringHandler.js 27 May 2014 11:54:41 -0000 1.14 +++ lams_central/web/includes/javascript/authoring/authoringHandler.js 2 Jun 2014 07:27:16 -0000 1.15 @@ -2,64 +2,12 @@ * This file contains event handlers for interaction with canvas and other Authoring elements. */ +/** + * Contains general (canvas, group of shapes) action handlers + */ var HandlerLib = { /** - * Default mode for canvas. Run after special mode is no longer needed. - */ - resetCanvasMode : function(init){ - // so elements understand that no events should be triggered, i.e. canvas is "dead" - layout.drawMode = !init; - - // remove selection if exists - ActivityLib.removeSelectEffect(); - canvas.css('cursor', 'default') - .off('click') - .off('mousedown') - .off('mouseup') - .off('mousemove'); - - if (init) { - // if clicked anywhere, activity selection is gone - canvas.click(HandlerLib.canvasClickHandler) - // when mouse gets closer to properties dialog, make it fully visible - .mousemove(HandlerLib.approachPropertiesDialogHandler); - } - }, - - - /** - * Makes properties dialog fully visible. - */ - approachPropertiesDialogHandler : function(event) { - // properties dialog is a singleton - var dialog = layout.items.propertiesDialog; - // do not run this method too often - var thisRun = new Date().getTime(); - if (thisRun - dialog.lastRun < layout.conf.propertiesDialogDimThrottle){ - return; - } - dialog.lastRun = thisRun; - - // is the dialog visible at all? - if (layout.items.selectedObject) { - // calculate dim/show threshold - var container = dialog.container, - dialogPosition = container.offset(), - dialogStartX = dialogPosition.left, - dialogStartY = dialogPosition.top, - dialogEndX = dialogStartX + container.width(), - dialogEndY = dialogStartY + container.height(), - dimTreshold = layout.conf.propertiesDialogDimThreshold, - tooFarX = event.pageX < dialogStartX - dimTreshold || event.pageX > dialogEndX + dimTreshold, - tooFarY = event.pageY < dialogStartY - dimTreshold || event.pageY > dialogEndY + dimTreshold, - opacity = tooFarX || tooFarY ? layout.conf.propertiesDialogDimOpacity : 1; - - container.css('opacity', opacity); - } - }, - - /** * Remove activity selection when user clicks on canvas. */ canvasClickHandler : function(event) { @@ -75,12 +23,13 @@ /** - * Start dragging an activity or transition. + * Start dragging an activity or a transition. */ dragItemsStartHandler : function(items, draggedElement, mouseupHandler, event, startX, startY) { // clear "clicked" flag, just in case items.clicked = false; + // if there is already a function waiting to be started, clear it if (items.dragStarter) { // prevent confusion when double clicking clearTimeout(items.dragStarter); @@ -108,16 +57,16 @@ items.attr('cursor', 'move'); var parentObject = draggedElement.data('parentObject'); - sticky = parentObject && (parentObject instanceof ActivityLib.ParallelActivity - || parentObject instanceof ActivityLib.OptionalActivity - || parentObject instanceof ActivityLib.FloatingActivity); + sticky = parentObject && (parentObject instanceof ActivityDefs.ParallelActivity + || parentObject instanceof ActivityDefs.OptionalActivity + || parentObject instanceof ActivityDefs.FloatingActivity); // hide child activities while moving the parent around if (sticky) { - $.each(parentObject.childActivities, function(){ + $.each(parentObject.childActivityDefs, function(){ this.items.hide(); - if (this.childActivities) { - $.each(this.childActivities, function() { + if (this.childActivityDefs) { + $.each(this.childActivityDefs, function() { this.items.hide(); }); } @@ -132,7 +81,7 @@ // finish dragging - restore various elements' default state items.isDragged = false; items.unmouseup(); - layout.items.bin.attr('fill', 'transparent'); + layout.bin.attr('fill', 'transparent'); // do whetver needs to be done with the dragged elements mouseupHandler(mouseupEvent); @@ -144,7 +93,7 @@ canvas.mouseup(mouseup); items.mouseup(mouseup); - setModified(true); + GeneralLib.setModified(true); }, layout.conf.dragStartThreshold); }, @@ -164,106 +113,229 @@ // highlight rubbish bin if dragged elements are over it if (HandlerLib.isElemenentBinned(event)) { - layout.items.bin.attr('fill', layout.colors.binActive); + layout.bin.attr('fill', layout.colors.binActive); } else { - layout.items.bin.attr('fill', 'transparent'); + layout.bin.attr('fill', 'transparent'); } }, + /** - * Start drawing a transition. + * Rewrites a shape's coordinates, so it is where the user dropped it. */ - drawTransitionStartHandler : function(activity, event, x, y) { - if (activity.fromTransition && !(activity instanceof ActivityLib.BranchingEdgeActivity)) { - alert(LABELS.TRANSITION_FROM_EXISTS_ERROR); + dropObject : function(object) { + // finally transform the dragged elements + var transformation = object.items.shape.attr('transform'); + object.items.transform(''); + if (transformation.length > 0) { + // find new X and Y and redraw the object + var box = object.items.shape.getBBox(), + x = box.x, + // adjust this coordinate for annotation labels + y = box.y + (object instanceof DecorationDefs.Label ? 6 : 0); + object.draw(x + transformation[0][1], + y + transformation[0][2]); } - HandlerLib.resetCanvasMode(); - - var startX = x + canvas.scrollLeft() - canvas.offset().left, - startY = y + canvas.scrollTop() - canvas.offset().top; - - canvas.mousemove(function(event){ - HandlerLib.drawTransitionMoveHandler(activity, event, startX, startY); - }) - .mouseup(function(event){ - HandlerLib.drawTransitionEndHandler(activity, event); - }); + // add space if dropped object is next to border + GeneralLib.resizePaper(); }, /** - * Keep drawing a transition. + * Checks whether activity or transition is over rubbish bin. */ - drawTransitionMoveHandler : function(activity, event, startX, startY) { - // remove the temporary transition (dashed line) - if (activity.tempTransition) { - activity.tempTransition.remove(); - activity.tempTransition = null; + isElemenentBinned : function(event) { + var translatedEvent = GeneralLib.translateEventOnCanvas(event); + return Raphael.isPointInsideBBox(layout.bin.getBBox(), translatedEvent[0], translatedEvent[1]); + }, + + + /** + * Selects an activity/transition/annotation. + */ + itemClickHandler : function(event) { + if (layout.drawMode || (event.originalEvent ? + event.originalEvent.defaultPrevented : event.defaultPrevented)){ + return; } - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - endX = translatedEvent[0], - endY = translatedEvent[1]; + var parentObject = this.data('parentObject'); + // inform that user wants to select, not drag the activity + parentObject.items.clicked = true; + if (parentObject != layout.selectedObject) { + HandlerLib.canvasClickHandler(event); + ActivityLib.addSelectEffect(parentObject, true); + } - // draw a temporary transition so user sees what he is doing - activity.tempTransition = paper.set(); - activity.tempTransition.push(paper.circle(startX, startY, 3)); - activity.tempTransition.push(paper.path(Raphael.format('M {0} {1} L {2} {3}', - startX, startY, endX, endY)) - .attr({ - 'arrow-end' : 'open-wide-long', - 'stroke-dasharray' : '- ' - })); + // so canvas handler unselectActivityHandler() is not run + event.preventDefault(); }, /** - * Finalise transition drawing. + * Default mode for canvas. Run after draw mode is no longer needed. */ - drawTransitionEndHandler : function(activity, event) { - // prevent triggering event on several activity items; we just need it on transition - event.stopImmediatePropagation(); - event.preventDefault(); + resetCanvasMode : function(init){ + // so elements understand that no events should be triggered, i.e. canvas is "dead" + layout.drawMode = !init; - //remove the temporary transition (dashed line) - if (activity.tempTransition) { - activity.tempTransition.remove(); - activity.tempTransition = null; + // remove selection if exists + ActivityLib.removeSelectEffect(); + canvas.css('cursor', 'default') + .off('click') + .off('mousedown') + .off('mouseup') + .off('mousemove'); + + if (init) { + // if clicked anywhere, activity selection is gone + canvas.click(HandlerLib.canvasClickHandler) + // when mouse gets closer to properties dialog, make it fully visible + .mousemove(HandlerPropertyLib.approachPropertiesDialogHandler); } + } +}, + + + +/** + * Contains handlers for actions over Activities. + */ +HandlerActivityLib = { - var endActivity = null, - targetElement = paper.getElementByPoint(event.pageX, event.pageY); - if (targetElement) { - endActivity = targetElement.data('parentObject'); + /** + * Double click opens activity authoring. + */ + activityDblclickHandler : function(event) { + var activity = this.data('parentObject'); + // inform that user wants to open, not drag the activity + activity.items.clicked = true; + ActivityLib.openActivityAuthoring(activity); + }, + + + /** + * Starts drawing a transition or dragging an activity. + */ + activityMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? + event.originalEvent.defaultPrevented : event.defaultPrevented)){ + return; } + + var activity = this.data('parentObject'); + if (event.ctrlKey) { + // when CTRL is held down, start drawing a transition + HandlerTransitionLib.drawTransitionStartHandler(activity, event, x, y); + } else if (!activity.parentActivity + || !(activity.parentActivity instanceof ActivityDefs.ParallelActivity)){ + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the activity was over rubbish bin, remove it + ActivityLib.removeActivity(activity); + } else { + // finalise movement - rewrite coordinates, see if the activity was not added to a container + HandlerLib.dropObject(activity); - if (endActivity && activity != endActivity) { - ActivityLib.addTransition(activity, endActivity); + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + endX = translatedEvent[0], + endY = translatedEvent[1]; + ActivityLib.dropActivity(activity, endX, endY); + } + } + // start dragging the activity + HandlerLib.dragItemsStartHandler(activity.items, this, mouseupHandler, event, x, y); } - - HandlerLib.resetCanvasMode(true); }, /** + * Lighthens up branching edges in the same colour for identifictation. + */ + branchingEdgeMouseoverHandler : function() { + var branchingActivity = this.data('parentObject').branchingActivity, + startItems = branchingActivity.start.items, + endItems = branchingActivity.end.items; + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.attr('fill', layout.colors.branchingEdgeMatch); + endItems.shape.attr('fill', layout.colors.branchingEdgeMatch); + } + }, + + + /** + * Return branching edges to their normal colours. + */ + branchingEdgeMouseoutHandler : function() { + var branchingActivity = this.data('parentObject').branchingActivity, + startItems = branchingActivity.start.items, + endItems = branchingActivity.end.items; + + if (!startItems.isDragged && !endItems.isDragged) { + startItems.shape.attr('fill', layout.colors.branchingEdgeStart); + endItems.shape.attr('fill', layout.colors.branchingEdgeEnd); + } + } +}, + + + +/** + * Contains handlers for actions over Decoration elements. + */ +HandlerDecorationLib = { + + /** + * Starts dragging a container + */ + containerMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? + event.originalEvent.defaultPrevented : event.defaultPrevented)){ + return; + } + + var container = this.data('parentObject'); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the container was over rubbish bin, remove it + if (container instanceof DecorationDefs.Region) { + DecorationLib.removeRegion(container); + } else { + ActivityLib.removeActivity(container); + } + } else { + HandlerLib.dropObject(container); + if (container instanceof ActivityDefs.FloatingActivity + || container instanceof ActivityDefs.OptionalActivity + || container instanceof ActivityDefs.ParallelActivity) { + ActivityLib.dropActivity(container); + } + } + } + + HandlerLib.dragItemsStartHandler(container.items, this, mouseupHandler, event, x, y); + }, + + /** * Start drawing a region. */ drawRegionStartHandler : function(startEvent) { HandlerLib.resetCanvasMode(); // remember what were the drawing start coordinates - var translatedEvent = ActivityLib.translateEventOnCanvas(startEvent), + var translatedEvent = GeneralLib.translateEventOnCanvas(startEvent), data = { 'startX' : translatedEvent[0], 'startY' : translatedEvent[1] }; canvas.mousemove(function(event){ - HandlerLib.drawRegionMoveHandler(data, event); + HandlerDecorationLib.drawRegionMoveHandler(data, event); }) .mouseup(function(event){ - HandlerLib.drawRegionEndHandler(data); + HandlerDecorationLib.drawRegionEndHandler(data); }); }, @@ -272,7 +344,7 @@ * Keep drawing a region. */ drawRegionMoveHandler : function(data, event) { - var translatedEvent = ActivityLib.translateEventOnCanvas(event), + var translatedEvent = GeneralLib.translateEventOnCanvas(event), x = translatedEvent[0], y = translatedEvent[1]; @@ -292,9 +364,9 @@ }); // immediatelly show which activities will be enveloped - var childActivities = DecorationLib.getChildActivities(data.shape); + var childActivityDefs = DecorationLib.getChildActivityDefs(data.shape); $.each(layout.activities, function(){ - if (!this.parentActivity && $.inArray(this, childActivities) > -1){ + if (!this.parentActivity && $.inArray(this, childActivityDefs) > -1){ ActivityLib.addSelectEffect(this, false); } else { ActivityLib.removeSelectEffect(this); @@ -323,6 +395,30 @@ /** + * Starts dragging a label + */ + labelMousedownHandler : function(event, x, y){ + if (layout.drawMode || (event.originalEvent ? + event.originalEvent.defaultPrevented : event.defaultPrevented)){ + return; + } + + var label = this.data('parentObject'); + // allow transition dragging + var mouseupHandler = function(event){ + if (HandlerLib.isElemenentBinned(event)) { + // if the region was over rubbish bin, remove it + DecorationLib.removeLabel(label); + } else { + HandlerLib.dropObject(label); + } + } + + HandlerLib.dragItemsStartHandler(label.items, this, mouseupHandler, event, x, y); + }, + + + /** * Start resizing a region. */ resizeRegionStartHandler : function(event) { @@ -335,126 +431,153 @@ var region = this.data('parentObject'); canvas.mousemove(function(event){ - HandlerLib.resizeRegionMoveHandler(region, event); + HandlerDecorationLib.resizeRegionMoveHandler(region, event); }) .mouseup(function(){ HandlerLib.resetCanvasMode(true); ActivityLib.addSelectEffect(region, true); }); - setModified(true); + GeneralLib.setModified(true); }, /** * Keep resising a region. */ resizeRegionMoveHandler : function(region, event){ - var translatedEvent = ActivityLib.translateEventOnCanvas(event), + var translatedEvent = GeneralLib.translateEventOnCanvas(event), x = translatedEvent[0], y = translatedEvent[1]; // keep the initial coordinates and adjust end coordinates region.draw(null, null, x, y); - }, - - + } +}, + + + +/** + * Contains handlers for actions over Properties dialog. + */ +HandlerPropertyLib = { + /** - * Lighthens up branching edges in the same colour for identifictation. + * Makes properties dialog fully visible. */ - branchingEdgeMouseoverHandler : function() { - var branchingActivity = this.data('parentObject').branchingActivity, - startItems = branchingActivity.start.items, - endItems = branchingActivity.end.items; - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.attr('fill', layout.colors.branchingEdgeMatch); - endItems.shape.attr('fill', layout.colors.branchingEdgeMatch); + approachPropertiesDialogHandler : function(event) { + // properties dialog is a singleton + var dialog = layout.propertiesDialog, + // do not run this method too often + thisRun = new Date().getTime(); + if (thisRun - dialog.lastRun < layout.conf.propertiesDialogDimThrottle){ + return; } - }, - - + dialog.lastRun = thisRun; + + // is the dialog visible at all? + if (layout.selectedObject) { + // calculate dim/show threshold + var container = dialog.container, + dialogPosition = container.offset(), + dialogStartX = dialogPosition.left, + dialogStartY = dialogPosition.top, + dialogEndX = dialogStartX + container.width(), + dialogEndY = dialogStartY + container.height(), + dimTreshold = layout.conf.propertiesDialogDimThreshold, + tooFarX = event.pageX < dialogStartX - dimTreshold || event.pageX > dialogEndX + dimTreshold, + tooFarY = event.pageY < dialogStartY - dimTreshold || event.pageY > dialogEndY + dimTreshold, + opacity = tooFarX || tooFarY ? layout.conf.propertiesDialogDimOpacity : 1; + + container.css('opacity', opacity); + } + } +}, + + + +/** + * Contains handlers for actions over Transitions + */ +HandlerTransitionLib = { + /** - * Return branching edges to their normal colours. + * Start drawing a transition. */ - branchingEdgeMouseoutHandler : function() { - var branchingActivity = this.data('parentObject').branchingActivity, - startItems = branchingActivity.start.items, - endItems = branchingActivity.end.items; - - if (!startItems.isDragged && !endItems.isDragged) { - startItems.shape.attr('fill', layout.colors.branchingEdgeStart); - endItems.shape.attr('fill', layout.colors.branchingEdgeEnd); + drawTransitionStartHandler : function(activity, event, x, y) { + if (activity.fromTransition && !(activity instanceof ActivityDefs.BranchingEdgeActivity)) { + alert(LABELS.TRANSITION_FROM_EXISTS_ERROR); } + + HandlerLib.resetCanvasMode(); + + var startX = x + canvas.scrollLeft() - canvas.offset().left, + startY = y + canvas.scrollTop() - canvas.offset().top; + + canvas.mousemove(function(event){ + HandlerTransitionLib.drawTransitionMoveHandler(activity, event, startX, startY); + }) + .mouseup(function(event){ + HandlerTransitionLib.drawTransitionEndHandler(activity, event); + }); }, /** - * Starts drawing a transition or dragging an activity. + * Keep drawing a transition. */ - activityMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? - event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; + drawTransitionMoveHandler : function(activity, event, startX, startY) { + // remove the temporary transition (dashed line) + if (activity.tempTransition) { + activity.tempTransition.remove(); + activity.tempTransition = null; } - var activity = this.data('parentObject'); - if (event.ctrlKey) { - // when CTRL is held down, start drawing a transition - HandlerLib.drawTransitionStartHandler(activity, event, x, y); - } else if (!activity.parentActivity - || !(activity.parentActivity instanceof ActivityLib.ParallelActivity)){ - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the activity was over rubbish bin, remove it - ActivityLib.removeActivity(activity); - } else { - HandlerLib.dropObject(activity); - - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - endX = translatedEvent[0], - endY = translatedEvent[1]; - ActivityLib.dropActivity(activity, endX, endY); - } - } - // start dragging the activity - HandlerLib.dragItemsStartHandler(activity.items, this, mouseupHandler, event, x, y); - } + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + endX = translatedEvent[0], + endY = translatedEvent[1]; + + // draw a temporary transition so user sees what he is doing + activity.tempTransition = paper.set(); + activity.tempTransition.push(paper.circle(startX, startY, 3)); + activity.tempTransition.push(paper.path(Raphael.format('M {0} {1} L {2} {3}', + startX, startY, endX, endY)) + .attr({ + 'arrow-end' : 'open-wide-long', + 'stroke-dasharray' : '- ' + })); }, /** - * Selects an activity. + * Finalise transition drawing. */ - itemClickHandler : function(event) { - if (layout.drawMode || (event.originalEvent ? - event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; + drawTransitionEndHandler : function(activity, event) { + // prevent triggering event on several activity items; we just need it on transition + event.stopImmediatePropagation(); + event.preventDefault(); + + //remove the temporary transition (dashed line) + if (activity.tempTransition) { + activity.tempTransition.remove(); + activity.tempTransition = null; } - var parentObject = this.data('parentObject'); - // inform that user wants to select, not drag the activity - parentObject.items.clicked = true; - if (parentObject != layout.items.selectedObject) { - HandlerLib.canvasClickHandler(event); - ActivityLib.addSelectEffect(parentObject, true); + var endActivity = null, + targetElement = paper.getElementByPoint(event.pageX, event.pageY); + if (targetElement) { + endActivity = targetElement.data('parentObject'); } + + if (endActivity && activity != endActivity) { + ActivityLib.addTransition(activity, endActivity); + } - // so canvas handler unselectActivityHandler() is not run - event.preventDefault(); + HandlerLib.resetCanvasMode(true); }, - /** - * Opens activity authoring. - */ - activityDblclickHandler : function(event) { - var activity = this.data('parentObject'); - // inform that user wants to open, not drag the activity - activity.items.clicked = true; - ActivityLib.openActivityAuthoring(activity); - }, - /** * Starts dragging a transition. */ @@ -471,93 +594,5 @@ } HandlerLib.dragItemsStartHandler(transition.items, this, mouseupHandler, event, x, y); - }, - - - /** - * Starts dragging a container - */ - containerMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? - event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var container = this.data('parentObject'); - // allow transition dragging - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the container was over rubbish bin, remove it - if (container instanceof DecorationLib.Region) { - DecorationLib.removeRegion(container); - } else { - ActivityLib.removeActivity(container); - } - } else { - HandlerLib.dropObject(container); - if (container instanceof ActivityLib.FloatingActivity - || container instanceof ActivityLib.OptionalActivity - || container instanceof ActivityLib.ParallelActivity) { - ActivityLib.dropActivity(container); - } - } - } - - HandlerLib.dragItemsStartHandler(container.items, this, mouseupHandler, event, x, y); - }, - - - /** - * Starts dragging a label - */ - labelMousedownHandler : function(event, x, y){ - if (layout.drawMode || (event.originalEvent ? - event.originalEvent.defaultPrevented : event.defaultPrevented)){ - return; - } - - var label = this.data('parentObject'); - // allow transition dragging - var mouseupHandler = function(event){ - if (HandlerLib.isElemenentBinned(event)) { - // if the region was over rubbish bin, remove it - DecorationLib.removeLabel(label); - } else { - HandlerLib.dropObject(label); - } - } - - HandlerLib.dragItemsStartHandler(label.items, this, mouseupHandler, event, x, y); - }, - - - dropObject : function(object) { - // finally transform the dragged elements - var transformation = object.items.shape.attr('transform'); - object.items.transform(''); - if (transformation.length > 0) { - // find new X and Y and redraw the object - var box = object.items.shape.getBBox(), - x = box.x, - // adjust this coordinate for annotation labels - y = box.y + (object instanceof DecorationLib.Label ? 6 : 0); - object.draw(x + transformation[0][1], - y + transformation[0][2]); - } - - // add space if dropped object is next to border - resizePaper(); - }, - - - /** - * Checks whether activity or transition is over rubbish bin. - */ - isElemenentBinned : function(event) { - var translatedEvent = ActivityLib.translateEventOnCanvas(event); - - // highlight rubbish bin if dragged elements are over it - return Raphael.isPointInsideBBox(layout.items.bin.getBBox(), - translatedEvent[0], translatedEvent[1]); } }; \ No newline at end of file Index: lams_central/web/includes/javascript/authoring/authoringMenu.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringMenu.js,v diff -u -r1.29 -r1.30 --- lams_central/web/includes/javascript/authoring/authoringMenu.js 27 May 2014 11:54:41 -0000 1.29 +++ lams_central/web/includes/javascript/authoring/authoringMenu.js 2 Jun 2014 07:27:16 -0000 1.30 @@ -19,7 +19,6 @@ primary : "ui-icon-triangle-1-s" } }); - buttonContainer.buttonset().next().hide().menu(); buttons.each(function(){ var button = $(this); @@ -37,64 +36,88 @@ }); } }); + + buttonContainer.buttonset().next().hide().menu().children().each(function(){ + var menuItem = $(this), + subMenu = menuItem.children('ul'); + if (subMenu.length > 0){ + + menuItem.click(function(){ + var menu = $(this).children('ul').show().position({ + my : "left+2px top", + at : "right top", + of : this + }); + $(document).one("click", function() { + menu.hide(); + }); + return false; + }); + } + }); }); - }, - - - /** - * Run when branching is selected from menu. Allows placing branching and converge points on canvas. - */ - addBranching : function(){ - HandlerLib.resetCanvasMode(); - var dialog = layout.items.infoDialog.text(LABELS.BRANCHING_START_PLACE_PROMPT); - dialog.dialog('open'); - var branchingActivity = null; - canvas.css('cursor', 'pointer').click(function(event){ - // pageX and pageY tell event coordinates relative to the whole page - // we need relative to canvas - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - x = translatedEvent[0] - 6, - y = translatedEvent[1] - 8; - - // if it is start point, branchingActivity is null and constructor acts accordingly - var branchingEdge = new ActivityLib.BranchingEdgeActivity(null, null, x, y, null, null, branchingActivity); - layout.activities.push(branchingEdge); - - if (branchingActivity) { - // converge point was just place, end of function - HandlerLib.resetCanvasMode(true); - - dialog.text('').dialog('close'); - - setModified(true); - } else { - // extract main branchingActivity structure from created start point - branchingActivity = branchingEdge.branchingActivity; - dialog.text(LABELS.BRANCHING_END_PLACE_PROMPT); + + // dialog allowing to save canvas as SVG or PNG image + layout.exportImageDialog = $('#exportImageDialog').dialog({ + 'autoOpen' : false, + 'width' : 350, + 'height' : 75, + 'show' : 'fold', + 'hide' : 'fold', + 'draggable': false, + 'resizable': false, + 'modal' : true, + 'title' : LABELS.EXPORT_IMAGE_DIALOG_TITLE + }).click(function(){ + layout.exportImageDialog.dialog('close'); + }); + + layout.dialogs.push(layout.exportImageDialog); + + + // dialog for downloading the sequence as ZIP + layout.exportLDDialog = $('#exportLDDialog').dialog({ + 'autoOpen' : false, + 'width' : 320, + 'height' : 120, + 'show' : 'fold', + 'hide' : 'fold', + 'draggable': false, + 'resizable': false, + 'modal' : true, + 'title' : LABELS.EXPORT_SEQUENCE_DIALOG_TITLE, + 'beforeClose' : function(){ + $('iframe', layout.exportLDDialog).attr('src', null); } + }).click(function(){ + layout.exportLDDialog.dialog('close'); }); + layout.dialogs.push(layout.exportLDDialog); }, /** - * Creates a new grouping activity. + * Creates a new annotation label. */ - addGrouping : function() { + addAnnotationLabel : function() { HandlerLib.resetCanvasMode(); - canvas.css('cursor', 'url("' + layout.toolMetadata.grouping.iconPath + '"), move') - .click(function(event){ - // pageX and pageY tell event coordinates relative to the whole page - // we need relative to canvas - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - x = translatedEvent[0] - 47, - y = translatedEvent[1] - 2; + var dialog = layout.infoDialog.text(LABELS.ANNOTATION_LABEL_PLACE_PROMPT); + dialog.dialog('open'); + + canvas.css('cursor', 'pointer').click(function(event){ + dialog.text(''); + dialog.dialog('close'); - layout.activities.push(new ActivityLib.GroupingActivity(null, null, x, y)); + + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0], + y = translatedEvent[1]; - setModified(true); HandlerLib.resetCanvasMode(true); + + DecorationLib.addLabel(x, y); }); }, @@ -105,7 +128,7 @@ addAnnotationRegion : function() { HandlerLib.resetCanvasMode(); - var dialog = layout.items.infoDialog.text(LABELS.ANNOTATION_REGION_PLACE_PROMPT); + var dialog = layout.infoDialog.text(LABELS.ANNOTATION_REGION_PLACE_PROMPT); dialog.dialog('open'); canvas.css('cursor', 'crosshair').mousedown(function(event){ @@ -118,105 +141,159 @@ // cancel HandlerLib.resetCanvasMode(true); } else { - HandlerLib.drawRegionStartHandler(event); + HandlerDecorationLib.drawRegionStartHandler(event); } }); }, /** - * Creates a new annotation label. + * Run when branching is selected from menu. Allows placing branching and converge points on canvas. */ - addAnnotationLabel : function() { + addBranching : function(){ HandlerLib.resetCanvasMode(); - - var dialog = layout.items.infoDialog.text(LABELS.ANNOTATION_LABEL_PLACE_PROMPT); + var dialog = layout.infoDialog.text(LABELS.BRANCHING_START_PLACE_PROMPT); dialog.dialog('open'); - + + var branchingActivity = null; canvas.css('cursor', 'pointer').click(function(event){ - dialog.text(''); - dialog.dialog('close'); - - - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - x = translatedEvent[0], - y = translatedEvent[1]; + // pageX and pageY tell event coordinates relative to the whole page + // we need relative to canvas + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0] - 6, + y = translatedEvent[1] - 8; - HandlerLib.resetCanvasMode(true); + // if it is start point, branchingActivity is null and constructor acts accordingly + var branchingEdge = new ActivityDefs.BranchingEdgeActivity(null, null, x, y, null, null, branchingActivity); + layout.activities.push(branchingEdge); - DecorationLib.addLabel(x, y); + if (branchingActivity) { + // converge point was just place, end of function + HandlerLib.resetCanvasMode(true); + + dialog.text('').dialog('close'); + + GeneralLib.setModified(true); + } else { + // extract main branchingActivity structure from created start point + branchingActivity = branchingEdge.branchingActivity; + dialog.text(LABELS.BRANCHING_END_PLACE_PROMPT); + } }); }, - + /** - * Creates a new optional activity. + * Creates a new floating activity. */ - addOptionalActivity : function() { + addFloatingActivity : function() { + if (layout.floatingActivity) { + // there can be only one + return; + } HandlerLib.resetCanvasMode(); - var dialog = layout.items.infoDialog.text(LABELS.OPTIONAL_ACTIVITY_PLACE_PROMPT); + var dialog = layout.infoDialog.text(LABELS.SUPPORT_ACTIVITY_PLACE_PROMPT); dialog.dialog('open'); canvas.css('cursor', 'pointer').click(function(event){ dialog.text(''); dialog.dialog('close'); - var translatedEvent = ActivityLib.translateEventOnCanvas(event), + var translatedEvent = GeneralLib.translateEventOnCanvas(event), x = translatedEvent[0], y = translatedEvent[1]; - setModified(true); + GeneralLib.setModified(true); HandlerLib.resetCanvasMode(true); - layout.activities.push(new ActivityLib.OptionalActivity(null, null, x, y)); + // do not add it to layout.activities as it behaves differently + new ActivityDefs.FloatingActivity(null, null, x, y); + + // there can be only one, so disable the button + $('#floatingActivityButton').attr('disabled', 'disabled') + .css('opacity', 0.2); }); }, + + /** + * Creates a new gate activity. + */ + addGate : function() { + HandlerLib.resetCanvasMode(); + + canvas.css('cursor', 'url("' + layout.toolMetadata.gate.iconPath + '"), move').click(function(event){ + // pageX and pageY tell event coordinates relative to the whole page + // we need relative to canvas + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0], + y = translatedEvent[1] + 2; + + layout.activities.push(new ActivityDefs.GateActivity(null, null, x, y)); + + GeneralLib.setModified(true); + HandlerLib.resetCanvasMode(true); + }); + }, + /** - * Creates a new floating activity. + * Creates a new grouping activity. */ - addFloatingActivity : function() { - if (layout.floatingActivity) { - // there can be only one - return; - } + addGrouping : function() { HandlerLib.resetCanvasMode(); - var dialog = layout.items.infoDialog.text(LABELS.SUPPORT_ACTIVITY_PLACE_PROMPT); + canvas.css('cursor', 'url("' + layout.toolMetadata.grouping.iconPath + '"), move') + .click(function(event){ + // pageX and pageY tell event coordinates relative to the whole page + // we need relative to canvas + var translatedEvent = GeneralLib.translateEventOnCanvas(event), + x = translatedEvent[0] - 47, + y = translatedEvent[1] - 2; + + layout.activities.push(new ActivityDefs.GroupingActivity(null, null, x, y)); + + GeneralLib.setModified(true); + HandlerLib.resetCanvasMode(true); + }); + }, + + + /** + * Creates a new optional activity. + */ + addOptionalActivity : function() { + HandlerLib.resetCanvasMode(); + + var dialog = layout.infoDialog.text(LABELS.OPTIONAL_ACTIVITY_PLACE_PROMPT); dialog.dialog('open'); canvas.css('cursor', 'pointer').click(function(event){ dialog.text(''); dialog.dialog('close'); - var translatedEvent = ActivityLib.translateEventOnCanvas(event), + var translatedEvent = GeneralLib.translateEventOnCanvas(event), x = translatedEvent[0], y = translatedEvent[1]; - setModified(true); + GeneralLib.setModified(true); HandlerLib.resetCanvasMode(true); - // do not add it to layout.activities as it behaves differently - new ActivityLib.FloatingActivity(null, null, x, y); - - // there can be only one, so disable the button - $('#floatingActivityButton').attr('disabled', 'disabled') - .css('opacity', 0.2); + layout.activities.push(new ActivityDefs.OptionalActivity(null, null, x, y)); }); }, - + /** * Creates a new transition. */ addTransition : function() { HandlerLib.resetCanvasMode(); - var dialog = layout.items.infoDialog.text(LABELS.TRANSITION_PLACE_PROMPT); + var dialog = layout.infoDialog.text(LABELS.TRANSITION_PLACE_PROMPT); dialog.dialog('open'); canvas.css('cursor', 'pointer').click(function(event){ @@ -229,99 +306,188 @@ if (targetElement) { startActivity = targetElement.data('parentObject'); if (startActivity) { - HandlerLib.drawTransitionStartHandler(startActivity, null, event.pageX, event.pageY); + HandlerPropertyLib.approachPropertiesDialogHandler(startActivity, null, event.pageX, event.pageY); } } }); }, /** - * Creates a new gate activity. + * Mark an activity as ready for pasting. */ - addGate : function() { - HandlerLib.resetCanvasMode(); - - canvas.css('cursor', 'url("' + layout.toolMetadata.gate.iconPath + '"), move').click(function(event){ - // pageX and pageY tell event coordinates relative to the whole page - // we need relative to canvas - var translatedEvent = ActivityLib.translateEventOnCanvas(event), - x = translatedEvent[0], - y = translatedEvent[1] + 2; - - layout.activities.push(new ActivityLib.GateActivity(null, null, x, y)); - - setModified(true); - HandlerLib.resetCanvasMode(true); - }); + copyActivity : function(){ + layout.copiedActivity = layout.selectedObject; }, - + /** - * Opens "Open sequence" dialog where an user can choose a Learning Design to load. + * Opens a pop up for exporting LD. */ - openLearningDesign : function(){ - var dialog = $('#ldStoreDialog'); - // remove the directory tree, if it remained for last dialog opening - dialog.dialog('option', { - 'title' : 'Open sequence', - 'buttons' : dialog.dialog('option', 'buttonsLoad'), - // it informs widgets that it is load dialog - 'dialogClass': 'ldStoreDialogLoad' - }) - .dialog('open'); + exportLearningDesign : function(format){ + if (layout.modified || layout.activities.length == 0) { + return; + } - MenuLib.initLearningDesignTree(); + layout.exportLDDialog.dialog('open'); + $('iframe', layout.exportLDDialog) + .attr('src', LAMS_URL + 'authoring/exportToolContent.do?method=export&format=' + format + '&learningDesignID=' + + layout.ld.learningDesignID); }, + - /** - * Opens "Save sequence" dialog where an user can choose where to save the Learning Design. + * Creates a PNG image out of current SVG contents. */ - saveLearningDesign : function(showDialog){ - if (!showDialog && layout.ld.learningDesignID) { - saveLearningDesign(layout.ld.folderID, layout.ld.learningDesignID, layout.ld.title); + exportPNG : function(download){ + var crop = MenuLib.getCanvasCrop(); + if (crop.x >= crop.x2) { return; } - var dialog = $('#ldStoreDialog'); - // remove the directory tree, if it remained for last dialog opening - dialog.dialog('option', { - 'title' : LABELS.SAVE_DIALOG_TITLE, - 'buttons' : dialog.dialog('option', 'buttonsSave'), - // it informs widgets that it is saved dialog - 'dialogClass': 'ldStoreDialogSave' - }) - .dialog('open'); + var ctx = crop.workspace.getContext('2d'), + w = crop.x2 - crop.x, + h = crop.y2 - crop.y, + cut = ctx.getImageData(crop.x, crop.y, w, h); + + crop.workspace.width = w; + crop.workspace.height = h; + ctx.putImageData(cut, 0, 0); - var tree = MenuLib.initLearningDesignTree(); - tree.getRoot().children[0].highlight(); + var imageCode = crop.workspace.toDataURL("image/png"); + if (download) { + $('a', layout.exportImageDialog).attr({ + 'href' : imageCode, + 'download' : (layout.ld.title ? layout.ld.title : 'Untitled') + '.png' + }); + layout.exportImageDialog.dialog('open'); + } else { + return imageCode; + } }, /** - * Loads Learning Design Tree from DB + * Creates a SVG image out of current SVG contents. */ - initLearningDesignTree : function(){ - var dialog = $('#ldStoreDialog'), - tree = dialog.dialog('option', 'ldTree'), - rootNode = tree.getRoot(); - // remove existing folders - $.each(rootNode.children, function(){ - tree.removeNode(this); + exportSVG : function(download){ + var crop = MenuLib.getCanvasCrop(); + if (crop.x >= crop.x2) { + return; + } + + // replace image links with PNG code + var iconLibrary = {}; + crop.canvasClone.find('image').each(function(){ + var attributeName = 'xlink:href', + iconLink = $(this).attr(attributeName); + if (!iconLink) { + attributeName = 'href', + iconLink = $(this).attr(attributeName); + } + + var iconCode = iconLibrary[iconLink]; + if (!iconCode) { + $.ajax({ + url : iconLink, + async: false, + dataType : 'text', + success : function(response) { + iconCode = iconLibrary[iconLink] = response; + } + }); + } + if (!iconCode) { + return true; + } + + canvg(crop.workspace, iconCode); + $(this).attr(attributeName, crop.workspace.toDataURL("image/png")); }); - // (re)load user's folders and LDs - tree.buildTreeFromObject(MenuLib.getFolderContents()); - tree.render(); - // expand the first (user) folder - tree.getRoot().children[0].expand(); - return tree; + // set viewBox so content is nicely aligned + var width = crop.x2 - crop.x + 2, + height = crop.y2 - crop.y + 2, + svg = $('svg', crop.canvasClone).attr({ + 'width' : width, + 'height' : height + })[0]; + + // need to set attributes using pure JS as jQuery sets them with lower case, which is unacceptable in SVG + svg.setAttribute('viewBox', crop.x + ' ' + crop.y + ' ' + width + ' ' + height); + svg.setAttribute('preserveAspectRatio', 'xMinYMin slice'); + + // reset any cursor=pointer styles + $('*[style*="cursor"]', svg).css('cursor', 'default'); + + var imageCode = crop.canvasClone.html(); + if (download) { + $('a', layout.exportImageDialog).attr({ + 'href' : 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(imageCode), + 'download' : (layout.ld.title ? layout.ld.title : 'Untitled') + '.svg' + }); + layout.exportImageDialog.dialog('open'); + } else { + return imageCode; + } }, /** + * Finds coordinates of canvas content, minus surrounding whitespace. + */ + getCanvasCrop : function(){ + var canvasClone = canvas.clone(); + // Raphael does not add this and it's needed by Firefox + $('svg', canvasClone).attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + // remove the rubbish bin icon + canvasClone.find('#rubbishBin').remove(); + // create HTML5 canvas element and fill it with SVG code using canvg library + var workspace = $('')[0]; + canvg(workspace, canvasClone.html()); + + // trim the image from white space + var ctx = workspace.getContext('2d'), + w = workspace.width, + h = workspace.height, + imageData = ctx.getImageData(0, 0, w, h), + result = { + x : w, + y : h, + x2 : 0, + y2 : 0, + workspace : workspace, + canvasClone : canvasClone + }; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + var index = (y * w + x) * 4, + a = imageData.data[index + 3]; + + if (a > 0) { + if (x < result.x) { + result.x = x; + } + if (y < result.y) { + result.y = y; + } + if (x > result.x2) { + result.x2 = x; + } + if (y > result.y2) { + result.y2 = y; + } + } + } + } + + return result; + }, + + + /** * Loads subfolders and LDs from the server. */ getFolderContents : function(folderID) { @@ -364,399 +530,76 @@ return result; }, + - /** - * Sorts activities on canvas. + * Opens a pop up for importing LD. Loads the imported LD to canvas. */ - arrangeActivities : function(){ - if ((layout.regions.length > 0 || layout.labels.length > 0) - && !confirm(LABELS.ARRANGE_CONFIRM)) { - return; - } - - if (layout.activities.length == 0) { - // no activities, nothing to do - return; - } - - // just to refresh the state of canvas - HandlerLib.resetCanvasMode(true); - - // activities are arranged in a grid - var row = 0, - // for special cases when row needs to shifted more - forceRowY = null, - column = 0, - // check how many columns current paper can hold - maxColumns = Math.floor((paper.width - layout.conf.arrangeHorizontalPadding) - / layout.conf.arrangeHorizontalSpace), - // the initial max length of subsequences is limited by paper space - subsequenceMaxLength = maxColumns, - // a shallow copy of activities array without inner activities - activitiesCopy = [], - // just to speed up processing when there are only activities with no transitions left - onlyDetachedLeft = false; - - $.each(layout.activities, function(){ - if (!this.parentActivity || !(this.parentActivity instanceof DecorationLib.Container)){ - activitiesCopy.push(this); - } - }); - - // branches will not be broken into few rows; if they are long, paper will be resized - // find the longes branch to find the new paper size - $.each(layout.activities, function(){ - if (this instanceof ActivityLib.BranchingEdgeActivity && this.isStart) { - // refresh branching metadata - ActivityLib.updateBranchesLength(this.branchingActivity); - // add start and end edges to the result - var longestBranchLength = this.branchingActivity.longestBranchLength + 2; - if (longestBranchLength > subsequenceMaxLength) { - subsequenceMaxLength = longestBranchLength; + importLearningDesign : function(){ + var importWindow = window.open(LAMS_URL + 'authoring/importToolContent.do?method=import','Import', + 'width=800,height=298,resize=yes,status=yes,scrollbar=no,menubar=no,toolbar=no'), + currentLearningDesignID = null, + regEx = /learningDesignID=(\d+)/g, + // since window.onload does not really work after submitting a form inside the window, + // this trick checks periodically for changes + loadCheckInterval = setInterval(function(){ + if (!importWindow){ + // window was closed + clearInterval(loadCheckInterval); + return; } - } - }); - - // check how many child activities are in Floating Activity, if any - if (layout.floatingActivity && layout.floatingActivity.childActivities.length > subsequenceMaxLength) { - subsequenceMaxLength = childActivities.length; - } - - // resize paper horizontally, if needed - if (subsequenceMaxLength > maxColumns) { - maxColumns = subsequenceMaxLength; - resizePaper(layout.conf.arrangeHorizontalPadding - + maxColumns * layout.conf.arrangeHorizontalSpace, - paper.height); - } - - // main loop; iterate over whatever is left in the array - while (activitiesCopy.length > 0) { - // look for activities with transitions first; detached ones go to the very end - var activity = null; - if (!onlyDetachedLeft) { - $.each(activitiesCopy, function(){ - if (this.transitions.to.length > 0) { - activity = this; - while (activity.transitions.to.length > 0) { - // check if previous activity was not drawn already - // it can happen for branching edges - var activityLookup = activity.transitions.to[0].fromActivity; - if (activitiesCopy.indexOf(activityLookup) == -1) { - break; - } - activity = activityLookup; - }; - return false; - } - }); - } - - if (!activity) { - // no activities with transitions left, take first detached one - onlyDetachedLeft = true; - activity = activitiesCopy[0]; - } - - // markers for complex activity processing - var complex = null; - - // crawl through a sequence of activities - while (activity) { - if (activity instanceof ActivityLib.BranchingEdgeActivity) { - if (activity.isStart) { - // draw branching edges straight away and remove them from normall processing - var branchingActivity = activity.branchingActivity, - start = branchingActivity.start, - end = branchingActivity.end, - complex = { - end : end - }, - // can the whole branching fit in current canvas width? - branchingFits = column + branchingActivity.longestBranchLength + 2 <= maxColumns; - if (!branchingFits) { - // start branching from the left side of canvas - row++; - if (forceRowY) { - while (forceRowY > layout.conf.arrangeVerticalPadding + 10 + row * layout.conf.arrangeVerticalSpace) { - row++; - } - forceRowY = null; - } - column = 0; - } - // store the column of converge point - end.column = column + branchingActivity.longestBranchLength + 1; - - complex.branchingRow = row + Math.floor(branchingActivity.branches.length / 2); - // edge points go to middle of rows with branches - var startX = layout.conf.arrangeHorizontalPadding + - column * layout.conf.arrangeHorizontalSpace + 54, - edgeY = layout.conf.arrangeVerticalPadding + - complex.branchingRow * layout.conf.arrangeVerticalSpace + 17, - endX = layout.conf.arrangeHorizontalPadding + - end.column * layout.conf.arrangeHorizontalSpace + 54; - - activitiesCopy.splice(activitiesCopy.indexOf(start), 1); - activitiesCopy.splice(activitiesCopy.indexOf(end), 1); - - // start point goes to very left, end goes wherever the longes branch ends - start.draw(startX, edgeY); - end.draw(endX, edgeY); - - complex.branchingColumn = column; - column++; - - $.each(branchingActivity.branches, function(){ - if (this.transitionFrom.toActivity == branchingActivity.end) { - complex.emptyBranch = this; - return false; - } - }); - - if (branchingActivity.branches.length > (complex.emptyBranch ? 1 : 0)) { - // set up branch drawing - // skip the first branch if it is the empty one - complex.branchIndex = - complex.emptyBranch == branchingActivity.branches[0] ? 1 : 0; - // next activity for normal processing will be first one from the first branch - activity = branchingActivity.branches[complex.branchIndex].transitionFrom.toActivity; - continue; - } else { - // no branches, nothing to do, carry on with normal activity processing - activity = complex.end; - activity.column = null; - complex = null; - } - } - } else { - // it is a simple activity, so redraw it - var x = layout.conf.arrangeHorizontalPadding + column * layout.conf.arrangeHorizontalSpace, - y = layout.conf.arrangeVerticalPadding + row * layout.conf.arrangeVerticalSpace; - - if (activity instanceof ActivityLib.GateActivity) { - // adjust placement for gate activity, so it's in the middle of its cell - x += 57; - y += 10; - } else if (activity instanceof ActivityLib.OptionalActivity){ - x -= 20; - } - - activity.draw(x, y); - // remove the activity so we do not process it twice - activitiesCopy.splice(activitiesCopy.indexOf(activity), 1); - - // learn where a tall Optional Activity has its end - // and later start drawing activities lower than in the next row - if (activity instanceof DecorationLib.Container && activity.childActivities.length > 1) { - var activityEndY = activity.items.shape.getBBox().y2; - if (!forceRowY || activityEndY > forceRowY) { - forceRowY = activityEndY; - } - } + var body = $('body', importWindow.document).html(), + match = regEx.exec(body); + // check if ID was found and it's not the same as previously + if (match && match[1] != currentLearningDesignID) { + currentLearningDesignID = match[1]; + // load the imported LD + GeneralLib.openLearningDesign(currentLearningDesignID); } - - // find the next row and column - column = (column + 1) % maxColumns; - if (column == 0) { - row++; - // if an Optional Activity forced next activities to be drawn lower than usual - if (forceRowY) { - while (forceRowY > layout.conf.arrangeVerticalPadding + 10 + row * layout.conf.arrangeVerticalSpace) { - row++; - } - forceRowY = null; - } - } - - // does the activity has further activities? - if (activity.transitions.from.length > 0) { - activity = activity.transitions.from[0].toActivity; - } else { - activity = null; - } - - if (complex && (!activity || activity == complex.end)) { - // end of branch - complex.branchIndex++; - - var branches = complex.end.branchingActivity.branches; - if (branches.length > complex.branchIndex) { - if (branches[complex.branchIndex] == complex.emptyBranch) { - // skip the empty branch - complex.branchIndex++; - } - } - - if (branches.length > complex.branchIndex) { - // there is another branch to process - activity = branches[complex.branchIndex].transitionFrom.toActivity; - // go back to left side of canvas and draw next branch - row++; - if (complex.emptyBranch && complex.branchingRow == row) { - row++; - } - - column = complex.branchingColumn + 1; - } else { - - // no more branches, return to normal activity processing - activity = complex.end.transitions.from.length == 0 ? - null : complex.end.transitions.from[0].toActivity; - column = (complex.end.column + 1) % maxColumns; - if (column == 0) { - row++; - } - if (row < complex.branchingRow) { - row = complex.branchingRow; - } - complex.end.column = null; - complex = null; - } - } - - if (!activity || activitiesCopy.indexOf(activity) == -1) { - // next activity was already processed, so stop crawling - break; - } - }; - }; - - if (layout.floatingActivity) { - if (column > 0) { - // if the last activity was in the last column, there is no need for another row - row++; - column = 0; - } - var x = layout.conf.arrangeHorizontalPadding, - y = layout.conf.arrangeVerticalPadding - 30 + row * layout.conf.arrangeVerticalSpace; - - layout.floatingActivity.draw(x, y); - } - - // redraw transitions one by one - $.each(layout.activities, function(){ - $.each(this.transitions.from.slice(), function(){ - ActivityLib.addTransition(this.fromActivity, this.toActivity, true); - }); - }); - - resizePaper(); - setModified(true); + }, 1000); }, /** - * Removes existing activities and prepares canvas for a new sequence. + * Loads Learning Design Tree from DB */ - newLearningDesign : function(force, soft){ - // force means that user should not be asked for confirmation. - if (!force && (layout.activities.length > 0 - || layout.regions.length > 0 - || layout.labels.length > 0 - || layout.floatingActivity) - && !confirm(LABELS.CLEAR_CANVAS_CONFIRM)){ - return; - } + loadLearningDesignTree : function(){ + var dialog = $('#ldStoreDialog'), + tree = dialog.dialog('option', 'ldTree'), + rootNode = tree.getRoot(); + // remove existing folders + $.each(rootNode.children, function(){ + tree.removeNode(this); + }); + // (re)load user's folders and LDs + tree.buildTreeFromObject(MenuLib.getFolderContents()); + tree.render(); - $('#ldDescriptionDetails').slideUp(); + // expand the first (user) folder + tree.getRoot().children[0].expand(); - // soft means that data is manually reset, instead of simply reloading the page. - if (soft) { - $('#ldDescriptionFieldTitle').text('Untitled'); - CKEDITOR.instances['ldDescriptionFieldDescription'].setData(null); - - layout.ld = { - 'maxUIID' : 0 - }; - layout.activities = []; - layout.regions = []; - layout.labels = []; - layout.floatingActivity = null; - setModified(true); - - if (paper) { - paper.clear(); - } else { - // need to set size right away for Chrome - paper = Raphael('canvas', canvas.width() - 5, canvas.height() - 5); - } - - resizePaper(); - } else { - // do not prompt again - window.onbeforeunload = null; - // full window reload so new content ID gets generated - document.location.href = LAMS_URL + 'authoring/author.do?method=openAuthoring'; - } + return tree; }, /** - * Mark an activity as ready for pasting. + * Opens "Open sequence" dialog where an user can choose a Learning Design to load. */ - copyActivity : function(){ - layout.items.copiedActivity = layout.items.selectedObject; - }, - - - /** - * Make a copy of an existing activity. - */ - pasteActivity : function(){ - var activity = layout.items.copiedActivity; - if (!activity) { - return; - } - // check if the activity was not removed - if (layout.activities.indexOf(activity) < 0){ - layout.items.copiedActivity = null; - return; - } - // only tool activities can be copied (todo?) - if (!(activity instanceof ActivityLib.ToolActivity)) { - alert(LABELS.PASTE_ERROR); - return; - } + openLearningDesign : function(){ + var dialog = $('#ldStoreDialog'); + // remove the directory tree, if it remained for last dialog opening + dialog.dialog('option', { + 'title' : LABELS.OPEN_DIALOG_TITLE, + 'buttons' : dialog.dialog('option', 'buttonsLoad'), + // it informs widgets that it is load dialog + 'dialogClass': 'ldStoreDialogLoad' + }) + .dialog('open'); - // find an unqiue title for the new activity - var copyCount = 1, - title = LABELS.ACTIVITY_COPY_TITLE_PREFIX.replace('[0]', '') + activity.title; - while (true) { - var sameTitleFound = false; - $.each(layout.activities, function(){ - if (title == this.title) { - sameTitleFound = true; - return false; - } - }); - - if (sameTitleFound) { - copyCount++; - title = LABELS.ACTIVITY_COPY_TITLE_PREFIX.replace('[0]', '(' + copyCount + ')') + activity.title; - } else { - break; - } - }; - - // draw the new activity next to the existing one - var x = activity.items.shape.getBBox().x + 10, - y = activity.items.shape.getBBox().y + 10, - newActivity = new ActivityLib.ToolActivity(null, null, null, activity.toolID, - null, x, y, title); - layout.activities.push(newActivity); - - if (activity.grouping) { - // add grouping effect if the existing activity had one - newActivity.grouping = activity.grouping; - newActivity.draw(); - } - - setModified(true); + MenuLib.loadLearningDesignTree(); }, + - openPreview : function(){ if (layout.modified) { // disabling the button does not do the trick, so we have to check it here @@ -801,201 +644,93 @@ /** - * Expands/collapses description field. + * Make a copy of an existing activity. */ - toggleDescriptionDiv: function() { - $('#ldDescriptionDetails').slideToggle(function(){ - $('#ldDescriptionHideTip').text($(this).is(':visible') ? '▲' : '▼'); - $('#templateContainer').height($('#ldDescriptionDiv').height() + $('#canvas').height() - 10); - }); - }, - - - /** - * Opens a pop up for importing LD. Loads the imported LD to canvas. - */ - importLearningDesign : function(){ - var importWindow = window.open(LAMS_URL + 'authoring/importToolContent.do?method=import','Import', - 'width=800,height=298,resize=yes,status=yes,scrollbar=no,menubar=no,toolbar=no'), - currentLearningDesignID = null, - regEx = /learningDesignID=(\d+)/g, - // since window.onload does not really work after submitting a form inside the window, - // this trick checks periodically for changes - loadCheckInterval = setInterval(function(){ - if (!importWindow){ - // window was closed - clearInterval(loadCheckInterval); - return; - } - var body = $('body', importWindow.document).html(), - match = regEx.exec(body); - // check if ID was found and it's not the same as previously - if (match && match[1] != currentLearningDesignID) { - currentLearningDesignID = match[1]; - // load the imported LD - openLearningDesign(currentLearningDesignID); - } - }, 1000); - }, - - - /** - * Opens a pop up for exporting LD. - */ - exportLearningDesign : function(){ - if (layout.modified || layout.activities.length == 0) { + pasteActivity : function(){ + var activity = layout.copiedActivity; + if (!activity) { return; } - - window.open(LAMS_URL + 'authoring/exportToolContent.do?learningDesignID=' + layout.ld.learningDesignID, - 'Export','width=712,height=298,resize=yes,status=yes,scrollbar=no,menubar=no,toolbar=no'); - }, - - - /** - * Creates a PNG image out of current SVG contents. - */ - convertToPNG : function(){ - var crop = MenuLib.getCanvasCrop(); - if (crop.x >= crop.x2) { + // check if the activity was not removed + if (layout.activities.indexOf(activity) < 0){ + layout.copiedActivity = null; return; } + // only tool activities can be copied (todo?) + if (!(activity instanceof ActivityDefs.ToolActivity)) { + alert(LABELS.PASTE_ERROR); + return; + } - var ctx = crop.workspace.getContext('2d'), - w = crop.x2 - crop.x, - h = crop.y2 - crop.y, - cut = ctx.getImageData(crop.x, crop.y, w, h); - - crop.workspace.width = w; - crop.workspace.height = h; - ctx.putImageData(cut, 0, 0); + // find an unqiue title for the new activity + var copyCount = 1, + title = LABELS.ACTIVITY_COPY_TITLE_PREFIX.replace('[0]', '') + activity.title; + while (true) { + var sameTitleFound = false; + $.each(layout.activities, function(){ + if (title == this.title) { + sameTitleFound = true; + return false; + } + }); + + if (sameTitleFound) { + copyCount++; + title = LABELS.ACTIVITY_COPY_TITLE_PREFIX.replace('[0]', '(' + copyCount + ')') + activity.title; + } else { + break; + } + }; - // open a dialog with the result - var dialog = $('#exportImageDialog'); - $('').attr('src', crop.workspace.toDataURL("image/png")) - .appendTo($('#exportCanvas', dialog).empty()); - dialog.dialog({ - 'minWidth' : w + 50, - 'show' : 'fold', - 'hide' : 'fold', - 'resizable': false, - 'title' : LABELS.EXPORT_IMAGE_DIALOG_TITLE - }); + // draw the new activity next to the existing one + var x = activity.items.shape.getBBox().x + 10, + y = activity.items.shape.getBBox().y + 10, + newActivity = new ActivityDefs.ToolActivity(null, null, null, activity.toolID, activity.learningLibraryID, + null, x, y, title); + layout.activities.push(newActivity); + + if (activity.grouping) { + // add grouping effect if the existing activity had one + newActivity.grouping = activity.grouping; + newActivity.draw(); + } + + GeneralLib.setModified(true); }, /** - * Creates a SVG image out of current SVG contents. + * Opens "Save sequence" dialog where an user can choose where to save the Learning Design. */ - convertToSVG : function(){ - var crop = MenuLib.getCanvasCrop(); - if (crop.x >= crop.x2) { + saveLearningDesign : function(showDialog){ + if (!showDialog && layout.ld.learningDesignID) { + GeneralLib.saveLearningDesign(layout.ld.folderID, layout.ld.learningDesignID, layout.ld.title); return; } - // replace image links with PNG code - var iconLibrary = {}; - crop.canvasClone.find('image').each(function(){ - var attributeName = 'xlink:href', - iconLink = $(this).attr(attributeName); - if (!iconLink) { - attributeName = 'href', - iconLink = $(this).attr(attributeName); - } - - var iconCode = iconLibrary[iconLink]; - if (!iconCode) { - $.ajax({ - url : iconLink, - async: false, - dataType : 'text', - success : function(response) { - iconCode = iconLibrary[iconLink] = response; - } - }); - } - if (!iconCode) { - return true; - } - - canvg(crop.workspace, iconCode); - $(this).attr(attributeName, crop.workspace.toDataURL("image/png")); - }); + var dialog = $('#ldStoreDialog'); + // remove the directory tree, if it remained for last dialog opening + dialog.dialog('option', { + 'title' : LABELS.SAVE_DIALOG_TITLE, + 'buttons' : dialog.dialog('option', 'buttonsSave'), + // it informs widgets that it is saved dialog + 'dialogClass': 'ldStoreDialogSave' + }) + .dialog('open'); - - // set viewBox so content is nicely aligned - var width = crop.x2 - crop.x + 2, - height = crop.y2 - crop.y + 2; - - $('svg', crop.canvasClone).attr({ - 'viewBox' : crop.x + ' ' + crop.y + ' ' + width + ' ' + height, - 'width' : width, - 'height' : height, - 'preserveAspectRatio' : 'xMinYMin slice' - }); - - var dialog = $('#exportImageDialog'); - $('#exportCanvas', dialog).html(crop.canvasClone.html()); - dialog.dialog({ - 'minWidth' : width + 50, - 'show' : 'fold', - 'hide' : 'fold', - 'resizable': false, - 'title' : LABELS.EXPORT_IMAGE_DIALOG_TITLE - }); + var tree = MenuLib.loadLearningDesignTree(); + tree.getRoot().children[0].highlight(); }, /** - * Finds coordinates of canvas content, minus surrounding whitespace. + * Expands/collapses description field. */ - getCanvasCrop : function(){ - var canvasClone = canvas.clone(); - // Raphael does not add this and it's needed by Firefox - $('svg', canvasClone).attr('xmlns:xlink', 'http://www.w3.org/1999/xlink'); - // remove the rubbish bin icon - canvasClone.find('#rubbishBin').remove(); - // create HTML5 canvas element and fill it with SVG code using canvg library - var workspace = $('')[0]; - canvg(workspace, canvasClone.html()); - - // trim the image from white space - var ctx = workspace.getContext('2d'), - w = workspace.width, - h = workspace.height, - imageData = ctx.getImageData(0, 0, w, h), - result = { - x : w, - y : h, - x2 : 0, - y2 : 0, - workspace : workspace, - canvasClone : canvasClone - }; - - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - var index = (y * w + x) * 4, - a = imageData.data[index + 3]; - - if (a > 0) { - if (x < result.x) { - result.x = x; - } - if (y < result.y) { - result.y = y; - } - if (x > result.x2) { - result.x2 = x; - } - if (y > result.y2) { - result.y2 = y; - } - } - } - } - - return result; + toggleDescriptionDiv: function() { + $('#ldDescriptionDetails').slideToggle(function(){ + $('#ldDescriptionHideTip').text($(this).is(':visible') ? '▲' : '▼'); + $('#templateContainer').height($('#ldDescriptionDiv').height() + $('#canvas').height() - 10); + }); } Index: lams_central/web/includes/javascript/authoring/authoringProperty.js =================================================================== RCS file: /usr/local/cvsroot/lams_central/web/includes/javascript/authoring/authoringProperty.js,v diff -u -r1.23 -r1.24 --- lams_central/web/includes/javascript/authoring/authoringProperty.js 27 May 2014 11:54:41 -0000 1.23 +++ lams_central/web/includes/javascript/authoring/authoringProperty.js 2 Jun 2014 07:27:16 -0000 1.24 @@ -1,11 +1,720 @@ /** * This file contains methods for Activity properties dialogs. */ -var PropertyLib = { + +/** + * Stores different Activity properties structures. + */ +var PropertyDefs = { + /** + * Properties dialog content for Branching activities. + */ + branchingProperties : function() { + var activity = this, + content = activity.propertiesContent, + fillWidgetsFunction = function(){ + // fill widgets based on data stored in Activity object + $('.propertiesContentFieldTitle', content).val(activity.branchingActivity.title); + $('.propertiesContentFieldBranchingType', content).val(activity.branchingActivity.branchingType); + // see what grouping and input Tools are available + PropertyLib.fillGroupingDropdown(activity, activity.branchingActivity.grouping); + PropertyLib.fillToolInputDropdown(activity, activity.branchingActivity.input); + + $('.propertiesContentFieldOptionalSequenceMin', content).spinner('value', + activity.branchingActivity.minOptions) + .spinner('option', 'max', + activity.branchingActivity.branches.length); + $('.propertiesContentFieldOptionalSequenceMax', content).spinner('value', + activity.branchingActivity.maxOptions) + .spinner('option', { + 'min' : activity.branchingActivity.minOptions, + 'max' : activity.branchingActivity.branches.length + }); + if (activity.branchingActivity.branches.length == 0) { + // no branches = no buttons to define and match groups/conditions to branches + $('.propertiesContentRowConditions', content) + .add($('.propertiesContentFieldMatchGroups', content).closest('tr')) + .hide(); + } + } + + if (!content) { + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentBranching').clone().attr('id', null) + .show().data('parentObject', activity); + // extra buttons for group/input based branching + $('.propertiesContentFieldMatchGroups', content).button().click(function(){ + PropertyLib.openGroupsToBranchesMappingDialog(activity.branchingActivity); + }); + $('.propertiesContentFieldCreateConditions', content).button().click(function(){ + PropertyLib.openOutputConditionsDialog(activity.branchingActivity); + }); + $('.propertiesContentFieldMatchConditions', content).button().click(function(){ + PropertyLib.openConditionsToBranchesMappingDialog(activity.branchingActivity); + }); + + // if the user changed something in the dialog, save the state + // make onChange function a local variable, because it's used several times + var changeFunction = function(){ + // extract changed properties and redraw the activity + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + branchingActivity = activity.branchingActivity, + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + // validate and save the title + if (newTitle != branchingActivity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + branchingActivity.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + branchingActivity.branchingType = $('.propertiesContentFieldBranchingType', content).val(); + + // show/hide group match button + var groupingRow = $('.propertiesContentFieldGrouping', content).closest('tr'); + if (branchingActivity.branchingType == 'group') { + branchingActivity.grouping = groupingRow.show() + .find('option:selected').data('grouping'); + } else { + groupingRow.hide(); + } + $('.propertiesContentFieldMatchGroups', content).closest('tr') + .css('display', branchingActivity.grouping && branchingActivity.branches.length > 0 ? '' : 'none'); + + // show/hide conditions define/match buttons + var inputRow = $('.propertiesContentFieldInput', content).closest('tr'), + inputDefinitionRows = $('.propertiesContentRowConditions', content); + if (branchingActivity.branchingType == 'tool') { + branchingActivity.input = inputRow.show() + .find('option:selected').data('input'); + // no branches = no buttons + if (branchingActivity.input && branchingActivity.branches.length > 0) { + inputDefinitionRows.show(); + } else { + inputDefinitionRows.hide(); + } + } else { + inputRow.hide(); + inputDefinitionRows.hide(); + } + + var optionalSequenceRows = $('.spinner', content).closest('tr'); + if (branchingActivity.branchingType == 'optional') { + optionalSequenceRows.show(); + } else { + optionalSequenceRows.hide(); + } + + // if title changed, redraw branching and converge points + if (redrawNeeded) { + branchingActivity.start.draw(); + branchingActivity.end.draw(); + ActivityLib.addSelectEffect(layout.selectedObject, true); + } + + GeneralLib.setModified(true); + } + + // min can not be higher than max; neither of them can be higher than number of branches + $('.propertiesContentFieldOptionalSequenceMin', content).spinner({'min' : 0}) + .on('spinchange', function(){ + var value = +$(this).val(); + activity.branchingActivity.minOptions = Math.min(value, activity.branchingActivity.branches.length); + if (value != activity.branchingActivity.minOptions) { + $(this, content).spinner('value', activity.branchingActivity.minOptions); + } + if (activity.branchingActivity.minOptions > activity.branchingActivity.maxOptions) { + $('.propertiesContentFieldOptionalSequenceMax', content).spinner('value', value); + } + $('.propertiesContentFieldOptionalSequenceMax', content).spinner('option', 'min', value); + }); + + + $('.propertiesContentFieldOptionalSequenceMax', content).spinner({'min' : 0}) + .on('spinchange', function(){ + var value = +$(this).val(); + activity.branchingActivity.maxOptions = Math.min(value, activity.branchingActivity.branches.length); + if (value != activity.branchingActivity.maxOptions) { + $(this, content).spinner('value', activity.branchingActivity.maxOptions); + } + }); + + // set up the handler and run functions for the first time + $('input, select', content).change(changeFunction); + fillWidgetsFunction(); + changeFunction.call(content); + } + + + fillWidgetsFunction(); + }, + + + /** + * Properties dialog content for Gates. + */ + gateProperties : function() { + var activity = this, + content = activity.propertiesContent; + if (!content) { + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentGate').clone().attr('id', null) + .show().data('parentObject', activity); + $('.propertiesContentFieldTitle', content).val(activity.title); + $('.propertiesContentFieldDescription', content).val(activity.description ? activity.description : ''); + $('.propertiesContentFieldGateType', content).val(activity.gateType); + + $('.propertiesContentFieldCreateConditions', content).button().click(function(){ + PropertyLib.openOutputConditionsDialog(activity); + }); + $('.propertiesContentFieldMatchConditions', content).button().click(function(){ + PropertyLib.openConditionsToBranchesMappingDialog(activity); + }); + + // make onChange function a local variable, because it's used several times + var changeFunction = function(){ + // extract changed properties and redraw the activity + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != activity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + activity.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + + // only Gate activity has description + activity.description = $('.propertiesContentFieldDescription', content).val(); + + activity.gateType = $('.propertiesContentFieldGateType', content).val(); + if (activity.gateType == 'schedule') { + // show inputs for setting delay before the gate is closed + $(".propertiesContentRowGateSchedule").show(); + activity.offsetDay = +$('.propertiesContentFieldOffsetDay', content).val(); + activity.offsetHour = +$('.propertiesContentFieldOffsetHour', content).val(); + activity.offsetMinute = +$('.propertiesContentFieldOffsetMinute', content).val(); + activity.gateActivityCompletionBased = $('.propertiesContentFieldActivityCompletionBased').is(':checked'); + } else { + $(".propertiesContentRowGateSchedule").hide(); + } + + // Gate can be input-based + var inputRow = $('.propertiesContentFieldInput', content).closest('tr'), + inputDefinitionRows = $('.propertiesContentRowConditions', content); + if (activity.gateType == 'condition') { + activity.input = inputRow.show().find('option:selected').data('input'); + if (activity.input) { + inputDefinitionRows.show(); + } else { + inputDefinitionRows.hide(); + } + } else { + inputRow.hide(); + inputDefinitionRows.hide(); + } + + if (redrawNeeded) { + activity.draw(); + ActivityLib.addSelectEffect(activity, true); + } + + GeneralLib.setModified(true); + }; + + // create groups/learners spinners + $('.propertiesContentFieldOffsetDay', content).spinner({ + 'min' : 0, + 'max' : 364 + }).spinner('value', activity.offsetDay || 0) + .on('spinchange', changeFunction); + + $('.propertiesContentFieldOffsetHour', content).spinner({ + 'min' : 0, + 'max' : 23 + }).spinner('value', activity.offsetHour || 0) + .on('spinchange', changeFunction); + + $('.propertiesContentFieldOffsetMinute', content).spinner({ + 'min' : 0, + 'max' : 59 + }).spinner('value', activity.offsetMinute || 0) + .on('spinchange', changeFunction); + + $('.propertiesContentFieldActivityCompletionBased', content) + .attr('checked', activity.gateActivityCompletionBased? 'checked' : null); + + $('input, textarea, select', content).change(changeFunction); + PropertyLib.fillToolInputDropdown(activity, activity.input); + changeFunction.call(content); + } + + if (activity.transitions.to.length == 0){ + $('.propertiesContentFieldActivityCompletionBased', content) + .attr('checked', null) + .attr('disabled', 'disabled'); + } else { + $('.propertiesContentFieldActivityCompletionBased', content) + .attr('disabled', null); + } + + PropertyLib.fillToolInputDropdown(activity, activity.input); + }, + + + /** + * Properties dialog content for Grouping activities. + */ + groupingProperties : function() { + var activity = this, + content = activity.propertiesContent; + + if (!content) { + // make onChange function a local variable, because it's used several times + var changeFunction = function(){ + // extract changed properties and redraw the activity, if needed + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(), + newGroupCount = +$('.propertiesContentFieldGroupCount', content).val(); + + // validate and save the title + if (newTitle != activity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + activity.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + + activity.groupingType = $('.propertiesContentFieldGroupingType', content).val(); + + $('input[name="propertiesContentFieldGroupDivide"]', content).each(function(){ + // enable/disable division types, depending on radio buttons next to them + $(this).next().find('.spinner').spinner('option', 'disabled', !$(this).is(':checked')); + }) + // hide group/learner division with some grouping types + .add($('.propertiesContentFieldLearnerCount', content).closest('tr')) + .css('display', activity.groupingType == 'monitor' ? 'none' : ''); + + $('.propertiesContentFieldEqualSizes, .propertiesContentFieldViewLearners', content) + .closest('tr').css('display', activity.groupingType == 'learner' ? '' : 'none'); + + activity.groupDivide = activity.groupingType == 'monitor' + || $('.propertiesContentFieldGroupCountEnable', content).is(':checked') + ? 'groups' : 'learners'; + $('.propertiesContentFieldNameGroups', content).css('display', activity.groupDivide == 'groups' ? '' : 'none'); + + if (activity.groupCount != newGroupCount){ + activity.groupCount = newGroupCount; + activity.groups = PropertyLib.fillNameAndUIIDList(activity.groupCount, activity.groups, 'name', + LABELS.DEFAULT_GROUP_PREFIX); + } + activity.learnerCount = +$('.propertiesContentFieldLearnerCount', content).val(); + activity.equalSizes = $('.propertiesContentFieldEqualSizes', content).is(':checked'); + activity.viewLearners = $('.propertiesContentFieldViewLearners', content).is(':checked'); + + if (redrawNeeded) { + activity.draw(); + ActivityLib.addSelectEffect(activity, true); + } + + GeneralLib.setModified(true); + }; + + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentGrouping').clone().attr('id', null) + .show().data('parentObject', activity); + + // init widgets + $('.propertiesContentFieldTitle', content).val(activity.title); + $('.propertiesContentFieldGroupingType', content).val(activity.groupingType); + if (activity.groupDivide == 'learners') { + $('.propertiesContentFieldLearnerCountEnable', content).attr('checked', 'checked'); + } else { + $('.propertiesContentFieldGroupCountEnable', content).attr('checked', 'checked'); + } + + // create groups/learners spinners + $('.propertiesContentFieldGroupCount', content).spinner({ + 'min' : 2, + 'disabled' : activity.groupDivide == 'learners' + }).spinner('value', activity.groupCount) + .on('spinchange', changeFunction); + + $('.propertiesContentFieldLearnerCount', content).spinner({ + 'min' : 1, + 'disabled' : activity.groupDivide == 'groups' + }).spinner('value', activity.learnerCount) + .on('spinchange', changeFunction); + + $('.propertiesContentFieldEqualSizes', content).attr('checked', activity.equalSizes ? 'checked' : null); + $('.propertiesContentFieldViewLearners', content).attr('checked', activity.viewLearners ? 'checked' : null); + $('.propertiesContentFieldNameGroups', content).button().click(function(){ + PropertyLib.openGroupNamingDialog(activity); + }); + + $('input, select', content).change(changeFunction); + changeFunction.call(content); + } + }, + + + /** + * Properties dialog content for labels (annotations). + */ + labelProperties : function() { + var label = this, + content = label.propertiesContent; + if (!content) { + // first run, create the content + content = label.propertiesContent = $('#propertiesContentLabel').clone().attr('id', null) + .show().data('parentObject', label); + $('.propertiesContentFieldTitle', content).val(label.title); + + $('input', content).change(function(){ + // extract changed properties and redraw the label, if needed + var content = $(this).closest('.dialogContainer'), + label = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != label.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + label.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + + if (redrawNeeded) { + label.draw(); + ActivityLib.addSelectEffect(label, true); + GeneralLib.setModified(true); + } + }); + } + }, + + + /** + * Properties dialog content for Optional Activity. + */ + optionalActivityProperties : function() { + var activity = this, + content = activity.propertiesContent; + + activity.minOptions = Math.min(activity.minOptions, activity.childActivityDefs.length); + activity.maxOptions = Math.min(activity.maxOptions, activity.childActivityDefs.length); + + if (!content) { + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentOptionalActivity').clone().attr('id', null) + .show().data('parentObject', activity); + $('.propertiesContentFieldTitle', content).val(activity.title); + + $('input', content).change(function(){ + // extract changed properties and redraw the Activity, if needed + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != activity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + activity.title = newTitle; + activity.draw(); + ActivityLib.addSelectEffect(activity, true); + GeneralLib.setModified(true); + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + }); + + // min can not be higher than max; neither of them can be higher than children count + $('.propertiesContentFieldOptionalActivityMin', content).spinner({'min' : 0}) + .on('spinchange', function(){ + var value = +$(this).val(); + activity.minOptions = Math.min(value, activity.childActivityDefs.length); + if (value != activity.minOptions) { + $(this, content).spinner('value', activity.minOptions); + } + if (activity.minOptions > activity.maxOptions) { + $('.propertiesContentFieldOptionalActivityMax', content).spinner('value', value); + } + $('.propertiesContentFieldOptionalActivityMax', content).spinner('option', 'min', value); + }); + + + $('.propertiesContentFieldOptionalActivityMax', content).spinner({'min' : 0}) + .on('spinchange', function(){ + var value = +$(this).val(); + activity.maxOptions = Math.min(value, activity.childActivityDefs.length); + if (value != activity.maxOptions) { + $(this, content).spinner('value', activity.maxOptions); + } + }); + } + + $('.propertiesContentFieldOptionalActivityMin', content).spinner('value', activity.minOptions) + .spinner('option', 'max', activity.childActivityDefs.length); + $('.propertiesContentFieldOptionalActivityMax', content).spinner('value', activity.maxOptions) + .spinner('option', { + 'min' : activity.minOptions, + 'max' : activity.childActivityDefs.length + }); + }, + + + /** + * Properties dialog content for Parallel activities. + */ + parallelProperties : function() { + var activity = this, + content = activity.propertiesContent; + + if (!content) { + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentParallel').clone().attr('id', null) + .show().data('parentObject', activity); + $('.propertiesContentFieldTitle', content).val(activity.title); + + $('input, select', content).change(function(){ + // extract changed properties and redraw the activity + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != activity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + activity.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + var newGroupingValue = $('.propertiesContentFieldGrouping option:selected', content) + .data('grouping'); + if (newGroupingValue != activity.grouping) { + activity.grouping = newGroupingValue; + redrawNeeded = true; + } + + if (redrawNeeded) { + activity.draw(); + ActivityLib.addSelectEffect(activity, true); + GeneralLib.setModified(true); + } + }); + } + + PropertyLib.fillGroupingDropdown(activity, activity.grouping); + }, + + + /** + * Properties dialog content for regions (annotations). + */ + regionProperties : function() { + var region = this, + content = region.propertiesContent; + if (!content) { + // first run, create the content + content = region.propertiesContent = $('#propertiesContentRegion').clone().attr('id', null) + .show().data('parentObject', region); + + $('.propertiesContentFieldTitle', content).val(region.title); + var color = region.items.shape.attr('fill'); + // init colour chooser + $('.propertiesContentFieldColor', content).val(color) + .simpleColor({ + 'colors' : layout.colors.annotationPalette, + 'chooserCSS' : { + 'left' : 0, + 'top' : '30px', + 'margin' : '0' + } + }); + + $('input', content).change(function(){ + // extract changed properties and redraw the transition + var content = $(this).closest('.dialogContainer'), + region = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(), + color = region.items.shape.attr('fill'), + newColor = $('.propertiesContentFieldColor', content).val(); + + // validate and save the title + if (newTitle != region.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + region.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + redrawNeeded |= newColor != color; + + if (redrawNeeded) { + region.draw(null, null, null, null, newColor); + ActivityLib.addSelectEffect(region, true); + GeneralLib.setModified(true); + } + }); + } + }, + + + /** + * Properties dialog content for Tool activities. + */ + toolProperties : function() { + var activity = this, + content = activity.propertiesContent, + allowsGrouping = !this.parentActivity || !(this.parentActivity instanceof ActivityDefs.ParallelActivity); + + if (!content) { + // first run, create the content + content = activity.propertiesContent = $('#propertiesContentTool').clone().attr('id', null) + .show().data('parentObject', activity); + $('.propertiesContentFieldTitle', content).val(activity.title); + if (!allowsGrouping) { + // parts of Parallel Activity can not be grouped + $('.propertiesContentFieldGrouping', content).closest('tr').remove(); + } + + $('input, select', content).change(function(){ + // extract changed properties and redraw the activity + var content = $(this).closest('.dialogContainer'), + activity = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != activity.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + activity.title = newTitle; + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + + var selectedGrouping = $('.propertiesContentFieldGrouping option:selected', content); + if (selectedGrouping.length > 0){ + var newGroupingValue = $('.propertiesContentFieldGrouping option:selected', content) + .data('grouping'); + if (newGroupingValue != activity.grouping) { + activity.grouping = newGroupingValue; + redrawNeeded = true; + } + } + + if (redrawNeeded) { + activity.draw(); + ActivityLib.addSelectEffect(activity, true); + GeneralLib.setModified(true); + } + }); + } + + if (allowsGrouping){ + PropertyLib.fillGroupingDropdown(activity, activity.grouping); + } + }, + + + /** + * Properties dialog content for transitions. + */ + transitionProperties : function() { + var transition = this, + content = transition.propertiesContent; + if (!content) { + // first run, create the content + content = transition.propertiesContent = $('#propertiesContentTransition').clone().attr('id', null) + .show().data('parentObject', transition); + $('.propertiesContentFieldTitle', content).val(transition.title); + + var defaultBranch = $('.propertiesContentFieldDefault', content); + if (transition.branch) { + defaultBranch.show(); + } else { + defaultBranch.hide(); + } + + $('input', content).change(function(){ + // extract changed properties and redraw the transition + var content = $(this).closest('.dialogContainer'), + transition = content.data('parentObject'), + redrawNeeded = false, + newTitle = $('.propertiesContentFieldTitle', content).val(); + + // validate and save the title + if (newTitle != transition.title) { + if (GeneralLib.nameValidator.test(newTitle)) { + transition.title = newTitle; + if (transition.branch) { + transition.branch.title = newTitle; + } + redrawNeeded = true; + } else { + alert(LABELS.TITLE_VALIDATION_ERROR); + } + } + + if (transition.branch) { + transition.branch.defaultBranch = $('.propertiesContentFieldDefault', content).is(':checked'); + if (transition.branch.defaultBranch) { + // reset default branch marker in other branches, there can be only one + $.each(transition.branch.branchingActivity.branches, function(){ + if (this != transition.branch) { + this.defaultBranch = false; + } + }); + } + } + + if (redrawNeeded) { + transition.draw(); + ActivityLib.addSelectEffect(activity, true); + GeneralLib.setModified(true); + } + }); + } + + $('.propertiesContentFieldDefault', content).attr('checked', + transition.branch && transition.branch.defaultBranch ? 'checked' : null); + } +}, + + + +/** + * Contains Properties dialog manipulation methods. + */ +PropertyLib = { + init : function(){ // initialise the properties dialog singleton - var propertiesDialog = layout.items.propertiesDialog = + var propertiesDialog = layout.propertiesDialog = $('
    ') .appendTo('body') .dialog({ @@ -25,12 +734,12 @@ propertiesDialog.container = propertiesDialog.closest('.ui-dialog'); propertiesDialog.container.addClass('propertiesDialogContainer') .css('opacity', layout.conf.propertiesDialogDimOpacity) - .mousemove(HandlerLib.approachPropertiesDialogHandler) + .mousemove(HandlerPropertyLib.approachPropertiesDialogHandler) .find('.ui-dialog-titlebar-close').remove(); layout.dialogs.push(propertiesDialog); // initialise dialog from group naming - layout.items.groupNamingDialog = $('
    ').dialog({ + layout.groupNamingDialog = $('
    ').dialog({ 'autoOpen' : false, 'modal' : true, 'show' : 'fold', @@ -45,24 +754,27 @@ 'click' : function() { var dialog = $(this), activity = dialog.dialog('option', 'parentObject'), + names = [], error = null; - // extract group names from text fields + // extract group names from text fields and validate them $('input', dialog).each(function(){ var groupName = $(this).val().trim(); - if (!nameValidator.test(groupName)) { + if (GeneralLib.nameValidator.test(groupName)) { + names.push(groupName); + } else { error = LABELS.GROUP_TITLE_VALIDATION_ERORR; return false; } - }); + }); if (error) { alert(error); return; } $('input', dialog).each(function(index){ - activity.groups[index].name = $(this).val().trim(); + activity.groups[index].name = names[index]; }); dialog.dialog('close'); @@ -77,11 +789,11 @@ ] }); // add to dialogs array so they can be easily closed at once - layout.dialogs.push(layout.items.groupNamingDialog); + layout.dialogs.push(layout.groupNamingDialog); // initialise dialog for matching groups to branches in branching activities - var gtbDialog = layout.items.groupsToBranchesMappingDialog = $('#branchMappingDialog') + var gtbDialog = layout.groupsToBranchesMappingDialog = $('#branchMappingDialog') .clone() .attr('id','gtbDialog') .dialog({ @@ -101,19 +813,20 @@ 'click' : function() { var dialog = $(this), branchingActivity = dialog.dialog('option', 'branchingActivity'), + groupsToBranches = [], assignedToDefault = false, defaultBranch = null, close = true; // find references to groups and branches - branchingActivity.groupsToBranches = []; $('.branchMappingBoundItemCell div, .branchMappingFreeItemCell div', dialog).each(function(){ var groupUIID = +$(this).attr('uiid'), boundItem = $(this).data('boundItem'), branchUIID = boundItem ? +boundItem.attr('uiid') : null, group = null, branch = null; + // look for branch if (branchUIID) { $.each(branchingActivity.branches, function(){ if (branchUIID == this.uiid) { @@ -122,6 +835,7 @@ } }); } else { + // assign to default branch if (!defaultBranch) { $.each(branchingActivity.branches, function(){ if (this.defaultBranch) { @@ -139,6 +853,7 @@ assignedToDefault = true; } + // look for group $.each(branchingActivity.grouping.groups, function(){ if (groupUIID == this.uiid) { group = this; @@ -147,20 +862,22 @@ }); // add the mapping - branchingActivity.groupsToBranches.push({ + groupsToBranches.push({ 'uiid' : ++layout.ld.maxUIID, 'group' : group, 'branch' : branch }); }); if (assignedToDefault){ + // ask the user if it's OK to assign groups to default branch close = confirm(LABELS.GROUPS_TO_DEFAULT_BRANCH_CONFIRM); } if (close) { + branchingActivity.groupsToBranches = groupsToBranches; dialog.dialog('close'); - setModified(true); + GeneralLib.setModified(true); } } } @@ -191,7 +908,7 @@ // initialise dialog for defining input conditions - var outputConditionsDialog = layout.items.outputConditionsDialog = $('#outputConditionsDialog').dialog({ + var outputConditionsDialog = layout.outputConditionsDialog = $('#outputConditionsDialog').dialog({ 'autoOpen' : false, 'modal' : true, 'show' : 'fold', @@ -259,7 +976,7 @@ activity = dialog.dialog('option', 'parentObject'); // extract created mappings from UI - if (activity instanceof ActivityLib.BranchingActivity) { + if (activity instanceof ActivityDefs.BranchingActivity) { activity.conditionsToBranches = []; $('#rangeConditions tr, #complexConditions li', dialog).each(function(){ // if it's hidden, then another output was selected, so skip it @@ -304,7 +1021,7 @@ * Get output definitions from Tool activity */ 'refreshDefinitions' : function(){ - var dialog = layout.items.outputConditionsDialog, + var dialog = layout.outputConditionsDialog, activity = dialog.dialog('option', 'parentObject'); $.ajax({ @@ -327,7 +1044,7 @@ * Link output data to UI widgets */ 'buildContent' : function(useDefaultConditions) { - var dialog = layout.items.outputConditionsDialog, + var dialog = layout.outputConditionsDialog, activity = dialog.dialog('option', 'parentObject'), outputSelect = $('#outputSelect', dialog), emptyOption = $('option[value="none"]', outputSelect).attr('selected', 'selected'), @@ -376,7 +1093,7 @@ * Rebuild dialog content based on selected output */ 'outputChange' : function(useDefaultConditions){ - var dialog = layout.items.outputConditionsDialog, + var dialog = layout.outputConditionsDialog, container = dialog.closest('.ui-dialog'), activity = dialog.dialog('option', 'parentObject'), outputOption = $('#outputSelect option:selected', dialog), @@ -464,7 +1181,7 @@ * Show widgets appropriate for given range definition */ 'rangeOptionChange' : function(){ - var dialog = layout.items.outputConditionsDialog, + var dialog = layout.outputConditionsDialog, selectedOption = $('#rangeOptionSelect option:selected', dialog).attr('value'); switch(selectedOption){ @@ -520,7 +1237,7 @@ $('#rangeOptionSelect', outputConditionsDialog).change(outputConditionsDialog.dialog('option', 'rangeOptionChange')); // add a new range condition $('#rangeAddButton', outputConditionsDialog).button().click(function(){ - var dialog = layout.items.outputConditionsDialog, + var dialog = layout.outputConditionsDialog, rangeConditionNames = $('#rangeConditions', dialog), selectedOption = $('#rangeOptionSelect option:selected', dialog).attr('value'), output = $('#outputSelect option:selected', dialog).data('output'), @@ -598,7 +1315,7 @@ // initialise dialog for matching conditions to branches/gate states in branching/gate activities - var ctbDialog = layout.items.conditionsToBranchesMappingDialog = $('#branchMappingDialog') + var ctbDialog = layout.conditionsToBranchesMappingDialog = $('#branchMappingDialog') .clone() .attr('id','ctbDialog') .dialog({ @@ -626,13 +1343,14 @@ 'beforeClose' : function(event) { var dialog = $(this), activity = dialog.dialog('option', 'activity'), - isGate = activity instanceof ActivityLib.GateActivity, + mappingCopy = activity.conditionsToBranches.slice(), + isGate = activity instanceof ActivityDefs.GateActivity, assignedToDefault = false, defaultBranch = isGate ? 'closed' : null, close = true; // see what was mapped - $.each(activity.conditionsToBranches, function(){ + $.each(mappingCopy, function(){ var mappingEntry = this; mappingEntry.branch = null; // find references to conditions and branches @@ -684,7 +1402,8 @@ } if (close) { - setModified(true); + activity.conditionsToBranches = mappingCopy; + GeneralLib.setModified(true); } // if false, the dialog will not close @@ -717,694 +1436,32 @@ /** - * Opens properties dialog with the contents specific for each activity + * Make a pair out of selected group/condition and branch. */ - openPropertiesDialog : function(object) { - object.loadPropertiesDialogContent(); - var dialog = layout.items.propertiesDialog; - dialog.children().detach(); - dialog.append(object.propertiesContent); - dialog.dialog('open'); - dialog.find('input').blur(); - var box = object.items.getBBox(), - x = box.x2 + canvas.offset().left + 5, - y = box.y + canvas.offset().top; - dialog.dialog('option', 'position', [x, y]); - if (dialog.offset().left < box.x2 + canvas.offset().left) { - // if dialog covers the activity (too close to right border), - // move it to the other side - x = box.x + canvas.offset().left - dialog.width() - 35; - dialog.dialog('option', 'position', [x, y]); - } - }, - - - /** - * Properties dialog content for transitions. - */ - transitionProperties : function() { - var transition = this, - content = transition.propertiesContent; - if (!content) { - // first run, create the content - content = transition.propertiesContent = $('#propertiesContentTransition').clone().attr('id', null) - .show().data('parentObject', transition); - $('.propertiesContentFieldTitle', content).val(transition.title); - - var defaultBranch = $('.propertiesContentFieldDefault', content); - if (transition.branch) { - defaultBranch.show(); - } else { - defaultBranch.hide(); - } - - $('input', content).change(function(){ - // extract changed properties and redraw the transition - var content = $(this).closest('.dialogContainer'), - transition = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != transition.title) { - if (nameValidator.test(newTitle)) { - transition.title = newTitle; - if (transition.branch) { - transition.branch.title = newTitle; - } - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - - if (transition.branch) { - transition.branch.defaultBranch = $('.propertiesContentFieldDefault', content).is(':checked'); - if (transition.branch.defaultBranch) { - $.each(transition.branch.branchingActivity.branches, function(){ - if (this != transition.branch) { - this.defaultBranch = false; - } - }); - } - } - - if (redrawNeeded) { - transition.draw(); - ActivityLib.addSelectEffect(activity, true); - setModified(true); - } - }); - } - - $('.propertiesContentFieldDefault', content).attr('checked', - transition.branch && transition.branch.defaultBranch ? 'checked' : null); - }, - - - /** - * Properties dialog content for Tool activities. - */ - toolProperties : function() { - var activity = this, - content = activity.propertiesContent, - allowsGrouping = !this.parentActivity || !(this.parentActivity instanceof ActivityLib.ParallelActivity); + addBranchMapping : function(dialog){ + var selectedItem = $('.branchMappingFreeItemCell .selected', dialog), + selectedBranch = $('.branchMappingFreeBranchCell .selected', dialog); - if (!content) { - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentTool').clone().attr('id', null) - .show().data('parentObject', activity); - $('.propertiesContentFieldTitle', content).val(activity.title); - if (!allowsGrouping) { - $('.propertiesContentFieldGrouping', content).closest('tr').remove(); - } - - $('input, select', content).change(function(){ - // extract changed properties and redraw the activity - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != activity.title) { - if (nameValidator.test(newTitle)) { - activity.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - - var selectedGrouping = $('.propertiesContentFieldGrouping option:selected', content); - if (selectedGrouping.length > 0){ - var newGroupingValue = $('.propertiesContentFieldGrouping option:selected', content) - .data('grouping'); - if (newGroupingValue != activity.grouping) { - activity.grouping = newGroupingValue; - redrawNeeded = true; - } - } - - if (redrawNeeded) { - activity.draw(); - ActivityLib.addSelectEffect(activity, true); - setModified(true); - } - }); + if (selectedItem.length != 1 || selectedBranch.length != 1) { + return; } - if (allowsGrouping){ - PropertyLib.fillGroupingDropdown(activity, activity.grouping); - } + // original branch stays in its list + selectedBranch = selectedBranch.clone().click(PropertyLib.selectBranchMappingListItem); + // add info about the pair for later reference + selectedItem.data('boundItem', selectedBranch); + selectedBranch.data('boundItem', selectedItem); + var itemCell = $('.branchMappingBoundItemCell', dialog), + branchCell = $('.branchMappingBoundBranchCell', dialog); + // clear existing selection + $('.selected', itemCell).removeClass('selected'); + $('.selected', branchCell).removeClass('selected'); + itemCell.append(selectedItem); + branchCell.append(selectedBranch); }, /** - * Properties dialog content for Grouping activities. - */ - groupingProperties : function() { - var activity = this, - content = activity.propertiesContent; - - if (!content) { - // make onChange function a local variable, because it's used several times - var changeFunction = function(){ - // extract changed properties and redraw the activity, if needed - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(), - newGroupCount = +$('.propertiesContentFieldGroupCount', content).val(); - if (newTitle != activity.title) { - if (nameValidator.test(newTitle)) { - activity.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - - activity.groupingType = $('.propertiesContentFieldGroupingType', content).val(); - - $('input[name="propertiesContentFieldGroupDivide"]', content).each(function(){ - // enable/disable division types, depending on radio buttons next to them - $(this).next().find('.spinner').spinner('option', 'disabled', !$(this).is(':checked')); - }) - // hide group/learner division with some grouping types - .add($('.propertiesContentFieldLearnerCount', content).closest('tr')) - .css('display', activity.groupingType == 'monitor' ? 'none' : ''); - - $('.propertiesContentFieldEqualSizes, .propertiesContentFieldViewLearners', content) - .closest('tr').css('display', activity.groupingType == 'learner' ? '' : 'none'); - - activity.groupDivide = activity.groupingType == 'monitor' || - $('.propertiesContentFieldGroupCountEnable', content).is(':checked') ? - 'groups' : 'learners'; - $('.propertiesContentFieldNameGroups', content).css('display', - activity.groupDivide == 'groups' ? '' : 'none'); - - if (activity.groupCount != newGroupCount){ - activity.groupCount = newGroupCount; - activity.groups = PropertyLib.fillNameAndUIIDList(activity.groupCount, activity.groups, 'name', - LABELS.DEFAULT_GROUP_PREFIX); - } - activity.learnerCount = +$('.propertiesContentFieldLearnerCount', content).val(); - activity.equalSizes = $('.propertiesContentFieldEqualSizes', content).is(':checked'); - activity.viewLearners = $('.propertiesContentFieldViewLearners', content).is(':checked'); - - if (redrawNeeded) { - activity.draw(); - ActivityLib.addSelectEffect(activity, true); - } - - setModified(true); - }; - - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentGrouping').clone().attr('id', null) - .show().data('parentObject', activity); - - // init widgets - $('.propertiesContentFieldTitle', content).val(activity.title); - $('.propertiesContentFieldGroupingType', content).val(activity.groupingType); - if (activity.groupDivide == 'learners') { - $('.propertiesContentFieldLearnerCountEnable', content).attr('checked', 'checked'); - } else { - $('.propertiesContentFieldGroupCountEnable', content).attr('checked', 'checked'); - } - - // create groups/learners spinners - $('.propertiesContentFieldGroupCount', content).spinner({ - 'min' : 2, - 'disabled' : activity.groupDivide == 'learners' - }).spinner('value', activity.groupCount) - .on('spinchange', changeFunction); - - $('.propertiesContentFieldLearnerCount', content).spinner({ - 'min' : 1, - 'disabled' : activity.groupDivide == 'groups' - }).spinner('value', activity.learnerCount) - .on('spinchange', changeFunction); - - $('.propertiesContentFieldEqualSizes', content).attr('checked', activity.equalSizes ? 'checked' : null); - $('.propertiesContentFieldViewLearners', content).attr('checked', activity.viewLearners ? 'checked' : null); - $('.propertiesContentFieldNameGroups', content).button().click(function(){ - PropertyLib.openGroupNamingDialog(activity); - }); - - $('input, select', content).change(changeFunction); - changeFunction.call(content); - } - }, - - - /** - * Properties dialog content for Gates. - */ - gateProperties : function() { - var activity = this, - content = activity.propertiesContent; - if (!content) { - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentGate').clone().attr('id', null) - .show().data('parentObject', activity); - $('.propertiesContentFieldTitle', content).val(activity.title); - $('.propertiesContentFieldDescription', content).val(activity.description ? activity.description : ''); - $('.propertiesContentFieldGateType', content).val(activity.gateType); - - $('.propertiesContentFieldCreateConditions', content).button().click(function(){ - PropertyLib.openOutputConditionsDialog(activity); - }); - $('.propertiesContentFieldMatchConditions', content).button().click(function(){ - PropertyLib.openConditionsToBranchesMappingDialog(activity); - }); - - // make onChange function a local variable, because it's used several times - var changeFunction = function(){ - // extract changed properties and redraw the activity - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != activity.title) { - if (nameValidator.test(newTitle)) { - activity.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - - activity.description = $('.propertiesContentFieldDescription', content).val(); - - activity.gateType = $('.propertiesContentFieldGateType', content).val(); - if (activity.gateType == 'schedule') { - $(".propertiesContentRowGateSchedule").show(); - activity.offsetDay = +$('.propertiesContentFieldOffsetDay', content).val(); - activity.offsetHour = +$('.propertiesContentFieldOffsetHour', content).val(); - activity.offsetMinute = +$('.propertiesContentFieldOffsetMinute', content).val(); - activity.gateActivityCompletionBased = $('.propertiesContentFieldActivityCompletionBased').is(':checked'); - } else { - $(".propertiesContentRowGateSchedule").hide(); - } - - var inputRow = $('.propertiesContentFieldInput', content).closest('tr'), - inputDefinitionRows = $('.propertiesContentRowConditions', content); - if (activity.gateType == 'condition') { - activity.input = inputRow.show().find('option:selected').data('input'); - if (activity.input) { - inputDefinitionRows.show(); - } else { - inputDefinitionRows.hide(); - } - } else { - inputRow.hide(); - inputDefinitionRows.hide(); - } - - if (redrawNeeded) { - activity.draw(); - ActivityLib.addSelectEffect(activity, true); - } - - setModified(true); - }; - - // create groups/learners spinners - $('.propertiesContentFieldOffsetDay', content).spinner({ - 'min' : 0, - 'max' : 364 - }).spinner('value', activity.offsetDay || 0) - .on('spinchange', changeFunction); - - $('.propertiesContentFieldOffsetHour', content).spinner({ - 'min' : 0, - 'max' : 23 - }).spinner('value', activity.offsetHour || 0) - .on('spinchange', changeFunction); - - $('.propertiesContentFieldOffsetMinute', content).spinner({ - 'min' : 0, - 'max' : 59 - }).spinner('value', activity.offsetMinute || 0) - .on('spinchange', changeFunction); - - $('.propertiesContentFieldActivityCompletionBased', content) - .attr('checked', activity.gateActivityCompletionBased? 'checked' : null); - - $('input, textarea, select', content).change(changeFunction); - PropertyLib.fillToolInputDropdown(activity, activity.input); - changeFunction.call(content); - } - - if (activity.transitions.to.length == 0){ - $('.propertiesContentFieldActivityCompletionBased', content) - .attr('checked', null) - .attr('disabled', 'disabled'); - } else { - $('.propertiesContentFieldActivityCompletionBased', content) - .attr('disabled', null); - } - - PropertyLib.fillToolInputDropdown(activity, activity.input); - }, - - - /** - * Properties dialog content for Branching activities. - */ - branchingProperties : function() { - var activity = this, - content = activity.propertiesContent, - fillWidgetsFunction = function(){ - $('.propertiesContentFieldTitle', content).val(activity.branchingActivity.title); - $('.propertiesContentFieldBranchingType', content).val(activity.branchingActivity.branchingType); - PropertyLib.fillGroupingDropdown(activity, activity.branchingActivity.grouping); - PropertyLib.fillToolInputDropdown(activity, activity.branchingActivity.input); - - $('.propertiesContentFieldOptionalSequenceMin', content).spinner('value', - activity.branchingActivity.minOptions) - .spinner('option', 'max', - activity.branchingActivity.branches.length); - $('.propertiesContentFieldOptionalSequenceMax', content).spinner('value', - activity.branchingActivity.maxOptions) - .spinner('option', { - 'min' : activity.branchingActivity.minOptions, - 'max' : activity.branchingActivity.branches.length - }); - if (activity.branchingActivity.branches.length == 0) { - $('.propertiesContentRowConditions', content) - .add($('.propertiesContentFieldMatchGroups', content).closest('tr')) - .hide(); - } - } - - if (!content) { - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentBranching').clone().attr('id', null) - .show().data('parentObject', activity); - $('.propertiesContentFieldMatchGroups', content).button().click(function(){ - PropertyLib.openGroupsToBranchesMappingDialog(activity.branchingActivity); - }); - $('.propertiesContentFieldCreateConditions', content).button().click(function(){ - PropertyLib.openOutputConditionsDialog(activity.branchingActivity); - }); - $('.propertiesContentFieldMatchConditions', content).button().click(function(){ - PropertyLib.openConditionsToBranchesMappingDialog(activity.branchingActivity); - }); - - var changeFunction = function(){ - // extract changed properties and redraw the activity - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - branchingActivity = activity.branchingActivity, - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != branchingActivity.title) { - if (nameValidator.test(newTitle)) { - branchingActivity.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - branchingActivity.branchingType = $('.propertiesContentFieldBranchingType', content).val(); - - var groupingRow = $('.propertiesContentFieldGrouping', content).closest('tr'); - if (branchingActivity.branchingType == 'group') { - branchingActivity.grouping = groupingRow.show() - .find('option:selected').data('grouping'); - } else { - groupingRow.hide(); - } - $('.propertiesContentFieldMatchGroups', content).closest('tr') - .css('display', branchingActivity.grouping && branchingActivity.branches.length > 0 ? '' : 'none'); - - var inputRow = $('.propertiesContentFieldInput', content).closest('tr'), - inputDefinitionRows = $('.propertiesContentRowConditions', content); - if (branchingActivity.branchingType == 'tool') { - branchingActivity.input = inputRow.show() - .find('option:selected').data('input'); - if (branchingActivity.input && branchingActivity.branches.length > 0) { - inputDefinitionRows.show(); - } else { - inputDefinitionRows.hide(); - } - } else { - inputRow.hide(); - inputDefinitionRows.hide(); - } - - var optionalSequenceRows = $('.spinner', content).closest('tr'); - if (branchingActivity.branchingType == 'optional') { - optionalSequenceRows.show(); - } else { - optionalSequenceRows.hide(); - } - - if (redrawNeeded) { - branchingActivity.start.draw(); - branchingActivity.end.draw(); - ActivityLib.addSelectEffect(layout.items.selectedObject, true); - } - - setModified(true); - } - - $('.propertiesContentFieldOptionalSequenceMin', content).spinner({'min' : 0}) - .on('spinchange', function(){ - var value = +$(this).val(); - activity.branchingActivity.minOptions = Math.min(value, activity.branchingActivity.branches.length); - if (value != activity.branchingActivity.minOptions) { - $(this, content).spinner('value', activity.branchingActivity.minOptions); - } - if (activity.branchingActivity.minOptions > activity.branchingActivity.maxOptions) { - $('.propertiesContentFieldOptionalSequenceMax', content).spinner('value', value); - } - $('.propertiesContentFieldOptionalSequenceMax', content).spinner('option', 'min', value); - }); - - - $('.propertiesContentFieldOptionalSequenceMax', content).spinner({'min' : 0}) - .on('spinchange', function(){ - var value = +$(this).val(); - activity.branchingActivity.maxOptions = Math.min(value, activity.branchingActivity.branches.length); - if (value != activity.branchingActivity.maxOptions) { - $(this, content).spinner('value', activity.branchingActivity.maxOptions); - } - }); - - $('input, select', content).change(changeFunction); - fillWidgetsFunction(); - changeFunction.call(content); - } - - - fillWidgetsFunction(); - }, - - - /** - * Properties dialog content for Parallel activities. - */ - parallelProperties : function() { - var activity = this, - content = activity.propertiesContent; - - if (!content) { - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentParallel').clone().attr('id', null) - .show().data('parentObject', activity); - $('.propertiesContentFieldTitle', content).val(activity.title); - - $('input, select', content).change(function(){ - // extract changed properties and redraw the activity - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != activity.title) { - if (nameValidator.test(newTitle)) { - activity.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - var newGroupingValue = $('.propertiesContentFieldGrouping option:selected', content) - .data('grouping'); - if (newGroupingValue != activity.grouping) { - activity.grouping = newGroupingValue; - redrawNeeded = true; - } - - if (redrawNeeded) { - activity.draw(); - ActivityLib.addSelectEffect(activity, true); - setModified(true); - } - }); - } - - PropertyLib.fillGroupingDropdown(activity, activity.grouping); - }, - - - /** - * Properties dialog content for Optional Activity. - */ - optionalActivityProperties : function() { - var activity = this, - content = activity.propertiesContent; - - activity.minOptions = Math.min(activity.minOptions, activity.childActivities.length); - activity.maxOptions = Math.min(activity.maxOptions, activity.childActivities.length); - - if (!content) { - // first run, create the content - content = activity.propertiesContent = $('#propertiesContentOptionalActivity').clone().attr('id', null) - .show().data('parentObject', activity); - $('.propertiesContentFieldTitle', content).val(activity.title); - - $('input', content).change(function(){ - // extract changed properties and redraw the transition - var content = $(this).closest('.dialogContainer'), - activity = content.data('parentObject'), - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != activity.title) { - if (nameValidator.test(newTitle)) { - activity.title = newTitle; - activity.draw(); - ActivityLib.addSelectEffect(activity, true); - setModified(true); - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - }); - - $('.propertiesContentFieldOptionalActivityMin', content).spinner({'min' : 0}) - .on('spinchange', function(){ - var value = +$(this).val(); - activity.minOptions = Math.min(value, activity.childActivities.length); - if (value != activity.minOptions) { - $(this, content).spinner('value', activity.minOptions); - } - if (activity.minOptions > activity.maxOptions) { - $('.propertiesContentFieldOptionalActivityMax', content).spinner('value', value); - } - $('.propertiesContentFieldOptionalActivityMax', content).spinner('option', 'min', value); - }); - - - $('.propertiesContentFieldOptionalActivityMax', content).spinner({'min' : 0}) - .on('spinchange', function(){ - var value = +$(this).val(); - activity.maxOptions = Math.min(value, activity.childActivities.length); - if (value != activity.maxOptions) { - $(this, content).spinner('value', activity.maxOptions); - } - }); - } - - $('.propertiesContentFieldOptionalActivityMin', content).spinner('value', activity.minOptions) - .spinner('option', 'max', activity.childActivities.length); - $('.propertiesContentFieldOptionalActivityMax', content).spinner('value', activity.maxOptions) - .spinner('option', { - 'min' : activity.minOptions, - 'max' : activity.childActivities.length - }); - }, - - - /** - * Properties dialog content for regions (annotations). - */ - regionProperties : function() { - var region = this, - content = region.propertiesContent; - if (!content) { - // first run, create the content - content = region.propertiesContent = $('#propertiesContentRegion').clone().attr('id', null) - .show().data('parentObject', region); - - $('.propertiesContentFieldTitle', content).val(region.title); - var color = region.items.shape.attr('fill'); - $('.propertiesContentFieldColor', content).val(color) - .simpleColor({ - 'colors' : layout.colors.annotationPalette, - 'chooserCSS' : { - 'left' : 0, - 'top' : '30px', - 'margin' : '0' - } - }); - - $('input', content).change(function(){ - // extract changed properties and redraw the transition - var content = $(this).closest('.dialogContainer'), - region = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(), - color = region.items.shape.attr('fill'), - newColor = $('.propertiesContentFieldColor', content).val(); - if (newTitle != region.title) { - if (nameValidator.test(newTitle)) { - region.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - redrawNeeded |= newColor != color; - - if (redrawNeeded) { - region.draw(null, null, null, null, newColor); - ActivityLib.addSelectEffect(region, true); - setModified(true); - } - }); - } - - - }, - - - /** - * Properties dialog content for labels (annotations). - */ - labelProperties : function() { - var label = this, - content = label.propertiesContent; - if (!content) { - // first run, create the content - content = label.propertiesContent = $('#propertiesContentLabel').clone().attr('id', null) - .show().data('parentObject', label); - $('.propertiesContentFieldTitle', content).val(label.title); - - $('input', content).change(function(){ - // extract changed properties and redraw the transition - var content = $(this).closest('.dialogContainer'), - label = content.data('parentObject'), - redrawNeeded = false, - newTitle = $('.propertiesContentFieldTitle', content).val(); - if (newTitle != label.title) { - if (nameValidator.test(newTitle)) { - label.title = newTitle; - redrawNeeded = true; - } else { - alert(LABELS.TITLE_VALIDATION_ERROR); - } - } - - if (redrawNeeded) { - label.draw(); - ActivityLib.addSelectEffect(label, true); - setModified(true); - } - }); - } - }, - - - /** * Find all groupings on canvas and fill dropdown menu with their titles */ fillGroupingDropdown : function(activity, grouping) { @@ -1413,10 +1470,10 @@ groupingDropdown = $('.propertiesContentFieldGrouping', activity.propertiesContent).empty().append(emptyOption), groupings = []; - if (activity.parentActivity && activity.parentActivity instanceof ActivityLib.FloatingActivity) { + if (activity.parentActivity && activity.parentActivity instanceof ActivityDefs.FloatingActivity) { // Support activities can use any grouping on canvas $.each(layout.activities, function(){ - if (this instanceof ActivityLib.GroupingActivity) { + if (this instanceof ActivityDefs.GroupingActivity) { groupings.push(this); } }); @@ -1434,7 +1491,7 @@ candidate = null; } - if (candidate instanceof ActivityLib.GroupingActivity) { + if (candidate instanceof ActivityDefs.GroupingActivity) { groupings.push(candidate); } } while (candidate != null); @@ -1455,13 +1512,33 @@ /** + * Creates group and branch names, if they are not already provided. + */ + fillNameAndUIIDList : function(size, existingList, nameAttr, prefix) { + var list = []; + for (var itemIndex = 1; itemIndex <= size; itemIndex++) { + // generate a name and an UIID if they do not exist + var item = itemIndex > existingList.length ? {} : existingList[itemIndex - 1]; + if (!item[nameAttr]) { + item[nameAttr] = prefix + itemIndex; + } + if (!item.uiid) { + item.uiid = ++layout.ld.maxUIID; + } + list.push(item); + } + return list; + }, + + + /** * Find all activities that support outputs and fill dropdown menu with their titles */ fillToolInputDropdown : function(activity, input) { // find all tools that support input and fill dropdown menu with their titles var emptyOption = $('