c53d3b99ecacea28607a4c0b545f4b65a4a6a7fd
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / PageTree / PageTree.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Module: TYPO3/CMS/Backend/PageTree/PageTree
16 */
17 define(['jquery',
18 'd3',
19 'TYPO3/CMS/Backend/Icons',
20 'TYPO3/CMS/Backend/PageTree/PageTreeDragDrop',
21 'TYPO3/CMS/Backend/SvgTree',
22 'TYPO3/CMS/Backend/ContextMenu',
23 'TYPO3/CMS/Backend/Storage/Persistent',
24 'TYPO3/CMS/Backend/Notification'
25 ],
26 function($, d3, Icons, PageTreeDragDrop, SvgTree, ContextMenu, Persistent, Notification) {
27 'use strict';
28
29 /**
30 * @constructor
31 * @exports TYPO3/CMS/Backend/PageTree/PageTree
32 */
33 var PageTree = function() {
34 SvgTree.call(this);
35 };
36
37 PageTree.prototype = Object.create(SvgTree.prototype);
38 var _super_ = SvgTree.prototype;
39
40 /**
41 * SelectTree initialization
42 *
43 * @param {String} selector
44 * @param {Object} settings
45 */
46 PageTree.prototype.initialize = function(selector, settings) {
47 var _this = this;
48
49 if (!_super_.initialize.call(_this, selector, settings)) {
50 return false;
51 }
52
53 _this.settings.isDragAnDrop = true;
54 _this.dispatch.on('nodeSelectedAfter.pageTree', _this.nodeSelectedAfter);
55 _this.dispatch.on('nodeRightClick.pageTree', _this.nodeRightClick);
56 _this.dispatch.on('contextmenu.pageTree', _this.contextmenu);
57 _this.dispatch.on('updateSvg.pageTree', _this.updateSvg);
58 _this.dragDrop = PageTreeDragDrop;
59 _this.dragDrop.init(_this);
60
61 if (_this.settings.temporaryMountPoint) {
62 _this.addMountPoint(_this.settings.temporaryMountPoint);
63 }
64
65 return this;
66 };
67
68 /**
69 * Add mount point
70 */
71 PageTree.prototype.addMountPoint = function(breadcrumb) {
72 var _this = this;
73
74 if (_this.wrapper.parent().find('.node-mount-point').length) {
75 _this.wrapper.parent().find('.node-mount-point').remove();
76 }
77
78 _this.mountPoint = _this.wrapper.before(
79 '<div class="node-mount-point">' +
80 '<div class="node-mount-point__icon" data-tree-icon="actions-document-info"></div>' +
81 '<div class="node-mount-point__text"><div>' + breadcrumb + '</div></div>' +
82 '<div class="node-mount-point__icon" data-tree-icon="actions-close" title="' + TYPO3.lang['labels.temporaryDBmount'] + '"></div>' +
83 '</div>'
84 );
85
86 _this.wrapper.parent()
87 .find('[data-tree-icon=actions-close]')
88 .on('click', function() {
89 top.TYPO3.Backend.NavigationContainer.PageTree.unsetTemporaryMountPoint();
90 _this.wrapper.parent().find('.node-mount-point').remove();
91 });
92
93 //get icons
94 _this.wrapper.parent().find('.node-mount-point [data-tree-icon]').each(function() {
95 var $this = $(this);
96
97 Icons.getIcon($this.attr('data-tree-icon'), Icons.sizes.small, null, null, 'inline').done(function(icon) {
98 $this.append(icon);
99 });
100 });
101 };
102
103 /**
104 * Displays a notification message and refresh nodes
105 *
106 * @param error
107 */
108 PageTree.prototype.errorNotification = function(error) {
109 var title = TYPO3.lang.pagetree_networkErrorTitle;
110 var desc = TYPO3.lang.pagetree_networkErrorDesc;
111
112 if (error && error.target && (error.target.status || error.target.statusText)) {
113 title += ' - ' + (error.target.status || '') + ' ' + (error.target.statusText || '');
114 }
115
116 Notification.error(title, desc);
117 this.loadData();
118 };
119
120 PageTree.prototype.sendChangeCommand = function(data) {
121 var _this = this;
122 var params = '';
123
124 if (data.target) {
125 var targetUid = data.target.identifier;
126 if (data.position === 'after') {
127 targetUid = -targetUid;
128 }
129 }
130
131 if (data.command === 'new') {
132 params = '&data[pages][NEW_1][pid]=' + targetUid +
133 '&data[pages][NEW_1][title]=' + encodeURIComponent(data.name) +
134 '&data[pages][NEW_1][doktype]=' + data.type;
135
136 } else if (data.command === 'edit') {
137 params = '&data[pages][' + data.uid + '][' + data.nameSourceField + ']=' + encodeURIComponent(data.title);
138 } else {
139 if (data.command === 'delete') {
140 params = '&cmd[pages][' + data.uid + '][delete]=1';
141 } else {
142 params = 'cmd[pages][' + data.uid + '][' + data.command + ']=' + targetUid;
143 }
144 }
145
146 _this.nodesAddPlaceholder();
147
148 d3.request(top.TYPO3.settings.ajaxUrls.record_process)
149 .header('X-Requested-With', 'XMLHttpRequest')
150 .header('Content-Type', 'application/x-www-form-urlencoded')
151 .on('error', function(error) {
152 _this.errorNotification(error);
153 throw error;
154 })
155 .post(params, function(data) {
156 if (data) {
157 var response = JSON.parse(data.response);
158
159 if (response && response.hasErrors) {
160 if (response.messages) {
161 $.each(response.messages, function(id, message) {
162 Notification.error(
163 message.title,
164 message.message
165 );
166 });
167 } else {
168 _this.errorNotification();
169 }
170
171 _this.nodesContainer.selectAll('.node').remove();
172 _this.update();
173 _this.nodesRemovePlaceholder();
174 } else {
175 _this.loadData();
176 }
177 } else {
178 _this.errorNotification();
179 }
180 });
181 };
182
183 /**
184 * Observer for the selectedNode event
185 *
186 * @param {Node} node
187 */
188 PageTree.prototype.nodeSelectedAfter = function(node) {
189 var separator = '?';
190 if (currentSubScript.indexOf('?') !== -1) {
191 separator = '&';
192 }
193
194 fsMod.recentIds.web = node.identifier;
195 TYPO3.Backend.ContentContainer.setUrl(
196 currentSubScript + separator + 'id=' + node.identifier
197 );
198 };
199
200 PageTree.prototype.nodeRightClick = function(node) {
201 d3.event.preventDefault();
202 var $node = $(node).closest('svg').find('.nodes .node[data-state-id=' + this.stateIdentifier + ']');
203
204 if ($node.length) {
205 ContextMenu.show(
206 $node.data('table'),
207 this.identifier,
208 $node.data('context'),
209 $node.data('iteminfo'),
210 $node.data('parameters')
211 );
212 }
213 };
214
215 PageTree.prototype.contextmenu = function(node) {
216 var $node = $(node).closest('svg').find('.nodes .node[data-state-id=' + this.stateIdentifier + ']');
217
218 if ($node.length) {
219 ContextMenu.show(
220 $node.data('table'),
221 this.identifier,
222 $node.data('context'),
223 $node.data('iteminfo'),
224 $node.data('parameters')
225 );
226 }
227 };
228
229 PageTree.prototype.updateSvg = function(nodeEnter) {
230 nodeEnter
231 .select('use')
232 .attr('data-table', 'pages')
233 .attr('data-context', 'tree');
234 };
235
236 PageTree.prototype.hideChildren = function(node) {
237 _super_.hideChildren(node);
238 Persistent.set('BackendComponents.States.Pagetree.stateHash.' + node.stateIdentifier, 0);
239 };
240
241 PageTree.prototype.showChildren = function(node) {
242 _super_.showChildren(node);
243 Persistent.set('BackendComponents.States.Pagetree.stateHash.' + node.stateIdentifier, 1);
244 };
245
246 PageTree.prototype.updateNodeBgClass = function(nodeBg) {
247 return _super_.updateNodeBgClass.call(this, nodeBg).call(this.dragDrop.drag());
248 };
249
250 PageTree.prototype.nodesUpdate = function(nodes) {
251 var _this = this;
252
253 nodes = _super_.nodesUpdate.call(this, nodes)
254 .call(this.dragDrop.drag())
255 .attr('data-table', 'pages')
256 .attr('data-context', 'tree')
257 .on('contextmenu', function(node) {
258 _this.dispatch.call('nodeRightClick', node, this);
259 });
260
261 var nodeStop = nodes
262 .append('text')
263 .text('+')
264 .attr('class', 'node-stop')
265 .attr('dx', 30)
266 .attr('dy', 5)
267 .attr('visibility', function(node) {
268 return (node.stopPageTree && (node.depth !== 0)) ? 'visible' : 'hidden';
269 })
270 .on('click', function(node) {
271 _this.setTemporaryMountPoint(node.identifier);
272 });
273
274 return nodes;
275 };
276
277 /**
278 * Event handler for double click on a node's label
279 * Changed text position if there is 'stop page tree' option
280 *
281 * @param {Node} node
282 */
283 PageTree.prototype.appendTextElement = function(node) {
284 var _this = this;
285 var clicks = 0;
286
287 _super_.appendTextElement.call(this, node)
288 .attr('dx', function(node) {
289 var position = _this.textPosition;
290 if (node.stopPageTree && node.depth !== 0) {
291 position += 15;
292 }
293
294 if (node.locked) {
295 position += 15;
296 }
297
298 return position;
299 })
300 .on('click', function(node) {
301 if (node.identifier !== 0) {
302 clicks++;
303
304 if (clicks === 1) {
305 setTimeout(function() {
306 if (clicks === 1) {
307 _this.clickOnLabel(node, this);
308 } else {
309 _this.editNodeLabel(node);
310 }
311
312 clicks = 0;
313 }, 300);
314 }
315 } else {
316 _this.clickOnLabel(node, this);
317 }
318 });
319 };
320
321 PageTree.prototype.setTemporaryMountPoint = function(pid) {
322 var params = 'pid=' + pid;
323 var _this = this;
324
325 d3.request(top.TYPO3.settings.ajaxUrls.page_tree_set_temporary_mount_point)
326 .header('X-Requested-With', 'XMLHttpRequest')
327 .header('Content-Type', 'application/x-www-form-urlencoded')
328 .on('error', function(error) {
329 _this.errorNotification(error);
330 throw error;
331 })
332 .post(params, function(data) {
333 if (data) {
334 var response = JSON.parse(data.response);
335
336 if (response && response.hasErrors) {
337 if (response.messages) {
338 $.each(response.messages, function(id, message) {
339 Notification.error(
340 message.title,
341 message.message
342 );
343 });
344 } else {
345 _this.errorNotification();
346 }
347
348 _this.update();
349 } else {
350 _this.addMountPoint(response.mountPointPath);
351 _this.loadData();
352 }
353 } else {
354 _this.errorNotification();
355 }
356 });
357 };
358
359 PageTree.prototype.unsetTemporaryMountPoint = function() {
360 var _this = this;
361 Persistent.unset('pageTree_temporaryMountPoint').then(function() {
362 _this.refreshTree();
363 });
364 };
365
366 PageTree.prototype.sendEditNodeLabelCommand = function(node) {
367 var _this = this;
368
369 var params = '&data[pages][' + node.identifier + '][' + node.nameSourceField + ']=' + encodeURIComponent(node.newName);
370
371 //remove old node from svg tree
372 _this.nodesAddPlaceholder(node);
373
374 d3.request(top.TYPO3.settings.ajaxUrls.record_process)
375 .header('X-Requested-With', 'XMLHttpRequest')
376 .header('Content-Type', 'application/x-www-form-urlencoded')
377 .on('error', function(error) {
378 _this.errorNotification(error);
379 throw error;
380 })
381 .post(params, function(data) {
382 if (data) {
383 var response = JSON.parse(data.response);
384
385 if (response && response.hasErrors) {
386 if (response.messages) {
387 $.each(response.messages, function(id, message) {
388 Notification.error(
389 message.title,
390 message.message
391 );
392 });
393 } else {
394 _this.errorNotification();
395 }
396
397 _this.nodesAddPlaceholder();
398 _this.loadData();
399 } else {
400 node.name = node.newName;
401 _this.svg.select('.node-placeholder[data-uid="' + node.stateIdentifier + '"]').remove();
402 _this.loadData();
403 _this.nodesRemovePlaceholder();
404 }
405 } else {
406 _this.errorNotification();
407 }
408
409 });
410 };
411
412 PageTree.prototype.editNodeLabel = function(node) {
413 var _this = this;
414
415 _this.removeEditedText();
416 _this.nodeIsEdit = true;
417
418 d3.select(_this.svg.node().parentNode)
419 .append('input')
420 .attr('class', 'node-edit')
421 .style('top', function() {
422 var top = node.y + _this.settings.marginTop;
423 return top + 'px';
424 })
425 .style('left', (node.x + _this.textPosition + 5) + 'px')
426 .style('width', _this.settings.width - (node.x + _this.textPosition + 20) + 'px')
427 .style('height', _this.settings.nodeHeight + 'px')
428 .attr('type', 'text')
429 .attr('value', node.name)
430 .on('keydown', function() {
431 var code = d3.event.keyCode;
432
433 if (code === 13 || code === 9) { //enter || tab
434 var newName = this.value.trim();
435
436 if (newName.length && (newName !== node.name)) {
437 _this.nodeIsEdit = false;
438 _this.removeEditedText();
439 node.nameSourceField = node.nameSourceField || 'title';
440 node.newName = newName;
441 _this.sendEditNodeLabelCommand(node);
442 } else {
443 _this.nodeIsEdit = false;
444 _this.removeEditedText();
445 }
446 } else if (code === 27) { //esc
447 _this.nodeIsEdit = false;
448 _this.removeEditedText();
449 }
450 })
451 .on('blur', function() {
452 if (_this.nodeIsEdit) {
453 var newName = this.value.trim();
454
455 if (newName.length && (newName !== node.name)) {
456 node.nameSourceField = node.nameSourceField || 'title';
457 node.newName = newName;
458
459 _this.sendEditNodeLabelCommand(node);
460 }
461
462 _this.removeEditedText();
463 }
464 })
465 .node()
466 .select();
467 };
468
469 PageTree.prototype.removeEditedText = function() {
470 var _this = this;
471 var inputWrapper = d3.selectAll('.node-edit');
472
473 if (inputWrapper.size()) {
474 try {
475 inputWrapper.remove();
476 _this.nodeIsEdit = false;
477 } catch (e) {
478
479 }
480 }
481 };
482
483 return PageTree;
484 });