package com.servoy.j2db.server.ngclient;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.JSONException;
import org.json.JSONWriter;
import org.sablo.Container;
import org.sablo.IEventHandler;
import org.sablo.specification.PropertyDescription;
import org.sablo.specification.WebComponentSpecProvider;
import org.sablo.specification.property.IBrowserConverterContext;
import org.sablo.websocket.utils.DataConversion;
import org.sablo.websocket.utils.JSONUtils.IToJSONConverter;
import com.servoy.j2db.persistence.IPersist;
import com.servoy.j2db.persistence.IRepository;
import com.servoy.j2db.persistence.StaticContentSpecLoader;
import com.servoy.j2db.util.Utils;
/**
* Servoy extension to work with webcomponents on a form
* @author jcompagner
*/
@SuppressWarnings("nls")
public class WebFormComponent extends Container implements IContextProvider
{
public static final String TAG_SCOPE = "scope";
private final Map<IWebFormUI, Integer> visibleForms = new HashMap<IWebFormUI, Integer>();
private FormElement formElement;
protected IDataAdapterList dataAdapterList;
protected PropertyChangeSupport propertyChangeSupport;
protected ComponentContext componentContext;
private IDirtyPropertyListener dirtyPropertyListener;
public WebFormComponent(String name, FormElement fe, IDataAdapterList dataAdapterList)
{
super(name, WebComponentSpecProvider.getInstance().getWebComponentSpecification(fe.getTypeName()));
this.formElement = fe;
this.dataAdapterList = dataAdapterList;
properties.put("svyMarkupId", ComponentFactory.getMarkupId(dataAdapterList.getForm().getName(), name));
}
/**
* @return
*/
public FormElement getFormElement()
{
return formElement;
}
/**
* Only used in designer to update this component to the latest desgin form element.
*
* @param formElement the formElement to set
*/
public void setFormElement(FormElement formElement)
{
this.formElement = formElement;
}
/**
* @param componentContext the componentContext to set
*/
public void setComponentContext(ComponentContext componentContext)
{
this.componentContext = componentContext;
}
/**
* @return the componentContext
*/
public ComponentContext getComponentContext()
{
return componentContext;
}
// TODO get rid of this! it should not be needed once all types are implemented properly (I think usually wrapped types use it, and they could keep
// this value in their internal state if needed)
public Object getInitialProperty(String propertyName)
{
// NGConversions.INSTANCE.applyConversion3(...) could be used here as this is currently wrong value type, but
// it's better to remove it altogether as previous comment says; anyway right now I think the types that call/use this method don't have conversion 3 defined
return formElement.getPropertyValue(propertyName);
}
@Override
protected void onPropertyChange(String propertyName, Object oldValue, Object propertyValue)
{
super.onPropertyChange(propertyName, oldValue, propertyValue);
if (propertyChangeSupport != null) propertyChangeSupport.firePropertyChange(propertyName, oldValue, propertyValue);
}
/**
* These listeners will be triggered when the property changes by reference.
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
{
if (propertyChangeSupport == null) propertyChangeSupport = new PropertyChangeSupport(this);
if (propertyName == null) propertyChangeSupport.addPropertyChangeListener(listener);
else propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
{
if (propertyChangeSupport != null)
{
if (propertyName == null) propertyChangeSupport.removePropertyChangeListener(listener);
else propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
}
public void add(String eventType, int functionID)
{
addEventHandler(eventType, new FormcomponentEventHandler(eventType, functionID));
}
@Override
public IEventHandler getEventHandler(String eventType)
{
IEventHandler handler = super.getEventHandler(eventType);
if (handler == null)
{
throw new IllegalArgumentException("Unknown event '" + eventType + "' for component " + this);
}
return handler;
}
@Override
public String toString()
{
return "<" + getName() + ">";
}
public void updateVisibleForm(IWebFormUI form, boolean visible, int formIndex)
{
if (!visible)
{
visibleForms.remove(form);
form.setParentContainer(null);
}
else if (!visibleForms.containsKey(form))
{
form.setParentContainer(this);
visibleForms.put(form, Integer.valueOf(formIndex));
}
}
public boolean notifyVisible(boolean visible, List<Runnable> invokeLaterRunnables, Set<IWebFormController> childFormsThatWereNotified)
{
// TODO if there are multiply forms visible and only 1 is reporting that it can't be made invisible
// what to do with that state? Should it be rollbacked? Should everything be made visible again?
// see also WebFormUI
boolean retValue = true;
for (IWebFormUI webUI : visibleForms.keySet())
{
IWebFormController fc = webUI.getController();
childFormsThatWereNotified.add(fc);
retValue = retValue && fc.notifyVisible(visible, invokeLaterRunnables);
}
if (!visible && retValue)
{
visibleForms.clear();
}
return retValue;
}
public int getFormIndex(IWebFormUI form)
{
return visibleForms.containsKey(form) ? visibleForms.get(form).intValue() : -1;
}
public IServoyDataConverterContext getDataConverterContext()
{
return new ServoyDataConverterContext(dataAdapterList.getForm());
}
public IWebFormUI[] getVisibleForms()
{
return visibleForms.keySet().toArray(new IWebFormUI[visibleForms.size()]);
}
@Override
public void dispose()
{
propertyChangeSupport = null;
((DataAdapterList)dataAdapterList).componentDisposed(this);
super.dispose();
}
public boolean isDesignOnlyProperty(String propertyName)
{
return isDesignOnlyProperty(specification.getProperty(propertyName));
}
public static boolean isDesignOnlyProperty(PropertyDescription propertyDescription)
{
return propertyDescription != null && "design".equals(propertyDescription.getTag(TAG_SCOPE));
}
/**
* TODO What does this mean 'private'?
*/
public boolean isPrivateProperty(String propertyName)
{
return isPrivateProperty(specification.getProperty(propertyName));
}
/**
* TODO What does this mean 'private'?
*/
public static boolean isPrivateProperty(PropertyDescription propertyDescription)
{
return propertyDescription != null && "private".equals(propertyDescription.getTag(TAG_SCOPE));
}
public class FormcomponentEventHandler implements IEventHandler
{
private final String eventType;
private final int functionID;
public FormcomponentEventHandler(String eventType, int functionID)
{
this.eventType = eventType;
this.functionID = functionID;
}
@Override
public Object executeEvent(Object[] args)
{
// verify if component is accessible due to security options
IPersist persist = formElement.getPersistIfAvailable();
if (persist != null)
{
int access = dataAdapterList.getApplication().getFlattenedSolution().getSecurityAccess(persist.getUUID());
if (!((access & IRepository.ACCESSIBLE) != 0))
throw new RuntimeException("Security error. Component '" + getProperty("name") + "' is not accessible.");
}
if (Utils.equalObjects(eventType, StaticContentSpecLoader.PROPERTY_ONFOCUSGAINEDMETHODID.getPropertyName()) &&
(formElement.getForm().getOnElementFocusGainedMethodID() > 0) && formElement.getForm().getOnElementFocusGainedMethodID() != functionID)
{
dataAdapterList.executeEvent(WebFormComponent.this, eventType, formElement.getForm().getOnElementFocusGainedMethodID(), args);
}
else if (Utils.equalObjects(eventType, StaticContentSpecLoader.PROPERTY_ONFOCUSLOSTMETHODID.getPropertyName()) &&
(formElement.getForm().getOnElementFocusLostMethodID() > 0) && formElement.getForm().getOnElementFocusLostMethodID() != functionID)
{
dataAdapterList.executeEvent(WebFormComponent.this, eventType, formElement.getForm().getOnElementFocusLostMethodID(), args);
}
return dataAdapterList.executeEvent(WebFormComponent.this, eventType, functionID, args);
}
}
/**
* @param dirtyPropertyListener set the listeners that is called when {@link WebFormComponent#flagPropertyAsDirty(String) is called
*/
public void setDirtyPropertyListener(IDirtyPropertyListener dirtyPropertyListener)
{
this.dirtyPropertyListener = dirtyPropertyListener;
}
@Override
public boolean flagPropertyAsDirty(String key, boolean dirty)
{
boolean modified = super.flagPropertyAsDirty(key, dirty);
if (modified && dirtyPropertyListener != null) dirtyPropertyListener.propertyFlaggedAsDirty(key, dirty);
return modified;
}
private boolean isWritingComponentProperties;
public boolean isWritingComponentProperties()
{
return isWritingComponentProperties;
}
@Override
protected boolean writeComponentProperties(JSONWriter w, IToJSONConverter<IBrowserConverterContext> converter, String nodeName,
DataConversion clientDataConversions) throws JSONException
{
try
{
isWritingComponentProperties = true;
return super.writeComponentProperties(w, converter, nodeName, clientDataConversions);
}
finally
{
isWritingComponentProperties = false;
}
}
}