001    package org.apache.myfaces.tobago.renderkit.html.scarborough.standard.tag;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.myfaces.tobago.compat.FacesUtils;
021    import org.apache.myfaces.tobago.component.Attributes;
022    import org.apache.myfaces.tobago.component.Facets;
023    import org.apache.myfaces.tobago.component.RendererTypes;
024    import org.apache.myfaces.tobago.component.UICommand;
025    import org.apache.myfaces.tobago.component.UIMenu;
026    import org.apache.myfaces.tobago.component.UIMenuCommand;
027    import org.apache.myfaces.tobago.component.UITab;
028    import org.apache.myfaces.tobago.component.UITabGroup;
029    import org.apache.myfaces.tobago.component.UIToolBar;
030    import org.apache.myfaces.tobago.context.Markup;
031    import org.apache.myfaces.tobago.event.TabChangeEvent;
032    import org.apache.myfaces.tobago.internal.component.AbstractUIPanelBase;
033    import org.apache.myfaces.tobago.internal.util.AccessKeyMap;
034    import org.apache.myfaces.tobago.layout.Measure;
035    import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
036    import org.apache.myfaces.tobago.renderkit.LayoutComponentRendererBase;
037    import org.apache.myfaces.tobago.renderkit.css.Classes;
038    import org.apache.myfaces.tobago.renderkit.css.Position;
039    import org.apache.myfaces.tobago.renderkit.css.Style;
040    import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
041    import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
042    import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
043    import org.apache.myfaces.tobago.renderkit.html.util.HtmlRendererUtils;
044    import org.apache.myfaces.tobago.renderkit.util.JQueryUtils;
045    import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
046    import org.apache.myfaces.tobago.util.ComponentUtils;
047    import org.apache.myfaces.tobago.util.CreateComponentUtils;
048    import org.apache.myfaces.tobago.util.FacetUtils;
049    import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
050    import org.slf4j.Logger;
051    import org.slf4j.LoggerFactory;
052    
053    import javax.faces.application.Application;
054    import javax.faces.application.FacesMessage;
055    import javax.faces.component.UIComponent;
056    import javax.faces.context.FacesContext;
057    import java.io.IOException;
058    import java.util.List;
059    import java.util.Map;
060    
061    public class TabGroupRenderer extends LayoutComponentRendererBase {
062    
063      private static final Logger LOG = LoggerFactory.getLogger(TabGroupRenderer.class);
064    
065      public static final String ACTIVE_INDEX_POSTFIX = ComponentUtils.SUB_SEPARATOR + "activeIndex";
066    
067      @Override
068      public void decode(FacesContext facesContext, UIComponent component) {
069        if (ComponentUtils.isOutputOnly(component)) {
070          return;
071        }
072    
073        int oldIndex = ((UITabGroup) component).getRenderedIndex();
074    
075        String clientId = component.getClientId(facesContext);
076        final Map parameters = facesContext.getExternalContext().getRequestParameterMap();
077        String newValue = (String) parameters.get(clientId + ACTIVE_INDEX_POSTFIX);
078        try {
079          int activeIndex = Integer.parseInt(newValue);
080          if (activeIndex != oldIndex) {
081            TabChangeEvent event = new TabChangeEvent(component, oldIndex, activeIndex);
082            component.queueEvent(event);
083          }
084        } catch (NumberFormatException e) {
085          LOG.error("Can't parse activeIndex: '" + newValue + "'");
086        }
087      }
088    
089      @Override
090      public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
091    
092        UITabGroup tabGroup = (UITabGroup) uiComponent;
093    
094        int activeIndex = ensureRenderedActiveIndex(facesContext, tabGroup);
095    
096        final String clientId = tabGroup.getClientId(facesContext);
097        final String hiddenId = clientId + TabGroupRenderer.ACTIVE_INDEX_POSTFIX;
098        final String switchType = tabGroup.getSwitchType();
099        TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
100    
101        writer.startElement(HtmlElements.DIV, null);
102        writer.writeIdAttribute(clientId);
103        writer.writeClassAttribute(Classes.create(tabGroup));
104        writer.writeStyleAttribute(new Style(facesContext, tabGroup));
105        writer.writeAttribute(HtmlAttributes.SWITCHTYPE, switchType, false);
106    
107        writer.startElement(HtmlElements.INPUT, null);
108        writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN, false);
109        writer.writeAttribute(HtmlAttributes.VALUE, activeIndex);
110        writer.writeNameAttribute(hiddenId);
111        writer.writeIdAttribute(hiddenId);
112        writer.endElement(HtmlElements.INPUT);
113    
114        encodeHeader(facesContext, writer, tabGroup, activeIndex);
115    
116        int index = 0;
117        for (UIComponent tab : (List<UIComponent>) tabGroup.getChildren()) {
118          if (tab instanceof UITab) {
119            if (tab.isRendered() && (UITabGroup.SWITCH_TYPE_CLIENT.equals(switchType) || index == activeIndex)) {
120              encodeContent(writer, facesContext, (UITab) tab, index);
121            }
122            index++;
123          }
124        }
125    
126        writer.endElement(HtmlElements.DIV);
127      }
128    
129      private int ensureRenderedActiveIndex(FacesContext context, UITabGroup tabGroup) {
130        int activeIndex = tabGroup.getSelectedIndex();
131        // ensure to select a rendered tab
132        int index = -1;
133        int closestRenderedTabIndex = -1;
134        for (UIComponent tab : (List<UIComponent>) tabGroup.getChildren()) {
135          index++;
136          if (tab instanceof AbstractUIPanelBase) {
137            if (index == activeIndex) {
138              if (tab.isRendered()) {
139                return index;
140              } else if (closestRenderedTabIndex > -1) {
141                break;
142              }
143            }
144            if (tab.isRendered()) {
145              closestRenderedTabIndex = index;
146              if (index > activeIndex) {
147                break;
148              }
149            }
150          }
151        }
152        if (closestRenderedTabIndex == -1) {
153          // resetting index to 0
154          closestRenderedTabIndex = 0;
155        }
156        if (FacesUtils.hasValueBindingOrValueExpression(tabGroup, Attributes.SELECTED_INDEX)) {
157          FacesUtils.setValueOfBindingOrExpression(context, closestRenderedTabIndex, tabGroup, Attributes.SELECTED_INDEX);
158        } else {
159          tabGroup.setSelectedIndex(closestRenderedTabIndex);
160        }
161        return closestRenderedTabIndex;
162      }
163    
164      private void encodeHeader(
165          FacesContext facesContext, TobagoResponseWriter writer, UITabGroup tabGroup, int activeIndex)
166          throws IOException {
167    
168        Measure width = tabGroup.getCurrentWidth();
169        Measure headerHeight = getResourceManager().getThemeMeasure(facesContext, tabGroup, "headerHeight");
170        Measure toolBarWidth = getResourceManager().getThemeMeasure(facesContext, tabGroup, "toolBarWidth");
171        Style header = new Style();
172        header.setPosition(Position.RELATIVE);
173        header.setWidth(width.subtractNotNegative(toolBarWidth));
174        header.setHeight(headerHeight);
175        writer.startElement(HtmlElements.DIV, tabGroup);
176        writer.writeClassAttribute(Classes.create(tabGroup, "header"));
177        writer.writeStyleAttribute(header);
178    
179        writer.startElement(HtmlElements.DIV, tabGroup);
180        writer.writeClassAttribute(Classes.create(tabGroup, "headerInner"));
181    
182        int index = 0;
183        for (UIComponent child : (List<UIComponent>) tabGroup.getChildren()) {
184          if (child instanceof UITab) {
185            UITab tab = (UITab) child;
186            if (tab.isRendered()) {
187              LabelWithAccessKey label = new LabelWithAccessKey(tab);
188              if (activeIndex == index) {
189                tab.setCurrentMarkup(tab.getCurrentMarkup().add(Markup.SELECTED));
190              }
191              FacesMessage.Severity maxSeverity = ComponentUtils.getMaximumSeverityOfChildrenMessages(facesContext, tab);
192              if (maxSeverity != null) {
193                tab.setCurrentMarkup(tab.getCurrentMarkup().add(ComponentUtils.markupOfSeverity(maxSeverity)));
194              }
195              writer.startElement(HtmlElements.DIV, tab);
196              writer.writeClassAttribute(Classes.create(tab));
197              writer.writeAttribute(HtmlAttributes.TABGROUPINDEX, index);
198    
199              writer.startElement(HtmlElements.A, tab);
200              if (!tab.isDisabled()) {
201                writer.writeAttribute(HtmlAttributes.HREF, "#", false);
202              }
203              final String tabId = tab.getClientId(facesContext);
204              writer.writeIdAttribute(tabId);
205              if (label.getText() != null) {
206                HtmlRendererUtils.writeLabelWithAccessKey(writer, label);
207              } else {
208                writer.writeText(Integer.toString(index + 1));
209              }
210              writer.endElement(HtmlElements.A);
211    
212              if (label.getAccessKey() != null) {
213                if (LOG.isWarnEnabled()
214                    && !AccessKeyMap.addAccessKey(facesContext, label.getAccessKey())) {
215                  LOG.warn("duplicated accessKey : " + label.getAccessKey());
216                }
217                HtmlRendererUtils.addClickAcceleratorKey(facesContext, tabId, label.getAccessKey());
218              }
219              writer.endElement(HtmlElements.DIV);
220            }
221          }
222          index++;
223        }
224        writer.endElement(HtmlElements.DIV);
225        Style body = new Style();
226        body.setWidth(width);
227        body.setHeight(tabGroup.getCurrentHeight().subtract(headerHeight));
228        writer.endElement(HtmlElements.DIV);
229        if (tabGroup.isShowNavigationBar()) {
230          UIToolBar toolBar = createToolBar(facesContext, tabGroup/*, activeIndex, switchType, tabList*/);
231          renderToolBar(facesContext, writer, tabGroup, toolBar/*, width.subtract(toolBarWidth), toolBarWidth*/);
232        }
233      }
234    
235      private UIToolBar createToolBar(FacesContext facesContext, UITabGroup tabGroup) {
236        final String clientId = tabGroup.getClientId(facesContext);
237        Application application = facesContext.getApplication();
238    
239        // previous
240        UICommand previous = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
241        previous.setRendererType(null);
242        previous.getAttributes().put(Attributes.IMAGE, "image/tabPrev.gif");
243        previous.setOnclick("/**/"); // XXX avoid submit
244        // next
245        UICommand next = (UICommand) application.createComponent(UICommand.COMPONENT_TYPE);
246        next.setRendererType(null);
247        next.getAttributes().put(Attributes.IMAGE, "image/tabNext.gif");
248        next.setOnclick("/**/"); // XXX avoid submit
249    
250        // all: sub menu to select any tab directly
251        UICommand all = (UICommand) CreateComponentUtils.createComponent(
252            facesContext, UICommand.COMPONENT_TYPE, null, "all");
253        UIMenu menu = (UIMenu) CreateComponentUtils.createComponent(
254            facesContext, UIMenu.COMPONENT_TYPE, RendererTypes.MENU, "menu");
255        FacetUtils.setDropDownMenu(all, menu);
256        int index = 0;
257        for (UIComponent child : (List<UIComponent>) tabGroup.getChildren()) {
258          if (child instanceof UITab) {
259            UITab tab = (UITab) child;
260            if (tab.isRendered()) {
261              UIMenuCommand entry = (UIMenuCommand) CreateComponentUtils.createComponent(
262                  facesContext, UIMenuCommand.COMPONENT_TYPE, RendererTypes.MENU_COMMAND, "entry-" + index);
263              LabelWithAccessKey label = new LabelWithAccessKey(tab);
264              entry.setLabel(label.getText());
265              if (tab.isDisabled()) {
266                entry.setDisabled(true);
267              } else {
268                entry.setOnclick(JQueryUtils.selectId(clientId)
269                    + ".find('.tobago-tab[tabgroupindex=" + index + "]')"
270                    + ".click();"
271                    + "if (event.stopPropagation === undefined) { "
272                    + "  event.cancelBubble = true; " // IE
273                    + "} else { "
274                    + "  event.stopPropagation(); " // other
275                    + "}"); // todo: register a onclick handler with jQuery
276              }
277              menu.getChildren().add(entry);
278            }
279            index++;
280          }
281        }
282        UIToolBar toolBar = (UIToolBar) application.createComponent(UIToolBar.COMPONENT_TYPE);
283        toolBar.setId(facesContext.getViewRoot().createUniqueId());
284        toolBar.setRendererType("TabGroupToolBar");
285        toolBar.setTransient(true);
286        toolBar.getChildren().add(previous);
287        toolBar.getChildren().add(next);
288        toolBar.getChildren().add(all);
289        tabGroup.getFacets().put(Facets.TOOL_BAR, toolBar);
290        return toolBar;
291      }
292    
293      private void renderToolBar(
294          FacesContext facesContext, TobagoResponseWriter writer, UITabGroup tabGroup, UIToolBar toolBar)
295          throws IOException {
296        writer.startElement(HtmlElements.DIV, null);
297        writer.writeClassAttribute(Classes.create(tabGroup, "toolBar"));
298        RenderUtils.encode(facesContext, toolBar);
299        writer.endElement(HtmlElements.DIV);
300      }
301    
302      protected void encodeContent(TobagoResponseWriter writer, FacesContext facesContext, UITab tab, int index)
303          throws IOException {
304    
305        if (tab.isDisabled()) {
306          return;
307        }
308    
309        writer.startElement(HtmlElements.DIV, null);
310        writer.writeClassAttribute(Classes.create(tab, "content"));
311        writer.writeIdAttribute(tab.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + "content");
312    
313        final Style style = new Style(facesContext, tab);
314        final Measure borderLeft = tab.getBorderLeft();
315        final Measure borderRight = tab.getBorderRight();
316        final Measure borderTop = tab.getBorderTop();
317        final Measure borderBottom = tab.getBorderBottom();
318        style.setWidth(style.getWidth().subtract(borderLeft).subtract(borderRight));
319        style.setHeight(style.getHeight().subtract(borderTop).subtract(borderBottom));
320        writer.writeStyleAttribute(style);
321        writer.writeAttribute(HtmlAttributes.TABGROUPINDEX, index);
322    
323        RenderUtils.encodeChildren(facesContext, tab);
324    
325        writer.endElement(HtmlElements.DIV);
326      }
327    }