this.reloadRoot();
},
- createElement: function(wlxmlNode, register, useRoot) {
+ createElement: function(wlxmlNode, register, useRoot, params) {
var Factory;
+ params = params || {
+ mirror: false
+ };
register = register || this.elementsRegister;
if(wlxmlNode.nodeType === Node.TEXT_NODE) {
Factory = documentElement.DocumentTextElement;
}
if(Factory) {
- return new Factory(wlxmlNode, this);
+ return new Factory(wlxmlNode, this, params);
}
},
this.element = element;
nodes.forEach(function(node) {
- var el = this.element.createElement(node);
+ var el = this.element.createElement(node, {mirror: this.mirrors});
if(el.dom) {
this.dom.append(el.dom);
}
var ptr = event.meta.node.prev(),
referenceElement, referenceAction, actionArg;
- while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr))) {
+ while(ptr && !(referenceElement = utils.getElementForElementRootNode(ptr, false, this))) {
ptr = ptr.prev();
}
if(event.meta.move) {
/* Let's check if this node had its own canvas element and it's accessible. */
- actionArg = utils.getElementForElementRootNode(event.meta.node);
+ actionArg = utils.getElementForElementRootNode(event.meta.node, false, this);
}
if(!actionArg) {
actionArg = event.meta.node;
}
- referenceElement[referenceAction](actionArg);
+ if(referenceAction === 'after') {
+ referenceElement.after(actionArg, {mirror: this.mirrors});
+ } else {
+ referenceElement[referenceAction](actionArg);
+ }
},
onNodeDetached: function(event) {
var container = this;
if(param instanceof documentElement.DocumentElement) {
element = param;
} else {
- element = this.element.createElement(param);//
+ element = this.element.createElement(param, {mirror: this.mirrors});//
}
if(element.dom) {
this.dom.prepend(element.dom);
/* global Node:false */
// DocumentElement represents a text or an element node from WLXML document rendered inside Canvas
-var DocumentElement = function(wlxmlNode, canvas) {
+var DocumentElement = function(wlxmlNode, canvas, params) {
+ params = params || {
+ mirror: false
+ };
this.wlxmlNode = wlxmlNode;
+ this.mirror = params.mirror;
this.canvas = canvas;
this.state = {
exposed: false,
this.dom = this.createDOM();
this.dom.data('canvas-element', this);
- this.wlxmlNode.setData('canvasElement', this);
+
+ var mirrorElements = this.wlxmlNode.getData('mirrorElements');
+ if(!mirrorElements) {
+ mirrorElements = [];
+ this.wlxmlNode.setData('mirrorElements', mirrorElements);
+ }
+ if(params.mirror) {
+ mirrorElements.push(this);
+ } else {
+ this.wlxmlNode.setData('canvasElement', this);
+ }
};
$.extend(DocumentElement.prototype, {
// DocumentNodeElement represents an element node from WLXML document rendered inside Canvas
-var DocumentNodeElement = function(wlxmlNode, canvas) {
- DocumentElement.call(this, wlxmlNode, canvas);
+var DocumentNodeElement = function(wlxmlNode, canvas, params) {
+ DocumentElement.call(this, wlxmlNode, canvas, params);
this.containers = [];
this.elementsRegister = canvas.createElementsRegister();
this.contextMenuActions = [];
};
-var manipulate = function(e, params, action) {
+var manipulate = function(e, params, action, params2) {
var element;
if(params instanceof DocumentElement) {
element = params;
} else {
- element = e.createElement(params);
+ element = e.createElement(params, params2);
}
if(element.dom) {
e.dom[action](element.dom);
this.containers.splice(idx, 1);
}
},
- createElement: function(wlxmlNode) {
+ createElement: function(wlxmlNode, params) {
+ params = params || {mirror: false};
var parent = this.wlxmlNode.parent() ? utils.getElementForNode(this.wlxmlNode.parent()) : null;
- return this.canvas.createElement(wlxmlNode, this.elementsRegister, !parent) || parent.createElement(wlxmlNode);
+ return this.canvas.createElement(wlxmlNode, this.elementsRegister, !parent, params) || parent.createElement(wlxmlNode, params);
},
addToContextMenu: function(actionFqName) {
this.contextMenuActions.push(this.canvas.createAction(actionFqName));
},
handle: function(event) {
var method = 'on' + event.type[0].toUpperCase() + event.type.substr(1),
- target;
+ containerExisted = false;
if(event.type === 'nodeAdded' || event.type === 'nodeDetached') {
this.containers.some(function(container) {
if(container.manages(event.meta.node, event.meta.parent)) {
- target = container;
- return true;
+ //target = container;
+ container[method](event);
+ containerExisted = true;
}
});
}
- if(!target && this[method]) {
- target = this;
+ if(!containerExisted && this[method]) {
+ this[method](event);
}
- if(target) {
- target[method](event);
- }
+ // if(target) {
+ // target[method](event);
+ // }
},
createDOM: function() {
var wrapper = $('<div>').attr('document-node-element', ''),
}
return this;
},
- before: function(params) {
- return manipulate(this, params, 'before');
+ before: function(params, params2) {
+ return manipulate(this, params, 'before', params2);
},
- after: function(params) {
- return manipulate(this, params, 'after');
+ after: function(params, params2) {
+ return manipulate(this, params, 'after', params2);
},
isBlock: function() {
// DocumentNodeElement represents a text node from WLXML document rendered inside Canvas
-var DocumentTextElement = function(wlxmlTextNode, canvas) {
- DocumentElement.call(this, wlxmlTextNode, canvas);
+var DocumentTextElement = function(wlxmlTextNode, canvas, params) {
+ DocumentElement.call(this, wlxmlTextNode, canvas, params);
};
$.extend(DocumentTextElement, {
// Having at least Zero Width Space is guaranteed be Content Observer
return this.dom.contents()[0].data === utils.unicode.ZWS;
},
- after: function(params) {
+ after: function(params, params2) {
if(params instanceof DocumentTextElement || params.text) {
return false;
}
if(params instanceof DocumentNodeElement) {
element = params;
} else {
- element = this.parent().createElement(params);
+ element = this.parent().createElement(params, params2);
}
if(element.dom) {
this.dom.wrap('<div>');
this.container = this.createContainer(this.wlxmlNode.contents(), {
manages: function(node) { return !node.isInside('comment'); },
- dom: this._container()
+ dom: this._container(),
+ mirrors: this.mirror
});
this.commentsView = new CommentsView(this.wlxmlNode, this.canvas.metadata.user);
return null;
};
-var getElementForElementRootNode = function(node) {
- return node.getData('canvasElement');
+var getElementForElementRootNode = function(node, mirrors, canvasContainer) {
+ if(canvasContainer) {
+ var candidates = [node.getData('canvasElement')].concat(node.getData('mirrorElements')),
+ toret;
+ candidates.some(function(c) {
+ // @@
+ if(c.dom.parents().index(canvasContainer.dom) !== -1) {
+ toret = c;
+ return true;
+ }
+ });
+ return toret;
+ }
+ return node.getData(mirrors ? 'mirrorElements' : 'canvasElement');
};
-var getElementForNode = function(node) {
+var getElementForNode = function(node, mirrors) {
while(!node.getData('canvasElement')) {
node = node.parent();
}
- return node.getData('canvasElement');
+ return node.getData(mirrors ? 'mirrorElements' : 'canvasElement');
};
-var getElementForDetachedNode = function(node, originalParent) {
+var getElementForDetachedNode = function(node, originalParent, mirrors) {
var ptr = originalParent;
if(ptr === null) {
- return node.getData('canvasElement');
+ return node.getData(mirrors ? 'mirrorElements' : 'canvasElement');
}
while(!ptr.getData('canvasElement')) {
ptr = ptr.parent();
}
- return ptr.getData('canvasElement');
+ return ptr.getData(mirrors ? 'mirrorElements' : 'canvasElement');
};
var caretPositionFromPoint = function(x, y) {
var _metadataEventHandler = function(event) {
- var element = utils.getElementForNode(event.meta.node);
+ var element = utils.getElementForNode(event.meta.node),
+ mirrors = utils.getElementForNode(event.meta.node, true);
element.handle(event);
+ mirrors.forEach(function(mirror) {
+ mirror.handle(event);
+ });
};
var handlers = {
nodeAttrChange: function(event) {
var element = utils.getElementForNode(event.meta.node),
+ mirrors = utils.getElementForNode(event.meta.node, true),
newElement;
if(event.meta.attr === 'class') {
if(element.wlxmlNode.getClass() !== event.meta.attr) {
} else {
newElement = element.parent().createElement(event.meta.node);
element.dom.replaceWith(newElement.dom);
+ mirrors.forEach(function(mirror) {
+ var newElement = element.parent().createElement(event.meta.node, true);
+ mirror.dom.replaceWith(newElement.dom);
+ });
}
}
} else {
element.handle(event);
+ mirrors.forEach(function(mirror) {
+ mirror.handle(event);
+ });
}
},
nodeAdded: function(event) {
this.canvas.reloadRoot();
return;
}
-
var containingNode = event.meta.node.parent(),
- containingElement = utils.getElementForNode(containingNode);
+ containingElement = utils.getElementForNode(containingNode),
+ mirrors = utils.getElementForNode(containingNode, true);
containingElement.handle(event);
+ mirrors.forEach(function(mirror) {
+ mirror.handle(event);
+ });
},
nodeDetached: function(event) {
- var element = utils.getElementForDetachedNode(event.meta.node, event.meta.parent);
+ var element = utils.getElementForDetachedNode(event.meta.node, event.meta.parent),
+ mirrors = utils.getElementForDetachedNode(event.meta.node, event.meta.parent, true);
element.handle(event);
+ mirrors.forEach(function(mirror) {
+ mirror.handle(event);
+ });
},
nodeTextChange: function(event) {
- var element = utils.getElementForNode(event.meta.node);
+ var element = utils.getElementForNode(event.meta.node),
+ mirrors = utils.getElementForNode(event.meta.node, true);
element.handle(event);
+ mirrors.forEach(function(mirror) {
+ mirror.handle(event);
+ });
},
metadataChanged: _metadataEventHandler,
if(cursorTarget) {
cursorTarget = cursorTarget.append({text: ''});
}
+ } else if(actionConfig.exercise === 'assign') {
+ cursorTarget = _.first(exerciseNode.contents('.p'));
+ if(cursorTarget) {
+ cursorTarget = cursorTarget.append({text: descriptionText});
+ }
}
if(cursorTarget) {
callback(doc.createFragment(doc.CaretFragment, {node: cursorTarget, offset: cursorTarget.getText().length}));
};
return [
+ createAction({name: 'insertAssignExercise', icon: 'resize-horizontal', exercise: 'assign', exerciseTitle: gettext('Assign')}),
createAction({name: 'insertOrderExercise', icon: 'random', exercise: 'order', exerciseTitle: gettext('Order')}),
createAction({name: 'insertChoiceSingleExercise', icon: 'ok-circle', exercise: 'choice.single', exerciseTitle: gettext('Single Choice')}),
createAction({name: 'insertChoiceMultiExercise', icon: 'check', exercise: 'choice', exerciseTitle: gettext('Multiple Choice')}),
--- /dev/null
+.exercise-assign {
+ .sources, .destinations {
+ width: 50%;
+ }
+
+ .source-item {
+ border: 1px solid lighten(@green, 30%);
+ }
+ .destination-item {
+ border: 1px solid lighten(@red, 30%);
+ }
+
+ .assignments {
+ margin-top: 5px;
+ border-top: 1px solid darken(#ddd,15%);
+ }
+ .wrapper {
+ border: 1px dashed #ddd;
+ margin: 5px 0;
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="destination-item">
+ <div class="content"></div>
+ <div class="assignments"></div>
+</div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+
+var _ = require('libs/underscore');
+
+var elementBase = require('plugins/core/edumed/elementBase'),
+ AssignExerciseView = require('./view');
+
+var AssignExerciseElement = Object.create(elementBase);
+_.extend(AssignExerciseElement, {
+ init: function() {
+ elementBase.init.call(this);
+
+ this.view = new AssignExerciseView(this, this.wlxmlNode);
+ this._container().append(this.view.dom);
+
+ this.view.on('assignmentAdded', function() {
+
+ }.bind(this));
+
+ this.view.on('assignmentRemoved', function() {
+
+ }.bind(this));
+
+
+ var exerciseNode = this.wlxmlNode;
+ this.createContainer(this.wlxmlNode.object.getDescription(), {
+ resetBackground: true,
+ manages: function(node, removedFrom) {
+ if(exerciseNode.object.isList(node) || (removedFrom && exerciseNode.object.isList(removedFrom))) {
+ return false;
+ }
+ return exerciseNode.sameNode(node.parent() || removedFrom);
+ }.bind(),
+ dom: this.view.dom.find('.description')
+ });
+
+ this.reloadView();
+ },
+ onNodeAdded: function(event) {
+ var node = event.meta.node;
+ if(this.wlxmlNode.object.isItemNode(node)) {
+ this.reloadView();
+ }
+ },
+ onNodeAttrChange: function(event) {
+ void(event);
+ },
+ onNodeDetached: function(event) {
+ if(this.wlxmlNode.object.isItemNode(event.meta.node, event.meta.parent)) {
+ this.reloadView();
+ }
+ },
+ reloadView: function() {
+ this.view.clearItems();
+ this.wlxmlNode.object.getSourceItems().forEach(function(item) {
+ this.view.addSourceItem(item);
+ }.bind(this));
+ this.wlxmlNode.object.getDestinationItems().forEach(function(item) {
+ this.view.addDestinationItem(item);
+ }.bind(this));
+ },
+ getVerticallyFirstTextElement: function() {
+ var toret;
+ this.containers.some(function(container) {
+ toret = container.getVerticallyFirstTextElement();
+ return !!toret;
+ });
+ return toret;
+ }
+});
+
+return {tag: 'div', klass: 'exercise.assign', prototype: AssignExerciseElement};
+
+});
--- /dev/null
+<div class="source-item"></div>
\ No newline at end of file
--- /dev/null
+<div class="edumed-exercise exercise-assign">
+ <div class="header"><%= gettext('Exercise') %>: <%= gettext('Assign') %></div>
+ <div class="description"></div>
+
+ <table class="table">
+ <tr>
+ <td class="sources"></td>
+ <td class="destinations"></td>
+ </tr>
+ </table>
+ <div class="placeholder placeholder-top"></div>
+</div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+var $ = require('libs/jquery');
+
+var _ = require('libs/underscore'),
+ Backbone = require('libs/backbone'),
+ viewTemplate = require('libs/text!./view.html'),
+ sourceItemTemplate = require('libs/text!./sourceItem.html'),
+ destinationItemTemplate = require('libs/text!./destinationItem.html');
+
+
+var AssignExerciseView = function(element) {
+ this.element = element;
+ this.dom = $(_.template(viewTemplate)());
+ this.modePills = this.dom.find('.modePills');
+ this.sources = this.dom.find('.sources');
+ this.destinations = this.dom.find('.destinations');
+ this.description = this.dom.find('.description');
+ this.sourceItemViews = [];
+ this.destinationItemViews = [];
+};
+
+_.extend(AssignExerciseView.prototype, Backbone.Events, {
+ addSourceItem: function(item) {
+ var view = new SourceItemView(item, this);
+ this.sources.append(view.dom);
+ this.sourceItemViews.push(view);
+ },
+ addDestinationItem: function(item) {
+ var view = new DestinationItemView(item, this);
+ view.on('receivedDrop', function(sourceItem, destinationItem) {
+ //this.trigger(this.mode === 'initial' ? 'moveItem' : 'moveAnswer', droppedItem.item, item, 'after');
+ sourceItem.assignTo(destinationItem);
+ }.bind(this));
+ this.destinations.append(view.dom);
+ this.destinationItemViews.push(view);
+ },
+ clearItems: function() {
+ this.sources.empty();
+ this.destinations.empty();
+ this.sourceItemViews.concat(this.destinationItemViews).forEach(function(view) {
+ view.remove();
+ });
+ this.sourceItemViews = [];
+ this.destinationItemViews = [];
+ }
+});
+
+var SourceItemView = function(item, exerciseView) {
+ this.item = item;
+ this.exerciseView = exerciseView;
+ this.dom = $(_.template(sourceItemTemplate)());
+
+
+ var dragSources = this.dom.find('.handle');
+
+ dragSources.on('dragstart', function(e) {
+ e.originalEvent.dataTransfer.setData('text', this.dom.attr('sourceItemId'));
+ e.originalEvent.effectAllowed = 'move';
+
+ }.bind(this));
+
+ this.container = exerciseView.element.createContainer(item.node.contents(), {
+ resetBackground: true,
+ manages: function(node, originaParent) {
+ return item.node.sameNode(node.parent() || originaParent);
+ },
+ dom: this.dom
+ });
+
+ this.dom.data('item', this.item);
+ this.dom.attr('itemDOMID', _.uniqueId());
+};
+
+_.extend(SourceItemView.prototype, Backbone.Events, {
+ remove: function() {
+ this.container.remove();
+ }
+});
+
+var DestinationItemView = function(item, exerciseView) {
+ this.item = item;
+ this.exerciseView = exerciseView;
+ this.dom = $(_.template(destinationItemTemplate)());
+
+ this.dom.on('dragover', function(e) {
+ e.preventDefault();
+ e.originalEvent.dataTransfer.dropEffect = 'move';
+ });
+
+ this.dom.on('drop', function(e) {
+ var DOMID = e.originalEvent.dataTransfer.getData('text'),
+ sourceItem = $('[DOMID='+DOMID+']').data('item');
+ e.preventDefault();
+ this.trigger('receivedDrop', sourceItem, this.item);
+ }.bind(this));
+
+ this.container = exerciseView.element.createContainer(item.node.contents(), {
+ resetBackground: true,
+ manages: function(node, originaParent) {
+ return item.node.sameNode(node.parent() || originaParent);
+ },
+ dom: this.dom.find('.content')
+ });
+
+ this.containers = [];
+
+ this.item.getAssignedSources().forEach(function(sourceItem) {
+ var wrapper = $('<div class="wrapper"></div>');
+ var container = exerciseView.element.createContainer(sourceItem.contents(), {
+ resetBackground: true,
+ manages: function(node, originaParent) {
+ return sourceItem.node.sameNode(node.parent() || originaParent);
+ },
+ dom: wrapper,
+ mirrors: true
+ });
+ this.containers.push(container);
+
+ this.dom.find('.assignments').append(container.dom);
+ }.bind(this));
+};
+_.extend(DestinationItemView.prototype, Backbone.Events, {
+ remove: function() {
+ this.container.remove();
+ this.containers.forEach(function(container) {
+ container.remove();
+ });
+ }
+});
+
+return AssignExerciseView;
+
+});
var actions = require('./actions'),
gapsActions = require('./gaps/actions'),
replaceActions = require('./replace/actions'),
+ assignExerciseElement = require('./assign/element'),
orderExerciseElement = require('./order/element'),
gapsExerciseElement = require('./gaps/element'),
replaceExerciseElement = require('./replace/element'),
return {
actions: actions.concat(gapsActions).concat(replaceActions),
- canvasElements: [orderExerciseElement, gapsExerciseElement, replaceExerciseElement].concat(choiceExerciseElements)
+ canvasElements: [
+ assignExerciseElement,
+ orderExerciseElement,
+ gapsExerciseElement,
+ replaceExerciseElement
+ ].concat(choiceExerciseElements)
};
});
\ No newline at end of file
+@import 'assign/assign.less';
@import 'order/order.less';
@import 'gaps/gaps.less';
@import 'replace/replace.less';