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