smartxml: Automatically rollback transaction on error thrown
[fnpeditor.git] / src / editor / modules / data / data.js
1 define([
2     'libs/jquery',
3     'views/dialog/dialog',
4     'wlxml/wlxml',
5     'wlxml/extensions/list/list',
6     'fnpjs/logging/logging',
7     'fnpjs/datetime'
8 ], function($, Dialog, wlxml, listExtension, logging, datetime) {
9
10 'use strict';
11 /* global gettext, alert, window */
12
13 var logger = logging.getLogger('editor.modules.data'),
14     stubDocument = '<section><div>' + gettext('This is an empty document.') + '</div></section>';
15
16
17 return function(sandbox) {
18
19     var document_id = sandbox.getBootstrappedData().document_id;
20     var history = sandbox.getBootstrappedData().history;
21     var documentDirty = false;
22     var draftDirty = false;
23
24     var documentProperties = {};
25     var data = sandbox.getBootstrappedData();
26     Object.keys(data)
27         .filter(function(key) {
28             return key !== 'history' && key !== 'document';
29         })
30         .forEach(function(key) {
31             documentProperties[key] = data[key];
32         });
33
34     var wlxmlDocument, text;
35
36     var loadDocument = function(text, isDraft, draftTimestamp) {
37         logger.debug('loading document');
38         try {
39             wlxmlDocument = wlxml.WLXMLDocumentFromXML(text);
40         } catch(e) {
41             logger.exception(e);
42             alert(gettext('This document contains errors and can\'t be loaded. :(')); // TODO
43             wlxmlDocument = wlxml.WLXMLDocumentFromXML(stubDocument);
44         }
45
46         wlxmlDocument.registerExtension(listExtension);
47         sandbox.getPlugins().forEach(function(plugin) {
48             if(plugin.documentExtension) {
49                 wlxmlDocument.registerExtension(plugin.documentExtension);
50             }
51         });
52         
53         var modificationFlag = true;
54         var handleChange = function() {
55             documentDirty = true;
56             draftDirty = true;
57             modificationFlag = true;
58         };
59         wlxmlDocument.on('change', handleChange);
60         wlxmlDocument.on('contentSet', handleChange);
61
62         if(window.localStorage) {
63             window.setInterval(function() {
64                 if(modificationFlag) {
65                     modificationFlag = false;
66                     return;
67                 }
68                 if(wlxmlDocument && documentDirty && draftDirty) {
69                     var timestamp = datetime.currentStrfmt();
70                     logger.debug('Saving draft to local storage.');
71                     sandbox.publish('savingStarted', 'local');
72                     window.localStorage.setItem(getLocalStorageKey().content, wlxmlDocument.toXML());
73                     window.localStorage.setItem(getLocalStorageKey().contentTimestamp, timestamp);
74                     sandbox.publish('savingEnded', 'success', 'local', {timestamp: timestamp});
75                     draftDirty = false;
76                 }
77             }, sandbox.getConfig().autoSaveInterval || 2500);
78         }
79         sandbox.publish('ready', isDraft, draftTimestamp);
80     };
81     
82     function readCookie(name) {
83         /* global escape, unescape, document */
84         var nameEQ = escape(name) + '=';
85         var ca = document.cookie.split(';');
86         for (var i = 0; i < ca.length; i++) {
87             var c = ca[i];
88             while (c.charAt(0) === ' ') {
89                 c = c.substring(1, c.length);
90             }
91             if (c.indexOf(nameEQ) === 0) {
92                 return unescape(c.substring(nameEQ.length, c.length));
93             }
94         }
95         return null;
96     }
97     
98     $.ajaxSetup({
99         crossDomain: false,
100         beforeSend: function(xhr, settings) {
101             if (!(/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type))) {
102                 xhr.setRequestHeader('X-CSRFToken', readCookie('csrftoken'));
103             }
104         }
105     });
106     
107     var reloadHistory = function() {
108         $.ajax({
109             method: 'get',
110             url: sandbox.getConfig().documentHistoryUrl(document_id),
111             success: function(data) {
112                 history = data;
113                 sandbox.publish('historyItemAdded', data.slice(-1)[0]);
114             },
115         });
116     };
117
118     var getLocalStorageKey = function() {
119         var base = 'draft-id:' + document_id + '-ver:' + documentProperties.version;
120         return {
121             content: base,
122             contentTimestamp: base + '-content-timestamp'
123         };
124     };
125
126    
127     return {
128         start: function() {
129             if(window.localStorage) {
130                 text = window.localStorage.getItem(getLocalStorageKey().content);
131
132                 var timestamp = window.localStorage.getItem(getLocalStorageKey().contentTimestamp),
133                     usingDraft;
134                 if(text) {
135                     logger.debug('Local draft exists');
136                     var dialog = Dialog.create({
137                         title: gettext('Local draft of a document exists'),
138                         text: gettext('Unsaved local draft of this version of the document exists in your browser. Do you want to load it instead?'),
139                         executeButtonText: gettext('Yes, restore local draft'),
140                         cancelButtonText: gettext('No, use version loaded from the server')
141                     });
142                     dialog.on('cancel', function() {
143                         logger.debug('Bootstrapped version chosen');
144                         usingDraft = false;
145                         text = sandbox.getBootstrappedData().document;
146                         
147                     });
148                     dialog.on('execute', function(event) {
149                         logger.debug('Local draft chosen');
150                         usingDraft = true;
151                         event.success();
152                     });
153                     dialog.show();
154                     dialog.on('close', function() {
155                         loadDocument(text, usingDraft, timestamp);
156                     });
157                 } else {
158                     loadDocument(sandbox.getBootstrappedData().document, false);
159                 }
160             } else {
161                 loadDocument(sandbox.getBootstrappedData().document, false);
162             }
163         },
164         getDocument: function() {
165             return wlxmlDocument;
166         },
167         saveDocument: function() {
168             var documentSaveForm = $.extend({
169                         fields: [],
170                         content_field_name: 'text',
171                         version_field_name: 'version'
172                     },
173                     sandbox.getConfig().documentSaveForm
174                 ),
175                 dialog = Dialog.create({
176                     fields: documentSaveForm.fields,
177                     title: gettext('Save Document'),
178                     executeButtonText: gettext('Save')
179                 });
180             
181             dialog.on('execute', function(event) {
182                 sandbox.publish('savingStarted', 'remote');
183
184                 var formData = event.formData;
185                 formData[documentSaveForm.content_field_name] = wlxmlDocument.toXML();
186                 formData[documentSaveForm.version_field_name] = documentProperties.version;
187                 if(sandbox.getConfig().jsonifySentData) {
188                     formData = JSON.stringify(formData);
189                 }
190
191                 dialog.toggleButtons(false);
192                 $.ajax({
193                     method: 'post',
194                     url: sandbox.getConfig().documentSaveUrl(document_id),
195                     data: formData,
196                     success: function(data) {
197                         event.success();
198                         sandbox.publish('savingEnded', 'success', 'remote', data);
199
200                         Object.keys(data)
201                             .filter(function(key) {
202                                 return key !== 'text';
203                             })
204                             .forEach(function(key) {
205                                 documentProperties[key] = data[key];
206                             });
207
208                         reloadHistory();
209                     },
210                     error: function() {event.error(); sandbox.publish('savingEnded', 'error', 'remote');}
211                 });
212             });
213             dialog.on('cancel', function() {
214             });
215             dialog.show();
216             
217
218         },
219         getHistory: function() {
220             return history;
221         },
222         fetchDiff: function(ver1, ver2) {
223             $.ajax({
224                 method: 'get',
225                 url: sandbox.getConfig().documentDiffUrl(document_id),
226                 data: {from: ver1, to: ver2},
227                 success: function(data) {
228                     sandbox.publish('diffFetched', {table: data, ver1: ver1, ver2: ver2});
229                 },
230             });
231         },
232         restoreVersion: function(version) {
233             var documentRestoreForm = $.extend({
234                         fields: [],
235                         version_field_name: 'version'
236                     },
237                     sandbox.getConfig().documentRestoreForm
238                 ),
239                 dialog = Dialog.create({
240                     fields: documentRestoreForm.fields,
241                     title: gettext('Restore Version'),
242                     executeButtonText: gettext('Restore')
243                 });
244
245             dialog.on('execute', function(event) {
246                 var formData = event.formData;
247                 formData[documentRestoreForm.version_field_name] = version;
248                 sandbox.publish('restoringStarted', {version: version});
249                 if(sandbox.getConfig().jsonifySentData) {
250                     formData = JSON.stringify(formData);
251                 }
252                 $.ajax({
253                     method: 'post',
254                     dataType: 'json',
255                     url: sandbox.getConfig().documentRestoreUrl(document_id),
256                     data: formData,
257                     success: function(data) {
258                         Object.keys(data)
259                             .filter(function(key) {
260                                 return key !== 'document';
261                             })
262                             .forEach(function(key) {
263                                 documentProperties = data[key];
264                             });
265                         reloadHistory();
266                         wlxmlDocument.loadXML(data.document);
267                         documentDirty = false;
268                         sandbox.publish('documentReverted', data.version);
269                         event.success();
270                     },
271                 });
272             });
273             dialog.show();
274         },
275         dropDraft: function() {
276             logger.debug('Dropping a draft...');
277             wlxmlDocument.loadXML(sandbox.getBootstrappedData().document);
278             draftDirty = false;
279             logger.debug('Draft dropped');
280             sandbox.publish('draftDropped');
281         },
282         getDocumentId: function() {
283             return document_id;
284         },
285         getDocumentProperties: function() {
286             return documentProperties;
287         }
288     };
289 };
290
291 });