Merge in first approach to edumed exercises
[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     };
14
15 var Item = function(node, exerciseNode) {
16     Object.defineProperty(this, 'text', {
17         get: function() {
18             /* globals Node */
19             var firstNode = node.contents()[0];
20             if(firstNode && firstNode.nodeType === Node.TEXT_NODE) {
21                 return firstNode.getText();
22             }
23             return '';
24         }
25     });
26     this.node = node;
27     this.exerciseNode = exerciseNode;
28 };
29 _.extend(Item.prototype, {
30     setText: function(text) {
31         /* globals Node */
32         var contents = this.node.contents();
33         if(contents.length === 1 && contents[0].nodeType === Node.TEXT_NODE) {
34             contents[0].setText(text);
35         } else {
36             contents.forEach(function(childNode) {
37                 childNode.detach();
38             });
39             contents.append({text: text});
40         }
41     },
42     remove: function() {
43         this.node.detach();
44     },
45     getAnswer: function() {
46         var toret = parseInt(this.node.getAttr('answer'), 10);
47         if(_.isNaN(toret)) {
48             toret = 1;
49         }
50         return toret;
51     },
52     setAnswer: function(answer) {
53         answer = parseInt(answer, 10);
54         var prev = answer;
55         if(!_.isNumber(answer)) {
56             return;
57         }
58         
59         this.exerciseNode.object.getItems()
60             .sort(function(item1, item2) {
61                 if(item1.getAnswer() > item2.getAnswer()) {
62                     return 1;
63                 }
64                 return -1;
65             })
66             .some(function(item) {
67                 if(item.getAnswer() === prev && !item.node.sameNode(this.node)) {
68                     item.node.setAttr('answer', prev+1);
69                     prev = prev + 1;
70                 }
71             }.bind(this));
72         this.node.setAttr('answer', answer);
73         
74     }
75 });
76
77 var isItemsList = function(node) {
78     return node.is('list.orderable');
79 };
80
81
82 var extension = {wlxmlClass: {'exercise.order': {
83     methods: {
84         isContextRoot: function(node) {
85             return this.object.isItemNode(node) || this.sameNode(node);
86         },
87         getItems: function() {
88             var toret = [],
89                 exerciseNode = this;
90
91             this.contents().some(function(node) {
92                 if(isItemsList(node)) {
93                     node.contents()
94                         .filter(function(node) {
95                             return node.is('item.answer');
96                         })
97                         .forEach(function(node) {
98                             toret.push(new Item(node, exerciseNode));
99                         });
100                     return true;
101                 }
102             });
103             return toret;
104         },
105         isItemNode: function(node) {
106             var list;
107             if(!node) {
108                 return;
109             }
110             this.contents().some(function(node) {
111                 if(isItemsList(node)) {
112                     list = node;
113                     return true;
114                 }
115             });
116             return list && list.sameNode(node.parent());
117         },
118         getDescription: function() {
119             var toret = [];
120             this.contents().some(function(node) {
121                 if(isItemsList(node)) {
122                    return true;
123                 }
124                 toret.push(node);
125             });
126             return toret;
127         }
128     },
129     transformations: {
130         addItem: function(text) {
131             var toret;
132             this.contents().some(function(node) {
133                 if(isItemsList(node)) {
134                     var itemNode = this.document.createDocumentNode({tagName: 'div', attrs: {'class': 'item.answer', answer: this.object.getItems().length+1}});
135                     toret = itemNode.append({text: text});
136                     node.append(itemNode);
137                     return true;
138                 }
139             }.bind(this));
140             return toret;
141         },
142         setDescription: function(text) {
143             this.contents().some(function(node) {
144                 var textNode;
145                 if(node.is('p')) {
146                     textNode = node.contents()[0];
147                     if(!(textNode && textNode.nodeType === Node.TEXT_NODE)) {
148                         node.prepend({text:text});
149                     } else {
150                         textNode.setText(text);
151                     }
152                     return true;
153                 }
154             });
155         }
156     }
157 }}};
158
159 var choiceMethods = {
160     isContextRoot: function(node) {
161         return this.object.isChoiceList(node.parent()) || this.sameNode(node);
162     },
163     getChoiceList: function() {
164         return this.contents()
165             .filter(function(n) { return this.object.isChoiceList(n); }.bind(this))[0];
166     },
167     isChoiceList: function(node) {
168         return node.is('list') && this.sameNode(node.parent());
169     },
170     isChoiceListItem: function(node) {
171         return this.object.isChoiceList(node.parent()) && node.is('item.answer');
172     }
173 };
174
175 extension.wlxmlClass['exercise.choice'] = {
176     transformations: {
177         setAnswer: function(itemNode, answer) {
178             if(!this.object.isChoiceListItem(itemNode)) {
179                 return;
180             }
181             itemNode.setAttr('answer', answer ? 'true' : 'false');
182         }
183     },
184     methods: choiceMethods
185 };
186
187 extension.wlxmlClass['exercise.choice.single'] = {
188     transformations: {
189         markAsAnswer: function(itemNode) {
190             if(!this.object.isChoiceListItem(itemNode)) {
191                 return;
192             }
193             this.object.getChoiceList().contents()
194                 .filter(function(node) { return node.is('item.answer'); })
195                 .forEach(function(node) {
196                     node.setAttr('answer', node.sameNode(itemNode) ? 'true' : 'false');
197                 });
198         }
199     },
200     methods: choiceMethods
201 };
202
203 extension.wlxmlClass['exercise.choice.true-or-false'] = {
204     transformations: {
205         setAnswer: function(itemNode, answer) {
206             if(!this.object.isChoiceListItem(itemNode)) {
207                 return;
208             }
209             itemNode.setAttr('answer', answer ? 'true' : 'false');
210         }
211     },
212     methods: choiceMethods
213 };
214
215 extension.wlxmlClass['exercise.gap'] = extension.wlxmlClass['exercise.replace'] = {
216     methods: {
217         isContextRoot: function(node) {
218             return this.sameNode(node);
219         }
220     }
221 };
222
223 extension.document = {
224     methods: {
225          edumedCreateExerciseNode: function(klass) {
226             void(klass);
227             return this.createDocumentNode(templates[klass]);
228          }
229     }
230 };
231
232 return extension;
233
234 });