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 }