IDEMPIERE-6105 Implement recently access menu items (#2311)

This commit is contained in:
hengsin 2024-04-20 10:55:41 +08:00 committed by Carlos Ruiz
parent e4e27f4ee5
commit 0eabdd9d08
7 changed files with 196 additions and 78 deletions

View File

@ -219,6 +219,7 @@ public class SystemIDs
public final static int WINDOW_LOT = 257;
public final static int WINDOW_MATERIAL_RECEIPT = 184;
public final static int WINDOW_MATERIALTRANSACTIONS_INDIRECTUSER = 223;
public final static int WINDOW_MENU = 105;
public final static int WINDOW_MY_REQUESTS = 237;
public final static int WINDOW_NOTICE = 193;
public final static int WINDOW_PAYMENTS_INTO_BATCH = 200031;

View File

@ -101,8 +101,9 @@ public class GlobalSearch extends Div implements EventListener<Event> {
bandbox.setCtrlKeys("#up#down");
bandbox.addEventListener(Events.ON_CTRL_KEY, this);
bandbox.addEventListener(Events.ON_FOCUS, e -> {
if (!bandbox.isOpen())
bandbox.setOpen(true);
bandbox.setOpen(true);
if (Util.isEmpty(bandbox.getValue(), true) && tabbox.getSelectedIndex() == 0)
menuController.updateRecentItems();
});
Bandpopup popup = new Bandpopup();
@ -224,6 +225,8 @@ public class GlobalSearch extends Div implements EventListener<Event> {
}
}
} else if (event.getName().equals(Events.ON_SELECT)) {
if (tabbox.getSelectedIndex() == 0)
menuController.updateRecentItems();
String value = (String) bandbox.getAttribute(LAST_ONCHANGING_ATTR);
if (value == null) {
value = bandbox.getValue();
@ -254,7 +257,7 @@ public class GlobalSearch extends Div implements EventListener<Event> {
* Handle client info event from browser.
*/
public void onClientInfo() {
ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-50);
ZKUpdateUtil.setWindowHeightX(bandbox.getDropdown(), ClientInfo.get().desktopHeight-100);
}
/**

View File

@ -31,8 +31,11 @@ import org.adempiere.webui.util.TreeNodeAction;
import org.adempiere.webui.util.TreeUtils;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.compiere.model.MMenu;
import org.compiere.model.MPreference;
import org.compiere.model.MToolBarButtonRestrict;
import org.compiere.model.MTreeNode;
import org.compiere.model.Query;
import org.compiere.model.SystemIDs;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.util.Util;
@ -68,6 +71,9 @@ import org.zkoss.zul.impl.LabelImageElement;
*/
public class MenuSearchController implements EventListener<Event>{
/** Initial number of menu items loaded into listbox */
private static final int INITIAL_LOADING_SIZE = 50;
/** Component attribute to hold reference of {@link MTreeNode} **/
public static final String M_TREE_NODE_ATTR = "MTreeNode";
@ -77,7 +83,7 @@ public class MenuSearchController implements EventListener<Event>{
private static final String Z_ICON_STAR = "z-icon-star";
/** Event echo from {@link #search(String)} to initiate search action **/
private static final String ON_SEARCH_ECHO_EVENT = "onSearchEcho";
/** Event to load all menu items into {@link #listbox}. Default is to load the first 50 only. **/
/** Event to load all menu items into {@link #listbox}. Default is to load the first {@link #INITIAL_LOADING_SIZE} only. **/
private static final String ON_LOAD_MORE_EVENT = "onLoadMore";
/** {@link Listitem} attribute to store the last timestamp of ON_CLICK or ON_SELECT event **/
private static final String ONSELECT_TIMESTAMP_ATTR = "onselect.timestamp";
@ -100,20 +106,58 @@ public class MenuSearchController implements EventListener<Event>{
private String highlightText = null;
/** List of recently access menu items (AD_Menu_ID) */
private List<String> recentMenuItemIds = new ArrayList<>();
/** Event post from {@link #selectTreeitem(Object, Boolean)} **/
private static final String ON_POST_SELECT_TREEITEM_EVENT = "onPostSelectTreeitem";
/**
* @param tree
* @param tree usually the tree instance from {@link}
*/
public MenuSearchController(Tree tree) {
this.tree = tree;
}
/**
* Load recently access menu items
*/
private List<String> loadRecentItems() {
List<String> recents = new ArrayList<String>();
int AD_User_ID = Env.getAD_User_ID(Env.getCtx());
int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx());
int AD_Org_ID = 0;
String attribute = AD_Role_ID+"|RecentMenuItems";
Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null);
MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first();
if (preference != null) {
String[] recentItems = preference.getValue().split("[,]");
for (String recentItem : recentItems) {
recents.add(recentItem);
}
}
return recents;
}
/**
* If there are changes in the recent menu items for user, reload and update menu items model
*/
public void updateRecentItems() {
List<String> recents = loadRecentItems();
if (!recents.equals(recentMenuItemIds)) {
recentMenuItemIds = recents;
sortMenuItemModel();
moveRecentItems();
if (fullModel != null)
updateListboxModel(model);
}
}
/**
* Populate {@link #model} from {@link #tree}
*/
public void refreshModel() {
recentMenuItemIds = loadRecentItems();
final List<MenuItem> list = new ArrayList<MenuItem>();
if (tree.getModel() == null) {
TreeUtils.traverse(tree, new TreeItemAction() {
@ -130,8 +174,15 @@ public class MenuSearchController implements EventListener<Event>{
});
}
model = new ListModelList<MenuItem>(list, true);
model.sort(new Comparator<MenuItem>() {
sortMenuItemModel();
moveRecentItems();
}
/**
* Sort menu items model in alphabetical order
*/
private void sortMenuItemModel() {
model.sort(new Comparator<MenuItem>() {
@Override
public int compare(MenuItem o1, MenuItem o2) {
return o1.getLabel().compareTo(o2.getLabel());
@ -139,6 +190,35 @@ public class MenuSearchController implements EventListener<Event>{
}, true);
}
/**
* Move the 7 most recently access menu items to the top of menu items model
*/
private void moveRecentItems() {
if (recentMenuItemIds.size() > 0) {
List<MenuItem> recents = new ArrayList<MenuItem>();
for(String id : recentMenuItemIds) {
for(int i = 0; i < model.getSize(); i++) {
if (model.get(i).getData() instanceof Treeitem ti) {
if (ti.getValue() instanceof String tis) {
if (tis.equals(id)) {
recents.add(model.get(i));
break;
}
}
}
}
}
if (recents.size() > 0) {
for (MenuItem mi : recents) {
model.remove(mi);
}
for(int i = recents.size()-1; i >= 0; i--) {
model.add(0, recents.get(i));
}
}
}
}
/**
* Add treeNode to list
* @param list
@ -373,16 +453,16 @@ public class MenuSearchController implements EventListener<Event>{
/**
* Load {@link #fullModel} to {@link #listbox}.
* Only first 50 loaded to {@link #listbox} initially.
* Only first {@link #INITIAL_LOADING_SIZE} loaded to {@link #listbox} initially.
*/
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());
List<MenuItem> subList = fullModel.subList(INITIAL_LOADING_SIZE, fullModel.size());
lml.addAll(subList);
fullModel = null;
listbox.setSelectedIndex(50);
listbox.setSelectedIndex(INITIAL_LOADING_SIZE);
Clients.scrollIntoView(listbox.getSelectedItem());
}
@ -430,8 +510,8 @@ public class MenuSearchController implements EventListener<Event>{
}
/**
* Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event.
* Post ON_CLICK event to link ({@link A} or {@link Treerow}).
* Handle {@link #ON_POST_SELECT_TREEITEM_EVENT} event.<br/>
* Post ON_CLICK event to link ({@link A} or {@link Treerow}, handle in {@link AbstractMenuPanel}).
* @param newRecord
*/
private void onPostSelectTreeitem(Boolean newRecord) {
@ -441,9 +521,10 @@ public class MenuSearchController implements EventListener<Event>{
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);
}
@ -474,15 +555,15 @@ public class MenuSearchController implements EventListener<Event>{
/**
* Update {@link #listbox} with newModel.
* If newModel has > 50 items, only first 50 is loaded into {@link #listbox}.
* If newModel has > {@link #INITIAL_LOADING_SIZE} items, only first {@link #INITIAL_LOADING_SIZE} is loaded into {@link #listbox}.
* User has to click the load more link (...) to load the rest of the items into {@link #listbox}.
* @param newModel
*/
private void updateListboxModel(ListModelList<MenuItem> newModel) {
fullModel = null;
if (newModel.size() > 50) {
if (newModel.size() > INITIAL_LOADING_SIZE) {
List<MenuItem> list = newModel.getInnerList();
List<MenuItem> subList = list.subList(0, 50);
List<MenuItem> subList = list.subList(0, INITIAL_LOADING_SIZE);
fullModel = newModel;
newModel = new ListModelList<MenuItem>(subList.toArray(new MenuItem[0]));
MenuItem more = new MenuItem();

View File

@ -15,19 +15,12 @@ import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import org.adempiere.util.Callback;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.adwindow.ADTabpanel;
import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.desktop.AbstractDesktop;
import org.adempiere.webui.desktop.FavouriteController;
import org.adempiere.webui.desktop.IDesktop;
import org.adempiere.webui.exception.ApplicationException;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.compiere.model.MMenu;
import org.compiere.model.MQuery;
import org.compiere.model.MTable;
import org.compiere.model.MToolBarButtonRestrict;
import org.compiere.model.MTreeNode;
import org.compiere.util.CLogger;
@ -331,27 +324,7 @@ public class FavoriteSimpleTreeModel extends SimpleTreeModel implements EventLis
{
try
{
MMenu menu = (MMenu) MTable.get(Env.getCtx(), MMenu.Table_ID).getPO(menuID, null);
IDesktop desktop = SessionManager.getAppDesktop();
if (desktop instanceof AbstractDesktop)
((AbstractDesktop)desktop).setPredefinedContextVariables(menu.getPredefinedContextVariables());
MQuery query = new MQuery("");
query.addRestriction("1=2");
query.setRecordCount(0);
SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback<ADWindow>() {
@Override
public void onCallback(ADWindow result)
{
if (result == null)
return;
result.getADWindowContent().onNew();
ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel();
adtabpanel.focusToFirstEditor(false);
}
});
SessionManager.getAppDesktop().onNewRecord(menuID);
}
catch (Exception e)
{

View File

@ -16,15 +16,24 @@ package org.adempiere.webui.desktop;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.adempiere.util.Callback;
import org.adempiere.webui.AdempiereWebUI;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.adwindow.ADTabpanel;
import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.component.Window;
import org.adempiere.webui.event.DialogEvents;
import org.adempiere.webui.exception.ApplicationException;
import org.adempiere.webui.part.AbstractUIPart;
import org.adempiere.webui.session.SessionManager;
import org.compiere.model.MMenu;
import org.compiere.model.MPreference;
import org.compiere.model.MQuery;
import org.compiere.model.Query;
import org.compiere.model.SystemIDs;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.zkoss.zk.ui.Component;
@ -58,8 +67,8 @@ public abstract class AbstractDesktop extends AbstractUIPart implements IDesktop
/**
* Event listener for menu item selection.<br/>
* Identifies the action associated with the selected
* menu item and acts accordingly.
* Identifies the action associated with the selected menu item and acts accordingly.<br/>
* Event from favourite panel, global search and application menu tree will be routed here.
*
* @param menuId Identifier for the selected menu item
*
@ -110,8 +119,78 @@ public abstract class AbstractDesktop extends AbstractUIPart implements IDesktop
{
setPredefinedContextVariables(null);
}
updateRecentMenuItem(menuId);
}
/**
* Open AD window in new record mode.<br/>
* Call by global search, application menu tree and favourite panel.
* @param menuId
*/
@Override
public void onNewRecord(int menuId) {
MMenu menu = new MMenu(Env.getCtx(), menuId, null);
setPredefinedContextVariables(menu.getPredefinedContextVariables());
MQuery query = new MQuery("");
query.addRestriction("1=2");
query.setRecordCount(0);
SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback<ADWindow>() {
@Override
public void onCallback(ADWindow result) {
if(result == null)
return;
result.getADWindowContent().onNew();
ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel();
adtabpanel.focusToFirstEditor(false);
}
});
updateRecentMenuItem(menuId);
}
/**
* Perform asynchronous update of recent menu items preference for user
* @param menuId
*/
protected void updateRecentMenuItem(int menuId) {
Runnable runnable = () -> {
int AD_User_ID = Env.getAD_User_ID(Env.getCtx());
int AD_Role_ID = Env.getAD_Role_ID(Env.getCtx());
int AD_Org_ID = 0;
String attribute = AD_Role_ID+"|RecentMenuItems";
Query query = new Query(Env.getCtx(), MPreference.Table_Name, "PreferenceFor=? AND Attribute=? AND AD_Org_ID=? AND AD_User_ID=? AND AD_Window_ID=?", null);
MPreference preference = query.setClient_ID().setParameters("W", attribute, AD_Org_ID, AD_User_ID, SystemIDs.WINDOW_MENU).first();
if (preference == null) {
preference = new MPreference(Env.getCtx(), 0, null);
preference.setAD_Org_ID(AD_Org_ID);
preference.setPreferenceFor("W");
preference.setAttribute(attribute);
preference.setAD_User_ID(AD_User_ID);
preference.setValue(Integer.toString(menuId));
preference.setAD_Window_ID(SystemIDs.WINDOW_MENU);
preference.saveEx();
} else {
String recentItemValue = preference.getValue();
List<String> itemList = new ArrayList<String>();
String[] recentItemValues = recentItemValue.split("[,]");
String menuIdValue = Integer.toString(menuId);
itemList.add(menuIdValue);
for (int i = 0; itemList.size() < 7 && i < recentItemValues.length; i++) {
if (!recentItemValues[i].equals(menuIdValue))
itemList.add(recentItemValues[i]);
}
recentItemValue = itemList.stream().collect(Collectors.joining(","));
preference.setValue(recentItemValue);
preference.saveEx();
}
};
Executions.schedule(getComponent().getDesktop(), e -> {
runnable.run();
}, new Event("onUpdateRecentMenuItem"));
}
/**
* @return {@link ClientInfo}
*/

View File

@ -49,11 +49,17 @@ public interface IDesktop extends UIPart {
public ClientInfo getClientInfo();
/**
*
* Launch menu item
* @param nodeId
*/
public void onMenuSelected(int nodeId);
/**
* Launch AD Window in new record mode
* @param menuId
*/
public void onNewRecord(int menuId);
/**
*
* @param window

View File

@ -20,18 +20,12 @@ import java.util.Collection;
import java.util.Enumeration;
import java.util.Properties;
import org.adempiere.util.Callback;
import org.adempiere.webui.adwindow.ADTabpanel;
import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.apps.MenuSearchController;
import org.adempiere.webui.desktop.AbstractDesktop;
import org.adempiere.webui.desktop.IDesktop;
import org.adempiere.webui.exception.ApplicationException;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.compiere.model.MMenu;
import org.compiere.model.MQuery;
import org.compiere.model.MToolBarButtonRestrict;
import org.compiere.model.MTree;
import org.compiere.model.MTreeNode;
@ -291,7 +285,8 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener<E
}
/**
* Handle onClick and onOk event
* Handle onClick and onOk event for menu tree item.<br/>
* The event from global search and application menu tree will be routed to here.
* @param comp
* @param eventData
*/
@ -361,31 +356,11 @@ public abstract class AbstractMenuPanel extends Panel implements EventListener<E
private void onNewRecord(Treeitem selectedItem) {
try
{
if (getParent() instanceof Popup) {
((Popup)getParent()).close();
}
int menuId = Integer.parseInt((String)selectedItem.getValue());
MMenu menu = new MMenu(Env.getCtx(), menuId, null);
IDesktop desktop = SessionManager.getAppDesktop();
if (desktop instanceof AbstractDesktop)
((AbstractDesktop)desktop).setPredefinedContextVariables(menu.getPredefinedContextVariables());
MQuery query = new MQuery("");
query.addRestriction("1=2");
query.setRecordCount(0);
if (getParent() instanceof Popup) {
((Popup)getParent()).close();
}
SessionManager.getAppDesktop().openWindow(menu.getAD_Window_ID(), query, new Callback<ADWindow>() {
@Override
public void onCallback(ADWindow result) {
if(result == null)
return;
result.getADWindowContent().onNew();
ADTabpanel adtabpanel = (ADTabpanel) result.getADWindowContent().getADTab().getSelectedTabpanel();
adtabpanel.focusToFirstEditor(false);
}
});
SessionManager.getAppDesktop().onNewRecord(menuId);
}
catch (Exception e)
{