View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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      // will be rendered in encodeEnd()
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); // must be > 1, default is 10, if not set
93      int depth = component.getTreeDataModel().getDepth();
94      if (depth < 0) {
95        depth = 7; // XXX
96        LOG.warn("No depth, set to {}!", depth);
97      }
98      // todo: use (TreeListbox ?)Layout
99  //    final Measure currentWidth = tree.getCurrentWidth();
100 //    final Measure width = currentWidth.divide(depth);
101     for (int level = 0; level < depth; level++) {
102 
103       writer.startElement(HtmlElements.DIV);
104       writer.writeClassAttribute(TobagoClass.TREE_LISTBOX__LEVEL);
105 //      final Style levelStyle = new Style();
106 //      levelStyle.setLeft(width.multiply(level));
107 //      levelStyle.setWidth(width);
108 //      writer.writeStyleAttribute(levelStyle);
109       // at the start of each div there is an empty and disabled select tag to show empty area.
110       // this is not needed for the 1st level.
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 //    writer.writeAttribute(HtmlAttributes.MULTIPLE, siblingMode);
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 }