package com.servoy.j2db.server.ngclient; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.border.Border; import org.json.JSONException; import org.json.JSONWriter; import org.sablo.Container; import org.sablo.WebComponent; import org.sablo.specification.PropertyDescription; import org.sablo.specification.WebObjectSpecification; import org.sablo.specification.property.IBrowserConverterContext; import org.sablo.specification.property.types.BooleanPropertyType; import org.sablo.specification.property.types.DimensionPropertyType; import org.sablo.specification.property.types.TypesRegistry; import org.sablo.specification.property.types.VisiblePropertyType; import org.sablo.websocket.utils.DataConversion; import org.sablo.websocket.utils.JSONUtils.IToJSONConverter; import com.servoy.j2db.BasicFormController; import com.servoy.j2db.FormController; import com.servoy.j2db.IApplication; import com.servoy.j2db.IForm; import com.servoy.j2db.IFormController; import com.servoy.j2db.dataprocessing.BufferedDataSet; import com.servoy.j2db.dataprocessing.IDataSet; import com.servoy.j2db.dataprocessing.IFoundSetInternal; import com.servoy.j2db.dataprocessing.IRecordInternal; import com.servoy.j2db.dataprocessing.IValueList; import com.servoy.j2db.dataprocessing.JSDataSet; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.FormElementGroup; import com.servoy.j2db.persistence.IFormElement; import com.servoy.j2db.persistence.IPersist; import com.servoy.j2db.persistence.Part; import com.servoy.j2db.persistence.PositionComparator; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.Tab; import com.servoy.j2db.persistence.TabPanel; import com.servoy.j2db.scripting.ElementScope; import com.servoy.j2db.scripting.FormScope; import com.servoy.j2db.server.ngclient.component.RuntimeLegacyComponent; import com.servoy.j2db.server.ngclient.component.RuntimeWebComponent; import com.servoy.j2db.server.ngclient.component.RuntimeWebGroup; import com.servoy.j2db.server.ngclient.property.types.NGEnabledPropertyType; import com.servoy.j2db.server.ngclient.property.types.NGEnabledSabloValue; import com.servoy.j2db.server.ngclient.property.types.ReadonlyPropertyType; import com.servoy.j2db.server.ngclient.property.types.ReadonlySabloValue; import com.servoy.j2db.server.ngclient.property.types.ValueListTypeSabloValue; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Utils; @SuppressWarnings("nls") public class WebFormUI extends Container implements IWebFormUI, IContextProvider { public static final String ENABLED = "enabled"; public static final String READONLY = "readOnly"; private static final class FormSpecification extends WebObjectSpecification { private FormSpecification() { super("form_spec", "", "", null, null, null, "", null); putProperty("size", new PropertyDescription("size", DimensionPropertyType.INSTANCE, PushToServerEnum.allow)); putProperty("visible", new PropertyDescription("visible", VisiblePropertyType.INSTANCE, PushToServerEnum.allow)); putProperty(WebFormUI.ENABLED, new PropertyDescription(WebFormUI.ENABLED, NGEnabledPropertyType.NG_INSTANCE, PushToServerEnum.allow)); putProperty("findmode", new PropertyDescription("findmode", BooleanPropertyType.INSTANCE, PushToServerEnum.allow)); } } private static final WebObjectSpecification FORM_SPEC = new FormSpecification(); private final Map<String, Integer> events = new HashMap<>(); //event name mapping to persist id private final IWebFormController formController; private Object parentContainerOrWindowName; protected DataAdapterList dataAdapterList; private PropertyChangeListener parentReadOnlyListener; protected List<FormElement> cachedElements = new ArrayList<FormElement>(); private final Map<String, RuntimeWebGroup> groups = new HashMap<String, RuntimeWebGroup>(); public WebFormUI(IWebFormController formController) { super(formController.getName(), FORM_SPEC); this.formController = formController; setVisible(false); setEnabled(true); setProperty("findmode", Boolean.FALSE); init(); } public final INGApplication getApplication() { return formController.getApplication(); } /** * this is a full recreate ui. * * @param formController * @param dal * @return * @throws RepositoryException */ public void init() { components.clear(); cachedElements.clear(); groups.clear(); IDataAdapterList previousDataAdapterList = dataAdapterList; dataAdapterList = new DataAdapterList(formController); ElementScope elementsScope = initElementScope(formController); List<FormElement> formElements = getFormElements(); int counter = 0; for (FormElement fe : formElements) { // TODO do something similar for child elements (so properties of type 'components' which contain componentSpecs in them) WebObjectSpecification componentSpec = fe.getWebComponentSpec(false); if (componentSpec == null) { getApplication().reportError("Didn't find a spec file for component " + fe + " when creating form: " + formController.getName(), null); continue; } WebFormComponent component = ComponentFactory.createComponent(getApplication(), dataAdapterList, fe, this); if (component != null) { counter = contributeComponentToElementsScope(elementsScope, counter, fe, componentSpec, component); } if (fe.getPersistIfAvailable() instanceof TabPanel) { //legacy behavior, automatically link the tab TabPanel tabpanel = (TabPanel)fe.getPersistIfAvailable(); Iterator<IPersist> it = tabpanel.getTabs(); if (it.hasNext()) { Tab tab = (Tab)it.next(); if (tab.getContainsFormID() > 0) { Form form = getApplication().getFlattenedSolution().getForm(tab.getContainsFormID()); if (form != null) { dataAdapterList.addUninitializedRelatedForm(form.getName(), tab.getRelationName()); } } } if (it.hasNext() && (tabpanel.getTabOrientation() == TabPanel.SPLIT_HORIZONTAL || tabpanel.getTabOrientation() == TabPanel.SPLIT_VERTICAL)) { Tab tab = (Tab)it.next(); if (tab.getContainsFormID() > 0) { Form form = getApplication().getFlattenedSolution().getForm(tab.getContainsFormID()); if (form != null) { dataAdapterList.addUninitializedRelatedForm(form.getName(), tab.getRelationName()); } } } } } DefaultNavigatorWebComponent nav = (DefaultNavigatorWebComponent)components.get(DefaultNavigator.NAME_PROP_VALUE); if (nav != null) { nav.newFoundset(null); } // special support for the default navigator if (formController.getForm().getNavigatorID() == Form.NAVIGATOR_DEFAULT) { add(new DefaultNavigatorWebComponent(dataAdapterList)); } if (previousDataAdapterList != null) { IRecordInternal record = ((DataAdapterList)previousDataAdapterList).getRecord(); if (record != null) { dataAdapterList.setRecord(record, false); previousDataAdapterList.setRecord(null, false); nav = (DefaultNavigatorWebComponent)components.get(DefaultNavigator.NAME_PROP_VALUE); if (nav != null) nav.newFoundset(record.getParentFoundSet()); } } } public void contributeComponentToElementsScope(FormElement fe, WebObjectSpecification componentSpec, WebFormComponent component) { ElementScope elementsScope = getElementsScope(); if (elementsScope != null) { Object tmp = elementsScope.get("length", elementsScope); int counter = tmp instanceof Integer ? ((Integer)tmp).intValue() : 0; contributeComponentToElementsScope(elementsScope, counter, fe, componentSpec, component); } else { Debug.error(new RuntimeException("Trying to contribute to a non-existent elements scope for form: " + getName())); } } private int contributeComponentToElementsScope(ElementScope elementsScope, int counterStart, FormElement fe, WebObjectSpecification componentSpec, WebFormComponent component) { int counter = counterStart; if (!FormElement.ERROR_BEAN.equals(componentSpec.getName()) && (!fe.getName().startsWith("svy_") || (fe.getPersistIfAvailable() instanceof IFormElement) && ((IFormElement)fe.getPersistIfAvailable()).getGroupID() != null)) { RuntimeWebComponent runtimeComponent = new RuntimeWebComponent(component, componentSpec); if (fe.isLegacy() || ((fe.getForm().getView() == IForm.LIST_VIEW || fe.getForm().getView() == FormController.LOCKED_LIST_VIEW || fe.getForm().getView() == FormController.TABLE_VIEW || fe.getForm().getView() == FormController.LOCKED_TABLE_VIEW) && fe.getTypeName().startsWith("servoydefault-"))) { // add legacy behavior runtimeComponent.setPrototype(new RuntimeLegacyComponent(component)); } if (!fe.getName().startsWith("svy_")) { elementsScope.put(fe.getRawName(), formController.getFormScope(), runtimeComponent); elementsScope.put(counter++, formController.getFormScope(), runtimeComponent); } String groupID = fe.getPersistIfAvailable() instanceof IFormElement ? ((IFormElement)fe.getPersistIfAvailable()).getGroupID() : null; if (groupID != null) { RuntimeWebGroup group = groups.get(groupID); if (group == null) { String groupName = FormElementGroup.getName(groupID); group = new RuntimeWebGroup(groupName); group.setParentScope(component.getDataConverterContext().getApplication().getScriptEngine().getSolutionScope()); elementsScope.put(groupName, formController.getFormScope(), group); groups.put(groupID, group); } group.add(runtimeComponent); } } return counter; } public IServoyDataConverterContext getDataConverterContext() { return new ServoyDataConverterContext(formController); } public IDataAdapterList getDataAdapterList() { return dataAdapterList; } public void add(String eventType, int functionID) { events.put(eventType, Integer.valueOf(functionID)); } @Override public boolean hasEvent(String eventType) { return events.containsKey(eventType); } @Override public Object doExecuteEvent(String eventType, Object[] args) { Integer eventId = events.get(eventType); if (eventId != null) { return dataAdapterList.executeEvent(this, eventType, eventId.intValue(), args); } throw new IllegalArgumentException("Unknown event '" + eventType + "' for component " + this); } @Override public WebFormComponent getWebComponent(String compname) { WebComponent wc = findChildComponent(compname, this); return wc instanceof WebFormComponent ? (WebFormComponent)wc : null; } private static WebComponent findChildComponent(String compname, WebComponent rootComponent) { if (compname.equals(rootComponent.getName())) return rootComponent; if (rootComponent instanceof Container) { Collection<WebComponent> childComponents = ((Container)rootComponent).getComponents(); if (childComponents.size() > 0) { for (WebComponent wc : childComponents) { WebComponent foundChildComponent = findChildComponent(compname, wc); if (foundChildComponent != null) return foundChildComponent; } } } return null; } public Collection<WebComponent> getScriptableComponents() { List<WebComponent> components = new ArrayList<WebComponent>(); Object[] names = getElementsScope().getIds(); if (names != null) { for (Object componentName : names) { Object component = getElementsScope().get((String)componentName, null); if (component instanceof RuntimeWebComponent) { components.add(((RuntimeWebComponent)component).getComponent()); } } } return components; } public Collection<WebComponent> getAllComponents() { Collection<WebComponent> allComponents = new ArrayList<WebComponent>(); addAllChildren(this, allComponents); return allComponents; } private void addAllChildren(Container parent, Collection<WebComponent> allComponents) { Collection<WebComponent> directChildren = parent.getComponents(); allComponents.addAll(directChildren); for (WebComponent component : directChildren) { if (component instanceof Container) { addAllChildren((Container)component, allComponents); } } } @Override public boolean writeAllComponentsProperties(JSONWriter w, IToJSONConverter<IBrowserConverterContext> converter) throws JSONException { try { getController().setRendering(true); return super.writeAllComponentsProperties(w, converter); } finally { getController().setRendering(false); } } @Override public boolean writeAllComponentsChanges(JSONWriter w, String keyInParent, IToJSONConverter<IBrowserConverterContext> converter, DataConversion clientDataConversions) throws JSONException { try { getController().setRendering(true); return super.writeAllComponentsChanges(w, keyInParent, converter, clientDataConversions); } finally { getController().setRendering(false); } } private ElementScope initElementScope(IFormController controller) { FormScope formScope = controller.getFormScope(); ElementScope elementsScope = new ElementScope(formScope); formScope.putWithoutFireChange("elements", elementsScope); //$NON-NLS-1$ return elementsScope; } @Override public void doPutBrowserProperty(String propertyName, Object propertyValue) throws JSONException { // TODO: convert this to property change listener if ("size".equals(propertyName)) { Dimension prev = (Dimension)properties.get("size"); super.doPutBrowserProperty(propertyName, propertyValue); Dimension newSize = (Dimension)properties.get("size"); if (!Utils.equalObjects(prev, newSize)) { formController.notifyResized(); } } else { super.doPutBrowserProperty(propertyName, propertyValue); } } @Override public IWebFormController getController() { return formController; } @Override public boolean isDisplayingMoreThanOneRecord() { return false; } @Override public void destroy() { if (dataAdapterList != null) dataAdapterList.destroy(); Collection<WebComponent> componentsList = new ArrayList<WebComponent>(components.values()); for (WebComponent c : componentsList) { c.dispose(); } components.clear(); cleanupListeners(); } @Override public void setModel(IFoundSetInternal fs) { DefaultNavigatorWebComponent nav = (DefaultNavigatorWebComponent)components.get(DefaultNavigator.NAME_PROP_VALUE); if (nav != null) { nav.newFoundset(fs); } if (fs == null) { // no data should be shown anymore, (form is made invisible) dataAdapterList.setRecord(null, false); } } @Override public void setComponentEnabled(boolean enabled) { setEnabled(enabled); } @Override public void setReadOnly(boolean readOnly) { propagatePropertyToAllComponents(READONLY, readOnly); } private void propagatePropertyToAllComponents(String property, boolean value) { for (WebComponent component : components.values()) { Object newValue = Boolean.valueOf(value); if (READONLY.equals(property)) { Object readonlyproperty = component.getProperty(READONLY); if (readonlyproperty instanceof ReadonlySabloValue) { ReadonlySabloValue oldValue = (ReadonlySabloValue)readonlyproperty; //use the rhino conversion to convert from boolean to ReadOnlySabloValue PropertyDescription pd = ((WebFormComponent)component).getFormElement().getWebComponentSpec().getProperty(READONLY); if (pd != null) newValue = ReadonlyPropertyType.INSTANCE.toSabloComponentValue(Boolean.valueOf(value), oldValue, pd, component); } } component.setProperty(property, newValue); } } private ElementScope getElementsScope() { FormScope formScope = formController.getFormScope(); if (formScope != null) { return (ElementScope)formScope.get("elements", null); } return null; } public void setParentContainer(WebFormComponent parentContainer) { if (this.parentContainerOrWindowName == parentContainer) return; cleanupListeners(); this.parentContainerOrWindowName = parentContainer; if (parentContainer != null) { parentReadOnlyListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { ((BasicFormController)getController()).setReadOnly((boolean)evt.getNewValue()); } }; parentContainer.addPropertyChangeListener(READONLY, parentReadOnlyListener); // set readonly state from form manager, just like in wc/sc ((BasicFormController)getController()).setReadOnly(formController.isReadOnly()); } NGEnabledSabloValue ngSabloValue = (NGEnabledSabloValue)getRawPropertyValue(ENABLED, false); ngSabloValue.flagChanged(this, ENABLED); } /** * Clean up editable and readonly listners that we added to the parent container before changing it. */ private void cleanupListeners() { if (parentContainerOrWindowName instanceof WebFormComponent && parentReadOnlyListener != null) { WebFormComponent parent = (WebFormComponent)parentContainerOrWindowName; parent.removePropertyChangeListener(READONLY, parentReadOnlyListener); } } public Object getParentContainer() { return parentContainerOrWindowName; } @Override public String getParentWindowName() { if (parentContainerOrWindowName instanceof String) { return (String)parentContainerOrWindowName; } else if (parentContainerOrWindowName instanceof WebFormComponent) { return ((WebFormComponent)parentContainerOrWindowName).findParent(IWebFormUI.class).getParentWindowName(); } return null; } public void setParentWindowName(String parentWindowName) { cleanupListeners(); this.parentContainerOrWindowName = parentWindowName; NGEnabledSabloValue ngSabloValue = (NGEnabledSabloValue)getRawPropertyValue(ENABLED, false); ngSabloValue.flagChanged(this, ENABLED); } @Override public void setComponentVisible(boolean visible) { setVisible(visible); } @Override public boolean notifyVisible(boolean visible, List<Runnable> invokeLaterRunnables) { // 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 WebFormComponent boolean retValue = true; Set<IWebFormController> childFormsThatWereNotified = new HashSet<>(); for (WebComponent component : components.values()) { // childFormsThatWereNotified will be populated with forms that are notified below retValue = retValue && ((WebFormComponent)component).notifyVisible(visible, invokeLaterRunnables, childFormsThatWereNotified); } if (retValue) setVisible(visible); // childFormsThatWereNotified is given here to avoid double calling for example onHide on the same form if the form's onHide returns false the first time dataAdapterList.notifyVisible(visible, invokeLaterRunnables, childFormsThatWereNotified); return retValue; } @Override public void setLocation(Point location) { // TODO Auto-generated method stub } @Override public Point getLocation() { // TODO Auto-generated method stub return null; } @Override public void setSize(Dimension size) { // TODO Auto-generated method stub } @Override public Dimension getSize() { // TODO Auto-generated method stub return null; } @Override public void setForeground(Color foreground) { // TODO Auto-generated method stub } @Override public Color getForeground() { // TODO Auto-generated method stub return null; } @Override public void setBackground(Color background) { // TODO Auto-generated method stub } @Override public Color getBackground() { // TODO Auto-generated method stub return null; } @Override public void setFont(Font font) { // TODO Auto-generated method stub } @Override public Font getFont() { // TODO Auto-generated method stub return null; } @Override public void setBorder(Border border) { // TODO Auto-generated method stub } @Override public Border getBorder() { // TODO Auto-generated method stub return null; } @Override public void setName(String name) { // TODO Auto-generated method stub } @Override public void setOpaque(boolean opaque) { // TODO Auto-generated method stub } @Override public boolean isOpaque() { // TODO Auto-generated method stub return false; } @Override public void setCursor(Cursor cursor) { // TODO Auto-generated method stub } @Override public void setToolTipText(String tooltip) { // TODO Auto-generated method stub } @Override public String getToolTipText() { // TODO Auto-generated method stub return null; } @Override public String getId() { // TODO Auto-generated method stub return null; } @Override public boolean showYesNoQuestionDialog(IApplication application, String dlgMessage, String string) { // TODO Auto-generated method stub return false; } @Override public String getContainerName() { if (parentContainerOrWindowName instanceof String) { return (String)parentContainerOrWindowName; } if (parentContainerOrWindowName instanceof WebFormComponent && ((WebFormComponent)parentContainerOrWindowName).getParent() != null) { return ((WebFormComponent)parentContainerOrWindowName).findParent(IWebFormUI.class).getContainerName(); } return null; } @Override public void printPreview(boolean showDialogs, boolean printCurrentRecordOnly, int zoomFactor, PrinterJob printerJob) { // TODO Auto-generated method stub } @Override public void print(boolean showDialogs, boolean printCurrentRecordOnly, boolean showPrinterSelectDialog, PrinterJob printerJob) { // TODO Auto-generated method stub } @Override public String printXML(boolean printCurrentRecordOnly) { // TODO Auto-generated method stub return null; } @Override public void showSortDialog(IApplication application, String options) { // TODO Auto-generated method stub } @Override public int getFormWidth() { Dimension size = (Dimension)properties.get("size"); if (size != null) return size.width; return formController.getForm().getWidth(); } @Override public int getPartHeight(int partType) { int totalHeight = 0; int bodyHeight = 0; for (Part part : Utils.iterate(formController.getForm().getParts())) { if (partType != Part.BODY) { if (part.getPartType() == partType) { return part.getHeight() - totalHeight; } } if (part.getPartType() == Part.BODY) { bodyHeight = part.getHeight() - totalHeight; } totalHeight = part.getHeight(); } if (partType == Part.BODY) { Dimension size = (Dimension)properties.get("size"); if (size != null) return size.height - totalHeight + bodyHeight; return bodyHeight; } return 0; } @Override public JSDataSet getFormContext() { IDataSet set = new BufferedDataSet(new String[] { "windowname", "formname", "containername", "tabname", "tabindex", "tabindex1based" }, //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$//$NON-NLS-5$//$NON-NLS-6$ new ArrayList<Object[]>()); set.addRow(new Object[] { null, formController.getName(), null, null, null, null }); Object currentContainer = parentContainerOrWindowName; WebFormUI currentForm = this; while (currentContainer instanceof WebFormComponent) { WebFormComponent currentComponent = (WebFormComponent)currentContainer; int index = currentComponent.getFormIndex(currentForm); currentForm = currentComponent.findParent(WebFormUI.class); set.addRow(0, new Object[] { null, currentForm.formController.getName(), currentComponent.getName(), null, new Integer(index), new Integer(index + 1) }); currentContainer = currentForm.getParentContainer(); } if (currentContainer instanceof String) { // fill in window name for (int i = 0; i < set.getRowCount(); i++) { set.getRow(i)[0] = currentContainer; } } return new JSDataSet(formController.getApplication(), set); } @Override public void changeFocusIfInvalid(List<Runnable> invokeLaterRunnables) { // TODO Auto-generated method stub } @Override public void prepareForSave(boolean looseFocus) { // TODO Auto-generated method stub } @Override public void start(IApplication app) { // TODO Auto-generated method stub } @Override public void stop() { // TODO Auto-generated method stub } @Override public boolean editCellAt(int row) { // TODO Auto-generated method stub return false; } @Override public boolean stopUIEditing(boolean looseFocus) { // TODO Auto-generated method stub return false; } @Override public boolean isEditing() { // TODO Auto-generated method stub return false; } @Override public void requestFocus() { // TODO Auto-generated method stub } @Override public void ensureIndexIsVisible(int index) { // TODO Auto-generated method stub } @Override public void setEditable(boolean findMode) { // TODO Auto-generated method stub } @Override public Rectangle getVisibleRect() { // TODO Auto-generated method stub return null; } @Override public void setVisibleRect(Rectangle scrollPosition) { // TODO Auto-generated method stub } @Override public void setRowBGColorScript(String bgColorScript, List<Object> args) { // TODO Auto-generated method stub } @Override public String getRowBGColorScript() { // TODO Auto-generated method stub return null; } @Override public List<Object> getRowBGColorArgs() { // TODO Auto-generated method stub return null; } @Override public final void valueChanged() { getApplication().getChangeListener().valueChanged(); } @Override public void refreshValueList(IValueList valuelist) { for (WebComponent component : components.values()) { WebFormComponent comp = (WebFormComponent)component; Collection<PropertyDescription> valuelistProps = comp.getFormElement().getWebComponentSpec().getProperties(TypesRegistry.getType("valuelist")); for (PropertyDescription vlProp : valuelistProps) { ValueListTypeSabloValue propertyValue = (ValueListTypeSabloValue)comp.getProperty(vlProp.getName()); if (propertyValue != null) { IValueList vl = propertyValue.getValueList(); if (vl.getValueList() == valuelist.getValueList()) { propertyValue.setValueList(valuelist); } } } } } public void clearCachedFormElements() { cachedElements.clear(); } public List<FormElement> getFormElements() { if (cachedElements.size() == 0) { cachedElements = FormElementHelper.INSTANCE.getFormElements( new ArrayList<IPersist>(formController.getForm().getFlattenedObjects(PositionComparator.XY_PERSIST_COMPARATOR)).iterator(), getDataConverterContext()); } return cachedElements; } @Override public String toString() { return "FormUI for " + (formController != null ? formController.toString() : ""); } }