All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.icefaces.ace.component.tabset.TabSetRenderer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2014 ICEsoft Technologies Canada Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS
 * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package org.icefaces.ace.component.tabset;

import java.io.IOException;
import java.util.*;

import javax.faces.application.ProjectStage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.ValueChangeEvent;

import org.icefaces.ace.renderkit.CoreRenderer;
import org.icefaces.ace.util.ARIA;
import org.icefaces.ace.util.HTML;
import org.icefaces.ace.util.JSONBuilder;
import org.icefaces.ace.util.ScriptWriter;
import org.icefaces.ace.util.Utils;
import org.icefaces.impl.util.DOMUtils;
import org.icefaces.util.EnvUtils;
import org.icefaces.render.MandatoryResourceComponent;

@MandatoryResourceComponent(tagName="tabSet", value="org.icefaces.ace.component.tabset.TabSet")
public class TabSetRenderer extends CoreRenderer {
    private static final String YUI_TABSET_INDEX = "yti";
    private static final Random RANDOM = new Random();
    
    public boolean getRendersChildren() {
        return true;
    }

    public void decode(FacesContext facesContext, UIComponent uiComponent) {
        Integer index = decodeSelectedIndex(facesContext, uiComponent);
        if (index != null) {
            TabSet tabSet = (TabSet) uiComponent;
            int old = tabSet.getSelectedIndex();
            if (old != index.intValue()) {
                uiComponent.queueEvent(new ValueChangeEvent (uiComponent,
                        new Integer(old), index));
            }
        }
        Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
		String proxyClientId = uiComponent.getClientId(facesContext) + "_tsc";
		Object source = requestParameterMap.get("ice.event.captured");
		if (source != null && proxyClientId.equals(source.toString())) {
			decodeBehaviors(facesContext, uiComponent, proxyClientId);
		} else {
			decodeBehaviors(facesContext, uiComponent);
		}
    }

    /**
     * @return Integer if TabSet's selectedIndex has been sent, otherwise null
     */
    protected Integer decodeSelectedIndex(FacesContext facesContext,
            UIComponent uiComponent) {
        Integer index = null;
        Map requestParameterMap = facesContext.getExternalContext().
                getRequestParameterMap();
        //one field per form will be use to send tabindex info
        if (requestParameterMap.containsKey(YUI_TABSET_INDEX)) {
        	//the value of yti should look something like this "clientId=tabindex"
            String param = String.valueOf(requestParameterMap.get(
                    YUI_TABSET_INDEX));
            String[] info = param.split("=");
            String clientId = uiComponent.getClientId(facesContext);
            //info[0] is containing a clientId of a tabset
            if (clientId.equals(info[0])) {
                try {
                	//info[1] is containing a tabindex
                    index = Integer.parseInt(info[1]);
                } catch(NumberFormatException e) {}
            }
        }
        return index;
    }
    
    public void encodeBegin(FacesContext facesContext, UIComponent uiComponent)
    throws IOException {
        ResponseWriter writer = facesContext.getResponseWriter();
        TabSet tabSet = (TabSet) uiComponent;
        String clientId = uiComponent.getClientId(facesContext);
        //tabset's toot div
        writer.startElement(HTML.DIV_ELEM, uiComponent);
        writer.writeAttribute(HTML.ID_ATTR, clientId, HTML.ID_ATTR);
        String style = tabSet.getStyle();
        if(style != null){
        	writer.writeAttribute(HTML.STYLE_ATTR, style, HTML.STYLE_ATTR);
        }        
    }
    
