November 14th, 2011
3:29 pm
Using jQuery/ThemeRoller based themes with WordPress

Posted under Wordpress
Tags , , , , ,

It appears that there are widgets and themes which do this, and which allow dynamic theme switching of the jQuery themes. The attraction of this is that it would allow closer integration of the look and feel when using a JSF/Primefaces Java based web app called from an enclosing WordPress based site. One interesting possibility would be to tie the theme selection used for the WordPress site in with the theme selection for the Primefaces based web app.

This will need more investigation but as a starter here are a few interesting links found from initial searches :-

No Comments »

July 25th, 2011
11:35 am
Using the Primefaces p:panel component

Posted under JSF
Tags , , , ,

This post details some tips discovered whilst experimenting with p:panel.

1/ The toggle button on the right hand side of the panel is rendered with ui-state-default. This does not look attractive with some themes – it often looks like a bit of a zit due to the background on the thick plus sign icon. I noticed that the dialog component p:dialog does not render its close icon in the header with ui-state-default,  and this looks much more attractive as the surrounding highlight on the icon only shows on hover.

This can be applied to p:panel by removing the ui-state-default class from the anchor tags around the icons on the panel header. The following example jQuery performs this (in my case, the panelDivId argument is the ID of an h:paneGroup/div surrounding the panel:-

function ss_PanelFixIcons(panelDivId) {
    var escapedPanelDivId = panelDivId.replace(/:/g, ‘\\3A ‘);
    var panelIcons = jQuery(‘div#’ + escapedPanelDivId + ‘ div.ui-panel-titlebar > a.ui-panel-titlebar-icon’);
    panelIcons.removeClass(‘ui-state-default’);
}

 

2/ It would be nice to be able to explicitly expand and collapse the panel as well as to just toggle it. In fact the panel widget does expose its current state via its cfg property, although this is not publicly documented. The following example functions use this to provide explict expand, collapse, and set to passed boolean state functions. In addition a function is provided to get the current panel state:-

function ss_PanelExpand(widgetVar) {
    if (widgetVar.cfg.collapsed) {
        widgetVar.toggle();
    }
}

function ss_PanelCollapse(widgetVar) {
    if (!widgetVar.cfg.collapsed) {
        widgetVar.toggle();
    }
}

function ss_PanelSetCollapsed(widgetVar, state) {
    if (!widgetVar.cfg.collapsed == state) {
        widgetVar.toggle();
    }   
}

function ss_PanelGetCollapsed(widgetVar) {
    return widgetVar.cfg.collapsed;
}

 

3/ A query came up on the Primefaces forum on how to lazy load the panel, as it eagerly loads and the expand/collapse is all done client side. The post is available here. the solution is straightforward:-

  • Use a toggleListener to update a renderPanel property to true if the panel state from the event was visible, and false if it was hidden.
  • Then conditionally render a content h:panelGroup inside the panel, using the above property as the rendered property.
  • One issue with this is that the actual panel toggle occurs before the ajax update of the panel not after, so with the slide toggle effect on (say with toggleSpeed=”1000”), you see an empty sliding toggle first, then a non sliding ajax update. In this case it is best to turn off the slide effect with toggleSpeed=”0”. If you use another button to toggle the panel you can get around this, as the onComplete event for the button can toggle the panel (via its widget/ as per the above JavaScript), and this happens after the Ajax to render the panel. In practice you would have to somehow overlay the standard toggle button with another one of your own, or manually disable the standard one via CSS, as if you set toggleable=”false” for the panel this completely disables toggling via the widget as well as removing the toggle button.
  • It is probably not worth the trouble of trying to get sliding to work if you are lazy loading. Note that when I tried some older/slow machines the slide effect rendered poorly anyway, so this should also be born in mind.

No Comments »

March 23rd, 2011
4:08 pm
Mapping Browser Keyboard events to Element Clicks

Posted under Javascript
Tags , , ,

I wanted to do this as a way to add keyboard accelerators, mainly for enter and escape when doing editing etc.

The code was already implemented to be driven from mouse click events, so causing keyboard events to soft click particular target buttons for save and cancel was convenient as this was then completely isolated from the rest of the code, which only had to be concerned with being button driven.

I found the following issues during the implementation:-

A keypress event was fine for trapping Enter, but it did not trap escape correctly. For escape, I needed to drive from the keyup event.

My enter event was bubbling up and causing unwanted navigation. I initially tried cancelling bubbling and propagation as detailed here on Quirksmode.org:-

function doSomething(e)
{
    if (!e) var e = window.event;
    e.cancelBubble = true;
    if (e.stopPropagation) e.stopPropagation();
}

This involves catering for browser differences , as shown, as the Microsoft/IE model uses cancelBubble, whereas the W3C model uses stopPropagation. In my case, this did not solve the issue.

My simple solution was for the event handler to return false when it had trapped and handled a keyclick such as enter, and to return true otherwise. Returning false blocked any further action on the event, and was also a standard mechanism. I did not bottom out the actual problem in my case, but this solved it completely.

When implementing the solution, I used the following technique:-

  1. An object literal was created which did a reverse map from all the key codes I might want to trap, to a mnemonic name for them (such as enter, escape, leftArrow).
  2. My event handler call accepts a keyActions object literal argument where the names are the above mnemonics, and the values are the element IDs which need to be clicked when the matching key code is detected.
  3. In the event handler, I reverse lookup the key code to the mnemonic, and then look up the element Id in the passed object literal using the mnemonic.
  4. If I find an element Id, I click it and return false. If I do not find one, I return true as I am not trapping that key.
  5. I also try a lookup on the passed keyActions object using the raw key code. This means that the caller can either use a mnemonic or a key code to specify what key is handled.
  6. The event handler therefore has very little code, and does not need any loops – it just does 2 associative lookups to get the target element to click.
  7. As an additional feature, I allow an idPath key/value to be passed in keyActions. If this is present, any element value which is not prefixed with ‘#’ will be prefixed with the id path, to simplify the call site where a number of elements with a common ID prefix are specified. Elements already prefixed with ‘#’ are left untouched.

This technique would also work well when implementing global key handling attached to the <body> tag. In this case, it may be desirable to switch the global handling depending on what is open on the page. For example, there might be an in-page detail form which is opened in-place and client side using a Primefaces p:panel. In this case, the keyActions object could be switched when the detail panel is opened, and switched back when it is closed.

Another useful trick if the complexity warrants it could be to implement a stack such that detail panels on the page push their own keyActions onto the stack, perhaps inheriting the global ones and modifying them as required. When a detail level is closed, it can just pop the stack and does not need to know about the higher levels of actions on the page. To assist with this, it would be useful to be able to clone the previous level actions when adding a new level. Prototypes could be used for this, but they need constructor functions etc. and do not map well to our simple case which just uses JSON like object literals. The jQuery extend method could be a good choice for this, and is detailed here on StackOverflow by none other than Mr jQuery himself, John Resig (!)

The following example code fragment from a utility object illustrates the idea:-

var uk=uk||{}; uk.co=uk.co||{}; uk.co.salientsoft=uk.co.salientsoft||{};
uk.co.salientsoft.util= {
    clearElement: function(elementId) {
        jQuery(uk.co.salientsoft.util.escapeId(elementId)).val(”);
    },
    escapeId: function(elementId) {
        //a value tests true if it is non-null, non-blank, not NAN, not undefined, and not false
        var escapedId = (elementId) ? ((elementId.charAt(0) == ‘#’ ? ” : ‘#’)  + elementId.replace(/:/g, ‘\\3A ‘)) : ”;
        return escapedId;
    },
    keyCodeNames: {
        /*
         * Common list of keycodes, allowing reverse lookup of name from code.
         * Derived from: http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
         * This only includes the common function/special keys which should be browser independant.
         */
        8:’backspace’,
        9:’tab’,
        13:’enter’,
        16:’shift’,
        17:’ctrl’,
        18:’alt’,
        27:’escape’,
        33:’pageUp’,
        34:’pageDown’,
        35:’end’,
        36:’home’,
        37:’leftArrow’,
        38:’upArrow’,
        39:’rightArrow’,
        40:’downArrow’,
        45:’insert’,
        46:’delete’,
        112:’f1′,
        113:’f2′,
        114:’f3′,
        115:’f4′,
        116:’f5′,
        117:’f6′,
        118:’f7′,
        119:’f8′,
        120:’f9′,
        121:’f10′,
        122:’f11′,
        123:’f12’       
    },
    clickOnKey: function(event, keyActions) {
        /*
         * This is a keyup/keydown/keypress event handler.
         * keyActions is an object literal containing key mnemonics for the keys, and target elements for the values.
         * As well as mnemonics, raw key code numbers can be used.
         * A special object literal key, idPath, may be used to specify a default ID prefix.
         * If present, it is applied to all element IDs which are not prefixed with a ‘#’. 
         * Examples:-
         *    onkeypress="return uk.co.salientsoft.util.clickOnKey(event, {enter:’frm1:cmdSaveNew:link’});"
         *    onkeyup="return uk.co.salientsoft.util.clickOnKey(event, {escape:’cmdCancelNew:link’, idPath:’frm1:’});"
         *
         * NOTE – escape handling does not work on a keypress event.
         * The event is detected, but the click fails to do anything.
         * onkeyup should be used for escape, as this does not suffer from the problem.
         */
        var keycode = (event.keyCode ? event.keyCode : event.which);
       
        /*
         * Try to fetch the target element id from the passed keyActions assuming a string key code name was used, e.g. ‘enter’
         * If this fails, try a lookup based on the raw key code.
         * Doing it this way avoids looping.
         */       
        var actionElement = keyActions[uk.co.salientsoft.util.keyCodeNames[keycode]];
        if (actionElement == null) {
            actionElement = keyActions[keycode];
        }       
       
        /* If found a target for the event keycode, click it
         * and return false to stop any propagation, else return true
         */
        if (actionElement != null) {
            if (actionElement.charAt(0) != ‘#’ && keyActions.idPath != null) {
                actionElement = keyActions.idPath + actionElement;
            }
            jQuery(uk.co.salientsoft.util.escapeId(actionElement)).click();
            return false;
        }
        return true;
    }
};

No Comments »

March 22nd, 2011
8:27 pm
Using the Browser Resize Event in jQuery

Posted under JSF
Tags , , , ,

jQuery ties into this event in a browser independant way. The following code fragment illustrates the use of this. Note tha following points:-

  • Different browsers implement resize differently. Most fire continual events during the resize, but some browsers (e.g. I believe older versions of Firefox) just fire the resize at the end of the drag. You therefore cannot predict how often and when the resize is called.
  • You need to place the code in the body section – I placed it in the head initially and it was never called.
  • When referring to a function defined in the head section as I have here, you need to prefix the call with “document.” otherwise the target function appears out of scope. The example assumes that yourFunctionName is a Javascript function defined elsewhere, e.g. in the header section of the page.

 

        <script type=”text/javascript”>
                /*
                 * This script block executes the target function on a resize.
                 * Note that jQuery has a browser independant means to hook into the event.
                 */
                jQuery(window).resize(function() {
                //alert(‘resize called’);
                document.yourFunctionName(args);
            });
        </script>

No Comments »

March 22nd, 2011
8:11 pm
Fixing Primefaces scrolling table header width issues using jQuery

Posted under JSF
Tags , , , , , , , ,

This has been very much an ongoing chestnut with Primefaces, mainly due to the tricks you have to use in order to get fixed table column headers and a scrolling table. Primefaces does this with a pair of tables – one for the header row and another for the data. The problems which often arise are mismatching columns between the header and the data, incorrectly sized table header above the column headers, and an incorrectly sized header row which does not appear to size properly dynamically when scrollbars are present/absent.

These issues can often be fixed, but it does appear that there is no overall silver bullet which makes everything work in every use case. The best approach I have found is to have a selection of approaches to try, and to apply the appropriate ones to solve problems which arise in particular use cases. The following details the different approaches I have used in various cases to solve width and alignment problems.

The following commented code sample shows how to use jQuery to correct table header width and column header width problems for scrolling tables. These problems are typically triggered by the dynamic appearance/removal of scrollbars, which will depend on table height and row count. Note that the function described, ss_FixTableHeaderWidth, would either be called in an inline script block placed immediately after a table definition in a facelets page (for a static table) or would be triggered as part of an Ajax call (for a dynamically rendered Ajax table). The example shows the static case. Note the following points about the code:-

  • The code may be found in the repository here.
  • The jQuery selectors should be optimised for performance. In general, ID selectors are the most efficient, so  narrow down with an ID selector first if you can (as below). Element selectors are also efficient, but classes can be really slow depending on what the browser supports – jQuery may end up doing a full DOM tree scan for every class selector, which is highly undesirable!. To avoid this below, I have prefixed class selectors with an element type which then makes use of the faster element searching, e.g. div.ui-scrollable-datatable.
  • Avoid duplicating selector searches where possible – in the example below a reuse them where I can, by using previous selector results as the context for subsequent lower level selectors, for example jQuery(‘div.ui-scrollable-datatable-container’, mainDiv) makes use of the jQuery result mainDiv from a previous selector when selecting its descendant scrollable container div.
  • You need to keep track of the difference between DOM elements returned by some calls, and jQuery objects which can be returned by others. You can always wrap a DOM element with a jQuery object using jQuery(element), but this is not always necessary. Avoid the overhead of doing it when you already have a jQuery object as it is redundant. The jQuery documentation makes all this clear.
  • The jQuery online docs are available here.  You cannot directly download the docs from the jQuery site, but it does link to some unofficial PDF downloads. However, the best and most searchable option seems to be an unofficial windows help file/CHM version download linked from StackOverflow (not fully up to date but still very useful). This is available here.
  • The RH header column size is set to auto to accommodate header row size changes, the idea being that this RH column will expand to fill the row, and the browser will not mess with other column sizes. Note that the column widths are just a guide to the browser and it is free to mess with them. This can be really awkward as we rely on the header table and data table being rendered exactly the same way to line up. This auto approach seems to work.
  • Firefox (3.6 and 4) has a problem whereby when setting an element width (in this case with jQuery), the actual value set is often 1px less than the requested value (particularly a problem when setting header table width). This can easily be seen by reading back the element width immediately after setting it. The fix I use for this is to do all width setting in a function (ss_setWidth) which checks the width afterwards and calculates the value of the mismatch. If there is a mismatch, the mismatch is then added to the width to set, and the element width is set a second time. This function is safe to use for all browsers.

 

Function definition, placed in page header section

    <script type=”text/javascript”>
    //<![CDATA[

        function ss_FixTableHeaderWidth(formId, tableId) {
           
            /*
             *Fix incorrect table header width due to scrollbars
             */
           
            var fullTableId = ‘#’ + formId + ‘\\3A’ + tableId;
            var topHeader = jQuery(fullTableId + ‘ > div.ui-datatable-header’);
            var mainDiv = jQuery(fullTableId + ‘ > div.ui-scrollable-datatable’);
            var headerTable = jQuery(‘table.ui-scrollable-datatable-header’, mainDiv);
            var dataContainer = jQuery(‘div.ui-scrollable-datatable-container’, mainDiv);           
            var dataTable = jQuery(‘table.ui-scrollable-datatable-body’, dataContainer);           
            var lastColumnHeader = jQuery(‘thead > tr:eq(0) > th:last’, headerTable);

/*           
            alert (‘TableId=’+fullTableId + ‘\n’ +
                   ‘topHeader=’+topHeader.attr(‘class’) + ‘\n’ +
                   ‘mainDiv=’+mainDiv.attr(‘class’) + ‘\n’ +
                   ‘headerTable=’+headerTable.attr(‘class’) + ‘\n’ +
                   ‘dataContainer=’+dataContainer.attr(‘class’) + ‘\n’ +
                   ‘dataTable=’+dataTable.attr(‘class’) + ‘\n’ +
                   ‘lastColumnHeader=’+topHeader.attr(‘class’));
*/           
            /*
             * Set the RH header column width to auto to prevent header table width adjustments
             *  from messing up column alignments
             */
            lastColumnHeader.css(‘width’, ‘auto’);

            /*
             * For some themes (pepper-grinder, overcast, ui-lightness), the width is incorrectly set for a scroll bar
             * even when there isn’t one. Detect whether scroll bars are actually present and fix the width if required.
             */
            if (dataContainer.outerHeight() >= dataContainer.get(0).scrollHeight) {
                //alert(‘Scroll bars not present’);
                //dataContainer.width(dataTable.outerWidth());
                ss_setWidth(dataContainer, dataTable.outerWidth());
            }       

            //Now set the width of the column headers table correctly and then finally the table header to match.
            var newHeaderTableWidth = headerTable.width() + dataContainer.outerWidth() – headerTable.outerWidth();
            var newTopHeaderWidth = topHeader.width() + dataContainer.outerWidth() – topHeader.outerWidth();

            //headerTable.width(newHeaderTableWidth);
            ss_setWidth(headerTable, newHeaderTableWidth);
            //topHeader.width(newTopHeaderWidth);
            ss_setWidth(topHeader, newTopHeaderWidth);
        }
       
        function ss_setWidth(element, value) {
           /*
            * This function overcomes an issue with Firefox (3.6 and 4), whereby the actual width set for an element
            * (particularly the header table) is sometimes less (typically 1px less) than the passed width to be set.
            * We read back the width after setting it, and if it differs from what we set,
            * we adjust our original value by the mismatch and set the width again.
            */           
            element.width(value);
            mismatch = value – element.width();
            if (mismatch != 0) {
                //alert(‘mismatch=’+mismatch+’ fixed’)
                element.width(value + mismatch);
            }
        }       
    //]]>
    </script>

Function call, placed inline immediately following table

<p:dataTable …>

</p:dataTable>
<script type=”text/javascript”>ss_FixTableHeaderWidth(‘frmTest’, ‘tblTable’);</script>

Iterating columns to set the width for each one individually

The following code sample uses the jQuery .each loop to iterate the header columns and set the width of each one to its matching data column (along with setting the table/row widths correctly). This sounds ideal for solving the header problems, but in practice it did not work well. The main issue is that jQuery reported a different value for the header column widths to that reported by the browser (via Firebug). The values were out by 1px, which caused an incremental mismatch in the column alignment as you go left to right. This approach was therefore abandoned in favour of just setting the RH header column size to auto to accommodate row size changes, as explained above. However, I reproduce the code here as it is a useful example of how to iterate a returned  jQuery object collection from a selector.

Function definition, column iteration version

    <script type=”text/javascript”>
    //<![CDATA[

        function ss_FixTableHeaderWidth(formId, tableId) {

            /*
             *Fix incorrect table header width due to scrollbars
             */
            
            var fullTableId = ‘#’ + formId + ‘\\3A’ + tableId;
            var topHeader = jQuery(fullTableId + ‘ > div.ui-datatable-header’);
            var mainDiv = jQuery(fullTableId + ‘ > div.ui-scrollable-datatable’);
            var headerTable = jQuery(‘table.ui-scrollable-datatable-header’, mainDiv);
            var dataContainer = jQuery(‘div.ui-scrollable-datatable-container’, mainDiv);           
            var dataTable = jQuery(‘table.ui-scrollable-datatable-body’, dataContainer);           
           
            /*
             * For some themes (pepper-grinder, overcast, ui-lightness), the width is incorrectly set for a scroll bar
             * even when there isn’t one. Detect whether scroll bars are actually present and fix the width if required.
             */
            if (dataContainer.outerHeight() >= dataContainer.get(0).scrollHeight) {
                //alert(‘Scroll bars not present’);
                //dataContainer.width(dataTable.outerWidth());
                ss_setWidth(dataContainer, dataTable.outerWidth());
            }                       

            /*
             * Now iterate the columns with a jQuery each loop and set the header column width
             * to be the same as its corresponding data column width.
             * This is for illustration – the idea has been abandoned, as I found that Javascript/jQuery
             * reports a different value for the header widths to firebug on the final rendered page (out by 1px).
             * This causes an increasing mismatch between data columns and header columns as you go from left to right.
             * The column widths are only a suggestion and the browser may modify them anyway.
             * Therefore the final version, in SortTable.xhtml just sets the RH column width to auto
             * to take up the slack when the row width is changed, and leaves the others alone.
             * This appears to work much better.
             * However the iteration technique here is a useful working jQuery example.
             */
            var columnHeaders = jQuery(‘thead > tr:eq(0) > th’, headerTable);
            var firstRowCells = jQuery(‘tbody > tr:eq(0) > td’, dataTable);
            var columnCount = columnHeaders.size()
           
            columnHeaders.each(function(idx){
                    var columnHeader = jQuery(this);
                    if (idx < columnCount – 1) {
                       
                        var firstRowCell = jQuery(firstRowCells.get(idx));
/*
                        alert(‘idx=’+idx+’\n’+
                              ‘header width=’+columnHeader.width()+’\n’ +
                              ‘header csswidth=’+columnHeader.css(‘width’)+’\n’ +
                              ‘header innerWidth=’+columnHeader.innerWidth()+’\n’ +
                              ‘header outerWidth=’+columnHeader.outerWidth()+’\n’ +
                              ‘data width=’+firstRowCell.width()+’\n’ +
                              ‘data csswidth=’+firstRowCell.css(‘width’)+’\n’ +
                              ‘data innerWidth=’+firstRowCell.innerWidth()+’\n’ +
                              ‘data outerWidth=’+firstRowCell.outerWidth());
*/
                        var newWidth = columnHeader.width() + firstRowCell.outerWidth() – columnHeader.outerWidth();
                        //alert(‘idx=’+idx+’,width=’+columnHeader.width()+’, newWidth=’+newWidth);
                        //columnHeader.width(newWidth);
                        ss_setWidth(columnHeader, newWidth);
                    }
                    else {
                        columnHeader.css(‘width’, ‘auto’);
                    }
              });

            //Now set the width of the column headers table correctly and then finally the table header to match.
            var newHeaderTableWidth = headerTable.width() + dataContainer.outerWidth() – headerTable.outerWidth();
            var newTopHeaderWidth = topHeader.width() + dataContainer.outerWidth() – topHeader.outerWidth();

            //headerTable.width(newHeaderTableWidth);
            ss_setWidth(headerTable, newHeaderTableWidth);

            //topHeader.width(newTopHeaderWidth);
            ss_setWidth(topHeader, newTopHeaderWidth);
        }
       
        function ss_setWidth(element, value) {
            /*
             * This function overcomes an issue with Firefox (3.6 and 4), whereby the actual width set for an element
             * (particularly the header table) is sometimes less (typically 1px less) than the passed width to be set.
             * We read back the width after setting it, and if it differs from what we set,
             * we adjust our original value by the mismatch and set the width again.
             */                       
            element.width(value);
            mismatch = value – element.width();
            if (mismatch != 0) {
                //alert(‘mismatch=’+mismatch+’ fixed’)
                element.width(value + mismatch);
            }
        }       
    //]]>
    </script>

No Comments »

November 30th, 2010
4:37 pm
Tailoring a Primefaces TreeTable with JavaScript/jQuery/JSF 2.0

Posted under JSF
Tags , , , , , , , ,

This example came about from the desire to hightlight a Primefaces TreeTable row when a checkbox in the row is ticked. I wanted to do this because a ticked checkbox is not as visible as I would like when a lot of data is present. I was not keen on adding DataTable style dynamic selection by hovering and clicking anywhere in a row, as this conflicts somewhat with the paradigm of clicking on the expander to expand/collapse the tree, and you would have to special case hovering/clicking on the expander somehow to prevent row selection.

Ideally you would use CSS for this, but this would involve tagging a component in a TreeTable cell (<td> element) with a class (as in the TreeTable component we cannot directly add row styling), and then using some kind of ancestor selector to traverse the DOM tree upwards to get the row. The simple answer to this is that whilst CSS can do some clever things, it does not have any kind of ancestor selector, so it is not possible to do it just with CSS.

An attractive alternative for this kind of thing in Primefaces is to use jQuery – a very powerful JavaScript based UI library upon which Primefaces is based. As it is already present in a Primefaces environment, it is trivial to call and ideal for this kind of thing. In particular, it has powerful mechanisms for traversing and searching the DOM tree and selecting elements etc. If you combine this with its features for dynamically adding and removing classes from elements, it can do exactly the kind of thing that we cannot do in CSS. The jQuery website is here, and the API documentation for it may be found here.

The sample code fragments below demonstrate the idea. The sample is based on a hacked version of the the Primefaces showcase TreeTable demo. Note the following points:-

  • It is perfectly possible to add both Javascript and JSF EL inline inside a JSF component, such that the JavaScript runs at that point when the page is rendered. This example adds some JavaScript inside the JSF tags for a checkbox.
  • As the example uses JavaScript, the hightlight is added/removed both client side from a click event on the check box, and when the page is rendered based on the current check box value in the TreeTableDocument object from the server.
  • The sample uses the JSF 2.0 EL implicit object reference component.clientId to get the client ID of the component where the EL reference is used. This is new for JSF 2.0 (previously additional code would be needed for this) and like the cc implicit object used with composite components, is not at all well documented. I was unable to find reference documentation for it anywhere in the JSF 2.0 specs, but found details of it here (at the end of the post). Note that the reference needs to be placed with care – it sometimes appears to come up with ‘ghost’ client IDs which may have been allocated by JSF but do not actually appear anywhere as IDs in the HTML. When using it, check that it returns the correct ID, for example by just placing a bare #{component.clientId} on the page directly adjacent to where you are using it, and view the source of the generated page in the browser to check that the client ID it returns matches the id of the component it is used for.
  • Be careful to consider any ordering issues – the inline JavaScript call I am using must be placed on the page after the component it refers to has been rendered. By default, using component.clientId  inside a script block which is in turn inside the checkbox component tags causes the script block to be output immediately prior to the html for the checkbox. Therefore, I use two script blocks – one inside the checkbox to save the component ID in a temporary JavaScript variable, which guarantees that I obtain the correct component ID and not an incorrect or ‘ghost’ one, and then another script block immediately after the closing tag for the checkbox component which passes the form ID and saved component ID to a JavaScript function which calls jQuery to do the highlighting after the html element has been rendered on the page. Note that the client side onClick() event on the checkbox will always run after  the full page has been rendered, when the user clicks the box, so ordering is not an issue for this.
  • The call to jQuery passes in the checkbox DOM element, which jQuery wraps with a jQuery object and returns. The parent() method is then used twice to traverse the DOM tree up from the check box to the table cell <td>, and thence to the table row <tr>. The addClass() and removeClass() methods are then used to dynamically add/remove the required CSS classes to the table row depending on the passed check box state. Adding and removing classes with jQuery is useful as it does all the string bashing for you to check if the class being added/removed is already present, handles spaces correctly, and leaves all other classes on the element intact.
  • Note that in an earlier version I had tricky issues with nested quotes in JSF EL. I initially used outer double quotes, with inner single quotes on the JavaScript calls. The EL through this out however. I ended up using double quotes throughout, and escaped the inner ones with backslashes (\). This worked fine. The version shown here does not need this as it is a later modification.
  • The example extends the Primefaces Document class used in the showcase, adding a boolean property for the check box value.
  • My example uses CDI, whereas the original showcase uses JSF managed beans.

Sample Code Fragments

Index.xhtml

<!DOCTYPE HTML>
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:c="http://java.sun.com/jsp/jstl/core"   
xmlns:util="http://java.sun.com/jsf/composite/uk.co.salientsoft/util">
<f:view contentType="text/html">
<h:head>
    <util:themeSwitcher    outputStyle="true"
                        themeBase="/resources/themes"
                        defaultTheme="redmond"/>
<style>
    .ui-widget {
        font-size: 75% !important;
    }
    h1.header {
        font-size: 1.2em !important;
        padding: 5px;
        margin: 0px 0px 20px 0px;
    }
    h2.subheader {
        font-size: 0.9em !important;
        padding: 2px;
        margin: 0px 0px 10px 0px;
    }
    .float {
        float: left;
    }
   
    .clear {
        clear: both;
    }
    .themeswitcher {
        float: left;
        clear: both;
        margin-bottom: 10px;
    }   
    .ss-selectcolumn {
        width:30px !important;
        padding:0px !important;
        text-align:center;
    }
    .ss-checkbox {
        color:red!important;
    }
/* treetable arrow light/dark switch
    .ui-treetable tr.collapsed td .expander {
        background-image:url("/TreeTable1/javax.faces.resource/treetable/images/toggle-expand-light.png.jsf?ln=primefaces&amp;v=2.2.RC2");
    }
    .ui-treetable tr.expanded td .expander {
        background-image:url("/TreeTable1/javax.faces.resource/treetable/images/toggle-collapse-dark.png.jsf?ln=primefaces&amp;v=2.2.RC2");
    }   
*/
/* Styling nicked from default.css in the Primefaces Showcase */
    a {text-decoration: none;}
    a:hover {text-decoration: underline;}
    a img {border: none;}   
</style>

<script type="text/javascript">
//<![CDATA[
    function highlightTreeTableRow(formId, checkBoxId) {
        var checkBox = document.forms[formId][checkBoxId];
        if (checkBox.checked)
            jQuery(checkBox).parent().parent().addClass(‘ui-state-highlight ui-selected’);
        else
            jQuery(checkBox).parent().parent().removeClass(‘ui-state-highlight ui-selected’);
    }
//]]>
</script>
</h:head>
<h:body>
<h1 class="float header ui-widget ui-widget-header ui-corner-all" style="">Theme Switcher</h1>
<h:form prependId="false" id="frmTest">
<util:themeSwitcher form="frmTest" outerStyleClass="themeswitcher" style="margin-bottom:20px;"/>
<br/><br/>

<div style="width:600px;clear:both;">
    <p:treeTable value="#{documentsController.root}" var="document"
       styleClass="#{(document.name==’logo.png’)? ‘ui-state-highlight ui-selected’:’fred’}"
        expanded="true">
        <p:column>
            <f:facet name="header">
                Name
            </f:facet>
            <h:outputText value="#{document.name}" />
        </p:column>
       
        <p:column>
            <f:facet name="header">
                Size
            </f:facet>
            <h:outputText value="#{document.size}" />
        </p:column>
       
        <p:column>
            <f:facet name="header">
                Type
            </f:facet>
            <h:outputText value="#{document.type}" />
        </p:column>
       
        <p:column>
            <f:facet name="header">
                Options
            </f:facet>
            <p:commandLink update="documentPanel" oncomplete="documentDialog.show()" title="View Detail">
                <p:graphicImage value="/images/search.png"/>
                <f:setPropertyActionListener value="#{document}" target="#{documentsController.selectedDocument}" />
            </p:commandLink>
        </p:column>
       
        <p:column styleClass="ss-selectcolumn">
            <f:facet name="header">
            <h:selectBooleanCheckbox disabled="true" label="box header"/>
            </f:facet>
            <h:selectBooleanCheckbox value="#{document.selected}"
                 
onclick="highlightTreeTableRow(‘frmTest’, this.id);" styleClass="ss-checkbox" id="selector" >                
                 <script>selectId = ‘#{component.clientId}’;</script>
            </h:selectBooleanCheckbox>
            <script>highlightTreeTableRow("frmTest", selectId);</script>
        </p:column>
    </p:treeTable>
    <p:commandButton type="submit" value="Submit" onclick="submit();" style="margin-top:10px;"/>
   
    <p:dialog header="Document Detail" fixedCenter="true" effect="FADE" effectDuration="0.3"
        widgetVar="documentDialog" modal="true">
   
        <p:outputPanel id="documentPanel">
            <h:panelGrid  columns="2" cellpadding="5">
                <h:outputLabel for="name" value="Name: " />
                <h:outputText id="name" value="#{documentsController.selectedDocument.name}" style="font-weight:bold" />
               
                <h:outputLabel for="size" value="Size: " />
                <h:outputText id="size" value="#{documentsController.selectedDocument.size}" style="font-weight:bold" />
               
                <h:outputLabel for="type" value="Type " />
                <h:outputText id="type" value="#{documentsController.selectedDocument.type}" style="font-weight:bold" />
            </h:panelGrid>
        </p:outputPanel>
    </p:dialog>
    </div>
</h:form>
</h:body>
</f:view>
</html>

DocumentsController.java (modified from Showcase)

package org.primefaces.examples.view;
import java.io.Serializable;
import java.util.logging.Logger;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import org.primefaces.model.DefaultTreeNode;
import org.primefaces.model.TreeNode;
import uk.co.salientsoft.treetable.view.TreeTableDocument;

@Named
@SessionScoped
public class DocumentsController implements Serializable {
   
    private static final long serialVersionUID = -3593168523028092346L;
    private static final Logger logger = Logger.getLogger(DocumentsController.class.getName());
    private TreeNode root;
    private TreeTableDocument selectedDocument;
    public DocumentsController() {
        root = new DefaultTreeNode("root", null);
       
        TreeNode documents = new DefaultTreeNode(new TreeTableDocument("Documents", "-", "Folder"), root);
        TreeNode pictures = new DefaultTreeNode(new TreeTableDocument("Pictures", "-", "Folder"), root);
        TreeNode music = new DefaultTreeNode(new TreeTableDocument("Music", "-", "Folder"), root);
       
        TreeNode work = new DefaultTreeNode(new TreeTableDocument("Work", "-", "Folder"), documents);
        TreeNode primefaces = new DefaultTreeNode(new TreeTableDocument("PrimeFaces", "-", "Folder"), documents);
       
        //Documents
        TreeNode expenses = new DefaultTreeNode("document", new TreeTableDocument("Expenses.doc", "30 KB", "Word Document"), work);
        TreeNode resume = new DefaultTreeNode("document", new TreeTableDocument("Resume.doc", "10 KB", "Word Document"), work);
        TreeNode refdoc = new DefaultTreeNode("document", new TreeTableDocument("RefDoc.pages", "40 KB", "Pages Document"), primefaces);
       
        //Pictures
        TreeNode barca = new DefaultTreeNode("picture", new TreeTableDocument("barcelona.jpg", "30 KB", "JPEG Image"), pictures);
        TreeNode primelogo = new DefaultTreeNode("picture", new TreeTableDocument("logo.jpg", "45 KB", "JPEG Image"), pictures);
        TreeNode optimus = new DefaultTreeNode("picture", new TreeTableDocument("optimusprime.png", "96 KB", "PNG Image"), pictures);
       
        //Music
        TreeNode turkish = new DefaultTreeNode(new TreeTableDocument("Turkish", "-", "Folder"), music);
       
        TreeNode cemKaraca = new DefaultTreeNode(new TreeTableDocument("Cem Karaca", "-", "Folder"), turkish);
        TreeNode erkinKoray = new DefaultTreeNode(new TreeTableDocument("Erkin Koray", "-", "Folder"), turkish);
        TreeNode mogollar = new DefaultTreeNode(new TreeTableDocument("Mogollar", "-", "Folder"), turkish);
       
        TreeNode nemalacak = new DefaultTreeNode("mp3", new TreeTableDocument("Nem Alacak Felek Benim", "1500 KB", "Audio File"), cemKaraca);
        TreeNode resimdeki = new DefaultTreeNode("mp3", new TreeTableDocument("Resimdeki Gozyaslari", "2400 KB", "Audio File"), cemKaraca);
       
        TreeNode copculer = new DefaultTreeNode("mp3", new TreeTableDocument("Copculer", "2351 KB", "Audio File"), erkinKoray);
        TreeNode oylebirgecer = new DefaultTreeNode("mp3", new TreeTableDocument("Oyle bir Gecer", "1794 KB", "Audio File"), erkinKoray);
       
        TreeNode toprakana = new DefaultTreeNode("mp3", new TreeTableDocument("Toprak Ana", "1536 KB", "Audio File"), mogollar);
        TreeNode bisiyapmali = new DefaultTreeNode("mp3", new TreeTableDocument("Bisi Yapmali", "2730 KB", "Audio File"), mogollar);
    }
    public TreeNode getRoot() {return root;}
    public void setRoot(TreeNode root) {this.root = root;}
    public TreeTableDocument getSelectedDocument() {return selectedDocument;}
    public void setSelectedDocument(TreeTableDocument selectedDocument) {
        this.selectedDocument = selectedDocument;
    }
}

TreeTableDocument.java

package uk.co.salientsoft.treetable.view;
import org.primefaces.examples.domain.Document;

public class TreeTableDocument extends Document {
    private static final long serialVersionUID = 7339615098050125785L;
    public TreeTableDocument(String name, String size, String type) {
        super(name, size, type);
    }   
    private boolean;
    public boolean isSelected() {return this.selected;}
    public void setSelected(boolean selected) {this.selected = selected;}
}

No Comments »

November 5th, 2010
11:08 am
Tying Primefaces page styling to the theme

Posted under JSF
Tags , , ,

With Primefaces, rather than applying your own style for borders, headers etc. it is important where possible to tie the styling in to the theme using theme classes.
Here is an example of this from the Primefaces showcase, used to display the header for each example:-

<h1 class=”title ui-widget-header ui-corner-all”>DataTable – Pagination</h1>

This example places a border around the header according to the theme, sets the appropriate background, and adds corner rounding as per the theme.
Whilst in some cases another Primefaces component might be the right approach (in this case a panel would do it), in many cases this is undesirable and also overkill for the job. Taking our lead from the Primefaces showcase, it is clearly acceptable to use the underlying jQuery UI classes where appropriate, as I have also done here.

Here is another example page fragment which I have used to group a few controls/buttons in a box:-

<h:head>
    <style type=”text/css”>       
        .ui-widget, .ui-widget .ui-widget {
            font-size: 80% !important;
        }
        .ss-controlgroup {
            float: left;
        }

    </style>
</h:head>
<h:body>
<h:form>
<h:panelGroup layout=”block” styleClass=”ui-widget-content ui-corner-all ss-controlgroup”>

   (controls in the box go here)

</h:panelGroup>
</h:form>
</h:body>

Details of the jQuery UI, on which Primefaces is based, may be found here.

No Comments »