001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package com.sun.rave.web.ui.renderer;
042:
043: import com.sun.rave.web.ui.util.ConversionUtilities;
044: import java.util.List;
045: import java.util.Iterator;
046: import java.io.IOException;
047: import java.lang.NullPointerException;
048:
049: import javax.faces.el.MethodBinding;
050: import javax.faces.context.FacesContext;
051: import javax.faces.event.ActionListener;
052: import javax.faces.component.UIComponent;
053: import javax.faces.context.ResponseWriter;
054: import javax.faces.component.UIForm;
055:
056: import com.sun.rave.web.ui.util.LogUtil;
057: import com.sun.rave.web.ui.component.Tab;
058: import com.sun.rave.web.ui.component.Icon;
059: import com.sun.rave.web.ui.component.TabSet;
060: import com.sun.rave.web.ui.component.SkipHyperlink;
061: import com.sun.rave.web.ui.theme.Theme;
062: import com.sun.rave.web.ui.theme.ThemeImages;
063: import com.sun.rave.web.ui.theme.ThemeStyles;
064: import com.sun.rave.web.ui.component.util.Util;
065: import com.sun.rave.web.ui.util.ThemeUtilities;
066: import com.sun.rave.web.ui.util.RenderingUtilities;
067:
068: /**
069: * Renders a TabSet component.
070: *
071: * @author Sean Comerford
072: */
073: public class TabSetRenderer extends AbstractRenderer {
074:
075: /** Default constructor */
076: public TabSetRenderer() {
077: super ();
078: }
079:
080: /**
081: * <p>Return a flag indicating whether this Renderer is responsible
082: * for rendering the children the component it is asked to render.
083: * The default implementation returns <code>false</code>.</p>
084: */
085: public boolean getRendersChildren() {
086: return true;
087: }
088:
089: public void renderEnd(FacesContext context, UIComponent component,
090: ResponseWriter writer) throws IOException {
091: // render any kids of the selecte tab component now
092: TabSet tabSet = (TabSet) component;
093: String selectedTabId = tabSet.getSelected();
094:
095: Theme theme = ThemeUtilities.getTheme(context);
096: String lite = theme.getStyleClass(ThemeStyles.TABGROUPBOX);
097:
098: if (selectedTabId == null) {
099: if (tabSet.isMini() && tabSet.isLite()) {
100: writer.startElement("div", tabSet); //NOI18N
101: writer.writeAttribute("class", lite, null); //NOI18N
102: writer.endElement("div"); //NOI18N
103: }
104: writer.endElement("div"); //NOI18N
105:
106: return;
107: }
108:
109: Tab selectedTab = tabSet.findChildTab(selectedTabId);
110: if (selectedTab == null) {
111: if (tabSet.isMini() && tabSet.isLite()) {
112: writer.startElement("div", tabSet); //NOI18N
113: writer.writeAttribute("class", lite, null); //NOI18N
114: writer.endElement("div"); //NOI18N
115: }
116: writer.endElement("div"); //NOI18N
117: return;
118: }
119:
120: if (tabSet.isMini() && tabSet.isLite()) {
121: writer.startElement("div", tabSet); //NOI18N
122: writer.writeAttribute("class", lite, null); //NOI18N
123: }
124:
125: while (selectedTab.hasTabChildren()) {
126: selectedTabId = selectedTab.getSelectedChildId();
127: if (selectedTabId == null) {
128: selectedTabId = ((Tab) selectedTab.getChildren().get(0))
129: .getId();
130: }
131: selectedTab = (Tab) selectedTab
132: .findComponent(selectedTabId);
133: }
134:
135: int numKids = selectedTab.getChildCount();
136: if (numKids > 0) {
137: // render the contentHeader facet if specified
138: UIComponent facet = tabSet.getFacet("contentHeader");
139: if (facet != null) {
140: RenderingUtilities.renderComponent(facet, context);
141: }
142:
143: // render the children of the selected Tab component
144: List kids = selectedTab.getChildren();
145: for (int i = 0; i < numKids; i++) {
146: UIComponent kid = (UIComponent) kids.get(i);
147: RenderingUtilities.renderComponent(kid, context);
148: }
149:
150: facet = tabSet.getFacet("contentFooter");
151: if (facet != null) {
152: RenderingUtilities.renderComponent(facet, context);
153: }
154: }
155: if (tabSet.isMini() && tabSet.isLite()) {
156: writer.endElement("div"); //NOI18N
157: }
158: writer.endElement("div"); //NOI18N
159:
160: }
161:
162: /**
163: * <p>Encode the Tab children of this TabSet component.</p>
164: *
165: * @param context The current FacesContext
166: * @param component The current TabSet component
167: */
168: public void encodeChildren(FacesContext context,
169: UIComponent component) throws IOException {
170: TabSet tabSet = (TabSet) component;
171: ResponseWriter writer = context.getResponseWriter();
172:
173: List level1 = tabSet.getChildren();
174:
175: if (level1.size() < 1) {
176: if (LogUtil.infoEnabled()) {
177: LogUtil.info(TabSetRenderer.class, "WEBUI0005",
178: new String[] { tabSet.getId() });
179: }
180: return;
181: }
182:
183: String style = tabSet.getStyle();
184: String styleClass = tabSet.getStyleClass();
185:
186: Theme theme = ThemeUtilities.getTheme(context);
187: String lite = theme.getStyleClass(ThemeStyles.TABGROUP);
188:
189: if (tabSet.isMini() && tabSet.isLite()) {
190: if (styleClass != null) {
191: styleClass = styleClass + " " + lite;
192: } else {
193: styleClass = lite;
194: }
195: }
196:
197: // <RAVE>
198: if (!tabSet.isVisible()) {
199: String hiddenStyle = theme
200: .getStyleClass(ThemeStyles.HIDDEN);
201: if (styleClass == null) {
202: styleClass = hiddenStyle;
203: } else {
204: styleClass = style + " " + hiddenStyle; //NOI18N
205: }
206: }
207: // </RAVE>
208:
209: writer.startElement("div", tabSet);
210:
211: if (style != null) {
212: writer.writeAttribute("style", style, null); // NOI18N
213: }
214: if (styleClass != null) {
215: writer.writeAttribute("class", styleClass, null); // NOI18N
216: }
217:
218: Tab selectedTab = tabSet.findChildTab(tabSet.getSelected());
219: // <RAVE>
220: // String[] args = new String[] {
221: // selectedTab != null ? selectedTab.getText() : ""
222: // };
223: String selectedTabText = "";
224: if (selectedTab != null)
225: ConversionUtilities.convertValueToString(selectedTab,
226: selectedTab.getText());
227: String[] args = new String[] { selectedTabText };
228: // </RAVE>
229:
230: // render the skip link for a11y
231: SkipHyperlink skipLink = new SkipHyperlink();
232: skipLink.setId(tabSet.getId() + "skipLink");
233: skipLink.setDescription(theme.getMessage("tab.skipTagAltText",
234: args));
235: skipLink.encodeBegin(context);
236:
237: List level2Tabs = renderLevel(context, tabSet, writer, 1,
238: tabSet.getChildren());
239:
240: // if there are any level 2 tabs render those now
241: if (level2Tabs != null) {
242: List level3Tabs = renderLevel(context, tabSet, writer, 2,
243: level2Tabs);
244:
245: // if there are any level 3 tabs render those now
246: if (level3Tabs != null) {
247: renderLevel(context, tabSet, writer, 3, level3Tabs);
248: }
249: }
250:
251: // output the bookmark for the SkipHyperlink
252: skipLink.encodeEnd(context);
253: }
254:
255: /**
256: * This method renders each of the Tab components in the given level.
257: *
258: * @param context The current FacesContext
259: * @param tabSet The current TabSet component
260: * @param writer The current ResponseWriter
261: * @param level The level (1, 2 or 3) of the Tab set to be rendered
262: * @param currentLevelTabs A List containing the Tab objects for the current
263: * level
264: */
265: protected List renderLevel(FacesContext context, TabSet tabSet,
266: ResponseWriter writer, int level, List currentLevelTabs)
267: throws IOException {
268: int numTabs = currentLevelTabs.size();
269:
270: if (numTabs == 0) {
271: // no tabs in given level
272: return null;
273: }
274:
275: Theme theme = ThemeUtilities.getTheme(context);
276: String selectedTabId = tabSet.getSelected();
277: if (selectedTabId == null) {
278: // set the first tab child as the selected
279: try {
280: selectedTabId = ((UIComponent) tabSet.getChildren()
281: .get(0)).getId();
282: tabSet.setSelected(selectedTabId);
283: } catch (Exception e) {
284: // gave it a shot but failed... no tab will be selected
285: }
286: }
287: List nextLevelToRender = null;
288:
289: // get the various level specific tab styles we'll need
290: String divStyle = "";
291: String tableStyle = "";
292: String linkStyle = "";
293: String selectedTdStyle = "";
294: String selectedTextStyle = "";
295:
296: String hidden = theme.getStyleClass(ThemeStyles.HIDDEN);
297:
298: switch (level) {
299: case 1: // get the level 1 tab styles
300: if (tabSet.isMini()) {
301: divStyle = theme
302: .getStyleClass(ThemeStyles.MINI_TAB_DIV);
303: tableStyle = theme
304: .getStyleClass(ThemeStyles.MINI_TAB_TABLE);
305: linkStyle = theme
306: .getStyleClass(ThemeStyles.MINI_TAB_LINK);
307: selectedTdStyle = theme
308: .getStyleClass(ThemeStyles.MINI_TAB_TABLE_SELECTED_TD);
309: selectedTextStyle = theme
310: .getStyleClass(ThemeStyles.MINI_TAB_SELECTED_TEXT);
311: } else {
312: divStyle = theme.getStyleClass(ThemeStyles.TAB1_DIV);
313: tableStyle = theme
314: .getStyleClass(ThemeStyles.TAB1_TABLE3_NEW);
315: linkStyle = theme.getStyleClass(ThemeStyles.TAB1_LINK);
316: selectedTdStyle = theme
317: .getStyleClass(ThemeStyles.TAB1_TABLE_SELECTED_TD);
318: selectedTextStyle = theme
319: .getStyleClass(ThemeStyles.TAB1_SELECTED_TEXT_NEW);
320: }
321: break;
322: case 2: // get the level 2 tab styles
323: divStyle = theme.getStyleClass(ThemeStyles.TAB2_DIV);
324: tableStyle = theme
325: .getStyleClass(ThemeStyles.TAB2_TABLE3_NEW);
326: linkStyle = theme.getStyleClass(ThemeStyles.TAB2_LINK);
327: selectedTdStyle = theme
328: .getStyleClass(ThemeStyles.TAB2_TABLE_SELECTED_TD);
329: selectedTextStyle = theme
330: .getStyleClass(ThemeStyles.TAB2_SELECTED_TEXT);
331: break;
332: case 3: // get the level 3 tab styles
333: divStyle = theme.getStyleClass(ThemeStyles.TAB3_DIV);
334: tableStyle = theme
335: .getStyleClass(ThemeStyles.TAB3_TABLE_NEW);
336: linkStyle = theme.getStyleClass(ThemeStyles.TAB3_LINK);
337: selectedTdStyle = theme
338: .getStyleClass(ThemeStyles.TAB3_TABLE_SELECTED_TD);
339: selectedTextStyle = theme
340: .getStyleClass(ThemeStyles.TAB3_SELECTED_TEXT);
341: break;
342: }
343:
344: writer.startElement("div", tabSet);
345: writer.writeAttribute("class", divStyle, null); // NOI18N
346: writer.startElement("table", tabSet); // NOI18N
347: writer.writeAttribute("border", "0", null); // NOI18N
348: writer.writeAttribute("cellspacing", "0", null); // NOI18N
349: writer.writeAttribute("cellpadding", "0", null); // NOI18N
350: writer.writeAttribute("class", tableStyle, null); // NOI18N
351: writer.writeAttribute("title", "", null); // NOI18N
352: writer.startElement("tr", tabSet);
353:
354: MethodBinding binding = tabSet.getActionListener();
355:
356: // render each tab in this level
357: for (int i = 0; i < numTabs; i++) {
358: Tab tab = null;
359:
360: try {
361: tab = (Tab) currentLevelTabs.get(i);
362: } catch (ClassCastException cce) {
363: // expected if a child of current Tab is not another Tab
364: continue;
365: }
366:
367: if (!tab.isRendered()) {
368: // don't render this tab
369: continue;
370: }
371:
372: // apply TabSet ActionListener to each tab that doesn't have one
373: if (binding != null && tab.getActionListener() == null) {
374: tab.setActionListener(binding);
375: }
376:
377: // each tab goes in its own table cell
378: writer.startElement("td", tabSet);
379:
380: String newSelectedClass = null;
381: String newNonSelectedClass = null;
382: if (!tab.isVisible()) {
383: newSelectedClass = selectedTdStyle + " " + hidden;
384: newNonSelectedClass = hidden;
385: } else {
386: newSelectedClass = selectedTdStyle;
387: newNonSelectedClass = null;
388: }
389:
390: if (selectedTabId != null && isSelected(tab, selectedTabId)) {
391: // ensure that the parent tab knows this one is selected
392: UIComponent parent = tab.getParent();
393:
394: if (parent != null && parent instanceof Tab) {
395: ((Tab) parent).setSelectedChildId(tab.getId());
396: }
397:
398: // this tab or one of it's children is selected
399: // <RAVE>
400: // String label = tab.getText();
401: String label = ConversionUtilities
402: .convertValueToString(tab, tab.getText());
403: // </RAVE>
404: if (label == null) {
405: label = "";
406: }
407: writer.writeAttribute("class", newSelectedClass, null); // NOI18N
408: writer.startElement("div", tabSet);
409: writer.writeAttribute("class", selectedTextStyle, null); // NOI18N
410: String titleString = theme.getMessage(
411: "tabSet.selectedTab", new Object[] { label });
412: writer.writeAttribute("title", titleString, null);
413:
414: // record the old, developer specified disabled value
415: boolean wasDisabled = tab.isDisabled();
416: // selected tab MUST be disabled
417: tab.setDisabled(true);
418: // render the selected tab
419: RenderingUtilities.renderComponent(tab, context);
420: // reset with developer specified disabled value
421: tab.setDisabled(wasDisabled);
422:
423: writer.endElement("div");
424:
425: // if selected has any Tab children, render those as next level
426: if (tab.hasTabChildren()) {
427: nextLevelToRender = tab.getChildren();
428: }
429: } else {
430: if (!tab.isVisible()) {
431: writer.writeAttribute("class", newNonSelectedClass,
432: null); // NOI18N
433: }
434: // not part of current selection
435: tab.setStyleClass(linkStyle);
436:
437: RenderingUtilities.renderComponent(tab, context);
438: }
439:
440: writer.endElement("td");
441:
442: // test if a level 3 divider needed
443: if (selectedTabId != null && level == 3 && i < numTabs - 1) {
444: Tab nextTab = (Tab) currentLevelTabs.get(i + 1);
445:
446: if (!nextTab.isRendered()) {
447: // the next tab is NOT rendered - check tab after next
448: try {
449: nextTab = (Tab) currentLevelTabs.get(i + 2);
450: } catch (IndexOutOfBoundsException e) {
451: // no more rendered Tabs
452: nextTab = null;
453: }
454: }
455:
456: if (nextTab != null
457: && !tab.getId().equals(selectedTabId)
458: && !nextTab.getId().equals(selectedTabId)) {
459: String dividerSrc = theme.getIcon(
460: ThemeImages.TAB_DIVIDER).getUrl();
461: writeDivider(tabSet, writer, dividerSrc);
462: }
463: }
464: }
465:
466: writer.endElement("tr");
467: writer.endElement("table");
468: writer.endElement("div");
469:
470: return nextLevelToRender;
471: }
472:
473: /**
474: * Helper function to write a tab dividier in a table cell
475: *
476: * @param tabSet The current TabSet component
477: * @param writer The current ResponseWriter
478: * @param src The image src to use for the tab divider
479: */
480: protected void writeDivider(TabSet tabSet, ResponseWriter writer,
481: String src) throws IOException {
482: writer.startElement("td", tabSet);
483: writer.startElement("img", tabSet);
484:
485: writer.writeAttribute("src", src, null); // NOI18N
486: writer.writeAttribute("alt", "", null); // NOI18N
487: writer.writeAttribute("border", "0", null); // NOI18N
488: writer.writeAttribute("height", "20", null); // NOI18N
489: writer.writeAttribute("width", "5", null); // NOI18N
490:
491: writer.endElement("img");
492: writer.endElement("td");
493: }
494:
495: /**
496: * Recursive function that determines if the given Tab component or any one
497: * of its descendants is the selected tab.
498: *
499: * @param tab The Tab component to check for selection
500: * @param selectedTabId The id of the currently selected Tab
501: */
502: protected boolean isSelected(Tab tab, String selectedTabId) {
503: if (tab.getId().equals(selectedTabId)) {
504: // this tab is the selected tab
505: return true;
506: }
507:
508: // check if a descendant is the selected tab
509: List subTabs = tab.getChildren();
510: boolean descendantSelected = false;
511:
512: for (int i = 0; i < subTabs.size(); i++) {
513: Tab subTab = null;
514:
515: try {
516: subTab = (Tab) subTabs.get(i);
517: } catch (ClassCastException cce) {
518: // some children may not actually be Tabs
519: continue;
520: }
521:
522: descendantSelected = isSelected(subTab, selectedTabId);
523:
524: if (descendantSelected) {
525: break;
526: }
527: }
528:
529: return descendantSelected;
530: }
531: }
|