$.extend(Canvas.prototype, Backbone.Events, {
+ createElementType: function(elementPrototype) {
+ /* TODO: reconcile this with ElementsRegister behavior */
+ var Constructor = function() {
+ documentElement.DocumentNodeElement.apply(this, Array.prototype.slice.call(arguments, 0));
+ };
+ Constructor.prototype = elementPrototype;
+ return Constructor;
+ },
+
getElementOffset: function(element) {
return element.dom.offset().top - this.dom.offset().top;
},
--- /dev/null
+@import 'choiceTrueOrFalse.less';
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ documentElement = require('modules/documentCanvas/canvas/documentElement'),
+ template = require('libs/text!./element.html');
+
+
+var choiceBase = Object.create(documentElement.DocumentNodeElement.prototype);
+_.extend(choiceBase, {
+ init: function() {
+ documentElement.DocumentNodeElement.prototype.init.call(this);
+ this.view = $(_.template(template)({type: this.type}));
+ this._container().append(this.view);
+
+ this.createContainer(this.wlxmlNode.contents().filter(function(n) {return !n.is('list');}), {
+ manages: function(node, removedFrom) {
+ if(node.is('list.orderable')) {
+ return false;
+ }
+ return this.wlxmlNode.sameNode(node.parent() || removedFrom);
+ }.bind(this),
+ dom: this.view.find('.description')
+ });
+
+ this.wlxmlNode.contents()
+ .filter(function(node) { return node.is('list'); })
+ .some(function(node) {
+ this.listView = this.createListView(node);
+ this.view.find('.list').append(this.listView.dom);
+ }.bind(this));
+ },
+ getVerticallyFirstTextElement: function() {
+ var toret;
+ this.containers.some(function(container) {
+ toret = container.getVerticallyFirstTextElement();
+ return !!toret;
+ });
+ return toret;
+ },
+ onNodeAdded: function(event) {
+ var node = event.meta.node;
+ if(this.listView.listNode.sameNode(node.parent()) && node.is('item.answer')) {
+ this.listView.addItem(node);
+ }
+ },
+ onNodeDetached: function(event) {
+ var node = event.meta.node;
+ if(this.listView.listNode.sameNode(event.meta.parent) && node.is('item.answer')) {
+ this.listView.removeItem(node);
+ }
+ }
+});
+
+return choiceBase;
+
+});
+
+
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+/* globals gettext */
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ choiceBase = require('./choiceBase'),
+ ListView = require('./list');
+
+
+var choiceMulti = Object.create(choiceBase);
+_.extend(choiceMulti, {
+ type: 'multi',
+ createListView: function(listNode) {
+ return new ListView(this, listNode, {
+ onItemViewAdded: function(itemView) {
+ var checkbox = new CheckboxView(itemView.node.getAttr('answer') === 'true', function(checked) {
+ itemView.node.document.transaction(function() {
+ itemView.node.getParent('exercise.choice').object.setAnswer(itemView.node, checked);
+ }, {
+ metadata: {
+ description: gettext('Change answer')
+ }
+ });
+ });
+ itemView.addPrefixView(checkbox);
+ }
+ });
+ },
+ onNodeAttrChange: function(event) {
+ if(this.listView.listNode.sameNode(event.meta.node.parent())) {
+ this.listView.getItemView(event.meta.node).prefixView.dom.attr('checked', event.meta.newVal === 'true');
+ }
+ }
+});
+
+var CheckboxView = function(checked, onValueChange) {
+ this.dom = $('<input type="checkbox">')
+ .attr('checked', checked);
+ this.dom.on('click', function() {
+ onValueChange(this.checked);
+ });
+};
+
+return choiceMulti;
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+/* globals gettext */
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ choiceBase = require('./choiceBase'),
+ ListView = require('./list');
+
+
+var choiceSingle = Object.create(choiceBase);
+_.extend(choiceSingle, {
+ type: 'single',
+ init: function() {
+ this._comboName = _.uniqueId('edumed_exercise_hash_');
+ choiceBase.init.call(this);
+ },
+ createListView: function(listNode) {
+ var el = this;
+ return new ListView(this, listNode, {
+ onItemViewAdded: function(itemView) {
+ var radiobox = new RadioView(itemView.node.getAttr('answer') === 'true', el._comboName, function() {
+ itemView.node.document.transaction(function() {
+ itemView.node.getParent('exercise.choice').object.markAsAnswer(itemView.node);
+ }, {
+ metadata: {
+ description: gettext('Change answer')
+ }
+ });
+
+ });
+ itemView.addPrefixView(radiobox);
+ }
+ });
+ },
+ onNodeAttrChange: function(event) {
+ if(this.listView.listNode.sameNode(event.meta.node.parent())) {
+ this.listView.getItemView(event.meta.node).prefixView.dom.attr('checked', event.meta.newVal === 'true');
+ }
+ }
+});
+
+var RadioView = function(checked, name, onValueChange) {
+ this.dom = $('<input type="radio" style="float: left; margin-right: 40px;">')
+ .attr('checked', checked)
+ .attr('name', name);
+ this.dom.on('change', function() {
+ onValueChange(this.checked);
+ });
+};
+
+return choiceSingle;
+
+});
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+/* globals gettext */
+
+var $ = require('libs/jquery'),
+ _ = require('libs/underscore'),
+ choiceBase = require('./choiceBase'),
+ switchTemplate = require('libs/text!./trueOrFalseSwitch.html'),
+ ListView = require('./list');
+
+
+var trueOrFalse = Object.create(choiceBase);
+_.extend(trueOrFalse, {
+ type: 'true-or-false',
+ createListView: function(listNode) {
+ return new ListView(this, listNode, {
+ onItemViewAdded: function(itemView) {
+ var switchView = new Switch(itemView.node.getAttr('answer') === 'true', function(choice) {
+ itemView.node.document.transaction(function() {
+ itemView.node.getParent('exercise.choice').object.setAnswer(itemView.node, choice);
+ }, {
+ metadata: {
+ description: gettext('Change answer')
+ }
+ });
+ });
+ itemView.addPrefixView(switchView);
+ }
+ });
+ }
+});
+
+var Switch = function(checked, onValueChange) {
+ this.dom = $(_.template(switchTemplate)());
+ var trueBtn = this.dom.find('.true'),
+ falseBtn = this.dom.find('.false');
+
+ trueBtn.on('click', function() {
+ trueBtn.addClass('selected');
+ falseBtn.removeClass('selected');
+ onValueChange(true);
+ });
+ this.dom.find('.false').on('click', function() {
+ falseBtn.addClass('selected');
+ trueBtn.removeClass('selected');
+ onValueChange(false);
+ });
+ trueBtn.toggleClass('selected', checked);
+ falseBtn.toggleClass('selected', !checked);
+};
+
+return trueOrFalse;
+
+});
\ No newline at end of file
--- /dev/null
+.exercise-true-or-false {
+ ul {
+ list-style: none;
+ }
+ .switch {
+ font-variant: small-caps;
+ float: left;
+ margin-right: 15px;
+ background-color: #d4d6d8;
+ cursor: pointer;
+ border-radius: 5px;
+ padding: 0px 5px;
+ font-weight: bold;
+ .selected {
+ color: white;
+ background-color: #ed7831;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<div class="edumed-exercise exercise-<%= type %>">
+ <div class="header"><%= gettext('Exercise') %>: <%= type %></div>
+ <div class="description"></div>
+ <div class="list"></div>
+</div>
\ No newline at end of file
--- /dev/null
+define(function(require) {
+
+'use strict';
+
+var choiceSingle = require('./choiceSingle'),
+ choiceMulti = require('./choiceMulti'),
+ choiceTrueOrFalse = require('./choiceTrueOrFalse');
+
+return [
+ {tag: 'div', klass: 'exercise.choice', prototype: choiceMulti},
+ {tag: 'div', klass: 'exercise.choice.single', prototype: choiceSingle},
+ {tag: 'div', klass: 'exercise.choice.true-or-false', prototype: choiceTrueOrFalse},
+];
+
+});
--- /dev/null
+<div class="exercise-list">
+ <ul></ul>
+</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!./list.html'),
+ viewItemTemplate = require('libs/text!./listItem.html');
+
+
+var ListView = function(element, listNode, params) {
+ this.element = element;
+ this.listNode = listNode;
+ this.params = params;
+ this.dom = $(_.template(viewTemplate)());
+ this.list = this.dom.find('ul');
+ this.addButton = this.dom.find('button.add');
+ this.itemViews = [];
+
+ this.listNode.contents()
+ .filter(function(node) {
+ return node.is('item.answer');
+ })
+ .forEach(function(node) {
+ this.addItem(node);
+ }.bind(this));
+};
+_.extend(ListView.prototype, Backbone.Events, {
+ addItem: function(node) {
+ var view = new ItemView(node, this);
+ var idx = this.listNode.contents()
+ .filter(function(n) { return n.is('item'); })
+ .indexOf(node);
+
+ if(idx <= this.itemViews.length - 1) {
+ this.itemViews.splice(idx, 0, view);
+ } else {
+ this.itemViews.push(view);
+ }
+ if(idx > 0) {
+ this.itemViews[idx-1].dom.after(view.dom);
+ } else {
+ this.list.prepend(view.dom);
+ }
+ if(this.params.onItemViewAdded) {
+ this.params.onItemViewAdded(view);
+ }
+ },
+ removeItem: function(node) {
+ this.itemViews.some(function(view, idx) {
+ if(view.node.sameNode(node)) {
+ view.remove();
+ this.itemViews.splice(idx, 1);
+ return true;
+ }
+ }.bind(this));
+ },
+ getItemView: function(node) {
+ var toret;
+ this.itemViews.some(function(view) {
+ if(view.node.sameNode(node)) {
+ toret = view;
+ return true;
+ }
+ }.bind(this));
+ return toret;
+ }
+});
+
+var ItemView = function(node, exerciseView) {
+ this.node = node;
+ this.exerciseView = exerciseView;
+ this.dom = $(_.template(viewItemTemplate)());
+
+ this.container = exerciseView.element.createContainer(node.contents(), {
+ manages: function(node, originaParent) {
+ return this.node.sameNode(node.parent() || originaParent);
+ }.bind(this),
+ dom: this.dom.find('.content')
+ });
+};
+
+_.extend(ItemView.prototype, Backbone.Events, {
+ remove: function() {
+ this.container.remove();
+ this.dom.remove();
+ },
+ addPrefixView: function(view) {
+ this.dom.find('.prefix').append(view.dom);
+ this.prefixView = view;
+ }
+});
+
+return ListView;
+
+});
--- /dev/null
+<li><span class="prefix"></span><span class="content"></span></li>
--- /dev/null
+<div class="switch">
+ <span class="true"><%= gettext('true') %></span><span class="false"><%= gettext('false') %></span>
+</div>
\ No newline at end of file
replaceActions = require('./replace/actions'),
orderExerciseElement = require('./order/element'),
gapsExerciseElement = require('./gaps/element'),
- replaceExerciseElement = require('./replace/element');
+ replaceExerciseElement = require('./replace/element'),
+ choiceExerciseElements = require('./choice/element');
return {
actions: actions.concat(gapsActions).concat(replaceActions),
- canvasElements: [orderExerciseElement, gapsExerciseElement, replaceExerciseElement]
+ canvasElements: [orderExerciseElement, gapsExerciseElement, replaceExerciseElement].concat(choiceExerciseElements)
};
});
\ No newline at end of file
@import 'order/order.less';
@import 'gaps/gaps.less';
@import 'replace/replace.less';
+@import 'choice/choice.less';
.edumed-exercise {
}
}}};
+var choiceMethods = {
+ getChoiceList: function() {
+ return this.contents()
+ .filter(function(n) { return this.object.isChoiceList(n); }.bind(this))[0];
+ },
+ isChoiceList: function(node) {
+ return node.is('list') && this.sameNode(node.parent());
+ },
+ isChoiceListItem: function(node) {
+ return this.object.isChoiceList(node.parent()) && node.is('item.answer');
+ }
+};
+
+extension.wlxmlClass['exercise.choice'] = {
+ transformations: {
+ setAnswer: function(itemNode, answer) {
+ if(!this.object.isChoiceListItem(itemNode)) {
+ return;
+ }
+ itemNode.setAttr('answer', answer ? 'true' : 'false');
+ }
+ },
+ methods: choiceMethods
+};
+
+extension.wlxmlClass['exercise.choice.single'] = {
+ transformations: {
+ markAsAnswer: function(itemNode) {
+ if(!this.object.isChoiceListItem(itemNode)) {
+ return;
+ }
+ this.object.getChoiceList().contents()
+ .filter(function(node) { return node.is('item.answer'); })
+ .forEach(function(node) {
+ node.setAttr('answer', node.sameNode(itemNode) ? 'true' : 'false');
+ });
+ }
+ },
+ methods: choiceMethods
+};
+
+extension.wlxmlClass['exercise.choice.true-or-false'] = {
+ transformations: {
+ setAnswer: function(itemNode, answer) {
+ if(!this.object.isChoiceListItem(itemNode)) {
+ return;
+ }
+ itemNode.setAttr('answer', answer ? 'true' : 'false');
+ }
+ },
+ methods: choiceMethods
+};
+
extension.document = {
methods: {
edumedCreateExerciseNode: function(klass) {