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.JQueryUtils;
46  import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
47  import org.apache.myfaces.tobago.util.ComponentUtils;
48  import org.apache.myfaces.tobago.util.CreateComponentUtils;
49  import org.apache.myfaces.tobago.util.FacetUtils;
50  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  
54  import javax.el.ValueExpression;
55  import javax.faces.application.Application;
56  import javax.faces.application.FacesMessage;
57  import javax.faces.component.UIComponent;
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 
203           writer.startElement(HtmlElements.A, tab);
204           if (!tab.isDisabled()) {
205             writer.writeAttribute(HtmlAttributes.HREF, "#", false);
206           }
207           final String tabId = tab.getClientId(facesContext);
208           writer.writeIdAttribute(tabId);
209           if (label.getAccessKey() != null) {
210             writer.writeAttribute(HtmlAttributes.ACCESSKEY, Character.toString(label.getAccessKey()), false);
211           }
212           if (label.getText() != null) {
213             HtmlRendererUtils.writeLabelWithAccessKey(writer, label);
214           } else {
215             writer.writeText(Integer.toString(index + 1));
216           }
217           writer.endElement(HtmlElements.A);
218 
219           if (label.getAccessKey() != null) {
220             if (LOG.isWarnEnabled()
221                 && !AccessKeyMap.addAccessKey(facesContext, label.getAccessKey())) {
222               LOG.warn("duplicated accessKey : " + label.getAccessKey());
223             }
224             HtmlRendererUtils.addClickAcceleratorKey(facesContext, tabId, label.getAccessKey());
225           }
226           writer.endElement(HtmlElements.DIV);
227         }
228       }
229       index++;
230     }
231     writer.endElement(HtmlElements.DIV);
232     Style body = new Style();
233     body.setWidth(width);
234     body.setHeight(tabGroup.getCurrentHeight().subtract(headerHeight));
235     writer.endElement(HtmlElements.DIV);
236     if (tabGroup.isShowNavigationBar()) {
237       UIToolBar toolBar = createToolBar(facesContext, tabGroup);
238       renderToolBar(facesContext, writer, tabGroup, toolBar);
239     }
240   }
241 
242   private UIToolBar createToolBar(FacesContext facesContext, UITabGroup tabGroup) {
243     final String clientId = tabGroup.getClientId(facesContext);
244     Application application = facesContext.getApplication();
245 
246     // previous
247     UICommand previous = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
248     previous.setRendererType(null);
249     previous.getAttributes().put(Attributes.IMAGE, "image/tabPrev.gif");
250     previous.setOnclick("/**/"); // XXX avoid submit
251     // next
252     UICommand next = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
253     next.setRendererType(null);
254     next.getAttributes().put(Attributes.IMAGE, "image/tabNext.gif");
255     next.setOnclick("/**/"); // XXX avoid submit
256 
257     // all: sub menu to select any tab directly
258     UICommand all = (UICommand) CreateComponentUtils.createComponent(
259         facesContext, UICommand.COMPONENT_TYPE, null);
260     UIMenu menu = (UIMenu) CreateComponentUtils.createComponent(
261         facesContext, UIMenu.COMPONENT_TYPE, RendererTypes.MENU);
262     FacetUtils.setDropDownMenu(all, menu);
263     int index = 0;
264     for (UIComponent child : tabGroup.getChildren()) {
265       if (child instanceof UITab) {
266         UITab tab = (UITab) child;
267         if (tab.isRendered()) {
268           UIMenuCommand entry = (UIMenuCommand) CreateComponentUtils.createComponent(
269               facesContext, UIMenuCommand.COMPONENT_TYPE, RendererTypes.MENU_COMMAND, "entry-" + index);
270           LabelWithAccessKey label = new LabelWithAccessKey(tab);
271           entry.setLabel(label.getText());
272           if (tab.isDisabled()) {
273             entry.setDisabled(true);
274           } else {
275             entry.setOnclick(JQueryUtils.selectId(clientId)
276                 + ".find('.tobago-tab[tabgroupindex=" + index + "]')"
277                 + ".click();"
278                 + "if (event.stopPropagation === undefined) { "
279                 + "  event.cancelBubble = true; " // IE
280                 + "} else { "
281                 + "  event.stopPropagation(); " // other
282                 + "}"); // todo: register a onclick handler with jQuery
283           }
284           menu.getChildren().add(entry);
285         }
286         index++;
287       }
288     }
289     UIToolBar toolBar = (UIToolBar) application.createComponent(UIToolBar.COMPONENT_TYPE);
290     toolBar.setId(facesContext.getViewRoot().createUniqueId());
291     toolBar.setRendererType("TabGroupToolBar");
292     toolBar.setTransient(true);
293     toolBar.getChildren().add(previous);
294     toolBar.getChildren().add(next);
295     toolBar.getChildren().add(all);
296     tabGroup.getFacets().put(Facets.TOOL_BAR, toolBar);
297     return toolBar;
298   }
299 
300   private void renderToolBar(
301       FacesContext facesContext, TobagoResponseWriter writer, UITabGroup tabGroup, UIToolBar toolBar)
302       throws IOException {
303     writer.startElement(HtmlElements.DIV, null);
304     writer.writeClassAttribute(Classes.create(tabGroup, "toolBar"));
305     RenderUtils.encode(facesContext, toolBar);
306     writer.endElement(HtmlElements.DIV);
307   }
308 
309   protected void encodeContent(TobagoResponseWriter writer, FacesContext facesContext, UITab tab, int index)
310       throws IOException {
311 
312     if (tab.isDisabled()) {
313       return;
314     }
315 
316     writer.startElement(HtmlElements.DIV, null);
317     writer.writeClassAttribute(Classes.create(tab, "content"));
318     writer.writeIdAttribute(tab.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "content");
319 
320     final Style style = new Style(facesContext, tab);
321     final Measure borderLeft = tab.getBorderLeft();
322     final Measure borderRight = tab.getBorderRight();
323     final Measure borderTop = tab.getBorderTop();
324     final Measure borderBottom = tab.getBorderBottom();
325     style.setWidth(style.getWidth().subtract(borderLeft).subtract(borderRight));
326     style.setHeight(style.getHeight().subtract(borderTop).subtract(borderBottom));
327     writer.writeStyleAttribute(style);
328     writer.writeAttribute(HtmlAttributes.TABGROUPINDEX, index);
329 
330     RenderUtils.encodeChildren(facesContext, tab);
331 
332     writer.endElement(HtmlElements.DIV);
333   }
334 }