-
FEATURED COMPONENTS
First time here? Check out the FAQ!
I want to create a menubar with sub menus and sub-sub menus for my application. However, the actual list of items appearing in my menus must be dynamic. I have the logic to retrieve the menu configuration at run-time from the database. However, I need to figure out an elegant way to populate the menubar with the various labels, and menuitems.
Obviously, this cannot be done with vanilla ZK. Any suggestions? Does it make sense to use the ZK API and write the logic in Java or is there a way to manipulate the content of a menubar and the various labels, menupopups and menuitems through scripting?
Your assistance is greatly appreciated.
Dave
You can use the classes from this sample app.
You'll find it in the packes de.forsthaus.common.menu..... TreeMenu + PulldownMenu
In that case the menu entries comes fom a xml-file. You must search where you can replace that logic to read from your database .
best
Stephan
Wow! I'm looking all over the source code for the specific code that you are referring to and I'm wondering if your link is pointing to the correct application. I don't even see reference to a menubar. Can you offer just a little more direction?
You must debug it.
begin here:
de.forsthaus.common.Menu.MainMenuCtrl.java
...
public void onClick$btnMainMenuChange(Event event) throws Exception {
Sorry
best
Stephan
Hi I have a solution for this, and it is working for me.
We need an interface for our data to implement it:
public interface TreeNode { public Long getId(); public String getName(); public boolean isLeaf(); public boolean isRoot(); public int getChildCount(); public TreeNode getChildAt(int childIndex); }
And a TreeModel:
public class MenuTreeModel extends AbstractTreeModel { public MenuTreeModel(TreeNode root) { super(root); } public Object getChild(Object parent, int index) { TreeNode node = (TreeNode)parent; return node.getChildAt(index); } public int getChildCount(Object parent) { TreeNode node = (TreeNode)parent; return node.getChildCount(); } public boolean isLeaf(Object node) { if(node instanceof TreeNode){ TreeNode node_ = (TreeNode)node; return node_.isLeaf(); }else{ return true; } } }
..and an extended Menubar component to do the job:
public class DynamicMenu extends Menubar{ private TreeModel _model; private MenuitemRenderer _renderer; public DynamicMenu(){ super(); } public DynamicMenu(String orient) { super(orient); } public void setModel(Object model) { if(!(model instanceof TreeModel)){ if(model instanceof MenuItem){ model=new MenuTreeModel((MenuItem)model); } } if (model != null && model instanceof TreeModel) { TreeModel tModel=(TreeModel)model; if (_model != tModel) { _model = tModel; } syncModel(); } else if (_model != null) { _model = null; } } public TreeModel getModel(){ return _model; } private void syncModel() { renderMenu(); } private void renderMenu(){ if(_model==null) return; Object node = _model.getRoot(); if(_renderer==null)_renderer=_defRend; try { renderChildren(_defRend, this, node); } catch (Throwable ex) { } finally { } } private void renderChildren(MenuitemRenderer renderer, Component parent, Object node) throws Throwable { for(int i = 0; i < _model.getChildCount(node); i++) { Object childNode = _model.getChild(node, i); if(_model.isLeaf(childNode)){ Menuitem mi= new Menuitem(); mi.setParent(parent); renderer.render(mi, childNode); } else { Menu menu=new Menu(); menu.setParent(parent); renderer.render(menu, childNode); Menupopup mp=new Menupopup(); mp.setParent(menu); renderChildren(renderer, mp, childNode); } } } private static final MenuitemRenderer _defRend = new MenuitemRenderer() { public void render(Menuitem mi, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; mi.setLabel(treeNode.getName()); mi.setId(treeNode.getId().toString()); mi.setAction("onclick: alert(this.id)"); } } public void render(Menu menu, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; menu.setLabel(treeNode.getName()); } } }; }
some configuration in the lang-addons.xml:
<!-- Dynamic menu component --> <component> <component-name>dynamicmenu</component-name> <component-class> my.zkext.menu.DynamicMenu</component-class> <extends>menubar</extends> <property> <property-name>model</property-name> <property-value></property-value> </property> </component>
and we can use it on the page, we only need to bind the root element of our tree to the new component:
<dynamicmenu id="mainMenu" autodrop="true" model="${menuDao.root}" />
It is working, but I would need more interaction with the page.
I would like to add the functionality for this component to change attributes and call actions on the page. Now it is called "mainWin", and i would like to change for example a title there <title id="mytitle"> how can I bind a Menuitem of the
I mean something like this:
<dynamicmenu id="mainMenu" forEach="${menuDao.elements}" value="${each}" > <menuitem id="${each.id}" onClick="content.value=this.id"/> </dynamicmenu>
or from program code trigger an event from menuitem:
public void render(Menuitem mi, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; mi.setLabel(treeNode.getName()); mi.setId(treeNode.getId().toString()); mi.setAction("onclick: ????"); } }
Any tips are welcome.
Best regards:
Bence
Hi
I modified DynamicMenu this way:
... private Object _selectedItem; ... private final MenuitemRenderer _defRend = new MenuitemRenderer() { public void render(Menuitem mi, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; mi.setLabel(treeNode.getName()); mi.setId(treeNode.getId().toString()); mi.addEventListener("onClick", new EventListener() { public void onEvent(Event event) throws Exception { setSelecteditem(event.getTarget().getId()); } }); } } public void setSelecteditem(Object selecteditem){ setSelectedItem(selecteditem); } public void render(Menu menu, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; menu.setLabel(treeNode.getName()); } } }; public Object getSelectedItem() { return _selectedItem; } public void setSelectedItem(Object selectedItem) { _selectedItem = selectedItem; }
...and this way the component updates the _selectedItem value with the clicked one.
But if I would like to bind it to the page like this:
<label id="label" value="${mainMenu.selectedItem}" />
I use only the zk-spring variableresolver on the page.
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
Do I need something else too? Maybe fire an Event in setSelectedItem and make the page catch it? If yes, how?
Or is there a way to extend my component and use it this way:
<dynamicmenu id="mainMenu" autodrop="true" model="${cmsService.menuDao.root}" onChange="label.value=this.selectedItem"/>
Thanks:
Bence
Hi
This is the latest working version of dynamic menu. You can check the required interfaces, and models above:
public class DynamicMenu extends Menubar { private TreeModel _model; private MenuitemRenderer _renderer; private Object _selectedItem; public DynamicMenu(){ super(); } public DynamicMenu(String orient) { super(orient); } public void setModel(Object model) { if(!(model instanceof TreeModel)){ if(model instanceof MenuItem){ model=new MenuTreeModel((MenuItem)model); } } if (model != null && model instanceof TreeModel) { TreeModel tModel=(TreeModel)model; if (_model != tModel) { _model = tModel; } syncModel(); } else if (_model != null) { _model = null; } } public TreeModel getModel(){ return _model; } private void syncModel() { renderMenu(); } private void renderMenu(){ if(_model==null) return; Object node = _model.getRoot(); if(_renderer==null)_renderer=_defRend; try { renderChildren(_defRend, this, node); } catch (Throwable ex) { } finally { } } /* * Renders the direct children for the specifed parent */ private void renderChildren(MenuitemRenderer renderer, Component parent, Object node) throws Throwable { for(int i = 0; i < _model.getChildCount(node); i++) { Object childNode = _model.getChild(node, i); if(_model.isLeaf(childNode)){ Menuitem mi= new Menuitem(); mi.setParent(parent); renderer.render(mi, childNode); } else { Menu menu=new Menu(); menu.setParent(parent); renderer.render(menu, childNode); Menupopup mp=new Menupopup(); mp.setParent(menu); renderChildren(renderer, mp, childNode); } } } private final MenuitemRenderer _defRend = new MenuitemRenderer() { public void render(Menuitem mi, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; mi.setLabel(treeNode.getName()); mi.setId(treeNode.getId().toString()); mi.addEventListener(Events.ON_CLICK, new EventListener() { public void onEvent(Event event) throws Exception { DynamicMenu.this.setSelectedItem(event.getTarget().getId()); } }); } } public void render(Menu menu, Object node){ if(node instanceof TreeNode){ TreeNode treeNode=(TreeNode)node; menu.setLabel(treeNode.getName()); } } }; public Object getSelectedItem() { return _selectedItem; } public void setSelectedItem(Object selectedItem) { _selectedItem = selectedItem; Events.postEvent(new Event(Events.ON_CLICK, this, selectedItem)); } }
usage:
<dbmenu id="mainMenu" autodrop="true" model="${menuDao.root}" onClick="label1.value=self.selectedItem;"/> <label id="label1" value="${menuDao.default}" />
You will need a getRoot() and a getDefault() method in your DAO. Root element will not be rendered (this is only the entry point of the tree), so this is why you will need to define an different Default element.
Merry Christmas!
Asked: 2009-12-07 12:43:38 +0800
Seen: 4,143 times
Last updated: Sep 29 '10