    public void encodeChildren(FacesContext facesContext, UIComponent uiComponent)
    throws IOException {
        String clientId = uiComponent.getClientId(facesContext);
        ResponseWriter writer = facesContext.getResponseWriter();
        TabSet tabSet = (TabSet) uiComponent;
        String orientation = tabSet.getOrientation();
        orientation = orientation == null || "".equals(orientation) ? "top" : orientation;
		boolean scrollableTabs = tabSet.isScrollableTabs();
        
        //As per YUI's contract if the orientation is set to bottom, the contents of the tab
        //should ger rendered first, and then tabs
        if ("bottom".equals(orientation)) {
            writer.startElement(HTML.DIV_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"cnt", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-content ui-tabs-panel ui-widget-content ui-corner-top", HTML.CLASS_ATTR);
            writer.endElement(HTML.DIV_ELEM);
        
			if (scrollableTabs) {
				writer.startElement(HTML.DIV_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable", HTML.CLASS_ATTR);
				writer.startElement(HTML.SPAN_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable-left", HTML.CLASS_ATTR);
				writer.endElement(HTML.SPAN_ELEM);
				writer.startElement(HTML.DIV_ELEM, null);
			}
            writer.startElement(HTML.UL_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"_nav", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-nav ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all", HTML.CLASS_ATTR);
                if (EnvUtils.isAriaEnabled(facesContext)) {
                    writer.writeAttribute(ARIA.ROLE_ATTR, ARIA.TABLIST_ROLE, ARIA.ROLE_ATTR);  
                }
                doTabs(facesContext, uiComponent, Do.RENDER_LABEL, null, null, null);
            writer.endElement(HTML.UL_ELEM);
			if (scrollableTabs) {
				writer.endElement(HTML.DIV_ELEM);
				writer.startElement(HTML.SPAN_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable-right", HTML.CLASS_ATTR);
				writer.endElement(HTML.SPAN_ELEM);
				writer.endElement(HTML.DIV_ELEM);
			}
              
        } else if ("top".equals(orientation)) {
			if (scrollableTabs) {
				writer.startElement(HTML.DIV_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable", HTML.CLASS_ATTR);
				writer.startElement(HTML.SPAN_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable-left", HTML.CLASS_ATTR);
				writer.endElement(HTML.SPAN_ELEM);
				writer.startElement(HTML.DIV_ELEM, null);
			}
            writer.startElement(HTML.UL_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"_nav", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-nav ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all", HTML.CLASS_ATTR);
                if (EnvUtils.isAriaEnabled(facesContext)) {
                    writer.writeAttribute(ARIA.ROLE_ATTR, ARIA.TABLIST_ROLE, ARIA.ROLE_ATTR);  
                }                
                doTabs(facesContext, uiComponent, Do.RENDER_LABEL, null, null, null);
            writer.endElement(HTML.UL_ELEM);
			if (scrollableTabs) {
				writer.endElement(HTML.DIV_ELEM);
				writer.startElement(HTML.SPAN_ELEM, null);
				writer.writeAttribute(HTML.CLASS_ATTR, "ui-tabs-scrollable-right", HTML.CLASS_ATTR);
				writer.endElement(HTML.SPAN_ELEM);
				writer.endElement(HTML.DIV_ELEM);
			}
            
            
            writer.startElement(HTML.DIV_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"cnt", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-content ui-tabs-panel ui-widget-content ui-corner-bottom", HTML.CLASS_ATTR);
            writer.endElement(HTML.DIV_ELEM);			  
        } else {
            writer.startElement(HTML.UL_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"_nav", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-nav ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all ui-tabs-vertical", HTML.CLASS_ATTR);
                if (EnvUtils.isAriaEnabled(facesContext)) {
                    writer.writeAttribute(ARIA.ROLE_ATTR, ARIA.TABLIST_ROLE, ARIA.ROLE_ATTR);  
                }                
                doTabs(facesContext, uiComponent, Do.RENDER_LABEL, null, null, null);
            writer.endElement(HTML.UL_ELEM);
            
            
            writer.startElement(HTML.DIV_ELEM, null);
                writer.writeAttribute(HTML.ID_ATTR, clientId+"cnt", HTML.ID_ATTR);
                writer.writeAttribute(HTML.CLASS_ATTR, "yui-content ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-content-vertical", HTML.CLASS_ATTR);
            writer.endElement(HTML.DIV_ELEM);
        }
    }
    
    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent)
    throws IOException {
        ResponseWriter writer = facesContext.getResponseWriter();
        final TabSet tabSet = (TabSet) uiComponent;        
        String clientId = uiComponent.getClientId(facesContext);
//System.out.println("TabSetRenderer.encodeEnd  clientId: " + clientId);
        // ICE-6703: default style classes should be for the top orientation of tabs. (Plus space at the end.)
        String styleClass = "yui-navset yui-navset-top ui-tabset ui-widget ui-widget-content ui-corner-all ";
        
        String orientation = tabSet.getOrientation();
        // ICE-6703: top, invalid or unspecified orientation should all use default style classes defined above.
        if ("left".equalsIgnoreCase(orientation)) {
            styleClass= "yui-navset yui-navset-left ui-tabset-vertical ui-widget ui-widget-content ui-corner-all ";
        } else if ("right".equalsIgnoreCase(orientation)) {
            styleClass= "yui-navset yui-navset-right ui-tabset-vertical ui-widget ui-widget-content ui-corner-all ";
        } else if ("bottom".equalsIgnoreCase(orientation)) {
            styleClass= "yui-navset yui-navset-bottom ui-tabset ui-widget ui-widget-content ui-corner-all ";
        } 
        Object userDefinedClass = tabSet.getAttributes().get("styleClass"); 
        if (userDefinedClass != null ) 
        		styleClass+= userDefinedClass.toString() ;
        writer.writeAttribute(HTML.CLASS_ATTR, styleClass, HTML.CLASS_ATTR);
        boolean isClientSide = tabSet.isClientSide();

		String showEffect = tabSet.getShowEffect();
		showEffect = showEffect == null || showEffect.trim().equals("") ? "" : showEffect;
		int showEffectLength = tabSet.getShowEffectLength();

        int selectedIndex = tabSet.getSelectedIndex();
        //see what the selectedIndex is
        if (selectedIndex >= getRenderedChildCount(tabSet)) {
        	selectedIndex = 0;
        }

        // The tabs that are depicted as clickable by the user
        List clickableTabs = new ArrayList();
        boolean tabSetDisabled = tabSet.isDisabled();
        ArrayList disabledTabs = tabSetDisabled ? null : new ArrayList();
        Map tabPaneClientId2Cache =
                new HashMap();
        doTabs(facesContext, uiComponent, Do.GET_CLIENT_IDS, clickableTabs,
                tabPaneClientId2Cache, disabledTabs);
        if (tabSetDisabled) {
            final int num = clickableTabs.size();
            disabledTabs = new ArrayList(num);
            for (int i = 0; i < num; i++) {
                disabledTabs.add(i);
            }
        }

        // The tabs whose contents we need to render. Subset of clickableTabs,
        // where the order has a different meaning: [safeIndex] -> tabClientId
        List visitedTabClientIds = tabSet.getVisitedTabClientIds();
        if (visitedTabClientIds == null) {
            visitedTabClientIds = new ArrayList();
        }

        // Used to detect changes from last lifecycle
        List toRender = new ArrayList();
        Set renderWithoutUpdate = new HashSet();
        if (isClientSide) {
            toRender.addAll(clickableTabs);
        }
        else {
            for (int i = 0; i < clickableTabs.size(); i++) {
                String tabClientId = clickableTabs.get(i);
                TabPaneCache cache = tabPaneClientId2Cache.get(tabClientId);
                if (cache == null) {
                    cache = TabPaneCache.get(TabPaneCache.DEFAULT);
                }
                if (cache.isCached() && visitedTabClientIds.contains(tabClientId)) {
//System.out.println("toRender  cached prev  tabClientId: " + tabClientId);
                    toRender.add(tabClientId);
                    if (cache.isCachedStatically()) {
//System.out.println("toRender  cached  statically");
                        renderWithoutUpdate.add(tabClientId);
                    }
                } else if(selectedIndex == i) {
//System.out.println("toRender  selectedIndex="+selectedIndex+"  tabClientId: " + tabClientId);
                    toRender.add(tabClientId);
                }
            }
        }

        for (int i = 0; i < visitedTabClientIds.size(); i++) {
            String tabClientId = (String) visitedTabClientIds.get(i);
            if (tabClientId != null) {
                if (!toRender.contains(tabClientId)) {
                    visitedTabClientIds.set(i, null);
                }
            }
        }
        for (String tabClientId : toRender) {
            if (!visitedTabClientIds.contains(tabClientId)) {
                visitedTabClientIds.add(tabClientId);
            }
        }
        tabSet.setVisitedTabClientIds(visitedTabClientIds);

        final String safeIdPrefix = clientId+"_safe_";
        int clickableLen = clickableTabs.size();
        String[] safeIds = new String[clickableLen];
        for (int i = 0; i < clickableLen; i++) {
            //System.out.println("Clickable + " + i + " of " + clickableLen + " : " + clickableTabs.get(i));
            int safeIndex = visitedTabClientIds.indexOf(clickableTabs.get(i));
            //System.out.println("  safeIndex: " + safeIndex);
            if (safeIndex >= 0) {
                safeIds[i] = safeIdPrefix + safeIndex;
            }
            // null for clickable tabs we're not rendering
        }
        
        // Write out the safe
        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, clientId+"_safe", HTML.ID_ATTR);
        writer.writeAttribute(HTML.STYLE_ATTR, "display:none;", HTML.STYLE_ATTR);
        recursivelyRenderSafe(facesContext, writer, tabSet, safeIdPrefix,
                visitedTabClientIds, renderWithoutUpdate, 0);
        writer.endElement(HTML.DIV_ELEM);
        writer.endElement(HTML.DIV_ELEM);

        // If the server is trumping the browser's selectedIndex, by reverting
        // it to the previous value, then the dom diff won't know to tell the
        // browser, so we need to induce a script update
        Integer decSelIdx = decodeSelectedIndex(facesContext, uiComponent);
        boolean unexpected = (decSelIdx != null) &&
                (decSelIdx.intValue() != selectedIndex);

        JSONBuilder jb = JSONBuilder.create();
        jb.beginFunction("ice.ace.tabset.updateProperties").
        item(clientId).
        beginMap().
            entry("orientation", orientation).
            entry("showEffect", showEffect).
            entry("showEffectLength", showEffectLength).
            entry("activeTabChangeRequest", decSelIdx != null).
            entry("scrollableTabs", tabSet.isScrollableTabs()).
        endMap().
        beginMap().
            entry("devMode", facesContext.isProjectStage(ProjectStage.Development)).
            entry("isClientSide", isClientSide).
            entry("aria", EnvUtils.isAriaEnabled(facesContext)).
            entry("selectedIndex", selectedIndex).
            entry("safeIds", safeIds).
            beginArray("disabledTabs");

            for (Integer i : disabledTabs)
                jb.item(i);

            jb.endArray().
            entry("overrideSelectedIndex",
                    (unexpected ? System.currentTimeMillis() : 0));
            encodeClientBehaviors(facesContext, tabSet, jb);
        jb.endMap().
        endFunction();
        String reEvaluate = tabSet.isClientSide() ? "" : ("//" + RANDOM.nextLong());
        ScriptWriter.insertScript(facesContext, uiComponent, jb.toString() + reEvaluate);
    }

    private void recursivelyRenderSafe(FacesContext facesContext,
            ResponseWriter writer, TabSet tabSet, String idPrefix,
            List visitedTabClientIds, Set renderWithoutUpdate,
            int index) throws IOException {
        if (index >= visitedTabClientIds.size()) {
            return;
        }

        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, idPrefix+index, HTML.ID_ATTR);
        String tabClientId = (String) visitedTabClientIds.get(index);
        if (tabClientId != null) {
            if (renderWithoutUpdate.contains(tabClientId)) {
//System.out.println("TabSetRenderer  RENDER  suppressed : " + tabClientId);
                // Statically cached, so render nothing, and have the DOM diff
                // check nothing and update nothing
                doTabs(facesContext, tabSet, Do.RENDER_CONTENT_DIV_BY_CLIENT_ID,
                        visitedTabClientIds.subList(index, index+1), null, null);
            } else {
//System.out.println("TabSetRenderer  RENDER  contents   : " + tabClientId);
                // Dynamically cached, or not cached but rendered regardless
                doTabs(facesContext, tabSet, Do.RENDER_CONTENTS_BY_CLIENT_ID,
                        visitedTabClientIds.subList(index, index+1), null, null);
            }
        }
        writer.endElement(HTML.DIV_ELEM);

        writer.startElement(HTML.DIV_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR, idPrefix+index+"_nxt", HTML.ID_ATTR);
        recursivelyRenderSafe(facesContext, writer, tabSet, idPrefix,
                visitedTabClientIds, renderWithoutUpdate, index+1);
        writer.endElement(HTML.DIV_ELEM);

        /*