/* * � Copyright IBM Corp. 2010, 2016 * * 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 com.ibm.xsp.extlib.component.dojo.layout; import java.io.IOException; import java.util.List; import java.util.Map; import javax.faces.FacesException; import javax.faces.component.ContextCallback; import javax.faces.component.NamingContainer; import javax.faces.component.UIComponent; import javax.faces.component.UIComponentBase; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.el.ValueBinding; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.ajax.AjaxUtil; import com.ibm.xsp.component.FacesAjaxComponent; import com.ibm.xsp.component.FacesComponent; import com.ibm.xsp.context.FacesContextEx; import com.ibm.xsp.event.FacesContextListener; import com.ibm.xsp.extlib.component.dojo.layout.UIDojoTabContainer.Action; import com.ibm.xsp.extlib.util.ExtLibUtil; import com.ibm.xsp.page.FacesComponentBuilder; import com.ibm.xsp.util.TypedUtil; /** * Dojo Tab Pane. * * @author Philippe Riand */ public class UIDojoTabPane extends UIDojoContentPane implements NamingContainer, FacesComponent, FacesAjaxComponent { public static final String DEFAULT_DOJO_TYPE = "extlib.dijit.TabPane"; // $NON-NLS-1$ public static final String RENDERER_TYPE = "com.ibm.xsp.extlib.dojo.layout.TabPane"; //$NON-NLS-1$ private Boolean partialEvents; private Boolean closable; private String tabUniqueKey; // TODO should change to transient when persist mode file does a _xspCleanTransientData, so won't need to serialize this. private boolean isDelayedRemoveTab; private transient String indexedClientId; // Intermediate panel inserted between the content pane and its children // This is used to support partial refresh on the pane public static class PopupContent extends UIComponentBase { public static final String RENDERER_TYPE = "com.ibm.xsp.extlib.dojo.layout.TabPaneContent"; //$NON-NLS-1$ private transient boolean restoreSubTree; private transient UIComponent oldSubTree; public PopupContent() { setRendererType(RENDERER_TYPE); } public UIDojoTabPane getTabPane() { return (UIDojoTabPane)getParent(); } protected UIComponent getSubTreeComponent() { if(getTabPane().isPartialEvents()) { return this; } return null; } @Override public String getFamily() { return "javax.faces.Panel"; // $NON-NLS-1$ } @Override public void encodeBegin(FacesContext context) throws IOException { UIComponent subTree = getSubTreeComponent(); if(subTree!=null) { FacesContextEx ctx = (FacesContextEx)context; this.oldSubTree = ctx.getSubTreeComponent(); ctx.setSubTreeComponent(this); restoreSubTree = true; } else { restoreSubTree = false; } super.encodeBegin(context); } @Override public void encodeEnd(FacesContext context) throws IOException { super.encodeEnd(context); if(restoreSubTree) { FacesContextEx ctx = (FacesContextEx)context; ctx.setSubTreeComponent(oldSubTree); oldSubTree = null; } } @Override public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { UIComponent subTree = getSubTreeComponent(); if(subTree!=null) { // Handle partial refresh here FacesContextEx ctx = (FacesContextEx)context; if(ctx.isRenderingPhase()) { this.oldSubTree = ctx.getSubTreeComponent(); ctx.setSubTreeComponent(subTree); try { return super.invokeOnComponent(context, clientId, callback); } finally { ctx.setSubTreeComponent(oldSubTree instanceof UIComponent?(UIComponent)oldSubTree:null); oldSubTree = null; } } } return super.invokeOnComponent(context, clientId, callback); } } public UIDojoTabPane() { setRendererType(RENDERER_TYPE); } @Override public String getClientId(FacesContext context) { // As tab are created dynamically from facets, all the instances are // sharing the same id, thus the same client id... And the NamingContainer // capability is of no help here, as the tab container is not a repeat // control. // The idea is then to maintain a unique index per tab (coming from an // incremented counter), and append it to the actual id. // We still want the regular id to be the original one, so code can find and // access the component easily if(tabUniqueKey!=null) { if(indexedClientId==null) { indexedClientId = super.getClientId(context); indexedClientId = indexedClientId+NamingContainer.SEPARATOR_CHAR+tabUniqueKey; } return indexedClientId; } return super.getClientId(context); } @Override public void setId(String id) { super.setId(id); this.indexedClientId = null; } public void setUniqueTabIndex(int uniqueIdx) { this.tabUniqueKey = Integer.toString(uniqueIdx); } public boolean isPartialEvents() { if (null != this.partialEvents) { return this.partialEvents; } ValueBinding _vb = getValueBinding("partialEvents"); //$NON-NLS-1$ if (_vb != null) { Boolean val = (java.lang.Boolean) _vb.getValue(FacesContext.getCurrentInstance()); if(val!=null) { return val; } } return false; } public void setPartialEvents(boolean partialEvents) { this.partialEvents = partialEvents; } public boolean isClosable() { if (null != this.closable) { return this.closable; } ValueBinding _vb = getValueBinding("closable"); //$NON-NLS-1$ if (_vb != null) { Boolean val = (Boolean) _vb.getValue(FacesContext.getCurrentInstance()); if(val!=null) { return val; } } return false; } public void setClosable(boolean closable) { this.closable = closable; } public String getTabUniqueKey() { if (null != this.tabUniqueKey) { return this.tabUniqueKey; } ValueBinding _vb = getValueBinding("tabUniqueKey"); //$NON-NLS-1$ if (_vb != null) { return (java.lang.String) _vb.getValue(FacesContext.getCurrentInstance()); } else { return null; } } public void setTabUniqueKey(String tabUniqueKey) { this.tabUniqueKey = tabUniqueKey; } // State management @Override public void restoreState(FacesContext _context, Object _state) { Object _values[] = (Object[]) _state; super.restoreState(_context, _values[0]); this.partialEvents = (Boolean)_values[1]; this.closable = (Boolean)_values[2]; this.tabUniqueKey = (String)_values[3]; this.isDelayedRemoveTab = (Boolean) _values[4]; } @Override public Object saveState(FacesContext _context) { Object _values[] = new Object[5]; _values[0] = super.saveState(_context); _values[1] = partialEvents; _values[2] = closable; _values[3] = tabUniqueKey; _values[4] = Boolean.valueOf(isDelayedRemoveTab); return _values; } // ============================================================= // Methods for scripting // ============================================================= public void closeTab() { closeTab(null,null); } public void closeTab(String refreshId) { closeTab(refreshId,null); } public void closeTab(String refreshId, Map<String,Object> refreshParams) { FacesContextEx context = FacesContextEx.getCurrentInstance(); // For SPR#LHEYA76KAS: button in tab area closing tab when partial updating said tab // gives server-side CLFAD0376E error in env where SPR#MKEE9UNMT6 fixed, // as it fails validation that the update area corresponds to an known control, // because the control ID becomes unknown when the tab control is removed. // Change from inline deleting the pane here: //getParent().getChildren().remove(this) // To instead set the tab pane to non-visible, and schedule it for // removal after the renderResponse phase. this.setRendered(false); // not render this tab pane // Do remove the children of the content panel now though. PopupContent contentPanel = (PopupContent) TypedUtil.getChildren(this).get(0); contentPanel.getChildren().clear(); isDelayedRemoveTab = true; // When xsp.persistence.mode=basic will remove in beforeContextReleased: FacesContextListener contextListener = new FacesContextListener() { @Override public void beforeContextReleased(FacesContext facesContext) { delayedRemove(); } @Override public void beforeRenderingPhase(FacesContext facesContext) { // do nothing here, only in beforeContextReleased } }; context.addRequestListener(contextListener); // When xsp.persistence.mode=fileex will remove in UIDojoTabContainer._xspCleanTransientData() // When xsp.persistence.mode=file will remove in UIDojoTabContainer.processRestoreState // With any persist mode, if attempting to create another tabPane with the same key // as this tabPane then will remove in UIDojoTabContainer.createTab. // add client-side script to remove the dojo tab pane dijit. UIDojoTabContainer parent = (UIDojoTabContainer)getParent(); Action pendingAction = new Action(context,parent,this,refreshId,refreshParams); ExtLibUtil.postScript(context, pendingAction.generateClientScript()); } public void delayedRemove() { if( isDelayedRemoveTab() ){ UIDojoTabContainer parent = (UIDojoTabContainer)getParent(); if( null != parent ){ parent.getChildren().remove(this); } } } /** * True if this tabPane has been closed and is scheduled to be removed * from the control tree after the renderResponse phase. * @return the isDelayedRemoveTab */ public boolean isDelayedRemoveTab() { return isDelayedRemoveTab; } // ============================================================= // Control construction // ============================================================= public void initBeforeContents(FacesContext context) throws FacesException { } public void buildContents(FacesContext context, FacesComponentBuilder builder) throws FacesException { PopupContent content = new PopupContent(); content.setId("_content"); // $NON-NLS-1$ TypedUtil.getChildren(this).add(content); builder.buildAll(context, content, true); } public void initAfterContents(FacesContext context) throws FacesException { } // ============================================================= // AJAX commands // ============================================================= public boolean handles(FacesContext context) { // no path info handling - only client id based ones return false; } public void processAjaxRequest(FacesContext context) throws IOException { int errorCode = 200; // OK StringBuilder b = new StringBuilder(); // Find the command Map<String, String> params = TypedUtil.getRequestParameterMap(context.getExternalContext()); // Create a new tab String command = params.get("_action"); // $NON-NLS-1$ if(StringUtil.equals(command, "closeTab")) { // $NON-NLS-1$ errorCode = axDeleteTab(context, b, params); } // Return the Javascript snippet // TODO: add the header... AjaxUtil.initRender(context); ResponseWriter writer = context.getResponseWriter(); writer.write(b.toString()); } protected int axDeleteTab(FacesContext context, StringBuilder b, Map<String, String> params) throws IOException { int errorCode = 200; // OK boolean removed = false; List<UIComponent> children = TypedUtil.getChildren(getParent()); children.remove(this); removed = true; b.append(removed?"true":"false"); // $NON-NLS-1$ $NON-NLS-2$ if(removed) { ExtLibUtil.saveViewState(context); } return errorCode; } }