/*
* � Copyright IBM Corp. 2013
*
* 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.
*/
/*
* Author: Maire Kehoe (mkehoe@ie.ibm.com)
* Date: 11 Jul 2013, but was present in UIMobilePage.java as PopupContent inner class.
* UIMobilePageContent.java
*/
package com.ibm.xsp.extlib.component.mobile;
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 com.ibm.commons.util.StringUtil;
import com.ibm.xsp.component.UIEventHandler;
import com.ibm.xsp.component.UIPanelEx;
import com.ibm.xsp.context.FacesContextEx;
import com.ibm.xsp.extlib.beans.ViewStateBean;
import com.ibm.xsp.extlib.component.util.DynamicUIUtil;
import com.ibm.xsp.page.FacesComponentBuilder;
import com.ibm.xsp.page.FacesPage;
import com.ibm.xsp.page.FacesPageDriver;
import com.ibm.xsp.util.FacesUtil;
import com.ibm.xsp.util.TypedUtil;
// Intermediate panel inserted between the content pane and its children
// This is used to support partial refresh on the pane
public class UIMobilePageContent extends UIComponentBase implements NamingContainer{
// Note this is implementing NamingContainer to work around an issue (SPR#MKEE8P4L8D) in
// the xspClientDojo.js validateAll implementation, where it expects
// the execId control to be a NamingContainer, and otherwise will not validate
// the content of that control.
public static final String RENDERER_TYPE = "com.ibm.xsp.extlib.mobile.MobilePageContent"; //$NON-NLS-1$
public static final String COMPONENT_FAMILY = "com.ibm.xsp.extlib.Mobile"; // $NON-NLS-1$
private transient UIComponent oldSubTree;
// Not set in the xsp source, this is set by the UIMobilePage parent control
// when this control is created. This is used for creating the inner content.
private String sourcePageName;
public UIMobilePageContent() {
setRendererType(RENDERER_TYPE);
// this constructor is only used when save/restoring the control tree
// the other constructor is used in normal tree creation.
}
public UIMobilePageContent(String sourcePageName) {
this(); // call the other constructor
this.sourcePageName = sourcePageName;
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
FacesContextEx ctx = (FacesContextEx)context;
// Create the content if required
if(isShouldCreateOrReloadChildren(ctx)) {
// note it is not possible to load/reset content controls
// during a decode or invokeApplication phase,
// because the requests that make the content initially visible
// are AJAX GET requests partialRefreshGet),
// rather than POST requests, and such requests do not
// go through the JSF lifecycle phases.
// So the control loading & resetting/reloading must occur here
// during the render/encode phase
boolean firstLoad = !isContentCreated();
createOrReloadContent(ctx);
handleEventHandlersLoaded(context, firstLoad);
}
// Make the current component the sub tree pane
// This ensures that the children event handlers
// only do partial execute, and always target this control
this.oldSubTree = ctx.getSubTreeComponent();
ctx.setSubTreeComponent(this);
super.encodeBegin(context);
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
FacesContextEx ctx = (FacesContextEx)context;
encodeEventHandlerContainerFacet(context);
super.encodeEnd(context);
// Restore the old subtree component
ctx.setSubTreeComponent(oldSubTree instanceof UIComponent?(UIComponent)oldSubTree:null);
oldSubTree = null;
}
private void encodeEventHandlerContainerFacet(FacesContext context) throws IOException {
UIPanelEx evtContainer = (UIPanelEx) TypedUtil.getFacets(this).get("eventHandlerContainer");//$NON-NLS-1$
if( null != evtContainer ){
Boolean registeredListenerClientSide = (Boolean) TypedUtil.getAttributes(evtContainer).get("registered"); //$NON-NLS-1$
if( null == registeredListenerClientSide || ! registeredListenerClientSide.booleanValue() ){
TypedUtil.getAttributes(evtContainer).put("registered", Boolean.TRUE);//$NON-NLS-1$
// only output the code to register the listeners to the appPage control once,
// during the first time this control is rendered,
// because previously registered listeners are not de-registered during partial
// update of the inner _content area within the appPage, so if you
// registered a listener every time the _content area was rendered,
// more and more listeners would be present on the client side
// as the page was re-rendered and there'd be multiple responses to a single event.
FacesUtil.renderComponent(context, evtContainer);
}
}
}
@Override
public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
// Handle partial refresh here
FacesContextEx ctx = (FacesContextEx)context;
if(ctx.isRenderingPhase()) {
this.oldSubTree = ctx.getSubTreeComponent();
ctx.setSubTreeComponent(this);
try {
return super.invokeOnComponent(context, clientId, callback);
} finally {
ctx.setSubTreeComponent(oldSubTree instanceof UIComponent?(UIComponent)oldSubTree:null);
oldSubTree = null;
}
} else {
return super.invokeOnComponent(context, clientId, callback);
}
}
public UIMobilePage getMobilePage() {
return (UIMobilePage)getParent();
}
private boolean isShouldCreateOrReloadChildren(FacesContextEx context) {
// We should create the children if the request is a partial refresh request
// for the current component, or its page (the mobile page)
if(context.isAjaxPartialRefresh()) {
String id = context.getPartialRefreshId();
UIMobilePage mobilePage = getMobilePage();
if(FacesUtil.isClientIdChildOf(context, mobilePage, id)) {
Map<String, String> params = TypedUtil.getRequestParameterMap(context.getExternalContext());
String pt = params.get("pageTransition"); // $NON-NLS-1$
if(StringUtil.isNotEmpty(pt)) {
// If the content doesn't exist, then create it...
if(!isContentCreated()) {
return true;
}
// A parameter is the url has a greater priority
String resetParam = params.get(UIMobilePage.PARAM_RESET);
if(StringUtil.equals(resetParam, "true")) { // $NON-NLS-1$
return true;
}
if(StringUtil.equals(resetParam, "false")) { // $NON-NLS-1$
return false;
}
// Else use the default page option
return mobilePage.isResetContent();
}
}
}
return false;
}
public void buildInitialContents(FacesContext context, FacesComponentBuilder builder) throws FacesException {
// called after this UIMobilePageContent is created in the createView phase,
// by the parent UIMobilePage
UIMobilePage mobilePage = getMobilePage();
// If this class is the selected one, then create the children right now
if(mobilePage.isAutoCreate() || mobilePage.isPreload()) {
// if the mobilePage has been
// configured to initially load its contents.
builder.buildAll(context, this, /*facets*/false);
handleEventHandlersLoaded(context, true);
}
}
public void buildDynamicContents(FacesContext context, FacesComponentBuilder builder) {
builder.buildAll(context, this, /*facets*/false);
}
private void handleEventHandlersLoaded(FacesContext context, boolean firstLoad){
int firstEventHandlerIndex = findFirstEventHandlerIndex();
if( -1 == firstEventHandlerIndex ){
// no eventHandlers in the children list
return;
}
if( ! firstLoad ){
// delete any eventHandlers added on subsequent server-side reload
// of the control tree. The eventHandlers that were created during
// the initial server-side load of the control tree are always used,
// as their client-side registered listener hangs around after
// a partial update of the _content area, so ensuring that same
// server-side eventHandler is used too (so the clientIds is as expected).
deleteChildEventHandlers(context);
return;
}// else firstLoad
// Any eventHandler children that were created need to be handled differently
// for rendering purposes. So moving them from the children list
// to a container facet, so they are not encoded in the general encoding
// of all children. Their encoding/rendering is handled specially in the
// encodeEventHandlerContainerFacet method above.
UIPanelEx eventHandlerContainer;
{
eventHandlerContainer = new UIPanelEx();
eventHandlerContainer.setId(getMobilePage().getId()+"_evtContainer"); //$NON-NLS-1$
// only output the container content, not the <div wrapper
eventHandlerContainer.setDisableOutputTag(true);
TypedUtil.getFacets(this).put("eventHandlerContainer", eventHandlerContainer); //$NON-NLS-1$
}
List<UIComponent> evtContainerKidsList = TypedUtil.getChildren(eventHandlerContainer);
String appPageId = getMobilePage().getId();
List<UIComponent> thisKids = TypedUtil.getChildren(this);
for (int i = firstEventHandlerIndex; i < thisKids.size(); i++) {
UIComponent child = thisKids.get(i);
if( child instanceof UIEventHandler ){
UIEventHandler evt = (UIEventHandler)child;
// retarget to listen for events on the grandparent appPage <div
// rather listening to the _content <div which
// never generates onBeforeTransitionIn events.
evt.addFor(appPageId);
evtContainerKidsList.add(evt);
i--;
}
}
}
private int findFirstEventHandlerIndex() {
List<UIComponent> thisKids = TypedUtil.getChildren(this);
for (int i = 0; i < thisKids.size(); i++) {
UIComponent child = thisKids.get(i);
if( child instanceof UIEventHandler ){
return i;
}
}
return -1;
}
private void deleteChildEventHandlers(FacesContext context) {
List<UIComponent> children = TypedUtil.getChildren(this);
for (int i = 0; i < children.size(); i++) {
UIComponent child = children.get(i);
if( child instanceof UIEventHandler ){
children.remove(i);
i--;
}
}
}
@Override
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[]) state;
super.restoreState(context, values[0]);
sourcePageName = (String)values[1];
}
@Override
public Object saveState(FacesContext context) {
Object values[] = new Object[2];
values[0] = super.saveState(context);
values[1] = sourcePageName;
return values;
}
public boolean isContentCreated() {
return getChildCount()>0;
}
private void createOrReloadContent(FacesContextEx context) {
// First, delete the existing content
if(isContentCreated()) {
deleteContent();
}
// And then create the children
createChildren(context);
// Finally, apply the styles
DynamicUIUtil.applyStyleKit(context,this);
// And update the xp:viewPanel/xe:dataView/xp:repeat controls, if necessary
ViewStateBean.get().initFromState();
}
private void deleteContent() {
// remove children from the hierarchy
// Can't use: DynamicUIUtil.removeChildren(this,/*facets*/false);
// because it doesn't honor the facets argument. Don't want to delete
// the "eventHandlerContainer" facet.
if(this.getChildCount()>0) {
TypedUtil.getChildren(this).clear();
}
}
private void createChildren(FacesContextEx context) {
// Load the current page (corresponds to an .xsp, may be a custom control)
FacesPageDriver driver = DynamicUIUtil.findPageDriver(context, this);
FacesPage page = DynamicUIUtil.loadPage(context, driver, sourcePageName);
// create a temporary container facet where the temporary mobilePage control will be created.
UIMobilePageTempContainer tempContainer = new UIMobilePageTempContainer();
TypedUtil.getFacets(this).put("temp", tempContainer); //$NON-NLS-1$
try{
// Create the components that are children of the xe:appPage in the XPage source.
// This will create another UIMobilePage control under the UIMobilePageTempContainer,
// and that temporary UIMobilePage will call the buildDynamicContents method above
// which will create the XPage source children of the xe:appPage underneath
// this UIMobilePageContent control (i.e. not under the temporary UIMobilePage control)
// Historically, the component id are stored in lower case in the XPage .java file.
String createId = getMobilePage().getId();
page.addComponent(context, null, tempContainer, createId.toLowerCase());
}finally{
// remove the temporary container, and
// the temporary UIMobilePage control under that container.
TypedUtil.getFacets(this).remove("temp"); //$NON-NLS-1$
}
}
}