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.AbstractUITreeListbox;
26  import org.apache.myfaces.tobago.internal.component.AbstractUITreeNode;
27  import org.apache.myfaces.tobago.internal.component.AbstractUITreeSelect;
28  import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
29  import org.apache.myfaces.tobago.model.Selectable;
30  import org.apache.myfaces.tobago.model.SelectedState;
31  import org.apache.myfaces.tobago.model.TreePath;
32  import org.apache.myfaces.tobago.renderkit.RendererBase;
33  import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
34  import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
35  import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
36  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
37  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
38  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
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.UINamingContainer;
45  import javax.faces.context.FacesContext;
46  import java.io.IOException;
47  import java.lang.invoke.MethodHandles;
48  import java.util.Map;
49  
50  public class TreeNodeRenderer<T extends AbstractUITreeNode> extends RendererBase<T> {
51  
52    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
53  
54    @Override
55    public void decodeInternal(final FacesContext facesContext, final T component) {
56  
57      super.decodeInternal(facesContext, component);
58  
59      if (component.isDisabled()) {
60        return;
61      }
62  
63      final AbstractUIData data = ComponentUtils.findAncestor(component, AbstractUIData.class);
64      if (data instanceof AbstractUITreeListbox) {
65        final String clientId = data.getClientId(facesContext);
66        final String nodeStateId = component.nodeStateId(facesContext);
67        final Map<String, String> requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
68        final String nodeId = component.getClientId(facesContext);
69        final boolean folder = component.isFolder();
70  
71        // expand state
72        if (folder) {
73          final boolean expanded = Boolean.parseBoolean(requestParameterMap.get(
74              nodeId + ComponentUtils.SUB_SEPARATOR + AbstractUITree.SUFFIX_EXPANDED));
75  /* XXX check
76        if (node.isExpanded() != expanded) {
77          new TreeExpansionEvent(node, node.isExpanded(), expanded).queue();
78        }
79  */
80        }
81  
82        // select
83        if (data.getSelectable() != Selectable.none) { // selection
84          String selected = requestParameterMap.get(
85              clientId + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
86  // todo        JsonUtils.decodeIntegerArray()StringArray()
87          selected = selected.replaceAll("\\[", ";");
88          selected = selected.replaceAll("]", ";");
89          selected = selected.replaceAll(",", ";");
90          final String searchString = ";" + component.getClientId(facesContext) + ";";
91          final AbstractUITreeSelect treeSelect = ComponentUtils.findDescendant(component, AbstractUITreeSelect.class);
92          if (treeSelect != null) {
93            treeSelect.setSubmittedValue(selected.contains(searchString));
94          }
95        }
96  
97        // marked
98        final String marked =
99            requestParameterMap.get(clientId + ComponentUtils.SUB_SEPARATOR + AbstractUITree.SUFFIX_MARKED);
100       if (marked != null) {
101         final String searchString = clientId + UINamingContainer.getSeparatorChar(facesContext) + nodeStateId;
102         final boolean markedValue = marked.equals(searchString);
103 /* XXX check
104       if (node.isMarked() != markedValue) {
105         new TreeMarkedEvent(node, node.isMarked(), markedValue).queue();
106       }
107 */
108       } else {
109         LOG.warn("This log message is help clarifying the occurrence of this else case.");
110       }
111     }
112   }
113 
114   @Override
115   public void encodeBeginInternal(final FacesContext facesContext, final T component) throws IOException {
116 
117     final AbstractUIData data = ComponentUtils.findAncestor(component, AbstractUIData.class);
118     final boolean dataRendersRowContainer = data.isRendersRowContainer();
119     final String clientId = component.getClientId(facesContext);
120     final String parentId = data.getRowParentClientId();
121     final boolean visible = data.isRowVisible();
122     final boolean folder = component.isFolder();
123     Markup markup = Markup.NULL;
124     final TreePath path = component.getPath();
125     final SelectedState selectedState = data.getSelectedState();
126     final boolean selected = data instanceof AbstractUITree && selectedState.isSelected(path);
127 
128     if (selected) {
129       markup = markup.add(Markup.SELECTED);
130     }
131     if (folder) {
132       markup = markup.add(Markup.FOLDER);
133       if (data.getExpandedState().isExpanded(path)) {
134         markup = markup.add(Markup.EXPANDED);
135       }
136     }
137 
138     final TobagoResponseWriter writer = getResponseWriter(facesContext);
139 
140     if (data instanceof AbstractUITreeListbox) {
141       writer.startElement(HtmlElements.OPTION);
142       // todo: define where to store the selection of a tree, node.getValue() seems not to be a god place.
143       //        writer.writeAttribute(HtmlAttributes.value, node.getValue().toString(), true); // XXX converter?
144       writer.writeAttribute(HtmlAttributes.VALUE, clientId, true);
145       writer.writeIdAttribute(clientId);
146       writer.writeAttribute(HtmlAttributes.SELECTED, selectedState.isAncestorOfSelected(path));
147       writer.writeAttribute(CustomAttributes.ROW_INDEX, data.getRowIndex());
148     } else {
149       writer.startElement(HtmlElements.TOBAGO_TREE_NODE);
150 
151       // div id
152       writer.writeIdAttribute(clientId);
153 
154       // In the case of a sheet, we need not hiding the node, because the whole TR will be hidden.
155       final boolean hidden = !dataRendersRowContainer && !visible;
156 
157       writer.writeClassAttribute(
158           null,
159           TobagoClass.TREE_NODE.createMarkup(markup),
160           hidden ? BootstrapClass.D_NONE : null,
161           component.getCustomClass());
162       writer.writeAttribute(CustomAttributes.SELECTED, selected);
163       writer.writeAttribute(CustomAttributes.EXPANDABLE, folder);
164       writer.writeAttribute(CustomAttributes.INDEX, data.getRowIndex());
165       HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
166       if (parentId != null) {
167         // TODO: replace with
168         // todo writer.writeIdAttribute(parentId + SUB_SEPARATOR + AbstractUITree.SUFFIX_PARENT);
169         // todo like in TreeListboxRenderer
170         writer.writeAttribute(DataAttributes.TREE_PARENT, parentId, false);
171         writer.writeAttribute(CustomAttributes.PARENT, parentId, false);
172       }
173       writer.writeAttribute(DataAttributes.LEVEL, data.isShowRoot() ? component.getLevel() : component.getLevel() - 1);
174     }
175   }
176 
177   @Override
178   public void encodeEndInternal(final FacesContext facesContext, final T component) throws IOException {
179     final AbstractUIData data = ComponentUtils.findAncestor(component, AbstractUIData.class);
180     final int level = component.getLevel();
181     final boolean folder = component.isFolder();
182     final boolean expanded = folder && data.getExpandedState().isExpanded(component.getPath()) || level == 0;
183 
184     final TobagoResponseWriter writer = getResponseWriter(facesContext);
185 
186     if (data instanceof AbstractUITreeListbox) {
187       if (folder) {
188         writer.writeText(" \u2192"); // this is an right arrow →
189       }
190       writer.endElement(HtmlElements.OPTION);
191     } else {
192       writer.endElement(HtmlElements.TOBAGO_TREE_NODE);
193     }
194   }
195 }