1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.renderkit.renderer;
21
22 import org.apache.myfaces.tobago.context.Markup;
23 import org.apache.myfaces.tobago.internal.component.AbstractUIData;
24 import org.apache.myfaces.tobago.internal.component.AbstractUITree;
25 import org.apache.myfaces.tobago.internal.component.AbstractUITreeLabel;
26 import org.apache.myfaces.tobago.internal.component.AbstractUITreeListbox;
27 import org.apache.myfaces.tobago.internal.component.AbstractUITreeNode;
28 import org.apache.myfaces.tobago.internal.component.AbstractUITreeSelect;
29 import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
30 import org.apache.myfaces.tobago.internal.util.JsonUtils;
31 import org.apache.myfaces.tobago.model.SelectedState;
32 import org.apache.myfaces.tobago.model.TreePath;
33 import org.apache.myfaces.tobago.renderkit.RendererBase;
34 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
35 import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
36 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
37 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
38 import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
39 import org.apache.myfaces.tobago.util.ComponentUtils;
40 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import javax.faces.component.UIComponent;
45 import javax.faces.context.FacesContext;
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.List;
49
50 import static org.apache.myfaces.tobago.util.ComponentUtils.SUB_SEPARATOR;
51
52 public class TreeListboxRenderer<T extends AbstractUITreeListbox> extends RendererBase<T> {
53
54 private static final Logger LOG = LoggerFactory.getLogger(TreeListboxRenderer.class);
55
56 @Override
57 public void decodeInternal(final FacesContext facesContext, final T component) {
58 decodeState(facesContext, component);
59 }
60
61 @Override
62 public void encodeChildrenInternal(final FacesContext context, final T component) throws IOException {
63
64 }
65
66 @Override
67 public void encodeEndInternal(final FacesContext facesContext, final T component) throws IOException {
68
69 final TobagoResponseWriter writer = getResponseWriter(facesContext);
70 final String clientId = component.getClientId(facesContext);
71 final Markup markup = component.getMarkup();
72
73 writer.startElement(HtmlElements.TOBAGO_TREE_LISTBOX);
74 writer.writeIdAttribute(clientId);
75 writer.writeClassAttribute(
76 TobagoClass.TREE_LISTBOX,
77 TobagoClass.TREE_LISTBOX.createMarkup(markup));
78 HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
79 writer.writeAttribute(DataAttributes.SELECTION_MODE, component.getSelectable().name(), false);
80
81 writer.startElement(HtmlElements.INPUT);
82 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
83 writer.writeNameAttribute(clientId + SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
84 writer.writeIdAttribute(clientId + SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
85 writer.writeAttribute(HtmlAttributes.VALUE, encodeState(component), false);
86 writer.endElement(HtmlElements.INPUT);
87
88 List<Integer> thisLevel = new ArrayList<>();
89 thisLevel.add(0);
90 List<Integer> nextLevel = new ArrayList<>();
91 Integer size = component.getSize();
92 size = Math.max(size != null ? size : 10, 2);
93 int depth = component.getTreeDataModel().getDepth();
94 if (depth < 0) {
95 depth = 7;
96 LOG.warn("No depth, set to {}!", depth);
97 }
98
99
100
101 for (int level = 0; level < depth; level++) {
102
103 writer.startElement(HtmlElements.DIV);
104 writer.writeClassAttribute(TobagoClass.TREE_LISTBOX__LEVEL);
105
106
107
108
109
110
111 if (level > 0) {
112 writer.startElement(HtmlElements.SELECT);
113 writer.writeAttribute(HtmlAttributes.DISABLED, true);
114 writer.writeAttribute(HtmlAttributes.SIZE, size);
115 writer.writeClassAttribute(TobagoClass.TREE_LISTBOX__SELECT);
116 writer.endElement(HtmlElements.SELECT);
117 }
118
119 for (final Integer rowIndex : thisLevel) {
120 encodeSelectBox(facesContext, component, writer, rowIndex, nextLevel, size);
121 }
122
123 thisLevel.clear();
124 final List<Integer> swap = thisLevel;
125 thisLevel = nextLevel;
126 nextLevel = swap;
127
128 writer.endElement(HtmlElements.DIV);
129 }
130
131 writer.endElement(HtmlElements.TOBAGO_TREE_LISTBOX);
132
133 component.setRowIndex(-1);
134 }
135
136 private void encodeSelectBox(
137 final FacesContext facesContext, final AbstractUITreeListbox tree, final TobagoResponseWriter writer,
138 final int parentRowIndex, final List<Integer> foldersRowIndices, final int size)
139 throws IOException {
140
141 tree.setRowIndex(parentRowIndex);
142
143 final AbstractUITreeNode node = ComponentUtils.findDescendant(tree, AbstractUITreeNode.class);
144 final String parentId = node.getClientId(facesContext);
145
146 writer.startElement(HtmlElements.SELECT);
147 writer.writeClassAttribute(TobagoClass.TREE_LISTBOX__SELECT);
148 writer.writeIdAttribute(parentId + SUB_SEPARATOR + AbstractUITree.SUFFIX_PARENT);
149
150 writer.writeAttribute(HtmlAttributes.SIZE, size);
151
152
153 final AbstractUITreeSelect select = ComponentUtils.findDescendant(tree, AbstractUITreeSelect.class);
154 final String labelValue;
155 if (select != null) {
156 labelValue = select.getLabel();
157 } else {
158 final AbstractUITreeLabel label = ComponentUtils.findDescendant(tree, AbstractUITreeLabel.class);
159 if (label != null) {
160 labelValue = label.getLabel();
161 } else {
162 labelValue = null;
163 }
164 }
165 if (labelValue != null) {
166 writer.startElement(HtmlElements.OPTGROUP);
167 writer.writeAttribute(HtmlAttributes.LABEL, labelValue, true);
168 writer.endElement(HtmlElements.OPTGROUP);
169 }
170
171 final List<Integer> rowIndices = tree.getRowIndicesOfChildren();
172
173 for (final Integer rowIndex : rowIndices) {
174 tree.setRowIndex(rowIndex);
175 if (!tree.isRowAvailable()) {
176 break;
177 }
178
179 for (final UIComponent child : tree.getChildren()) {
180 child.encodeAll(facesContext);
181 }
182
183 if (tree.isFolder()) {
184 foldersRowIndices.add(rowIndex);
185 }
186 }
187
188 writer.endElement(HtmlElements.SELECT);
189 }
190
191 private void decodeState(final FacesContext facesContext, final AbstractUITree tree) {
192 final String hiddenInputId = tree.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR
193 + AbstractUIData.SUFFIX_SELECTED;
194 final String selectedIndicesString = facesContext.getExternalContext().getRequestParameterMap().get(hiddenInputId);
195 final List<Integer> selectedIndices = JsonUtils.decodeIntegerArray(selectedIndicesString);
196 final SelectedState selectedState = tree.getSelectedState();
197
198 final int last = tree.isRowsUnlimited() ? Integer.MAX_VALUE : tree.getFirst() + tree.getRows();
199 for (int rowIndex = tree.getFirst(); rowIndex < last; rowIndex++) {
200 tree.setRowIndex(rowIndex);
201 if (!tree.isRowAvailable()) {
202 break;
203 }
204
205 final TreePath path = tree.getPath();
206
207 if (selectedIndices != null && selectedIndices.equals(JsonUtils.decodeIntegerArray(path.toString()))) {
208 selectedState.select(path);
209 } else {
210 selectedState.unselect(path);
211 }
212 }
213 tree.setRowIndex(-1);
214 }
215
216 private String encodeState(final AbstractUITreeListbox tree) {
217 final SelectedState selectedState = tree.getSelectedState();
218
219 final int last = tree.isRowsUnlimited() ? Integer.MAX_VALUE : tree.getFirst() + tree.getRows();
220 for (int rowIndex = tree.getFirst(); rowIndex < last; rowIndex++) {
221 tree.setRowIndex(rowIndex);
222 if (!tree.isRowAvailable()) {
223 break;
224 }
225
226 final TreePath path = tree.getPath();
227
228 if (selectedState.isSelected(path)) {
229 return path.toString();
230 }
231 }
232 tree.setRowIndex(-1);
233
234 return JsonUtils.encodeEmptyArray();
235 }
236 }