IDEMPIERE-2050 Improvement to Menu Lookup. Add back support for search definition transaction code. Separate search definition into a separate tab so there's no performance penalty for menu. Added keyboard navigation ( up and down arrow ) for the search definition result list.

IDEMPIERE-2095 Menu Lookup return records from another client
This commit is contained in:
Heng Sin Low 2014-08-21 00:05:50 +08:00
parent fa3a9fa428
commit 543153c407
4 changed files with 153 additions and 36 deletions

View File

@ -54,6 +54,8 @@ public class DocumentSearchController implements EventListener<Event>{
private static final String SEARCH_RESULT = "search.result"; private static final String SEARCH_RESULT = "search.result";
private static final String ON_SEARCH_DOCUMENTS = "onSearchDocuments"; private static final String ON_SEARCH_DOCUMENTS = "onSearchDocuments";
private Vlayout layout; private Vlayout layout;
private ArrayList<SearchResult> list;
private int selected = -1;
/** /**
* *
@ -64,7 +66,8 @@ public class DocumentSearchController implements EventListener<Event>{
public void create(Component parent) { public void create(Component parent) {
layout = new Vlayout(); layout = new Vlayout();
layout.setStyle("padding: 3px;"); layout.setStyle("padding: 3px;");
layout.setWidth("200px"); layout.setWidth("100%");
layout.setVflex("true");
parent.appendChild(layout); parent.appendChild(layout);
@ -77,6 +80,7 @@ public class DocumentSearchController implements EventListener<Event>{
} }
private void onSearchDocuments(String searchString) { private void onSearchDocuments(String searchString) {
list = new ArrayList<SearchResult>();
if (Util.isEmpty(searchString)) { if (Util.isEmpty(searchString)) {
return; return;
} }
@ -113,8 +117,8 @@ public class DocumentSearchController implements EventListener<Event>{
private List<SearchResult> doSearch(String searchString) { private List<SearchResult> doSearch(String searchString) {
final MRole role = MRole.get(Env.getCtx(), Env.getAD_Role_ID(Env.getCtx()), Env.getAD_User_ID(Env.getCtx()), true); final MRole role = MRole.get(Env.getCtx(), Env.getAD_Role_ID(Env.getCtx()), Env.getAD_User_ID(Env.getCtx()), true);
List<SearchResult> list = new ArrayList<SearchResult>(); selected = -1;
Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, "", null); Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, "", null);
List<MSearchDefinition> definitions = query.setOnlyActiveRecords(true).list(); List<MSearchDefinition> definitions = query.setOnlyActiveRecords(true).list();
for(MSearchDefinition msd : definitions) { for(MSearchDefinition msd : definitions) {
@ -140,6 +144,7 @@ public class DocumentSearchController implements EventListener<Event>{
} else { } else {
sql.append("WHERE UPPER(").append(column.getColumnName()).append(") LIKE UPPER(?)"); sql.append("WHERE UPPER(").append(column.getColumnName()).append(") LIKE UPPER(?)");
} }
sql.append(" AND AD_Client_ID=@AD_Client_ID@ ");
// search for a Integer // search for a Integer
if (msd.getDataType().equals(MSearchDefinition.DATATYPE_INTEGER)) { if (msd.getDataType().equals(MSearchDefinition.DATATYPE_INTEGER)) {
@ -198,6 +203,13 @@ public class DocumentSearchController implements EventListener<Event>{
String sql = builder.toString(); String sql = builder.toString();
if (!Util.isEmpty(extraWhereClase)) if (!Util.isEmpty(extraWhereClase))
sql = sql + extraWhereClase; sql = sql + extraWhereClase;
//@@ is full text search operator for postgresql
boolean hasFullTextOperator = sql.indexOf("@@") >= 0;
if (hasFullTextOperator)
sql = sql.replace("@@", "~!#$*");
sql = Env.parseContext(Env.getCtx(), -1, sql, false, true);
if (hasFullTextOperator)
sql = sql.replace("~!#$*", "@@");
pstmt = DB.prepareStatement(sql, (String)null); pstmt = DB.prepareStatement(sql, (String)null);
if (params.size() > 0) if (params.size() > 0)
DB.setParameters(pstmt, params); DB.setParameters(pstmt, params);
@ -245,7 +257,7 @@ public class DocumentSearchController implements EventListener<Event>{
AEnv.zoom(result.getWindowId(), query); AEnv.zoom(result.getWindowId(), query);
} }
private class SearchResult { public static class SearchResult {
private String windowName; private String windowName;
private int windowId; private int windowId;
private String tableName; private String tableName;
@ -362,4 +374,44 @@ public class DocumentSearchController implements EventListener<Event>{
return false; return false;
} }
public SearchResult selectPrior() {
if (selected > 0) {
selected--;
SearchResult result = list.get(selected);
List<Component> links = layout.getChildren();
for(Component link : links) {
if (link instanceof A) {
A a = (A) link;
if (result.getLabel().equals(a.getLabel())) {
a.setSclass("document-search-current-link");
} else if ("document-search-current-link".equals(a.getSclass())) {
a.setSclass(null);
}
}
}
return result;
}
return null;
}
public SearchResult selectNext() {
if (selected < (list.size()-1)) {
selected++;
SearchResult result = list.get(selected);
List<Component> links = layout.getChildren();
for(Component link : links) {
if (link instanceof A) {
A a = (A) link;
if (result.getLabel().equals(a.getLabel())) {
a.setSclass("document-search-current-link");
} else if ("document-search-current-link".equals(a.getSclass())) {
a.setSclass(null);
}
}
}
return result;
}
return null;
}
} }

View File

@ -13,7 +13,17 @@
*****************************************************************************/ *****************************************************************************/
package org.adempiere.webui.apps; package org.adempiere.webui.apps;
import org.adempiere.webui.apps.DocumentSearchController.SearchResult;
import org.adempiere.webui.component.Bandbox; import org.adempiere.webui.component.Bandbox;
import org.adempiere.webui.component.Tab;
import org.adempiere.webui.component.Tabbox;
import org.adempiere.webui.component.Tabpanel;
import org.adempiere.webui.component.Tabpanels;
import org.adempiere.webui.component.Tabs;
import org.adempiere.webui.util.DocumentSearch;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.EventListener;
@ -23,8 +33,6 @@ import org.zkoss.zk.ui.event.KeyEvent;
import org.zkoss.zk.ui.util.Clients; import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Bandpopup; import org.zkoss.zul.Bandpopup;
import org.zkoss.zul.Div; import org.zkoss.zul.Div;
import org.zkoss.zul.Hlayout;
import org.zkoss.zul.Separator;
/** /**
* @author hengsin * @author hengsin
@ -40,6 +48,8 @@ public class GlobalSearch extends Div implements EventListener<Event> {
private static final String ON_SEARCH = "onSearch"; private static final String ON_SEARCH = "onSearch";
private static final String PREFIX_DOCUMENT_SEARCH = "/";
/** /**
* generated serial id * generated serial id
*/ */
@ -50,6 +60,8 @@ public class GlobalSearch extends Div implements EventListener<Event> {
private MenuSearchController menuController; private MenuSearchController menuController;
private DocumentSearchController docController; private DocumentSearchController docController;
private Tabbox tabbox;
/** /**
* *
*/ */
@ -61,26 +73,43 @@ public class GlobalSearch extends Div implements EventListener<Event> {
private void init() { private void init() {
bandbox = new Bandbox(); bandbox = new Bandbox();
bandbox.setSclass("global-search-box");
appendChild(bandbox); appendChild(bandbox);
bandbox.setWidth("100%"); bandbox.setWidth("100%");
bandbox.setAutodrop(true); bandbox.setAutodrop(true);
bandbox.addEventListener(Events.ON_CHANGING, this); bandbox.addEventListener(Events.ON_CHANGING, this);
bandbox.addEventListener(Events.ON_CHANGE, this);
bandbox.setCtrlKeys("#up#down"); bandbox.setCtrlKeys("#up#down");
bandbox.addEventListener(Events.ON_CTRL_KEY, this); bandbox.addEventListener(Events.ON_CTRL_KEY, this);
Bandpopup popup = new Bandpopup(); Bandpopup popup = new Bandpopup();
popup.setHeight("600px"); popup.setHeight("500px");
bandbox.appendChild(popup); bandbox.appendChild(popup);
Hlayout hlayout = new Hlayout(); tabbox = new Tabbox();
popup.appendChild(hlayout); tabbox.setVflex("true");
menuController.create(hlayout); tabbox.addEventListener(Events.ON_SELECT, this);
Tabs tabs = new Tabs();
tabbox.appendChild(tabs);
Tab tab = new Tab();
tab.setLabel(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Menu")));
tabs.appendChild(tab);
Tabpanels tabPanels = new Tabpanels();
tabbox.appendChild(tabPanels);
Tabpanel tabPanel = new Tabpanel();
tabPanel.setVflex("true");
tabPanel.setSclass("global-search-tabpanel");
tabPanels.appendChild(tabPanel);
popup.appendChild(tabbox);
menuController.create(tabPanel);
Separator separator = new Separator(); tab = new Tab();
separator.setHflex("0"); tab.setLabel(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Document")));
separator.setOrient("vertical"); tabs.appendChild(tab);
hlayout.appendChild(separator); tabPanel = new Tabpanel();
docController.create(hlayout); tabPanel.setSclass("global-search-tabpanel");
tabPanels.appendChild(tabPanel);
docController.create(tabPanel);
addEventListener(ON_SEARCH, this); addEventListener(ON_SEARCH, this);
addEventListener(ON_CREATE_ECHO, this); addEventListener(ON_CREATE_ECHO, this);
@ -90,31 +119,50 @@ public class GlobalSearch extends Div implements EventListener<Event> {
@Override @Override
public void onEvent(Event event) throws Exception { public void onEvent(Event event) throws Exception {
if (Events.ON_CHANGING.equals(event.getName())) { if (Events.ON_CHANGING.equals(event.getName())) {
InputEvent inputEvent = (InputEvent) event; InputEvent inputEvent = (InputEvent) event;
String value = inputEvent.getValue(); String value = inputEvent.getValue();
Events.postEvent(ON_SEARCH, this, value); bandbox.setAttribute("last.onchanging", value);
Events.postEvent(ON_SEARCH, this, value);
} else if (Events.ON_CHANGE.equals(event.getName())) {
bandbox.removeAttribute("last.onchanging");
} else if (Events.ON_CTRL_KEY.equals(event.getName())) { } else if (Events.ON_CTRL_KEY.equals(event.getName())) {
KeyEvent ke = (KeyEvent) event; KeyEvent ke = (KeyEvent) event;
if (ke.getKeyCode() == KeyEvent.UP) { if (ke.getKeyCode() == KeyEvent.UP) {
if (bandbox.getFirstChild().isVisible()) { if (bandbox.getFirstChild().isVisible()) {
MenuItem selected = menuController.selectPrior(); if (tabbox.getSelectedIndex()==0) {
if (selected != null) { MenuItem selected = menuController.selectPrior();
bandbox.setText(selected.getLabel()); if (selected != null) {
bandbox.setText(selected.getLabel());
}
} else {
SearchResult selected = docController.selectPrior();
if (selected != null) {
bandbox.setText(selected.getLabel());
}
} }
} }
} else if (ke.getKeyCode() == KeyEvent.DOWN) { } else if (ke.getKeyCode() == KeyEvent.DOWN) {
if (bandbox.getFirstChild().isVisible()) { if (bandbox.getFirstChild().isVisible()) {
MenuItem selected = menuController.selectNext(); if (tabbox.getSelectedIndex()==0) {
if (selected != null && !"...".equals(selected.getType())) { MenuItem selected = menuController.selectNext();
bandbox.setText(selected.getLabel()); if (selected != null && !"...".equals(selected.getType())) {
bandbox.setText(selected.getLabel());
}
} else {
SearchResult selected = docController.selectNext();
if (selected != null) {
bandbox.setText(selected.getLabel());
}
} }
} }
} }
} else if (event.getName().equals(ON_SEARCH)) { } else if (event.getName().equals(ON_SEARCH)) {
String value = (String) event.getData(); String value = (String) event.getData();
menuController.search(value); if (tabbox.getSelectedIndex()==0)
docController.search(value); menuController.search(value);
else
docController.search(value);
bandbox.focus(); bandbox.focus();
} else if (event.getName().equals(ON_CREATE_ECHO)) { } else if (event.getName().equals(ON_CREATE_ECHO)) {
StringBuilder script = new StringBuilder("jq('#") StringBuilder script = new StringBuilder("jq('#")
@ -132,11 +180,23 @@ public class GlobalSearch extends Div implements EventListener<Event> {
Events.echoEvent(ON_POST_ENTER_KEY, this, null); Events.echoEvent(ON_POST_ENTER_KEY, this, null);
} else if (event.getName().equals(ON_POST_ENTER_KEY)) { } else if (event.getName().equals(ON_POST_ENTER_KEY)) {
Clients.clearBusy(bandbox); Clients.clearBusy(bandbox);
if (menuController.onOk(bandbox)) { if (bandbox.getValue() != null && bandbox.getValue().startsWith(PREFIX_DOCUMENT_SEARCH)) {
return; DocumentSearch search = new DocumentSearch();
} else { if (search.openDocumentsByDocumentNo(bandbox.getValue().substring(1)))
docController.onOk(bandbox); bandbox.setText(null);
} else {
if (tabbox.getSelectedIndex()==0) {
menuController.onOk(bandbox);
} else {
docController.onOk(bandbox);
}
} }
} else if (event.getName().equals(Events.ON_SELECT)) {
String value = (String) bandbox.getAttribute("last.onchanging");
if (value == null) {
value = bandbox.getValue();
}
Events.postEvent(ON_SEARCH, this, value);
} }
} }

View File

@ -17,7 +17,6 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import org.adempiere.webui.component.Label;
import org.adempiere.webui.component.ListHead; import org.adempiere.webui.component.ListHead;
import org.adempiere.webui.component.ListItem; import org.adempiere.webui.component.ListItem;
import org.adempiere.webui.component.Listbox; import org.adempiere.webui.component.Listbox;
@ -182,13 +181,11 @@ public class MenuSearchController implements EventListener<Event>{
layout.setHeight("100%"); layout.setHeight("100%");
parent.appendChild(layout); parent.appendChild(layout);
Label label = new Label(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Menu")));
label.setStyle("padding: 3px; font-weight: bold; display: block;");
layout.appendChild(label);
listbox = new Listbox(); listbox = new Listbox();
listbox.setEmptyMessage(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "FindZeroRecords"))); listbox.setEmptyMessage(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "FindZeroRecords")));
listbox.setStyle("border: none"); listbox.setStyle("border: none");
listbox.setWidth("500px"); listbox.setWidth("100%");
listbox.setVflex("true");
layout.appendChild(listbox); layout.appendChild(listbox);
listbox.setItemRenderer(new MenuItemRenderer()); listbox.setItemRenderer(new MenuItemRenderer());
listbox.addEventListener(Events.ON_SELECT, this); listbox.addEventListener(Events.ON_SELECT, this);
@ -316,7 +313,7 @@ public class MenuSearchController implements EventListener<Event>{
} }
public void search(String value) { public void search(String value) {
listbox.setVisible(false); listbox.setModel((ListModel)null);
Events.echoEvent(ON_SEARCH_ECHO, layout, value); Events.echoEvent(ON_SEARCH_ECHO, layout, value);
} }
@ -330,7 +327,6 @@ public class MenuSearchController implements EventListener<Event>{
newModel = (ListModelList<MenuItem>) subModel.getSubModel(null, -1); newModel = (ListModelList<MenuItem>) subModel.getSubModel(null, -1);
} }
updateListboxModel(newModel); updateListboxModel(newModel);
listbox.setVisible(true);
} }
private void updateListboxModel(ListModelList<MenuItem> newModel) { private void updateListboxModel(ListModelList<MenuItem> newModel) {

View File

@ -1676,6 +1676,15 @@ font-size: 0;
height: 16px; height: 16px;
width: 16px; width: 16px;
} }
.document-search-current-link {
background-image:url(${c:encodeThemeURL('~./zul/img/tree/item-sel.gif')});
}
.global-search-tabpanel {
width: 500px;
}
<%-- workaround for http://jira.idempiere.com/browse/IDEMPIERE-692 --%> <%-- workaround for http://jira.idempiere.com/browse/IDEMPIERE-692 --%>
.z-combobox-pp { .z-combobox-pp {
max-height: 200px; max-height: 200px;