IDEMPIERE-2050 Improvement to Menu Lookup.
This commit is contained in:
parent
7815708472
commit
7c390b1d25
|
@ -0,0 +1,365 @@
|
|||
/******************************************************************************
|
||||
* Copyright (C) 2014 Low Heng Sin *
|
||||
* Copyright (C) 2014 Trek Global *
|
||||
* 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.apps;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.adempiere.webui.component.Label;
|
||||
import org.compiere.model.I_AD_SearchDefinition;
|
||||
import org.compiere.model.MColumn;
|
||||
import org.compiere.model.MLookup;
|
||||
import org.compiere.model.MLookupFactory;
|
||||
import org.compiere.model.MLookupInfo;
|
||||
import org.compiere.model.MQuery;
|
||||
import org.compiere.model.MRole;
|
||||
import org.compiere.model.MSearchDefinition;
|
||||
import org.compiere.model.MTable;
|
||||
import org.compiere.model.MWindow;
|
||||
import org.compiere.model.Query;
|
||||
import org.compiere.util.DB;
|
||||
import org.compiere.util.DisplayType;
|
||||
import org.compiere.util.Env;
|
||||
import org.compiere.util.Util;
|
||||
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.A;
|
||||
import org.zkoss.zul.Textbox;
|
||||
import org.zkoss.zul.Vlayout;
|
||||
|
||||
/**
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
public class DocumentSearchController implements EventListener<Event>{
|
||||
|
||||
private static final String SEARCH_RESULT = "search.result";
|
||||
private static final String ON_SEARCH_DOCUMENTS = "onSearchDocuments";
|
||||
private Vlayout layout;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public DocumentSearchController() {
|
||||
}
|
||||
|
||||
public void create(Component parent) {
|
||||
layout = new Vlayout();
|
||||
layout.setStyle("padding: 3px;");
|
||||
layout.setWidth("200px");
|
||||
|
||||
parent.appendChild(layout);
|
||||
|
||||
layout.addEventListener(ON_SEARCH_DOCUMENTS, this);
|
||||
}
|
||||
|
||||
public void search(String value) {
|
||||
layout.getChildren().clear();
|
||||
Events.echoEvent(ON_SEARCH_DOCUMENTS, layout, value);
|
||||
}
|
||||
|
||||
private void onSearchDocuments(String searchString) {
|
||||
if (Util.isEmpty(searchString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SearchResult> list = doSearch(searchString);
|
||||
if (list.size() > 0) {
|
||||
Collections.sort(list, new Comparator<SearchResult>() {
|
||||
@Override
|
||||
public int compare(SearchResult o1, SearchResult o2) {
|
||||
int r = o1.getWindowName().compareTo(o2.getWindowName());
|
||||
if (r == 0)
|
||||
r = o1.getLabel().compareTo(o2.getLabel());
|
||||
return r;
|
||||
}
|
||||
});
|
||||
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;");
|
||||
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;");
|
||||
a.addEventListener(Events.ON_CLICK, this);
|
||||
}
|
||||
layout.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
List<SearchResult> list = new ArrayList<SearchResult>();
|
||||
Query query = new Query(Env.getCtx(), I_AD_SearchDefinition.Table_Name, "", null);
|
||||
List<MSearchDefinition> definitions = query.setOnlyActiveRecords(true).list();
|
||||
for(MSearchDefinition msd : definitions) {
|
||||
MTable table = new MTable(Env.getCtx(), msd.getAD_Table_ID(), null);
|
||||
StringBuilder sql = null;
|
||||
MWindow window = msd.getAD_Window_ID() > 0 && role.getWindowAccess(msd.getAD_Window_ID()) != null ? MWindow.get(Env.getCtx(), msd.getAD_Window_ID()) : null;
|
||||
MWindow powindow = msd.getPO_Window_ID() > 0 && role.getWindowAccess(msd.getPO_Window_ID()) != null ? MWindow.get(Env.getCtx(), msd.getPO_Window_ID()) : null;
|
||||
if (window == null && powindow == null)
|
||||
continue;
|
||||
List<Object> params = new ArrayList<Object>();
|
||||
// SearchDefinition with a given table and column
|
||||
if (msd.getSearchType().equals(MSearchDefinition.SEARCHTYPE_TABLE)) {
|
||||
MColumn column = new MColumn(Env.getCtx(), msd.getAD_Column_ID(), null);
|
||||
sql = new StringBuilder("SELECT ").append(table.getTableName()).append("_ID, ")
|
||||
.append(column.getColumnName());
|
||||
sql.append(" FROM ")
|
||||
.append(table.getTableName())
|
||||
.append(" ");
|
||||
// search for an Integer
|
||||
if (msd.getDataType().equals(MSearchDefinition.DATATYPE_INTEGER)) {
|
||||
sql.append("WHERE ").append(column.getColumnName()).append("=?");
|
||||
// search for a String
|
||||
} else {
|
||||
sql.append("WHERE UPPER(").append(column.getColumnName()).append(") LIKE UPPER(?)");
|
||||
}
|
||||
|
||||
// search for a Integer
|
||||
if (msd.getDataType().equals(MSearchDefinition.DATATYPE_INTEGER)) {
|
||||
params.add(Integer.valueOf(searchString.replaceAll("\\D", "")));
|
||||
// search for a String
|
||||
} else if (msd.getDataType().equals(MSearchDefinition.DATATYPE_STRING)) {
|
||||
if (searchString.endsWith("%"))
|
||||
params.add(searchString);
|
||||
else
|
||||
params.add(searchString+"%");
|
||||
}
|
||||
// SearchDefinition with a special query
|
||||
} else if (msd.getSearchType().equals(MSearchDefinition.SEARCHTYPE_QUERY)) {
|
||||
sql = new StringBuilder().append(msd.getQuery());
|
||||
// count '?' in statement
|
||||
int count = 1;
|
||||
for (char c : sql.toString().toCharArray()) {
|
||||
if (c == '?') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (msd.getDataType().equals(MSearchDefinition.DATATYPE_INTEGER)) {
|
||||
params.add(Integer.valueOf(searchString.replaceAll("\\D", "")));
|
||||
} else if (msd.getDataType().equals(MSearchDefinition.DATATYPE_STRING)) {
|
||||
if (searchString.endsWith("%"))
|
||||
params.add(searchString);
|
||||
else
|
||||
params.add(searchString+"%");
|
||||
}
|
||||
}
|
||||
}
|
||||
MLookupInfo lookupInfo = MLookupFactory.getLookupInfo(Env.getCtx(), -1, -1, DisplayType.Search, Env.getLanguage(Env.getCtx()), table.getTableName() + "_ID", 0, false, null);
|
||||
MLookup lookup = new MLookup(lookupInfo, -1);
|
||||
|
||||
if (sql != null) {
|
||||
if (powindow != null) {
|
||||
if (window != null) {
|
||||
doRetrieval(msd, sql, params, lookup, window, table.getTableName(), " AND IsSOTrx='Y' ", list);
|
||||
}
|
||||
doRetrieval(msd, sql, params, lookup, powindow, table.getTableName(), " AND IsSOTrx='N' ", list);
|
||||
} else if (window != null) {
|
||||
doRetrieval(msd, sql, params, lookup, window, table.getTableName(), null, list);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void doRetrieval(MSearchDefinition msd, StringBuilder builder, List<Object> params, MLookup lookup, MWindow window, String tableName,
|
||||
String extraWhereClase, List<SearchResult> list) {
|
||||
PreparedStatement pstmt = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = builder.toString();
|
||||
if (!Util.isEmpty(extraWhereClase))
|
||||
sql = sql + extraWhereClase;
|
||||
pstmt = DB.prepareStatement(sql, (String)null);
|
||||
if (params.size() > 0)
|
||||
DB.setParameters(pstmt, params);
|
||||
pstmt.setQueryTimeout(1);
|
||||
rs = pstmt.executeQuery();
|
||||
int count = 0;
|
||||
while (rs.next() && count < 3) {
|
||||
count++;
|
||||
int id = rs.getInt(1);
|
||||
SearchResult result = new SearchResult();
|
||||
result.setLabel(lookup.getDisplay(id));
|
||||
result.setRecordId(id);
|
||||
result.setWindowName(window.get_Translation("Name"));
|
||||
result.setWindowId(window.getAD_Window_ID());
|
||||
|
||||
result.setTableName(tableName);
|
||||
if (rs.getMetaData().getColumnCount() > 1) {
|
||||
result.setName(rs.getString(2));
|
||||
}
|
||||
list.add(result);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
DB.close(rs, pstmt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) throws Exception {
|
||||
if (Events.ON_CLICK.equals(event.getName())) {
|
||||
if (event.getTarget() instanceof A) {
|
||||
SearchResult result = (SearchResult) event.getTarget().getAttribute(SEARCH_RESULT);
|
||||
doZoom(result);
|
||||
}
|
||||
} else if (event.getName().equals(ON_SEARCH_DOCUMENTS)) {
|
||||
onSearchDocuments((String)event.getData());
|
||||
}
|
||||
}
|
||||
|
||||
private void doZoom(SearchResult result) {
|
||||
MQuery query = new MQuery();
|
||||
query.addRestriction(result.getTableName()+"_ID", "=", result.getRecordId());
|
||||
AEnv.zoom(result.getWindowId(), query);
|
||||
}
|
||||
|
||||
private class SearchResult {
|
||||
private String windowName;
|
||||
private int windowId;
|
||||
private String tableName;
|
||||
private int recordId;
|
||||
private String label;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* @return the windowId
|
||||
*/
|
||||
public int getWindowId() {
|
||||
return windowId;
|
||||
}
|
||||
/**
|
||||
* @param windowId the windowId to set
|
||||
*/
|
||||
public void setWindowId(int windowId) {
|
||||
this.windowId = windowId;
|
||||
}
|
||||
/**
|
||||
* @return the tableName
|
||||
*/
|
||||
public String getTableName() {
|
||||
return tableName;
|
||||
}
|
||||
/**
|
||||
* @param tableName the tableName to set
|
||||
*/
|
||||
public void setTableName(String tableName) {
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the windowName
|
||||
*/
|
||||
public String getWindowName() {
|
||||
return windowName;
|
||||
}
|
||||
/**
|
||||
* @param windowName the windowName to set
|
||||
*/
|
||||
public void setWindowName(String windowName) {
|
||||
this.windowName = windowName;
|
||||
}
|
||||
/**
|
||||
* @return the recordId
|
||||
*/
|
||||
public int getRecordId() {
|
||||
return recordId;
|
||||
}
|
||||
/**
|
||||
* @param recordId the recordId to set
|
||||
*/
|
||||
public void setRecordId(int recordId) {
|
||||
this.recordId = recordId;
|
||||
}
|
||||
/**
|
||||
* @return the label
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
/**
|
||||
* @param label the label to set
|
||||
*/
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* @param name the name to set
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onOk(Textbox textbox) {
|
||||
String text = textbox.getText();
|
||||
if (Util.isEmpty(text))
|
||||
return false;
|
||||
text = text.toLowerCase();
|
||||
int size = layout.getChildren().size();
|
||||
A firstStart = null;
|
||||
A exact = null;
|
||||
for(int i = 0; i < size; i++) {
|
||||
if (!(layout.getChildren().get(i) instanceof A)) continue;
|
||||
A a = (A) layout.getChildren().get(i);
|
||||
SearchResult result = (SearchResult) a.getAttribute(SEARCH_RESULT);
|
||||
if (result.getLabel().equalsIgnoreCase(text)) {
|
||||
exact = a;
|
||||
break;
|
||||
} else if (text.equalsIgnoreCase(result.getName())) {
|
||||
exact = a;
|
||||
break;
|
||||
} else if (firstStart == null && result.getLabel().toLowerCase().startsWith(text) && text.length() >=3 ) {
|
||||
firstStart = a;
|
||||
}
|
||||
}
|
||||
|
||||
SearchResult result = null;
|
||||
if (exact != null)
|
||||
result = (SearchResult) exact.getAttribute(SEARCH_RESULT);
|
||||
else if (firstStart != null)
|
||||
result = (SearchResult) firstStart.getAttribute(SEARCH_RESULT);
|
||||
if (result != null) {
|
||||
doZoom(result);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
/******************************************************************************
|
||||
* Copyright (C) 2014 Low Heng Sin *
|
||||
* Copyright (C) 2014 Trek Global *
|
||||
* 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.apps;
|
||||
|
||||
import org.adempiere.webui.component.Bandbox;
|
||||
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.KeyEvent;
|
||||
import org.zkoss.zk.ui.util.Clients;
|
||||
import org.zkoss.zul.Bandpopup;
|
||||
import org.zkoss.zul.Div;
|
||||
import org.zkoss.zul.Hlayout;
|
||||
import org.zkoss.zul.Separator;
|
||||
|
||||
/**
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
public class GlobalSearch extends Div implements EventListener<Event> {
|
||||
|
||||
private static final String ON_ENTER_KEY = "onEnterKey";
|
||||
|
||||
private static final String ON_POST_ENTER_KEY = "onPostEnterKey";
|
||||
|
||||
private static final String ON_CREATE_ECHO = "onCreateEcho";
|
||||
|
||||
private static final String ON_SEARCH = "onSearch";
|
||||
|
||||
/**
|
||||
* generated serial id
|
||||
*/
|
||||
private static final long serialVersionUID = -8793878697269469837L;
|
||||
|
||||
private Bandbox bandbox;
|
||||
|
||||
private MenuSearchController menuController;
|
||||
private DocumentSearchController docController;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public GlobalSearch(MenuSearchController menuController) {
|
||||
this.menuController = menuController;
|
||||
docController = new DocumentSearchController();
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
bandbox = new Bandbox();
|
||||
appendChild(bandbox);
|
||||
bandbox.setWidth("100%");
|
||||
bandbox.setAutodrop(true);
|
||||
bandbox.addEventListener(Events.ON_CHANGING, this);
|
||||
bandbox.setCtrlKeys("#up#down");
|
||||
bandbox.addEventListener(Events.ON_CTRL_KEY, this);
|
||||
|
||||
Bandpopup popup = new Bandpopup();
|
||||
popup.setWidth("700px");
|
||||
popup.setHeight("600px");
|
||||
bandbox.appendChild(popup);
|
||||
|
||||
Hlayout hlayout = new Hlayout();
|
||||
hlayout.setHflex("1");
|
||||
popup.appendChild(hlayout);
|
||||
menuController.create(hlayout);
|
||||
|
||||
Separator separator = new Separator();
|
||||
separator.setHeight("100%");
|
||||
separator.setBar(true);
|
||||
separator.setHflex("0");
|
||||
separator.setOrient("horizontal");
|
||||
hlayout.appendChild(separator);
|
||||
docController.create(hlayout);
|
||||
|
||||
addEventListener(ON_SEARCH, this);
|
||||
addEventListener(ON_CREATE_ECHO, this);
|
||||
bandbox.addEventListener(ON_ENTER_KEY, this);
|
||||
addEventListener(ON_POST_ENTER_KEY, this);
|
||||
|
||||
Events.echoEvent(ON_CREATE_ECHO, this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) throws Exception {
|
||||
if (Events.ON_CHANGING.equals(event.getName())) {
|
||||
InputEvent inputEvent = (InputEvent) event;
|
||||
String value = inputEvent.getValue();
|
||||
Events.postEvent(ON_SEARCH, this, value);
|
||||
} else if (Events.ON_CTRL_KEY.equals(event.getName())) {
|
||||
KeyEvent ke = (KeyEvent) event;
|
||||
if (ke.getKeyCode() == KeyEvent.UP) {
|
||||
if (bandbox.getFirstChild().isVisible()) {
|
||||
MenuItem selected = menuController.selectPrior();
|
||||
if (selected != null) {
|
||||
bandbox.setText(selected.getLabel());
|
||||
}
|
||||
}
|
||||
} else if (ke.getKeyCode() == KeyEvent.DOWN) {
|
||||
if (bandbox.getFirstChild().isVisible()) {
|
||||
MenuItem selected = menuController.selectNext();
|
||||
if (selected != null && !"...".equals(selected.getType())) {
|
||||
bandbox.setText(selected.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.getName().equals(ON_SEARCH)) {
|
||||
String value = (String) event.getData();
|
||||
menuController.search(value);
|
||||
docController.search(value);
|
||||
bandbox.focus();
|
||||
} else if (event.getName().equals(ON_CREATE_ECHO)) {
|
||||
StringBuilder script = new StringBuilder("jq('#")
|
||||
.append(bandbox.getUuid())
|
||||
.append("').bind('keydown', function(e) {var code=e.keyCode||e.which;console.log(code);if(code==13){")
|
||||
.append("var widget=zk.Widget.$('#").append(bandbox.getUuid()).append("');")
|
||||
.append("var event=new zk.Event(widget,'")
|
||||
.append(ON_ENTER_KEY)
|
||||
.append("',{},{toServer:true});")
|
||||
.append("zAu.send(event);")
|
||||
.append("}});");
|
||||
Clients.evalJavaScript(script.toString());
|
||||
} else if (event.getName().equals(ON_ENTER_KEY)) {
|
||||
Clients.showBusy(bandbox, null);
|
||||
Events.echoEvent(ON_POST_ENTER_KEY, this, null);
|
||||
} else if (event.getName().equals(ON_POST_ENTER_KEY)) {
|
||||
Clients.clearBusy(bandbox);
|
||||
if (menuController.onOk(bandbox)) {
|
||||
return;
|
||||
} else {
|
||||
docController.onOk(bandbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
*
|
||||
*/
|
||||
package org.adempiere.webui.apps;
|
||||
|
||||
/**
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
public class MenuItem {
|
||||
|
||||
private String label;
|
||||
private String description;
|
||||
private String image;
|
||||
private String type;
|
||||
private Object data;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public MenuItem() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the label
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param label the label to set
|
||||
*/
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the image
|
||||
*/
|
||||
public String getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param image the image to set
|
||||
*/
|
||||
public void setImage(String image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type the type to set
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,481 @@
|
|||
/******************************************************************************
|
||||
* Copyright (C) 2014 Low Heng Sin *
|
||||
* Copyright (C) 2014 Trek Global *
|
||||
* 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.apps;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.adempiere.webui.component.Label;
|
||||
import org.adempiere.webui.component.ListHead;
|
||||
import org.adempiere.webui.component.ListItem;
|
||||
import org.adempiere.webui.component.Listbox;
|
||||
import org.adempiere.webui.theme.ThemeManager;
|
||||
import org.adempiere.webui.util.TreeItemAction;
|
||||
import org.adempiere.webui.util.TreeNodeAction;
|
||||
import org.adempiere.webui.util.TreeUtils;
|
||||
import org.compiere.model.MTreeNode;
|
||||
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.Executions;
|
||||
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.Clients;
|
||||
import org.zkoss.zul.A;
|
||||
import org.zkoss.zul.DefaultTreeNode;
|
||||
import org.zkoss.zul.ListModel;
|
||||
import org.zkoss.zul.ListModelList;
|
||||
import org.zkoss.zul.ListModels;
|
||||
import org.zkoss.zul.ListSubModel;
|
||||
import org.zkoss.zul.Listcell;
|
||||
import org.zkoss.zul.Listheader;
|
||||
import org.zkoss.zul.Listitem;
|
||||
import org.zkoss.zul.ListitemRenderer;
|
||||
import org.zkoss.zul.ListitemRendererExt;
|
||||
import org.zkoss.zul.Textbox;
|
||||
import org.zkoss.zul.Toolbarbutton;
|
||||
import org.zkoss.zul.Tree;
|
||||
import org.zkoss.zul.Treechildren;
|
||||
import org.zkoss.zul.Treeitem;
|
||||
import org.zkoss.zul.Vlayout;
|
||||
import org.zkoss.zul.impl.LabelElement;
|
||||
import org.zkoss.zul.impl.LabelImageElement;
|
||||
|
||||
/**
|
||||
* @author hengsin
|
||||
*
|
||||
*/
|
||||
public class MenuSearchController implements EventListener<Event>{
|
||||
|
||||
private static final String ON_SEARCH_ECHO = "onSearchEcho";
|
||||
private static final String ON_LOAD_MORE = "onLoadMore";
|
||||
private static final String ONSELECT_TIMESTAMP = "onselect.timestamp";
|
||||
private Tree tree;
|
||||
private Listbox listbox;
|
||||
private ListModelList<MenuItem> model;
|
||||
private Vlayout layout;
|
||||
private ListModelList<MenuItem> fullModel;
|
||||
|
||||
private static final String ON_POST_SELECT_TREEITEM_EVENT = "onPostSelectTreeitem";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public MenuSearchController(Tree tree) {
|
||||
this.tree = tree;
|
||||
}
|
||||
|
||||
public void refreshModel() {
|
||||
final List<MenuItem> list = new ArrayList<MenuItem>();
|
||||
if (tree.getModel() == null) {
|
||||
TreeUtils.traverse(tree, new TreeItemAction() {
|
||||
public void run(Treeitem treeItem) {
|
||||
if (treeItem.isVisible())
|
||||
addTreeItem(list, treeItem);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
TreeUtils.traverse(tree.getModel(), new TreeNodeAction() {
|
||||
public void run(DefaultTreeNode<?> treeNode) {
|
||||
addTreeItem(list, treeNode);
|
||||
}
|
||||
});
|
||||
}
|
||||
model = new ListModelList<MenuItem>(list, true);
|
||||
model.sort(new Comparator<MenuItem>() {
|
||||
|
||||
@Override
|
||||
public int compare(MenuItem o1, MenuItem o2) {
|
||||
return o1.getLabel().compareTo(o2.getLabel());
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void addTreeItem(List<MenuItem> list, DefaultTreeNode<?> treeNode) {
|
||||
MTreeNode mNode = (MTreeNode) treeNode.getData();
|
||||
if (!mNode.isLeaf())
|
||||
return;
|
||||
|
||||
MenuItem item = new MenuItem();
|
||||
item.setLabel(mNode.getName());
|
||||
item.setDescription(mNode.getDescription());
|
||||
item.setImage(mNode.getImagePath());
|
||||
item.setData(treeNode);
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
private boolean isFolder(Treeitem treeItem) {
|
||||
List<Component> list = treeItem.getChildren();
|
||||
for (Component c : list) {
|
||||
if (c instanceof Treechildren && ((Treechildren)c).getChildren().size() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addTreeItem(List<MenuItem> list, Treeitem treeItem) {
|
||||
if (isFolder(treeItem))
|
||||
return;
|
||||
|
||||
MenuItem item = new MenuItem();
|
||||
item.setLabel(getLabel(treeItem));
|
||||
item.setDescription(treeItem.getTooltiptext());
|
||||
|
||||
String image = getImage(treeItem);
|
||||
if (image == null || image.length() == 0)
|
||||
{
|
||||
image = ThemeManager.getThemeResource("images/Folder16.png");
|
||||
}
|
||||
item.setImage(image);
|
||||
item.setData(treeItem);
|
||||
list.add(item);
|
||||
item.setType((String) treeItem.getAttribute("menu.type"));
|
||||
}
|
||||
|
||||
private String getLabel(Treeitem treeItem) {
|
||||
String label = treeItem.getLabel();
|
||||
if (label == null || label.trim().length() == 0)
|
||||
{
|
||||
if (treeItem.getTreerow().getFirstChild().getFirstChild() != null &&
|
||||
treeItem.getTreerow().getFirstChild().getFirstChild() instanceof LabelElement)
|
||||
{
|
||||
LabelElement element = (LabelElement) treeItem.getTreerow().getFirstChild().getFirstChild();
|
||||
label = element.getLabel();
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private String getImage(Treeitem treeItem) {
|
||||
String image = treeItem.getImage();
|
||||
if (image == null || image.trim().length() == 0)
|
||||
{
|
||||
if (treeItem.getTreerow().getFirstChild().getFirstChild() != null &&
|
||||
treeItem.getTreerow().getFirstChild().getFirstChild() instanceof LabelImageElement)
|
||||
{
|
||||
LabelImageElement element = (LabelImageElement) treeItem.getTreerow().getFirstChild().getFirstChild();
|
||||
image = element.getImage();
|
||||
}
|
||||
}
|
||||
return image != null ? image.intern() : null;
|
||||
}
|
||||
|
||||
public void create(Component parent) {
|
||||
refreshModel();
|
||||
|
||||
layout = new Vlayout();
|
||||
layout.setHeight("100%");
|
||||
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.setEmptyMessage(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "FindZeroRecords")));
|
||||
listbox.setStyle("border: none");
|
||||
listbox.setWidth("500px");
|
||||
layout.appendChild(listbox);
|
||||
listbox.setItemRenderer(new MenuItemRenderer());
|
||||
listbox.addEventListener(Events.ON_SELECT, this);
|
||||
listbox.addEventListener(ON_POST_SELECT_TREEITEM_EVENT, this);
|
||||
ListHead listhead = new ListHead();
|
||||
listbox.appendChild(listhead);
|
||||
Listheader listheader = new Listheader();
|
||||
listhead.appendChild(listheader);
|
||||
listheader = new Listheader();
|
||||
listheader.setWidth("32px");
|
||||
listhead.appendChild(listheader);
|
||||
|
||||
layout.addEventListener(ON_SEARCH_ECHO, this);
|
||||
layout.addEventListener(ON_LOAD_MORE, this);
|
||||
updateListboxModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) throws Exception {
|
||||
if (Events.ON_SELECT.equals(event.getName())) {
|
||||
ListItem selected = listbox.getSelectedItem();
|
||||
if (selected == null) return;
|
||||
onSelect(selected, Boolean.FALSE);
|
||||
} else if (event.getName().equals(ON_POST_SELECT_TREEITEM_EVENT)) {
|
||||
onPostSelectTreeitem((Boolean) event.getData());
|
||||
} else if (Events.ON_CLICK.equals(event.getName())) {
|
||||
if (event.getTarget() instanceof ListItem) {
|
||||
ListItem item = (ListItem) event.getTarget();
|
||||
Long onSelect = (Long) item.getAttribute(ONSELECT_TIMESTAMP);
|
||||
if (onSelect == null) {
|
||||
onSelect(item, Boolean.FALSE);
|
||||
} else if (System.currentTimeMillis() - onSelect.longValue() > 1000) {
|
||||
onSelect(item, Boolean.FALSE);
|
||||
}
|
||||
} else if (event.getTarget() instanceof Toolbarbutton) {
|
||||
ListItem item = null;
|
||||
Component parent = event.getTarget();
|
||||
while (parent != null) {
|
||||
if (parent instanceof ListItem) {
|
||||
item = (ListItem) parent;
|
||||
break;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (item != null) {
|
||||
onSelect(item, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
} else if (event.getName().equals(ON_SEARCH_ECHO)) {
|
||||
onSearchEcho((String) event.getData());
|
||||
} else if (event.getName().equals(ON_LOAD_MORE)) {
|
||||
loadMore();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSelect(ListItem selected, Boolean newRecord) {
|
||||
MenuItem item = selected.getValue();
|
||||
if (item == null) return;
|
||||
if ("...".equals(item.getType())) {
|
||||
selected.setAttribute(ONSELECT_TIMESTAMP, System.currentTimeMillis());
|
||||
Clients.showBusy(selected, null);
|
||||
Events.echoEvent(ON_LOAD_MORE, layout, null);
|
||||
} else {
|
||||
selectTreeitem(item.getData(), newRecord);
|
||||
selected.setAttribute(ONSELECT_TIMESTAMP, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMore() {
|
||||
ListModel<MenuItem> listModel = listbox.getModel();
|
||||
ListModelList<MenuItem> lml = (ListModelList<MenuItem>) listModel;
|
||||
lml.remove(lml.size()-1);
|
||||
List<MenuItem> subList = fullModel.subList(50, fullModel.size());
|
||||
lml.addAll(subList);
|
||||
fullModel = null;
|
||||
listbox.setSelectedIndex(50);
|
||||
Clients.scrollIntoView(listbox.getSelectedItem());
|
||||
}
|
||||
|
||||
private void selectTreeitem(Object node, Boolean newRecord) {
|
||||
if (Executions.getCurrent().getAttribute(listbox.getUuid()+".selectTreeitem") != null)
|
||||
return;
|
||||
|
||||
Treeitem treeItem = null;
|
||||
if (node == null) {
|
||||
return;
|
||||
} else if (node instanceof Treeitem) {
|
||||
treeItem = (Treeitem) node;
|
||||
} else {
|
||||
DefaultTreeNode<?> sNode = (DefaultTreeNode<?>) node;
|
||||
int[] path = tree.getModel().getPath(sNode);
|
||||
treeItem = tree.renderItemByPath(path);
|
||||
tree.setSelectedItem(treeItem);
|
||||
}
|
||||
if (treeItem != null) {
|
||||
Executions.getCurrent().setAttribute(listbox.getUuid()+".selectTreeitem", Boolean.TRUE);
|
||||
|
||||
select(treeItem);
|
||||
Events.postEvent(ON_POST_SELECT_TREEITEM_EVENT, listbox, newRecord);
|
||||
}
|
||||
}
|
||||
|
||||
private void select(Treeitem selectedItem) {
|
||||
Treeitem parent = selectedItem.getParentItem();
|
||||
while (parent != null) {
|
||||
if (!parent.isOpen())
|
||||
parent.setOpen(true);
|
||||
|
||||
parent = parent.getParentItem();
|
||||
}
|
||||
selectedItem.getTree().setSelectedItem(selectedItem);
|
||||
}
|
||||
|
||||
private void onPostSelectTreeitem(Boolean newRecord) {
|
||||
Event event = null;
|
||||
if (tree.getSelectedItem().getTreerow().getFirstChild().getFirstChild() instanceof A)
|
||||
{
|
||||
event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow().getFirstChild().getFirstChild(), newRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
event = new Event(Events.ON_CLICK, tree.getSelectedItem().getTreerow(), newRecord);
|
||||
}
|
||||
Events.postEvent(event);
|
||||
}
|
||||
|
||||
public void search(String value) {
|
||||
listbox.setVisible(false);
|
||||
Events.echoEvent(ON_SEARCH_ECHO, layout, value);
|
||||
}
|
||||
|
||||
public void onSearchEcho(String value) {
|
||||
ListModelList<MenuItem> newModel = null;
|
||||
if (Util.isEmpty(value)) {
|
||||
newModel = model;
|
||||
} else {
|
||||
@SuppressWarnings("unchecked")
|
||||
ListSubModel<MenuItem> subModel = (ListSubModel<MenuItem>) ListModels.toListSubModel(model, new MenuListComparator(value), model.size());
|
||||
newModel = (ListModelList<MenuItem>) subModel.getSubModel(null, -1);
|
||||
}
|
||||
updateListboxModel(newModel);
|
||||
listbox.setVisible(true);
|
||||
}
|
||||
|
||||
private void updateListboxModel(ListModelList<MenuItem> newModel) {
|
||||
fullModel = null;
|
||||
if (newModel.size() > 50) {
|
||||
List<MenuItem> list = newModel.getInnerList();
|
||||
List<MenuItem> subList = list.subList(0, 50);
|
||||
fullModel = newModel;
|
||||
newModel = new ListModelList<MenuItem>(subList.toArray(new MenuItem[0]));
|
||||
MenuItem more = new MenuItem();
|
||||
more.setLabel("...");
|
||||
more.setType("...");
|
||||
newModel.add(more);
|
||||
}
|
||||
listbox.setModel(newModel);
|
||||
}
|
||||
|
||||
private class MenuListComparator implements Comparator<MenuItem> {
|
||||
|
||||
private String compare;
|
||||
|
||||
private MenuListComparator(String compare) {
|
||||
this.compare = compare;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(MenuItem o1, MenuItem o2) {
|
||||
compare = compare.toLowerCase().trim();
|
||||
boolean match = false;
|
||||
if (compare.length() < 3)
|
||||
{
|
||||
match = o2.getLabel().toLowerCase().startsWith(compare);
|
||||
}
|
||||
else
|
||||
{
|
||||
match = o2.getLabel().toLowerCase().contains(compare);
|
||||
}
|
||||
return match ? 0 : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public MenuItem selectPrior() {
|
||||
int i = listbox.getSelectedIndex();
|
||||
if (i > 0) {
|
||||
listbox.setSelectedIndex(i-1);
|
||||
ListItem selected = listbox.getSelectedItem();
|
||||
if (selected == null) return null;
|
||||
Clients.scrollIntoView(selected);
|
||||
MenuItem item = selected.getValue();
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MenuItem selectNext() {
|
||||
int i = listbox.getSelectedIndex();
|
||||
if (i < 0 && listbox.getItemCount() > 0) {
|
||||
listbox.setSelectedIndex(0);
|
||||
ListItem selected = listbox.getSelectedItem();
|
||||
if (selected == null) return null;
|
||||
MenuItem item = selected.getValue();
|
||||
return item;
|
||||
} else if (i+1 < listbox.getItemCount()) {
|
||||
listbox.setSelectedIndex(i+1);
|
||||
ListItem selected = listbox.getSelectedItem();
|
||||
if (selected == null) return null;
|
||||
MenuItem item = selected.getValue();
|
||||
if (item == null) return null;
|
||||
if ("...".equals(item.getType())) {
|
||||
onSelect(selected, Boolean.FALSE);
|
||||
}
|
||||
Clients.scrollIntoView(selected);
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean onOk(Textbox textbox) {
|
||||
String text = textbox.getText();
|
||||
if (Util.isEmpty(text))
|
||||
return false;
|
||||
text = text.toLowerCase();
|
||||
ListItem exact = null;
|
||||
ListItem firstStart = null;
|
||||
int count = listbox.getItemCount();
|
||||
for(int i = 0; i < count; i++) {
|
||||
ListItem item = listbox.getItemAtIndex(i);
|
||||
String label = item.getLabel();
|
||||
if (Util.isEmpty(label)) continue;
|
||||
if (label.equalsIgnoreCase(text)) {
|
||||
exact = item;
|
||||
break;
|
||||
} else if (firstStart == null && label.toLowerCase().startsWith(text) && text.length() >= 3) {
|
||||
firstStart = item;
|
||||
}
|
||||
}
|
||||
if (exact != null) {
|
||||
textbox.setText(exact.getLabel());
|
||||
onSelect(exact, false);
|
||||
return true;
|
||||
} else if (firstStart != null) {
|
||||
textbox.setText(firstStart.getLabel());
|
||||
onSelect(firstStart, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class MenuItemRenderer implements ListitemRenderer<MenuItem>, ListitemRendererExt {
|
||||
@Override
|
||||
public Listitem newListitem(org.zkoss.zul.Listbox listbox) {
|
||||
return new ListItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Listcell newListcell(Listitem item) {
|
||||
return new Listcell(item.getLabel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControls() {
|
||||
return ListitemRendererExt.DETACH_ON_RENDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(Listitem item, MenuItem data, int index)
|
||||
throws Exception {
|
||||
Listcell cell = new Listcell(data.getLabel(), data.getImage());
|
||||
item.appendChild(cell);
|
||||
cell.setTooltip(data.getDescription());
|
||||
item.setValue(data);
|
||||
item.addEventListener(Events.ON_CLICK, MenuSearchController.this);
|
||||
|
||||
cell = new Listcell();
|
||||
item.appendChild(cell);
|
||||
boolean isWindow = data.getType() != null && data.getType().equals("window");
|
||||
if (isWindow) {
|
||||
Toolbarbutton newBtn = new Toolbarbutton(null, ThemeManager.getThemeResource("images/New16.png"));
|
||||
newBtn.addEventListener(Events.ON_CLICK, MenuSearchController.this);
|
||||
newBtn.setSclass("fav-new-btn");
|
||||
newBtn.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "New")));
|
||||
cell.appendChild(newBtn);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.adempiere.webui.panel;
|
||||
|
||||
import org.adempiere.webui.apps.GlobalSearch;
|
||||
import org.adempiere.webui.apps.MenuSearchController;
|
||||
import org.adempiere.webui.component.Panel;
|
||||
import org.adempiere.webui.theme.ThemeManager;
|
||||
import org.adempiere.webui.window.AboutWindow;
|
||||
|
@ -49,6 +51,8 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
protected LabelImageElement btnMenu;
|
||||
protected Popup popMenu;
|
||||
|
||||
private MenuTreePanel menuTreePanel;
|
||||
|
||||
public HeaderPanel()
|
||||
{
|
||||
super();
|
||||
|
@ -62,9 +66,9 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
image.addEventListener(Events.ON_CLICK, this);
|
||||
image.setStyle("cursor: pointer;");
|
||||
|
||||
createSearchPanel();
|
||||
|
||||
createPopupMenu();
|
||||
|
||||
createSearchPanel();
|
||||
|
||||
btnMenu = (LabelImageElement) getFellow("menuButton");
|
||||
btnMenu.setLabel(Util.cleanAmp(Msg.getMsg(Env.getCtx(),"Menu")));
|
||||
|
@ -74,7 +78,7 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
protected void createPopupMenu() {
|
||||
popMenu = new Popup();
|
||||
popMenu.setId("menuTreePopup");
|
||||
new MenuTreePanel(popMenu);
|
||||
menuTreePanel = new MenuTreePanel(popMenu);
|
||||
popMenu.setSclass("desktop-menu-popup");
|
||||
popMenu.setHeight("90%");
|
||||
popMenu.setWidth("600px");
|
||||
|
@ -82,11 +86,11 @@ public class HeaderPanel extends Panel implements EventListener<Event>
|
|||
}
|
||||
|
||||
protected void createSearchPanel() {
|
||||
MenuSearchPanel menuSearchPanel = new MenuSearchPanel(this);
|
||||
GlobalSearch globalSearch = new GlobalSearch(new MenuSearchController(menuTreePanel.getMenuTree()));
|
||||
Component stub = getFellow("menuLookup");
|
||||
stub.getParent().insertBefore(menuSearchPanel, stub);
|
||||
stub.getParent().insertBefore(globalSearch, stub);
|
||||
stub.detach();
|
||||
menuSearchPanel.setId("menuLookup");
|
||||
globalSearch.setId("menuLookup");
|
||||
}
|
||||
|
||||
public void onEvent(Event event) throws Exception {
|
||||
|
|
|
@ -1376,6 +1376,18 @@ tbody.z-grid-empty-body td {
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
tbody.z-listbox-empty-body td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.z-listbox-body .z-listcell {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.z-listbox-autopaging .z-listcell-cnt {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
<%-- notification message --%>
|
||||
.z-notification .z-notification-cl,
|
||||
.z-notification .z-notification-cnt {
|
||||
|
|
Loading…
Reference in New Issue