Przeksztalcenie odwrotne.
[redakcja.git] / platforma / static / js / models.js
index 4052594..d2869ed 100755 (executable)
@@ -36,76 +36,174 @@ Editor.ToolbarButtonsModel = Editor.Model.extend({
 });
 
 
-// Stany modelu:
 //
-//                  -> error -> loading
-//                 /
-// empty -> loading -> synced -> unsynced -> loading
-//                           \
-//                            -> dirty -> updating -> updated -> synced
+// HTML Document Model
 //
-Editor.XMLModel = Editor.Model.extend({
-    _className: 'Editor.XMLModel',
-    serverURL: null,
-    data: '',
+Editor.HTMLModel = Editor.Model.extend({
+    _className: 'Editor.HTMLModel',
+    textURL: null,    
     state: 'empty',
-  
-    init: function(document, serverURL) {
+
+    init: function(document, textURL) {
         this._super();
         this.set('state', 'empty');
         this.set('revision', document.get('revision'));
         this.document = document;
-        this.serverURL = serverURL;
-        this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+
+        this.textURL = textURL;        
+
+        this.htmlXSL = null;
+        this.wlmlXSL = null;
+        this.rawText = null;
+
+        // create a parser and a serializer
+        this.parser = new DOMParser();
+        this.serializer = new XMLSerializer();
+
         this.addObserver(this, 'data', this.dataChanged.bind(this));
     },
-  
+
     load: function(force) {
         if (force || this.get('state') == 'empty') {
             this.set('state', 'loading');
-            messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
+            messageCenter.addMessage('info', 'xmlload', 'Wczytuję HTML...');
+
+            // request all stylesheets
             $.ajax({
-                url: this.serverURL,
+                url: documentInfo.staticURL + 'xsl/wl2html_client.xsl',
+                dataType: 'xml',                
+                success: this.htmlXSLLoadSuccess.bind(this),
+                error: this.loadingFailed.bind(this)
+            });
+
+            $.ajax({
+                url: documentInfo.staticURL + 'xsl/html2wl_client.xsl',
+                dataType: 'xml',
+                success: this.wlmlXSLLoadSuccess.bind(this),
+                error: this.loadingFailed.bind(this)
+            });
+
+            $.ajax({
+                url: this.textURL,
                 dataType: 'text',
                 data: {
                     revision: this.get('revision'),
                     user: this.document.get('user')
                     },
-                success: this.loadingSucceeded.bind(this),
+                success: this.textLoadSuccess.bind(this),
                 error: this.loadingFailed.bind(this)
             });
             return true;
         }
         return false;
     },
-  
-    loadingSucceeded: function(data) {
+
+    asWLML: function(element) 
+    {        
+        var result = this.wlmlXSL.transformToFragment(element, document);
+
+        if(!result) {
+            console.log(this.wlmlXSL.transformToDocument(element));
+            throw "Failed to transform fragment";
+        }
+        
+        console.log("Transform result:", result);
+        return this.serializer.serializeToString(result);
+    },
+
+    updateWithWLML: function($element, text)
+    {
+        // filter the string
+        text = text.replace(/\/\s+/g, '<br />');
+        var chunk = this.parser.parseFromString("<chunk>"+text+"</chunk>", "text/xml");
+
+        var errors = $('parsererror', chunk);
+
+        // check if chunk is parsable
+        if(errors.length > 0)
+            throw {text: errors.text(), html: errors.html()};
+        
+        var result = this.htmlXSL.transformToFragment(chunk, document);
+
+        console.log("RESULT", this.serializer.serializeToString(result));
+
+        if(!result)
+            throw "WLML->HTML transformation failed.";
+        
+        $element.replaceWith(result);
+    },
+
+    createXSLT: function(xslt_doc) {
+        var p = new XSLTProcessor();
+        p.importStylesheet(xslt_doc);
+        return p;
+    },
+
+    htmlXSLLoadSuccess: function(data) 
+    {
+        try {
+            this.htmlXSL = this.createXSLT(data);
+
+            if(this.wlmlXSL && this.htmlXSL && this.rawText)
+                this.loadSuccess();
+        } catch(e) {
+            this.loadingFailed();
+        }
+    },
+
+    wlmlXSLLoadSuccess: function(data)
+    {
+        try {
+            this.wlmlXSL = this.createXSLT(data);
+
+            if(this.wlmlXSL && this.htmlXSL && this.rawText)
+                this.loadSuccess();
+        } catch(e) {
+            this.loadingFailed();
+        }
+    },
+
+    textLoadSuccess: function(data) {
+        this.rawText = data;
+
+        if(this.wlmlXSL && this.htmlXSL && this.rawText)
+                this.loadSuccess();
+    },
+
+    loadSuccess: function() {
         if (this.get('state') != 'loading') {
             alert('erroneous state:', this.get('state'));
         }
-        this.set('data', data);
+
+        // prepare text
+        var doc = null;
+        doc = this.rawText.replace(/\/\s+/g, '<br />');
+        doc = this.parser.parseFromString(doc, 'text/xml');
+        doc = this.htmlXSL.transformToFragment(doc, document).firstChild;
+
+        this.set('data', doc);
         this.set('state', 'synced');
-        messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
+        messageCenter.addMessage('success', 'xmlload', 'Wczytałem HTML :-)');
     },
-  
+
     loadingFailed: function(response)
     {
         if (this.get('state') != 'loading') {
             alert('erroneous state:', this.get('state'));
         }
-        
+
         var message = parseXHRError(response);
-        
+
         this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
         this.set('state', 'error');
-        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
+        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać HTML. Spróbuj ponownie :-(');
     },
-  
+
     save: function(message) {
         if (this.get('state') == 'dirty') {
             this.set('state', 'updating');
             messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
-      
+
             var payload = {
                 contents: this.get('data'),
                 revision: this.get('revision'),
@@ -114,7 +212,7 @@ Editor.XMLModel = Editor.Model.extend({
             if (message) {
                 payload.message = message;
             }
-      
+
             $.ajax({
                 url: this.serverURL,
                 type: 'post',
@@ -127,7 +225,7 @@ Editor.XMLModel = Editor.Model.extend({
         }
         return false;
     },
-  
+
     saveSucceeded: function(data) {
         if (this.get('state') != 'updating') {
             alert('erroneous state:', this.get('state'));
@@ -136,7 +234,7 @@ Editor.XMLModel = Editor.Model.extend({
         this.set('state', 'updated');
         messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
     },
-  
+
     saveFailed: function() {
         if (this.get('state') != 'updating') {
             alert('erroneous state:', this.get('state'));
@@ -144,7 +242,7 @@ Editor.XMLModel = Editor.Model.extend({
         messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
         this.set('state', 'dirty');
     },
-  
+
     // For debbuging
     set: function(property, value) {
         if (property == 'state') {
@@ -152,13 +250,13 @@ Editor.XMLModel = Editor.Model.extend({
         }
         return this._super(property, value);
     },
-  
+
     dataChanged: function(property, value) {
         if (this.get('state') == 'synced') {
             this.set('state', 'dirty');
         }
     },
-  
+
     dispose: function() {
         this.removeObserver(this);
         this._super();
@@ -166,36 +264,36 @@ Editor.XMLModel = Editor.Model.extend({
 });
 
 
-Editor.HTMLModel = Editor.Model.extend({
-    _className: 'Editor.HTMLModel',
-    dataURL: null,
-    htmlURL: null,
-    renderURL: null,
-    displaData: '',
-    xmlParts: {},
+// Stany modelu:
+//
+//                  -> error -> loading
+//                 /
+// empty -> loading -> synced -> unsynced -> loading
+//                           \
+//                            -> dirty -> updating -> updated -> synced
+//
+Editor.XMLModel = Editor.Model.extend({
+    _className: 'Editor.XMLModel',
+    serverURL: null,
+    data: '',
     state: 'empty',
   
-    init: function(document, dataURL, htmlURL) {
+    init: function(document, serverURL) {
         this._super();
         this.set('state', 'empty');
-        this.set('revision', document.get('revision'));        
-        
+        this.set('revision', document.get('revision'));
         this.document = document;
-        this.htmlURL = htmlURL;
-        this.dataURL = dataURL;
-        this.renderURL = documentInfo.renderURL;
-        this.xmlParts = {};
+        this.serverURL = serverURL;
+        this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
+        this.addObserver(this, 'data', this.dataChanged.bind(this));
     },
   
     load: function(force) {
         if (force || this.get('state') == 'empty') {
             this.set('state', 'loading');
-
-            // load the transformed data
-            // messageCenter.addMessage('info', 'Wczytuję HTML...');
-
+            messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
             $.ajax({
-                url: this.htmlURL,
+                url: this.serverURL,
                 dataType: 'text',
                 data: {
                     revision: this.get('revision'),
@@ -204,8 +302,10 @@ Editor.HTMLModel = Editor.Model.extend({
                 success: this.loadingSucceeded.bind(this),
                 error: this.loadingFailed.bind(this)
             });
+            return true;
         }
-    },    
+        return false;
+    },
   
     loadingSucceeded: function(data) {
         if (this.get('state') != 'loading') {
@@ -213,98 +313,38 @@ Editor.HTMLModel = Editor.Model.extend({
         }
         this.set('data', data);
         this.set('state', 'synced');
+        messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
     },
   
-    loadingFailed: function(response) {
+    loadingFailed: function(response)
+    {
         if (this.get('state') != 'loading') {
             alert('erroneous state:', this.get('state'));
         }
         
-        var err = parseXHRError(response);
+        var message = parseXHRError(response);
         
-        this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + err.error_message);
-        this.set('state', 'error');        
-    },
-
-    getXMLPart: function(elem, callback)
-    {
-        var path = elem.attr('x-pointer');
-        if(!this.xmlParts[path])
-            this.loadXMLPart(elem, callback);
-        else
-            callback(path, this.xmlParts[path]);
-    },
-
-    loadXMLPart: function(elem, callback)
-    {
-        var path = elem.attr('x-pointer');
-        var self = this;
-
-        $.ajax({
-            url: this.dataURL,
-            dataType: 'text',
-            data: {
-                revision: this.get('revision'),
-                user: this.document.get('user'),
-                chunk: path
-                // format: 'nl'
-            },
-            success: function(data) {
-                self.xmlParts[path] = data;
-                console.log(data);
-                callback(path, data);
-            },
-            // TODO: error handling
-            error: function(data) {
-                console.log('Failed to load fragment');
-                callback(undefined, undefined);
-            }
-        });
-    },
-
-    putXMLPart: function(elem, data, callback) {
-        var self = this;
-      
-        var path = elem.attr('x-pointer');
-        this.xmlParts[path] = data;
-
-        this.set('state', 'dirty');
-
-        /* re-render the changed fragment */
-        $.ajax({
-            url: this.renderURL,
-            type: "POST",
-            dataType: 'text; charset=utf-8',
-            data: {
-                fragment: data,
-                chunk: path
-                // format: 'nl'
-            },
-            success: function(htmldata) {
-                callback(elem, htmldata);
-                self.set('state', 'dirty');
-            }
-        });
+        this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
+        this.set('state', 'error');
+        messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
     },
-
+  
     save: function(message) {
         if (this.get('state') == 'dirty') {
             this.set('state', 'updating');
-
+            messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
+      
             var payload = {
-                chunks: $.toJSON(this.xmlParts),
+                contents: this.get('data'),
                 revision: this.get('revision'),
                 user: this.document.get('user')
             };
-
             if (message) {
                 payload.message = message;
             }
-
-            console.log(payload)
-
+      
             $.ajax({
-                url: this.dataURL,
+                url: this.serverURL,
                 type: 'post',
                 dataType: 'json',
                 data: payload,
@@ -314,38 +354,45 @@ Editor.HTMLModel = Editor.Model.extend({
             return true;
         }
         return false;
-      
     },
-
+  
     saveSucceeded: function(data) {
         if (this.get('state') != 'updating') {
             alert('erroneous state:', this.get('state'));
         }
-
-        // flush the cache
-        this.xmlParts = {};
-    
         this.set('revision', data.revision);
         this.set('state', 'updated');
+        messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
     },
-
+  
     saveFailed: function() {
         if (this.get('state') != 'updating') {
             alert('erroneous state:', this.get('state'));
-        }        
+        }
+        messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
         this.set('state', 'dirty');
     },
-
+  
     // For debbuging
     set: function(property, value) {
         if (property == 'state') {
             console.log(this.description(), ':', property, '=', value);
         }
         return this._super(property, value);
+    },
+  
+    dataChanged: function(property, value) {
+        if (this.get('state') == 'synced') {
+            this.set('state', 'dirty');
+        }
+    },
+  
+    dispose: function() {
+        this.removeObserver(this);
+        this._super();
     }
 });
 
-
 Editor.ImageGalleryModel = Editor.Model.extend({
     _className: 'Editor.ImageGalleryModel',
     serverURL: null,
@@ -449,7 +496,7 @@ Editor.DocumentModel = Editor.Model.extend({
 
         this.contentModels = {
             'xml': new Editor.XMLModel(this, data.text_url),
-            'html': new Editor.HTMLModel(this, data.text_url, data.html_url),
+            'html': new Editor.HTMLModel(this, data.text_url),
             'gallery': new Editor.ImageGalleryModel(this, data.gallery_url)
         };