1 define(function(require) {
5 var _ = require('libs/underscore'),
7 order: require('libs/text!./order.xml'),
8 choice: require('libs/text!./choice.xml'),
9 'choice.single': require('libs/text!./choiceSingle.xml'),
10 'choice.true-or-false': require('libs/text!./choiceTrueOrFalse.xml'),
11 gap: require('libs/text!./gaps.xml'),
12 replace: require('libs/text!./replace.xml'),
13 assign: require('libs/text!./assign.xml')
16 var Item = function(node, exerciseNode) {
17 Object.defineProperty(this, 'text', {
20 var firstNode = node.contents()[0];
21 if(firstNode && firstNode.nodeType === Node.TEXT_NODE) {
22 return firstNode.getText();
28 this.exerciseNode = exerciseNode;
30 _.extend(Item.prototype, {
31 setText: function(text) {
33 var contents = this.node.contents();
34 if(contents.length === 1 && contents[0].nodeType === Node.TEXT_NODE) {
35 contents[0].setText(text);
37 contents.forEach(function(childNode) {
40 contents.append({text: text});
46 getAnswer: function() {
47 var toret = parseInt(this.node.getAttr('answer'), 10);
53 setAnswer: function(answer) {
54 answer = parseInt(answer, 10);
56 if(!_.isNumber(answer)) {
60 this.exerciseNode.object.getItems()
61 .sort(function(item1, item2) {
62 if(item1.getAnswer() > item2.getAnswer()) {
67 .some(function(item) {
68 if(item.getAnswer() === prev && !item.node.sameNode(this.node)) {
69 item.node.setAttr('answer', prev+1);
73 this.node.setAttr('answer', answer);
78 var isItemsList = function(node) {
79 return node.is('list.orderable');
83 var extension = {wlxmlClass: {'exercise.order': {
85 isContextRoot: function(node) {
86 return this.object.isItemNode(node) || this.sameNode(node);
88 getItems: function() {
92 this.contents().some(function(node) {
93 if(isItemsList(node)) {
95 .filter(function(node) {
96 return node.is('item.answer');
98 .forEach(function(node) {
99 toret.push(new Item(node, exerciseNode));
106 isItemNode: function(node) {
111 this.contents().some(function(node) {
112 if(isItemsList(node)) {
117 return list && list.sameNode(node.parent());
119 getDescription: function() {
121 this.contents().some(function(node) {
122 if(isItemsList(node)) {
131 addItem: function(text) {
133 this.contents().some(function(node) {
134 if(isItemsList(node)) {
135 var itemNode = this.document.createDocumentNode({tagName: 'div', attrs: {'class': 'item.answer', answer: this.object.getItems().length+1}});
136 toret = itemNode.append({text: text});
137 node.append(itemNode);
143 setDescription: function(text) {
144 this.contents().some(function(node) {
147 textNode = node.contents()[0];
148 if(!(textNode && textNode.nodeType === Node.TEXT_NODE)) {
149 node.prepend({text:text});
151 textNode.setText(text);
160 var choiceMethods = {
161 isContextRoot: function(node) {
162 return this.object.isChoiceList(node.parent()) || this.sameNode(node);
164 getChoiceList: function() {
165 return this.contents()
166 .filter(function(n) { return this.object.isChoiceList(n); }.bind(this))[0];
168 isChoiceList: function(node) {
169 return node.is('list') && this.sameNode(node.parent());
171 isChoiceListItem: function(node) {
172 return this.object.isChoiceList(node.parent()) && node.is('item.answer');
176 extension.wlxmlClass['exercise.choice'] = {
178 setAnswer: function(itemNode, answer) {
179 if(!this.object.isChoiceListItem(itemNode)) {
182 itemNode.setAttr('answer', answer ? 'true' : 'false');
185 methods: choiceMethods
188 extension.wlxmlClass['exercise.choice.single'] = {
190 markAsAnswer: function(itemNode) {
191 if(!this.object.isChoiceListItem(itemNode)) {
194 this.object.getChoiceList().contents()
195 .filter(function(node) { return node.is('item.answer'); })
196 .forEach(function(node) {
197 node.setAttr('answer', node.sameNode(itemNode) ? 'true' : 'false');
201 methods: choiceMethods
204 extension.wlxmlClass['exercise.choice.true-or-false'] = {
206 setAnswer: function(itemNode, answer) {
207 if(!this.object.isChoiceListItem(itemNode)) {
210 itemNode.setAttr('answer', answer ? 'true' : 'false');
213 methods: choiceMethods
216 extension.wlxmlClass['exercise.gap'] = extension.wlxmlClass['exercise.replace'] = {
218 isContextRoot: function(node) {
219 return this.sameNode(node);
225 var SourceItem = function(node) {
228 _.extend(SourceItem.prototype, {
229 assignTo: function(destinationItem) {
231 if(!destinationItem.accepts(this)) {
232 throw new Error('Cannot assign: target ids mismatch.');
234 ids = (this.node.getAttr('answer') || '').split(',');
235 ids.push(destinationItem.getId());
236 this.node.setAttr('answer', ids.filter(function(id) { return !!id; }).join(','));
238 removeFrom: function(destinationItem) {
240 if(!destinationItem.accepts(this)) {
241 throw new Error('Cannot assign: target ids mismatch.');
243 ids = (this.node.getAttr('answer') || '').split(',');
244 this.node.setAttr('answer', ids.filter(function(id) { return id !== destinationItem.getId(); }).join(',') || undefined);
246 isAssignedTo: function(destinationItem) {
247 return (this.node.getAttr('answer') || '').indexOf(destinationItem.getId()) !== -1;
249 getMyTargetId: function() {
250 return this.node.parent().getAttr('target');
252 contents: function() {
253 return this.node.contents();
257 var DestinationItem = function(node, exerciseNode) {
259 this.exerciseNode = exerciseNode;
261 _.extend(DestinationItem.prototype, {
263 return this.node.getAttr('id');
265 getTargetId: function() {
266 return this.node.parent().getAttr('id');
268 accepts: function(sourceItem) {
269 return sourceItem && sourceItem.getMyTargetId() === this.getTargetId();
271 getAssignedSources: function() {
272 return this.exerciseNode.object.getSourceItems()
273 .filter(function(item) {
274 return item.isAssignedTo(this);
279 extension.wlxmlClass['exercise.assign'] = {
281 isContextRoot: function(node) {
282 return this.object.isList(node.parent()) || this.sameNode(node);
284 getSourceItems: function() {
286 this.contents().some(function(node) {
287 if(node.is('list') && node.getAttr('target')) {
293 throw new Error('Missing source list');
295 return list.contents().map(function(node) {return new SourceItem(node, this);});
297 getDestinationItems: function() {
299 this.contents().some(function(node) {
300 if(node.is('list') && node.getAttr('id')) {
306 throw new Error('Missing destination list');
308 return list.contents().map(function(node) {return new DestinationItem(node, this);}.bind(this));
310 getDescription: function() {
312 this.contents().some(function(node) {
313 if(this.isList(node)) {
317 }.bind(this.object));
320 isList: function(node) {
321 return this.sameNode(node.parent()) && node.is('list') && (node.getAttr('target') || node.getAttr('id'));
323 isItemNode: function(node, parent) {
324 return node && this.object.isList(node.parent() || parent);
329 extension.document = {
331 edumedCreateExerciseNode: function(klass) {
333 return this.createDocumentNode(templates[klass]);