diff --git a/migration/i6.2z/oracle/201906171811_IDEMPIERE-3413.sql b/migration/i6.2z/oracle/201906171811_IDEMPIERE-3413.sql new file mode 100644 index 0000000000..2369f4e539 --- /dev/null +++ b/migration/i6.2z/oracle/201906171811_IDEMPIERE-3413.sql @@ -0,0 +1,86 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Dec 4, 2018 3:41:36 PM MYT +-- AP2-766 Implement Chosen Box control +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200161,'Chosen Multiple Selection List','fa2c2787-e93f-42be-a5d1-7a12f6b30b72','N','Chosen multiple selection box for reference list','D',TO_DATE('2018-12-04 15:41:35','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_DATE('2018-12-04 15:41:35','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 3:41:56 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200162,'Chosen Multiple Selection Table','07d7bf33-8998-4538-920d-5c0e0b2d46d2','N','Chosen multiple selection box for table list','D',TO_DATE('2018-12-04 15:41:56','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_DATE('2018-12-04 15:41:56','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 3:44:27 PM MYT +UPDATE AD_Val_Rule SET Code='AD_Reference.ValidationType=CASE WHEN @AD_Reference_ID@ IN (17,28,200152,200161) THEN ''L'' ELSE ''T'' END',Updated=TO_DATE('2018-12-04 15:44:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Val_Rule_ID=115 +; + +-- Dec 4, 2018 3:48:07 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200152 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-04 15:48:07','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 4, 2018 3:48:49 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=19 | @AD_Reference_ID@=28 | @AD_Reference_ID@=30 | @AD_Reference_ID@=200012 | @AD_Reference_ID@=31 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-04 15:48:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=172 +; + +-- Dec 4, 2018 4:49:17 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200163,'Chosen Multiple Selection Search','563a482c-4f06-448d-bb7f-e109d33cead9','N','Chosen multiple selection box for search','D',TO_DATE('2018-12-04 16:49:16','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_DATE('2018-12-04 16:49:16','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 4:49:55 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=19 | @AD_Reference_ID@=28 | @AD_Reference_ID@=30 | @AD_Reference_ID@=200012 | @AD_Reference_ID@=31 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-04 16:49:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=172 +; + +-- Dec 4, 2018 4:50:41 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200152 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-04 16:50:41','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 5, 2018 11:18:30 AM MYT +-- AP2-766 Implement Chosen Box control +UPDATE AD_Field SET AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-05 11:18:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 7, 2018 4:37:39 PM MYT +-- AP2-766 Implement Chosen Box control +UPDATE AD_Field SET AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2018-12-07 16:37:39','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=2540 +; + +-- IDEMPIERE-3413 Multi Select List and table reference +-- Jun 22, 2019, 5:11:30 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2019-06-22 17:11:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201622 +; + +-- Jun 22, 2019, 5:12:43 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsQueryCriteria@=Y & @AD_Reference_ID@!200161 & @AD_Reference_ID@!200162 & @AD_Reference_ID@!200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2019-06-22 17:12:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201636 +; + +-- Jun 22, 2019, 5:13:24 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsQueryCriteria@=Y & @AD_Reference_ID@!200161 & @AD_Reference_ID@!200162 & @AD_Reference_ID@!200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_DATE('2019-06-22 17:13:24','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201635 +; + +-- Jun 22, 2019, 5:15:39 PM MYT +UPDATE AD_Field SET AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', IsToolbarButton=NULL,Updated=TO_DATE('2019-06-22 17:15:39','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201622 +; + +CREATE TYPE TABLE_OF_VARCHAR2 AS TABLE OF VARCHAR2(4000) +; + +CREATE OR REPLACE FUNCTION toTableOfVarchar2(p_list IN VARCHAR2) + RETURN TABLE_OF_VARCHAR2 DETERMINISTIC + AS + l_tab TABLE_OF_VARCHAR2 := TABLE_OF_VARCHAR2(); + BEGIN + FOR i IN + ( select trim('"' from REGEXP_SUBSTR(p_list, '(".*?"|.*?)(,|$)', 1, level, NULL, 1)) split + from dual + connect by level<=length(regexp_replace(p_list,'".*?"|[^,]*'))+1) + LOOP + l_tab.EXTEND; + l_tab(l_tab.COUNT) := i.split; + END LOOP; + RETURN l_tab; + END toTableOfVarchar2 +; + +SELECT register_migration_script('201906171811_IDEMPIERE-3413.sql') FROM dual +; + diff --git a/migration/i6.2z/postgresql/201906171811_IDEMPIERE-3413.sql b/migration/i6.2z/postgresql/201906171811_IDEMPIERE-3413.sql new file mode 100644 index 0000000000..371c51b8b7 --- /dev/null +++ b/migration/i6.2z/postgresql/201906171811_IDEMPIERE-3413.sql @@ -0,0 +1,63 @@ +-- Dec 4, 2018 3:41:36 PM MYT +-- AP2-766 Implement Chosen Box control +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200161,'Chosen Multiple Selection List','fa2c2787-e93f-42be-a5d1-7a12f6b30b72','N','Chosen multiple selection box for reference list','D',TO_TIMESTAMP('2018-12-04 15:41:35','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_TIMESTAMP('2018-12-04 15:41:35','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 3:41:56 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200162,'Chosen Multiple Selection Table','07d7bf33-8998-4538-920d-5c0e0b2d46d2','N','Chosen multiple selection box for table list','D',TO_TIMESTAMP('2018-12-04 15:41:56','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_TIMESTAMP('2018-12-04 15:41:56','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 3:44:27 PM MYT +UPDATE AD_Val_Rule SET Code='AD_Reference.ValidationType=CASE WHEN @AD_Reference_ID@ IN (17,28,200152,200161) THEN ''L'' ELSE ''T'' END',Updated=TO_TIMESTAMP('2018-12-04 15:44:27','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Val_Rule_ID=115 +; + +-- Dec 4, 2018 3:48:07 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200152 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-04 15:48:07','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 4, 2018 3:48:49 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=19 | @AD_Reference_ID@=28 | @AD_Reference_ID@=30 | @AD_Reference_ID@=200012 | @AD_Reference_ID@=31 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-04 15:48:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=172 +; + +-- Dec 4, 2018 4:49:17 PM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,AD_Reference_UU,IsOrderByValue,Description,ValidationType,Updated,IsActive,CreatedBy,UpdatedBy,AD_Client_ID,Created,EntityType,AD_Org_ID) VALUES (200163,'Chosen Multiple Selection Search','563a482c-4f06-448d-bb7f-e109d33cead9','N','Chosen multiple selection box for search','D',TO_TIMESTAMP('2018-12-04 16:49:16','YYYY-MM-DD HH24:MI:SS'),'Y',100,100,0,TO_TIMESTAMP('2018-12-04 16:49:16','YYYY-MM-DD HH24:MI:SS'),'D',0) +; + +-- Dec 4, 2018 4:49:55 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=19 | @AD_Reference_ID@=28 | @AD_Reference_ID@=30 | @AD_Reference_ID@=200012 | @AD_Reference_ID@=31 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-04 16:49:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=172 +; + +-- Dec 4, 2018 4:50:41 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200152 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Val_Rule_ID=NULL, AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-04 16:50:41','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 5, 2018 11:18:30 AM MYT +-- AP2-766 Implement Chosen Box control +UPDATE AD_Field SET AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-05 11:18:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=171 +; + +-- Dec 7, 2018 4:37:39 PM MYT +-- AP2-766 Implement Chosen Box control +UPDATE AD_Field SET AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2018-12-07 16:37:39','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=2540 +; + +-- IDEMPIERE-3413 Multi Select List and table reference +-- Jun 22, 2019, 5:11:30 PM MYT +UPDATE AD_Field SET DisplayLogic='@AD_Reference_ID@=17 | @AD_Reference_ID@=18 | @AD_Reference_ID@=30 | @AD_Reference_ID@=28 | @AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2019-06-22 17:11:30','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201622 +; + +-- Jun 22, 2019, 5:12:43 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsQueryCriteria@=Y & @AD_Reference_ID@!200161 & @AD_Reference_ID@!200162 & @AD_Reference_ID@!200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2019-06-22 17:12:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201636 +; + +-- Jun 22, 2019, 5:13:24 PM MYT +UPDATE AD_Field SET DisplayLogic='@IsQueryCriteria@=Y & @AD_Reference_ID@!200161 & @AD_Reference_ID@!200162 & @AD_Reference_ID@!200163', AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2019-06-22 17:13:24','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201635 +; + +-- Jun 22, 2019, 5:15:39 PM MYT +UPDATE AD_Field SET AD_Reference_Value_ID=NULL, AD_Val_Rule_ID=NULL, MandatoryLogic='@AD_Reference_ID@=200161 | @AD_Reference_ID@=200162 | @AD_Reference_ID@=200163', IsToolbarButton=NULL,Updated=TO_TIMESTAMP('2019-06-22 17:15:39','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=201622 +; + +SELECT register_migration_script('201906171811_IDEMPIERE-3413.sql') FROM dual +; + diff --git a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java index 877ef7402b..c919be842c 100644 --- a/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java +++ b/org.adempiere.base/src/org/compiere/db/AdempiereDatabase.java @@ -339,5 +339,18 @@ public interface AdempiereDatabase public String getNameOfUniqueConstraintError(Exception e); + /** + * @param columnName + * @param csv comma separated value + * @return subset sql clause + */ + public String subsetClauseForCSV(String columnName, String csv); + + /** + * @param columnName + * @param csv comma separated value + * @return subset sql clause + */ + public String intersectClauseForCSV(String columnName, String csv); } // AdempiereDatabase diff --git a/org.adempiere.base/src/org/compiere/model/MLookup.java b/org.adempiere.base/src/org/compiere/model/MLookup.java index e8ba95e710..b2f65f4a78 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookup.java +++ b/org.adempiere.base/src/org/compiere/model/MLookup.java @@ -238,8 +238,29 @@ public final class MLookup extends Lookup implements Serializable public String getDisplay (Object key) { if (key == null) - return ""; - // + return ""; + // + if (m_info.DisplayType==DisplayType.ChosenMultipleSelectionList || m_info.DisplayType==DisplayType.ChosenMultipleSelectionSearch + || m_info.DisplayType==DisplayType.ChosenMultipleSelectionTable) + { + StringBuilder builder = new StringBuilder(); + String[] keys = key.toString().split("[,]"); + for(String k : keys) + { + if (builder.length() > 0) + builder.append(", "); + Object display = get(k); + if (display == null) + { + builder.append("<").append(k).append(">"); + } + else + { + builder.append(display.toString()); + } + } + return builder.toString(); + } Object display = get (key); if (display == null){ StringBuilder msgreturn = new StringBuilder("<").append(key.toString()).append(">"); diff --git a/org.adempiere.base/src/org/compiere/model/MLookupFactory.java b/org.adempiere.base/src/org/compiere/model/MLookupFactory.java index d396aec622..e8d00c8547 100644 --- a/org.adempiere.base/src/org/compiere/model/MLookupFactory.java +++ b/org.adempiere.base/src/org/compiere/model/MLookupFactory.java @@ -185,13 +185,14 @@ public class MLookupFactory MLookupInfo info = null; boolean needToAddSecurity = true; // List - if (AD_Reference_ID == DisplayType.List) // 17 + if (AD_Reference_ID == DisplayType.List || AD_Reference_ID == DisplayType.ChosenMultipleSelectionList) // 17 { info = getLookup_List(language, AD_Reference_Value_ID); needToAddSecurity = false; } // Table or Search with Reference_Value - else if ((AD_Reference_ID == DisplayType.Table || AD_Reference_ID == DisplayType.Search) + else if ((AD_Reference_ID == DisplayType.Table || AD_Reference_ID == DisplayType.Search + || AD_Reference_ID == DisplayType.ChosenMultipleSelectionTable || AD_Reference_ID == DisplayType.ChosenMultipleSelectionSearch) && AD_Reference_Value_ID != 0) { info = getLookup_Table (ctx, language, WindowNo, AD_Reference_Value_ID); diff --git a/org.adempiere.base/src/org/compiere/model/MQuery.java b/org.adempiere.base/src/org/compiere/model/MQuery.java index c19020b03f..951f964667 100644 --- a/org.adempiere.base/src/org/compiere/model/MQuery.java +++ b/org.adempiere.base/src/org/compiere/model/MQuery.java @@ -87,7 +87,7 @@ public class MQuery implements Serializable SQL = "SELECT ip.ParameterName,ip.P_String,ip.P_String_To," // 1..3 + "ip.P_Number,ip.P_Number_To," // 4..5 + "ip.P_Date,ip.P_Date_To, ip.Info,ip.Info_To, " // 6..9 - + "pp.Name, pp.IsRange, pp.AD_Reference_ID " // 10..12 + + "pp.Name, pp.IsRange, pp.AD_Reference_ID " // 10..12 + "FROM AD_PInstance_Para ip, AD_PInstance i, AD_Process_Para pp " + "WHERE i.AD_PInstance_ID=ip.AD_PInstance_ID" + " AND pp.AD_Process_ID=i.AD_Process_ID" @@ -166,12 +166,32 @@ public class MQuery implements Serializable { if (P_String_To == null) { - if (P_String.indexOf('%') == -1) - query.addRestriction(ParameterName, MQuery.EQUAL, - P_String, Name, Info); + if (Reference_ID == DisplayType.ChosenMultipleSelectionList) + { + String columnName = TableName + "." + ParameterName; + int cnt = DB.getSQLValueEx(null, "SELECT Count(*) From AD_Column WHERE IsActive='Y' AND AD_Client_ID=0 AND Upper(ColumnName)=? AND AD_Reference_ID=?", ParameterName.toUpperCase(), DisplayType.ChosenMultipleSelectionList); + if (cnt > 0) + query.addRestriction(DB.intersectClauseForCSV(columnName, P_String), MQuery.EQUAL, Name, Info); + else + query.addRestriction(DB.inClauseForCSV(columnName, P_String), MQuery.EQUAL, Name, Info); + } + else if (Reference_ID == DisplayType.ChosenMultipleSelectionTable || Reference_ID == DisplayType.ChosenMultipleSelectionSearch) + { + String columnName = TableName + "." + ParameterName; + if (columnName.endsWith("_ID")) + query.addRestriction(DB.inClauseForCSV(columnName, P_String), MQuery.EQUAL, Name, Info); + else + query.addRestriction(DB.intersectClauseForCSV(columnName, P_String), MQuery.EQUAL, Name, Info); + } else - query.addRestriction(ParameterName, MQuery.LIKE, - P_String, Name, Info); + { + if (P_String.indexOf('%') == -1) + query.addRestriction(ParameterName, MQuery.EQUAL, + P_String, Name, Info); + else + query.addRestriction(ParameterName, MQuery.LIKE, + P_String, Name, Info); + } } else query.addRangeRestriction(ParameterName, @@ -235,7 +255,6 @@ public class MQuery implements Serializable return query; } // get - /** * Get Zoom Column Name. * Converts Synonyms like SalesRep_ID to AD_User_ID @@ -632,6 +651,20 @@ public class MQuery implements Serializable m_newRecord = whereClause.equals(NEWRECORD); } // addRestriction + public void addRestriction (String whereClause, String Operator, String InfoName, String InfoDisplay) + { + if (whereClause == null || whereClause.trim().length() == 0) + return; + Restriction r = new Restriction (whereClause, true, 0); + r.Operator = Operator; + if (InfoName != null) + r.InfoName = InfoName; + if (InfoDisplay != null) + r.InfoDisplay = InfoDisplay.trim(); + m_list.add(r); + m_newRecord = whereClause.equals(NEWRECORD); + } + /** * New Record Query * @return true if new record query diff --git a/org.adempiere.base/src/org/compiere/model/SystemIDs.java b/org.adempiere.base/src/org/compiere/model/SystemIDs.java index 686c738a80..82ad176a9c 100644 --- a/org.adempiere.base/src/org/compiere/model/SystemIDs.java +++ b/org.adempiere.base/src/org/compiere/model/SystemIDs.java @@ -96,6 +96,9 @@ public class SystemIDs public final static int REFERENCE_DATATYPE_ASSIGNMENT = 33; public final static int REFERENCE_DATATYPE_BINARY = 23; public final static int REFERENCE_DATATYPE_BUTTON = 28; + public final static int REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_LIST = 200161; + public final static int REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_TABLE = 200162; + public final static int REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_SEARCH = 200163; public final static int REFERENCE_DATATYPE_COLOR = 27; public final static int REFERENCE_DATATYPE_COSTPRICE = 37; public final static int REFERENCE_DATATYPE_DATE = 15; diff --git a/org.adempiere.base/src/org/compiere/util/DB.java b/org.adempiere.base/src/org/compiere/util/DB.java index 3972555cb1..4a814f549d 100644 --- a/org.adempiere.base/src/org/compiere/util/DB.java +++ b/org.adempiere.base/src/org/compiere/util/DB.java @@ -2578,4 +2578,57 @@ public final class DB return ProxyFactory.newCPreparedStatement(resultSetType, resultSetConcurrency, sql, trxName); } + /** + * @param columnName + * @param csv comma separated value + * @return IN clause + */ + public static String inClauseForCSV(String columnName, String csv) + { + StringBuilder builder = new StringBuilder(); + builder.append(columnName).append(" IN ("); + String[] values = csv.split("[,]"); + for(int i = 0; i < values.length; i++) + { + if (i > 0) + builder.append(","); + String key = values[i]; + if (columnName.endsWith("_ID")) + { + builder.append(key); + } + else + { + if (key.startsWith("\"") && key.endsWith("\"")) + { + key = key.substring(1, key.length()-1); + } + builder.append(TO_STRING(key)); + } + } + builder.append(")"); + return builder.toString(); + } + + /** + * + * @param columnName + * @param csv + * @return subset sql clause + */ + public static String subsetClauseForCSV(String columnName, String csv) + { + return getDatabase().subsetClauseForCSV(columnName, csv); + } + + /** + * + * @param columnName + * @param csv + * @return intersect sql clause + */ + public static String intersectClauseForCSV(String columnName, String csv) + { + return getDatabase().intersectClauseForCSV(columnName, csv); + } } // DB diff --git a/org.adempiere.base/src/org/compiere/util/DisplayType.java b/org.adempiere.base/src/org/compiere/util/DisplayType.java index 2a4fe4be99..c73db00a25 100644 --- a/org.adempiere.base/src/org/compiere/util/DisplayType.java +++ b/org.adempiere.base/src/org/compiere/util/DisplayType.java @@ -21,6 +21,9 @@ import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_AMOUNT; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_ASSIGNMENT; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_BINARY; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_BUTTON; +import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_LIST; +import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_TABLE; +import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_SEARCH; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_COLOR; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_COSTPRICE; import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_DATE; @@ -156,6 +159,12 @@ public final class DisplayType public static final int MultipleSelectionGrid = REFERENCE_DATATYPE_MULTIPLE_SELECTION_GRID; + public static final int ChosenMultipleSelectionList = REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_LIST; + + public static final int ChosenMultipleSelectionTable = REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_TABLE; + + public static final int ChosenMultipleSelectionSearch = REFERENCE_DATATYPE_CHOSEN_MULTIPLE_SELECTION_SEARCH; + /** * - New Display Type INSERT INTO AD_REFERENCE @@ -272,7 +281,10 @@ public final class DisplayType || displayType == FilePath || displayType == FileName || displayType == URL || displayType == PrinterName || displayType == SingleSelectionGrid || displayType == Color - || displayType == MultipleSelectionGrid) + || displayType == MultipleSelectionGrid + || displayType == ChosenMultipleSelectionList + || displayType == ChosenMultipleSelectionTable + || displayType == ChosenMultipleSelectionSearch) return true; List factoryList = Service.locator().list(IDisplayTypeFactory.class).getServices(); @@ -312,7 +324,10 @@ public final class DisplayType public static boolean isLookup(int displayType) { if (displayType == List || displayType == Table - || displayType == TableDir || displayType == Search) + || displayType == TableDir || displayType == Search + || displayType == ChosenMultipleSelectionTable + || displayType == ChosenMultipleSelectionSearch + || displayType == ChosenMultipleSelectionList) return true; List factoryList = Service.locator().list(IDisplayTypeFactory.class).getServices(); diff --git a/org.adempiere.ui.zk/META-INF/MANIFEST.MF b/org.adempiere.ui.zk/META-INF/MANIFEST.MF index 3bb82eb2bb..b8f79ac7f9 100644 --- a/org.adempiere.ui.zk/META-INF/MANIFEST.MF +++ b/org.adempiere.ui.zk/META-INF/MANIFEST.MF @@ -91,6 +91,10 @@ Export-Package: fi.jawsy.jawwa.zk.atmosphere, org.adempiere.webui.window, org.zkforge.ckez, org.zkforge.keylistener, + web.chosenbox.img, + web.js.chosenbox, + web.js.chosenbox.css, + web.js.chosenbox.mold, web.ckez.html, web.ckez.img, web.js.ckez, diff --git a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml index 7a46ecca0d..ac09f246e8 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml +++ b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml @@ -33,6 +33,17 @@ Copyright (C) 2007 Ashley G Ramdass (ADempiere WebUI). + + chosenbox + org.zkoss.addon.chosenbox.Chosenbox + chosenbox.Chosenbox + + default + mold/chosenbox.js + css/chosenbox.css.dsp + + + diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/ChosenSearchBox.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/ChosenSearchBox.java new file mode 100644 index 0000000000..e07a351337 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/component/ChosenSearchBox.java @@ -0,0 +1,152 @@ +/****************************************************************************** + * Project: Trek Global ERP * + * Copyright (C) 2009-2018 Trek Global Corporation * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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 General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.component; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +import org.adempiere.webui.LayoutUtils; +import org.compiere.util.ValueNamePair; +import org.zkoss.addon.chosenbox.Chosenbox; +import org.zkoss.zk.ui.Page; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.Div; + +/** + * @author Low Heng Sin + */ +public class ChosenSearchBox extends Div { + /** + * + */ + private static final long serialVersionUID = -3152111756471436612L; + protected PropertyChangeSupport m_propertyChangeListeners = new PropertyChangeSupport( + this); + protected Chosenbox chosenbox; + protected Button btn; + + public ChosenSearchBox() { + initComponents(); + } + + /** + * @param imageSrc + */ + public void setButtonImage(String imageSrc) { + btn.setImage(imageSrc); + } + + private void initComponents() { + chosenbox = new Chosenbox<>(); + chosenbox.setSclass("editor-input"); + chosenbox.setWidth("100%"); + appendChild(chosenbox); + btn = new Button(); + btn.setTabindex(-1); + btn.setHflex("0"); + btn.setSclass("editor-button"); + appendChild(btn); + + LayoutUtils.addSclass("editor-box", this); + setTableEditorMode(false); + } + + /** + * @return chosenbox component + */ + public Chosenbox getChosenbox() { + return chosenbox; + } + + /** + * @param enabled + */ + public void setEnabled(boolean enabled) { + chosenbox.setDisabled(!enabled); + btn.setEnabled(enabled); + btn.setVisible(enabled); + if (enabled) { + if (btn.getParent() != chosenbox.getParent()) + btn.setParent(chosenbox.getParent()); + } else { + if (btn.getParent() != null) + btn.detach(); + } + if (enabled) { + LayoutUtils.removeSclass("editor-input-disd", chosenbox); + } else { + LayoutUtils.addSclass("editor-input-disd", chosenbox); + } + } + + /** + * @return boolean + */ + public boolean isEnabled() { + return btn.isEnabled(); + } + + /** + * @param evtnm + * @param listener + */ + public boolean addEventListener(String evtnm, EventListener listener) { + if (Events.ON_CLICK.equals(evtnm)) { + return btn.addEventListener(evtnm, listener); + } else { + return chosenbox.addEventListener(evtnm, listener); + } + } + + /** + * @param l + */ + public synchronized void addPropertyChangeListener(PropertyChangeListener l) { + m_propertyChangeListeners.addPropertyChangeListener(l); + } + + /** + * @param tooltiptext + */ + public void setToolTipText(String tooltiptext) { + chosenbox.setTooltiptext(tooltiptext); + } + + /** + * @return Button + */ + public Button getButton() { + return btn; + } + + public void setTableEditorMode(boolean flag) { + if (flag) { + setHflex("0"); + LayoutUtils.addSclass("grid-editor-input", chosenbox); + LayoutUtils.addSclass("grid-editor-button", btn); + } else { + setHflex("1"); + LayoutUtils.removeSclass("grid-editor-input", chosenbox); + LayoutUtils.removeSclass("grid-editor-button", btn); + } + + } + + @Override + public void onPageAttached(Page newpage, Page oldpage) { + super.onPageAttached(newpage, oldpage); + } +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxListEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxListEditor.java new file mode 100644 index 0000000000..ff2fe712b5 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxListEditor.java @@ -0,0 +1,560 @@ +/****************************************************************************** + * Project: Trek Global ERP * + * Copyright (C) 2009-2018 Trek Global Corporation * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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 General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.editor; + +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.adempiere.webui.ValuePreference; +import org.adempiere.webui.event.ContextMenuEvent; +import org.adempiere.webui.event.ContextMenuListener; +import org.adempiere.webui.event.ValueChangeEvent; +import org.adempiere.webui.window.WFieldRecordInfo; +import org.compiere.model.GridField; +import org.compiere.model.Lookup; +import org.compiere.model.MLookup; +import org.compiere.util.CCache; +import org.compiere.util.CLogger; +import org.compiere.util.CacheMgt; +import org.compiere.util.KeyNamePair; +import org.compiere.util.Util; +import org.compiere.util.ValueNamePair; +import org.zkoss.addon.chosenbox.Chosenbox; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Desktop; +import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.Page; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zk.ui.util.DesktopCleanup; +import org.zkoss.zul.ListModelList; + +/** + * + * @author hengsin + * + */ +public class WChosenboxListEditor extends WEditor implements ContextMenuListener +{ + public final static String[] LISTENER_EVENTS = {Events.ON_SELECT}; + + @SuppressWarnings("unused") + private static final CLogger logger; + + static + { + logger = CLogger.getCLogger(WTableDirEditor.class); + } + + private Lookup lookup; + private Object oldValue; + + private CCacheListener tableCacheListener; + + private boolean onselecting = false; + + private ListModelList model = new ListModelList<>(); + + public WChosenboxListEditor(GridField gridField) + { + this(new ChosenboxEditor(), gridField); + } + + private WChosenboxListEditor(Component comp, GridField gridField) + { + super(comp, gridField); + lookup = gridField.getLookup(); + init(); + } + + /** + * Constructor for use if a grid field is unavailable + * + * @param lookup Store of selectable data + * @param label column name (not displayed) + * @param description description of component + * @param mandatory whether a selection must be made + * @param readonly whether or not the editor is read only + * @param updateable whether the editor contents can be changed + */ + public WChosenboxListEditor(Lookup lookup, String label, String description, boolean mandatory, boolean readonly, boolean updateable) + { + this(lookup, label, description, mandatory, readonly, updateable, false); + } + + public WChosenboxListEditor(Lookup lookup, String label, String description, boolean mandatory, boolean readonly, boolean updateable, boolean autocomplete) + { + this(new ChosenboxEditor(), lookup, label, description, mandatory, readonly, updateable); + } + + private WChosenboxListEditor(Component comp, Lookup lookup, String label, String description, boolean mandatory, boolean readonly, boolean updateable) + { + super(comp, label, description, mandatory, readonly, updateable); + + if (lookup == null) + { + throw new IllegalArgumentException("Lookup cannot be null"); + } + + this.lookup = lookup; + super.setColumnName(lookup.getColumnName()); + init(); + } + + /** + * For ease of porting swing form + * @param columnName + * @param mandatory + * @param isReadOnly + * @param isUpdateable + * @param lookup + */ + public WChosenboxListEditor(String columnName, boolean mandatory, boolean isReadOnly, boolean isUpdateable, Lookup lookup) + { + this(columnName, mandatory, isReadOnly, isUpdateable, lookup, false); + } + + public WChosenboxListEditor(String columnName, boolean mandatory, boolean isReadOnly, boolean isUpdateable, Lookup lookup, boolean autocomplete) + { + this(new ChosenboxEditor(), columnName, mandatory, isReadOnly, isUpdateable, lookup); + } + + private WChosenboxListEditor(Component comp, String columnName, boolean mandatory, boolean isReadOnly, boolean isUpdateable, Lookup lookup) + { + super(comp, columnName, null, null, mandatory, isReadOnly, isUpdateable); + if (lookup == null) + { + throw new IllegalArgumentException("Lookup cannot be null"); + } + this.lookup = lookup; + init(); + } + + private void init() + { + getComponent().setHflex("true"); + getComponent().editor = this; + getComponent().setModel(model); + + if (lookup != null) + { + lookup.setMandatory(true); + + //no need to refresh readonly lookup + if (isReadWrite()) + { + refreshLookup(); + } + else + { + updateModel(); + } + } + + if (gridField != null) + { + popupMenu = new WEditorPopupMenu(false, true, isShowPreference(), false, false, false, lookup); + addChangeLogMenu(popupMenu); + } + } + + protected void refreshLookup() { + lookup.refresh(); + updateModel(); + } + + @Override + public String getDisplay() + { + StringBuilder display = new StringBuilder(); + LinkedHashSet selected = getComponent().getSelectedObjects(); + if (selected != null && selected.size() > 0) + { + for(ValueNamePair pair : selected) + { + if (display.length() > 0) + display.append(", "); + display.append(pair.getName()); + } + } + return display.toString(); + } + + @Override + public Object getValue() + { + return oldValue; + } + + private String getValueFromComponent() + { + StringBuilder retVal = new StringBuilder(); + LinkedHashSet selected = getComponent().getSelectedObjects(); + if (selected != null && selected.size() > 0) + { + for(ValueNamePair pair : selected) + { + if (retVal.length() > 0) + retVal.append(","); + StringBuilder builder = new StringBuilder(pair.getValue()); + if (builder.indexOf(",") >= 0) + { + builder.insert(0, "\""); + builder.append("\""); + } + retVal.append(builder.toString()); + } + } + return retVal.length() > 0 ? retVal.toString() : null; + } + + public void setValue(Object value) + { + if (onselecting) { + return; + } + + if (value != null) + { + String[] values = ((String)value).split("[,]"); + Set selected = new LinkedHashSet<>(); + for (String key : values) { + if (!Util.isEmpty(key)) { + if (key.startsWith("\"") && key.endsWith("\"")) { + key = key.substring(1, key.length()-1); + } + String name = lookup.getDisplay(key); + ValueNamePair pair = new ValueNamePair(key, name); + selected.add(pair); + } + } + getComponent().setSelectedObjects(selected); + if (getComponent().getSelectedObjects().size() != selected.size()) + { + Object curValue = oldValue; + oldValue = value; + + if (isReadWrite() && lookup != null) + { + refreshLookup(); + } + else + { + updateModel(); + } + getComponent().setSelectedObjects(selected); + + //still not in list, reset to zero + if (getComponent().getSelectedObjects().size() != selected.size()) + { + getComponent().setSelectedObjects(new LinkedHashSet()); + if (curValue == null) + curValue = value; + ValueChangeEvent changeEvent = new ValueChangeEvent(this, this.getColumnName(), curValue, null); + super.fireValueChange(changeEvent); + oldValue = null; + } + } + else + { + oldValue = value; + } + } + else + { + getComponent().setSelectedObjects(new LinkedHashSet()); + oldValue = value; + } + } + + @Override + public ChosenboxEditor getComponent() { + return (ChosenboxEditor) component; + } + + @Override + public boolean isReadWrite() { + return getComponent().isEnabled(); + } + + @Override + public void setReadWrite(boolean readWrite) { + getComponent().setEnabled(readWrite); + } + + private void updateModel() + { + List list = new ArrayList<>(); + + if (isReadWrite()) + { + if (lookup != null) + { + int size = lookup.getSize(); + + for (int i = 0; i < size; i++) + { + Object obj = lookup.getElementAt(i); + if (obj instanceof KeyNamePair) + { + KeyNamePair lookupKNPair = (KeyNamePair) obj; + ValueNamePair vnp = new ValueNamePair(Integer.toString(lookupKNPair.getKey()), lookupKNPair.getName()); + list.add(vnp); + } + else if (obj instanceof ValueNamePair) + { + ValueNamePair lookupKNPair = (ValueNamePair) obj; + list.add(lookupKNPair); + } + } + } + } + else + { + if (lookup != null && oldValue != null) + { + String[] values = ((String)oldValue).split("[,]"); + List selected = new ArrayList<>(); + for (String key : values) { + if (!Util.isEmpty(key)) { + if (key.startsWith("\"") && key.endsWith("\"")) { + key = key.substring(1, key.length()-1); + } + String name = lookup.getDisplay(key); + ValueNamePair pair = new ValueNamePair(key, name); + selected.add(pair); + } + } + list = selected; + } + } + + model.clear(); + model.addAll(list); + } + + public void onEvent(Event event) + { + if (Events.ON_SELECT.equalsIgnoreCase(event.getName())) + { + try { + onselecting = true; + Object newValue = getValueFromComponent(); + if (isValueChange(newValue)) { + try { + if (gridField != null) + gridField.setLookupEditorSettingValue(true); + ValueChangeEvent changeEvent = new ValueChangeEvent(this, this.getColumnName(), oldValue, newValue); + super.fireValueChange(changeEvent); + oldValue = newValue; + } finally { + if (gridField != null) + gridField.setLookupEditorSettingValue(false); + } + } + } finally { + onselecting = false; + } + } + } + + private boolean isValueChange(Object newValue) { + return (oldValue == null && newValue != null) || (oldValue != null && newValue == null) + || ((oldValue != null && newValue != null) && !oldValue.equals(newValue)); + } + + public String[] getEvents() + { + return LISTENER_EVENTS; + } + + public void actionRefresh() + { + if (lookup != null) + { + Object curValue = getValue(); + + if (isReadWrite()) + refreshLookup(); + else + updateModel(); + if (curValue != null) + { + setValue(curValue); + } + } + } + + public Lookup getLookup() + { + return lookup; + } + + public void onMenu(ContextMenuEvent evt) + { + if (WEditorPopupMenu.REQUERY_EVENT.equals(evt.getContextEvent())) + { + actionRefresh(); + } + else if (WEditorPopupMenu.PREFERENCE_EVENT.equals(evt.getContextEvent())) + { + if (isShowPreference()) + ValuePreference.start (getComponent(), this.getGridField(), getValue()); + return; + } + else if (WEditorPopupMenu.CHANGE_LOG_EVENT.equals(evt.getContextEvent())) + { + WFieldRecordInfo.start(gridField); + } + } + + public void propertyChange(PropertyChangeEvent evt) + { + if ("FieldValue".equals(evt.getPropertyName())) + { + setValue(evt.getNewValue()); + } + } + + @Override + public void dynamicDisplay(Properties ctx) + { + if (lookup instanceof MLookup) + { + ((MLookup) lookup).getLookupInfo().ctx = ctx; + } + if ((lookup != null) && (!lookup.isValidated() || !lookup.isLoaded() + || (isReadWrite() && lookup.getSize() != getComponent().getModel().getSize()))) + this.actionRefresh(); + + super.dynamicDisplay(ctx); + } + + private void createCacheListener() { + if (lookup != null) { + String columnName = lookup.getColumnName(); + int dotIndex = columnName.indexOf("."); + if (dotIndex > 0) { + String tableName = columnName.substring(0, dotIndex); + tableCacheListener = new CCacheListener(tableName, this); + } + } + } + + private final static class ChosenboxEditor extends Chosenbox { + + /** + * generated serial id + */ + private static final long serialVersionUID = 7777300782255405327L; + private DesktopCleanup listener = null; + private WChosenboxListEditor editor = null; + + protected ChosenboxEditor() { + } + + public void setEnabled(boolean readWrite) { + setDisabled(readWrite==false); + } + + public boolean isEnabled() { + return isDisabled() == false; + } + + @Override + public void setPage(Page page) { + super.setPage(page); + } + + @Override + public void onPageAttached(Page newpage, Page oldpage) { + super.onPageAttached(newpage, oldpage); + if (editor != null && editor.tableCacheListener == null) { + editor.createCacheListener(); + if (listener == null) { + listener = new DesktopCleanup() { + @Override + public void cleanup(Desktop desktop) throws Exception { + ChosenboxEditor.this.cleanup(); + } + }; + newpage.getDesktop().addListener(listener); + } + } + } + + @Override + public void onPageDetached(Page page) { + super.onPageDetached(page); + if (listener != null && page.getDesktop() != null) + page.getDesktop().removeListener(listener); + cleanup(); + } + + /** + * + */ + protected void cleanup() { + if (editor != null && editor.tableCacheListener != null) { + CacheMgt.get().unregister(editor.tableCacheListener); + editor.tableCacheListener = null; + } + } + } + + private static class CCacheListener extends CCache { + /** + * generated serial + */ + private static final long serialVersionUID = 3543247404379028327L; + private WChosenboxListEditor editor; + + protected CCacheListener(String tableName, WChosenboxListEditor editor) { + super(tableName, tableName, 0, true); + this.editor = editor; + } + + @Override + public int reset() { + if (editor.getComponent().getDesktop() != null && editor.isReadWrite()) { + refreshLookupList(); + } + return 0; + } + + private void refreshLookupList() { + Executions.schedule(editor.getComponent().getDesktop(), new EventListener() { + @Override + public void onEvent(Event event) { + try { + if (editor.isReadWrite()) + editor.actionRefresh(); + } catch (Exception e) {} + } + }, new Event("onResetLookupList")); + } + + @Override + public void newRecord(int record_ID) { + if (editor.getComponent().getDesktop() != null && editor.isReadWrite()) { + refreshLookupList(); + } + } + } +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxSearchEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxSearchEditor.java new file mode 100644 index 0000000000..dca79d4d1e --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WChosenboxSearchEditor.java @@ -0,0 +1,793 @@ +/****************************************************************************** + * Project: Trek Global ERP * + * Copyright (C) 2009-2018 Trek Global Corporation * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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 General Public License for more details. * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.adempiere.webui.editor; + +import java.beans.PropertyChangeEvent; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Level; + +import org.adempiere.webui.ValuePreference; +import org.adempiere.webui.apps.AEnv; +import org.adempiere.webui.component.ChosenSearchBox; +import org.adempiere.webui.event.ContextMenuEvent; +import org.adempiere.webui.event.ContextMenuListener; +import org.adempiere.webui.event.DialogEvents; +import org.adempiere.webui.event.ValueChangeEvent; +import org.adempiere.webui.factory.InfoManager; +import org.adempiere.webui.panel.IHelpContext; +import org.adempiere.webui.panel.InfoPanel; +import org.adempiere.webui.part.WindowContainer; +import org.adempiere.webui.session.SessionManager; +import org.adempiere.webui.theme.ThemeManager; +import org.adempiere.webui.window.WFieldRecordInfo; +import org.compiere.model.GridField; +import org.compiere.model.Lookup; +import org.compiere.model.MLookup; +import org.compiere.model.MRole; +import org.compiere.model.X_AD_CtxHelp; +import org.compiere.util.CLogger; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Util; +import org.compiere.util.ValueNamePair; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.ListModel; +import org.zkoss.zul.ListModelList; +import org.zkoss.zul.ListSubModel; + +/** + * + * @author hengsin + * + */ +public class WChosenboxSearchEditor extends WEditor implements ContextMenuListener +{ + private static final String[] LISTENER_EVENTS = {Events.ON_CLICK, Events.ON_SELECT}; + private Lookup lookup; + private String m_tableName = null; + private String m_keyColumnName = null; + private String columnName; + private String value; + private InfoPanel infoPanel = null; + private String imageUrl; + private MyListModel model = new MyListModel(); + + private static CLogger log = CLogger.getCLogger(WChosenboxSearchEditor.class); + private boolean onselecting; + + public WChosenboxSearchEditor (GridField gridField) + { + super(new ChosenSearchBox(), gridField); + + lookup = gridField.getLookup(); + + if (lookup != null) + columnName = lookup.getColumnName(); + + init(); + } + + + @Override + public ChosenSearchBox getComponent() { + return (ChosenSearchBox) super.getComponent(); + } + + @Override + public boolean isReadWrite() { + return getComponent().isEnabled(); + } + + + @Override + public void setReadWrite(boolean readWrite) { + getComponent().setEnabled(readWrite); + } + + + /** + * Constructor for use if a grid field is unavailable + * + * @param lookup Store of selectable data + * @param label column name (not displayed) + * @param description description of component + * @param mandatory whether a selection must be made + * @param readonly whether or not the editor is read only + * @param updateable whether the editor contents can be changed + */ + public WChosenboxSearchEditor (Lookup lookup, String label, String description, boolean mandatory, boolean readonly, boolean updateable) + { + super(new ChosenSearchBox(), label, description, mandatory, readonly, updateable); + + if (lookup == null) + { + throw new IllegalArgumentException("Lookup cannot be null"); + } + + this.lookup = lookup; + columnName = lookup.getColumnName(); + super.setColumnName(columnName); + init(); + } + + public WChosenboxSearchEditor(String columnName, boolean mandatory, boolean readonly, boolean updateable, + Lookup lookup) + { + super(new ChosenSearchBox(), null, null, mandatory, readonly, updateable); + + if (lookup == null) + { + throw new IllegalArgumentException("Lookup cannot be null"); + } + + this.lookup = lookup; + this.columnName = columnName; + super.setColumnName(columnName); + init(); + } + + + /** + * initialise editor + * @param columnName columnName + */ + private void init() + { + columnName = this.getColumnName(); + imageUrl = ThemeManager.getThemeResource("images/PickOpen16.png"); + if (lookup instanceof MLookup) + { + MLookup mlookup = (MLookup) lookup; + if ("C_BPartner_ID".equals(mlookup.getLookupInfo().KeyColumn)) + { + imageUrl = ThemeManager.getThemeResource("images/BPartner16.png"); + } + else if ("M_Product_ID".equals(mlookup.getLookupInfo().KeyColumn)) + { + imageUrl = ThemeManager.getThemeResource("images/Product16.png"); + } + } + popupMenu = new WEditorPopupMenu(false, true, isShowPreference(), false, false, false, lookup); + getComponent().getButton().setImage(imageUrl); + getComponent().getChosenbox().setModel(model); + + addChangeLogMenu(popupMenu); + + return; + } + + @Override + public void setValue(Object value) + { + if (onselecting) { + return; + } + + if (value != null && value instanceof String && !Util.isEmpty((String) value, true)) + { + String[] values = ((String)value).split("[,]"); + Set selected = new LinkedHashSet<>(); + for (String key : values) { + if (!Util.isEmpty(key)) { + if (key.startsWith("\"") && key.endsWith("\"")) { + key = key.substring(1, key.length()-1); + } + String name = lookup.getDisplay(key); + ValueNamePair pair = new ValueNamePair(key, name); + selected.add(pair); + } + } + model.clear(); + model.addAll(selected); + getComponent().getChosenbox().setSelectedObjects(selected); + this.value = (String)value; + } + else + { + model.clear(); + getComponent().getChosenbox().setSelectedObjects(new LinkedHashSet()); + this.value = null; + } + } + + @Override + public Object getValue() + { + return value; + } + + private String getValueFromComponent() + { + StringBuilder retVal = new StringBuilder(); + LinkedHashSet selected = getComponent().getChosenbox().getSelectedObjects(); + if (selected != null && selected.size() > 0) + { + for(ValueNamePair pair : selected) + { + if (retVal.length() > 0) + retVal.append(","); + StringBuilder builder = new StringBuilder(pair.getValue()); + if (builder.indexOf(",") >= 0) + { + builder.insert(0, "\""); + builder.append("\""); + } + retVal.append(builder.toString()); + } + } + return retVal.length() > 0 ? retVal.toString() : null; + } + + @Override + public String getDisplay() + { + StringBuilder display = new StringBuilder(); + LinkedHashSet selected = getComponent().getChosenbox().getSelectedObjects(); + if (selected != null && selected.size() > 0) + { + for(ValueNamePair pair : selected) + { + if (display.length() > 0) + display.append(", "); + display.append(pair.getName()); + } + } + return display.toString(); + } + + public void onEvent(Event e) + { + if (Events.ON_CLICK.equals(e.getName())) + { + if (infoPanel != null) + { + infoPanel.detach(); + infoPanel = null; + } + actionButton(); + } + else if (Events.ON_SELECT.equalsIgnoreCase(e.getName())) + { + try { + onselecting = true; + String newValue = getValueFromComponent(); + if (isValueChange(newValue)) { + try { + if (gridField != null) + gridField.setLookupEditorSettingValue(true); + ValueChangeEvent changeEvent = new ValueChangeEvent(this, this.getColumnName(), value, newValue); + super.fireValueChange(changeEvent); + value = newValue; + } finally { + if (gridField != null) + gridField.setLookupEditorSettingValue(false); + } + } + } finally { + onselecting = false; + } + } + } + + private boolean isValueChange(Object newValue) { + return (value == null && newValue != null) || (value != null && newValue == null) + || ((value != null && newValue != null) && !value.equals(newValue)); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + if ("FieldValue".equals(evt.getPropertyName())) + { + setValue(evt.getNewValue()); + } + } + + public void onMenu(ContextMenuEvent evt) + { + if (WEditorPopupMenu.PREFERENCE_EVENT.equals(evt.getContextEvent())) + { + if (isShowPreference()) + ValuePreference.start (getComponent(), this.getGridField(), getValue()); + return; + } + else if (WEditorPopupMenu.CHANGE_LOG_EVENT.equals(evt.getContextEvent())) + { + WFieldRecordInfo.start(gridField); + } + } + + private void processSelectedKeys (Object value) + { + if (log.isLoggable(Level.FINE)) + log.fine("Value=" + value); + + try + { + if (gridField != null) + gridField.setLookupEditorSettingValue(true); + + String newValue = this.value; + if (newValue == null) + { + if (value instanceof Object[]) + { + if (((Object[])value).length > 0) + { + StringBuilder builder = new StringBuilder(); + for(Object obj : (Object[])value) + { + if (obj != null) + { + if (builder.length() > 0) + builder.append(","); + builder.append(obj.toString()); + } + } + newValue = builder.toString(); + } + } + else + { + newValue = value != null ? value.toString() : null; + } + } + else if (value != null) + { + if (value instanceof Object[]) + { + if (((Object[])value).length > 0) + { + StringBuilder builder = new StringBuilder(newValue); + for(Object obj : (Object[])value) + { + if (obj != null) + { + if (builder.length() > 0) + builder.append(","); + builder.append(obj.toString()); + } + } + newValue = builder.toString(); + } + } + else + { + newValue = newValue + "," + value.toString(); + } + } + fireValueChangeEvent(newValue); + + // is the value updated ? + boolean updated = false; + if (newValue == null && getValue() == null) { + updated = true; + } else if (newValue != null && newValue.equals(getValue())) { + updated = true; + } + if (!updated) + { + setValue(newValue); + } + } + finally + { + if (gridField != null) + gridField.setLookupEditorSettingValue(false); + } + + } // actionCombo + + + protected void fireValueChangeEvent(Object newValue) { + ValueChangeEvent evt = new ValueChangeEvent(this, this.getColumnName(), getValue(), newValue); + // -> ADTabpanel - valuechange + fireValueChange(evt); + } + + private void actionButton() + { + if (lookup == null) + return; // leave button disabled + + /** + * Three return options: + * - Value Selected & OK pressed => store result => result has value + * - Cancel pressed => store null => result == null && cancelled + * - Window closed -> ignore => result == null && !cancalled + */ + + // Validation + String whereClause = getWhereClause(); + + if (m_tableName == null) // sets table name & key column + getDirectAccessSQL("*"); + + final InfoPanel ip = InfoManager.create(lookup, gridField, m_tableName, m_keyColumnName, null, false, whereClause); + if (ip != null) + showInfoPanel(ip); + } + + + protected void showInfoPanel(final InfoPanel ip) { + ip.setVisible(true); + ip.setStyle("border: 2px"); + ip.setClosable(true); + infoPanel = ip; + ip.addEventListener(DialogEvents.ON_WINDOW_CLOSE, new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + Component component = SessionManager.getAppDesktop().getActiveWindow(); + if (component instanceof IHelpContext) + Events.sendEvent(new Event(WindowContainer.ON_WINDOW_CONTAINER_SELECTION_CHANGED_EVENT, component)); + else + SessionManager.getAppDesktop().updateHelpContext(X_AD_CtxHelp.CTXTYPE_Home, 0); + + boolean cancelled = ip.isCancelled(); + Object[] result = ip.getSelectedKeys(); + + infoPanel = null; + // Result + if (!cancelled && result != null && result.length > 0) + { + //ensure data binding happen + if (result.length > 1) + processSelectedKeys (result); + else + processSelectedKeys (result[0]); + } + getComponent().getChosenbox().focus(); + } + }); + ip.setId(ip.getTitle()+"_"+ip.getWindowNo()); + AEnv.showWindow(ip); + } + + /** + * Generate Access SQL for Search. + * The SQL returns the ID of the value entered + * Also sets m_tableName and m_keyColumnName + * @param text uppercase text for LIKE comparison + * @return sql or "" + * Example + * SELECT C_Payment_ID FROM C_Payment WHERE UPPER(DocumentNo) LIKE x OR ... + */ + private String getDirectAccessSQL (String text) + { + String m_columnName = getColumnName(); + + StringBuffer sql = new StringBuffer(); + m_tableName = m_columnName.substring(0, m_columnName.length()-3); + m_keyColumnName = m_columnName; + + if (m_columnName.equals("M_Product_ID")) + { + // Reset + Env.setContext(Env.getCtx(), lookup.getWindowNo(), Env.TAB_INFO, "M_Product_ID", "0"); + Env.setContext(Env.getCtx(), lookup.getWindowNo(), Env.TAB_INFO, "M_AttributeSetInstance_ID", "0"); + Env.setContext(Env.getCtx(), lookup.getWindowNo(), Env.TAB_INFO, "M_Locator_ID", "0"); + + sql.append("SELECT M_Product_ID FROM M_Product WHERE (UPPER(Value) LIKE ") + .append(DB.TO_STRING(text)) + .append(" OR UPPER(Name) LIKE ").append(DB.TO_STRING(text)) + .append(" OR UPC LIKE ").append(DB.TO_STRING(text)).append(")"); + } + else if (m_columnName.equals("C_BPartner_ID")) + { + sql.append("SELECT C_BPartner_ID FROM C_BPartner WHERE (UPPER(Value) LIKE ") + .append(DB.TO_STRING(text)) + .append(" OR UPPER(Name) LIKE ").append(DB.TO_STRING(text)).append(")"); + } + else if (m_columnName.equals("C_Order_ID")) + { + sql.append("SELECT C_Order_ID FROM C_Order WHERE UPPER(DocumentNo) LIKE ") + .append(DB.TO_STRING(text)); + } + else if (m_columnName.equals("C_Invoice_ID")) + { + sql.append("SELECT C_Invoice_ID FROM C_Invoice WHERE UPPER(DocumentNo) LIKE ") + .append(DB.TO_STRING(text)); + } + else if (m_columnName.equals("M_InOut_ID")) + { + sql.append("SELECT M_InOut_ID FROM M_InOut WHERE UPPER(DocumentNo) LIKE ") + .append(DB.TO_STRING(text)); + } + else if (m_columnName.equals("C_Payment_ID")) + { + sql.append("SELECT C_Payment_ID FROM C_Payment WHERE UPPER(DocumentNo) LIKE ") + .append(DB.TO_STRING(text)); + } + else if (m_columnName.equals("GL_JournalBatch_ID")) + { + sql.append("SELECT GL_JournalBatch_ID FROM GL_JournalBatch WHERE UPPER(DocumentNo) LIKE ") + .append(DB.TO_STRING(text)); + } + else if (m_columnName.equals("SalesRep_ID")) + { + sql.append("SELECT AD_User_ID FROM AD_User WHERE UPPER(Name) LIKE ") + .append(DB.TO_STRING(text)); + + m_tableName = "AD_User"; + m_keyColumnName = "AD_User_ID"; + } + + // Predefined + + if (sql.length() > 0) + { + String wc = getWhereClause(); + + if (wc != null && wc.length() > 0) + sql.append(" AND ").append(wc); + + sql.append(" AND IsActive='Y'"); + // *** + + if (log.isLoggable(Level.FINEST)) log.finest(m_columnName + " (predefined) " + sql.toString()); + + return MRole.getDefault().addAccessSQL(sql.toString(), + m_tableName, MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); + } + + // Check if it is a Table Reference + + if (lookup != null && lookup instanceof MLookup) + { + int AD_Reference_ID = ((MLookup)lookup).getAD_Reference_Value_ID(); + + if (AD_Reference_ID != 0) + { + boolean isValueDisplayed = false; + String query = "SELECT kc.ColumnName, dc.ColumnName, t.TableName, rt.IsValueDisplayed " + + "FROM AD_Ref_Table rt" + + " INNER JOIN AD_Column kc ON (rt.AD_Key=kc.AD_Column_ID)" + + " INNER JOIN AD_Column dc ON (rt.AD_Display=dc.AD_Column_ID)" + + " INNER JOIN AD_Table t ON (rt.AD_Table_ID=t.AD_Table_ID) " + + "WHERE rt.AD_Reference_ID=?"; + + String displayColumnName = null; + PreparedStatement pstmt = null; + ResultSet rs = null; + + try + { + pstmt = DB.prepareStatement(query, null); + pstmt.setInt(1, AD_Reference_ID); + rs = pstmt.executeQuery(); + + if (rs.next()) + { + m_keyColumnName = rs.getString(1); + displayColumnName = rs.getString(2); + m_tableName = rs.getString(3); + String t = rs.getString(4); + isValueDisplayed = "Y".equalsIgnoreCase(t); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, query, e); + } + finally + { + DB.close(rs, pstmt); + } + + + if (displayColumnName != null) + { + sql = new StringBuffer(); + sql.append("SELECT ").append(m_keyColumnName) + .append(" FROM ").append(m_tableName) + .append(" WHERE (UPPER(").append(displayColumnName) + .append(") LIKE ").append(DB.TO_STRING(text)); + if (isValueDisplayed) + { + sql.append(" OR UPPER(").append("Value") + .append(") LIKE ").append(DB.TO_STRING(text)); + } + sql.append(")"); + sql.append(" AND IsActive='Y'"); + + String wc = getWhereClause(); + + if (wc != null && wc.length() > 0) + sql.append(" AND ").append(wc); + + // *** + + if (log.isLoggable(Level.FINEST)) log.finest(m_columnName + " (Table) " + sql.toString()); + + return MRole.getDefault().addAccessSQL(sql.toString(), + m_tableName, MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); + } + } // Table Reference + } // MLookup + + /** Check Well Known Columns of Table - assumes TableDir **/ + + String query = "SELECT t.TableName, c.ColumnName " + + "FROM AD_Column c " + + " INNER JOIN AD_Table t ON (c.AD_Table_ID=t.AD_Table_ID AND t.IsView='N') " + + "WHERE (c.ColumnName IN ('DocumentNo', 'Value', 'Name') OR c.IsIdentifier='Y')" + + " AND c.AD_Reference_ID IN (10,14)" + + " AND EXISTS (SELECT * FROM AD_Column cc WHERE cc.AD_Table_ID=t.AD_Table_ID" + + " AND cc.IsKey='Y' AND cc.ColumnName=?)"; + + m_keyColumnName = m_columnName; + sql = new StringBuffer(); + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + pstmt = DB.prepareStatement(query, null); + pstmt.setString(1, m_keyColumnName); + rs = pstmt.executeQuery(); + + while (rs.next()) + { + if (sql.length() != 0) + sql.append(" OR "); + + m_tableName = rs.getString(1); + sql.append("UPPER(").append(rs.getString(2)).append(") LIKE ").append(DB.TO_STRING(text)); + } + } + catch (SQLException ex) + { + log.log(Level.SEVERE, query, ex); + } + finally + { + DB.close(rs, pstmt); + rs = null; + pstmt = null; + } + // + if (sql.length() == 0) + { + log.log(Level.SEVERE, m_columnName + " (TableDir) - no standard/identifier columns"); + return ""; + } + // + StringBuffer retValue = new StringBuffer ("SELECT ") + .append(m_columnName).append(" FROM ").append(m_tableName) + .append(" WHERE ").append(sql) + .append(" AND IsActive='Y'"); + + String wc = getWhereClause(); + + if (wc != null && wc.length() > 0) + retValue.append(" AND ").append(wc); + // *** + if (log.isLoggable(Level.FINEST)) log.finest(m_columnName + " (TableDir) " + sql.toString()); + return MRole.getDefault().addAccessSQL(retValue.toString(), + m_tableName, MRole.SQL_NOTQUALIFIED, MRole.SQL_RO); + } + + private String getWhereClause() + { + String whereClause = ""; + + if (lookup == null) + return ""; + + String validation = lookup.getValidation(); + + if (validation == null) + validation = ""; + + if (whereClause.length() == 0) + whereClause = validation; + else if (validation.length() > 0) + whereClause += " AND " + validation; + + if (whereClause.indexOf('@') != -1) + { + Properties ctx = lookup instanceof MLookup ? ((MLookup)lookup).getLookupInfo().ctx : Env.getCtx(); + String validated = Env.parseContext(ctx, lookup.getWindowNo(), whereClause, false); + + if (validated.length() == 0) + log.severe(getColumnName() + " - Cannot Parse=" + whereClause); + else + { + if (log.isLoggable(Level.FINE)) + log.fine(getColumnName() + " - Parsed: " + validated); + return validated; + } + } + return whereClause; + } // getWhereClause + + + public String[] getEvents() + { + return LISTENER_EVENTS; + } + + @Override + public void setTableEditor(boolean b) { + super.setTableEditor(b); + getComponent().setTableEditorMode(b); + } + + public Lookup getLookup() { + return lookup; + } + + + @Override + public void dynamicDisplay(Properties ctx) { + if (lookup instanceof MLookup) { + ((MLookup) lookup).getLookupInfo().ctx = ctx; + } + super.dynamicDisplay(ctx); + } + + private class MyListModel extends ListModelList implements ListSubModel { + + /** + * + */ + private static final long serialVersionUID = -1210525428410505409L; + + @Override + public ListModel getSubModel(Object value, int nRows) { + ListModelList model = new ListModelList<>(); + if (value != null && !Util.isEmpty(value.toString(), true)) { + String queryText = value.toString().trim(); + + if (m_tableName == null) // sets table name & key column + getDirectAccessSQL("*"); + + final InfoPanel ip = InfoManager.create(lookup, gridField, m_tableName, m_keyColumnName, queryText, false, getWhereClause()); + if (ip != null && ip.loadedOK()) { + int rowCount = ip.getRowCount(); + if (rowCount > 0) { + List added = new ArrayList(); + for(int i = 0; i < rowCount; i++) { + Integer key = ip.getRowKeyAt(i); + if (key != null && key.intValue() > 0) { + String name = getLookup().getDisplay(key); + if (added.contains(name)) + continue; + else + added.add(name); + ValueNamePair pair = new ValueNamePair(key.toString(), name); + model.add(pair); + if (added.size() == 50) + break; + } + } + } + } + } + getComponent().getChosenbox().setSubListModel(model); + return model; + } + + } +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java index 470d927d6c..b1c6b1c8c7 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/factory/DefaultEditorFactory.java @@ -18,6 +18,8 @@ import org.adempiere.webui.editor.WAssignmentEditor; import org.adempiere.webui.editor.WBinaryEditor; import org.adempiere.webui.editor.WButtonEditor; import org.adempiere.webui.editor.WChartEditor; +import org.adempiere.webui.editor.WChosenboxListEditor; +import org.adempiere.webui.editor.WChosenboxSearchEditor; import org.adempiere.webui.editor.WDashboardContentEditor; import org.adempiere.webui.editor.WDateEditor; import org.adempiere.webui.editor.WDatetimeEditor; @@ -209,6 +211,14 @@ public class DefaultEditorFactory implements IEditorFactory { { editor = new WGridTabMultiSelectionEditor(gridField, tableEditor); } + else if (displayType == DisplayType.ChosenMultipleSelectionList || displayType == DisplayType.ChosenMultipleSelectionTable) + { + editor = new WChosenboxListEditor(gridField); + } + else if (displayType == DisplayType.ChosenMultipleSelectionSearch) + { + editor = new WChosenboxSearchEditor(gridField); + } else { editor = new WUnknownEditor(gridField); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java index 74a7b14310..c0827b9ddd 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/info/InfoWindow.java @@ -71,7 +71,6 @@ import org.compiere.model.AccessSqlParser.TableInfo; import org.compiere.model.GridField; import org.compiere.model.GridFieldVO; import org.compiere.model.GridWindow; -import org.compiere.model.Lookup; import org.compiere.model.MInfoColumn; import org.compiere.model.MInfoWindow; import org.compiere.model.MLookupFactory; @@ -874,7 +873,8 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (! colSQL.toUpperCase().contains(" AS ")) colSQL += " AS " + infoColumn.getColumnName(); editorMap.put(colSQL, editor); - ColumnInfo columnInfo = new ColumnInfo(infoColumn.get_Translation("Name"), colSQL, KeyNamePair.class, (String)null, infoColumn.isReadOnly() || haveNotProcess); + Class colClass = columnName.endsWith("_ID") ? KeyNamePair.class : String.class; + ColumnInfo columnInfo = new ColumnInfo(infoColumn.get_Translation("Name"), colSQL, colClass, (String)null, infoColumn.isReadOnly() || haveNotProcess); return columnInfo; } @@ -946,33 +946,60 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL } else { builder.append(checkAND.isChecked() ? " AND " : " OR "); } - - String columnClause = null; - if (mInfoColumn.getQueryFunction() != null && mInfoColumn.getQueryFunction().trim().length() > 0) { - String function = mInfoColumn.getQueryFunction(); - if (function.indexOf("@") >= 0) { - String s = Env.parseContext(infoContext, p_WindowNo, function, true, false); - if (s.length() == 0) { - log.log(Level.SEVERE, "Failed to parse query function. " + function); - } else { - function = s; - } + + if (mInfoColumn.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionList) + { + String pString = editor.getValue().toString(); + String column = columnName; + if (column.indexOf(".") > 0) + column = column.substring(column.indexOf(".")+1); + int cnt = DB.getSQLValueEx(null, "SELECT Count(*) From AD_Column WHERE IsActive='Y' AND AD_Client_ID=0 AND Upper(ColumnName)=? AND AD_Reference_ID=?", column.toUpperCase(), DisplayType.ChosenMultipleSelectionList); + if (cnt > 0) + builder.append(DB.intersectClauseForCSV(columnName, pString)); + else + builder.append(DB.inClauseForCSV(columnName, pString)); + } + else if (mInfoColumn.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionTable || mInfoColumn.getAD_Reference_ID() == DisplayType.ChosenMultipleSelectionSearch) + { + String pString = editor.getValue().toString(); + if (columnName.endsWith("_ID")) + { + builder.append(DB.inClauseForCSV(columnName, pString)); } - if (function.indexOf("?") >= 0) { - columnClause = function.replaceFirst("[?]", columnName); - } else { - columnClause = function+"("+columnName+")"; + else + { + builder.append(DB.intersectClauseForCSV(columnName, pString)); } - } else { - columnClause = columnName; } - builder.append(columnClause) - .append(" ") - .append(mInfoColumn.getQueryOperator()); - if (columnClause.toUpperCase().startsWith("UPPER(")) { - builder.append(" UPPER(?)"); - } else { - builder.append(" ?"); + else + { + String columnClause = null; + if (mInfoColumn.getQueryFunction() != null && mInfoColumn.getQueryFunction().trim().length() > 0) { + String function = mInfoColumn.getQueryFunction(); + if (function.indexOf("@") >= 0) { + String s = Env.parseContext(infoContext, p_WindowNo, function, true, false); + if (s.length() == 0) { + log.log(Level.SEVERE, "Failed to parse query function. " + function); + } else { + function = s; + } + } + if (function.indexOf("?") >= 0) { + columnClause = function.replaceFirst("[?]", columnName); + } else { + columnClause = function+"("+columnName+")"; + } + } else { + columnClause = columnName; + } + builder.append(columnClause) + .append(" ") + .append(mInfoColumn.getQueryOperator()); + if (columnClause.toUpperCase().startsWith("UPPER(")) { + builder.append(" UPPER(?)"); + } else { + builder.append(" ?"); + } } } } @@ -1070,6 +1097,10 @@ public class InfoWindow extends InfoPanel implements ValueChangeListener, EventL if (mInfoColumn == null || mInfoColumn.getSelectClause().equals("0")) { continue; } + if (mInfoColumn.getAD_Reference_ID()==DisplayType.ChosenMultipleSelectionList || mInfoColumn.getAD_Reference_ID()==DisplayType.ChosenMultipleSelectionSearch + || mInfoColumn.getAD_Reference_ID()==DisplayType.ChosenMultipleSelectionTable) { + continue; + } Object value = editor.getValue(); parameterIndex++; prevParameterValues.add(value); diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java index 8e01796548..6a891bb1a0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/panel/InfoPanel.java @@ -651,6 +651,14 @@ public abstract class InfoPanel extends Window implements EventListener, else { value = rs.getString(colIndex); + if (! rs.wasNull()) { + WEditor editor = editorMap.get(p_layout[col].getColSQL()); + if (editor != null && editor.getGridField() != null && editor.getGridField().isLookup()) + { + editor.setValue(value); + value = editor.getDisplay(); + } + } } data.add(value); } @@ -2532,6 +2540,10 @@ public abstract class InfoPanel extends Window implements EventListener, return contentPanel.getFirstRowKey(); } + public Integer getRowKeyAt(int row) { + return contentPanel.getRowKeyAt(row); + } + /** * @return the cacheStart */ diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java index 0e76450a06..bbf0554ac8 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/FindWindow.java @@ -1701,6 +1701,15 @@ public class FindWindow extends Window implements EventListener, ValueCha } m_query.addRestriction(getSubCategoryWhereClause(field, ((Integer) parsedValue).intValue()), and, openBrackets); } + else if ((field.getDisplayType()==DisplayType.ChosenMultipleSelectionList||field.getDisplayType()==DisplayType.ChosenMultipleSelectionSearch||field.getDisplayType()==DisplayType.ChosenMultipleSelectionTable) && + (MQuery.OPERATORS[MQuery.EQUAL_INDEX].getValue().equals(Operator) || MQuery.OPERATORS[MQuery.NOT_EQUAL_INDEX].getValue().equals(Operator))) + { + String clause = DB.intersectClauseForCSV(ColumnSQL, parsedValue.toString()); + if (MQuery.OPERATORS[MQuery.EQUAL_INDEX].getValue().equals(Operator)) + m_query.addRestriction(clause, and, openBrackets); + else + m_query.addRestriction("NOT (" + clause + ")", and, openBrackets); + } else m_query.addRestriction(ColumnSQL, Operator, parsedValue, infoName, infoDisplay, and, openBrackets); @@ -1849,6 +1858,13 @@ public class FindWindow extends Window implements EventListener, ValueCha continue; } + if (field.getDisplayType()==DisplayType.ChosenMultipleSelectionList||field.getDisplayType()==DisplayType.ChosenMultipleSelectionSearch||field.getDisplayType()==DisplayType.ChosenMultipleSelectionTable) + { + String clause = DB.intersectClauseForCSV(ColumnSQL.toString(), value.toString()); + m_query.addRestriction(clause); + continue; + } + // // Be more permissive for String columns if (isSearchLike(field)) diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Chosenbox.java b/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Chosenbox.java new file mode 100644 index 0000000000..7c06672c19 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Chosenbox.java @@ -0,0 +1,899 @@ +/****************************************************************************** + * Project: Trek Global ERP * + * + * Chosenbox.java + + Purpose: + + Description: + + History: + Tue Nov 16 15:15:52 TST 2011, Created by benbai + +Copyright (C) 2011 Potix Corporation. All Rights Reserved. + +{{IS_RIGHT + This program is distributed under LGPL Version 3.0 in the hope that + it will be useful, but WITHOUT ANY WARRANTY. +}}IS_RIGHT +*/ +package org.zkoss.addon.chosenbox; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.zkoss.lang.Objects; +import org.zkoss.xel.VariableResolver; +import org.zkoss.zk.au.out.AuSetAttribute; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.HtmlBasedComponent; +import org.zkoss.zk.ui.UiException; +import org.zkoss.zk.ui.WrongValueException; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zk.ui.event.InputEvent; +import org.zkoss.zk.ui.event.OpenEvent; +import org.zkoss.zk.ui.event.SelectEvent; +import org.zkoss.zk.ui.util.Clients; +import org.zkoss.zk.ui.util.ForEachStatus; +import org.zkoss.zk.ui.util.Template; +import org.zkoss.zul.ItemRenderer; +import org.zkoss.zul.Label; +import org.zkoss.zul.ListModel; +import org.zkoss.zul.ListModelList; +import org.zkoss.zul.ListSubModel; +import org.zkoss.zul.event.ListDataEvent; +import org.zkoss.zul.event.ListDataListener; + +/** + * A ZK component like JQuery Chosen. + *

Default {@link #getZclass}: z-chosenbox. + * It does not create child widgets for each data, so the memory usage is much + * lower at the server. + * @author benbai + * + */ +public class Chosenbox extends HtmlBasedComponent { + private static final String CHOSENBOX_PREPARE_DATA = "chosenbox.prepareData"; + /** + * generated serial id + */ + private static final long serialVersionUID = 4501010016457525407L; + private List _selIdxs = new ArrayList<>(); + private String _name, _value = ""; + private boolean _disabled; + private int _jsel = -1; + private int _tabindex; + private boolean _open; + private boolean _creatable; + private String _emptyMessage; + private String _noResultsText; + private String _createMessage; + private String _separator; + private transient ListModelList _model; + private transient ListModel _subListModel; + private transient ListDataListener _dataListener; + private transient EventListener _eventListener; + private transient ItemRenderer _renderer; + private transient boolean _childable; + private transient String[] _options; + private transient String[] _chgSel; + private long _onSelectTimestamp; + private long _onOkTimestamp; + static { + addClientEvent(Chosenbox.class, Events.ON_SELECT, CE_DUPLICATE_IGNORE | CE_IMPORTANT); + addClientEvent(Chosenbox.class, Events.ON_FOCUS, CE_DUPLICATE_IGNORE); + addClientEvent(Chosenbox.class, Events.ON_BLUR, CE_DUPLICATE_IGNORE); + addClientEvent(Chosenbox.class, Events.ON_OPEN, CE_IMPORTANT); + addClientEvent(Chosenbox.class, "onSearching", CE_DUPLICATE_IGNORE | CE_IMPORTANT); + addClientEvent(Chosenbox.class, "onSearch", CE_DUPLICATE_IGNORE | CE_IMPORTANT); + addClientEvent(Chosenbox.class, Events.ON_OK, CE_DUPLICATE_IGNORE | CE_IMPORTANT); + } + + public Chosenbox() { + addEventListener("onOkTimer", e -> { + onOkTimer(); + }); + } + + private void onOkTimer() { + if (_onSelectTimestamp==0 || _onOkTimestamp==0) { + postOnOk(); + } else { + long diff = _onSelectTimestamp - _onOkTimestamp; + _onSelectTimestamp = _onOkTimestamp = 0; + if (diff < 0) + diff = diff * -1l; + if (diff > 500) + postOnOk(); + } + } + + private void postOnOk() { + Component p = getParent(); + while (p != null) { + Iterable> iterable = p.getEventListeners(Events.ON_OK); + if (iterable.iterator().hasNext()) { + Events.postEvent(Events.ON_OK, p, null); + break; + } else { + p = p.getParent(); + } + } + } + + public String getZclass() { + return _zclass == null ? "z-chosenbox" : _zclass; + } + + public void setOpen(boolean open) { + if (_open != open) { + _open = open; + smartUpdate("open", _open); + } + } + + public boolean isOpen() { + return _open; + } + + /** + * Returns the tab order of the input node of this component. + *

+ * Default: 0 (means the same as browser's default). + */ + public int getTabindex() { + return _tabindex; + } + + /** + * Sets the tab order of the input node of this component. + */ + public void setTabindex(int tabindex) throws WrongValueException { + if (_tabindex != tabindex) { + _tabindex = tabindex; + smartUpdate("tabindex", tabindex); + } + } + + /** + * Returns whether it is disabled. + *

+ * Default: false. + */ + public boolean isDisabled() { + return _disabled; + } + + /** + * Sets whether it is disabled. + */ + public void setDisabled(boolean disabled) { + if (_disabled != disabled) { + _disabled = disabled; + smartUpdate("disabled", _disabled); + } + } + + /** + * Returns the name of this component. + *

+ * Default: null. + *

+ * The name is used only to work with "legacy" Web application that handles + * user's request by servlets. It works only with HTTP/HTML-based browsers. + * It doesn't work with other kind of clients. + *

+ * Don't use this method if your application is purely based on ZK's + * event-driven model. + */ + public String getName() { + return _name; + } + + /** + * Sets the name of the input element of this component. + *

+ * The name is used only to work with "legacy" Web application that handles + * user's request by servlets. It works only with HTTP/HTML-based browsers. + * It doesn't work with other kind of clients. + *

+ * Don't use this method if your application is purely based on ZK's + * event-driven model. + * + * @param name + * the name of this component. + */ + public void setName(String name) { + if (name != null && name.length() == 0) + name = null; + if (!Objects.equals(_name, name)) { + _name = name; + smartUpdate("name", name); + } + } + + /** + * Returns the emptyMessage of the input of this component. + *

+ * Default: null. + *

+ * The emptyMessage will be displayed in input if nothing selected and not focused. + * @return String + */ + public String getEmptyMessage() { + return _emptyMessage; + } + + /** + * Sets the emptyMessage of the input of this component. + *

+ * The emptyMessage will be displayed in input if nothing selected and not focused. + * @param String emptyMessage + * the emptyMessage of the input of this component. + */ + public void setEmptyMessage(String emptyMessage) { + if (emptyMessage != null && emptyMessage.length() == 0) + emptyMessage = null; + if (!Objects.equals(_emptyMessage, emptyMessage)) { + _emptyMessage = emptyMessage; + smartUpdate("emptyMessage", getEmptyMessage()); + } + } + + /** + * Returns the no-result text of this component. + *

+ * Default: null. + *

+ * The no-result text will be displayed in popup if nothing match to the input value and can not create either, + * the syntax "{0}" will be replaced with the input value at client side. + * @return String + */ + public String getNoResultsText() { + return _noResultsText; + } + + /** + * Sets the no-result text of this component. + *

+ * The no-result text will be displayed in popup if nothing match to the input value and can not create either, + * the syntax "{0}" will be replaced with the input value at client side. + * @param String noResultsText + * the no-result text of this component. + */ + public void setNoResultsText(String noResultsText) { + if (noResultsText != null && noResultsText.length() == 0) + noResultsText = null; + if (!Objects.equals(_noResultsText, noResultsText)) { + _noResultsText = noResultsText; + smartUpdate("noResultsText", getNoResultsText()); + } + } + + /** + * Returns the create message of this component. + *

+ * Default: null. + *

+ * The create message will be displayed in popup if nothing match to the input value but can create as new label, + * the syntax "{0}" will be replaced with the input value at client side. + * @return String + */ + public String getCreateMessage() { + return _createMessage; + } + + /** + * Sets the create message of this component. + *

+ * The create message will be displayed in popup if nothing match to the input value but can create as new label, + * the syntax "{0}" will be replaced with the input value at client side. + * @param String createMessage + * the create message of this component. + */ + public void setCreateMessage(String createMessage) { + if (createMessage != null && createMessage.length() == 0) + createMessage = null; + if (!Objects.equals(_createMessage, createMessage)) { + _createMessage = createMessage; + smartUpdate("createMessage", getCreateMessage()); + } + } + + /** + * Returns the separate chars of this component. + *

+ * Support: 0-9, A-Z (case insensitive), and ,.;'[]/\-= + *

+ * Default: null. + *

+ * The separate chars will work as 'Enter' key, + * it will not considered as input value but send onSerch or onSearching while key up. + * @return String + */ + public String getSeparator() { + return _separator; + } + + /** + * Sets the separate chars of this component. + *

+ * Support: 0-9, A-Z (case insensitive), and ,.;'[]/\-= + *

+ * The separate chars will work as 'Enter' key, + * it will not considered as input value but send onSerch or onSelect while key up. + * @param String createMessage + * the create message of this component. + */ + public void setSeparator(String separator) { + if (separator != null && separator.length() == 0) + separator = null; + if (!Objects.equals(_separator, separator)) { + _separator = separator; + smartUpdate("separator", getSeparator()); + } + } + + /** + * Returns the selected objects. + * @return Set + */ + public LinkedHashSet getSelectedObjects () { + final LinkedHashSet objects = new LinkedHashSet<>(); + ListModel model = this.getModel(); + if (model != null) { + for (int i = 0; i < _selIdxs.size(); i ++) { + objects.add(model.getElementAt(_selIdxs.get(i))); + } + } + return objects; + } + + /** + * Sets the selected objects. + * It will clear selection first. + * @param List objects + * the objects to select. + */ + public void setSelectedObjects (Set objects) { + // do nothing if no model + if (getModel() != null) { + _selIdxs.clear(); + ListModel lm = getModel(); + boolean found = false; + for (T object : objects) { + for (int i = 0; i < lm.getSize(); i++) { + if (lm.getElementAt(i).equals(object)) { + if (_jsel == -1 || _jsel > i) + _jsel = i; + found = true; + _selIdxs.add(i); + break; + } + } + if (!found) { + if (Logger.getLogger(getClass().getName()).isLoggable(Level.INFO)) + Logger.getLogger(getClass().getName()).info("No such item: " + object); + } + found = false; + } + smartUpdate("chgSel", getChgSel()); + } + } + + /** + * Returns the index of the selected item (-1 if no one is selected). + * @return int + */ + public int getSelectedIndex() { + return _jsel; + } + + /** + * Sets the index of the selected item (-1 if no one is selected). + * It will clear selection first. + * @param int index + * the index to select. + */ + public void setSelectedIndex(int jsel) { + if (jsel <= -1) + jsel = -1; + if (jsel < 0) { // unselect all + clearSelection(); + } else if (jsel != _jsel || _selIdxs.size() > 1) { + if (_selIdxs.size() > 1 && jsel == _jsel) { + // clear client side old value + smartUpdate("selectedIndex", -1); + } + // check size + if (getModel() != null && jsel >= getModel().getSize()) { + throw new UiException("Out of bound: " + jsel + " while size=" + + getModel().getSize()); + } + _selIdxs.clear(); + _jsel = jsel; + _selIdxs.add(jsel); + smartUpdate("chgSel", getChgSel()); + } + } + + /** + * Returns whether can create new item. + *

+ * Default: false. + *

+ * true: will show create message while value of input not exists. + *

+ * false: will show no result message while value of input not exists. + */ + public boolean isCreatable() { + return _creatable; + } + + /** + * Sets whether can create new item. + *

+ * Default: false. + *

+ * true: will show create message while value of input not exists. + *

+ * false: will show no-result text while value of input not exists. + * + * @param creatable + * the boolean value. + */ + public void setCreatable(boolean creatable) { + if (_creatable != creatable) { + _creatable = creatable; + smartUpdate("creatable", _creatable); + } + } + + public ItemRenderer getRealRenderer() { + final ItemRenderer renderer = getItemRenderer(); + return renderer != null ? renderer : _defRend; + } + + /** + * Returns the renderer to render each item, or null if the default renderer + * is used. + */ + public ItemRenderer getItemRenderer() { + return _renderer; + } + + /** + * Returns the model associated with this chosenbox, or null if this + * chosenbox is not associated with any list data model. + */ + public ListModel getModel() { + return _model; + } + + /** + * Sets the list model associated with this chosenbox. If a non-null model + * is assigned, no matter whether it is the same as the previous, it will + * always cause re-render. + * + * @param model + * the list model to associate, or null to dis-associate any + * previous model. + * @exception UiException + * if failed to initialize with the model + */ + public void setModel(ListModelList model) { + if (model != null) { + if (_model != model) { + // fix selected index + if (getSelectedIndex() >= model.getSize()) + setSelectedIndex(model.getSize()-1); + if (_model != null) { + _model.removeListDataListener(_dataListener); + } + _model = model; + initDataListener(); + } + } else if (_model != null) { + _model.removeListDataListener(_dataListener); + if (_model instanceof ListSubModel) + removeEventListener("onSearching", _eventListener); + _model = null; + } + fixIndexs(true, null); + smartUpdate("renderByServer", _model instanceof ListSubModel); + updateItems(); + } + + /** + * Clear all selected objects. + */ + public void clearSelection() { + _selIdxs.clear(); + _jsel = -1; + smartUpdate("chgSel", getChgSel()); + } + + /** + * Add an item into selection. + * @param o + * the object to add. + */ + public void addItemToSelection(Object o) { + // do nothing if no model + if (getModel() != null) { + ListModel lm = getModel(); + for (int i = 0;i < lm.getSize();i ++) { + if (lm.getElementAt(i).equals(o)) { + _selIdxs.add(i); + if (i < _jsel) + _jsel = i; + smartUpdate("chgSel", getChgSel()); + } + } + } + } + + /** + * Remove an item from selection. + * @param o + * the object to remove. + */ + public void removeItemFromSelection(Object o) { + // do nothing if no model + if (getModel() != null) { + ListModel lm = getModel(); + for (int i = 0;i < lm.getSize();i ++) { + if (lm.getElementAt(i).equals(o)) { + int cur = -1, min = -1; + for (int j = 0; j < _selIdxs.size(); j++) { + if (i == _selIdxs.get(j).intValue()) { + cur = j; + } else if (min == -1 || _selIdxs.get(j).intValue() < min) { + min = _selIdxs.get(j).intValue(); + } + } + if (cur != -1) { + _jsel = min; + _selIdxs.remove(cur); + smartUpdate("chgSel", getChgSel()); + } + break; + } + } + } + } + + private String[] getChgSel() { + prepareItems(null, true, _model); + if (_options != null) { + String [] chgSel = _options; + _options = null; + return chgSel; + } + return new String[0]; + } + + protected boolean isChildable() { + return _childable; + } + + private void prepareData() { + if (getAttribute(CHOSENBOX_PREPARE_DATA) != null) + return; + + if (_selIdxs.size() > 0) + _chgSel = getChgSel(); + + if (!(_model instanceof ListSubModel)) + prepareItems(null, false, _model); + + setAttribute(CHOSENBOX_PREPARE_DATA, Boolean.TRUE); + } + + // fix selected indexes while model changed or replaced + private void fixIndexs(boolean modelReplaced, ListDataEvent event) { + // model instance is changed + if (modelReplaced) { + if (_model == null) { + clearSelection(); + } else { + // remove the out of range indexes + Iteratorit = _selIdxs.iterator(); + while (it.hasNext()) { + if (it.next() >= _model.getSize()) { + it.remove(); + } + } + } + } else { + int pos0 = event.getIndex0(); + int pos1 = event.getIndex1(); + int amount = pos1 - pos0 + 1; + switch (event.getType()) { + case ListDataEvent.INTERVAL_ADDED: + for (int i = 0; i < _selIdxs.size(); i++) { + if (_selIdxs.get(i) >= pos0) + _selIdxs.set(i, _selIdxs.get(i) + amount); + } + break; + case ListDataEvent.INTERVAL_REMOVED: + for(ListIterator lit = _selIdxs.listIterator(); lit.hasNext();) { + Integer i = lit.next(); + if (i > pos1) + lit.set(i - amount); + else if (i >= pos0) + lit.remove(); + } + break; + } + } + } + /** + * prepare the list content or selected items to render, + * @param prefix + * Only add the item starts with it if it is not null. + * @param excludeUnselected + * Only add selected item, with select order. + * @param model + * the model to render. + */ + private void prepareItems(String prefix, boolean excludeUnselected, ListModel model) { + if (model != null) { + List optList = new ArrayList(); + final boolean old = _childable; + try { + _childable = true; + final ItemRenderer renderer = getRealRenderer(); + // order by _selIdxs content if only prepare selected items + if (excludeUnselected) { + for (int i = 0; i < _selIdxs.size(); i++) { + String s = renderer.render(this, model.getElementAt(_selIdxs.get(i)), _selIdxs.get(i)); + if (prefix == null || s.toLowerCase().startsWith(prefix.toLowerCase())) + optList.add(s); + } + } else { + for (int i = 0; i < model.getSize(); i++) { + String s = renderer.render(this, model.getElementAt(i), i); + if (prefix == null || s.toLowerCase().startsWith(prefix.toLowerCase())) + optList.add(s); + } + } + if (optList.size() > 0) + _options = optList.toArray(new String[0]); + } catch (Exception e) { + throw UiException.Aide.wrap(e); + } finally { + //clear possible children created in renderer + _childable = old; + getChildren().clear(); + } + } + } + + private void updateItems() { + prepareItems(null, false, _model); + if (_options != null) { + smartUpdate("items", _options); + _options = null; //purge the data + } + smartUpdate("chgSel", getChgSel()); + } + + private void updateListContent(String prefix, ListModel subModel) { + if (!(_model instanceof ListSubModel)) + prepareItems(null, false, subModel); + else + prepareItems(prefix, false, subModel); + if (_options != null) { + smartUpdate("listContent", _options); + _options = null; //purge the data + } else + smartUpdate("listContent", new String[0]); + } + + private void initDataListener() { + if (_dataListener == null) + _dataListener = new ListDataListener() { + public void onChange(ListDataEvent event) { + fixIndexs(false, event); + updateItems(); + } + }; + if (_eventListener == null) + _eventListener = new EventListener() { + @SuppressWarnings("unchecked") + public void onEvent(InputEvent event) throws Exception { + if (getModel() instanceof ListSubModel) { + updateListContent(null, ((ListSubModel)_model).getSubModel(event.getValue(), _model.getSize())); + } + } + }; + _model.addListDataListener(_dataListener); + + if (_model instanceof ListSubModel) + addEventListener("onSearching", _eventListener); + } + + private Integer getIndexFromValue(String value, boolean checkSubList) { + for (int i = 0; i < _model.getSize(); i++) { + if (value.equals(_model.getElementAt(i).toString())) + return Integer.valueOf(i); + } + if (checkSubList && _subListModel != null) { + for (int i = 0; i < _subListModel.getSize(); i++) { + if (value.equals(_subListModel.getElementAt(i).toString())) { + _model.add(_subListModel.getElementAt(i)); + return Integer.valueOf(_model.getSize()-1); + } + } + } + throw new UiException("No such item: " + value); + } + + // -- ComponentCtrl --// + public void invalidate() { + prepareData(); + super.invalidate(); + } + + protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) + throws IOException { + super.renderProperties(renderer); + prepareData(); + removeAttribute(CHOSENBOX_PREPARE_DATA); + if (_options != null) { + render(renderer, "items", _options); + _options = null; //purge the data + } else { + render(renderer, "items", new String[0]); + } + if (_chgSel != null) { + render(renderer, "chgSel", _chgSel); + _chgSel = null; //purge the data + } else { + render(renderer, "chgSel", new String[0]); + } + + render(renderer, "name", _name); + render(renderer, "disabled", isDisabled()); + if (_tabindex != 0) + renderer.render("tabindex", _tabindex); + + render(renderer, "emptyMessage", getEmptyMessage()); + render(renderer, "noResultsText", getNoResultsText()); + render(renderer, "separator", getSeparator()); + render(renderer, "createMessage", getCreateMessage()); + renderer.render("selectedIndex", _jsel); + renderer.render("creatable", _creatable); + renderer.render("renderByServer", _model instanceof ListSubModel); + render(renderer, "open", _open); + + //maintain selected items for listsubmodel/renderbyserver + if (_model instanceof ListSubModel && _selIdxs.size() > 0) { + prepareItems(null, true, _model); + if (_options != null && _options.length > 0) { + response("listContent", new AuSetAttribute(this, "listContent", _options)); + response("chgSel", new AuSetAttribute(this, "chgSel", _options)); + _options = null; //purge the data + } + } + } + + public void service(org.zkoss.zk.au.AuRequest request, boolean everError) { + final String cmd = request.getCommand(); + if (cmd.equals(Events.ON_SELECT)) { + List selItems = (List)request.getData().get(""); + // clear at first + _selIdxs.clear(); + _jsel = -1; + + for (int i = 0; i < selItems.size(); i++) { + int idx = getIndexFromValue(selItems.get(i).toString(), true); + _selIdxs.add(idx); + if (idx < _jsel || _jsel == -1) + _jsel = idx; + } + final Integer index = getSelectedIndex(); + final Set objects = getSelectedObjects(); + Events.postEvent(new SelectEvent(Events.ON_SELECT, this, null, null, null, + objects, null, null, null, index, 0)); + _onSelectTimestamp = System.currentTimeMillis(); + } else if (cmd.equals(Events.ON_OPEN)) { + _open = (Boolean)request.getData().get("open"); + Events.postEvent(OpenEvent.getOpenEvent(request)); + } else if (cmd.equals("onSearch")) { + Events.postEvent(new Event("onSearch", this, ((List)request.getData().get("")).get(0).toString())); + } else if (cmd.equals("onSearching")) { + Object data = ((List)request.getData().get("")).get(0); + Events.postEvent(new InputEvent(cmd, this, (String)data, _value)); + _value = (String)data; + } else if (cmd.equals(Events.ON_OK)) { + _onOkTimestamp = System.currentTimeMillis(); + Clients.evalJavaScript("setTimeout(function(){zAu.send(new zk.Event(zk.Widget.$('#" + + getUuid() + "'), 'onOkTimer', null));}, 100);"); + } else if (cmd.equals("onOkTimer")) { + Events.postEvent("onOkTimer", this, null); + } + } + + public ListModel getSubListModel() { + return _subListModel; + } + + public void setSubListModel(ListModel _subListModel) { + this._subListModel = _subListModel; + } + + private final ItemRenderer _defRend = new ItemRenderer() { + public String render(final Component owner, final T data, final int index) { + final Chosenbox self = (Chosenbox) owner; + final Template tm = self.getTemplate("model"); + if (tm == null) + return Objects.toString(data); + else { + final Component[] items = tm.create(owner, null, + new VariableResolver() { + public Object resolveVariable(String name) { + if ("each".equals(name)) { + return data; + } else if ("forEachStatus".equals(name)) { + return new ForEachStatus() { + public ForEachStatus getPrevious() { + return null; + } + public Object getEach() { + return data; + } + public int getIndex() { + return index; + } + public Integer getBegin() { + return 0; + } + public Integer getEnd() { + return ((Chosenbox)owner).getModel().getSize(); + } + public boolean isFirst() { + return index == 0; + } + public boolean isLast() { + return (index+1) == ((Chosenbox)owner).getModel().getSize(); + } + public int getCount() { + return index+1; + } + public Object getCurrent() { + return data; + } + public Integer getStep() { + return Integer.valueOf(1); + } + }; + } else { + return null; + } + } + }, null); + if (items.length != 1) + throw new UiException( + "The model template must have exactly one item, not " + + items.length); + if (!(items[0] instanceof Label)) + throw new UiException( + "The model template can only support Label component, not " + + items[0]); + items[0].detach(); //remove the label from owner + return ((Label) items[0]).getValue(); + } + } + }; +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Version.java b/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Version.java new file mode 100644 index 0000000000..26ffcd0260 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/org/zkoss/addon/chosenbox/Version.java @@ -0,0 +1,28 @@ +/* Chosenbox.java + + Purpose: + + Description: + + History: + Tue Nov 16 15:15:52 TST 2011, Created by benbai + +Copyright (C) 2011 Potix Corporation. All Rights Reserved. + +{{IS_RIGHT + This program is distributed under LGPL Version 3.0 in the hope that + it will be useful, but WITHOUT ANY WARRANTY. +}}IS_RIGHT +*/ +package org.zkoss.addon.chosenbox; + +/** + * Specified this in lang.xml, such that ZK knows what lang-addon.xml is associated + * with chosenbox. + * + */ +public class Version { + /** Returns the version UID. + */ + public static final String UID = "1.0.0"; +} diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-del.gif b/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-del.gif new file mode 100644 index 0000000000..503617267e Binary files /dev/null and b/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-del.gif differ diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-sprite.png b/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-sprite.png new file mode 100644 index 0000000000..d08e4b7e62 Binary files /dev/null and b/org.adempiere.ui.zk/WEB-INF/src/web/chosenbox/img/chosen-sprite.png differ diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/Chosenbox.js b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/Chosenbox.js new file mode 100644 index 0000000000..3085a640b6 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/Chosenbox.js @@ -0,0 +1,1114 @@ +/* Chosenbox.js + + Purpose: + + Description: + + History: + Tue Nov 16 15:15:52 TST 2011, Created by benbai + +Copyright (C) 2011 Potix Corporation. All Rights Reserved. + +This program is distributed under LGPL Version 3.0 in the hope that +it will be useful, but WITHOUT ANY WARRANTY. + */ +(function() { + function clearAllData(wgt) { + wgt._clearSelection(); + wgt._ppMaxHeight = wgt.fixDisplay = wgt._separatorCode = wgt._startOnSearching = wgt._chgSel = wgt.fixInputWidth = null; + } + function startOnSearching(wgt) { + if (!wgt._startOnSearching) + wgt._startOnSearching = setTimeout(function() { + wgt._fireOnSearching(wgt.$n(('inp')).value); + wgt._startOnSearching = null; + }, 350); + } + + var Chosenbox = + + chosenbox.Chosenbox = zk.$extends(zul.Widget, { + $init : function(props) { + this.$supers('$init', arguments); + this._selItems = []; + this._separatorCode = []; + this._ppMaxHeight = 350; + }, + $define : { + items : function(v) { + if (!this._renderByServer) + this.setListContent(); + }, + /** + * Returns the tab order of the input of this + * component. + *

+ * Default: 0 (means the same as browser's default). + * + * @return int + */ + /** + * Sets the tab order of the input of this + * component. + * + * @param int + * tabindex + */ + tabindex : function(tabindex) { + var n = this.$n('inp'); + if (n) + n.tabindex = tabindex || ''; + }, + /** + * Returns the index of the selected item (-1 if no + * one is selected). + * + * @return int + */ + /** + * Selects the item with the given index. + * + * @param int + * selectedIndex + */ + selectedIndex : function(v, opts) { + var options, sel; + this._clearSelection(); + if ((sel = this.$n('sel')) && v >= 0) { + options = jq(sel).children(); + this._doSelect(this._getItemByIndex(v)); + } + if (v == -1) + this._fixEmptyMessage(true); + }, + renderByServer : function(v) { + if (v && this.$n()) + this._clearListContent(); + }, + /** + * Returns whether it is disabled. + *

+ * Default: false. + * + * @return boolean + */ + /** + * Sets whether it is disabled. + * + * @param boolean + * disabled + */ + disabled : function(disabled) { + var n = this.$n('inp'); + if (n) + n.disabled = disabled ? 'disabled' : ''; + }, + /** + * Returns the name of the input of this component. + *

+ * Default: null. + *

+ * The name is used only to work with "legacy" Web + * application that handles user's request by + * servlets. It works only with HTTP/HTML-based + * browsers. It doesn't work with other kind of + * clients. + *

+ * Don't use this method if your application is + * purely based on ZK's event-driven model. + * + * @return String + */ + /** + * Sets the name of the input of this component. + *

+ * The name is used only to work with "legacy" Web + * application that handles user's request by + * servlets. It works only with HTTP/HTML-based + * browsers. It doesn't work with other kind of + * clients. + *

+ * Don't use this method if your application is + * purely based on ZK's event-driven model. + * + * @param String + * name the name of this component. + */ + name : function(name) { + var n = this.$n('inp'); + if (n) + n.name = name; + }, + /** + * Returns the emptyMessage, it will be displayed if + * no selected items while not focused. + * + * @return String + */ + /** + * Sets the emptyMessage. + * + * @param String + * emptyMessage + */ + emptyMessage : null, + /** + * Returns the no-result text of this component. + *

+ * Default: null. + *

+ * The no-result text will be displayed in popup if + * nothing match to the input value and can not + * create either, the syntax "{0}" will be replaced + * with the input value at client side. + * + * @return String + */ + /** + * Sets the no-result text of this component. + *

+ * The no-result text will be displayed in popup if + * nothing match to the input value and can not + * create either, the syntax "{0}" will be replaced + * with the input value at client side. + * + * @param String + * noResultsText the no-result text of + * this component. + */ + noResultsText : null, + /** + * Returns the create message of this component. + *

+ * Default: null. + *

+ * The create message will be displayed in popup if + * nothing match to the input value but can create + * as new label, the syntax "{0}" will be replaced + * with the input value at client side. + * + * @return String + */ + /** + * Sets the create message of this component. + *

+ * The create message will be displayed in popup if + * nothing match to the input value but can create + * as new label, the syntax "{0}" will be replaced + * with the input value at client side. + * + * @param String + * createMessage the create message of + * this component. + */ + createMessage : null, + /** + * Returns the separate chars of this component. + *

+ * Support: 0-9, A-Z (case insensitive), and + * ,.;'[]/\-= + *

+ * Default: null. + *

+ * The separate chars will work as 'Enter' key, it + * will not considered as input value but send + * onSerch or onSearching while key up. + * + * @return String + */ + /** + * Sets the separate chars of this component. + *

+ * Support: 0-9, A-Z (case insensitive), and + * ,.;'[]/\-= + *

+ * The separate chars will work as 'Enter' key, it + * will not considered as input value but send + * onSerch or onSearching while key up. + * + * @param String + * createMessage the create message of + * this component. + */ + separator : function(v) { + var separatorCode = this._separatorCode; + separatorCode.length = 0; + // save keycode for special symble + // handle the code of special char because + // we need process it with both keyUp and + // keyDown + // which has different code with keyPress + if (v.indexOf(',') != -1) + separatorCode.push(188); + if (v.indexOf('.') != -1) + separatorCode.push(190); + if (v.indexOf('/') != -1) + separatorCode.push(191); + if (v.indexOf(';') != -1) + separatorCode.push(zk.ie ? 186 : 59); + if (v.indexOf("'") != -1) + separatorCode.push(222); + if (v.indexOf('[') != -1) + separatorCode.push(219); + if (v.indexOf(']') != -1) + separatorCode.push(221); + if (v.indexOf('\\') != -1) + separatorCode.push(220); + if (v.indexOf('-') != -1) + separatorCode.push(zk.ie ? 189 : 109); + if (v.indexOf('=') != -1) + separatorCode.push(107); + }, + /** + * Returns whether can create new item, The input + * will considered to be a new item if it is not + * exist and this property is true. + * + * @return boolean + */ + /** + * Sets whether can create new item. + * + * @param boolean + * creatable + */ + creatable : null, + /** + * Returns the open status of drop down list. + * + * @return boolean + */ + /** + * Sets the drop down list open status, and + * open/close drop down list as need. + * + * @param boolean + * open + */ + open : null + }, + setListContent : function(v) { + var sel, out, oldHlite, value; + if (sel = this.$n('sel')) { + if (oldHlite = jq(this.$n('sel')) + .find( + '.' + this.getZclass() + + '-option-over')[0]) + value = oldHlite.innerHTML; + out = []; + this._renderItems(out, v); + this._clearListContent(); + sel.innerHTML = out.join(''); + // restore old high-light + if (value + && (oldHlite = this + ._getItemByValue(value))) + this._hliteOpt(oldHlite, true); + this._startFixDisplay({ + hliteFirst : true, + fromServer : true + }); + } + }, + _clearListContent : function() { + if (this.$n()) { + this.$n('sel').innerHTML = ''; + this.$n('empty').style.display = 'none'; + } + }, + _renderItems : function(out, content) { + var s = $eval(content ? content : this._items) + || [], zcls = this.getZclass(); + for (var i = 0, j = s.length; i < j; i++) { + out.push('

', + zUtl.encodeXML(s[i]), '
'); + } + return out; + }, + getZclass : function() { + var zcls = this._zclass; + return zcls != null ? zcls : "z-chosenbox"; + }, + // update the selected items, the old selection will be + // cleared at first + setChgSel : function(val) { // called from the server + this._clearSelection(); + var sel, options; + if (sel = this.$n('sel')) { // select each item + options = jq(sel).children(); + var s = $eval(val), renderByServer = this._renderByServer, item, value; + for (var i = 0; i < s.length; i++) { + value = s[i]; + if (item = this._getItemByValue(value)) + this._doSelect(item); + else + this._selectItemDirectly(value); + } + } else + this._chgSel = val; // not binded, just store it + this._fixEmptyMessage(true); + }, + bind_ : function() { + this.$supers(Chosenbox, 'bind_', arguments); + var n = this.$n(), inp = this.$n('inp'); + + this.domListen_(inp, 'onFocus', 'doFocus_') + .domListen_(inp, 'onBlur', 'doBlur_'); + zWatch.listen({ + onFloatUp : this, + onSize : this + }); + this._fixWidth(n); + //fix selection + if (this._selItems && this._selItems.length > 0) { + var s = this._selItems; + this._selItems = []; + for (var i = 0; i < s.length; i++) { + var value = s[i]; + if (item = this._getItemByValue(value)) + this._doSelect(item); + else + this._selectItemDirectly(value); + } + } else if (this._chgSel) { + var s = this._chgSel; + this._chgSel = null; + for (var i = 0; i < s.length; i++) { + value = s[i]; + if (item = this._getItemByValue(value)) + this._doSelect(item); + else + this._selectItemDirectly(value); + } + } + // fix emptyMessage + this._fixEmptyMessage(true); + if (this._open && !this.isDisabled()) + this.setOpen(true); + }, + unbind_ : function() { + var inp = this.$n('inp'); + this.domUnlisten_(inp, 'onFocus', 'doFocus_') + .domUnlisten_(inp, 'onBlur', 'doBlur_'); + zWatch.unlisten({ + onFloatUp : this, + onSize : this + }); + clearAllData(this); + this.$supers(Chosenbox, 'unbind_', arguments); + }, + redraw: function (out) { + this.$supers('redraw', arguments); + }, + onSize : function() { + this._fixInputWidth(); + }, + _fixWidth : function(n) { + if (this._width) + n.style.width = this._width; + this.$n('pp').style.width = jq(n).width() + 'px'; + }, + doBlur_ : function(evt) { + jq(this.$n()).removeClass( + this.getZclass() + '-focus'); + }, + doFocus_ : function(evt) { + if (!this.isDisabled()) + jq(this.$n()).addClass( + this.getZclass() + '-focus'); + }, + doMouseOver_ : function(evt) { + var target = evt.domTarget; + // mouseover option + if (jq(target).hasClass( + this.getZclass() + '-option')) + this._hliteOpt(target, true); + }, + doMouseOut_ : function(evt) { + var target = evt.domTarget; + // mouseout option + if (jq(target).hasClass( + this.getZclass() + '-option-over')) + this._hliteOpt(target, false); + }, + _hliteOpt : function(target, highlight) { + var zcls = this.getZclass() + '-option-over'; + if (highlight) { + // clear old first + var oldHlite = jq(this.$n('sel')) + .find( + '.' + this.getZclass() + + '-option-over')[0]; + if (oldHlite) + jq(oldHlite).removeClass(zcls); + jq(target).addClass(zcls); + } else + jq(target).removeClass(zcls); + }, + _doArrowDown : function(key, evt) { + if (key == 'up') + this._moveOptionFocus('prev'); + else if (key == 'down') + this._moveOptionFocus('next'); + else { + var inp = this.$n('inp'), pos = zk(inp) + .getSelectionRange(), label = jq( + this.$n()).find( + '.' + this.getZclass() + + '-sel-item-focus')[0]; + // only works if cursor is at the begining of + // input + if (pos[0] == 0 && pos[1] == 0) { + if (key == 'left') + this._moveLabelFocus(label, 'prev'); + else if (key == 'right') { + if (label) + evt.stop(); + this._moveLabelFocus(label, 'next'); + } + } + } + }, + // focus previous or next visible option, + // depends on dir + _moveOptionFocus : function(dir) { + var sel = this.$n('sel'), $sel = jq(sel), oldHlite = $sel + .find('.' + this.getZclass() + + '-option-over')[0], newHlite, next = dir == 'next', prev = dir == 'prev'; + if (next && !this._open) // default focus first + // while open + this.setOpen(true, { + sendOnOpen : true + }); + else { + // preset newHlite + if (oldHlite) // get previous or next item of + // old hi-lighted one + newHlite = next ? oldHlite.nextSibling + : oldHlite.previousSibling; + else + // get first/last item if no old hi-lighted + newHlite = next ? sel.firstChild : // choose + // first/last + // option + // if no + // old + // highlighted + prev ? sel.lastChild : null; + if (newHlite) // find closest visible new item + while (newHlite + && newHlite.style.display == 'none') + newHlite = next ? newHlite.nextSibling + : prev ? newHlite.previousSibling + : null; + + if (newHlite) + this._hliteOpt(newHlite, true); + else if (oldHlite) + this._hliteOpt(oldHlite, false); + } + }, + // focus previous or next label, + // depends on dir + _moveLabelFocus : function(label, dir) { + var zcls = this.getZclass() + '-sel-item-focus', newLabel, prev = dir == 'prev', next = dir == 'next'; + if (label) { + jq(label).removeClass(zcls); + newLabel = prev ? label.previousSibling + : next ? label.nextSibling : null; + if (prev && !newLabel) + newLabel = label; + else if (next && newLabel == this.$n('inp')) + newLabel = null; + } else if (prev) + newLabel = this.$n('inp').previousSibling; + if (newLabel) + jq(newLabel).addClass(zcls); + }, + // called after press backspace or del and release + _deleteLabel : function(key, evt) { + var inp = this.$n('inp'), pos = zk(inp) + .getSelectionRange(), label; + + // only works if cursor is at the begining of input + if (pos[0] == 0 && pos[1] == 0) { + var zcls = this.getZclass() + '-sel-item-focus'; + if (label = jq(this.$n()).find('.' + zcls)[0]) { + var dir = (label.previousSibling && key == 'backspace') ? 'prev' + : 'next'; + this._moveLabelFocus(label, dir); + this._doDeselect(label, { + sendOnSelect : true + }); + evt.stop(); // should stop or will delete + // text + // maybe have to filt out deselected item + this._startFixDisplay(); + } else if ((label = inp.previousSibling) + && key == 'backspace') + jq(label).addClass(zcls); + } + }, + _removeLabelFocus : function() { + var zcls = this.getZclass() + '-sel-item-focus', label = jq( + this.$n()).find('.' + zcls)[0]; + if (label) + jq(label).removeClass(zcls); + }, + // called after press enter and release + _doEnterPressed : function(evt) { + var $sel, hlited, old; + // clear timer and fix display before process + if (old = this.fixDisplay) + clearTimeout(old); + this._fixDisplay(); + if (this._open) { + if ((hlited = this.$n('empty')) + && jq(hlited).hasClass( + this.getZclass() + + '-empty-creatable')) { + this._fireOnSearch(this.$n('inp').value); + if (this._open) { + this.setOpen(false, { + sendOnOpen : true + }); + } + } else if (($sel = jq(this.$n('sel'))) + && (hlited = $sel.find('.' + + this.getZclass() + + '-option-over')[0])) { + var options = $sel.children(); + this._doSelect(hlited, { + sendOnSelect : true + }); + if (this._open) { + this.setOpen(false, { + sendOnOpen : true + }); + } + } + } + }, + doClick_ : function(evt) { + if (!this.isDisabled()) { + var target = evt.domTarget, $target = jq(target), inp = this + .$n('inp'), zcls = this.getZclass(); + this._removeLabelFocus(); + if (inp.value == this._emptyMessage) + inp.value = ''; + if ($target.hasClass(zcls + '-option')) { // click + // on + // option + this._doSelect(target, { + sendOnSelect : true + }); + if (this._open) + this.setOpen(false, { + sendOnOpen : true, + fixEmptyMessage : true + }); + } else if ($target.hasClass(zcls + + '-empty-creatable')) { // click on + // new label + this._fireOnSearch(inp.value); + if (this._open) + this.setOpen(false, { + sendOnOpen : true + }); + } else { + var label = target, zcls = this.getZclass() + + '-sel-item'; + if ($target.hasClass(zcls) + || (label = $target.parent('.' + + zcls)[0])) { // click on + // label + jq(label).addClass(zcls + '-focus'); + } + if (!this._open) + this.setOpen(true, { + sendOnOpen : true + }); + } + + inp.focus(); + this.$supers('doClick_', arguments); + } + }, + // select an item + _doSelect : function(target, opts) { + this._hliteOpt(target, false); + var value = target.innerHTML; + if (this._selItems.indexOf(value) == -1) { + this._createLabel(value); + target.style.display = 'none'; // hide selected + // item + // record the selected item + this._selItems.push(value); + this._fixEmptyMessage(true); + + if (opts && opts.sendOnSelect) + this.fireSelectEvent(); + } + }, + _selectItemDirectly : function(value) { + if (this._selItems.indexOf(value) == -1) { + this._createLabel(value); + // record the selected item + this._selItems.push(value); + this._fixEmptyMessage(true); + } + }, + // deselect an item + _doDeselect : function(selectedItem, opts) { + var value = jq(selectedItem).find( + '.' + this.getZclass() + '-sel-item-cnt')[0].innerHTML, element = this + ._getItemByValue(value), _selItems = this._selItems; + if (this._open) + this.setOpen(false, { + sendOnOpen : true + }); + // remove record + _selItems.splice(_selItems.indexOf(value), 1); + // show origin option of deselected item if it + // exists + if (element) + element.style.display = 'block'; + // remove label of deselected item + jq(selectedItem).remove(); + if (opts && opts.sendOnSelect) + this.fireSelectEvent(); // only fire if active + // from client + // maybe have to filt out deselected item + this._startFixDisplay(); + }, + _getItemByValue : function(value) { + var options = jq(this.$n('sel')).children(), item; + for (var i = 0; i < options.length; i++) + if ((item = options[i]) + && item.innerHTML == value) + return item; + else if (!item) // over index + return null; + }, + // create label for selected item + _createLabel : function(value) { + var span = document.createElement("span"), content = document + .createElement("div"), delbtn = document + .createElement("div"), wgt = this, zcls = this + .getZclass(); + span.className = zcls + '-sel-item'; + content.innerHTML = value; + content.className = zcls + '-sel-item-cnt'; + delbtn.className = zcls + '-del-btn'; + + span.appendChild(content); + span.appendChild(delbtn); + jq(delbtn).bind('click', function() { + if (!wgt.isDisabled()) { + wgt.$n('inp').focus(); + wgt._doDeselect(span, { + sendOnSelect : true + }); + } + }); + this.$n().insertBefore(span, this.$n('inp')); // add + // div + // mark + }, + // clear all selected items + _clearSelection : function(opts) { + var n = this.$n(), inp = this.$n('inp'), c, // selected + // item + del; + if (n) + c = n.firstChild; + while (c && c != inp) { + del = c; + c = c.nextSibling; + this._doDeselect(del, opts); + } + this._selItems.length = 0; + }, + // fire onSelectevent to server + fireSelectEvent : function() { + var data = [], selItems = this._selItems; // selected + // item + for (var i = 0; i < selItems.length; i++) + data.push(selItems[i]); + this.fire('onSelect', data); + }, + // fire onSearch event + _fireOnSearch : function(value) { + var data = []; + data.push(value); + this.fire('onSearch', data); + }, + // fire onSearching event + _fireOnSearching : function(value) { + var data = []; + data.push(value); + this.fire('onSearching', data); + }, + // should close drop-down list if not click self + onFloatUp : function(ctl) { + if (ctl.origin != this) { + if (this._open) + this.setOpen(false, { + sendOnOpen : true, + fixEmptyMessage : true + }); + this._removeLabelFocus(); + } + }, + + doKeyDown_ : function(evt) { + var keyCode = evt.keyCode; + switch (keyCode) { + case 8:// backspace + this._deleteLabel('backspace', evt); + break; + case 13:// enter processed in key up only + break; + case 27:// esc processed in key up only + break; + case 37:// left + this._doArrowDown('left', evt); + break; + case 38:// up + this._doArrowDown('up'); + break; + case 39:// right + this._doArrowDown('right', evt); + break; + case 40:// down + this._doArrowDown('down'); + break; + case 46:// del + this._deleteLabel('del', evt); + break; + default: + // separator processed in key up only + if (!this._isSeparator(keyCode)) { + this._updateInput(evt); + if (!this._open) + this.setOpen(true, { + sendOnOpen : true + }); + } else + evt.stop(); + } + if (!(keyCode == 39 || keyCode == 46 + || keyCode == 8 || keyCode == 37)) + this._removeLabelFocus(); + }, + doKeyUp_ : function(evt) { + var keyCode = evt.keyCode, opts = { + hliteFirst : true + }; + switch (keyCode) { + case 13:// enter + this._doEnterPressed(evt); + break; + case 27:// esc + if (this._open) + this.setOpen(false, { + sendOnOpen : true + }); + this._fixEmptyMessage(); + break; + default: + if (this._isSeparator(keyCode)) + this._doEnterPressed(evt); + else { + this._fixInputWidth(); + if (keyCode == 38 || keyCode == 40) + opts = null; + if (!this._renderByServer) + this._startFixDisplay(opts); + } + } + if (!(keyCode >= 37 && keyCode <= 40 || keyCode == 13)) + startOnSearching(this); + }, + _isSeparator : function(keyCode) { + var separator = this._separator, separatorCode = this._separatorCode; + return (separatorCode && separatorCode + .indexOf(keyCode) != -1) + || ((keyCode >= 48 && keyCode <= 122) + && separator && separator + .toUpperCase() + .indexOf( + String + .fromCharCode(keyCode)) != -1); + }, + _updateInput : function(evt) { + var inp = evt ? evt.domTarget : this.$n('inp'), txcnt = this + .$n('txcnt'), wgt = this; + + // check every 100ms while input + if (!this.fixInputWidth) + this.fixInputWidth = setTimeout(function() { + wgt._fixInputWidth() + }, 100); + }, + setOpen : function(open, opts) { + if (!this.isDisabled()) + this._open = open; + if (this.$n('pp')) { + var pp = this.$n('pp'); + if (open) + this.open(this.$n(), pp, opts); + else + this.close(pp, opts); + } + }, + open : function(n, pp, opts) { + var ppstyle = pp.style; + + this._fixWidth(n); + this._fixsz(pp); + + zk(pp).makeVParent(); + // required for setTopmost + this.setFloating_(true); + this.setTopmost(); + ppstyle.zIndex = n.style.zIndex; + if (opts) { + var inp = this.$n(); + zk(pp).position(inp, "after_start"); + } + + zk(pp).slideDown(this, { + duration : 100 + }); + this._startFixDisplay({ + hliteFirst : true + }); + + if (opts && opts.sendOnOpen) + this.fire('onOpen', { + open : true + }); + }, + close : function(pp, opts) { + zk(pp).undoVParent(); + this.setFloating_(false); + pp.style.display = 'none'; + + if (opts) { + if (opts.sendOnOpen) + this.fire('onOpen', { + open : false + }); + if (opts.fixEmptyMessage) + this._fixEmptyMessage(); + } + if (this._renderByServer) + this._clearListContent(); + }, + _fixsz : function(pp) { + var ppstyle = pp.style, maxh = this._ppMaxHeight; + ppstyle.height = 'auto'; + ppstyle.left = "-10000px"; + ppstyle.display = "block"; + ppstyle.visibility = "hidden"; + if (jq(pp).height() > maxh) + ppstyle.height = maxh + 'px'; + ppstyle.display = "none"; + ppstyle.visibility = "visible"; + }, + // calculate the width for input field + _fixInputWidth : function() { + var n = this.$n(), inp = this.$n('inp'), txcnt = this + .$n('txcnt'), oldh = jq(n).height(), width, max = parseInt(this._width) - 10; + // copy value to hidden txcnt + txcnt.innerHTML = inp.value; + // get width from hidden txcnt + width = jq(txcnt).width() + 30; + + if (width > max) + inp.style.width = max + 'px'; + else + inp.style.width = width + 'px'; + if (this.fixInputWidth) + clearTimeout(this.fixInputWidth); + this.fixInputWidth = null; + }, + // prevent redundent fix display + _startFixDisplay : function(opts) { + // fix asap if from server + if (opts && opts.fromServer) + this._fixDisplay(opts); + else { // replace old if exist and hold a while + // while input + var wgt = this, old; + if (old = this.fixDisplay) + clearTimeout(old); + this.fixDisplay = setTimeout(function() { + wgt._fixDisplay(opts); + }, 200); + } + }, + // filt out not matched item + _fixDisplay : function(opts) { + if (!this._open) + return; + var fromServer = opts && opts.fromServer; + if (!this._renderByServer + || (opts && opts.fromServer)) { + var str = this.$n('inp').value, oldhlite = jq( + this.$n('sel')) + .find( + '.' + this.getZclass() + + '-option-over')[0], existance = this + ._fixSelDisplay(opts && opts.hliteFirst + && !oldhlite, str, fromServer); + str = str ? str.trim() : ''; + this._fixEmptyDisplay({ + showExistance : true + }, str, existance._found, existance._exist); + } else { + this._fixEmptyDisplay({ + showBlank : !this.$n('sel').firstChild + }); + } + }, + // fix the display content of options + _fixSelDisplay : function(hliteFirst, str, fromServer) { + var pp = this.$n('pp'), $pp = jq(pp), maxh = this._ppMaxHeight, ppstyle = pp.style, selItems = this._selItems, options = jq( + this.$n('sel')).children(), found = false, // unselected + // match + // item + // exist + exist = false, // selected match item exist + index, element, showAll, selected; + str = str ? str.trim() : ''; + showAll = str && str == this._emptyMessage + || str == ''; + if (fromServer && str && str != this._emptyMessage && str != '' && this._renderByServer) + showAll = true; + // iterate through item list + for (index = 0, element = options[index]; index < options.length; index++, element = options[index]) { + // should fix each element if renew content from + // server + selected = selItems.indexOf(element.innerHTML) != -1; + if (fromServer || !selected) { + if (!selected + && (showAll || str + && element.innerHTML + .toLowerCase() + .startsWith( + str + .toLowerCase()))) { + if (!found) { + found = true; + if (hliteFirst) + this._hliteOpt(element, true); + } + element.style.display = 'block'; + } else { + this._hliteOpt(element, false); + element.style.display = 'none'; // hide + // if + // has + // input + // and + // not + // match + } + } + if (!exist + && str + && element.innerHTML.toLowerCase() == str + .toLowerCase()) + exist = true; + } + ppstyle.height = 'auto'; + if ($pp.height() > maxh) + ppstyle.height = maxh + 'px'; + return { + _found : found, + _exist : exist + }; + }, + // fix the display of no-result text block + _fixEmptyDisplay : function(type, str, found, exist) { + var ecls = this.getZclass() + '-empty-creatable', empty = this + .$n('empty'); + if (type + && (type.showBlank || type.showExistance + && this._renderByServer && !str)) { + empty.innerHTML = ' '; + jq(empty).removeClass(ecls); + empty.style.display = 'block'; + } else if (type && type.showExistance) { + // set the status of empty block + if (!found) { + if (this._creatable && !exist && str) {// show + // create + // message + // if + // input + // new + // item + // and + // creatable + var createMsg = this._createMessage; + if (createMsg) + createMsg = zUtl.encodeXML( + createMsg.replace(/\{0\}/g, + str)).replace( + /\n/g, '
'); + else + createMsg = ' '; + empty.innerHTML = createMsg; + jq(empty).addClass(ecls); + } else { // show no-result text if + // nothing can be selected + var empMsg = this._noResultsText; + if (empMsg) + empMsg = zUtl.encodeXML( + empMsg.replace(/\{0\}/g, + str)).replace( + /\n/g, '
'); + else + empMsg = ' '; + empty.innerHTML = empMsg; + jq(empty).removeClass(ecls); + } + empty.style.display = 'block'; + } else { + empty.style.display = 'none'; + jq(empty).removeClass(ecls); + } + } + }, + // show emptyMessage or clear input + _fixEmptyMessage : function(force) { + var inp; + if ((!this._open || force) + && (inp = this.$n('inp'))) { + + inp.value = this._selItems.length == 0 ? zUtl + .encodeXML(this.getEmptyMessage()) : ''; + this._fixInputWidth(); + if (this._open) { + this._startFixDisplay(); + } + } + }, + domAttrs_ : function() { + var v; + return this.$supers('domAttrs_', arguments) + + (this.isDisabled() ? ' disabled="disabled"' + : '') + + ((v = this.getEmptyMessage()) ? ' value="' + + zUtl.encodeXML(v) + '"' + : '') + + ((v = this.getTabindex()) ? ' tabindex="' + + v + '"' : '') + + ((v = this.getName()) ? ' name="' + + zUtl.encodeXML(v) + '"' : ''); + } + }); +})(); \ No newline at end of file diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/css/chosenbox.css.dsp b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/css/chosenbox.css.dsp new file mode 100644 index 0000000000..2a3321d059 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/css/chosenbox.css.dsp @@ -0,0 +1,159 @@ +<%@ taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" %> + +.z-chosenbox { + background-color: #FFF; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #EEEEEE)); + background-image: -webkit-linear-gradient(center bottom, white 85%, #EEEEEE 99%); + background-image: -moz-linear-gradient(center bottom, white 85%, #EEEEEE 99%); + background-image: -o-linear-gradient(bottom, white 85%, #EEEEEE 99%); + background-image: -ms-linear-gradient(top, #FFFFFF 85%,#EEEEEE 99%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#EEEEEE',GradientType=0 ); + background-image: linear-gradient(top, #FFFFFF 85%,#EEEEEE 99%); + + display:-moz-inline-box; + display: inline-block; + vertical-align: middle; + overflow: hidden; + + zoom: 1; + display: inline; + + border: 1px solid #cfcfcf; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + -o-border-radius: 3px; + -ms-border-radius: 3px; + border-radius: 3px; + margin: 0; + padding: 2px 5px; + line-height: 14px; +} +.z-chosenbox-focus { + border: 1px solid #0000ff; +} +.z-chosenbox-sel { + padding-bottom: 3px; +} +.z-chosenbox-sel-item { + -webkit-border-radius: 3px; + -moz-border-radius : 3px; + border-radius : 3px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background-color: #E4E4E4; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #E4E4E4), color-stop(0.7, #EEEEEE)); + background-image: -webkit-linear-gradient(center bottom, #E4E4E4 0%, #EEEEEE 70%); + background-image: -moz-linear-gradient(center bottom, #E4E4E4 0%, #EEEEEE 70%); + background-image: -o-linear-gradient(bottom, #E4E4E4 0%, #EEEEEE 70%); + background-image: -ms-linear-gradient(top, #E4E4E4 0%,#EEEEEE 70%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#E4E4E4', endColorstr='#EEEEEE',GradientType=0 ); + background-image: linear-gradient(top, #E4E4E4 0%,#EEEEEE 70%); + color: #333; + border: 1px solid #B4B4B4; + margin: 0px 0px 0px 4px; + white-space: nowrap; + display: inline-block; + + display: inline; + zoom: 1; + + font-size: ${fontSizeM}; + font-family: ${fontFamilyC}; + font-style: normal; + padding-bottom: 0px; + vertical-align: middle; +} +.z-chosenbox-sel-item-cnt { + font-size: 13px; + font-family: 'lucida grande',tahoma,verdana,arial,sans-serif; + padding: 0px 2px; + display: inline-block; + + display: inline; + + font-size: ${fontSizeM}; + font-family: ${fontFamilyC}; +} +.z-chosenbox-sel-item-focus { + background: #D4D4D4; + border-color: #FED700; +} +.z-chosenbox-del-btn { + width: 12px; + height: 13px; + font-size: 1px; + background: url(${c:encodeURL('~./chosenbox/img/chosen-sprite.png')}) right top no-repeat; + border: 1px solid #CCCCCC; + + display: inline-block; + + display: inline; + background: url(${c:encodeURL('~./chosenbox/img/chosen-del.gif')}) no-repeat; + zoom: 1; + + +} +.z-chosenbox-inp { + color: #666; + background: transparent !important; + border: 0 !important; + + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow : none; + -o-box-shadow : none; + box-shadow : none; + + padding: 3px 5px 3px 5px; + + display: inline-block; + width: 30px; + + display: inline; + + font-size: ${fontSizeM}; + font-family: ${fontFamilyC}; +} +.z-chosenbox-txcnt { + display: none; + font-size: ${fontSizeM}; + font-family: ${fontFamilyC}; + white-space: nowrap; +} +.z-chosenbox-pp { + position: absolute; + background-color: #FFFFFF; + border: 1px solid #CCCCCC; + border-top: 0; + font-family: ${fontFamilyC}; + font-size: ${fontSizeM}; + font-weight: normal; + margin:0; + overflow:auto; + + -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15); + -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15); + -o-box-shadow : 0 4px 5px rgba(0,0,0,.15); + box-shadow : 0 4px 5px rgba(0,0,0,.15); +} + +.z-chosenbox-pp-hidden { + display: none; +} +.z-chosenbox-option { + cursor: pointer; + padding-top: 3px; + padding-left: 10px; +} +.z-chosenbox-option-over { + background-color: #D3EFFA; +} +.z-chosenbox-empty { + padding: 3px; + padding-left: 10px; +} +.z-chosenbox-empty-creatable { + cursor: pointer; + background-color: #D3EFFA; +} \ No newline at end of file diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/mold/chosenbox.js b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/mold/chosenbox.js new file mode 100644 index 0000000000..ef2da6eefe --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/mold/chosenbox.js @@ -0,0 +1,28 @@ +/* chosenbox.js + + Purpose: + + Description: + + History: + Tue Nov 16 15:15:52 TST 2011, Created by benbai + +Copyright (C) 2011 Potix Corporation. All Rights Reserved. + +This program is distributed under LGPL Version 3.0 in the hope that +it will be useful, but WITHOUT ANY WARRANTY. +*/ +function (out) { + var zcls = this.getZclass(), + uid = this.uuid; + + out.push('', + '', + '
', // hidden field for change input width dynamically + '
', + '
'); + if (!this._renderByServer) + this._renderItems(out); + out.push('
', + '
','
'); +} \ No newline at end of file diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/zk.wpd b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/zk.wpd new file mode 100644 index 0000000000..2fe97cfc18 --- /dev/null +++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/chosenbox/zk.wpd @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java index 4588832139..d12ffe0ba5 100644 --- a/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java +++ b/org.compiere.db.oracle.provider/src/org/compiere/db/DB_Oracle.java @@ -1420,5 +1420,33 @@ public class DB_Oracle implements AdempiereDatabase if (toIndex == -1) return info; return info.substring(fromIndex + 1, toIndex); + } + + @Override + public String subsetClauseForCSV(String columnName, String csv) { + StringBuilder builder = new StringBuilder(); + builder.append("toTableOfVarchar2(") + .append(columnName) + .append(")"); + builder.append(" submultiset of ") + .append("toTableOfVarchar2('") + .append(csv) + .append("')"); + + return builder.toString(); + } + + @Override + public String intersectClauseForCSV(String columnName, String csv) { + StringBuilder builder = new StringBuilder(); + builder.append("toTableOfVarchar2(") + .append(columnName) + .append(")"); + builder.append(" MULTISET INTERSECT ") + .append("toTableOfVarchar2('") + .append(csv) + .append("') IS NOT EMPTY"); + + return builder.toString(); } } // DB_Oracle diff --git a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java index c0ba95ae96..90191f9146 100755 --- a/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java +++ b/org.compiere.db.postgresql.provider/src/org/compiere/db/DB_PostgreSQL.java @@ -1145,4 +1145,32 @@ public class DB_PostgreSQL implements AdempiereDatabase return info; return info.substring(fromIndex + 1, toIndex); } + + @Override + public String subsetClauseForCSV(String columnName, String csv) { + StringBuilder builder = new StringBuilder(); + builder.append("string_to_array(") + .append(columnName) + .append(",',')"); + builder.append(" <@ "); //is contained by + builder.append("string_to_array('") + .append(csv) + .append("',',')"); + + return builder.toString(); + } + + @Override + public String intersectClauseForCSV(String columnName, String csv) { + StringBuilder builder = new StringBuilder(); + builder.append("string_to_array(") + .append(columnName) + .append(",',')"); + builder.append(" && "); //is contained by + builder.append("string_to_array('") + .append(csv) + .append("',',')"); + + return builder.toString(); + } } // DB_PostgreSQL