/*
* � Copyright IBM Corp. 2010, 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.
*/
package com.ibm.xsp.extlib.component.dialog;
import java.io.IOException;
import java.util.HashMap;
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.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonGenerator;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.xsp.FacesExceptionEx;
import com.ibm.xsp.component.FacesComponent;
import com.ibm.xsp.component.UIEventHandler;
import com.ibm.xsp.component.UIFormEx;
import com.ibm.xsp.component.xp.XspForm;
import com.ibm.xsp.context.FacesContextEx;
import com.ibm.xsp.dojo.FacesDojoComponent;
import com.ibm.xsp.extlib.component.dojo.layout.UIDojoContentPane;
import com.ibm.xsp.extlib.component.dynamiccontent.UIDynamicControl;
import com.ibm.xsp.extlib.component.util.DynamicUIUtil;
import com.ibm.xsp.extlib.stylekit.StyleKitExtLibDefault;
import com.ibm.xsp.extlib.util.ExtLibUtil;
import com.ibm.xsp.page.FacesComponentBuilder;
import com.ibm.xsp.stylekit.ThemeControl;
import com.ibm.xsp.util.FacesUtil;
import com.ibm.xsp.util.JavaScriptUtil;
import com.ibm.xsp.util.StateHolderUtil;
import com.ibm.xsp.util.TypedUtil;
/**
* Dialog container.
* <p>
* Defines a modal dialog using the dijit.Dialog class. The dialog can be displayed from either
* a piece of client side or server side script.
* </p>
*
* This is a complex component as it is not generated as part of the page when rendered.
* This is because Dojo moves a dialog from where it had been generated, directly to the
* body tag for display reasons. From an XPages standpoint, this has several consequences:
* - The dialog is moved out of the main form, which means that the dialog should use
* its own form. It is created dynamically as a child of the popup content.
* - When doing partial refresh of a piece of markup containing a dialog, the dialog widget
* as to be destroyed. This is done by a wrapper component that stays where the dialog is
* generated, and find/destroy the dialog when it is itself destroyed. He will then be
* recreated.
* - The content of the dialog is created dynamically the first time the dialog is displayed.
* This is done by a partial refresh request on the dialog itself.
* - The initial markup dialog generates the destroy wrapper and a simple tag without the dojoType
* assigned. When the dialog is to be displayed, the tag is moved to the body and the dialog is
* partial refreshed.
* - The dialog can have events assigned to it (onHide, OnShow...). As these events are generally
* handled though child components, the handlers are only created when the content of the dialog
* is created. See the code which ensures that the events are assigned back to the dialog and
* not to the form, as the other chidren.
*
*/
public class UIDialog extends UIDojoContentPane implements NamingContainer, FacesComponent, FacesDojoComponent, ThemeControl {
public static boolean DIALOG_NEXT = false;
public static final String DIALOGACTION_PARAM = "$$action"; // $NON-NLS-1$
public static final int ACTION_NONE = 0;
public static final int ACTION_SHOW_DIALOG = 1;
public static final int ACTION_HIDE_DIALOG = 2;
public static final String COMPONENT_TYPE = "com.ibm.xsp.extlib.dialog.Dialog"; // $NON-NLS-1$
public static final String COMPONENT_FAMILY = "com.ibm.xsp.extlib.Dialog"; //$NON-NLS-1$
public static final String RENDERER_TYPE = "com.ibm.xsp.extlib.dialog.Dialog"; //$NON-NLS-1$
// Dialog attributes
private Boolean keepComponents;
// Server side events events
private MethodBinding beforeContentLoad;
private MethodBinding afterContentLoad;
public static class Action {
private UIDialog dialog;
private int action;
private String refreshId;
private Map<String,String> refreshParams;
Action(FacesContext context, UIDialog dialog, int action, Map<String,String> refreshParams) {
this.dialog = dialog;
this.action = action;
this.refreshParams = refreshParams;
}
Action(FacesContext context, UIDialog dialog, int action, String refreshId, Map<String,String> refreshParams) {
this.dialog = dialog;
this.action = action;
this.refreshId = refreshId;
this.refreshParams = refreshParams;
}
public String generateClientScript() {
FacesContext context = FacesContext.getCurrentInstance();
switch (action) {
case UIDialog.ACTION_SHOW_DIALOG: {
String method = "openDialog"; // $NON-NLS-1$
if(dialog instanceof UITooltipDialog) {
method = "openTooltipDialog"; // $NON-NLS-1$
}
// Generate the piece of code that shows the dialog
StringBuilder b = new StringBuilder();
// setTimeOut(xxx,0) will ensure it will be executed the partial refresh completed
b.append("setTimeout(function(){XSP."); // $NON-NLS-1$
b.append(method);
b.append("(");
JavaScriptUtil.addString(b, dialog.getClientId(context));
b.append(",null"); // $NON-NLS-1$
Object params = refreshParams;
if(params!=null) {
try {
String json = JsonGenerator.toJson(JsonJavaFactory.instance,params,true);
b.append(",");
b.append(json);
} catch(Exception ex) {
throw new FacesExceptionEx(ex);
}
}
b.append("),0});");
String script = b.toString();
return script;
}
case UIDialog.ACTION_HIDE_DIALOG: {
String method = "closeDialog"; // $NON-NLS-1$
if(dialog instanceof UITooltipDialog) {
method = "closeTooltipDialog"; // $NON-NLS-1$
}
// Generate the piece of code that closes the dialog
StringBuilder b = new StringBuilder();
//b.append("setTimeout(function(){XSP.");
b.append("XSP."); // $NON-NLS-1$
b.append(method);
b.append("(");
JavaScriptUtil.addString(b, dialog.getClientId(context));
String rid=ExtLibUtil.getClientId(context,dialog,refreshId,true);
if(StringUtil.isNotEmpty(rid)) {
b.append(",");
JavaScriptUtil.addString(b, rid);
Object params = refreshParams;
if(params!=null) {
try {
String json = JsonGenerator.toJson(JsonJavaFactory.instance,params,true);
b.append(",");
b.append(json);
} catch(Exception ex) {
throw new FacesExceptionEx(ex);
}
}
}
b.append(");");
String script = b.toString();
return script;
}
}
return null;
}
}
public static class PopupContent extends UIDynamicControl {
public static final String RENDERER_TYPE = "com.ibm.xsp.extlib.dialog.DialogPopup"; //$NON-NLS-1$
public PopupContent() {
setRendererType(RENDERER_TYPE);
}
@Override
public String getFamily() {
return UIDialog.COMPONENT_FAMILY;
}
@Override
protected boolean isValidInContext(FacesContext context) {
FacesContextEx ctx = (FacesContextEx)context;
return getDialog().isDialogRequest(ctx);
}
@Override
public boolean getRendersChildren() {
return false;
}
public UIDialog getDialog() {
return (UIDialog)getParent();
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
if(!DIALOG_NEXT) {
// If the original dialog mode, the content is created by the popup panel
//if(getDialog().pendingAction==null) {
FacesContextEx ctx = (FacesContextEx)context;
if(getDialog().isDialogCreateRequest(ctx)) {
// If the content is already created, then we first delete it
// and then recreate it
createContent(ctx);
} else {
// Else, we delete the content if the request is out of the dialog
if(isContentCreated() && !getDialog().isKeepComponents()) {
if(!getDialog().isDialogRequest(ctx)) {
deleteContent(ctx);
}
}
}
//}
}
super.encodeBegin(context);
}
@Override
public void createChildren(FacesContextEx context) {
PopupForm form = new PopupForm();
form.setAutoForm(true);
form.setId("form1"); // TODO allow user-specified formId $NON-NLS-1$
TypedUtil.getChildren(this).add(form);
super.createChildren(context);
}
@Override
protected UIFormEx getRootComponent() {
if(getChildCount()>0) {
return (UIFormEx)getChildren().get(0); //must be UIFormEx
}
return null;
}
@Override
protected String getCreateId() {
return getDialog().getId();
}
@Override
protected MethodBinding getBeforeContentLoad() {
return getDialog().getBeforeContentLoad();
}
@Override
protected MethodBinding getAfterContentLoad() {
return getDialog().getAfterContentLoad();
}
@Override
protected void deleteContent(FacesContextEx context) {
if(!getDialog().isKeepComponents()) {
// Remove the EventHandlers add to the dialog
List<UIComponent> l = TypedUtil.getChildren(getDialog());
for(int i=0; i<l.size(); ) {
UIComponent c = l.get(i);
if(c instanceof UIEventHandler) {
l.remove(i);
} else {
i++;
}
}
// Then delete the content of the panel
super.deleteContent(context);
}
}
}
// The embedded form ensures that the event handlers are assigned
public static class PopupForm extends XspForm implements DynamicUIUtil.IDynamicContainer {
public PopupForm() {
// We completely prevent the form submission as everything should
// be done using partial refresh. This also prevent the default form
// submission when the user presses 'enter'
setOnsubmit("return false;"); // $NON-NLS-1$
}
public void addDynamicChild(UIComponent c) {
if(c instanceof UIEventHandler) {
UIDialog dlg = (UIDialog)getParent().getParent();
TypedUtil.getChildren(dlg).add(c);
} else {
TypedUtil.getChildren(this).add(c);
}
}
}
/**
*
*/
public UIDialog() {
setRendererType(RENDERER_TYPE);
}
@Override
public String getStyleKitFamily() {
return StyleKitExtLibDefault.DIALOG_MODAL;
}
public boolean isVisible() {
PopupContent popup = getPopupContent();
if(popup!=null && popup.isContentCreated()) {
return popup.isValidInContext(FacesContext.getCurrentInstance());
}
return false;
}
public void show() {
show(null);
}
public void show(Map<String,String> parameters) {
FacesContextEx context = FacesContextEx.getCurrentInstance();
Map<String,String> p = new HashMap<String, String>();
if(parameters!=null) {
p.putAll(parameters);
}
p.put("$$createdialog", "false"); // $NON-NLS-1$ $NON-NLS-2$
Action pendingAction = new Action(context,this,ACTION_SHOW_DIALOG,p);
ExtLibUtil.postScript(context, pendingAction.generateClientScript());
// Force the content to be recreated here so the JS code can access the dialog
// components right after it is closed
// Note that we should then prevent the dialog from being recreated during
// the rendering phase of the popup panel, else the values will be lost
PopupContent popup = getPopupContent();
popup.createContent(context);
}
public void hide() {
hide(null,null);
}
public void hide(String refreshId) {
hide(refreshId,null);
}
public void hide(String refreshId, Map<String,String> refreshParams) {
FacesContextEx context = FacesContextEx.getCurrentInstance();
PopupContent popup = getPopupContent();
if(popup.isContentCreated()) {
// Remove the content
popup.deleteContent(context);
Action pendingAction = new Action(context,this,ACTION_HIDE_DIALOG,refreshId,refreshParams);
ExtLibUtil.postScript(context, pendingAction.generateClientScript());
}
}
public PopupContent getPopupContent() {
if(getChildCount()>0) {
return (PopupContent)getChildren().get(0);
}
return null;
}
public UIFormEx getRootComponent() {
PopupContent ct = getPopupContent();
if(ct!=null) {
return ct.getRootComponent();
}
return null;
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
public boolean isKeepComponents() {
if (null != this.keepComponents) {
return this.keepComponents;
}
ValueBinding _vb = getValueBinding("keepComponents"); //$NON-NLS-1$
if (_vb != null) {
Boolean val = (java.lang.Boolean) _vb.getValue(FacesContext.getCurrentInstance());
if(val!=null) {
return val;
}
}
return false;
}
public void setKeepComponents(boolean keepComponents) {
this.keepComponents = keepComponents;
}
public MethodBinding getBeforeContentLoad() {
return beforeContentLoad;
}
public void setBeforeContentLoad(MethodBinding beforeContentLoad) {
this.beforeContentLoad = beforeContentLoad;
}
public MethodBinding getAfterContentLoad() {
return afterContentLoad;
}
public void setAfterContentLoad(MethodBinding afterContentLoad) {
this.afterContentLoad = afterContentLoad;
}
public boolean isDialogRequest(FacesContextEx context) {
// The current panel is the current one if the request is
// a partial refresh, where the panel is the target component
if(context.isAjaxPartialRefresh()) {
String id = context.getPartialRefreshId();
if(DIALOG_NEXT) {
if(FacesUtil.isClientIdChildOf(context, this, id)) {
return true;
}
} else {
if(FacesUtil.isClientIdChildOf(context, getPopupContent(), id)) {
return true;
}
}
}
return false;
}
public boolean isDialogCreateRequest(FacesContextEx context) {
// The current panel is the current one if the request is
// a partial refresh, where the panel is the target component
// The request must also include a $$showdialog=true param in the URL
Map<String, String> params = TypedUtil.getRequestParameterMap(context.getExternalContext());
if(StringUtil.equals(params.get("$$showdialog"),"true")) { // $NON-NLS-1$ $NON-NLS-2$
// If the creation had been prohibited, then do not create it
if(StringUtil.equals(params.get("$$createdialog"),"false")) { // $NON-NLS-1$ $NON-NLS-2$
return false;
}
if(context.isAjaxPartialRefresh()) {
String id = context.getPartialRefreshId();
if(DIALOG_NEXT) {
if(StringUtil.equals(getPopupContent(), id)) {
return true;
}
} else {
if(StringUtil.equals(getPopupContent().getClientId(context), id)) {
return true;
}
}
}
}
return false;
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
if(DIALOG_NEXT) {
// In the dialog next implementation, the content is created by the dialog itself
//if(pendingAction==null) {
FacesContextEx ctx = (FacesContextEx)context;
if(isDialogCreateRequest(ctx)) {
getPopupContent().createContent(ctx);
} else {
// Else, we delete the content if the request is out of the dialog
if(getPopupContent().isContentCreated() && !isKeepComponents()) {
if(!isDialogRequest(ctx)) {
getPopupContent().deleteContent(ctx);
}
}
}
//}
}
super.encodeBegin(context);
}
//
// The dialog never renders its children in 852 mode.
// If we are here, it is because the page is refreshing and the dialog is
// not the current context.
//
@Override
public boolean getRendersChildren() {
if(!DIALOG_NEXT) {
// In the original dialog, it does not render it children
if(isDialogRequest(FacesContextEx.getCurrentInstance())) {
return false;
}
return true;
}
return super.getRendersChildren();
}
@Override
public void encodeChildren(FacesContext context) throws IOException {
if(DIALOG_NEXT) {
super.encodeChildren(context);
} else {
// If we are here,it is because we should not render the children...
}
}
//
// Implementation of com.ibm.xsp.component.FacesComponent
//
public void initBeforeContents(FacesContext context) throws FacesException {
}
public void buildContents(FacesContext context, FacesComponentBuilder builder) throws FacesException {
buildContents(context, builder, this);
}
protected void buildContents(FacesContext context, FacesComponentBuilder builder, UIComponent parent) throws FacesException {
if(DynamicUIUtil.isDynamicallyConstructing(context)) {
// We should reset the dynamically constructing flag as we don't want it to be
// propagated to its children (ex: nested dialogs...)
UIComponent c = DynamicUIUtil.getDynamicallyConstructedComponent(context);
DynamicUIUtil.setDynamicallyConstructing(context, null);
try {
builder.buildAll(context, this, false);
return;
} finally {
DynamicUIUtil.setDynamicallyConstructing(context, c);
}
} else {
PopupContent content = new PopupContent();
content.setId("_content"); // $NON-NLS-1$
TypedUtil.getChildren(parent).add(content);
// Set the source page name for creating the inner content
content.setSourcePageName(DynamicUIUtil.getSourcePageName(builder));
}
}
public void initAfterContents(FacesContext context) throws FacesException {
}
@Override
public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
return super.invokeOnComponent(context, clientId, callback);
}
//
// State handling
//
@Override
public void restoreState(FacesContext _context, Object _state) {
Object _values[] = (Object[]) _state;
super.restoreState(_context, _values[0]);
this.keepComponents = (Boolean) _values[1];
this.beforeContentLoad = StateHolderUtil.restoreMethodBinding(_context, this, _values[2]);
this.afterContentLoad = StateHolderUtil.restoreMethodBinding(_context, this, _values[3]);
}
@Override
public Object saveState(FacesContext _context) {
Object _values[] = new Object[4];
_values[0] = super.saveState(_context);
_values[1] = keepComponents;
_values[2] = StateHolderUtil.saveMethodBinding(_context, beforeContentLoad);
_values[3] = StateHolderUtil.saveMethodBinding(_context, afterContentLoad);
return _values;
}
}