wip: wlxml layer
[fnpeditor.git] / src / wlxml / extensions / edumed / edumed.js
1 define(function(require) {
2     
3 'use strict';
4
5 var _ = require('libs/underscore'),
6     templates = {
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')
14     };
15
16 var Item = function(node, exerciseNode) {
17     Object.defineProperty(this, 'text', {
18         get: function() {
19             /* globals Node */
20             var firstNode = node.contents()[0];
21             if(firstNode && firstNode.nodeType === Node.TEXT_NODE) {
22                 return firstNode.getText();
23             }
24             return '';
25         }
26     });
27     this.node = node;
28     this.exerciseNode = exerciseNode;
29 };
30 _.extend(Item.prototype, {
31     setText: function(text) {
32         /* globals Node */
33         var contents = this.node.contents();
34         if(contents.length === 1 && contents[0].nodeType === Node.TEXT_NODE) {
35             contents[0].setText(text);
36         } else {
37             contents.forEach(function(childNode) {
38                 childNode.detach();
39             });
40             contents.append({text: text});
41         }
42     },
43     remove: function() {
44         this.node.detach();
45     },
46     getAnswer: function() {
47         var toret = parseInt(this.node.getAttr('answer'), 10);
48         if(_.isNaN(toret)) {
49             toret = 1;
50         }
51         return toret;
52     },
53     setAnswer: function(answer) {
54         answer = parseInt(answer, 10);
55         var prev = answer;
56         if(!_.isNumber(answer)) {
57             return;
58         }
59         
60         this.exerciseNode.object.getItems()
61             .sort(function(item1, item2) {
62                 if(item1.getAnswer() > item2.getAnswer()) {
63                     return 1;
64                 }
65                 return -1;
66             })
67             .some(function(item) {
68                 if(item.getAnswer() === prev && !item.node.sameNode(this.node)) {
69                     item.node.setAttr('answer', prev+1);
70                     prev = prev + 1;
71                 }
72             }.bind(this));
73         this.node.setAttr('answer', answer);
74         
75     }
76 });
77
78 var isItemsList = function(node) {
79     return node.is('list.orderable');
80 };
81
82
83 var extension = {wlxmlClass: {'exercise.order': {
84     methods: {
85         isContextRoot: function(node) {
86             return this.object.isItemNode(node) || this.sameNode(node);
87         },
88         getItems: function() {
89             var toret = [],
90                 exerciseNode = this;
91
92             this.contents().some(function(node) {
93                 if(isItemsList(node)) {
94                     node.contents()
95                         .filter(function(node) {
96                             return node.is('item.answer');
97                         })
98                         .forEach(function(node) {
99                             toret.push(new Item(node, exerciseNode));
100                         });
101                     return true;
102                 }
103             });
104             return toret;
105         },
106         isItemNode: function(node) {
107             var list;
108             if(!node) {
109                 return;
110             }
111             this.contents().some(function(node) {
112                 if(isItemsList(node)) {
113                     list = node;
114                     return true;
115                 }
116             });
117             return list && list.sameNode(node.parent());
118         },
119         getDescription: function() {
120             var toret = [];
121             this.contents().some(function(node) {
122                 if(isItemsList(node)) {
123                    return true;
124                 }
125                 toret.push(node);
126             });
127             return toret;
128         }
129     },
130     transformations: {
131         addItem: function(text) {
132             var toret;
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);
138                     return true;
139                 }
140             }.bind(this));
141             return toret;
142         },
143         setDescription: function(text) {
144             this.contents().some(function(node) {
145                 var textNode;
146                 if(node.is('p')) {
147                     textNode = node.contents()[0];
148                     if(!(textNode && textNode.nodeType === Node.TEXT_NODE)) {
149                         node.prepend({text:text});
150                     } else {
151                         textNode.setText(text);
152                     }
153                     return true;
154                 }
155             });
156         }
157     }
158 }}};
159
160 var choiceMethods = {
161     isContextRoot: function(node) {
162         return this.object.isChoiceList(node.parent()) || this.sameNode(node);
163     },
164     getChoiceList: function() {
165         return this.contents()
166             .filter(function(n) { return this.object.isChoiceList(n); }.bind(this))[0];
167     },
168     isChoiceList: function(node) {
169         return node.is('list') && this.sameNode(node.parent());
170     },
171     isChoiceListItem: function(node) {
172         return this.object.isChoiceList(node.parent()) && node.is('item.answer');
173     }
174 };
175
176 extension.wlxmlClass['exercise.choice'] = {
177     transformations: {
178         setAnswer: function(itemNode, answer) {
179             if(!this.object.isChoiceListItem(itemNode)) {
180                 return;
181             }
182             itemNode.setAttr('answer', answer ? 'true' : 'false');
183         }
184     },
185     methods: choiceMethods
186 };
187
188 extension.wlxmlClass['exercise.choice.single'] = {
189     transformations: {
190         markAsAnswer: function(itemNode) {
191             if(!this.object.isChoiceListItem(itemNode)) {
192                 return;
193             }
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');
198                 });
199         }
200     },
201     methods: choiceMethods
202 };
203
204 extension.wlxmlClass['exercise.choice.true-or-false'] = {
205     transformations: {
206         setAnswer: function(itemNode, answer) {
207             if(!this.object.isChoiceListItem(itemNode)) {
208                 return;
209             }
210             itemNode.setAttr('answer', answer ? 'true' : 'false');
211         }
212     },
213     methods: choiceMethods
214 };
215
216 extension.wlxmlClass['exercise.gap'] = extension.wlxmlClass['exercise.replace'] = {
217     methods: {
218         isContextRoot: function(node) {
219             return this.sameNode(node);
220         }
221     }
222 };
223
224
225 var SourceItem = function(node) {
226     this.node = node;
227 };
228 _.extend(SourceItem.prototype, {
229     assignTo: function(destinationItem) {
230         var ids;
231         if(!destinationItem.accepts(this)) {
232             throw new Error('Cannot assign: target ids mismatch.');
233         }
234         ids = (this.node.getAttr('answer') || '').split(',');
235         ids.push(destinationItem.getId());
236         this.node.setAttr('answer', ids.filter(function(id) { return !!id; }).join(','));
237     },
238     removeFrom: function(destinationItem) {
239         var ids;
240         if(!destinationItem.accepts(this)) {
241             throw new Error('Cannot assign: target ids mismatch.');
242         }
243         ids = (this.node.getAttr('answer') || '').split(',');
244         this.node.setAttr('answer', ids.filter(function(id) { return id !== destinationItem.getId(); }).join(',') || undefined);
245     },
246     isAssignedTo: function(destinationItem) {
247         return (this.node.getAttr('answer') || '').indexOf(destinationItem.getId()) !== -1;
248     },
249     getMyTargetId: function() {
250         return this.node.parent().getAttr('target');
251     },
252     contents: function() {
253         return this.node.contents();
254     }
255 });
256
257 var DestinationItem = function(node, exerciseNode) {
258     this.node = node;
259     this.exerciseNode = exerciseNode;
260 };
261 _.extend(DestinationItem.prototype, {
262     getId: function() {
263         return this.node.getAttr('id');
264     },
265     getTargetId: function() {
266         return this.node.parent().getAttr('id');
267     },
268     accepts: function(sourceItem) {
269         return sourceItem && sourceItem.getMyTargetId() === this.getTargetId();
270     },
271     getAssignedSources: function() {
272         return this.exerciseNode.object.getSourceItems()
273             .filter(function(item) {
274                 return item.isAssignedTo(this);
275             }.bind(this));
276     }
277 });
278
279 extension.wlxmlClass['exercise.assign'] = {
280     methods: {
281         isContextRoot: function(node) {
282             return this.object.isList(node.parent()) || this.sameNode(node);
283         },
284         getSourceItems: function() {
285             var list;
286             this.contents().some(function(node) {
287                 if(node.is('list') && node.getAttr('target')) {
288                     list = node;
289                     return true;
290                 }
291             });
292             if(!list) {
293                 throw new Error('Missing source list');
294             }
295             return list.contents().map(function(node) {return new SourceItem(node, this);});
296         },
297         getDestinationItems: function() {
298             var list;
299             this.contents().some(function(node) {
300                 if(node.is('list') && node.getAttr('id')) {
301                     list = node;
302                     return true;
303                 }
304             });
305             if(!list) {
306                 throw new Error('Missing destination list');
307             }
308             return list.contents().map(function(node) {return new DestinationItem(node, this);}.bind(this));
309         },
310         getDescription: function() {
311             var toret = [];
312             this.contents().some(function(node) {
313                 if(this.isList(node)) {
314                    return true;
315                 }
316                 toret.push(node);
317             }.bind(this.object));
318             return toret;
319         },
320         isList: function(node) {
321             return this.sameNode(node.parent()) && node.is('list') && (node.getAttr('target') || node.getAttr('id'));
322         },
323         isItemNode: function(node, parent) {
324             return node && this.object.isList(node.parent() || parent);
325         }
326     }
327 };
328
329 extension.document = {
330     methods: {
331          edumedCreateExerciseNode: function(klass) {
332             void(klass);
333             return this.createDocumentNode(templates[klass]);
334          }
335     }
336 };
337
338 return extension;
339
340 });