editor: keep document properties on document instance, inform about changes via events
[fnpeditor.git] / src / editor / modules / metadataEditor / metadataEditor.js
1 define([
2 'libs/jquery',
3 'libs/underscore',
4 'libs/text!./templates/main.html',
5 'libs/text!./templates/item.html',
6 'views/openSelect/openSelect'
7 ], function($, _, mainTemplate, itemTemplate, OpenSelectView) {
8
9 'use strict';
10 /* globals gettext */
11
12 return function(sandbox) {
13
14     var currentNode,
15         adding = false,
16         metadataConfig = (sandbox.getConfig().metadata || []).sort(function(configRow1, configRow2) {
17             if(configRow1.key < configRow2.key) {
18                 return -1;
19             }
20             if(configRow1.key > configRow2.key) {
21                 return 1;
22             }
23             return 0;
24         });
25
26     var getValuesForKey = function(key) {
27         var toret = [];
28         metadataConfig.some(function(configRow) {
29             if(configRow.key === key) {
30                 toret = configRow.values || [];
31                 return true;
32             }
33         });
34         return toret;
35     };
36     
37     var view = {
38         node: $(_.template(mainTemplate)()),
39         setup: function() {
40             var view = this;
41             var metaTable = this.metaTable = this.node.find('table');
42             
43             this.node.find('.rng-module-metadataEditor-addBtn').click(function() {
44                 adding = true;
45                 currentNode.document.transaction(function() {
46                     currentNode.getMetadata().add('','');
47                 }, this, gettext('Add metadata row'));
48             });
49             
50             this.metaTable.on('click', '.rng-visualEditor-metaRemoveBtn', function(e) {
51                 currentNode.document.transaction(function() {
52                     $(e.target).closest('tr').data('row').remove();
53                 }, this, gettext('Remove metadata row'));
54             });
55             
56             this.metaTable.on('keydown', '[contenteditable]', function(e) {
57                 /* globals document */
58                 if(e.which === 13) {
59                     if($(document.activeElement).hasClass('rng-module-metadataEditor-metaItemKey')) {
60                         metaTable.find('.rng-module-metadataEditor-metaItemValue').focus();
61                     } else {
62                         var input = $('<input>');
63                         input.appendTo('body').focus();
64                         view.node.find('.rng-module-metadataEditor-addBtn').focus();
65                         input.remove();
66                     }
67                     e.preventDefault();
68                 }
69             });
70             
71             this.metaTable.on('keyup', '[contenteditable]', _.throttle(function(e) {
72                 if(e.which !== 13) {
73                     var editable = $(e.target),
74                         toSet = editable.text(),
75                         row = editable.parents('tr').data('row'),
76                         isKey = _.last(editable.attr('class').split('-')) === 'metaItemKey',
77                         method = isKey ? 'setKey' : 'setValue';
78                     row.metadata.node.document.transaction(function() {
79                         row[method](toSet);
80                     }, this, gettext('Metadata edit'));
81                 }
82             }, 500));
83         },
84         clear: function() {
85         },
86         setMetadata: function(node) {
87             this.node.find('.rng-module-metadataEditor-addBtn').attr('disabled', !node);
88             if(!node) {
89                 this.metaTable.html('');
90                 return;
91             }
92             var view = this,
93                 metadata = node.getMetadata();
94             this.metaTable.find('tr').remove();
95             metadata.forEach(function(row) {
96                 view.addMetadataRow(row);
97             });
98         },
99         addMetadataRow: function(row) {
100             var newRow = $(_.template(itemTemplate)({key: row.getKey() || '', value: row.getValue() || ''}));
101             newRow.appendTo(this.metaTable);
102             newRow.data('row', row);
103
104             var keySelectView = new OpenSelectView({
105                 value: row.getKey() || '',
106                 inputTemplate: _.template('<div class="openInput rng-module-metadataEditor-metaItemKey" contentEditable="true"><%= value %></div>')({value: row.getKey() || '' }),
107                 setInput: function(inputDOM, value) {
108                     if(inputDOM.text() !== value) {
109                         inputDOM.text(value);
110                         row.setKey(value);
111                     }
112                     valueSelectView.clearItems();
113                     getValuesForKey(value).forEach(function(value) {
114                         valueSelectView.addItem(value);
115                     });
116                 }
117             });
118             newRow.find('td:first').append(keySelectView.el).data('view', keySelectView);
119
120
121             var valueSelectView = new OpenSelectView({
122                 value: row.getValue(),
123                 inputTemplate: _.template('<div class="openInput rng-module-metadataEditor-metaItemValue" contentEditable="true"><%= value %></div>')({value: row.getValue() || '' }),
124                 maxHeight: '300px',
125                 maxWidth: '100px',
126                 setInput: function(inputDOM, value) {
127                     if(inputDOM.text() !== value) {
128                         inputDOM.text(value);
129                         row.setValue(value);
130                     }
131                 }
132             });
133             newRow.find('td:nth-child(2)').append(valueSelectView.el).data('view', valueSelectView);
134             
135
136             metadataConfig.forEach(function(configRow) {
137                 keySelectView.addItem(configRow.key);
138                 if(row.getKey() === configRow.key) {
139                     (configRow.values || []).forEach(function(value) {
140                         valueSelectView.addItem(value);
141                     });
142                 }
143             });
144
145             if(adding) {
146                 $(newRow.find('td div')[0]).focus();
147                 adding = false;
148             }
149             return newRow;
150         },
151         updateMetadataRow: function(row) {
152             this._getRowTr(row, function(tr) {
153                 var tds = tr.find('td'),
154                     keyTd = $(tds[0]),
155                     valueTd = $(tds[1]);
156
157                 keyTd.data('view').setInput(row.getKey());
158                 valueTd.data('view').setInput(row.getValue());
159             });
160         },
161         removeMetadataRow: function(row) {
162             this._getRowTr(row, function(tr) {
163                 tr.remove();
164             });
165         },
166         _getRowTr: function(row, callback) {
167             this.metaTable.find('tr').each(function() {
168                 var tr = $(this);
169                 if(tr.data('row') === row) {
170                     callback(tr);
171                     return false;
172                 }
173             });
174         }
175     };
176     
177     view.setup();
178     
179     return {
180         start: function() {
181             sandbox.publish('ready');
182         },
183         setDocument: function(document) {
184             document.on('change', function(event) {
185                 if(event.type === 'metadataAdded' && event.meta.node.sameNode(currentNode)) {
186                     view.addMetadataRow(event.meta.row);
187                 }
188                 if(event.type === 'metadataChanged' && event.meta.node.sameNode(currentNode)) {
189                     view.updateMetadataRow(event.meta.row);
190                 }
191                 if(event.type === 'metadataRemoved' && event.meta.node.sameNode(currentNode)) {
192                     view.removeMetadataRow(event.meta.row);
193                 }
194                 if(event.type === 'nodeDetached' && event.meta.node.containsNode(currentNode)) {
195                     view.setMetadata(null);
196                 }
197             });
198         },
199         setNodeElement: function(node) {
200             if(currentNode && currentNode.sameNode(node)) {
201                 return;
202             }
203             currentNode = node;
204             view.setMetadata(node);
205         },
206         getView: function() {
207             return view.node;
208         }
209     };
210 };
211
212 });