Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1209d6b
TableViewForm no longer implements DynaBean
labkey-matthewb Oct 24, 2025
3942f7b
Merge remote-tracking branch 'origin/develop' into fb_tvf_string_array
labkey-matthewb Oct 24, 2025
34f41a5
revert convert path for now
labkey-matthewb Oct 24, 2025
0a5dbbf
Merge remote-tracking branch 'origin/develop' into fb_tvf_string_array
labkey-matthewb Oct 27, 2025
b3291a5
rm TableWrapperDynaClass
labkey-matthewb Oct 27, 2025
bae6284
oops
labkey-matthewb Oct 27, 2025
81b37c5
LF->CRLF
labkey-matthewb Oct 28, 2025
1687b4d
typo
labkey-matthewb Oct 28, 2025
d6fd803
remove stray change
labkey-matthewb Oct 29, 2025
288546d
Merge remote-tracking branch 'origin/develop' into fb_tvf_string_array
labkey-matthewb Oct 29, 2025
4032cc3
CR feedback
labkey-matthewb Oct 29, 2025
164a550
CR feedback
labkey-matthewb Oct 29, 2025
f4b52c3
CR feedback
labkey-matthewb Oct 29, 2025
5518897
string array parsing/formating ala Google Sheets
labkey-matthewb Oct 30, 2025
3429dbb
StringArrayConverter uses splitStringToValuesForImport()
labkey-matthewb Oct 30, 2025
40f0588
public static
labkey-matthewb Oct 30, 2025
98dac58
PropertyType.MULTI_CHOICE
labkey-matthewb Oct 31, 2025
45a5bc3
Merge remote-tracking branch 'origin/develop' into fb_multichoice
labkey-matthewb Nov 3, 2025
14256bc
Update api/src/org/labkey/api/data/ColumnRenderProperties.java
labkey-matthewb Nov 3, 2025
af54995
undo
labkey-matthewb Nov 3, 2025
6840507
List is probably better here. Collection is pretty broad.
labkey-matthewb Nov 3, 2025
82652d5
CR fixes
labkey-matthewb Nov 4, 2025
caeed04
add List support to Converter.convert()
labkey-matthewb Nov 4, 2025
b4a312a
Merge remote-tracking branch 'origin/develop' into fb_multichoice
labkey-matthewb Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion api/src/org/labkey/api/data/ColumnInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.labkey.api.util.StringExpression;
import org.labkey.data.xml.ColumnType;

import java.beans.Introspector;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
Expand All @@ -56,6 +55,10 @@ else if (colInfo.getPropertyType() == PropertyType.ATTACHMENT)
{
return new AttachmentDisplayColumn(colInfo);
}
if (JdbcType.ARRAY == colInfo.getJdbcType() && PropertyType.MULTI_CHOICE == colInfo.getPropertyType())
{
return new MultiChoice.DisplayColumn(colInfo);
}

DataColumn dataColumn = new DataColumn(colInfo);
if (colInfo.getPropertyType() == PropertyType.MULTI_LINE)
Expand Down
32 changes: 18 additions & 14 deletions api/src/org/labkey/api/data/ColumnRenderProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

Expand Down Expand Up @@ -168,8 +169,9 @@ else if (java.sql.Date.class.isAssignableFrom(javaClass))
return "Date";
else if (Date.class.isAssignableFrom(javaClass))
return "Date and Time";
else
return "Other";
else if (List.class.isAssignableFrom(javaClass) || javaClass.isArray())
return "Array";
return "Other";
}

/** Don't return TYPEs just real java objects */
Expand Down Expand Up @@ -381,18 +383,20 @@ static Function<Object,Object> getDefaultConvertFn(ColumnRenderProperties col)
final var defaultUnit = col.getDisplayUnit();
final @NotNull var jdbcType = col.getJdbcType();

if (null == defaultUnit)
if (null != defaultUnit)
return defaultUnit::convert;

if (PropertyType.MULTI_CHOICE == col.getPropertyType())
return MultiChoice.Converter.getInstance();

