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.Attributes;
23  import org.apache.myfaces.tobago.internal.component.AbstractUIBadge;
24  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
25  import org.apache.myfaces.tobago.internal.component.AbstractUIFormBase;
26  import org.apache.myfaces.tobago.internal.component.AbstractUILink;
27  import org.apache.myfaces.tobago.internal.component.AbstractUISelectBooleanCheckbox;
28  import org.apache.myfaces.tobago.internal.component.AbstractUISelectManyCheckbox;
29  import org.apache.myfaces.tobago.internal.component.AbstractUISelectOneRadio;
30  import org.apache.myfaces.tobago.internal.component.AbstractUISeparator;
31  import org.apache.myfaces.tobago.internal.component.AbstractUIStyle;
32  import org.apache.myfaces.tobago.internal.util.AccessKeyLogger;
33  import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
34  import org.apache.myfaces.tobago.internal.util.RenderUtils;
35  import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
36  import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
37  import org.apache.myfaces.tobago.renderkit.css.CssItem;
38  import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
39  import org.apache.myfaces.tobago.renderkit.html.Arias;
40  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
41  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
42  import org.apache.myfaces.tobago.renderkit.html.HtmlButtonTypes;
43  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
44  import org.apache.myfaces.tobago.util.ComponentUtils;
45  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import javax.faces.component.UIComponent;
50  import javax.faces.component.UIParameter;
51  import javax.faces.context.FacesContext;
52  import java.io.IOException;
53  import java.lang.invoke.MethodHandles;
54  import java.util.ArrayList;
55  import java.util.List;
56  
57  public abstract class CommandRendererBase<T extends AbstractUICommand> extends DecodingCommandRendererBase<T> {
58  
59    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
60  
61    @Override
62    public void encodeBeginInternal(final FacesContext facesContext, final T component) throws IOException {
63  
64      final String clientId = component.getClientId(facesContext);
65      final boolean disabled = component.isDisabled();
66      final LabelWithAccessKey label = new LabelWithAccessKey(component);
67      final boolean anchor = (component.getLink() != null || component.getOutcome() != null) && !disabled;
68      final String target = component.getTarget();
69      final boolean parentOfCommands = component.isParentOfCommands();
70      final boolean dropdownSubmenu = isInside(facesContext, HtmlElements.COMMAND);
71  
72      final TobagoResponseWriter writer = getResponseWriter(facesContext);
73  
74      encodeBeginOuter(facesContext, component);
75  
76      if (anchor) {
77        writer.startElement(HtmlElements.A);
78      } else {
79        writer.startElement(HtmlElements.BUTTON);
80        writer.writeAttribute(HtmlAttributes.TYPE, HtmlButtonTypes.BUTTON);
81      }
82      writer.writeIdAttribute(component.getFieldId(facesContext));
83      writer.writeNameAttribute(clientId);
84      writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
85  
86      if (!disabled) {
87        if (anchor) {
88          final String href = RenderUtils.generateUrl(facesContext, component);
89          writer.writeAttribute(HtmlAttributes.HREF, href, true);
90          writer.writeAttribute(HtmlAttributes.TARGET, target, true);
91  
92          component.setOmit(true);
93        }
94  
95        if (label.getAccessKey() != null) {
96          writer.writeAttribute(HtmlAttributes.ACCESSKEY, Character.toString(label.getAccessKey()), false);
97          AccessKeyLogger.addAccessKey(facesContext, label.getAccessKey(), clientId);
98        }
99  
100       final int tabIndex = ComponentUtils.getIntAttribute(component, Attributes.tabIndex);
101       if (tabIndex != 0) {
102         writer.writeAttribute(HtmlAttributes.TABINDEX, tabIndex);
103       }
104     }
105 
106     HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
107 
108     if (parentOfCommands) {
109       writer.writeAttribute(DataAttributes.TOGGLE, "dropdown", false);
110     }
111     final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, component);
112     writer.writeAttribute(HtmlAttributes.TITLE, title, true);
113 
114     writer.writeClassAttribute(
115         getRendererCssClass(),
116         getRendererCssClass().createMarkup(component.getMarkup()),
117         getCssItems(facesContext, component),
118         !parentOfCommands && dropdownSubmenu ? BootstrapClass.DROPDOWN_ITEM : null,
119         parentOfCommands && !dropdownSubmenu ? BootstrapClass.DROPDOWN_TOGGLE : null,
120         component.getCustomClass(),
121         isInside(facesContext, HtmlElements.TOBAGO_LINKS) ? BootstrapClass.NAV_LINK : null);
122 
123     final boolean defaultCommand = ComponentUtils.getBooleanAttribute(component, Attributes.defaultCommand);
124     if (defaultCommand) {
125       final AbstractUIFormBase form = ComponentUtils.findAncestor(component, AbstractUIFormBase.class);
126       if (form != null) {
127         writer.writeAttribute(DataAttributes.DEFAULT, form.getClientId(facesContext), false);
128       } else {
129         LOG.warn("No from found for {}", clientId);
130       }
131     }
132 
133     if (!disabled) {
134       encodeBehavior(writer, facesContext, component);
135     }
136 
137     final String image = ComponentUtils.getStringAttribute(component, Attributes.image);
138     HtmlRendererUtils.encodeIconOrImage(writer, image);
139 
140     if (label.getLabel() != null) {
141       writer.startElement(HtmlElements.SPAN);
142       HtmlRendererUtils.writeLabelWithAccessKey(writer, label);
143       writer.endElement(HtmlElements.SPAN);
144       encodeBadge(facesContext, component);
145     }
146 
147     if (anchor) {
148       writer.endElement(HtmlElements.A);
149     } else {
150       writer.endElement(HtmlElements.BUTTON);
151     }
152   }
153 
154   @Override
155   public boolean getRendersChildren() {
156     return true;
157   }
158 
159   @Override
160   public void encodeChildrenInternal(final FacesContext facesContext, final T component) throws IOException {
161     final boolean parentOfCommands = component.isParentOfCommands();
162 
163     final TobagoResponseWriter writer = getResponseWriter(facesContext);
164 
165     if (parentOfCommands) {
166       List<UIComponent> renderLater = null;
167 
168       writer.startElement(HtmlElements.DIV);
169       writer.writeClassAttribute(BootstrapClass.DROPDOWN_MENU);
170 // fixme         isInside(facesContext, x) ? BootstrapClass.DROPDOWN_MENU_RIGHT : null);
171       writer.writeAttribute(Arias.LABELLEDBY, component.getFieldId(facesContext), false);
172       writer.writeAttribute(HtmlAttributes.NAME, component.getClientId(facesContext), false);
173 
174       for (final UIComponent child : component.getChildren()) {
175         if (child.isRendered()
176             && !(child instanceof UIParameter)
177             && !(child instanceof AbstractUIBadge)) {
178           if (child instanceof AbstractUIStyle) {
179             if (renderLater == null) {
180               renderLater = new ArrayList<>();
181             }
182             renderLater.add(child);
183           } else if (child instanceof AbstractUILink
184               || child instanceof AbstractUISelectBooleanCheckbox
185               || child instanceof AbstractUISelectManyCheckbox
186               || child instanceof AbstractUISelectOneRadio
187               || child instanceof AbstractUISeparator) {
188             insideBegin(facesContext, HtmlElements.COMMAND); // XXX may refactor / cleanup
189             child.encodeAll(facesContext);
190             insideEnd(facesContext, HtmlElements.COMMAND); // XXX may refactor / cleanup
191           } else {
192             writer.startElement(HtmlElements.DIV);
193             writer.writeClassAttribute(BootstrapClass.DROPDOWN_ITEM);
194             child.encodeAll(facesContext);
195             writer.endElement(HtmlElements.DIV);
196           }
197         }
198       }
199       writer.endElement(HtmlElements.DIV);
200 
201       if (renderLater != null) {
202         for (UIComponent child : renderLater) {
203           child.encodeAll(facesContext);
204         }
205       }
206     } else {
207       for (final UIComponent child : component.getChildren()) {
208         if (!(child instanceof AbstractUIBadge)) {
209           child.encodeAll(facesContext);
210         }
211       }
212     }
213   }
214 
215   @Override
216   public void encodeEndInternal(final FacesContext facesContext, final T component) throws IOException {
217     encodeEndOuter(facesContext, component);
218   }
219 
220   protected void encodeBeginOuter(final FacesContext facesContext, final T command) throws IOException {
221 
222     final String clientId = command.getClientId(facesContext);
223     final boolean parentOfCommands = command.isParentOfCommands();
224     final boolean dropdownSubmenu = isInside(facesContext, HtmlElements.COMMAND);
225     final TobagoResponseWriter writer = getResponseWriter(facesContext);
226 
227     if (parentOfCommands) {
228       writer.startElement(HtmlElements.TOBAGO_DROPDOWN);
229       writer.writeIdAttribute(clientId);
230 
231       writer.writeClassAttribute(
232           dropdownSubmenu ? TobagoClass.DROPDOWN__SUBMENU : BootstrapClass.DROPDOWN,
233           dropdownSubmenu ? BootstrapClass.DROPDOWN_ITEM : null);
234     }
235   }
236 
237   protected void encodeEndOuter(final FacesContext facesContext, final T command) throws IOException {
238     final TobagoResponseWriter writer = getResponseWriter(facesContext);
239     if (command.isParentOfCommands()) {
240       writer.endElement(HtmlElements.TOBAGO_DROPDOWN);
241     }
242   }
243 
244   protected CssItem[] getOuterCssItems(final FacesContext facesContext, final T command) {
245     return null;
246   }
247 
248   abstract TobagoClass getRendererCssClass();
249 
250   protected CssItem[] getCssItems(final FacesContext facesContext, final T command) {
251     return null;
252   }
253 
254   protected void encodeBadge(final FacesContext facesContext, final T command) throws IOException {
255   }
256 }