wip: experiments with Canvas support for exercise.assign exercise_assign
authorAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Mon, 18 Aug 2014 08:29:08 +0000 (10:29 +0200)
committerAleksander Łukasz <aleksander.lukasz@nowoczesnapolska.org.pl>
Mon, 18 Aug 2014 08:29:08 +0000 (10:29 +0200)
Incomplete and somewhat messy:

- partial custom element implementation (renders assignments
when set manually via source editor),
- experiments with mirroring elements on Canvas (partially working),
- incomplete drag & drop code.

15 files changed:
src/editor/modules/documentCanvas/canvas/canvas.js
src/editor/modules/documentCanvas/canvas/container.js
src/editor/modules/documentCanvas/canvas/documentElement.js
src/editor/modules/documentCanvas/canvas/genericElement.js
src/editor/modules/documentCanvas/canvas/utils.js
src/editor/modules/documentCanvas/canvas/wlxmlListener.js
src/editor/plugins/core/edumed/actions.js
src/editor/plugins/core/edumed/assign/assign.less [new file with mode: 0644]
src/editor/plugins/core/edumed/assign/destinationItem.html [new file with mode: 0644]
src/editor/plugins/core/edumed/assign/element.js [new file with mode: 0644]
src/editor/plugins/core/edumed/assign/sourceItem.html [new file with mode: 0644]
src/editor/plugins/core/edumed/assign/view.html [new file with mode: 0644]
src/editor/plugins/core/edumed/assign/view.js [new file with mode: 0644]
src/editor/plugins/core/edumed/edumed.js
src/editor/plugins/core/edumed/edumed.less

index ebede40..5d432ea 100644 (file)
@@ -120,8 +120,11 @@ $.extend(Canvas.prototype, Backbone.Events, {
         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;
@@ -136,7 +139,7 @@ $.extend(Canvas.prototype, Backbone.Events, {
         }
 
         if(Factory) {
-            return new Factory(wlxmlNode, this);
+            return new Factory(wlxmlNode, this, params);
         }
     },
 
index 663a159..28565bc 100644 (file)
@@ -16,7 +16,7 @@ var Container = function(nodes, params, element) {
     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);
         }
@@ -37,7 +37,7 @@ _.extend(Container.prototype, {
         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();
         }
 
@@ -50,13 +50,17 @@ _.extend(Container.prototype, {
       
         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;
@@ -104,7 +108,7 @@ _.extend(Container.prototype, {
         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);
index a1965cd..c53f562 100644 (file)
@@ -9,8 +9,12 @@ define([
 /* 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,
@@ -19,7 +23,17 @@ var DocumentElement = function(wlxmlNode, canvas) {
 
     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, {
@@ -92,8 +106,8 @@ $.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 = [];
@@ -101,12 +115,12 @@ var DocumentNodeElement = function(wlxmlNode, canvas) {
 };
 
 
-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);
@@ -148,32 +162,34 @@ $.extend(DocumentNodeElement.prototype, {
             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', ''),
@@ -210,12 +226,12 @@ $.extend(DocumentNodeElement.prototype, {
         }
         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() {
@@ -249,8 +265,8 @@ $.extend(DocumentNodeElement.prototype, {
 
 
 // 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, {
@@ -297,7 +313,7 @@ $.extend(DocumentTextElement.prototype, {
         // 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;
         }
@@ -305,7 +321,7 @@ $.extend(DocumentTextElement.prototype, {
         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>');
index fd0dfc4..44b340a 100644 (file)
@@ -21,7 +21,8 @@ $.extend(generic, {
 
         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);
index 620e652..3efc450 100644 (file)
@@ -20,26 +20,38 @@ var nearestInDocumentOrder = function(selector, direction, element) {
     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) {
index b1a9582..ec626ac 100644 (file)
@@ -35,14 +35,19 @@ $.extend(Listener.prototype, {
 
 
 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) {
@@ -51,11 +56,18 @@ var handlers = {
                 } 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) {
@@ -63,19 +75,30 @@ var handlers = {
             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,
index b79d109..4db4bc9 100644 (file)
@@ -50,6 +50,11 @@ var createAction = function(actionConfig) {
                         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}));
@@ -69,6 +74,7 @@ var createAction = function(actionConfig) {
 };
 
 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')}),
diff --git a/src/editor/plugins/core/edumed/assign/assign.less b/src/editor/plugins/core/edumed/assign/assign.less
new file mode 100644 (file)
index 0000000..caeadd4
--- /dev/null
@@ -0,0 +1,21 @@
+.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
diff --git a/src/editor/plugins/core/edumed/assign/destinationItem.html b/src/editor/plugins/core/edumed/assign/destinationItem.html
new file mode 100644 (file)
index 0000000..42f5a50
--- /dev/null
@@ -0,0 +1,4 @@
+<div class="destination-item">
+    <div class="content"></div>
+    <div class="assignments"></div>
+</div>
\ No newline at end of file
diff --git a/src/editor/plugins/core/edumed/assign/element.js b/src/editor/plugins/core/edumed/assign/element.js
new file mode 100644 (file)
index 0000000..cec77db
--- /dev/null
@@ -0,0 +1,77 @@
+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};
+
+});
diff --git a/src/editor/plugins/core/edumed/assign/sourceItem.html b/src/editor/plugins/core/edumed/assign/sourceItem.html
new file mode 100644 (file)
index 0000000..3ba53f5
--- /dev/null
@@ -0,0 +1 @@
+<div class="source-item"></div>
\ No newline at end of file
diff --git a/src/editor/plugins/core/edumed/assign/view.html b/src/editor/plugins/core/edumed/assign/view.html
new file mode 100644 (file)
index 0000000..6228d5c
--- /dev/null
@@ -0,0 +1,12 @@
+<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
diff --git a/src/editor/plugins/core/edumed/assign/view.js b/src/editor/plugins/core/edumed/assign/view.js
new file mode 100644 (file)
index 0000000..bfd2a0f
--- /dev/null
@@ -0,0 +1,135 @@
+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;
+
+});
index 6ba5996..dd5b013 100644 (file)
@@ -5,6 +5,7 @@ define(function(require) {
 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'),
@@ -12,7 +13,12 @@ var actions = require('./actions'),
 
 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
index fc79e39..9d0928f 100644 (file)
@@ -1,3 +1,4 @@
+@import 'assign/assign.less';
 @import 'order/order.less';
 @import 'gaps/gaps.less';
 @import 'replace/replace.less';