return (value) ->
{
return (value) ->
{
// quick check for unnecessary conversion
if (value == null || javaClass == value.getClass())
return value;
if (value instanceof CharSequence)
ConvertUtils.convert(value.toString(), javaClass);
return jdbcType.convert(value);
};
}
return defaultUnit::convert;
// quick check for unnecessary conversion
if (value == null || javaClass == value.getClass())
return value;
if (value instanceof CharSequence)
return ConvertUtils.convert(value.toString(), javaClass);
return jdbcType.convert(value);
};
}
}
13 changes: 8 additions & 5 deletions api/src/org/labkey/api/data/ColumnRenderPropertiesImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -791,19 +791,22 @@ public Class<?> getJavaClass(boolean isNullable)

public static Class<?> defaultJavaClass(ColumnRenderProperties col, boolean isNullable)
{
Class<?> ret;
boolean isNumeric;
PropertyType pt = col.getPropertyType();
JdbcType jdbcType = col.getJdbcType();
boolean isNumeric;
Class<?> ret;

if (pt != null)
{
ret = pt.getJavaType();
if (JdbcType.ARRAY == jdbcType && PropertyType.MULTI_CHOICE == pt)
return MultiChoice.Array.class;
isNumeric = pt.getJdbcType().isNumeric();
ret = pt.getJavaType();
}
else
{
JdbcType jdbcType = col.getJdbcType();
ret = jdbcType.getJavaClass(isNullable);
isNumeric = jdbcType.isNumeric();
ret = jdbcType.getJavaClass(isNullable);
}
if (isNumeric)
{
Expand Down
30 changes: 16 additions & 14 deletions api/src/org/labkey/api/data/DataColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -710,7 +712,7 @@ else if (_inputType.equalsIgnoreCase("checkbox"))
{
IPropertyValidator textChoiceValidator = PropertyService.get().getValidatorForColumn(_boundColumn, PropertyValidatorType.TextChoice);
if (textChoiceValidator != null)
renderTextChoiceFormInput(out, formFieldName, value, strVal, disabledInput, textChoiceValidator);
renderTextChoiceFormInput(out, formFieldName, value, List.of(strVal), disabledInput, textChoiceValidator);
else
renderTextFormInput(out, formFieldName, value, strVal, disabledInput);
}
Expand All @@ -735,7 +737,8 @@ else if (_inputType.equalsIgnoreCase("checkbox"))
return ctx.getForm() == null || col == null ? HtmlString.EMPTY_STRING : ctx.getErrors(col);
}

private void renderSelectFormInput(HtmlWriter out, String formFieldName, Object value, String strVal, boolean disabledInput, NamedObjectList entryList)

private void renderSelectFormInput(HtmlWriter out, String formFieldName, Object value, List<String> strValues, boolean disabledInput, NamedObjectList entryList)
{
SelectBuilder select = new SelectBuilder()
.disabled(disabledInput)
Expand All @@ -747,12 +750,15 @@ private void renderSelectFormInput(HtmlWriter out, String formFieldName, Object
// add empty option
options.add(new OptionBuilder().build());

Set<String> selectedValues = strValues.isEmpty() ? Set.of() :
strValues.size()==1 ? (null == strValues.get(0) ? Set.of() : Set.of(strValues.get(0))) :
new HashSet<>(strValues);
for (NamedObject entry : entryList)
{
String entryName = entry.getName();
OptionBuilder option = new OptionBuilder()
.selected(isSelectInputSelected(entryName, value, strVal))
.value(entryName);
.selected(selectedValues.contains(entryName))
.value(entryName);

if (null != entry.getObject())
option.label(getSelectInputDisplayValue(entry));
Expand All @@ -767,22 +773,18 @@ private void renderSelectFormInput(HtmlWriter out, String formFieldName, Object
renderHiddenFormInput(out, formFieldName, value);
}

private void renderTextChoiceFormInput(HtmlWriter out, String formFieldName, Object value, String strVal, boolean disabledInput, IPropertyValidator textChoiceValidator)
protected void renderTextChoiceFormInput(HtmlWriter out, String formFieldName, Object value, List<String> strValues, boolean disabledInput, IPropertyValidator textChoiceValidator)
{
NamedObjectList options = new NamedObjectList();
List<String> choices = PropertyService.get().getTextChoiceValidatorOptions(textChoiceValidator);
LinkedHashSet<String> choices = new LinkedHashSet<>(PropertyService.get().getTextChoiceValidatorOptions(textChoiceValidator));

// if the already saved strVal is not in the current choice set, add it (as it seems wrong to remove a value that the user hasn't explicitly touched)
if (!StringUtils.isEmpty(strVal) && !choices.contains(strVal))
{
choices = new ArrayList<>(choices);
choices.add(strVal);
}
choices.addAll(strValues);

NamedObjectList options = new NamedObjectList();
for (String choice : choices)
options.put(new SimpleNamedObject(choice, choice));

renderSelectFormInput(out, formFieldName, value, strVal, disabledInput, options);
renderSelectFormInput(out, formFieldName, value, strValues, disabledInput, options);
}

protected void renderSelectFormInputFromFk(RenderContext ctx, HtmlWriter out, String formFieldName, Object value, String strVal, boolean disabledInput)
Expand All @@ -807,7 +809,7 @@ protected void renderSelectFormInputFromFk(RenderContext ctx, HtmlWriter out, St
}
else
{
renderSelectFormInput(out, formFieldName, value, strVal, disabledInput, entryList);
renderSelectFormInput(out, formFieldName, value, List.of(Objects.toString(value)), disabledInput, entryList);
}
}

Expand Down
9 changes: 7 additions & 2 deletions api/src/org/labkey/api/data/DisplayColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import java.text.DecimalFormatSymbols;
import java.text.Format;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
Expand Down Expand Up @@ -631,7 +632,11 @@ public String getJsonTypeName()

public static String getJsonTypeName(Class<?> valueClass)
{
if (String.class.isAssignableFrom(valueClass))
if (Map.class.isAssignableFrom(valueClass))
return "object";
else if (valueClass.isArray() || List.class.isAssignableFrom(valueClass))
return "array";
else if (String.class.isAssignableFrom(valueClass))
return "string";
else if (Boolean.class.isAssignableFrom(valueClass) || boolean.class.isAssignableFrom(valueClass))
return "boolean";
Expand Down Expand Up @@ -1166,7 +1171,7 @@ protected Object getInputValue(RenderContext ctx)
val = viewForm.getAsString(formFieldName);
}
else if (ctx.getRow() != null)
val = col.getValue(ctx);
val = getValue(ctx);
}

return val;
Expand Down
4 changes: 3 additions & 1 deletion api/src/org/labkey/api/data/JdbcType.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.sql.Array;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
Expand Down Expand Up @@ -299,6 +300,8 @@ protected Collection<Integer> getSqlTypes()
}
},

