RichFaces 4 dynamic contextMenu

Version 2

    If you ever happen to migrate the old RF 3 UI to the new RF 4 components you would discover that a lot has changed, especially the handling of contextMenus: among other things the development of dynamic parts of menu has been abandoned. I could not find any match in much praised PrimeFaces in order to provide the same UX to my customers (besides some strange manipulations of the contextMenu object prototypes), so i decided to investigate further on dynamic rendering and macros substitutions in contextMenus. There are several issues still open concerning different aspect of menu flexibility of components: [RF-11842] Support for macrosubstitutions in the contextMenu - JBoss Issue Tracker, [RF-10078] Client-side templating - JBoss Issue Tracker, [RF-11973] rich:contextMenu - after ajax re-render of table contextMenu no longer works - JBoss Issue Tracker .

     

    I want to show you how to implement macro substitutions in contextMenu attached to extended data table. Note that the provided solution only works after [RF-14035] Showcase context menu: JS error in console - JBoss Issue Tracker and [RF-14023] rich:contextMenu - on ajax re-render the destroy function doesn`t clean up properly - JBoss Issue Tracker  are fixed.

    The interpolations works somewhat similar to JsRender: we have a contextMenu template attached to the document body, data provided for every single row in the table, and we do the manipulation of the DOM at mouse events (no keyboard- or multiple selections are supported). Since RF4 is based on jQuery, we will use it for manipulations:

     

    1. First we store the html template at the document load (put it somewhere where AJAX re-render can not reach it, otherwise you get some weird effects):

    <script type="text/javascript">
                $(document).ready(function() {
                    var cm = $('div.rf-ctx-lbl');
                    if(cm) {
                        jQuery.data(document.body, "cm-tmpl", cm.html());
                    }
                });
    </script>
    
    
    

    Then we go on in order of invocation:

     

    2. On mouse up event we attach the row data (make it current data set) to the body as well. Lets say the cars with the prices greater that 50K can be removed from the items list:

    <rich:extendedDataTable .....
      onrowmouseup="$(document.body).data('cm-param', {'model': '#{car.model}', 'vin': '#{car.vin}', 'displayDelete': '#{car.price > 50000 ? 'block' : 'none'}'});"
    
    
    

    Here we create JSON-style map with the actual row data and define the style (block/none) for items which could be hidden. Note: IE (at least 9) does not accept the '{yyy}'-looking style attributes, you have to switch to styleClass manipulations if you are targeting an IE.

     

    3. Just before changing the selection on the client we get notified by EDT, destroy the current conextMenu instance (that`s why we need a tidy cleanup of all registered listeners: the menu is not shown, we just dispose it) and replace all placeholders by current row values:

     

    onbeforeselectionchange="#{rich:component('contextMenu')}.destroy(); var cm = $('div.rf-ctx-lbl'); cm.html(RichFaces.interpolate($(document.body).data('cm-tmpl'), $(document.body).data('cm-param')));"

     

    The jQuery will evaluate provided HTML and run all JavaScript functions (in this case

    new RichFaces.ui.ContextMenu("form:contextMenu",{"target":"form:table","mode":"ajax"} ).initiateGroups();
    
    
    

    )

     

    4. Since we killed the contextMenu istance attached to EDT, we have to propagate the original event manually and trigger the popup:

     

    onselectionchange="var cm = #{rich:component('contextMenu')}; if(event.type==cm.options.showEvent) {cm.show(event);}"

     

    5.  At almost every AJAX request the EDT selection and our saved row data will become misaligned, therefore we reset the current selection on EDT-ready event and the user can start the selection over:

     

    onready="var t = #{rich:component('table')}; t.deselectRow(t.activeIndex);"

     

     

    We move on to the contextMenu, the template looks like (delete functions are not entirely implemented):

    <rich:contextMenu id="contextMenu" target="table" mode="ajax">
        <rich:menuItem label="View {model}" execute="@this" render="popupContent" oncomplete="#{rich:component('popup')}.show();"
                               icon="/images/icons/open.gif">
                <a4j:param name="vin" assignTo="#{extTableSelectionBean.selectedVin}" value="{vin}"/>
        </rich:menuItem>
        <rich:menuItem label="Delete {vin}" acton="#" render="table" style="display: {displayDelete}"
                               icon="/images/icons/delete.gif"/>
    </rich:contextMenu>
    
    
    

    Actually we switch here from the EDT` row indexes to the business keys (vehicle identification number) and do not fully utilize the selection model of EDT, but it is just a demo an you are free to combine both (while playing with iterationStatusVar.index) in order to build up a complex use case for contextMenu. Note: we use here a4j:param instead of f:setPropertyActionListener because the RF param becomes a part of menu HTML template (through JavaScript 'parameters' attribute) and JSF listener does not.

     

    The full source code based on RF demo is located here: Context Menu Showcase

     

    Later on I will explain how to render the dynamic contextMenu with AJAX (see [RF-11973] rich:contextMenu - after ajax re-render of table contextMenu no longer works - JBoss Issue Tracker , notably proposed solutions in forum references)