Fixing event handling in documentCanvas module
[fnpeditor.git] / modules / documentCanvas / documentCanvas.js
1 // Module that implements main WYSIWIG edit area\r
2 \r
3 define([\r
4 'libs/underscore-min',\r
5 './transformations', \r
6 'libs/text!./template.html'], function(_, transformations, template) {\r
7 \r
8 \r
9 \r
10 return function(sandbox) {\r
11 \r
12     var view = {\r
13         node: $(_.template(template)()),\r
14         currentNode: null,\r
15         setup: function() {\r
16             var view = this;\r
17 \r
18             this.node.find('#rng-module-documentCanvas-content').on('keyup', function() {\r
19                 //isDirty = true;\r
20                 sandbox.publish('contentChanged');\r
21             });\r
22 \r
23             this.node.on('mouseover', '[wlxml-tag]', function(e) {\r
24                 e.stopPropagation();\r
25                 sandbox.publish('nodeHovered', $(e.target));\r
26             });\r
27             this.node.on('mouseout', '[wlxml-tag]', function(e) {\r
28                 e.stopPropagation();\r
29                 sandbox.publish('nodeBlured', $(e.target));\r
30             });\r
31             this.node.on('click', '[wlxml-tag]', function(e) {\r
32                 e.stopPropagation();\r
33                 console.log('clicked node type: '+e.target.nodeType);\r
34                 view._markSelected($(e.target));\r
35             });\r
36 \r
37             this.node.on('keyup', '#rng-module-documentCanvas-contentWrapper', function(e) {\r
38                 var anchor = $(window.getSelection().anchorNode);\r
39                 if(anchor[0].nodeType === Node.TEXT_NODE)\r
40                     anchor = anchor.parent();\r
41                 if(!anchor.is('[wlxml-tag]'))\r
42                     return;\r
43                 view._markSelected(anchor);\r
44             });\r
45             \r
46             this.node.on('keydown', '#rng-module-documentCanvas-contentWrapper', function(e) {\r
47                 if(e.which === 13) { \r
48                     e.preventDefault();\r
49                     view.insertNewNode(null, null);\r
50                 }\r
51             });\r
52             \r
53             \r
54             var observer = new MutationObserver(function(mutations) {\r
55               mutations.forEach(function(mutation) {\r
56                 _.each(mutation.addedNodes, function(node) {\r
57                     node = $(node);\r
58                     node.parent().find('[wlxml-tag]').each(function() {\r
59                         tag = $(this);\r
60                         if(!tag.attr('id'))\r
61                             tag.attr('id', 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random()*16|0,v=c=='x'?r:r&0x3|0x8;return v.toString(16);}));\r
62                     });\r
63                 });\r
64               });    \r
65             });\r
66             var config = { attributes: true, childList: true, characterData: true, subtree: true };\r
67             observer.observe(this.node.find('#rng-module-documentCanvas-contentWrapper')[0], config);\r
68             \r
69             this.gridToggled = false;\r
70         },\r
71         _createNode: function(wlxmlTag, wlxmlClass) {\r
72             var toBlock = ['div', 'document', 'section', 'header'];\r
73             var htmlTag = _.contains(toBlock, wlxmlTag) ? 'div' : 'span';\r
74             var toret = $('<' + htmlTag + '>');\r
75             toret.attr('wlxml-tag', wlxmlTag);\r
76             if(wlxmlClass)\r
77                 toret.attr('wlxml-class', wlxmlClass);\r
78             return toret;\r
79         },\r
80         insertNewNode: function(wlxmlTag, wlxmlClass) {\r
81             //TODO: Insert inline\r
82             var anchor = $(window.getSelection().anchorNode);\r
83             var anchorOffset = window.getSelection().anchorOffset;\r
84             if(anchor[0].nodeType === Node.TEXT_NODE)\r
85                 anchor = anchor.parent();\r
86             if(anchor.text() === '') {\r
87                 var todel = anchor;\r
88                 anchor = anchor.parent();\r
89                 todel.remove();\r
90             }\r
91             if(anchorOffset > 0 && anchorOffset < anchor.text().length) {\r
92                 if(wlxmlTag === null && wlxmlClass === null) {\r
93                     return this.splitWithNewNode(anchor);\r
94                 }\r
95                 return this.wrapSelectionWithNewNode(wlxmlTag, wlxmlClass);\r
96             }\r
97             var newNode = this._createNode(wlxmlTag || anchor.attr('wlxml-tag'), wlxmlClass || anchor.attr('wlxml-class'));\r
98             if(anchorOffset === 0)\r
99                 anchor.before(newNode)\r
100             else\r
101                 anchor.after(newNode);\r
102             this.selectNode(newNode);\r
103             //isDirty = true;\r
104             sandbox.publish('contentChanged');\r
105         },\r
106         wrapSelectionWithNewNode: function(wlxmlTag, wlxmlClass) {\r
107             var selection = window.getSelection();\r
108             if(selection.anchorNode === selection.focusNode && selection.anchorNode.nodeType === Node.TEXT_NODE) {\r
109                 var startOffset = selection.anchorOffset;\r
110                 var endOffset = selection.focusOffset;\r
111                 if(startOffset > endOffset) {\r
112                     var tmp = startOffset;\r
113                     startOffset = endOffset;\r
114                     endOffset = tmp;\r
115                 }\r
116                 var node = selection.anchorNode;\r
117                 var prefix = node.data.substr(0, startOffset);\r
118                 var suffix = node.data.substr(endOffset);\r
119                 var core = node.data.substr(startOffset, endOffset - startOffset);\r
120                 var newNode = this._createNode(wlxmlTag, wlxmlClass);\r
121                 newNode.text(core || 'test');\r
122                 $(node).replaceWith(newNode);\r
123                 newNode.before(prefix);\r
124                 newNode.after(suffix);\r
125                 \r
126                 this.selectNode(newNode);\r
127                 //isDirty = true;\r
128                 sandbox.publish('contentChanged');\r
129             }\r
130         },\r
131         splitWithNewNode: function(node) {\r
132             var selection = window.getSelection();\r
133             if(selection.anchorNode === selection.focusNode && selection.anchorNode.nodeType === Node.TEXT_NODE) {\r
134                 var startOffset = selection.anchorOffset;\r
135                 var endOffset = selection.focusOffset;\r
136                 if(startOffset > endOffset) {\r
137                     var tmp = startOffset;\r
138                     startOffset = endOffset;\r
139                     endOffset = tmp;\r
140                 }\r
141                 var anchor = selection.anchorNode;\r
142                 var prefix = anchor.data.substr(0, startOffset);\r
143                 var suffix = anchor.data.substr(endOffset);\r
144                 var prefixNode = this._createNode(node.attr('wlxml-tag'), node.attr('wlxml-class'));\r
145                 var newNode = this._createNode(node.attr('wlxml-tag'), node.attr('wlxml-class'));\r
146                 var suffixNode = this._createNode(node.attr('wlxml-tag'), node.attr('wlxml-class'));\r
147                 prefixNode.text(prefix);\r
148                 suffixNode.text(suffix);\r
149                 node.replaceWith(newNode);\r
150                 newNode.before(prefixNode);\r
151                 newNode.after(suffixNode);\r
152                 \r
153                 this.selectNode(newNode);\r
154                 //isDirty = true;\r
155                 sandbox.publish('contentChanged');\r
156             }\r
157         },\r
158         setBody: function(HTMLTree) {\r
159             this.node.find('#rng-module-documentCanvas-content').html(HTMLTree);\r
160         },\r
161         getBody: function() {\r
162             return this.node.find('#rng-module-documentCanvas-content').html();\r
163         }, \r
164         _markSelected: function(node) {\r
165             this.dimNode(node);\r
166             \r
167             this.node.find('.rng-module-documentCanvas-currentNode').removeClass('rng-module-documentCanvas-currentNode');\r
168             \r
169             node.addClass('rng-module-documentCanvas-currentNode');\r
170 \r
171             this.currentNode = node;\r
172             sandbox.publish('nodeSelected', node);\r
173             \r
174         },\r
175         selectNode: function(node) {\r
176             view._markSelected(node);\r
177             var range = document.createRange();\r
178             range.selectNodeContents(node[0]);\r
179             range.collapse(false);\r
180 \r
181             var selection = document.getSelection();\r
182             selection.removeAllRanges()\r
183             selection.addRange(range);\r
184         },\r
185         selectNodeById: function(id) {\r
186             var node = this.node.find('#'+id);\r
187             if(node)\r
188                 this.selectNode(node);\r
189         },\r
190         highlightNode: function(node) {\r
191             if(!this.gridToggled) {\r
192                 node.addClass('rng-common-hoveredNode');\r
193                 var label = node.attr('wlxml-tag');\r
194                 if(node.attr('wlxml-class'))\r
195                     label += ' / ' + node.attr('wlxml-class');\r
196                 var tag = $('<div>').addClass('rng-module-documentCanvas-hoveredNodeTag').text(label);\r
197                 node.append(tag);\r
198             }\r
199         },\r
200         dimNode: function(node) {\r
201             if(!this.gridToggled) {\r
202                 node.removeClass('rng-common-hoveredNode');\r
203                 node.find('.rng-module-documentCanvas-hoveredNodeTag').remove();\r
204             }\r
205         },\r
206         highlightNodeById: function(id) {\r
207             var node = this.node.find('#'+id);\r
208             if(node)\r
209                 this.highlightNode(node);\r
210         },\r
211         dimNodeById: function(id) {\r
212             var node = this.node.find('#'+id);\r
213             if(node)\r
214                 this.dimNode(node);\r
215         },\r
216         selectFirstNode: function() {\r
217             var firstNodeWithText = this.node.find('[wlxml-tag]').filter(function() {\r
218                 return $(this).clone().children().remove().end().text().trim() !== '';\r
219             }).first();\r
220             var node;\r
221             if(firstNodeWithText.length)\r
222                 node = $(firstNodeWithText[0])\r
223             else {\r
224                 node = this.node.find('[wlxml-class|="p"]')\r
225             }\r
226             this.selectNode(node);\r
227         },\r
228         toggleGrid: function(toggle) {\r
229             this.node.find('[wlxml-tag]').toggleClass('rng-common-hoveredNode', toggle);\r
230             this.gridToggled = toggle;\r
231         }\r
232     };\r
233     \r
234     view.setup();\r
235 \r
236     /* public api */\r
237     return {\r
238         start: function() { sandbox.publish('ready'); },\r
239         getView: function() { return view.node; },\r
240         setDocument: function(xml) {\r
241             var transformed = transformations.fromXML.getDocumentDescription(xml);\r
242             view.setBody(transformed.HTMLTree);\r
243             view.selectFirstNode();\r
244             //isDirty = false;\r
245         },\r
246         modifyCurrentNode: function(attr, value) {\r
247             if(view.currentNode)\r
248                 view.currentNode.attr('wlxml-'+attr, value);\r
249         },\r
250         highlightNode: function(id) {\r
251             view.highlightNodeById(id);\r
252         },\r
253         dimNode: function(id) {\r
254             view.dimNodeById(id);\r
255         },\r
256         selectNode: function(id) {\r
257             view.selectNodeById(id);\r
258         },\r
259         toggleGrid: function(toggle) {\r
260             view.toggleGrid(toggle);\r
261         },\r
262         insertNewNode: function(wlxmlTag, wlxmlClass) {\r
263             view.insertNewNode(wlxmlTag, wlxmlClass);\r
264         },\r
265         wrapSelectionWithNewNode: function(wlxmlTag, wlxmlClass) {\r
266             view.wrapSelectionWithNewNode(wlxmlTag, wlxmlClass);\r
267         }\r
268     }\r
269     \r
270 };\r
271 \r
272 });