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.component.ClientBehaviors;
23  import org.apache.myfaces.tobago.context.Markup;
24  import org.apache.myfaces.tobago.internal.component.AbstractUIData;
25  import org.apache.myfaces.tobago.internal.component.AbstractUITree;
26  import org.apache.myfaces.tobago.internal.component.AbstractUITreeListbox;
27  import org.apache.myfaces.tobago.internal.component.AbstractUITreeNodeBase;
28  import org.apache.myfaces.tobago.internal.component.AbstractUITreeSelect;
29  import org.apache.myfaces.tobago.internal.renderkit.Command;
30  import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
31  import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
32  import org.apache.myfaces.tobago.internal.util.StringUtils;
33  import org.apache.myfaces.tobago.model.Selectable;
34  import org.apache.myfaces.tobago.renderkit.RendererBase;
35  import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
36  import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
37  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
38  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
39  import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
40  import org.apache.myfaces.tobago.util.ComponentUtils;
41  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  import javax.faces.component.UINamingContainer;
46  import javax.faces.context.FacesContext;
47  import java.io.IOException;
48  import java.lang.invoke.MethodHandles;
49  import java.util.Map;
50  
51  public class TreeSelectRenderer<T extends AbstractUITreeSelect> extends RendererBase<T> {
52  
53    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54  
55    @Override
56    public void decodeInternal(final FacesContext facesContext, final T component) {
57  
58      if (component.isDisabled()) {
59        return;
60      }
61  
62      final AbstractUITreeNodeBase node = ComponentUtils.findAncestor(component, AbstractUITreeNodeBase.class);
63      final AbstractUIData data = ComponentUtils.findAncestor(node, AbstractUIData.class);
64      final String clientId = component.getClientId(facesContext);
65      final String name;
66  
67      if (data.getSelectable().isSingle()) {
68        name = getClientIdWithoutRowIndex(data, clientId);
69      } else {
70        name = clientId;
71      }
72  
73      final String parameter = facesContext.getExternalContext().getRequestParameterMap().get(name);
74  
75      if (LOG.isDebugEnabled()) {
76        LOG.debug("parameter = '" + parameter + "'");
77      }
78  
79      final boolean selected = clientId.equals(parameter);
80      if (!component.isValueStoredInState()) {
81        component.setSubmittedValue(selected ? "true" : "false");
82      }
83    }
84  
85    @Override
86    public void encodeBeginInternal(final FacesContext facesContext, final T component) throws IOException {
87  
88      final AbstractUITree tree = ComponentUtils.findAncestor(component, AbstractUITree.class);
89      final AbstractUITreeNodeBase node = ComponentUtils.findAncestor(component, AbstractUITreeNodeBase.class);
90      final AbstractUIData data = ComponentUtils.findAncestor(node, AbstractUIData.class);
91  
92      final TobagoResponseWriter writer = getResponseWriter(facesContext);
93  
94      if (data instanceof AbstractUITreeListbox) {
95        writer.write(StringUtils.defaultString(component.getLabel()));
96        return;
97      }
98  
99      final String id = component.getClientId(facesContext);
100     final String currentValue = getCurrentValue(facesContext, component);
101     final boolean checked;
102     if (component.isValueStoredInState()) {
103       checked = data.getSelectedState().isSelected(node.getPath());
104     } else {
105       checked = "true".equals(currentValue);
106     }
107 
108     final boolean folder = data.isFolder();
109     final Selectable selectable = data.getSelectable();
110     final boolean showCustomControl = component.isShowCheckbox()
111         && selectable != Selectable.none && (!selectable.isLeafOnly() || !folder);
112 
113     writer.startElement(HtmlElements.TOBAGO_TREE_SELECT);
114     final Markup markup = component.getMarkup();
115     writer.writeClassAttribute(
116         component.getCustomClass(),
117         TobagoClass.TREE_SELECT.createMarkup(markup),
118         // TODO: check rendered page for other selectables. Are them looking good?
119         showCustomControl ? BootstrapClass.FORM_CHECK_INLINE : null,
120         showCustomControl && selectable.isMulti() ? BootstrapClass.FORM_CHECK : null,
121         showCustomControl && selectable.isSingle() ? BootstrapClass.FORM_CHECK : null);
122     HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
123 
124     if (showCustomControl) {
125       writer.startElement(HtmlElements.INPUT);
126       writer.writeClassAttribute(BootstrapClass.FORM_CHECK_INPUT);
127       if (selectable.isSingle()) {
128         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.RADIO);
129         writer.writeNameAttribute(getClientIdWithoutRowIndex(data, id));
130       } else {
131         writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.CHECKBOX);
132         writer.writeNameAttribute(id);
133       }
134       writer.writeAttribute(HtmlAttributes.VALUE, id, false);
135       // TODO: ID must be at the outer tag
136       writer.writeIdAttribute(id);
137       writer.writeAttribute(HtmlAttributes.CHECKED, checked);
138 
139       writer.endElement(HtmlElements.INPUT);
140     }
141 
142     final String label = component.getLabel();
143     writer.startElement(HtmlElements.LABEL);
144     writer.writeClassAttribute(TobagoClass.TREE_SELECT__LABEL,
145         showCustomControl ? BootstrapClass.FORM_CHECK_LABEL : null);
146     final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, component);
147     if (title != null) {
148       writer.writeAttribute(HtmlAttributes.TITLE, title, true);
149     }
150     writer.writeAttribute(HtmlAttributes.FOR, id, false);
151     writer.writeText(label);
152     writer.endElement(HtmlElements.LABEL);
153 
154     if (showCustomControl) {
155       final CommandMap behaviorCommands = getBehaviorCommands(facesContext, component);
156       if (behaviorCommands != null) {
157         Map<ClientBehaviors, Command> other = behaviorCommands.getOther();
158         if (other != null) {
159           Command change = other.get(ClientBehaviors.change);
160           change.setExecute(change.getExecute() + " " + tree.getClientId(facesContext));
161           change.setRender(change.getRender() + " " + tree.getClientId(facesContext));
162         }
163       }
164       encodeBehavior(writer, behaviorCommands);
165     }
166 
167     writer.endElement(HtmlElements.TOBAGO_TREE_SELECT);
168   }
169 
170   private String getClientIdWithoutRowIndex(final AbstractUIData data, final String id) {
171     final char separatorChar = UINamingContainer.getSeparatorChar(FacesContext.getCurrentInstance());
172     return id.substring(0, id.indexOf("" + separatorChar + data.getRowIndex() + separatorChar));
173   }
174 
175   @Override
176   public void encodeEndInternal(final FacesContext facesContext, final T component) throws IOException {
177   }
178 }