6ea3ef7627d2ada92b37859cbec823f2f5844e8f
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / FormEngine / Element / SelectTree.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/FormEngine/Element/SelectTree
16 * Logic for SelectTree
17 */
18 define(['d3',
19 'TYPO3/CMS/Backend/FormEngine/Element/SvgTree',
20 'TYPO3/CMS/Backend/FormEngine'
21 ], function (d3, SvgTree) {
22 'use strict';
23
24 /**
25 * @constructor
26 * @exports TYPO3/CMS/Backend/FormEngine/Element/SelectTree
27 */
28 var SelectTree = function () {
29 SvgTree.call(this);
30 this.settings.showCheckboxes = true;
31 };
32
33 SelectTree.prototype = Object.create(SvgTree.prototype);
34 var _super_ = SvgTree.prototype;
35
36 /**
37 * SelectTree initialization
38 *
39 * @param {String} selector
40 * @param {Object} settings
41 */
42 SelectTree.prototype.initialize = function (selector, settings) {
43 if (!_super_.initialize.call(this, selector, settings)) {
44 return false;
45 }
46 this.addIcons();
47 this.dispatch.on('updateNodes.selectTree', this.updateNodes);
48 this.dispatch.on('loadDataAfter.selectTree', this.loadDataAfter);
49 this.dispatch.on('updateSvg.selectTree', this.renderCheckbox);
50 this.dispatch.on('nodeSelectedAfter.selectTree', this.nodeSelectedAfter);
51 return true;
52 };
53
54 /**
55 * Function relays on node.indeterminate state being up to date
56 *
57 * @param {Selection} nodeSelection
58 */
59 SelectTree.prototype.updateNodes = function (nodeSelection) {
60 var me = this;
61 if (this.settings.showCheckboxes) {
62 nodeSelection
63 .selectAll('.tree-check use')
64 .attr('visibility', function (node) {
65 var checked = Boolean(node.checked);
66 if (d3.select(this).classed('icon-checked') && checked) {
67 return 'visible';
68 } else if (d3.select(this).classed('icon-indeterminate') && node.indeterminate && !checked) {
69 return 'visible';
70 } else if (d3.select(this).classed('icon-check') && !node.indeterminate && !checked) {
71 return 'visible';
72 } else {
73 return 'hidden';
74 }
75 });
76 }
77 };
78
79 /**
80 * Adds svg elements for checkbox rendering.
81 *
82 * @param {Selection} nodeSelection ENTER selection (only new DOM objects)
83 */
84 SelectTree.prototype.renderCheckbox = function (nodeSelection) {
85 var me = this;
86 if (this.settings.showCheckboxes) {
87 this.textPosition = 50;
88 //this can be simplified to single "use" element with changing href on click when we drop IE11 on WIN7 support
89 var g = nodeSelection.filter(function (node) {
90 //do not render checkbox if node is not selectable
91 return me.isNodeSelectable(node) || Boolean(node.checked);
92 })
93 .append('g')
94 .attr('class', 'tree-check')
95 .on('click', function (d) {
96 me.selectNode(d);
97 });
98 g.append('use')
99 .attr('x', 28)
100 .attr('y', -8)
101 .attr('visibility', 'hidden')
102 .attr('class', 'icon-check')
103 .attr('xlink:href', '#icon-check');
104 g.append('use')
105 .attr('x', 28)
106 .attr('y', -8)
107 .attr('visibility', 'hidden')
108 .attr('class', 'icon-checked')
109 .attr('xlink:href', '#icon-checked');
110 g.append('use')
111 .attr('x', 28)
112 .attr('y', -8)
113 .attr('visibility', 'hidden')
114 .attr('class', 'icon-indeterminate')
115 .attr('xlink:href', '#icon-indeterminate');
116 }
117 };
118
119 /**
120 * Updates the indeterminate state for ancestors of the current node
121 *
122 * @param {Node} node
123 */
124 SelectTree.prototype.updateAncestorsIndetermineState = function (node) {
125 var me = this;
126 //foreach ancestor except node itself
127 var indeterminate = false;
128 node.parents.forEach(function (index) {
129 var n = me.nodes[index];
130 n.indeterminate = (node.checked || node.indeterminate || indeterminate);
131 // check state for the next level
132 indeterminate = (node.checked || node.indeterminate || n.checked || n.indeterminate)
133 });
134 };
135
136
137 /**
138 * Resets the node.indeterminate for the whole tree
139 * It's done once after loading data. Later indeterminate state is updated just for the subset of nodes
140 */
141 SelectTree.prototype.loadDataAfter = function () {
142 this.nodes.forEach(function (node) {
143 node.indeterminate = false;
144 });
145 this.calculateIndeterminate(this.nodes);
146 // Initialise "value" attribute of input field after load and revalidate form engine fields
147 this.saveCheckboxes(this.nodes);
148 if (typeof TYPO3.FormEngine.Validation !== 'undefined' && typeof TYPO3.FormEngine.Validation.validate === 'function') {
149 TYPO3.FormEngine.Validation.validate();
150 }
151 };
152
153 /**
154 * Sets indeterminate state for a subtree. It relays on the tree to have indeterminate state reset beforehand.
155 *
156 * @param {Array} nodes
157 */
158 SelectTree.prototype.calculateIndeterminate = function (nodes) {
159 nodes.forEach(function(node) {
160 if ((node.checked || node.indeterminate) && node.parents && node.parents.length > 0) {
161 node.parents.forEach(function(parentNodeIndex) {
162 nodes[parentNodeIndex].indeterminate = true;
163 })
164 }
165 })
166 };
167
168 /**
169 * Observer for the selectedNode event
170 *
171 * @param {Node} node
172 */
173 SelectTree.prototype.nodeSelectedAfter = function (node) {
174 this.updateAncestorsIndetermineState(node);
175 // check all nodes again, to ensure correct display of indeterminate state
176 this.calculateIndeterminate(this.nodes);
177 this.saveCheckboxes(node);
178 };
179
180 /**
181 * Sets a comma-separated list of selected nodes identifiers to configured input
182 *
183 * @param {Node} node
184 */
185 SelectTree.prototype.saveCheckboxes = function (node) {
186 if (typeof this.settings.input !== 'undefined') {
187 var selectedNodes = this.getSelectedNodes();
188 this.settings.input.val(selectedNodes.map(function (d) {
189 return d.identifier
190 }));
191 }
192 };
193
194 /**
195 * Add icons imitating checkboxes
196 */
197 SelectTree.prototype.addIcons = function () {
198
199 this.data.icons = {
200 'check': {
201 identifier: 'check',
202 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">' +
203 '<rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
204 },
205 'checked': {
206 identifier: 'checked',
207 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"></path></g>'
208 },
209 'indeterminate': {
210 identifier: 'indeterminate',
211 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1344 800v64q0 14-9 23t-23 9h-832q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
212 }
213 };
214 };
215
216 return SelectTree;
217 });