list fixes: headers, invalid elements, easier ending
[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         match: require('libs/text!./match.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'] = extension.wlxmlClass['exercise.match'] = {
217     methods: {
218         isContextRoot: function(node) {
219             return this.sameNode(node);
220         }
221     }
222 };
223
224 extension.document = {
225     methods: {
226          edumedCreateExerciseNode: function(klass) {
227             void(klass);
228             return this.createDocumentNode(templates[klass]);
229          }
230     }
231 };
232
233 return extension;
234
235 });