IDEMPIERE-6099 Global Search Enhancements (#2305)
* IDEMPIERE-6099 Global Search Enhancements * IDEMPIERE-6099 Global Search Enhancements
This commit is contained in:
parent
0d0c33d197
commit
9799cebac8
|
@ -21,6 +21,7 @@ import java.util.Collections;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.adempiere.webui.LayoutUtils;
|
||||
import org.adempiere.webui.component.Label;
|
||||
import org.adempiere.webui.util.ZKUpdateUtil;
|
||||
import org.compiere.model.I_AD_SearchDefinition;
|
||||
|
@ -38,6 +39,7 @@ import org.compiere.model.Query;
|
|||
import org.compiere.util.DB;
|
||||
import org.compiere.util.DisplayType;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.util.Msg;
|
||||
import org.compiere.util.Util;
|
||||
import org.zkoss.zk.ui.Component;
|
||||
import org.zkoss.zk.ui.event.Event;
|
||||
|
@ -52,7 +54,9 @@ import org.zkoss.zul.Vlayout;
|
|||
*
|
||||
*/
|
||||
public class DocumentSearchController implements EventListener<Event>{
|
||||
|
||||
|
||||
/** Style for transaction code guide or execution error */
|
||||
private static final String MESSAGE_LABEL_STYLE = "color: rgba(0,0,0,0.34)";
|
||||
/** {@link A} component attribute to hold reference to corresponding {@link #SEARCH_RESULT} **/
|
||||
private static final String SEARCH_RESULT = "search.result";
|
||||
/** onSearchDocuments event **/
|
||||
|
@ -64,6 +68,8 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
private ArrayList<SearchResult> list;
|
||||
/** Current selected index of {@link #list} **/
|
||||
private int selected = -1;
|
||||
/** True when showing transaction code available */
|
||||
private boolean showingGuide = false;
|
||||
|
||||
/**
|
||||
* default constructor
|
||||
|
@ -92,7 +98,12 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
* @param value
|
||||
*/
|
||||
public void search(String value) {
|
||||
layout.getChildren().clear();
|
||||
if (Util.isEmpty(value) || (value.startsWith("/") && value.indexOf(" ") < 0)) {
|
||||
if (!showingGuide)
|
||||
layout.getChildren().clear();
|
||||
} else {
|
||||
layout.getChildren().clear();
|
||||
}
|
||||
Events.echoEvent(ON_SEARCH_DOCUMENTS_EVENT, layout, value);
|
||||
}
|
||||
|
||||
|
@ -103,12 +114,31 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
*/
|
||||
private void onSearchDocuments(String searchString) {
|
||||
list = new ArrayList<SearchResult>();
|
||||
if (Util.isEmpty(searchString)) {
|
||||
if (Util.isEmpty(searchString) || (searchString.startsWith("/") && searchString.indexOf(" ") < 0)) {
|
||||
// No search string, show available transaction code
|
||||
if (!showingGuide) {
|
||||
Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, "TransactionCode IS NOT NULL", null);
|
||||
List<MSearchDefinition> definitions = query.setOnlyActiveRecords(true).setOrderBy("TransactionCode").list();
|
||||
for(MSearchDefinition definition : definitions) {
|
||||
Label label = new Label("/"+definition.getTransactionCode() + " " + definition.getName());
|
||||
label.setStyle(MESSAGE_LABEL_STYLE);
|
||||
layout.appendChild(label);
|
||||
}
|
||||
showingGuide = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
showingGuide = false;
|
||||
|
||||
// Search and show results
|
||||
List<SearchResult> list = doSearch(searchString);
|
||||
if (list.size() > 0) {
|
||||
|
||||
if (list.size() == 1 && list.get(0).getRecordId() == -1) {
|
||||
// DB error or query timeout
|
||||
Label label = new Label(list.get(0).getLabel());
|
||||
label.setStyle(MESSAGE_LABEL_STYLE);
|
||||
layout.appendChild(label);
|
||||
} else if (list.size() > 0) {
|
||||
Collections.sort(list, new Comparator<SearchResult>() {
|
||||
@Override
|
||||
public int compare(SearchResult o1, SearchResult o2) {
|
||||
|
@ -118,20 +148,47 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
return r;
|
||||
}
|
||||
});
|
||||
|
||||
String matchString = searchString.toLowerCase();
|
||||
if (searchString != null && searchString.startsWith("/") && searchString.indexOf(" ") > 1) {
|
||||
// "/TransactionCode Search Text"
|
||||
matchString = searchString.substring(searchString.indexOf(" ")+1).toLowerCase();
|
||||
}
|
||||
|
||||
String windowName = null;
|
||||
for(SearchResult result : list) {
|
||||
if (windowName == null || !windowName.equals(result.getWindowName())) {
|
||||
windowName = result.getWindowName();
|
||||
Label label = new Label(windowName);
|
||||
label.setStyle("padding: 3px; font-weight: bold; display: inline-block;");
|
||||
LayoutUtils.addSclass("window-name", label);
|
||||
layout.appendChild(label);
|
||||
}
|
||||
A a = new A();
|
||||
a.setAttribute(SEARCH_RESULT, result);
|
||||
a.setLabel(result.getLabel());
|
||||
layout.appendChild(a);
|
||||
a.setStyle("padding-left: 3px; display: inline-block;");
|
||||
LayoutUtils.addSclass("search-result", a);
|
||||
a.addEventListener(Events.ON_CLICK, this);
|
||||
String label = result.getLabel();
|
||||
if (!Util.isEmpty(matchString, true)) {
|
||||
int match = label.toLowerCase().indexOf(matchString);
|
||||
while (match >= 0) {
|
||||
if (match > 0) {
|
||||
a.appendChild(new Label(label.substring(0, match)));
|
||||
Label l = new Label(label.substring(match, match+matchString.length()));
|
||||
LayoutUtils.addSclass("highlight", l);
|
||||
a.appendChild(l);
|
||||
label = label.substring(match+matchString.length());
|
||||
} else {
|
||||
Label l = new Label(label.substring(0, matchString.length()));
|
||||
LayoutUtils.addSclass("highlight", l);
|
||||
a.appendChild(l);
|
||||
label = label.substring(matchString.length());
|
||||
}
|
||||
match = label.toLowerCase().indexOf(matchString);
|
||||
}
|
||||
}
|
||||
if (label.length() > 0)
|
||||
a.appendChild(new Label(label));
|
||||
}
|
||||
layout.invalidate();
|
||||
}
|
||||
|
@ -146,7 +203,23 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
final MRole role = MRole.get(Env.getCtx(), Env.getAD_Role_ID(Env.getCtx()), Env.getAD_User_ID(Env.getCtx()), true);
|
||||
|
||||
selected = -1;
|
||||
Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, "TransactionCode IS NULL", null);
|
||||
|
||||
// Search with or without transaction code
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
String transactionCode = null;
|
||||
if (searchString != null && searchString.startsWith("/") && searchString.indexOf(" ") > 1) {
|
||||
// "/TransactionCode Search Text"
|
||||
transactionCode = searchString.substring(1, searchString.indexOf(" "));
|
||||
searchString = searchString.substring(searchString.indexOf(" ")+1);
|
||||
whereClause.append("Upper(TransactionCode) = ?");
|
||||
} else {
|
||||
// Search with definition that doesn't use transaction code
|
||||
whereClause.append("TransactionCode IS NULL");
|
||||
}
|
||||
|
||||
Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, whereClause.toString(), null);
|
||||
if (transactionCode != null)
|
||||
query.setParameters(transactionCode.toUpperCase());
|
||||
List<MSearchDefinition> definitions = query.setOnlyActiveRecords(true).list();
|
||||
for(MSearchDefinition msd : definitions) {
|
||||
MTable table = new MTable(Env.getCtx(), msd.getAD_Table_ID(), null);
|
||||
|
@ -270,7 +343,15 @@ public class DocumentSearchController implements EventListener<Event>{
|
|||
list.add(result);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
SearchResult result = new SearchResult();
|
||||
result.setRecordId(-1);
|
||||
if (DB.getDatabase().isQueryTimeout(e)) {
|
||||
result.setLabel(Msg.getMsg(Env.getCtx(), "Timeout"));
|
||||
} else {
|
||||
result.setLabel(Msg.getMsg(Env.getCtx(), "DBExecuteError"));
|
||||
e.printStackTrace();
|
||||
}
|
||||
list.add(result);
|
||||
} finally {
|
||||
DB.close(rs, pstmt);
|
||||
}
|
||||
|
|
|
@ -100,9 +100,13 @@ public class GlobalSearch extends Div implements EventListener<Event> {
|
|||
bandbox.addEventListener(Events.ON_CHANGE, this);
|
||||
bandbox.setCtrlKeys("#up#down");
|
||||
bandbox.addEventListener(Events.ON_CTRL_KEY, this);
|
||||
bandbox.addEventListener(Events.ON_FOCUS, e -> {
|
||||
if (!bandbox.isOpen())
|
||||
bandbox.setOpen(true);
|
||||
});
|
||||
|
||||
Bandpopup popup = new Bandpopup();
|
||||
ZKUpdateUtil.setWindowHeightX(popup, ClientInfo.get().desktopHeight-50);
|
||||
ZKUpdateUtil.setWindowHeightX(popup, ClientInfo.get().desktopHeight-100);
|
||||
bandbox.appendChild(popup);
|
||||
|
||||
tabbox = new Tabbox();
|
||||
|
@ -139,15 +143,18 @@ public class GlobalSearch extends Div implements EventListener<Event> {
|
|||
@Override
|
||||
public void onEvent(Event event) throws Exception {
|
||||
if (Events.ON_CHANGING.equals(event.getName())) {
|
||||
//post ON_SEARCH_EVENT for ON_CHANGING from bandbox
|
||||
// Post ON_SEARCH_EVENT for ON_CHANGING from bandbox
|
||||
InputEvent inputEvent = (InputEvent) event;
|
||||
String value = inputEvent.getValue();
|
||||
String value = inputEvent.getValue();
|
||||
// Auto switch to Search with "/"
|
||||
if (value != null && value.startsWith("/") && tabbox.getSelectedIndex()==0)
|
||||
tabbox.setSelectedIndex(1);
|
||||
bandbox.setAttribute(LAST_ONCHANGING_ATTR, value);
|
||||
Events.postEvent(ON_SEARCH_EVENT, this, value);
|
||||
} else if (Events.ON_CHANGE.equals(event.getName())) {
|
||||
bandbox.removeAttribute(LAST_ONCHANGING_ATTR);
|
||||
} else if (Events.ON_CTRL_KEY.equals(event.getName())) {
|
||||
//handle keyboard navigation for bandbox items
|
||||
// Handle keyboard navigation for bandbox items
|
||||
KeyEvent ke = (KeyEvent) event;
|
||||
if (ke.getKeyCode() == KeyEvent.UP) {
|
||||
if (bandbox.getFirstChild().isVisible()) {
|
||||
|
@ -180,10 +187,12 @@ public class GlobalSearch extends Div implements EventListener<Event> {
|
|||
}
|
||||
} else if (event.getName().equals(ON_SEARCH_EVENT)) {
|
||||
String value = (String) event.getData();
|
||||
if (tabbox.getSelectedIndex()==0)
|
||||
if (tabbox.getSelectedIndex()==0) {
|
||||
menuController.setHighlightText(value);
|
||||
menuController.search(value);
|
||||
else
|
||||
} else {
|
||||
docController.search(value);
|
||||
}
|
||||
bandbox.focus();
|
||||
} else if (event.getName().equals(ON_CREATE_ECHO_EVENT)) {
|
||||
//setup client side listener for enter key
|
||||
|
@ -247,4 +256,20 @@ public class GlobalSearch extends Div implements EventListener<Event> {
|
|||
public void onClientInfo() {
|
||||
ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set place holder text for global search input box
|
||||
* @param placeHolder
|
||||
*/
|
||||
public void setPlaceHolderText(String placeHolder) {
|
||||
bandbox.setPlaceholder(placeHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tooltip text for global search input box
|
||||
* @param tooltipText
|
||||
*/
|
||||
public void setTooltipText(String tooltipText) {
|
||||
bandbox.setTooltiptext(tooltipText);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import java.util.ArrayList;
|
|||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.adempiere.webui.LayoutUtils;
|
||||
import org.adempiere.webui.component.Label;
|
||||
import org.adempiere.webui.component.ListHead;
|
||||
import org.adempiere.webui.component.ListItem;
|
||||
import org.adempiere.webui.component.Listbox;
|
||||
|
@ -95,6 +97,8 @@ public class MenuSearchController implements EventListener<Event>{
|
|||
private ListModelList<MenuItem> fullModel;
|
||||
/** true when controller is handling event from Star/Favourite button **/
|
||||
private boolean inStarEvent;
|
||||
|
||||
private String highlightText = null;
|
||||
|
||||
/** Event post from {@link #selectTreeitem(Object, Boolean)} **/
|
||||
private static final String ON_POST_SELECT_TREEITEM_EVENT = "onPostSelectTreeitem";
|
||||
|
@ -604,6 +608,14 @@ public class MenuSearchController implements EventListener<Event>{
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text to highlight
|
||||
* @param s
|
||||
*/
|
||||
public void setHighlightText(String s) {
|
||||
highlightText = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ListitemRenderer} for {@link #listbox}
|
||||
*/
|
||||
|
@ -634,6 +646,33 @@ public class MenuSearchController implements EventListener<Event>{
|
|||
cell.setImage(null);
|
||||
cell.setIconSclass(data.getImage());
|
||||
}
|
||||
|
||||
// Highlight search text
|
||||
if (!Util.isEmpty(highlightText, true) && data.getLabel().toLowerCase().contains(highlightText.toLowerCase())) {
|
||||
// Space to maintain proper gap between icon and label
|
||||
cell.setLabel(" ");
|
||||
String label = data.getLabel();
|
||||
String matchString = highlightText.toLowerCase();
|
||||
int match = label.toLowerCase().indexOf(matchString);
|
||||
while (match >= 0) {
|
||||
if (match > 0) {
|
||||
cell.appendChild(new Label(label.substring(0, match)));
|
||||
Label l = new Label(label.substring(match, match+matchString.length()));
|
||||
LayoutUtils.addSclass("highlight", l);
|
||||
cell.appendChild(l);
|
||||
label = label.substring(match+matchString.length());
|
||||
} else {
|
||||
Label l = new Label(label.substring(0, matchString.length()));
|
||||
LayoutUtils.addSclass("highlight", l);
|
||||
cell.appendChild(l);
|
||||
label = label.substring(matchString.length());
|
||||
}
|
||||
match = label.toLowerCase().indexOf(matchString);
|
||||
}
|
||||
if (label.length() > 0)
|
||||
cell.appendChild(new Label(label));
|
||||
}
|
||||
|
||||
item.appendChild(cell);
|
||||
cell.setTooltip(data.getDescription());
|
||||
item.setValue(data);
|
||||
|
|
|
@ -95,7 +95,7 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
|
||||
btnMenu = (LabelImageElement) getFellow("menuButton");
|
||||
btnMenu.setIconSclass("z-icon-sitemap");
|
||||
btnMenu.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Menu")));
|
||||
btnMenu.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Menu")) + " Alt+M");
|
||||
btnMenu.addEventListener(Events.ON_CLICK, this);
|
||||
if (ClientInfo.isMobile()) {
|
||||
LayoutUtils.addSclass("mobile", this);
|
||||
|
@ -131,6 +131,8 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
stub.getParent().insertBefore(globalSearch, stub);
|
||||
stub.detach();
|
||||
globalSearch.setId("menuLookup");
|
||||
globalSearch.setPlaceHolderText("Alt+G");
|
||||
globalSearch.setTooltipText("Alt+G");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -154,16 +156,19 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
} else if (Events.ON_CREATE.equals(event.getName())) {
|
||||
onCreate();
|
||||
}else if (event instanceof KeyEvent)
|
||||
{
|
||||
//alt+m for the menu
|
||||
{
|
||||
KeyEvent ke = (KeyEvent) event;
|
||||
if (ke.getKeyCode() == 77)
|
||||
if (ke.getKeyCode() == 77) // alt+m for the menu
|
||||
{
|
||||
popMenu.open(btnMenu, "after_start");
|
||||
popMenu.setFocus(true);
|
||||
}else if (ke.getKeyCode() == 27) {
|
||||
}
|
||||
else if (ke.getKeyCode() == 27) // esc to close menu
|
||||
{
|
||||
popMenu.close();
|
||||
}else if (ke.getKeyCode() == 71) {
|
||||
}
|
||||
else if (ke.getKeyCode() == 71) // alt+g for the search
|
||||
{
|
||||
globalSearch.setFocus(true);
|
||||
}
|
||||
} else if(event.getName().equals(ZoomEvent.EVENT_NAME)) {
|
||||
|
|
|
@ -152,6 +152,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
.global-search-tabpanel .window-name.z-label {
|
||||
padding: 3px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
.global-search-tabpanel .search-result.z-a {
|
||||
padding-left: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
.global-search-tabpanel .highlight {
|
||||
background-color: #FFFF00;
|
||||
}
|
||||
|
||||
.menu-href [class^="z-icon-"] {
|
||||
font-size: larger;
|
||||
color: #333;
|
||||
|
|
Loading…
Reference in New Issue