[IMP] Proper hash update for Ace
[odoo/odoo.git] / addons / website / static / src / js / website.ace.js
1 (function () {
2     'use strict';
3
4     var globalEditor;
5
6     var hash = "#advanced-view-editor";
7
8     var website = openerp.website;
9     website.templates.push('/website/static/src/xml/website.ace.xml');
10
11     website.ready().then(function () {
12         if (window.location.hash.indexOf(hash) >= 0) {
13             launch();
14         }
15     });
16
17     function launch () {
18         if (globalEditor) {
19             globalEditor.open();
20         } else {
21             globalEditor = new website.ace.ViewEditor(this);
22             globalEditor.appendTo($(document.body));
23         }
24     }
25
26     website.EditorBar.include({
27         events: _.extend({}, website.EditorBar.prototype.events, {
28             'click a[data-action=ace]': 'launch',
29         }),
30         launch: launch,
31     });
32
33     website.ace = {};
34
35     website.ace.XmlDocument = openerp.Class.extend({
36         init: function (text) {
37             this.xml = text;
38         },
39         isWellFormed: function () {
40             if (document.implementation.createDocument) {
41                 var dom = new DOMParser().parseFromString(this.xml, "text/xml");
42                 return dom.getElementsByTagName("parsererror").length === 0;
43             } else if (window.ActiveXObject) {
44                 // TODO test in IE
45                 var msDom = new ActiveXObject("Microsoft.XMLDOM");
46                 msDom.async = false;
47                 return !msDom.loadXML(this.xml);
48             }
49             return true;
50         },
51         format: function () {
52             return vkbeautify.xml(this.xml, 4);
53         },
54     });
55
56     website.ace.ViewOption = openerp.Widget.extend({
57         template: 'website.ace_view_option',
58         init: function (parent, options) {
59             this.view_id = options.id;
60             this.view_name = options.name;
61             this._super(parent);
62         },
63     });
64
65     website.ace.ViewEditor = openerp.Widget.extend({
66         template: 'website.ace_view_editor',
67         events: {
68             'change #ace-view-list': 'displaySelectedView',
69             'click button[data-action=save]': 'saveViews',
70             'click button[data-action=format]': 'formatXml',
71             'click button[data-action=close]': 'close',
72         },
73         init: function (parent) {
74             this.buffers = {};
75             this._super(parent);
76         },
77         start: function () {
78             var self = this;
79             self.aceEditor = ace.edit(self.$('#ace-view-editor')[0]);
80             self.aceEditor.setTheme("ace/theme/monokai");
81             var viewId = $(document.documentElement).data('view-xmlid');
82             openerp.jsonRpc('/website/customize_template_get', 'call', {
83                 'xml_id': viewId,
84                 'optional': false,
85             }).then(function (views) {
86                 self.loadViews.call(self, views);
87                 self.open.call(self);
88             });
89         },
90         loadViews: function (views) {
91             var self = this;
92             var activeViews = _.filter(views, function (view) {
93                return view.active;
94             });
95             var $viewList = self.$('#ace-view-list');
96             _.each(activeViews, function (view) {
97                 if (view.id) {
98                     new website.ace.ViewOption(self, view).appendTo($viewList);
99                     self.loadView(view.id);
100                 }
101             });
102         },
103         loadView: function (id) {
104             var viewId = parseInt(id, 10);
105             var self = this;
106             openerp.jsonRpc('/web/dataset/call', 'call', {
107                 model: 'ir.ui.view',
108                 method: 'read',
109                 args: [[viewId], ['arch'], website.get_context()],
110             }).then(function(result) {
111                 var editingSession = self.buffers[viewId] = new ace.EditSession(result[0].arch);;
112                 editingSession.setMode("ace/mode/xml");
113                 editingSession.setUndoManager(new ace.UndoManager());
114                 editingSession.on("change", function () {
115                     setTimeout(function () {
116                         var $option = self.$('#ace-view-list').find('[value='+viewId+']');
117                         var bufferName = $option.text();
118                         var dirtyMarker = " (unsaved changes)";
119                         var isDirty = editingSession.getUndoManager().hasUndo();
120                         if (isDirty && bufferName.indexOf(dirtyMarker) < 0) {
121                             $option.text(bufferName + dirtyMarker);
122                         } else if (!isDirty && bufferName.indexOf(dirtyMarker) > 0) {
123                             $option.text(bufferName.substring(0, bufferName.indexOf(dirtyMarker)));
124                         }
125                     }, 1);
126                 });
127                 if (viewId === self.selectedViewId()) {
128                     self.displayView.call(self, viewId);
129                 }
130             });
131         },
132         selectedViewId: function () {
133             return parseInt(this.$('#ace-view-list').val(), 10);
134         },
135         displayView: function (id) {
136             var viewId = parseInt(id, 10);
137             var editingSession = this.buffers[viewId];
138             if (editingSession) {
139                 this.aceEditor.setSession(editingSession);
140             }
141         },
142         displaySelectedView: function () {
143             this.displayView(this.selectedViewId());
144             this.updateHash();
145         },
146         formatXml: function () {
147             var xml = new website.ace.XmlDocument(this.aceEditor.getValue());
148             this.aceEditor.setValue(xml.format());
149         },
150         saveViews: function () {
151             var self = this;
152             var toSave = _.filter(_.map(self.buffers, function (editingSession, viewId) {
153                 return {
154                     id: parseInt(viewId, 10),
155                     isDirty: editingSession.getUndoManager().hasUndo(),
156                     text: editingSession.getValue(),
157                 };
158             }), function (session) {
159                 return session.isDirty;
160             });
161             var requests = _.map(toSave, self.saveView);
162             $.when.apply($, requests).then(function () {
163                 self.reloadPage.call(self);
164             }).fail(function () {
165                 self.displayError.call(self);
166             });
167         },
168         saveView: function (session) {
169             var xml = new website.ace.XmlDocument(session.text);
170             if (xml.isWellFormed()) {
171                 return openerp.jsonRpc('/web/dataset/call', 'call', {
172                     model: 'ir.ui.view',
173                     method: 'write',
174                     args: [[session.id], { 'arch':  xml.xml }, website.get_context()],
175                 });
176             } else {
177                 return $.Deferred().fail("Malformed XML document");
178             }
179         },
180         updateHash: function () {
181             window.location.hash = hash + "?view=" + this.selectedViewId();
182         },
183         reloadPage: function () {
184             this.updateHash();
185             window.location.reload();
186         },
187         displayError: function (error) {
188             // TODO Improve feedback (e.g. update 'Save' button + tooltip)
189             alert(error);
190         },
191         open: function () {
192             this.$el.removeClass('oe_ace_closed').addClass('oe_ace_open');
193             var curentHash = window.location.hash;
194             var indexOfView = curentHash.indexOf("?view=");
195             if (indexOfView >= 0) {
196                 var viewId = parseInt(curentHash.substring(indexOfView + 6, curentHash.length), 10);
197                 this.$('#ace-view-list').val(viewId).change();
198             } else {
199                 window.location.hash = hash;
200             }
201         },
202         close: function () {
203             window.location.hash = "";
204             var self = this;
205             this.$el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {
206                 globalEditor = null;
207                 self.destroy.call(self);
208             }).removeClass('oe_ace_open').addClass('oe_ace_closed');
209         },
210     });
211
212 })();