/* * � Copyright IBM Corp. 2011 * * 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.designer.tooling.panels.dataview; import static com.ibm.xsp.extlib.designer.tooling.constants.IExtLibAttrNames.EXT_LIB_ATTR_DATA; import static com.ibm.xsp.extlib.designer.tooling.constants.IExtLibAttrNames.EXT_LIB_ATTR_VALUE; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.ibm.commons.iloader.node.DataChangeNotifier; import com.ibm.commons.iloader.node.DataNode; import com.ibm.commons.iloader.node.DataNode.ComputedField; import com.ibm.commons.iloader.node.IAttribute; import com.ibm.commons.iloader.node.IClassDef; import com.ibm.commons.iloader.node.ILoader; import com.ibm.commons.iloader.node.IMember; import com.ibm.commons.iloader.node.NodeException; import com.ibm.commons.iloader.node.collections.SingleCollection; import com.ibm.commons.iloader.node.views.DataNodeBinding; import com.ibm.commons.swt.SWTLayoutUtils; import com.ibm.commons.swt.SWTUtils; import com.ibm.commons.swt.data.controls.DCComboBox; import com.ibm.commons.swt.data.controls.DCUtils; import com.ibm.commons.util.StringUtil; import com.ibm.designer.domino.scripting.api.IScriptData.PublishedObject; import com.ibm.designer.domino.scripting.api.published.PublishedUtil; import com.ibm.designer.domino.xsp.api.panels.complex.ComplexPanelComposite; import com.ibm.designer.domino.xsp.api.util.XPagesDOMUtil; import com.ibm.designer.domino.xsp.api.util.XPagesDataUtil; import com.ibm.designer.domino.xsp.api.util.XPagesKey; import com.ibm.designer.domino.xsp.registry.ComplexDesignerExtension; import com.ibm.designer.domino.xsp.registry.DesignerExtensionUtil; import com.ibm.designer.ide.xsp.components.api.panels.XSPPropLayout2; import com.ibm.designer.prj.resources.commons.DesignerProjectException; import com.ibm.xsp.extlib.designer.tooling.utils.ExtLibToolingLogger; import com.ibm.xsp.extlib.designer.tooling.utils.XPagesKeyLookup; import com.ibm.xsp.registry.FacesComplexDefinition; import com.ibm.xsp.registry.FacesDefinition; /** * A complex properties panel that is used to display information about the data source for * a viewData (and dynamicViewPanel) control. The data source for the control can be defined in one of two places; * either within the control as a complex property of the 'data' property, or in one of the panels * that contains the viewData control. If the data source is defined external to the current control * then the value attribute of the viewData contol will be set, and will be in the form * value="#{viewDataSourceName}". * */ public class DataViewDataPanel extends XSPPropLayout2 { //we want two columns //A composite that can contain a sub panel which is contributed via extension point private ComplexPanelComposite _dynamicComposite = null; //The data node for the current control (viewData) private DataNode _viewDataDataNode = null; //The data node for the current data source private DataNode _dataSourceDataNode = null; //A computed field that operates on the data node. As different data source options //are available for the control, this field represents the contents of the "Show data from:" //combo box. This field catches the getValue and setValue operations and reacts to those events private DataSourceTypeField dataSourceType = null; //The lookup used to populate the "Show data from" combo box. private XPagesKeyLookup dataLookup = null; //A key used to store information about the currently selected data source private XPagesKey key = null; /* * Constants */ private final static String DS_TYPE_FLD_NAME = "datasource"; //$NON-NLS-1$ private final static String PAGE_DS_NS = "p.d.s.n.s"; //$NON-NLS-1$ private final static String SEPARATOR= "-----------------------"; //$NON-NLS-1$ /** * A computed field that is used to get and set the "Show data from" combo box. * @author doconnor * */ private class DataSourceTypeField extends ComputedField{ public DataSourceTypeField() { //The 'field name' is "datasource". The combo box that is going to use //this computed field must set "datasource" as its attribute name super(DS_TYPE_FLD_NAME, IMember.TYPE_STRING); } /* (non-Javadoc) * @see com.ibm.commons.iloader.node.DataNode.ComputedField#getValue(java.lang.Object) */ @Override public String getValue(Object instance) throws NodeException { //This method is called by the combo box when it is about to display for the first time //It returns the current value of the combo box. The code below looks at the contents //of the dataView tag and returns information about the data source based on the tag contents if(instance instanceof Element){ Element element = (Element)instance; //Get the value attribute from the dataView tag String value = XPagesDOMUtil.getAttribute(element, EXT_LIB_ATTR_VALUE); if(StringUtil.isEmpty(value)){ //If the value is not set, then maybe the 'data' complex attribute is set? Element child = XPagesDOMUtil.getAttributeElement(element, EXT_LIB_ATTR_DATA); if(child != null){ /* * <this.data> has been defined.. need to figure out which data source is embedded? */ NodeList nl = child.getChildNodes(); if(nl != null && nl.getLength() > 0){ for(int i = 0; i < nl.getLength(); i++){ Node n = nl.item(i); if(n.getNodeType() == Node.ELEMENT_NODE){ FacesDefinition def = XPagesDOMUtil.getFacesDefinition((Element)n, getExtraData().getDesignerProject().getFacesRegistry()); if(def != null){ if(_dataSourceDataNode != null){ ILoader loader = _dataSourceDataNode.getLoader(); IClassDef cdef = loader.loadClass(n.getNamespaceURI(), n.getLocalName()); _dataSourceDataNode.setClassDef(cdef); _dataSourceDataNode.setDataProvider(new SingleCollection(n)); } //this is a known data source! String name = DesignerExtensionUtil.getDefinitionExtension(def).getDisplayName(); if (dataLookup != null) { //We have the name of the data source.. Now compare that with the names in the lookup (in the combo box) //to see if we have a match - we should! for(int index = 0; index < dataLookup.size(); index++){ if(StringUtil.equals(dataLookup.getLabel(index), name)){ //match found.. now get the XPagesKey from the lookup key = dataLookup.getKey(index); break; } } } return name; } } } } } } else{ //value attribute will be of the form #{viewDataSourceName}.. strip off extra info if(value.startsWith("#{") && value.endsWith("}")){ value = value.substring(2, value.length() - 1); for(int index = 0; index < dataLookup.size(); index++){ //figure out which data source was selected from the combo box if(StringUtil.equals(dataLookup.getLabel(index), value)){ key = dataLookup.getKey(index); //get the key break; } } return value; } } } return ""; } /* (non-Javadoc) * @see com.ibm.commons.iloader.node.DataNode.ComputedField#setValue(java.lang.Object, java.lang.String, com.ibm.commons.iloader.node.DataChangeNotifier) */ @Override public void setValue(Object instance, String value, DataChangeNotifier notifier) throws NodeException { if(StringUtil.equals(value, SEPARATOR)){ return;//do nothing! } //This method will be called when the user changes selection in the "Using data from:" combobox try{ //Get the data attribute and the value attribute IMember dataAttr = _viewDataDataNode.getMember(EXT_LIB_ATTR_DATA); IMember valueAttr = _viewDataDataNode.getMember(EXT_LIB_ATTR_VALUE); //get the loader that is used to generate new Elements on the page ILoader loader = _viewDataDataNode.getLoader(); if(StringUtil.isEmpty(value)){ //Clear all attribute values.. set everything back to null! loader.setValue(instance, (IAttribute)dataAttr, null, null); _viewDataDataNode.setValue((IAttribute)valueAttr, null, null); key = null; return; } else{ if (dataLookup != null) { for(int index = 0; index < dataLookup.size(); index++){ //figure out which data source was selected from the combo box if(StringUtil.equals(dataLookup.getLabel(index), value)){ key = dataLookup.getKey(index); //get the key if(StringUtil.equals(key.getNamespaceUri(), PAGE_DS_NS)){ //This means the user has picked a data source that is defined at the page level if(valueAttr instanceof IAttribute){ String newVal = "#{" + value + "}"; _viewDataDataNode.setValue((IAttribute)valueAttr, newVal, null); //clear the data attribute also! loader.setValue(instance, (IAttribute)dataAttr, null, null); } }else{ //need to clear the value attribute in case it was previously set _viewDataDataNode.setValue((IAttribute)valueAttr, null, null); //Get a class defintion for a new instance of the given tag (probably xp:dominoView) IClassDef def = loader.loadClass(key.getNamespaceUri(), key.getTagName()); Object o = def.newInstance(getDataNode().getCurrentObject()); //create a new tag //in this case we know that 'data' is a 'complex attribute'.. but lets make sure if(dataAttr instanceof IAttribute && dataAttr.getType() == IMember.TYPE_OBJECT){ //add a <xp:this.data> to the current viewData tag and add the data source as a child of that! _viewDataDataNode.setObject(instance, (IAttribute)dataAttr, o, notifier); Element e = (Element)o; String[] vars = XPagesDOMUtil.getVars(e.getOwnerDocument(), null); String var = XPagesDOMUtil.generateUniqueVar(Arrays.asList(vars), e, "view"); // $NON-NLS-1$ XPagesDOMUtil.setAttribute(e, "var", var); // $NON-NLS-1$ _dataSourceDataNode.setClassDef(def); _dataSourceDataNode.setDataProvider(new SingleCollection(o)); } } //our work is done.. break; } } } } }finally{ //Format the tag and update the UI if(instance instanceof Element){ XPagesDOMUtil.formatNode((Element)instance, null); } updateContentsBasedOnKey(false); } } /* (non-Javadoc) * @see com.ibm.commons.iloader.node.DataNode.ComputedField#shouldRecompute(java.lang.Object, java.lang.Object, int, com.ibm.commons.iloader.node.IMember, int) */ @Override public boolean shouldRecompute(Object instance, Object object, int operation, IMember member, int position) { //we are only interested in changes to the value and the data attributes if(member != null && (StringUtil.equals(member.getName(), EXT_LIB_ATTR_VALUE) || StringUtil.equals(member.getName(), EXT_LIB_ATTR_DATA))){ return true; } return false; } /* (non-Javadoc) * @see com.ibm.commons.iloader.node.DataNode.ComputedField#isReadOnly() */ @Override public boolean isReadOnly() { return false; } } /** * @param parent * @param style */ public DataViewDataPanel(Composite parent, int style) { super(parent, style, true, false); } /* (non-Javadoc) * @see com.ibm.commons.swt.data.layouts.PropLayout1#createContents(org.eclipse.swt.widgets.Composite) */ @Override protected void createLeftContents(Composite parent) { initDataNode(parent); Label l = createLabel("The data source determines what data is shown in the view", null); // $NLX-DataViewDataPanel.Thedatasourcedetermineswhatdatais-1$ GridData data = SWTLayoutUtils.createGDFillHorizontalNoGrab(); data.horizontalSpan = 2; l.setLayoutData(data); Label sep = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); sep.setLayoutData(GridDataFactory.copyData(data)); Label label1 = new Label(parent, SWT.NONE); label1.setText("Show data from:"); // $NLX-DataViewDataPanel.Showdatafrom-1$ GridData gd = createControlGDNoWidth(1); gd.horizontalIndent = 0; label1.setLayoutData(gd); //Create a combo box that can be bound to data.. In this case the data is a computed field.. //the computed field will take care of getting and setting the values of the combo box in the model DCComboBox queryTypeCombo = new DCComboBox(parent, SWT.READ_ONLY); queryTypeCombo.setFirstBlankLine(true); //Need to set the lookup before the data attribute name //so the model can find the item in the lookup when the attribute is set queryTypeCombo.setLookup(dataLookup); /* * In this combo box we will want to show all of the data sources that have been defined * at the xpage level (or indeed in an data containing panels that are in the hierarchy of * this control). Depending on what the user selects in the combo box we will want * to dynamically populate the contents the area below the combo box * To do this we will need to add a ComplexPanelComposite * The ComplexPanelComposite queries it's parent for a DataNode. The DataNode is used * to populate the contents of the ComplexPanelComposite */ Composite middleParent = new Composite(parent, SWT.NONE); initDataNodeForComplexComp(middleParent); queryTypeCombo.setAttributeName(DS_TYPE_FLD_NAME); middleParent.setLayout(SWTLayoutUtils.createLayoutNoMarginDefaultSpacing(1)); data = SWTLayoutUtils.createGDFillNoGrab(); data.horizontalSpan = 2; middleParent.setLayoutData(data); _dynamicComposite = new ComplexPanelComposite(middleParent, SWT.NONE); _dynamicComposite.setLayoutData(SWTLayoutUtils.createGDFill()); _dynamicComposite.setBackground(SWTUtils.getBackgroundColor(_dynamicComposite)); _dynamicComposite.updatePanelData(getExtraData()); //Update the contents of the ComplexComposite based on the attributes of the viewData tag updateContentsBasedOnKey(true); } /** * Create a DataNode to be used by the ComplexPanelComposite. This DataNode should refer to the datasource * that is a child of the currently selected tag. The ComplexPanelComposite queries it's parent for a DataNode. * If the ComplexPanelComposite calculates that a panel has not been contributed for the tag that it is trying to * display a panel for then it will show the all properties tree for the current DataNode. In this case we want to display * the all properties for the data source not the currently selected tag. * @param parent */ private void initDataNodeForComplexComp(Composite parent){ DCUtils.initDataBinding(parent); DataNodeBinding dnb = DCUtils.findDataNodeBinding(parent, true); _dataSourceDataNode = dnb.getDataNode(); _dataSourceDataNode.setClassDef(_viewDataDataNode.getClassDef()); _dataSourceDataNode.setDataProvider(_viewDataDataNode.getDataProvider()); } /** * Initialize the ILookup for the combo box, create a computed field and assign that * computed field as the data modifier to be used by the combo box. * @param parent */ private void initDataNode(Composite parent){ updateLookup(); if(_viewDataDataNode == null){ //get the DataNode representing the viewData tag DataNodeBinding dnb = DCUtils.findDataNodeBinding(parent, true); _viewDataDataNode = dnb.getDataNode(); dataSourceType = new DataSourceTypeField(); _viewDataDataNode.addComputedField(dataSourceType); } } private void updateLookup(){ //Get all of the data sources that support view data List<FacesDefinition> defs = XPagesDataUtil.getViewPanelDataSources(getExtraData().getDesignerProject().getFacesRegistry()); if(defs != null){ ArrayList<String>names = new ArrayList<String>(); ArrayList<XPagesKey>keys = new ArrayList<XPagesKey>(); for(FacesDefinition def : defs){ String name = def.getTagName(); if(def instanceof FacesComplexDefinition){ //Get the display name for the data sources ComplexDesignerExtension extsn = DesignerExtensionUtil.getComplexExtension((FacesComplexDefinition) def); if (extsn != null) { name = StringUtil.getNonNullString(extsn.getDisplayName()); } } names.add(name); keys.add(new XPagesKey(def.getNamespaceUri(), def.getTagName())); } Map <String, PublishedObject> map = new HashMap<String, PublishedObject>(); try { //Get all of the view data sources already defined in the XSP hierarchy PublishedUtil.getAllPublishedObjects(map, getExtraData().getNode(), getExtraData().getDesignerProject(), false); } catch (DesignerProjectException e) { if(ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()){ ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.errorp(this, "updateLookup", e, "Failed to find any data sources defined on the page.. An error was encountered by the published object utilities"); // $NON-NLS-1$ $NLE-DataViewDataPanel.Failedtofindanydatasourcesdefined-2$ } } if(!map.isEmpty()){ boolean markerAdded = false; Set<String> dsNames = map.keySet(); for(String name : dsNames){ PublishedObject po = map.get(name); if(PublishedUtil.isViewDataSupported(po)){ if(!markerAdded){ names.add(SEPARATOR); keys.add(new XPagesKey(SEPARATOR, SEPARATOR)); markerAdded = true; } names.add(name); //These are special data sources.. Page level data sources.. We will use a complex panel to display the information //to be displayed in the event of one of these being used.. //See: com.ibm.xsp.extlib.designer.tooling.panels.complex.PageDataSourcePanel keys.add(new XPagesKey(PAGE_DS_NS, "pageDataSource")); // $NON-NLS-1$ } } } dataLookup = new XPagesKeyLookup(keys.toArray(new XPagesKey[0]), names.toArray(new String[0])); } } private void updateContentsBasedOnKey(final boolean skipRefresh){ if(_dynamicComposite != null){ String ns = null; String tag = null; if(key != null){ ns = key.getNamespaceUri(); tag = key.getTagName(); } _dynamicComposite.setContextId(null); _dynamicComposite.updatePanel(ns, tag); //It is possible (likely) that we removed a data source or added one... //in such a case the UI changed drastically.. //We need to re-layout the UI in order to update the requirement //for scrollbars.. //Walk through the composite hierarchy until we get a scrollable parent.. //once found reset the min size for the scrolled parent. getDisplay().asyncExec(new Runnable() { public void run() { if (DataViewDataPanel.this.isDisposed()) { return; } _dynamicComposite.pack(); _dynamicComposite.layout(); Composite parent = _dynamicComposite.getParent(); Composite prevParent = parent; while (parent != null && !parent.isDisposed()) { try { if (parent instanceof ScrolledComposite) { ((ScrolledComposite) parent).setMinSize(prevParent.computeSize(SWT.DEFAULT, SWT.DEFAULT)); break; } if(parent.isDisposed()){ return; } parent.pack(); parent.layout(); } catch (Throwable t) { if(ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()){ ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.errorp(this, "updateContentsBasedOnKey", t, "Error encountered when refreshing UI"); // $NON-NLS-1$ $NLE-DataViewDataPanel.ErrorencounteredwhenrefeshingUI-2$ } } prevParent = parent; parent = parent.getParent(); } } }); } } public void dispose(){ //Remove the computed field just as a precaution.. if(_viewDataDataNode != null){ _viewDataDataNode.removeComputedField(dataSourceType); } } }