a20c7221faf1fc0a8de412deae899d49782a5277
[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', 'TYPO3/CMS/Backend/FormEngine/Element/SvgTree'], function (d3, SvgTree) {
19 'use strict';
20
21 /**
22 * @constructor
23 * @exports TYPO3/CMS/Backend/FormEngine/Element/SelectTree
24 */
25 var SelectTree = function () {
26 SvgTree.call(this);
27 this.settings.showCheckboxes = true;
28 };
29
30 SelectTree.prototype = Object.create(SvgTree.prototype);
31 var _super_ = SvgTree.prototype;
32
33 /**
34 * SelectTree initialization
35 *
36 * @param {String} selector
37 * @param {Object} settings
38 */
39 SelectTree.prototype.initialize = function (selector, settings) {
40 if (!_super_.initialize.call(this, selector, settings)) {
41 return false;
42 }
43 this.addIcons();
44 this.dispatch.on('updateNodes.selectTree', this.updateNodes);
45 this.dispatch.on('loadDataAfter.selectTree', this.loadDataAfter);
46 this.dispatch.on('updateSvg.selectTree', this.renderCheckbox);
47 this.dispatch.on('nodeSelectedAfter.selectTree', this.nodeSelectedAfter);
48 return true;
49 };
50
51 /**
52 * Function relays on node.indeterminate state being up to date
53 *
54 * @param {Selection} nodeSelection
55 */
56 SelectTree.prototype.updateNodes = function (nodeSelection) {
57 var me = this;
58 if (this.settings.showCheckboxes) {
59 nodeSelection
60 .selectAll('.tree-check use')
61 .attr('visibility', function (node) {
62 var checked = Boolean(node.data.checked);
63 if (d3.select(this).classed('icon-checked') && checked) {
64 return 'visible';
65 } else if (d3.select(this).classed('icon-indeterminate') && node.indeterminate && !checked) {
66 return 'visible';
67 } else if (d3.select(this).classed('icon-check') && !node.indeterminate && !checked) {
68 return 'visible';
69 } else {
70 return 'hidden';
71 }
72 });
73 }
74 };
75
76 /**
77 * Adds svg elements for checkbox rendering.
78 *
79 * @param {Selection} nodeSelection ENTER selection (only new DOM objects)
80 */
81 SelectTree.prototype.renderCheckbox = function (nodeSelection) {
82 var me = this;
83 if (this.settings.showCheckboxes) {
84 this.textPosition = 50;
85 //this can be simplified to single "use" element with changing href on click when we drop IE11 on WIN7 support
86 var g = nodeSelection.filter(function (node) {
87 //do not render checkbox if node is not selectable
88 return me.isNodeSelectable(node) || Boolean(node.data.checked);
89 })
90 .append('g')
91 .attr('class', 'tree-check')
92 .on('click', function (d) {
93 me.selectNode(d);
94 });
95 g.append('use')
96 .attr('x', 28)
97 .attr('y', -8)
98 .attr('visibility', 'hidden')
99 .attr('class', 'icon-check')
100 .attr('xlink:href', '#icon-check');
101 g.append('use')
102 .attr('x', 28)
103 .attr('y', -8)
104 .attr('visibility', 'hidden')
105 .attr('class', 'icon-checked')
106 .attr('xlink:href', '#icon-checked');
107 g.append('use')
108 .attr('x', 28)
109 .attr('y', -8)
110 .attr('visibility', 'hidden')
111 .attr('class', 'icon-indeterminate')
112 .attr('xlink:href', '#icon-indeterminate');
113 }
114 };
115
116 /**
117 * Does not modify the data, just checking with early return
118 *
119 * @param {Node} node
120 */
121 SelectTree.prototype.hasCheckedOrIndeterminateChildren = function (node) {
122 if (!node.children) {
123 return false;
124 }
125
126 return node.children.some(function (child) {
127 if (child.data.checked || child.indeterminate) {
128 return true;
129 }
130 });
131 };
132
133 /**
134 * Updates the indeterminate state for ancestors of the current node
135 *
136 * @param {Node} node
137 */
138 SelectTree.prototype.updateAncestorsIndetermineState = function (node) {
139 var me = this;
140 //foreach ancestor except node itself
141 node.ancestors().slice(1).forEach(function (n) {
142 n.indeterminate = (node.data.checked || node.indeterminate) ? true : me.hasCheckedOrIndeterminateChildren(n);
143 });
144 };
145
146
147 /**
148 * Resets the node.indeterminate for the whole tree
149 * It's done once after loading data. Later indeterminate state is updated just for the subset of nodes
150 */
151 SelectTree.prototype.loadDataAfter = function () {
152 this.rootNode.each(function (node) {
153 node.indeterminate = false;
154 });
155 this.calculateIndeterminate(this.rootNode);
156 // Initialise "value" attribute of input field after load and revalidate form engine fields
157 this.saveCheckboxes(this.rootNode);
158 if (typeof TYPO3.FormEngine.Validation !== 'undefined' && typeof TYPO3.FormEngine.Validation.validate === 'function') {
159 TYPO3.FormEngine.Validation.validate();
160 }
161 };
162
163 /**
164 * Sets indeterminate state for a subtree. It relays on the tree to have indeterminate state reset beforehand.
165 *
166 * @param {Node} node
167 */
168 SelectTree.prototype.calculateIndeterminate = function (node) {
169 if (!node.children) {
170 node.indeterminate = false;
171 return;
172 }
173
174 node.eachAfter(function (n) {
175 if ((n.data.checked || n.indeterminate) && n.parent) {
176 n.parent.indeterminate = true;
177 }
178 })
179 };
180
181 /**
182 * Observer for the selectedNode event
183 *
184 * @param {Node} node
185 */
186 SelectTree.prototype.nodeSelectedAfter = function (node) {
187 this.updateAncestorsIndetermineState(node);
188 this.saveCheckboxes(node);
189 };
190
191 /**
192 * Sets a comma-separated list of selected nodes identifiers to configured input
193 *
194 * @param {Node} node
195 */
196 SelectTree.prototype.saveCheckboxes = function (node) {
197 if (typeof this.settings.input !== 'undefined') {
198 var selectedNodes = this.getSelectedNodes();
199 this.settings.input.val(selectedNodes.map(function (d) {
200 return d.data.identifier
201 }));
202 }
203 };
204
205 /**
206 * Add icons imitating checkboxes
207 */
208 SelectTree.prototype.addIcons = function () {
209
210 var iconsData = [
211 {
212 identifier: 'icon-check',
213 icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">' +
214 '<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>'
215 },
216 {
217 identifier: 'icon-checked',
218 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>'
219 },
220 {
221 identifier: 'icon-indeterminate',
222 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>'
223 }
224 ];
225
226 var icons = this.iconsContainer
227 .selectAll('.icon-def')
228 .data(iconsData);
229 icons
230 .enter()
231 .append('g')
232 .attr('class', 'icon-def')
233 .attr('id', function (i) {
234 return i.identifier;
235 })
236 .append(function (i) {
237 //workaround for IE11 where you can't simply call .html(content) on svg
238 var parser = new DOMParser();
239 var dom = parser.parseFromString(i.icon, "image/svg+xml");
240 return dom.documentElement;
241 });
242
243 };
244
245 return SelectTree;
246 });