ARRAY(Types.ARRAY, Array.class),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still unclear to me how are we going to support comma character in choice. TabLoader would have stripped the enclosing quotes from "a,b" and then ARRAY needs to decide if this is a single choice a,b or [a, b]. But sure that can be dealt with in a later story.

Copy link
Contributor Author

@labkey-matthewb labkey-matthewb Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See PageFlowUtil$TestCase.testGoogleSheetMultiValue(), the string value will be
a, b, "A,B"
This will then get escaped according to the rules of the particular file file format. This will look much better in a TAB separated file thans COMMA separated file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a, b, "A,B" will parse to array.
but what about just "A,B"? TabLoader would have stripped the enclosing quotes so we still have to decide if it's A,B or [A, B]

Copy link
Contributor Author

@labkey-matthewb labkey-matthewb Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RowId, MultiChoice
5, "a, b, ""A,B"""

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kinda like writing a regular expression in a .java file. Regex has it's own escape rules and Java strings have their own escape rules. And you have to nest them.


NULL(Types.NULL, Object.class),

OTHER(Types.OTHER, Object.class);
Expand Down Expand Up @@ -398,7 +401,6 @@ protected void addSqlTypes(Collection<Integer> sqlTypes)
public static JdbcType valueOf(int type)
{
JdbcType jt = sqlTypeMap.get(type);

return null != jt ? jt : OTHER;
}

Expand Down
Loading