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.renderkit.html.scarborough.standard.tag;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Facets;
24  import org.apache.myfaces.tobago.component.RendererTypes;
25  import org.apache.myfaces.tobago.component.UICommand;
26  import org.apache.myfaces.tobago.component.UIMenu;
27  import org.apache.myfaces.tobago.component.UIMenuCommand;
28  import org.apache.myfaces.tobago.component.UITab;
29  import org.apache.myfaces.tobago.component.UITabGroup;
30  import org.apache.myfaces.tobago.component.UIToolBar;
31  import org.apache.myfaces.tobago.context.Markup;
32  import org.apache.myfaces.tobago.event.TabChangeEvent;
33  import org.apache.myfaces.tobago.internal.component.AbstractUIPanelBase;
34  import org.apache.myfaces.tobago.internal.util.AccessKeyMap;
35  import org.apache.myfaces.tobago.layout.Measure;
36  import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
37  import org.apache.myfaces.tobago.renderkit.LayoutComponentRendererBase;
38  import org.apache.myfaces.tobago.renderkit.css.Classes;
39  import org.apache.myfaces.tobago.renderkit.css.Position;
40  import org.apache.myfaces.tobago.renderkit.css.Style;
41  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
42  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
43  import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
44  import org.apache.myfaces.tobago.renderkit.html.util.HtmlRendererUtils;
45  import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
46  import org.apache.myfaces.tobago.util.ComponentUtils;
47  import org.apache.myfaces.tobago.util.CreateComponentUtils;
48  import org.apache.myfaces.tobago.util.FacetUtils;
49  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  import javax.el.ValueExpression;
54  import javax.faces.application.Application;
55  import javax.faces.application.FacesMessage;
56  import javax.faces.component.UIComponent;
57  import javax.faces.component.UIViewRoot;
58  import javax.faces.context.FacesContext;
59  import java.io.IOException;
60  import java.util.List;
61  import java.util.Map;
62  
63  public class TabGroupRenderer extends LayoutComponentRendererBase {
64  
65    private static final Logger LOG = LoggerFactory.getLogger(TabGroupRenderer.class);
66  
67    public static final String ACTIVE_INDEX_POSTFIX = ComponentUtils.SUB_SEPARATOR + "activeIndex";
68  
69    @Override
70    public void decode(FacesContext facesContext, UIComponent component) {
71      if (ComponentUtils.isOutputOnly(component)) {
72        return;
73      }
74  
75      int oldIndex = ((UITabGroup) component).getRenderedIndex();
76  
77      String clientId = component.getClientId(facesContext);
78      final Map parameters = facesContext.getExternalContext().getRequestParameterMap();
79      String newValue = (String) parameters.get(clientId + ACTIVE_INDEX_POSTFIX);
80      try {
81        int activeIndex = Integer.parseInt(newValue);
82        if (activeIndex != oldIndex) {
83          TabChangeEvent event = new TabChangeEvent(component, oldIndex, activeIndex);
84          component.queueEvent(event);
85        }
86      } catch (NumberFormatException e) {
87        LOG.error("Can't parse activeIndex: '" + newValue + "'");
88      }
89    }
90  
91    @Override
92    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
93  
94      UITabGroup tabGroup = (UITabGroup) uiComponent;
95  
96      int activeIndex = ensureRenderedActiveIndex(facesContext, tabGroup);
97  
98      final String clientId = tabGroup.getClientId(facesContext);
99      final String hiddenId = clientId + TabGroupRenderer.ACTIVE_INDEX_POSTFIX;
100     final String switchType = tabGroup.getSwitchType();
101     TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
102 
103     writer.startElement(HtmlElements.DIV, null);
104     writer.writeIdAttribute(clientId);
105     writer.writeClassAttribute(Classes.create(tabGroup));
106     HtmlRendererUtils.writeDataAttributes(facesContext, writer, tabGroup);
107     writer.writeStyleAttribute(new Style(facesContext, tabGroup));
108     writer.writeAttribute(HtmlAttributes.SWITCHTYPE, switchType, false);
109 
110     writer.startElement(HtmlElements.INPUT, null);
111     writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN, false);
112     writer.writeAttribute(HtmlAttributes.VALUE, activeIndex);
113     writer.writeNameAttribute(hiddenId);
114     writer.writeIdAttribute(hiddenId);
115     writer.endElement(HtmlElements.INPUT);
116 
117     encodeHeader(facesContext, writer, tabGroup, activeIndex);
118 
119     int index = 0;
120     for (UIComponent tab : tabGroup.getChildren()) {
121       if (tab instanceof UITab) {
122         if (tab.isRendered() && (UITabGroup.SWITCH_TYPE_CLIENT.equals(switchType) || index == activeIndex)) {
123           encodeContent(writer, facesContext, (UITab) tab, index);
124         }
125         index++;
126       }
127     }
128 
129     writer.endElement(HtmlElements.DIV);
130   }
131 
132   private int ensureRenderedActiveIndex(FacesContext context, UITabGroup tabGroup) {
133     int activeIndex = tabGroup.getSelectedIndex();
134     // ensure to select a rendered tab
135     int index = -1;
136     int closestRenderedTabIndex = -1;
137     for (UIComponent tab : (List<UIComponent>) tabGroup.getChildren()) {
138       index++;
139       if (tab instanceof AbstractUIPanelBase) {
140         if (index == activeIndex) {
141           if (tab.isRendered()) {
142             return index;
143           } else if (closestRenderedTabIndex > -1) {
144             break;
145           }
146         }
147         if (tab.isRendered()) {
148           closestRenderedTabIndex = index;
149           if (index > activeIndex) {
150             break;
151           }
152         }
153       }
154     }
155     if (closestRenderedTabIndex == -1) {
156       // resetting index to 0
157       closestRenderedTabIndex = 0;
158     }
159     final ValueExpression expression = tabGroup.getValueExpression(Attributes.SELECTED_INDEX);
160     if (expression != null) {
161       expression.setValue(context.getELContext(), closestRenderedTabIndex);
162     } else {
163       tabGroup.setSelectedIndex(closestRenderedTabIndex);
164     }
165     return closestRenderedTabIndex;
166   }
167 
168   private void encodeHeader(
169       FacesContext facesContext, TobagoResponseWriter writer, UITabGroup tabGroup, int activeIndex)
170       throws IOException {
171 
172     Measure width = tabGroup.getCurrentWidth();
173     Measure headerHeight = getResourceManager().getThemeMeasure(facesContext, tabGroup, "headerHeight");
174     Measure toolBarWidth = getResourceManager().getThemeMeasure(facesContext, tabGroup, "toolBarWidth");
175     Style header = new Style();
176     header.setPosition(Position.RELATIVE);
177     header.setWidth(width.subtractNotNegative(toolBarWidth));
178     header.setHeight(headerHeight);
179     writer.startElement(HtmlElements.DIV, tabGroup);
180     writer.writeClassAttribute(Classes.create(tabGroup, "header"));
181     writer.writeStyleAttribute(header);
182 
183     writer.startElement(HtmlElements.DIV, tabGroup);
184     writer.writeClassAttribute(Classes.create(tabGroup, "headerInner"));
185 
186     int index = 0;
187     for (UIComponent child : tabGroup.getChildren()) {
188       if (child instanceof UITab) {
189         UITab tab = (UITab) child;
190         if (tab.isRendered()) {
191           LabelWithAccessKey label = new LabelWithAccessKey(tab);
192           if (activeIndex == index) {
193             ComponentUtils.addCurrentMarkup(tab, Markup.SELECTED);
194           }
195           FacesMessage.Severity maxSeverity = ComponentUtils.getMaximumSeverityOfChildrenMessages(facesContext, tab);
196           if (maxSeverity != null) {
197             ComponentUtils.addCurrentMarkup(tab, ComponentUtils.markupOfSeverity(maxSeverity));
198           }
199           writer.startElement(HtmlElements.DIV, tab);
200           writer.writeClassAttribute(Classes.create(tab));
201           writer.writeAttribute(HtmlAttributes.TABGROUPINDEX, index);
202           String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, tab);
203           if (title != null) {
204             writer.writeAttribute(HtmlAttributes.TITLE, title, true);
205           }
206 
207           writer.startElement(HtmlElements.A, tab);
208           if (!tab.isDisabled()) {
209             writer.writeAttribute(HtmlAttributes.HREF, "#", false);
210           }
211           final String tabId = tab.getClientId(facesContext);
212           writer.writeIdAttribute(tabId);
213           if (label.getAccessKey() != null) {
214             writer.writeAttribute(HtmlAttributes.ACCESSKEY, Character.toString(label.getAccessKey()), false);
215           }
216           if (label.getText() != null) {
217             HtmlRendererUtils.writeLabelWithAccessKey(writer, label);
218           } else {
219             writer.writeText(Integer.toString(index + 1));
220           }
221           writer.endElement(HtmlElements.A);
222 
223           if (label.getAccessKey() != null) {
224             if (LOG.isWarnEnabled()
225                 && !AccessKeyMap.addAccessKey(facesContext, label.getAccessKey())) {
226               LOG.warn("duplicated accessKey : " + label.getAccessKey());
227             }
228             HtmlRendererUtils.addClickAcceleratorKey(facesContext, tabId, label.getAccessKey());
229           }
230           writer.endElement(HtmlElements.DIV);
231         }
232       }
233       index++;
234     }
235     writer.endElement(HtmlElements.DIV);
236     Style body = new Style();
237     body.setWidth(width);
238     body.setHeight(tabGroup.getCurrentHeight().subtract(headerHeight));
239     writer.endElement(HtmlElements.DIV);
240     if (tabGroup.isShowNavigationBar()) {
241       UIToolBar toolBar = createToolBar(facesContext, tabGroup);
242       renderToolBar(facesContext, writer, tabGroup, toolBar);
243     }
244   }
245 
246   private UIToolBar createToolBar(FacesContext facesContext, UITabGroup tabGroup) {
247     final String clientId = tabGroup.getClientId(facesContext);
248     Application application = facesContext.getApplication();
249     UIViewRoot viewRoot = facesContext.getViewRoot();
250 
251     // previous
252     UICommand previous = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
253     previous.setId(viewRoot.createUniqueId());
254     previous.setRendererType(null);
255     previous.getAttributes().put(Attributes.IMAGE, "image/tabPrev.gif");
256     previous.setOmit(true); // avoid submit
257     // next
258     UICommand next = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
259     next.setId(viewRoot.createUniqueId());
260     next.setRendererType(null);
261     next.getAttributes().put(Attributes.IMAGE, "image/tabNext.gif");
262     next.setOmit(true); // avoid submit
263 
264     // all: sub menu to select any tab directly
265     UICommand all = (UICommand) CreateComponentUtils.createComponent(
266         facesContext, UICommand.COMPONENT_TYPE, null, viewRoot.createUniqueId());
267     all.setOmit(true); // avoid submit
268 
269     UIMenu menu = (UIMenu) CreateComponentUtils.createComponent(
270         facesContext, UIMenu.COMPONENT_TYPE, RendererTypes.MENU, viewRoot.createUniqueId());
271     menu.setTransient(true);
272     ComponentUtils.addCurrentMarkup(menu, Markup.TOP);
273     FacetUtils.setDropDownMenu(all, menu);
274     int index = 0;
275     for (UIComponent child : tabGroup.getChildren()) {
276       if (child instanceof UITab) {
277         UITab tab = (UITab) child;
278         if (tab.isRendered()) {
279           UIMenuCommand entry = (UIMenuCommand) CreateComponentUtils.createComponent(
280               facesContext, UIMenuCommand.COMPONENT_TYPE, RendererTypes.MENU_COMMAND, viewRoot.createUniqueId());
281           entry.setTransient(true);
282           entry.setOmit(true); // avoid submit
283           LabelWithAccessKey label = new LabelWithAccessKey(tab);
284           entry.setLabel(label.getText());
285           if (tab.isDisabled()) {
286             entry.setDisabled(true);
287           } else {
288             ComponentUtils.putDataAttribute(entry, "tobago-index", index); // XXX todo, define a DataAttribute
289           }
290           menu.getChildren().add(entry);
291         }
292         index++;
293       }
294     }
295     UIToolBar toolBar = (UIToolBar) application.createComponent(UIToolBar.COMPONENT_TYPE);
296     toolBar.setId(viewRoot.createUniqueId());
297     toolBar.setRendererType("TabGroupToolBar");
298     toolBar.setTransient(true);
299     toolBar.getChildren().add(previous);
300     toolBar.getChildren().add(next);
301     toolBar.getChildren().add(all);
302     tabGroup.getFacets().put(Facets.TOOL_BAR, toolBar);
303     return toolBar;
304   }
305 
306   private void renderToolBar(
307       FacesContext facesContext, TobagoResponseWriter writer, UITabGroup tabGroup, UIToolBar toolBar)
308       throws IOException {
309     writer.startElement(HtmlElements.DIV, null);
310     writer.writeClassAttribute(Classes.create(tabGroup, "toolBar"));
311     RenderUtils.encode(facesContext, toolBar);
312     writer.endElement(HtmlElements.DIV);
313   }
314 
315   protected void encodeContent(TobagoResponseWriter writer, FacesContext facesContext, UITab tab, int index)
316       throws IOException {
317 
318     if (tab.isDisabled()) {
319       return;
320     }
321 
322     writer.startElement(HtmlElements.DIV, null);
323     writer.writeClassAttribute(Classes.create(tab, "content"));
324     writer.writeIdAttribute(tab.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "content");
325 
326     final Style style = new Style(facesContext, tab);
327     final Measure borderLeft = tab.getBorderLeft();
328     final Measure borderRight = tab.getBorderRight();
329     final Measure borderTop = tab.getBorderTop();
330     final Measure borderBottom = tab.getBorderBottom();
331     style.setWidth(style.getWidth().subtract(borderLeft).subtract(borderRight));
332     style.setHeight(style.getHeight().subtract(borderTop).subtract(borderBottom));
333     writer.writeStyleAttribute(style);
334     writer.writeAttribute(HtmlAttributes.TABGROUPINDEX, index);
335 
336     RenderUtils.encodeChildren(facesContext, tab);
337 
338     writer.endElement(HtmlElements.DIV);
339   }
340 }