bb8ad61cb0859e03990069a1027a24fdf301a53e
[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.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]=' + 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 + ']=' + 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-uid=' + this.identifier + ']');
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-uid=' + this.identifier + ']');
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 };
234
235 PageTree.prototype.hideChildren = function (node) {
236 _super_.hideChildren(node);
237 Persistent.set('BackendComponents.States.Pagetree.stateHash.' + node.stateIdentifier, 0);
238 };
239
240 PageTree.prototype.showChildren = function (node) {
241 _super_.showChildren(node);
242 Persistent.set('BackendComponents.States.Pagetree.stateHash.' + node.stateIdentifier, 1);
243 };
244
245 PageTree.prototype.updateNodeBgClass = function (nodeBg) {
246 return _super_.updateNodeBgClass.call(this, nodeBg).call(this.dragDrop.drag());
247 };
248
249 PageTree.prototype.nodesUpdate = function (nodes) {
250 var _this = this;
251
252 nodes = _super_.nodesUpdate.call(this, nodes)
253 .call(this.dragDrop.drag());
254
255 var nodeStop = nodes
256 .append('text')
257 .text('+')
258 .attr('class', 'node-stop')
259 .attr('dx', 30)
260 .attr('dy', 5)
261 .attr('visibility', function (node) {
262 return (node.stopPageTree && (node.depth !== 0)) ? 'visible' : 'hidden';
263 })
264 .on('click', function (node) {
265 _this.setTemporaryMountPoint(node.identifier);
266 });
267
268 return nodes;
269 };
270
271 /**
272 * Event handler for double click on a node's label
273 * Changed text position if there is 'stop page tree' option
274 *
275 * @param {Node} node
276 */
277 PageTree.prototype.appendTextElement = function (node) {
278 var _this = this;
279 var clicks = 0;
280
281 _super_.appendTextElement.call(this, node)
282 .attr('dx', function (node) {
283 if (node.stopPageTree && node.depth !== 0) {
284 return _this.textPosition + 15;
285 }
286
287 return _this.textPosition;
288 })
289 .on('click', function (node) {
290 if (node.identifier !== 0) {
291 clicks++;
292
293 if (clicks === 1) {
294 setTimeout(function () {
295 if (clicks === 1) {
296 _this.clickOnLabel(node, this);
297 _this.nodeBgEvents().click(node, this);
298 _this.selectNode(node);
299 } else {
300 _this.editNodeLabel(node);
301 }
302
303 clicks = 0;
304 }, 300);
305 }
306 } else {
307 _this.clickOnLabel(node, this);
308 _this.nodeBgEvents().click(node, this);
309 _this.selectNode(node);
310 }
311 });
312 };
313
314 PageTree.prototype.setTemporaryMountPoint = function (pid) {
315 var params = 'pid=' + pid;
316 var _this = this;
317
318 d3.request(top.TYPO3.settings.ajaxUrls.page_tree_set_temporary_mount_point)
319 .header('X-Requested-With', 'XMLHttpRequest')
320 .header('Content-Type', 'application/x-www-form-urlencoded')
321 .on('error', function (error) {
322 _this.errorNotification(error);
323 throw error;
324 })
325 .post(params, function (data) {
326 if (data) {
327 var response = JSON.parse(data.response);
328
329 if (response && response.hasErrors) {
330 if (response.messages) {
331 $.each(response.messages, function (id, message) {
332 Notification.error(
333 message.title,
334 message.message
335 );
336 });
337 } else {
338 _this.errorNotification();
339 }
340
341 _this.update();
342 } else {
343 _this.addMountPoint(response.mountPointPath);
344 _this.loadData();
345 }
346 } else {
347 _this.errorNotification();
348 }
349 });
350 };
351
352 PageTree.prototype.unsetTemporaryMountPoint = function () {
353 var _this = this;
354 Persistent.unset('pageTree_temporaryMountPoint').then(function () {
355 _this.refreshTree();
356 });
357 };
358
359 PageTree.prototype.sendEditNodeLabelCommand = function (node) {
360 var _this = this;
361
362 var params = '&data[pages][' + node.identifier + '][' + node.nameSourceField + ']=' + node.newName;
363
364 //remove old node from svg tree
365 _this.nodesAddPlaceholder(node);
366
367 d3.request(top.TYPO3.settings.ajaxUrls.record_process)
368 .header('X-Requested-With', 'XMLHttpRequest')
369 .header('Content-Type', 'application/x-www-form-urlencoded')
370 .on('error', function (error) {
371 _this.errorNotification(error);
372 throw error;
373 })
374 .post(params, function (data) {
375 if (data) {
376 var response = JSON.parse(data.response);
377
378 if (response && response.hasErrors) {
379 if (response.messages) {
380 $.each(response.messages, function (id, message) {
381 Notification.error(
382 message.title,
383 message.message
384 );
385 });
386 } else {
387 _this.errorNotification();
388 }
389
390 _this.nodesAddPlaceholder();
391 _this.loadData();
392 } else {
393 node.name = node.newName;
394 _this.svg.select('.node-placeholder[data-uid="' + node.identifier + '"]').remove();
395 _this.update();
396 _this.nodesRemovePlaceholder();
397 }
398 } else {
399 _this.errorNotification();
400 }
401
402 });
403 };
404
405 PageTree.prototype.editNodeLabel = function (node) {
406 var _this = this;
407
408 _this.removeEditedText();
409 _this.nodeIsEdit = true;
410
411 d3.select(_this.svg.node().parentNode)
412 .append('input')
413 .attr('class', 'node-edit')
414 .style('top', function () {
415 var top = _this.data.nodes.indexOf(node) * _this.settings.nodeHeight;
416 top = top + 15; //svg margin top
417 return top + 'px';
418 })
419 .style('left', (node.x + _this.textPosition + 5) + 'px')
420 .style('width', _this.settings.width - (node.x + _this.textPosition + 20) + 'px')
421 .style('height', _this.settings.nodeHeight + 'px')
422 .attr('type', 'text')
423 .attr('value', node.name)
424 .on('keydown', function () {
425 var code = d3.event.keyCode;
426
427 if (code === 13 || code === 9) { //enter || tab
428 var newName = this.value.trim();
429
430 if (newName.length && (newName !== node.name)) {
431 _this.nodeIsEdit = false;
432 _this.removeEditedText();
433 node.nameSourceField = node.nameSourceField || 'title';
434 node.newName = newName;
435 _this.sendEditNodeLabelCommand(node);
436 } else {
437 _this.nodeIsEdit = false;
438 _this.removeEditedText();
439 }
440 } else if (code === 27) { //esc
441 _this.nodeIsEdit = false;
442 _this.removeEditedText();
443 }
444 })
445 .on('blur', function () {
446 if (_this.nodeIsEdit) {
447 var newName = this.value.trim();
448
449 if (newName.length && (newName !== node.name)) {
450 node.nameSourceField = node.nameSourceField || 'title';
451 node.newName = newName;
452
453 _this.sendEditNodeLabelCommand(node);
454 }
455
456 _this.removeEditedText();
457 }
458 })
459 .node()
460 .select();
461 };
462
463 PageTree.prototype.removeEditedText = function () {
464 var _this = this;
465 var inputWrapper = d3.selectAll('.node-edit');
466
467 if (inputWrapper.size()) {
468 try {
469 inputWrapper.remove();
470 _this.nodeIsEdit = false;
471 } catch (e) {
472
473 }
474 }
475 };
476
477 return PageTree;
478 });