diff --git a/archetypes/domui-hello/src/main/resources/archetype-resources/pom.xml b/archetypes/domui-hello/src/main/resources/archetype-resources/pom.xml
index 7b1aa8fd29..93f76a1172 100644
--- a/archetypes/domui-hello/src/main/resources/archetype-resources/pom.xml
+++ b/archetypes/domui-hello/src/main/resources/archetype-resources/pom.xml
@@ -14,7 +14,7 @@
3.12.31.81.8
- 1.5.20
+ 1.7.20UTF-89.4.6.v20170531
diff --git a/common/property-annotation-processor/src/main/java/db/annotationprocessing/PropertyAnnotationProcessor.java b/common/property-annotation-processor/src/main/java/db/annotationprocessing/PropertyAnnotationProcessor.java
index 253be2c6ac..7bf4b22f0f 100644
--- a/common/property-annotation-processor/src/main/java/db/annotationprocessing/PropertyAnnotationProcessor.java
+++ b/common/property-annotation-processor/src/main/java/db/annotationprocessing/PropertyAnnotationProcessor.java
@@ -31,8 +31,6 @@
import java.util.TreeMap;
import java.util.stream.Collectors;
-@SupportedAnnotationTypes({"javax.persistence.Entity", "to.etc.annotations.GenerateProperties"})
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
/**
* Generates QField classes for every Entity annotated class in the project where this processor is selected.
* Leave the default .apt_generated folder as is.
@@ -46,6 +44,8 @@
* @author Frits Jalvingh
* Created on Feb 3, 2013
*/
+@SupportedAnnotationTypes({"javax.persistence.Entity", "to.etc.annotations.GenerateProperties"})
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class PropertyAnnotationProcessor extends AbstractProcessor {
static public final String PERSISTENCE_ANNOTATION = "javax.persistence.Entity";
@@ -61,6 +61,8 @@ public class PropertyAnnotationProcessor extends AbstractProcessor {
private SourceVersion m_sourceVersion;
+ private boolean m_debug = System.getenv().get("DOMUI_ANNDEBUG") != null;
+
static public final class Property {
private final TypeMirror m_type;
@@ -128,8 +130,9 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
String pkgName = processingEnv.getElementUtils().getPackageOf(classElement).getQualifiedName().toString();
String entityName = classElement.getSimpleName().toString();
+ if(m_debug)
+ System.out.println("ANN> Processing entity " + entityName);
- //String entityName = classElement.asType().toString();
try {
List properties = getProperties(classElement);
@@ -161,6 +164,8 @@ String getLinkClass(String entityName) {
}
private JavaFileObject createFile(String name, TypeElement ann) throws IOException {
+ if((m_debug))
+ System.out.println("ANN> createFile " + name + " source " + ann);
return processingEnv.getFiler().createSourceFile(name, ann);
}
diff --git a/common/to.etc.alg/src/main/java/to/etc/util/FileTool.java b/common/to.etc.alg/src/main/java/to/etc/util/FileTool.java
index ed7e170ed0..53757137b7 100644
--- a/common/to.etc.alg/src/main/java/to/etc/util/FileTool.java
+++ b/common/to.etc.alg/src/main/java/to/etc/util/FileTool.java
@@ -1967,7 +1967,7 @@ public static void delete(File file) {
try {
Files.delete(file.toPath());
} catch(Exception x) {
- LOG.error("Failed to delete " + file + ": " + x, x);
+ LOG.debug("Failed to delete " + file + ": " + x, x);
}
}
}
diff --git a/common/to.etc.alg/src/main/java/to/etc/util/SecurityUtils.java b/common/to.etc.alg/src/main/java/to/etc/util/SecurityUtils.java
index f29f387fd1..b5ec0f089e 100644
--- a/common/to.etc.alg/src/main/java/to/etc/util/SecurityUtils.java
+++ b/common/to.etc.alg/src/main/java/to/etc/util/SecurityUtils.java
@@ -266,6 +266,17 @@ static public String getSha256Hex(@NonNull String in, @NonNull Charset encoding)
}
}
+ @NonNull
+ static public String getSha256Base64(@NonNull String in, @NonNull Charset encoding) {
+ try {
+ MessageDigest sha = MessageDigest.getInstance("SHA-256");
+ byte[] hash = sha.digest(in.getBytes(encoding));
+ return StringTool.encodeBase64ToString(hash);
+ } catch(Exception ex) {
+ throw WrappedException.wrap(ex);
+ }
+ }
+
static public byte[] createSalt(int bytes) {
byte[] salt = new byte[bytes];
RANDOM.nextBytes(salt);
diff --git a/common/to.etc.alg/src/main/java/to/etc/util/StringTool.java b/common/to.etc.alg/src/main/java/to/etc/util/StringTool.java
index b4b1bfca46..7e68ab6910 100644
--- a/common/to.etc.alg/src/main/java/to/etc/util/StringTool.java
+++ b/common/to.etc.alg/src/main/java/to/etc/util/StringTool.java
@@ -45,6 +45,8 @@
import java.util.StringTokenizer;
import java.util.regex.Pattern;
+import static java.lang.Character.isJavaIdentifierPart;
+
/**
* This static utility class contains a load of string functions. And some other
* stuff I could not quickly find a place for ;-)
@@ -112,7 +114,7 @@ static public boolean isValidJavaIdentifier(@NonNull final String s) {
if(!Character.isJavaIdentifierStart(s.charAt(0)))
return false;
for(int i = 1; i < len; i++) {
- if(!Character.isJavaIdentifierPart(s.charAt(i)))
+ if(!isJavaIdentifierPart(s.charAt(i)))
return false;
}
return true;
@@ -3099,6 +3101,71 @@ public static String stripAccents(String s) {
return s;
}
+ /**
+ * Util that locates given qualified name in expression (with ignored casing), and replaces it with new qualified name.
+ * It ignores other cases when old name is part of naming of other variables in expression.
+ * It requires that old qualified name prefix and name are named by java identifier convention.
+ *
+ * @param expression expression where we replace variables with qualified names
+ * @param prefixQName prefix in qualified name to replace.
+ * @param oldName name in qualified name to replace.
+ * @param newQName new qualified name that replaced old one.
+ * @return replaced expression.
+ */
+ @Nullable
+ public static String replaceQualifiedNameInExpression(@Nullable String expression, String prefixQName, String oldName, String newQName) {
+ if(null == expression) {
+ return null;
+ }
+ String lEntityName = prefixQName.toLowerCase();
+ String lOldName = oldName.toLowerCase();
+ String literalToFind = lEntityName + "." + lOldName;
+ return replaceVariableNameInExpression(expression, literalToFind, newQName);
+ }
+
+ /**
+ * Util that locates given variable name in expression (with ignored casing), and replaces it with new name.
+ * It ignores other cases when old name is part of naming of other variables in expression.
+ * It requires that old and new name are named by java identifier convention.
+ *
+ * @param expression expression where we replace variable name
+ * @param oldName name to replace.
+ * @return replaced expression.
+ */
+ @Nullable
+ public static String replaceVariableNameInExpression(@Nullable String expression, String oldName, String newName) {
+ if(null == expression) {
+ return null;
+ }
+ String lowerCaseExpression = expression.toLowerCase();
+ String literalToFind = oldName.toLowerCase();
+ int lastReplacedIndex = 0;
+ int pos = -1;
+ StringBuilder replaceSb = new StringBuilder();
+ do {
+ pos = lowerCaseExpression.indexOf(literalToFind, pos + 1);
+ if(pos > -1) {
+ Character nextChar = null;
+ if(lowerCaseExpression.length() > pos + literalToFind.length()) {
+ nextChar = lowerCaseExpression.charAt(pos + literalToFind.length());
+ }
+ Character prevChar = null;
+ if(pos > 0) {
+ prevChar = expression.charAt(pos - 1);
+ }
+ if((nextChar == null || !isJavaIdentifierPart(nextChar)) && (prevChar == null || !isJavaIdentifierPart(prevChar))) {
+ replaceSb
+ .append(expression.substring(lastReplacedIndex, pos))
+ .append(newName);
+ lastReplacedIndex = pos + literalToFind.length();
+ }
+ } else if(lastReplacedIndex < expression.length()) {
+ replaceSb.append(expression.substring(lastReplacedIndex));
+ }
+ } while(pos >= 0);
+ return replaceSb.toString();
+ }
+
/**
* This method checks that the name passed only contains a name,
* and nothing that looks like a SQL injection.
@@ -3121,6 +3188,67 @@ static public void sqlCheckNoQuotes(@Nullable String password) {
if(null != password && password.contains("'"))
throw new IllegalArgumentException("Invalid characters in SQL");
}
+
+ static private final char[] PUNCT = "!#_^&*.;".toCharArray();
+
+ static private final char[] DIGITS = "023456789".toCharArray();
+
+ static private final char[] LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+
+ /**
+ * Generate a reasonably secure password.
+ */
+ static public String generatePassword(int nchar) {
+ return generatePassword(nchar, 2, 2);
+ }
+
+ /*
+ * Postgres' passwords should not include dollar signs nor percentage signs.
+ * jal 20200608 actually postgres is OK with it, it's Azure Tabular that dies with it.
+ */
+ static public String generatePassword(int nchar, int punctuation, int digits) {
+ if(nchar < 6)
+ throw new IllegalStateException("Don't be silly.");
+
+ char[] buf = new char[nchar]; // Password buffer
+
+ //-- Randomly assign the #of punctuation chars
+ while(punctuation > 0) {
+ char c = PUNCT[m_random.nextInt(PUNCT.length)]; // Random punctuation
+
+ for(; ; ) {
+ int pos = m_random.nextInt(nchar); // Get a position
+ if(buf[pos] == 0) {
+ buf[pos] = c;
+ break;
+ }
+ }
+ punctuation--;
+ }
+
+ //-- Randomly assign digits
+ while(digits > 0) {
+ char c = DIGITS[m_random.nextInt(DIGITS.length)]; // Random punctuation
+
+ for(; ; ) {
+ int pos = m_random.nextInt(nchar); // Get a position
+ if(buf[pos] == 0) {
+ buf[pos] = c;
+ break;
+ }
+ }
+ digits--;
+ }
+
+ //-- And finally: fill the rest with random letters.
+ for(int i = 0; i < nchar; i++) {
+ if(buf[i] == 0) {
+ buf[i] = LETTERS[m_random.nextInt(LETTERS.length)];
+ }
+ }
+ return new String(buf);
+ }
+
}
diff --git a/common/to.etc.alg/src/test/java/to/etc/util/StringToolTest.java b/common/to.etc.alg/src/test/java/to/etc/util/StringToolTest.java
deleted file mode 100644
index 3326c846af..0000000000
--- a/common/to.etc.alg/src/test/java/to/etc/util/StringToolTest.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package to.etc.util;
-
-import org.junit.Test;
-
-import java.util.List;
-
-import static junit.framework.TestCase.assertEquals;
-
-public class StringToolTest {
-
- @Test
- public void testStripAccents() {
- var examples = List.of(
- new Pair<>("filë_name.xls", "file_name.xls"),
- new Pair<>("ćčćč", "cccc") );
- for(var example : examples) {
- var rename = StringTool.stripAccents(example.get1());
- assertEquals(example.get2(), rename);
- }
- }
-}
diff --git a/common/to.etc.alg/src/test/java/to/etc/util/TestStringTool.java b/common/to.etc.alg/src/test/java/to/etc/util/TestStringTool.java
index 04ed7541a2..d0c91a547c 100644
--- a/common/to.etc.alg/src/test/java/to/etc/util/TestStringTool.java
+++ b/common/to.etc.alg/src/test/java/to/etc/util/TestStringTool.java
@@ -8,7 +8,10 @@
import java.util.Arrays;
import java.util.List;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
@@ -456,4 +459,27 @@ public void testRemoveRepeatingCharacters_whenHasRepeatingDigits_dontShortenStri
res = StringTool.removeRepeatingCharacters(s);
Assert.assertEquals("abc1111111abc 123456789123456789", res);
}
+
+ @Test
+ public void testStripAccents() {
+ var examples = List.of(
+ new Pair<>("filë_name.xls", "file_name.xls"),
+ new Pair<>("ćčćč", "cccc") );
+ for(var example : examples) {
+ var rename = StringTool.stripAccents(example.get1());
+ assertEquals(example.get2(), rename);
+ }
+ }
+
+ @Test
+ public void testReplaceQualifiedNameInExpression() {
+ assertEquals(StringTool.replaceQualifiedNameInExpression("a.aaaa + a.aaab-a.aAa", "A", "aaa", "Aa.ccc"), "a.aaaa + a.aaab-Aa.ccc");
+ assertEquals(StringTool.replaceQualifiedNameInExpression("aa.aaa-A.aaa*1-a.AAa ", "A", "aaa", "Aa.ccc"), "aa.aaa-Aa.ccc*1-Aa.ccc ");
+ }
+
+ @Test
+ public void testReplaceVariableNameInExpression() {
+ assertEquals(StringTool.replaceVariableNameInExpression("aaaa + aaab-aAa", "aaa", "ccc"), "aaaa + aaab-ccc");
+ assertEquals(StringTool.replaceVariableNameInExpression("baaa-aaa*1-AAa ", "aaa", "ccc"), "baaa-ccc*1-ccc ");
+ }
}
diff --git a/examples/skeleton/pom.xml b/examples/skeleton/pom.xml
index 009a6a1423..5a7f57f2e3 100644
--- a/examples/skeleton/pom.xml
+++ b/examples/skeleton/pom.xml
@@ -37,7 +37,7 @@
1.81.83.12.3
- 1.5.20
+ 1.7.203.3.0
@@ -249,7 +249,7 @@
org.postgresqlpostgresql
- 42.4.1
+ 42.5.1
diff --git a/integrations/to.etc.domui.hibutil/src/main/java/to/etc/domui/hibernate/idgen/UUIDGenerator23.java b/integrations/to.etc.domui.hibutil/src/main/java/to/etc/domui/hibernate/idgen/UUIDGenerator23.java
index 3c2f3be132..796fe891b3 100644
--- a/integrations/to.etc.domui.hibutil/src/main/java/to/etc/domui/hibernate/idgen/UUIDGenerator23.java
+++ b/integrations/to.etc.domui.hibutil/src/main/java/to/etc/domui/hibernate/idgen/UUIDGenerator23.java
@@ -29,16 +29,10 @@
* Created on 12-2-18.
*/
final public class UUIDGenerator23 implements IdentifierGenerator {
+
@Override
public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
- UUID uuid = UUID.randomUUID();
- byte[] data = new byte[16];
-
- moveBytes(data, 0, uuid.getMostSignificantBits());
- moveBytes(data, 8, uuid.getLeastSignificantBits());
- String str = StringTool.encodeBase64ToString(data);
-
- return str.substring(0, 23); // Strip the ==
+ return createUUID();
}
static private void moveBytes(byte[] bytes, int offset, long bits) {
diff --git a/integrations/to.etc.domui.selenium/pom.xml b/integrations/to.etc.domui.selenium/pom.xml
index ae78375b7b..b078e6f313 100644
--- a/integrations/to.etc.domui.selenium/pom.xml
+++ b/integrations/to.etc.domui.selenium/pom.xml
@@ -104,19 +104,10 @@
-
- org.seleniumhq.selenium
- htmlunit-driver
-
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverConnector.java b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverConnector.java
index a63f60eeac..46e88145d2 100644
--- a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverConnector.java
+++ b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverConnector.java
@@ -223,12 +223,14 @@ static public WebDriverConnector get() throws Exception {
@NonNull
private static WebDriverType getDriverType(@Nullable String hubUrl) {
- if(null == hubUrl || hubUrl.trim().isEmpty())
- return WebDriverType.HTMLUNIT; // Used as a target because it can emulate multiple browser types
- if("local".equals(hubUrl.trim()))
- return WebDriverType.LOCAL;
- if(hubUrl.startsWith(BROWSERSTACK)) {
- return WebDriverType.BROWSERSTACK;
+ if(null != hubUrl) {
+ //if(null == hubUrl || hubUrl.trim().isEmpty())
+ // return WebDriverType.HTMLUNIT; // Used as a target because it can emulate multiple browser types
+ if("local".equals(hubUrl.trim()))
+ return WebDriverType.LOCAL;
+ if(hubUrl.startsWith(BROWSERSTACK)) {
+ return WebDriverType.BROWSERSTACK;
+ }
}
return WebDriverType.REMOTE;
}
diff --git a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverFactory.java b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverFactory.java
index 0a928478bb..2607d97989 100644
--- a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverFactory.java
+++ b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverFactory.java
@@ -4,7 +4,6 @@
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.UnexpectedAlertBehaviour;
import org.openqa.selenium.WebDriver;
@@ -14,7 +13,6 @@
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
-import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
@@ -60,8 +58,8 @@ public static WebDriver allocateInstance(WebDriverType type, BrowserModel browse
default:
throw new IllegalStateException("? unhandled driver type");
- case HTMLUNIT:
- return allocateHtmlUnitInstance(browser, lang);
+ //case HTMLUNIT:
+ // return allocateHtmlUnitInstance(browser, lang);
case LOCAL:
return allocateLocalInstance(browser, lang);
@@ -199,10 +197,10 @@ private static WebDriver allocateBrowserStack(BrowserModel browser, String hubur
return dir;
}
- private static WebDriver allocateHtmlUnitInstance(BrowserModel browser, Locale lang) throws Exception {
- Capabilities capabilities = calculateCapabilities(browser, lang);
- return new HtmlUnitDriver(capabilities);
- }
+ //private static WebDriver allocateHtmlUnitInstance(BrowserModel browser, Locale lang) throws Exception {
+ // Capabilities capabilities = calculateCapabilities(browser, lang);
+ // return new HtmlUnitDriver(capabilities);
+ //}
private static WebDriver allocateRemoteInstance(BrowserModel browser, @NonNull String hubUrl, Locale lang) throws Exception {
return new RemoteWebDriver(new URL(hubUrl), calculateCapabilities(browser, lang));
@@ -494,24 +492,16 @@ private static DesiredCapabilities getChromeHeadlessCapabilities(Locale lang) {
return options;
}
- //private static DesiredCapabilities getPhantomCapabilities(Locale lang) {
- // DesiredCapabilities capabilities = DesiredCapabilities.phantomjs();
- // String value = lang.getLanguage().toLowerCase();
- // capabilities.setCapability(PhantomJSDriverService.PHANTOMJS_PAGE_CUSTOMHEADERS_PREFIX + "Accept-Language", value);
- // capabilities.setCapability(CapabilityType.UNEXPECTED_ALERT_BEHAVIOUR, UnexpectedAlertBehaviour.IGNORE);
- // return capabilities;
- //}
-
@Nullable
public static IWebdriverScreenshotHelper getScreenshotHelper(WebDriverType webDriverType, BrowserModel browserModel) {
- switch(webDriverType) {
- default:
- break;
-
- case HTMLUNIT:
- //-- HTMLUNIT does not render, so it cannot create screenshots.
- return null;
- }
+ //switch(webDriverType) {
+ // default:
+ // break;
+ //
+ // case HTMLUNIT:
+ // //-- HTMLUNIT does not render, so it cannot create screenshots.
+ // return null;
+ //}
switch(browserModel) {
default:
diff --git a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverType.java b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverType.java
index 9dce081537..3a2ea27dcb 100644
--- a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverType.java
+++ b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/core/WebDriverType.java
@@ -5,5 +5,5 @@
* Created on 20-3-17.
*/
public enum WebDriverType {
- LOCAL, HTMLUNIT, BROWSERSTACK, REMOTE
+ LOCAL, BROWSERSTACK, REMOTE
}
diff --git a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/poproxies/AbstractCpPage.java b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/poproxies/AbstractCpPage.java
index 011eea43ac..8ce8326922 100644
--- a/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/poproxies/AbstractCpPage.java
+++ b/integrations/to.etc.domui.selenium/src/main/java/to/etc/domui/webdriver/poproxies/AbstractCpPage.java
@@ -1,7 +1,7 @@
package to.etc.domui.webdriver.poproxies;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.net.URLEncodedUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedConditions;
diff --git a/parent/pom.xml b/parent/pom.xml
index c1efa8b69d..72df610a4f 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -56,7 +56,7 @@
113.20.0
- 1.6.20
+ 1.7.203.3.0
@@ -100,7 +100,7 @@
1.7.70.7.5
- 1.8
+ 1.163.41.1.4
@@ -115,7 +115,7 @@
9.4.14.v201811143.141.59
- 2.7.0
+ 2.7.12.3.0
@@ -314,7 +314,7 @@
org.postgresqlpostgresql
- 42.5.0
+ 42.5.1
@@ -522,11 +522,11 @@
1.15.0
-
- org.seleniumhq.selenium
- htmlunit-driver
- 2.50.0
-
+
+
+
+
+
diff --git a/to.etc.domui.demo/pom.xml b/to.etc.domui.demo/pom.xml
index 4ade32d101..de8fb87ecc 100644
--- a/to.etc.domui.demo/pom.xml
+++ b/to.etc.domui.demo/pom.xml
@@ -245,7 +245,7 @@
-->
maven-resources-plugin
- 3.2.0
+ 3.3.0copy-resources
diff --git a/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindError1Page.java b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindError1Page.java
index 597ecd2833..c61d6c7009 100644
--- a/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindError1Page.java
+++ b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindError1Page.java
@@ -10,10 +10,10 @@
/**
* Test for the following:
- *
- * - a mandatoty control bound to a model has an initial value through the model (i.e. the start value is not null)
+ *
+ * - a mandatory control bound to a model has an initial value through the model (i.e. the start value is not null)
* - clear the value in the control, then press the click button which validates the bindings
- *
+ *
* expected result: as the control is now empty it is invalid, and a mandatory error must be shown.
* actual result before: the validation fails, but no message is shown.
*
@@ -23,7 +23,8 @@
public class BindError1Page extends UrlPage {
private String m_fullName = "Hello ladies";
- @Override public void createContent() throws Exception {
+ @Override
+ public void createContent() throws Exception {
Text2 text = new Text2<>(String.class);
add(text);
text.setMandatory(true);
@@ -31,7 +32,7 @@ public class BindError1Page extends UrlPage {
text.bind().to(this, "fullName");
- add(new DefaultButton("click", a-> save()));
+ add(new DefaultButton("click", a -> save()));
}
private void save() throws Exception {
diff --git a/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindvalidationErrorPage.java b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindvalidationErrorPage.java
new file mode 100644
index 0000000000..304c463282
--- /dev/null
+++ b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/BindvalidationErrorPage.java
@@ -0,0 +1,58 @@
+package to.etc.domuidemo.pages.test.binding.binderrors;
+
+import to.etc.domui.component.buttons.DefaultButton;
+import to.etc.domui.component.misc.VerticalSpacer;
+import to.etc.domui.component2.form4.FormBuilder;
+import to.etc.domui.dom.html.Div;
+import to.etc.domui.dom.html.TextArea;
+import to.etc.domui.dom.html.UrlPage;
+
+/**
+ * @author Frits Jalvingh
+ * Created on 10-11-22.
+ */
+final public class BindvalidationErrorPage extends UrlPage {
+ private String m_value;
+
+ private final Div m_resultDiv = new Div();
+
+ @Override
+ public void createContent() throws Exception {
+ m_value = "bad";
+ TextArea ta = new TextArea(80, 2);
+ ta.addValidator(new TestValueValidator());
+ FormBuilder fb = new FormBuilder(this);
+ fb.property(this, "value").control(ta);
+
+ ta.setTestID("text");
+
+ DefaultButton click = new DefaultButton("Click", a -> handleClick());
+ add(click);
+ click.setTestID("click");
+
+ add(new VerticalSpacer(10));
+ add(m_resultDiv);
+ m_resultDiv.setTestID("result");
+ }
+
+ private void handleClick() throws Exception {
+ m_resultDiv.removeAllChildren();
+ if(bindErrors()) {
+ m_resultDiv.add("Failed");
+ m_resultDiv.addCssClass("test-failed");
+ m_resultDiv.removeCssClass("test-ok");
+ } else {
+ m_resultDiv.add("worked");
+ m_resultDiv.removeCssClass("test-failed");
+ m_resultDiv.addCssClass("test-ok");
+ }
+ }
+
+ public String getValue() {
+ return m_value;
+ }
+
+ public void setValue(String value) {
+ m_value = value;
+ }
+}
diff --git a/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/TestValueValidator.java b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/TestValueValidator.java
new file mode 100644
index 0000000000..6d4d1fbac4
--- /dev/null
+++ b/to.etc.domui.demo/src/main/java/to/etc/domuidemo/pages/test/binding/binderrors/TestValueValidator.java
@@ -0,0 +1,22 @@
+package to.etc.domuidemo.pages.test.binding.binderrors;
+
+import to.etc.domui.converter.IValueValidator;
+import to.etc.domui.trouble.ValidationException;
+import to.etc.domui.util.Msgs;
+
+/**
+ * Test validator which accepts only the value "good".
+ *
+ * @author Frits Jalvingh
+ * Created on 10-11-22.
+ */
+final public class TestValueValidator implements IValueValidator {
+ @Override
+ public void validate(String input) throws Exception {
+ if(null == input)
+ return;
+ if(input.equals("good"))
+ return;
+ throw new ValidationException(Msgs.vInvalid, input);
+ }
+}
diff --git a/to.etc.domui.demo/src/test/java/to/etc/domuidemo/pages/test/binding/ITBindValidationError.java b/to.etc.domui.demo/src/test/java/to/etc/domuidemo/pages/test/binding/ITBindValidationError.java
new file mode 100644
index 0000000000..94005508a1
--- /dev/null
+++ b/to.etc.domui.demo/src/test/java/to/etc/domuidemo/pages/test/binding/ITBindValidationError.java
@@ -0,0 +1,35 @@
+package to.etc.domuidemo.pages.test.binding;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import to.etc.domui.webdriver.core.AbstractWebDriverTest;
+import to.etc.domuidemo.pages.test.binding.binderrors.BindvalidationErrorPage;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Frits Jalvingh
+ * Created on 10-11-22.
+ */
+final public class ITBindValidationError extends AbstractWebDriverTest {
+ @Test
+ public void testValidationErrorIsShown() throws Exception {
+ wd().openScreen(BindvalidationErrorPage.class);
+
+ //-- Initially the value shown must be "bad"
+ String value = wd().getValue("text");
+ Assert.assertEquals("The initial value in the control must be correct", "bad", value);
+
+ //-- Now: enter another (incorrect) value
+ String incorrectValue = "incorrect";
+ wd().cmd().type(incorrectValue).on("text");
+ wd().cmd().click().on("click");
+
+ //-- We should have a failure
+ wd().wait(By.className("test-failed"), 2, TimeUnit.SECONDS);
+
+ String newval = wd().getValue("text");
+ Assert.assertEquals("The incorrect value entered should still be seen in the control", incorrectValue, newval);
+ }
+}
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/ace/AceEditor.java b/to.etc.domui/src/main/java/to/etc/domui/component/ace/AceEditor.java
index 109f2da613..9df99c9844 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/ace/AceEditor.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/ace/AceEditor.java
@@ -185,6 +185,7 @@ public void createContent() throws Exception {
sb.append("ed.__id='").append(editorId).append("';\n");
sb.append("var Range = require('ace/range').Range;\n");
sb.append("window['").append(editorId).append("'] = ed;\n");
+ sb.append("ed.setBehavioursEnabled(true);\n");
sb.append("WebUI.registerInputControl('").append(editorId).append("', {getInputField: function(fields) {");
sb.append(" let select = ed.getSelectedText();\n");
sb.append(" fields['").append(editorId).append("_s'] = select;\n");
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/binding/AbstractComponentPropertyBinding.java b/to.etc.domui/src/main/java/to/etc/domui/component/binding/AbstractComponentPropertyBinding.java
index 971e0b43e9..158b7a196b 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/binding/AbstractComponentPropertyBinding.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/binding/AbstractComponentPropertyBinding.java
@@ -115,7 +115,7 @@ public M getInstance() {
}
//@Nullable
- public IValueAccessor getInstanceProperty() {
+ public PropertyMetaModel getInstanceProperty() {
return m_instanceProperty;
}
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/binding/ComponentPropertyBindingBidi.java b/to.etc.domui/src/main/java/to/etc/domui/component/binding/ComponentPropertyBindingBidi.java
index e51e0c672f..2757f27937 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/binding/ComponentPropertyBindingBidi.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/binding/ComponentPropertyBindingBidi.java
@@ -122,7 +122,26 @@ public BindingValuePair getBindingDifference() throws Exception {
m_bindError = null;
} catch(CodeException cx) {
controlModelValue = null;
- m_lastValueFromControlAsModelValue = null;
+ /*
+ * 20221110 jal Commented out because it seems wrong. With it, the following happens.
+ * Have a TextArea with a Validator and some initial (invalid) value coming from a Model. Now
+ * change the value to some other (invalid) value and save the screen. This SHOULD report an
+ * error. Instead what happens is this:
+ * - No error is shown
+ * - The screen save does not complete however
+ * - The control gets back its PREVIOUS (incorrect) value!
+ *
+ * The reason is this assignment. The last value field is used to check whether the data
+ * in the MODEL actually changed inside AbstractComponentPropertyBinding.moveModelToControl. We
+ * only want to change the value inside the CONTROL when the MODEL has a change if the control is
+ * in error. If the control is in error and the model value does not change the control needs to
+ * retain its incorrect value. By clearing that last-read value here we effectively say that the
+ * model value WAS null previous time, and as it is not the control gets overwritten from the
+ * actual value.
+ *
+ * ERRONEOUS STATEMENT:
+ * m_lastValueFromControlAsModelValue = null;
+ */
newError = UIMessage.error(cx);
newError.setErrorNode(control);
newError.setErrorLocation(control.getErrorLocation());
@@ -134,18 +153,29 @@ public BindingValuePair getBindingDifference() throws Exception {
//System.out.println("~~ " + control + " to " + instanceProperty + ": " + cx);
}
- //-- When in error we cannot set anything anyway, so exit.
- if(null != newError && !newError.getCode().equals(Msgs.mandatory)) {
- /*
- * jal 20171018 When a mandatory LookupInput gets cleared its value becomes null, and this
- * value should be propagated to the model. It seems likely that in ALL cases of error
- * we need to move a null there!
- */
+ MV currentModelValue = getValueFromModel();
+
+ if(null != newError) {
+ //-- When in error the only option we have is to set something to null.. We only do that for the mandatory error if possible
+ if(newError.getCode().equals(Msgs.mandatory)) {
+ /*
+ * jal 20171018 When a mandatory LookupInput gets cleared its value becomes null, and this
+ * value should be propagated to the model. It seems likely that in ALL cases of error
+ * we need to move a null there!
+ *
+ * jal 20221110 but only if the property can accept that, i.e. is not a primitive..
+ */
+ if(! MetaManager.areObjectsEqual(currentModelValue, controlModelValue) && ! getInstanceProperty().getActualType().isPrimitive()) {
+ //-- We WILL set the value of the MODEL to null, but we need to KEEP the value in the control
+ m_lastValueFromControlAsModelValue = null; // This should make sure the control does NOT get updated
+ return new BindingValuePair<>(this, null);
+ }
+ }
+ //-- For all other errors: leave the value be
return null;
}
- MV currentModelValue = getValueFromModel();
if(MetaManager.areObjectsEqual(currentModelValue, controlModelValue))
return null;
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/binding/OldBindingHandler.java b/to.etc.domui/src/main/java/to/etc/domui/component/binding/OldBindingHandler.java
index d20115332b..459ba3ed02 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/binding/OldBindingHandler.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/binding/OldBindingHandler.java
@@ -18,12 +18,13 @@
* This is the default binding manager.
*
* @author Frits Jalvingh
- * Created on 12-3-17.
+ * Created on 12-3-17.
*/
final public class OldBindingHandler {
static public final String BINDING_ERROR = "BindingError";
- private OldBindingHandler() {}
+ private OldBindingHandler() {
+ }
/**
* System helper method to move all bindings from control into the model (called at request start).
@@ -73,7 +74,7 @@ public Object after(NodeBase n) throws Exception {
* errors.
*/
static public boolean reportBindingErrors(@NonNull NodeBase root) throws Exception {
- final boolean[] silly = new boolean[1]; // Not having free variables is a joke.
+ final boolean[] silly = new boolean[1]; // Not having free variables is a joke.
DomUtil.walkTreeUndelegated(root, new DomUtil.IPerNode() {
@Override
public Object before(NodeBase n) throws Exception {
@@ -82,7 +83,7 @@ public Object before(NodeBase n) throws Exception {
List list = n.getBindingList();
if(null != list) {
- List bindErrorList= new ArrayList<>();
+ List bindErrorList = new ArrayList<>();
//-- Find all bindings with an error
for(IBinding sb : list) {
@@ -94,7 +95,7 @@ public Object before(NodeBase n) throws Exception {
//-- If there is an error somewhere- report the 1st one on the component
if(!bindErrorList.isEmpty()) {
- UIMessage message = bindErrorList.get(0); // Report the first error as the binding error.
+ UIMessage message = bindErrorList.get(0); // Report the first error as the binding error.
message.group(BINDING_ERROR);
silly[0] = true;
n.setMessage(message);
@@ -126,7 +127,7 @@ public Object after(NodeBase n) throws Exception {
}
@Nullable
- public static ComponentPropertyBindingBidi,?,?,?> findBinding(NodeBase nodeBase, String string) {
+ public static ComponentPropertyBindingBidi, ?, ?, ?> findBinding(NodeBase nodeBase, String string) {
List list = nodeBase.getBindingList();
if(list != null) {
for(IBinding sb : list) {
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/ckeditor/CKEditResPart.java b/to.etc.domui/src/main/java/to/etc/domui/component/ckeditor/CKEditResPart.java
index 2e18e603fa..a4225471b0 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/ckeditor/CKEditResPart.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/ckeditor/CKEditResPart.java
@@ -116,7 +116,7 @@ private IBrowserOutput defaultHeader(RequestContextImpl ctx, String cmd, String
DomApplication.get().getDefaultHTTPHeaderMap().forEach((header, value) -> rr.addHeader(header, value));
Writer outputWriter = ctx.getOutputWriter("text/xml; charset=UTF-8", "utf-8");
- IBrowserOutput w = new PrettyXmlOutputWriter(outputWriter);
+ IBrowserOutput w = new PrettyXmlOutputWriter(outputWriter, null);
w.tag("Connector");
w.attr("command", cmd);
w.attr("resourceType", rtype);
@@ -200,7 +200,7 @@ private void sendInit(DomApplication app, IEditorFileSystem ifs, RequestContextI
IRequestResponse rr = ctx.getRequestResponse();
DomApplication.get().getDefaultHTTPHeaderMap().forEach((header, value) -> rr.addHeader(header, value));
- IBrowserOutput w = new PrettyXmlOutputWriter(ctx.getOutputWriter("text/xml; charset=UTF-8", "utf-8"));
+ IBrowserOutput w = new PrettyXmlOutputWriter(ctx.getOutputWriter("text/xml; charset=UTF-8", "utf-8"), null);
w.tag("Connector");
w.endtag();
w.tag("Error");
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/input/AbstractLookupInputBase.java b/to.etc.domui/src/main/java/to/etc/domui/component/input/AbstractLookupInputBase.java
index b81e5f278a..7a8bcfee95 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/input/AbstractLookupInputBase.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/input/AbstractLookupInputBase.java
@@ -29,8 +29,10 @@
import to.etc.util.StringTool;
import to.etc.util.WrappedException;
import to.etc.webapp.query.QCriteria;
+import to.etc.webapp.query.QField;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -135,6 +137,9 @@ protected enum RebuildCause {
@Nullable
private String m_selectionCssClass;
+ @Nullable
+ private List> m_customSearchFields;
+
@Nullable
private IClickableRowRenderer m_formRowRenderer;
@@ -763,6 +768,20 @@ public List getKeywordLookupPropertyList() {
return m_keywordLookupPropertyList;
}
+ @Nullable
+ public List> getCustomSearchFields() {
+ return m_customSearchFields;
+ }
+
+ /** The search properties to use in the lookup form when created. If null uses the default attributes on the class. */
+ public void setCustomSearchFields(@Nullable List> searchFields) {
+ m_customSearchFields = searchFields;
+ }
+
+ /** The search properties to use in the lookup form when created. If null uses the default attributes on the class. */
+ public void setSearchProperties(QField... searchFields) {
+ m_customSearchFields = Arrays.asList(searchFields);
+ }
/**
* Returns configured custom {@link IClickableRowRenderer}<OT> render for rows when the popup lookup form is used.
*
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/input/LookupInputBase.java b/to.etc.domui/src/main/java/to/etc/domui/component/input/LookupInputBase.java
index 6676816959..5ef529c482 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/input/LookupInputBase.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/input/LookupInputBase.java
@@ -763,6 +763,7 @@ public void setKeySearchHint(@Nullable String keySearchHint) {
* Set the list of lookup properties to use for lookup in the lookup form, when shown.
* @return
*/
+ @Nullable
public List getSearchProperties() {
return m_searchPropertyList;
}
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component/layout/TabPanelBase.java b/to.etc.domui/src/main/java/to/etc/domui/component/layout/TabPanelBase.java
index 2bcc92e742..ff9fd5fd9d 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component/layout/TabPanelBase.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component/layout/TabPanelBase.java
@@ -60,6 +60,13 @@ public interface ITabSelected {
void onTabSelected(TabPanelBase tabPanel, int oldTabIndex, int newTabIndex) throws Exception;
}
+ /**
+ * Represents check if tab can be selected.
+ */
+ public interface ITabSelectable {
+ boolean isTabSelectable(ITabHandle currentSelection, ITabHandle newSelection) throws Exception;
+ }
+
private List m_tablist = new ArrayList();
/**
@@ -72,8 +79,15 @@ public interface ITabSelected {
*/
final private boolean m_markErrorTabs;
+ @Nullable
private ITabSelected m_onTabSelected;
+ /**
+ * Used when we need to have explicit control if certain tab can be selected in given moment.
+ */
+ @Nullable
+ private ITabSelectable m_onTabSelectable;
+
@Nullable
private TabBuilder m_tabBuilder;
@@ -382,8 +396,17 @@ public void setCurrentTab(int index) throws Exception {
return;
//-- We must switch the styles on the current "active" panel and the current "old" panel
int oldIndex = getCurrentTab();
+
TabInstance oldti = m_tablist.get(getCurrentTab()); // Get the currently active instance,
TabInstance newti = m_tablist.get(index);
+
+ ITabSelectable onTabSelectable = m_onTabSelectable;
+ if(null != onTabSelectable) {
+ if(!onTabSelectable.isTabSelectable(oldti, newti)) {
+ return;
+ }
+ }
+
NodeBase oldc = oldti.getContent();
oldc.setDisplay(DisplayType.NONE); // Switch displays on content
@@ -403,8 +426,9 @@ public void setCurrentTab(int index) throws Exception {
if(null != newtab)
newtab.addCssClass("ui-tab-sel");
- if(m_onTabSelected != null) {
- m_onTabSelected.onTabSelected(this, oldIndex, index);
+ ITabSelected onTabSelected = m_onTabSelected;
+ if(onTabSelected != null) {
+ onTabSelected.onTabSelected(this, oldIndex, index);
}
INotify onHide = oldti.getOnHide();
@@ -428,15 +452,29 @@ public int getTabCount() {
return m_tablist.size();
}
-
- public void setOnTabSelected(ITabSelected onTabSelected) {
+ public void setOnTabSelected(@Nullable ITabSelected onTabSelected) {
m_onTabSelected = onTabSelected;
}
+ @Nullable
public ITabSelected getOnTabSelected() {
return m_onTabSelected;
}
+ /**
+ * If used, please make sure that there is some UI that makes clear that selection of tab has failed due to some check failure.
+ *
+ * @param onTabSelectable tab selectable check handler
+ */
+ public void setOnTabSelectable(@Nullable ITabSelectable onTabSelectable) {
+ m_onTabSelectable = onTabSelectable;
+ }
+
+ @Nullable
+ public ITabSelectable getOnTabSelectable() {
+ return m_onTabSelectable;
+ }
+
public int getTabIndex(NodeBase tabContent) {
for(TabInstance tab : m_tablist) {
if(tab.getContent() == tabContent) {
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/DefaultPopupOpener.java b/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/DefaultPopupOpener.java
index b9b579518a..bbb6fb1579 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/DefaultPopupOpener.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/DefaultPopupOpener.java
@@ -4,17 +4,33 @@
import org.eclipse.jdt.annotation.Nullable;
import to.etc.domui.component.layout.Dialog;
import to.etc.domui.component.layout.IWindowClosed;
+import to.etc.domui.component.meta.SearchPropertyMetaModel;
import to.etc.domui.component.tbl.IClickableRowRenderer;
import to.etc.domui.component.tbl.ITableModel;
import to.etc.domui.component2.lookupinput.LookupInputBase2.IPopupOpener;
import to.etc.domui.dom.html.IClicked;
import to.etc.function.IExecute;
+import java.util.List;
+
public class DefaultPopupOpener implements IPopupOpener {
+ @Nullable
+ private List m_searchPropertyList;
+
@Nullable
private IClickableRowRenderer m_formRowRenderer;
+ @Nullable
+ public List getSearchPropertyList() {
+ return m_searchPropertyList;
+ }
+
+ /** The search properties to use in the lookup form when created. If null uses the default attributes on the class. */
+ public void setSearchPropertyList(@Nullable List searchPropertyList) {
+ m_searchPropertyList = searchPropertyList;
+ }
+
/**
* Returns configured custom {@link IClickableRowRenderer}<OT> render for rows when the popup lookup form is used.
*
@@ -45,6 +61,8 @@ public void clicked(DefaultLookupInputDialog clickednode) throws Exception
}
});
+ dlg.setSearchProperties(getSearchPropertyList());
+
//-- Move all extra stuff needed
String ttl = control.getDefaultTitle();
dlg.title(ttl);
diff --git a/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/LookupInputBase2.java b/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/LookupInputBase2.java
index 7e6720e695..b4b7341529 100644
--- a/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/LookupInputBase2.java
+++ b/to.etc.domui/src/main/java/to/etc/domui/component2/lookupinput/LookupInputBase2.java
@@ -33,7 +33,9 @@
import to.etc.domui.component.input.SimpleLookupInputRenderer;
import to.etc.domui.component.layout.Dialog;
import to.etc.domui.component.meta.ClassMetaModel;
+import to.etc.domui.component.meta.MetaManager;
import to.etc.domui.component.meta.SearchPropertyMetaModel;
+import to.etc.domui.component.meta.impl.SearchPropertyMetaModelImpl;
import to.etc.domui.component.tbl.BasicRowRenderer;
import to.etc.domui.component.tbl.IClickableRowRenderer;
import to.etc.domui.component.tbl.IQueryHandler;
@@ -52,9 +54,11 @@
import to.etc.domui.util.Msgs;
import to.etc.webapp.ProgrammerErrorException;
import to.etc.webapp.query.QCriteria;
+import to.etc.webapp.query.QField;
import java.util.List;
import java.util.Objects;
+import java.util.stream.Collectors;
abstract public class LookupInputBase2 extends AbstractLookupInputBase implements IControl, ITypedControl, IHasModifiedIndication, IQueryManipulator, IForTarget {
private static boolean m_globalDisableSelectOne = false;
@@ -80,6 +84,8 @@ protected void setKeySearch(@Nullable SearchInput2 keySearch) {
private int m_keyWordSearchPopupWidth;
+ private String m_keyWordSearchPopupMaxHeight;
+
@Nullable
private INotify