/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.scripting.solutionmodel;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import com.servoy.base.persistence.constants.IValueListConstants;
import com.servoy.base.solutionmodel.IBaseSMMethod;
import com.servoy.base.solutionmodel.IBaseSMValueList;
import com.servoy.j2db.FlattenedSolution;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.documentation.ServoyDocumented;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.ScriptMethod;
import com.servoy.j2db.persistence.ScriptNameValidator;
import com.servoy.j2db.persistence.Solution;
import com.servoy.j2db.persistence.ValueList;
import com.servoy.j2db.scripting.IConstantsObject;
import com.servoy.j2db.solutionmodel.ISMValueList;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.UUID;
/**
* @author jcompagner
*/
@ServoyDocumented(category = ServoyDocumented.RUNTIME)
public class JSValueList implements IConstantsObject, ISMValueList
{
private ValueList valuelist;
private final IApplication application;
private boolean isCopy;
public JSValueList(ValueList valuelist, IApplication application, boolean isNew)
{
this.valuelist = valuelist;
this.application = application;
this.isCopy = isNew;
}
void checkModification()
{
// TODO already in use check???
// make copy if needed
if (!isCopy)
{
valuelist = application.getFlattenedSolution().createPersistCopy(valuelist);
isCopy = true;
}
}
ValueList getValueList()
{
return valuelist;
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getAddEmptyValue()
*
* @sample
* var vlist = solutionModel.newValueList('options', JSValueList.CUSTOM_VALUES);
* vlist.customValues = "one\ntwo\nthree\nfour";
* vlist.addEmptyValue = JSValueList.EMPTY_VALUE_NEVER;
* var cmb = form.newComboBox('my_table_text', 10, 10, 100, 20);
* cmb.valuelist = vlist;
*/
@JSGetter
public int getAddEmptyValue()
{
return valuelist.getAddEmptyValue();
}
@JSSetter
public void setAddEmptyValue(int arg)
{
checkModification();
valuelist.setAddEmptyValue(arg);
}
protected void setDisplayDataProviderIdsInternal(String[] ids)
{
checkModification();
if (ids != null && ids.length > 3) throw new IllegalArgumentException("max 3 ids allowed in the display dataproviders ids"); //$NON-NLS-1$
clearUnusedDataProviders(valuelist.getReturnDataProviders());
if (ids == null || ids.length == 0)
{
valuelist.setShowDataProviders(0);
}
else
{
int showId = 0;
for (int i = 0; i < ids.length; i++)
{
String id = ids[i];
String currentId = valuelist.getDataProviderID1();
if (i < 1 && testId(id, currentId))
{
showId += 1;
valuelist.setDataProviderID1(id);
}
else
{
// dataprovider1 is used as a return value test dataprovider2
currentId = valuelist.getDataProviderID2();
if (i < 2 && testId(id, currentId))
{
showId += 2;
valuelist.setDataProviderID2(id);
}
else
{
// dataprovider2 is used as a return value test dataprovider3
currentId = valuelist.getDataProviderID3();
if (i < 3 && testId(id, currentId))
{
showId += 4;
valuelist.setDataProviderID3(id);
}
else
{
throw new RuntimeException("Cant set display values all slots are used by return dataproviders"); //$NON-NLS-1$
}
}
}
}
valuelist.setShowDataProviders(showId);
}
}
/**
* @clonedesc setDisplayDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
*/
@JSFunction
public void setDisplayDataProviderIds()
{
setDisplayDataProviderIdsInternal(null);
}
/**
* @clonedesc setDisplayDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first display dataprovider.
*/
@JSFunction
public void setDisplayDataProviderIds(String dataprovider1)
{
setDisplayDataProviderIdsInternal(new String[] { dataprovider1 });
}
/**
* @clonedesc setDisplayDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first display dataprovider.
*
* @param dataprovider2 The second display dataprovider.
*/
@JSFunction
public void setDisplayDataProviderIds(String dataprovider1, String dataprovider2)
{
setDisplayDataProviderIdsInternal(new String[] { dataprovider1, dataprovider2 });
}
/**
* Set the display dataproviders. There can be at most 3 of them, combined with the return dataproviders.
* The values taken from these dataproviders, in order, separated by the separator, will be displayed
* by the valuelist.
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first display dataprovider.
*
* @param dataprovider2 The second display dataprovider.
*
* @param dataprovider3 The third display dataprovider.
*/
@JSFunction
public void setDisplayDataProviderIds(String dataprovider1, String dataprovider2, String dataprovider3)
{
setDisplayDataProviderIdsInternal(new String[] { dataprovider1, dataprovider2, dataprovider3 });
}
private void clearUnusedDataProviders(int valueToKeep)
{
if (valuelist != null)
{
if ((valueToKeep & 1) != 1)
{
valuelist.setDataProviderID1(null);
}
if ((valueToKeep & 2) != 2)
{
valuelist.setDataProviderID2(null);
}
if ((valueToKeep & 4) != 4)
{
valuelist.setDataProviderID3(null);
}
}
}
private boolean testId(String wantedId, String currentId)
{
return wantedId.equals(currentId) || currentId == null;
}
/**
* Returns an array of the dataproviders that will be used to display the valuelist value.
*
* @sample
* var vlist = solutionModel.newValueList('options', JSValueList.DATABASE_VALUES);
* vlist.dataSource = 'db:/example_data/parent_table';
* vlist.setDisplayDataProviderIds('parent_table_text', 'parent_table_id');
* vlist.setReturnDataProviderIds('parent_table_text');
* var dispDP = vlist.getDisplayDataProviderIds();
* for (var i=0; i<dispDP.length; i++)
* application.output(dispDP[i]);
* var retDP = vlist.getReturnDataProviderIds();
* for (var i=0; i<retDP.length; i++)
* application.output(retDP[i]);
*
* @return An array of Strings representing the names of the display dataproviders.
*/
@JSFunction
public Object[] getDisplayDataProviderIds()
{
return getDataProviders(valuelist.getShowDataProviders());
}
protected void setReturnDataProviderIdsInternal(String[] ids)
{
checkModification();
if (ids != null && ids.length > 3) throw new IllegalArgumentException("max 3 ids allowed in the display dataproviders ids"); //$NON-NLS-1$
clearUnusedDataProviders(valuelist.getShowDataProviders());
if (ids == null || ids.length == 0)
{
valuelist.setReturnDataProviders(0);
}
else
{
int returnId = 0;
for (int i = 0; i < ids.length; i++)
{
String id = ids[i];
String currentId = valuelist.getDataProviderID1();
if (i < 1 && testId(id, currentId))
{
returnId += 1;
valuelist.setDataProviderID1(id);
}
else
{
// dataprovider1 is used as a return value test dataprovider2
currentId = valuelist.getDataProviderID2();
if (i < 2 && testId(id, currentId))
{
returnId += 2;
valuelist.setDataProviderID2(id);
}
else
{
// dataprovider2 is used as a return value test dataprovider3
currentId = valuelist.getDataProviderID3();
if (i < 3 && testId(id, currentId))
{
returnId += 4;
valuelist.setDataProviderID3(id);
}
else
{
throw new RuntimeException("Cant set display values all slots are used by other dataproviders (3 max combined)"); //$NON-NLS-1$
}
}
}
}
valuelist.setReturnDataProviders(returnId);
}
}
/**
* @clonedesc setReturnDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSFunction
public void setReturnDataProviderIds()
{
setReturnDataProviderIdsInternal(null);
}
/**
* @clonedesc setReturnDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first return dataprovider.
*/
@JSFunction
public void setReturnDataProviderIds(String dataprovider1)
{
setReturnDataProviderIdsInternal(new String[] { dataprovider1 });
}
/**
* @clonedesc setReturnDataProviderIds(String, String, String)
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first return dataprovider.
*
* @param dataprovider2 The second return dataprovider.
*/
@JSFunction
public void setReturnDataProviderIds(String dataprovider1, String dataprovider2)
{
setReturnDataProviderIdsInternal(new String[] { dataprovider1, dataprovider2 });
}
/**
* Set the return dataproviders. There can be at most 3 of them, combined with the display dataproviders.
* The values taken from these dataproviders, in order, separated by the separator, will be returned
* by the valuelist.
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*
* @param dataprovider1 The first return dataprovider.
*
* @param dataprovider2 The second return dataprovider.
*
* @param dataprovider3 The third return dataprovider.
*/
@JSFunction
public void setReturnDataProviderIds(String dataprovider1, String dataprovider2, String dataprovider3)
{
setReturnDataProviderIdsInternal(new String[] { dataprovider1, dataprovider2, dataprovider3 });
}
/**
* Returns an array of the dataproviders that will be used to define the valuelist value that is saved.
*
* @sampleas getDisplayDataProviderIds()
*
* @return An array of Strings representing the names of the return dataprovider.
*/
@JSFunction
public Object[] getReturnDataProviderIds()
{
return getDataProviders(valuelist.getReturnDataProviders());
}
private String[] getDataProviders(int selection)
{
ArrayList<String> dataproviders = new ArrayList<String>();
if ((selection & 1) == 1 && valuelist.getDataProviderID1() != null)
{
dataproviders.add(valuelist.getDataProviderID1());
}
if ((selection & 2) == 2 && valuelist.getDataProviderID2() != null)
{
dataproviders.add(valuelist.getDataProviderID2());
}
if ((selection & 4) == 4 && valuelist.getDataProviderID3() != null)
{
dataproviders.add(valuelist.getDataProviderID3());
}
return dataproviders.toArray(new String[dataproviders.size()]);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getCustomValues()
*
* @sample
* var vl1 = solutionModel.newValueList("customtext",JSValueList.CUSTOM_VALUES);
* vl1.customValues = "customvalue1\ncustomvalue2";
* var vl2 = solutionModel.newValueList("customid",JSValueList.CUSTOM_VALUES);
* vl2.customValues = "customvalue1|1\ncustomvalue2|2";
* var form = solutionModel.newForm("customvaluelistform",controller.getDataSource(),null,true,300,300);
* var combo1 = form.newComboBox("scopes.globals.text",10,10,120,20);
* combo1.valuelist = vl1;
* var combo2 = form.newComboBox("scopes.globals.id",10,60,120,20);
* combo2.valuelist = vl2;
*/
@JSGetter
public String getCustomValues()
{
return valuelist.getCustomValues();
}
@JSSetter
public void setCustomValues(String arg)
{
checkModification();
valuelist.setCustomValues(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getName()
*
* @sampleas getUseTableFilter()
*/
@JSGetter
public String getName()
{
return valuelist.getName();
}
@JSSetter
public void setName(String arg)
{
checkModification();
try
{
valuelist.updateName(new ScriptNameValidator(application.getFlattenedSolution()), arg);
}
catch (RepositoryException e)
{
Debug.error("Failed to update name of valuelist to '" + arg + "'.", e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getRelationName()
*
* @sample
* var rel = solutionModel.newRelation('parent_to_child', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN);
* rel.newRelationItem('parent_table_id', '=', 'child_table_parent_id');
*
* var vlist = solutionModel.newValueList('options', JSValueList.DATABASE_VALUES);
* vlist.dataSource = 'db:/example_data/parent_table';
* vlist.relationName = 'parent_to_child';
* vlist.setDisplayDataProviderIds('child_table_text');
* vlist.setReturnDataProviderIds('child_table_text');
*/
@JSGetter
public String getRelationName()
{
return valuelist.getRelationName();
}
@JSSetter
public void setRelationName(String arg)
{
checkModification();
valuelist.setRelationName(arg);
}
/**
* @deprecated relationName supports multiple levels relations
*/
@Deprecated
public String js_getRelationNMName()
{
return valuelist.getRelationNMName();
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getSeparator()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSGetter
public String getSeparator()
{
return valuelist.getSeparator();
}
@JSSetter
public void setSeparator(String arg)
{
checkModification();
valuelist.setSeparator(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getServerName()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSGetter
public String getServerName()
{
return valuelist.getServerName();
}
@JSSetter
public void setServerName(String arg)
{
checkModification();
valuelist.setServerName(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getSortOptions()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSGetter
public String getSortOptions()
{
return valuelist.getSortOptions();
}
@JSSetter
public void setSortOptions(String arg)
{
checkModification();
valuelist.setSortOptions(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getTableName()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSGetter
public String getTableName()
{
return valuelist.getTableName();
}
@JSSetter
public void setTableName(String arg)
{
checkModification();
valuelist.setTableName(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getDataSource()
*
* @sample
* var vlist = solutionModel.newValueList('options', JSValueList.DATABASE_VALUES);
* vlist.dataSource = 'db:/example_data/parent_table';
* vlist.setDisplayDataProviderIds('parent_table_text');
* vlist.setReturnDataProviderIds('parent_table_text');
*/
@JSGetter
public String getDataSource()
{
return valuelist.getDataSource();
}
@JSSetter
public void setDataSource(String arg)
{
// check syntax, do not accept invalid URIs
if (arg != null)
{
try
{
new URI(arg);
}
catch (URISyntaxException e)
{
throw new RuntimeException("Invalid dataSourc1e URI: '" + arg + "' :" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
checkModification();
valuelist.setDataSource(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getUseTableFilter()
*
* @sample
* var vlist = solutionModel.newValueList('options', JSValueList.DATABASE_VALUES);
* vlist.dataSource = 'db:/example_data/valuelists';
* vlist.setDisplayDataProviderIds('valuelist_data');
* vlist.setReturnDataProviderIds('valuelist_data');
* vlist.useTableFilter = true;
* vlist.name = 'two';
*/
@JSGetter
public boolean getUseTableFilter()
{
return valuelist.getUseTableFilter();
}
@JSSetter
public void setUseTableFilter(boolean arg)
{
checkModification();
valuelist.setUseTableFilter(arg);
}
/**
* @clonedesc com.servoy.j2db.persistence.ValueList#getValueListType()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMValueList#DATABASE_VALUES
*/
@JSGetter
public int getValueListType()
{
return valuelist.getValueListType();
}
@JSSetter
public void setValueListType(int arg)
{
checkModification();
valuelist.setValueListType(arg);
}
/**
* A global method that provides the data for the valuelist. The global method must provided the data
* as a JSDataSet.
*
* It is called when the valuelist needs data, it has 3 modes.
* real and display params both null: return the whole list
* only display is specified, called by a typeahead, return a filtered list
* only real value is specified, called when the list doesnt contain the real value for the give record value, this will insert this value into the existing list.
*
* In find mode the record with be the FindRecord which is just like a normal JSRecord (DataRecord) it has the same properties (column/dataproviders) but doesnt have its methods (like isEditing())
*
* The last argument is rawDisplayValue which contains the same text as displayValue but without converting it to lowercase.
*
* @sample
* var listProvider = solutionModel.newGlobalMethod('globals', 'function getDataSetForValueList(displayValue, realValue, record, valueListName, findMode, rawDisplayValue) {' +
* ' ' +
* 'var args = null;' +
* 'var query = datasources.db.example_data.employees.createSelect();' +
* '/** @type {JSDataSet} */' +
* 'var result = null;' +
* 'if (displayValue == null && realValue == null) {' +
* ' // TODO think about caching this result. can be called often!' +
* ' // return the complete list' +
* ' query.result.add(query.columns.firstname.concat(' ').concat(query.columns.lastname)).add(query.columns.employeeid);' +
* ' result = databaseManager.getDataSetByQuery(query,100);' +
* '} else if (displayValue != null) {' +
* ' // TYPE_AHEAD filter call, return a filtered list' +
* ' args = [displayValue + "%", displayValue + "%"]' +
* ' query.result.add(query.columns.firstname.concat(' ').concat(query.columns.lastname)).add(query.columns.employeeid).' +
* ' root.where.add(query.or.add(query.columns.firstname.lower.like(args[0] + '%')).add(query.columns.lastname.lower.like(args[1] + '%')));' +
* ' result = databaseManager.getDataSetByQuery(query,100);' +
* '} else if (realValue != null) {' +
* ' // TODO think about caching this result. can be called often!' +
* ' // real object not found in the current list, return 1 row with display,realvalue that will be added to the current list' +
* ' // dont return a complete list in this mode because that will be added to the list that is already there' +
* ' args = [realValue];' +
* ' query.result.add(query.columns.firstname.concat(' ').concat(query.columns.lastname)).add(query.columns.employeeid).' +
* ' root.where.add(query.columns.employeeid.eq(args[0]));' +
* ' result = databaseManager.getDataSetByQuery(query,1);' +
* '}' +
* 'return result;' +
* '}');
* var vlist = solutionModel.newValueList('vlist', JSValueList.CUSTOM_VALUES);
* vlist.globalMethod = listProvider;
*/
@JSGetter
public JSMethod getGlobalMethod()
{
String uuid = valuelist.getCustomValues();
ScriptMethod scriptMethod = application.getFlattenedSolution().getScriptMethod(uuid);
if (scriptMethod != null)
{
return new JSMethod(scriptMethod, application, true);
}
return null;
}
@JSSetter
public void setGlobalMethod(IBaseSMMethod method)
{
checkModification();
if (method == null)
{
valuelist.setCustomValues(null);
valuelist.setValueListType(IValueListConstants.CUSTOM_VALUES);
}
else
{
ScriptMethod scriptMethod = ((JSMethod)method).getScriptMethod();
if (scriptMethod.getParent() instanceof Solution)
{
valuelist.setCustomValues(scriptMethod.getUUID().toString());
valuelist.setValueListType(IValueListConstants.GLOBAL_METHOD_VALUES);
}
else
{
throw new RuntimeException("Only global methods are supported for a valuelist"); //$NON-NLS-1$
}
}
}
/**
* Gets or sets the fallback valuelist.
*
* @sample
* var myValueList = solutionModel.getValueList('myValueListHere')
* //get fallback value list
* var fallbackValueList = myValueList.fallbackValueList
*/
@JSGetter
public JSValueList getFallbackValueList()
{
FlattenedSolution fs = application.getFlattenedSolution();
int fallbackVLID = valuelist.getFallbackValueListID();
ValueList fallbackVL = fs.getValueList(fallbackVLID);
if (fallbackVL != null)
{
return new JSValueList(fallbackVL, application, false);
}
else
{
return null;
}
}
@JSGetter
public void setFallbackValueList(IBaseSMValueList vl)
{
checkModification();
if (vl == null)
{
valuelist.setFallbackValueListID(0);
}
else
{
valuelist.setFallbackValueListID(((JSValueList)vl).getValueList().getID());
}
}
@Deprecated
public void js_setRelationNMName(String arg)
{
checkModification();
valuelist.setRelationNMName(arg);
}
/**
* Returns the UUID of the value list
*
* @sample
* var vlist = solutionModel.newValueList('options', JSValueList.CUSTOM_VALUES);
* application.output(vlist.getUUID().toString());
*/
@JSFunction
public UUID getUUID()
{
return valuelist.getUUID();
}
/**
* @see java.lang.Object#toString()
*/
@SuppressWarnings("nls")
@Override
public String toString()
{
int type = valuelist.getValueListType();
String typeString = "";
switch (type)
{
case IValueListConstants.CUSTOM_VALUES :
typeString = "Custom";
break;
case IValueListConstants.GLOBAL_METHOD_VALUES :
ScriptMethod globalMethod = application.getFlattenedSolution().getScriptMethod(valuelist.getCustomValues());
typeString = "GlobalMethod:" + globalMethod != null ? globalMethod.getPrefixedName() : valuelist.getCustomValues();
break;
case IValueListConstants.TABLE_VALUES :
typeString = valuelist.getDatabaseValuesType() == IValueListConstants.TABLE_VALUES ? "Table:" + valuelist.getDataSource() : "Related:" +
valuelist.getRelationName();
}
return "JSValueList[name:" + valuelist.getName() + ',' + typeString + ']';
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((valuelist == null) ? 0 : valuelist.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
JSValueList other = (JSValueList)obj;
if (valuelist == null)
{
if (other.valuelist != null) return false;
}
else if (!valuelist.getUUID().equals(other.valuelist.getUUID())) return false;
return true;
}
}