d57640bc1f34939c4bee6867f2bd98a61a3de524
[redakcja.git] / platforma / static / js / views / html.js
1 /*global View render_template panels */
2 var HTMLView = View.extend({
3     _className: 'HTMLView',
4     element: null,
5     model: null,
6     template: 'html-view-template',
7   
8     init: function(element, model, parent, template) {
9         this._super(element, model, template);
10         this.parent = parent;
11     
12         this.model
13         .addObserver(this, 'data', this.modelDataChanged.bind(this))        
14         .addObserver(this, 'state', this.modelStateChanged.bind(this));
15       
16         $('.htmlview', this.element).html(this.model.get('data'));
17
18         this.$menuTemplate = $(render_template('html-view-frag-menu-template', this));
19         
20         this.modelStateChanged('state', this.model.get('state'));
21         this.model.load();
22
23         this.currentOpen = null;
24         this.currentFocused = null;
25         this.themeBoxes = [];
26     },
27
28     modelDataChanged: function(property, value) {
29         $('.htmlview', this.element).html(value);
30         this.updatePrintLink();
31         var self = this;
32
33         /* upgrade editable elements */
34         $("*[x-editable]").each(function() {
35             $(this).append( self.$menuTemplate.clone() );
36         });
37
38         var n = 5001;
39
40         var doc_base = $('.htmlview .utwor', this.element);
41
42         /* mark themes */
43         $(".theme-ref").each(function() {
44             var id = $(this).attr('x-theme-class');
45
46             var end = $("span.theme-end[x-theme-class = " + id+"]");
47             var begin = $("span.theme-begin[x-theme-class = " + id+"]");
48
49             var h = $(this).outerHeight();
50
51             h = Math.max(h, end.offset().top - begin.offset().top);
52             $(this).css('height', h);
53         }); 
54     },
55
56     updatePrintLink: function() {
57         var base = this.$printLink.attr('ui:baseref');
58         this.$printLink.attr('href', base + "?user="+this.model.document.get('user')+"&revision=" + this.model.get('revision'));
59     },
60   
61     modelStateChanged: function(property, value) 
62     {
63         var self = $(this);
64
65         if (value == 'synced' || value == 'dirty') {
66             this.unfreeze();
67         } else if (value == 'unsynced') {
68             if(this.currentOpen) this.closeWithoutSave(this.currentOpen);
69             this.freeze('Niezsynchronizowany...');
70         } else if (value == 'loading') {
71             this.freeze('Ɓadowanie...');
72         } else if (value == 'saving') {
73             this.freeze('Zapisywanie...');
74         } else if (value == 'error') {
75             this.freeze(this.model.get('error'));
76             $('.xml-editor-ref', this.overlay).click(
77             function(event) {
78                 console.log("Sending scroll rq.", this);
79                 try {
80                     var href = $(this).attr('href').split('-');
81                     var line = parseInt(href[1]);
82                     var column = parseInt(href[2]);
83                     
84                     $(document).trigger('xml-scroll-request', {line:line, column:column});
85                 } catch(e) {
86                     console.log(e);
87                 }
88                 
89                 return false;
90             });
91         }
92     },
93
94     render: function() {
95         this.element.unbind('click');
96
97         if(this.$printLink) this.$printLink.unbind();
98         this._super();
99         this.$printLink = $('.html-print-link', this.element);
100         this.updatePrintLink();
101
102         this.element.bind('click', this.itemClicked.bind(this));
103         // this.element.bind('mouseover', this.itemHover.bind(this));
104     },
105   
106     reload: function() {
107         this.model.load(true);
108     },
109   
110     dispose: function() {
111         this.model.removeObserver(this);
112         this._super();
113     },
114
115     itemClicked: function(event) 
116     {
117         var self = this;
118         
119         console.log('click:', event, event.ctrlKey, event.target);        
120         var $e = $(event.target);
121
122         if($e.hasClass('annotation'))
123         {
124             if(this.currentOpen) return false;
125             
126             var $p = $e.parent();
127             if(this.currentFocused) 
128             {
129                 console.log(this.currentFocused, $p);
130                 if($p[0] == this.currentFocused[0]) {
131                     console.log('unfocus of current');
132                     this.unfocusAnnotation();
133                     return false;
134                 }
135
136                 console.log('switch unfocus');
137                 this.unfocusAnnotation();                
138             }
139
140             this.focusAnnotation($p);
141             return false;
142         }
143
144         /*
145          * Clicking outside of focused area doesn't unfocus by default
146          *  - this greatly simplifies the whole click check
147          */
148
149         /* other buttons */
150         if($e.hasClass('edit-button'))
151             this.openForEdit( this.editableFor($e) );
152
153         if($e.hasClass('accept-button'))
154             this.closeWithSave( this.editableFor($e) );
155
156         if($e.hasClass('reject-button'))
157             this.closeWithoutSave( this.editableFor($e) );        
158     },
159
160     unfocusAnnotation: function()
161     {
162         if(!this.currentFocused)
163         {
164             console.log('Redundant unfocus');
165             return false;
166         }
167
168         if(this.currentOpen 
169           && this.currentOpen.is("*[x-annotation-box]")
170           && this.currentOpen.parent()[0] == this.currentFocused[0])
171         {
172             console.log("Can't unfocus open box");
173             return false;
174         }
175
176         var $box = $("*[x-annotation-box]", this.currentFocused);
177         $box.css({'display': 'none'});
178         // this.currentFocused.removeAttr('x-focused');
179         // this.currentFocused.hide();
180         this.currentFocused = null;
181     },
182
183     focusAnnotation: function($e) {
184         this.currentFocused = $e;
185         var $box = $("*[x-annotation-box]", $e);
186         $box.css({'display': 'block'});
187         
188         // $e.attr('x-focused', 'focused');        
189     },
190
191     closeWithSave: function($e) {
192         var $edit = $e.data('edit-overlay');
193         var newText = $('textarea', $edit).val();
194
195         this.model.putXMLPart($e, newText, function($e, html) {
196             this.renderPart($e, html);
197             $edit.remove();
198             $e.removeAttr('x-open');            
199         }.bind(this) );
200         this.currentOpen = null;
201     },
202
203     closeWithoutSave: function($e) {
204         var $edit = $e.data('edit-overlay');
205         $edit.remove();
206         $e.removeAttr('x-open');
207         this.currentOpen = null;
208     },
209
210     renderPart: function($e, html) {
211         // exceptions aren't good, but I don't have a better idea right now
212         if($e.attr('x-annotation-box')) {
213             // replace the whole annotation
214             var $p = $e.parent();
215             $p.html(html);
216             var $box = $('*[x-annotation-box]', $p);
217             $box.append( this.$menuTemplate.clone() );
218             
219             if(this.currentFocused && $p[0] == this.currentFocused[0])
220             {                
221                 this.currentFocused = $p;
222                 $box.css({'display': 'block'});
223             }
224             
225             return;
226         }
227
228         $e.html(html);
229         $e.append( this.$menuTemplate.clone() );
230     },
231
232     editableFor: function($button) 
233     {
234         var $e = $button;
235         var n = 0;
236         
237         while( ($e[0] != this.element[0]) && !($e.attr('x-editable')) && n < 50)
238         {
239             // console.log($e, $e.parent(), this.element);
240             $e = $e.parent();
241             n += 1;
242         }
243
244         if(!$e.attr('x-editable'))
245             throw Exception("Click outside of editable")
246
247         console.log("Trigger", $button, " yields editable: ", $e);
248         return $e;
249     },
250
251     openForEdit: function($origin)
252     {       
253         if(this.currentOpen && this.currentOpen != $origin) {
254             this.closeWithSave(this.currentOpen);    
255         }
256         
257         var x = $origin[0].offsetLeft;
258         var y = $origin[0].offsetTop;
259         var w = $origin.outerWidth();
260         var h = $origin.innerHeight();
261
262         console.log("Editable:", $origin, " offsetParent:", $origin[0].offsetParent);
263         console.log("Dimensions: ", x, y, w , h);
264
265         // start edition on this node
266         var $overlay = $('<div class="html-editarea"><textarea></textarea></div>');
267         
268         $overlay.css({position: 'absolute', height: h, left: x, top: y, width: '95%'});        
269         $($origin[0].offsetParent).append($overlay);
270         $origin.data('edit-overlay', $overlay);
271
272         this.model.getXMLPart($origin, function(path, data) {
273             $('textarea', $overlay).val(data);
274         });      
275
276         if($origin.is("*[x-annotation-box]"))
277         {
278             var $b =  $origin.parent();
279             if(this.currentFocused) {
280                 // if some other is focused
281                 if($b[0] != this.currentFocused[0]) {
282                     this.unfocusAnnotation();
283                     this.focusAnnotation($b);
284                 }
285                 // already focues
286             }
287             else { // nothing was focused
288                 this.focusAnnotation($b);
289             }
290         }
291         else { // this item is not focusable
292             if(this.currentFocused) this.unfocusAnnotation();
293         }
294
295         this.currentOpen = $origin;
296         $origin.attr('x-open', 'open');
297                 
298         return false;
299     }
300   
301 });
302
303 // Register view
304 panels['html'] = HTMLView;