March 13th, 2020
5:05 pm
Creating a fully trusted Self Signed localhost certificate

Posted under Web
Tags , , ,

I was getting fed up with the various hacks and switches needed e.g. in Chrome to persuade it to use an ordinary self signed certificate to allow ssl for localhost during local development and testing.

My holy grail was to have seamless self signed localhost certificate support, such that the browsers would not complain at all or need any special hacks, but ‘just work’, and without any nasty warnings in the address bar. I wanted to use the same certificate in a Java Keystore for Spring Boot, as well as with a node http-server for serving webpack bundles. After much fishing around, the following solution worked for me.

Summary of Issues found/solved

1/ You can get a self signed cert to be fully trusted locally if you add it to the local computer’s Trusted Root Certification Authorities Store. For windows this is straightforward with the management console – details below in the steps.

2/ In addition, the certificate needs to have a Subject Alternative Name (SAN) defined as localhost, or Chrome will spit it out with a Common Name error. This is a misnomer – the problem is not with the Common Name, it is due to the lack of a correct SAN. Just setting the Common Name (CN) to localhost is deprecated and will no longer work in Chrome.

3/ The Java Keytool can create a keystore containing a certificate with a SAN, using the ext switch. This is detailed here for Java 7 (see the table under the -ext switch). It also works for Java 8 but I found the above docs somewhat clearer than those for java 8. However, I could not find a way to add multiple SAN DNS references, e.g. for localhost and for www.localhost.

4/ I found a way via this post here to use openssl to create a certificate keystore containing the certificate, which I could then import into a java keystore using keytool. openssl  was able to create multiple SAN DNS entries as required. My previous post here links to this post re setting up a Wamp Server, which links to this page re installing openssl.

5/ A trivial gotcha which I hit initially – once you have created and installed the certificate e.g. in the computer’s trust store as above, make sure that this certificate is also the exact one that you are using with all your web servers – in this case, http-server and Spring Boot.

6/ Once all done and working, I could happily use Chrome, IE11 and Firefox without any hacks or switches for self signed certs, and without any hacky warning on the address bar – just the normal black padlock.

Steps to Implement the Solution.

1/ I created a config file for openssl, e.g. req.cnf, as follows:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = GB
O = Salient Soft Ltd
CN = localhost
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = www.localhost

2/ I created the certificate and key via openssl using the above config file:

openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -keyout ss-localhost-cert.key -out ss-localhost-cert.crt -config req.cnf -sha256

3/ Import the certificate and key into a PKCS12 store, and then import that store into a java keystore with keytool:

openssl pkcs12 -export -name salientsoftdev -in ss-localhost-cert.crt -inkey ss-localhost-cert.key -out ss-localhost-cert-keystore.p12
keytool -importkeystore -destkeystore salientsoft.p12 -storetype PKCS12 -srckeystore ss-localhost-cert-keystore.p12 -srcstoretype pkcs12 -alias salientsoftdev

4/ Use the Microsoft Management Console to import the above certificate (ss-localhost-cert.crt) into the local computer’s Trusted Root Certification Authorities Store. This post  details how to do this on Windows 10, but Windows 7 was the same.

5/ I used the final keystore for Spring Boot (per details here), and the certificate and key files for node http-server (per details here).

Comments Off on Creating a fully trusted Self Signed localhost certificate

May 10th, 2011
1:24 pm
Using Namespaces in Javascript to avoid naming collisions

Posted under Javascript
Tags , ,

Update 15/9/2011

Here is an example of my initial implementation, similar to the corejsf example above, but using a shorthand syntax to avoid the ifs. This only requires the 2 additional lines in front to declare the namespace. This can be done with global ‘register’ functions, but these in turn have namespacing issues and seem overly complex compared to the code below. Note also that this uses Javascript Object literal syntax to declare the functions (the ‘colon operator’ below, which is also used in JSON). This syntax in Javascript is a concise notation for name value pairs.

This implementation needs rolling out across all the other Javascript – this still needs doing.

var uk=uk||{}; uk.co=uk.co||{}; uk.co.salientsoft=uk.co.salientsoft||{};
uk.co.salientsoft.util= {
 clearElement: function(elementId) {
  var escapedId = ‘#’ + elementId.replace(/:/g, ‘\\3A ‘);
  jQuery(escapedId).val(”);
 }
};

Original Post

It is important to avoid collisions between Javascript function/variable names used by different objects – it is all too easy for entirely different pieces of code to be used in the same application and result in a name collision.

A good mechanism to avoid this for both functions and data is detailed in Core JavaServer Faces Edition 3, page 399. An excerpt from this page detailing how this is done is shown below.

 JavaScript Namespaces

In “The f:ajax Tag” on page 389, we implemented a JavaScript function—showProgress()—that uses Prototype to show and hide a DOM element. That method is fragile, however, because it can be overwritten by another JavaScript method of the same name.

It sounds plausible that someone might implement another JavaScript function named showProgress(), thereby replacing your version of the method with theirs. To prevent that replacement from happening, you could name your function something more unique, such as com.corejsf.showProgress(). With that strategy in mind for protecting our JavaScript method from being overwritten, we implement a simple map that serves as a namespace, and we define functions in that map**:

if (!com) var com = {}
if (!com.corejsf) {
    com.corejsf = {
        showProgress: function(data) {
            var inputId = data.source.id
            var progressbarId = inputId.substring(0, inputId.length – “name”.length) + “pole”;
            if (data.status == “begin”)
                Element.show(progressbarId);
            else if (data.status == “success”)
                Element.hide(progressbarId);
        }
    }
}

So now the caller accesses the function through the namespace, like this:

<f:ajax event=”blur” render=”nameError” onevent=”com.corejsf.showProgress”/>

JavaScript namespacing not only prevents others from overwriting your functions, but it also indicates to readers of your code that you are a JavaScript coder of some substance.

NOTE: Besides creating an ad-hoc namespace by putting JavaScript functions in a map, you can also put data in a map. Namespacing data is a consideration when you implement custom components that maintain data on the client. If you have multiple instances of the component, they will overwrite each other’s data. Putting the data in a map, keyed by the client identifier of the component to which the data is associated, is a way to ensure that multiple Ajax-based custom components of the same type that store data on the client, can co-exist in a single page.

** The map is actually an object literal—JavaScript does not have a map data structure—but semantically a JavaScript object literal is similar to a map.

No Comments »

March 25th, 2011
6:43 pm
Creating Javascript Objects using new – the pros and cons

Posted under Javascript
Tags , ,

There are a number of pros and cons regarding this practice, and they are debated on StackOverflow here. A w3schools tutorial on it may be found here.

I have used this e.g. here, following the lead of the Primefaces dialog.js. A downside is that your code fails if you forget the new and just call the function instead. However, this can be detected and an error thrown as per the StackOverflow post. In my case, the thrown error was not easily detected so I elected to pop an alert as well.

I do not discuss the issue further here – just follow the links and you will find plenty of information on it.

No Comments »

March 25th, 2011
6:42 pm
Implementing confirmation dialogs with Primefaces p:dialog

Posted under JSF
Tags , , , , ,

Update 7/3/2012

Primefaces p:ajax partial refreshes can also be blocked by returning false to the onclick event.  In this case, an If statement needs to be used, so that “return false” is done to block the refresh, but no return at all is done to allow the refresh. This allows the Primefaces ppr logic to execute – returning true explicitly in the onclick actually stops the ppr/ajax logic from executing at all:-

onclick="if (!confirmAction(Arguments)) {return false};"

The nice feature of this method is that it works in an identical fashion for both full page and partial refreshes, so that is is not necessary to mess with logic which couples both the onstart and onclick attributes based on the ajax setting.

The downside is that as before we are adding bare Javascript logic into an element attribute. In defence, this situation has already been forced on us by the JSF/Primefaces design – the interface already requires us to return false to block a request, so whilst this might be distasteful from a purist Javascript point of view, it is a fact of life we have to live with.

 

Original Post

Confirmation dialogs are typically used when for example a form is being edited and a navigation to another page is attempted.

Traditionally, this might be done by using the Javascript confirm function which pops a browser dialog, and returns true or false depending on the user choice. However, modern web apps shy away from using browser popup dialogs. Their styling is browser dependant and they cannot be restyled. A modern app will use custom styled dialogs with features like fade in/out and lightboxing to give a more subtle appearance and to allow the dialog to be displayed in-page to avoid popup blocking problems. Also, component libraries such as Primefaces allow custom themes and theme switching for an application, which would therefore allow their own custom dialogs to be dynamically restyled.

The fundamental complication when using such a custom dialog is that unlike the built-in Javascript confirm function, it does not operate synchronously. This immediately raises major issues when using it to confirm navigation, as it cannot be used inline in a navigation confirmation event on a button or link.  This post looks at how to get around this issue and use a Primefaces p:dialog to implement a confirmation dialog.

 

Allowing or blocking navigation attempts

The first point to address is how we can actually allow or block a navigation attempt on a Primefaces/JSF button or link. There are two options available, which are straightforward and applicable to all buttons and links:-

  • For full page refreshes, returning false from the onclick event aborts the navigation. This is a standard JSF feature which is described here. It is also mentioned in Core Javaserver Faces Edition III on p573, although it must be said that it is rather hidden near the back of the book and hard to dig out.
  • For Primefaces p:ajax partial refreshes, returning false from the onstart event aborts the ajax call.

Note that in both cases, it is important to prefix the function call with the return statement, otherwise the function return value is not correctly returned by the event call. For example, using the traditional Javascript confirm function:-

  • onclick=”return confirm(‘Pending edits will be cancelled, do you want to continue?’);”
  • onstart=”return confirm(‘Pending edits will be cancelled, do you want to continue?’);”

When applying a default case, it is also possible to just return true or false without calling anything, e.g. using onclick=”return true;”

 

Using the p:dialog  to allow/block the navigation

This is a little tricky to get right, but actually turns out to be fairly straightforward, and in particular, we can still initiate the whole process from a single function call in the onclick or onstart event as above. The idea makes use of the fact that we can issue a soft click on an HTML (or in this case jQuery) element using the .click() call. The steps are as follows :-

  1. The user clicks on a button, which causes our own Javascript confirm(elementId) function to be called by say the onstart event. Importantly, the clientId of the target element which was clicked is passed and stored, as it may be needed later.
  2. if edit mode is inactive, the function just returns true to allow the action.
  3. If edit mode is active, then the function sets an internal confirmInProgress flag and shows the confirmation p:dialog via its widget show() function. The function then returns false to (initially) block the Ajax action.
  4. When the dialog is displayed and the user clicks No, a Javascript cancelAction() function is called which hides the dialog and clears the confirmInProgress flag. The Ajax action is not then performed.
  5. When the dialog is displayed and the user clicks Yes, a Javascript performAction() function is called which hides the dialog and forces a second (software) click on the button, using the previously stored elementId. This results in confirm() being called a second time.
  6. When confirm() is called the second time via the software click (because the user clicked yes to confirm), we can detect this as the confirmInProgress flag will be set. The function then clears the confirmInProgress flag and returns true to allow the Ajax action to proceed.

 

Implementation Points

  • When implementing this, my confirmation dialog is a facelets custom tag. Within the tag, I create a Javascript object (whose precise name is passed by the caller) which contains the above logic and manages the confirmation process.
  • The Javascript object is created via the new keyword – see my other post here with links on this topic.
  • I then pass the confirm function call used on this object as a text attribute value to any tags, buttons or links that need to implement confirmation. For a high level tag I would pass it as an onNavigate attribute, and within the tag it would be passed on to any links or buttons to which it applies.
  • In order to do the software click, the clientId of the element that was clicked is passed to the confirm function and stored on the Javascript object. To do this, the confirm function call is passed as a MessageFormat style string with a placeholder which will take the clientId, as this must be added by the target button typically via a reference to #{component.clientId}. In this way the final target could add additional parameters if required, but is nicely decoupled from the actual Javascript call. The only interface contract is the placeholder parameter index, and the fact that a boolean must be returned to permit/deny navigation. I typically add MessageFormat.format (in addition to string concatenation) as custom EL java functions declared in a tag library – in the example below, these functions have the el: prefix.
  • In our case, the actual element clientId is a constant string as far as the Javascript call is concerned, so it must have single quotes added. These are added in the context of the target element – they are not passed in the MessageFormat string. This way, the caller does not decide whether or not a parameter is a Javascript String – this is decided in the target element environment. For example, if an additional argument such as a JavaScript reference such as this was passed in the target element environment, it would not need to be quoted, and the caller’s MessageFormat string containing the Javascript call would not have to know. This maintains good decoupling.
  • The Javascript object also has an internal confirmMode property which indicates whether confirmation mode is actually on, i.e. if edit mode is active. If not, it just permits everything as per the above logic steps. This JS property would normally be set from an edit mode bean property on the actual edit form which triggers the confirm. To do this, I pass the JS property reference in to the tag handling the edit form. and it calls a standard utility tag to issue an inline script statement to assign the property reference from the bean. This assignment needs to be within a div which is updated by ajax when the edit mode changes, to ensure the property is reassigned.

 

Example Code Fragments

Facelets Page declaring the Confirmation Dialog

<util2:confirmActionDialog tagId="confirmDialog" widgetVar="confirmWidget" />           

<cheep:campaignTreeBrowser id="campaignTreeBrowser" idPath="#{form}" currentNodeChange="#{form}:pnlCampaignDetail"
                           currentNodeChangeOnComplete="ss_PanelExpand(widgetVarPnlCampaignDetail)"
                           controller="#{campaignsPage.campaignTreeBrowser}" edit="#{true}"
                           onNavigate="confirmWidget.confirm({0})"/>
                                                             
<cheep:campaignDetailPanel tagId="pnlCampaignDetail" idPath="#{form}" editModeFlag="confirmWidget.confirmMode"
                           controller="#{campaignsPage.campaignDetailPanel}"/>

confirmActionDialog.xhtml custom tag

<!DOCTYPE HTML>
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core"   
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
xmlns:util="http://java.sun.com/jsf/composite/uk.co.salientsoft/util">

<ui:composition>
    <h:outputScript library="uk.co.salientsoft/util" name="confirmActionDialog.js" target="head"/>
       
    <script type="text/javascript">#{widgetVar} = new uk.co.salientsoft.ConfirmActionDialog();</script>   
   
    <p:confirmDialog id="#{tagId}" widgetVar="#{widgetVar}.dialogWidget" styleClass="ss-confirmaction-dialog"
                message="#{empty confirmMessage ? mainMsg.confirmEditCancel : confirmMessage}"
                showEffect="fade" hideEffect="fade"
                header="#{empty confirmTitle ? mainMsg.confirmEditCancelTitle : confirmTitle}" severity="alert">
               
        <util:iconTextButton id="cmdAbort"  image="ui-icon ui-icon-close"
                label="#{mainMsg.optionNo}" title="#{mainMsg.optionTitleNo}" onclick="#{widgetVar}.cancelAction()" />                                                    
       
        <util:iconTextButton id="cmdConfirm"  image="ui-icon ui-icon-check" 
                label="#{mainMsg.optionYes}" title="#{mainMsg.optionTitleYes}" onclick="#{widgetVar}.performAction()" />
    </p:confirmDialog>
</ui:composition>
</html>

Setting the EditMode flag in the campaignDetailPanel edit form

<!– Set the edit mode state in the specified Javascript flag used by the navigation confirmation dialog when in edit mode –>
<util2:setScriptVar name="#{editModeFlag}" value="#{controller.editMode}" defaultValue="false" />

Custom tag util2:setScriptVar

<!DOCTYPE HTML>
<html xmlns="http://www.w3c.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"   
xmlns:el="http://salientsoft.co.uk/EL">

    <ui:composition>   
        <!– Set the specified Javascript variable/property from the given EL expression –>
        <c:if test="#{not empty name}">
            <script type="text/javascript">#{name} = #{empty value ? defaultValue : value};</script>
        </c:if>
    </ui:composition>
</html>

Code within a link which adds the actual onstart event (this example is taken from a composite component)

<p:commandLink id="link" ajax="#{cc.attrs.ajax}" async="#{cc.attrs.async}" disabled="#{cc.attrs.disabled}"
         action="#{cc.attrs.controller.iconButtonAction(cc.attrs.eventHandler, cc.attrs.actionMethod,
                              cc.attrs.actionParam, cc.attrs.passController, cc.attrs.passActionParam)}"
         actionListener="#{cc.attrs.controller.iconButtonActionListener}"
         process="#{cc.attrs.process}" update="#{cc.attrs.update}" immediate="#{cc.attrs.immediate}"
         href="#{cc.attrs.href}" title="#{cc.attrs.title}" tabindex="#{cc.attrs.tabIndex}"
        onstart="return #{empty cc.attrs.onNavigate ? ‘true’ : el:format1(cc.attrs.onNavigate, el:concat3(‘\”, component.clientId, ‘\”))}"
         oncomplete="#{cc.attrs.oncomplete}" onsuccess="#{cc.attrs.onsuccess}" onerror="#{cc.attrs.onerror}">
…   

</p:commandLink>

Javascript object ConfirmActionDialog.js

/*
* This object handles the logic for confirming Primefaces Ajax actions via a confirmation dialog.
* It is typically used to pop a confirmation when in edit mode on a form and e.g. a navigation link/button has been clicked.
* A navigation link/button should call confirm() in its onstart Ajax event. The following then happens:-
*
* 1/ if EditMode is inactive, confirm() just returns true to allow the action.
* 2/ If EditMode is active, then confirm() pops the confirm dialog and returns false to (initially) block the Ajax action.
* 3/ When the dialog is popped and the user clicks No, hide the dialog and return to the idle state (Ajax action not performed).
* 4/ When the dialog is popped and the user clicks Yes, we hide the dialog and force a software click on the Ajax link/button.
* 5/ When confirm() is called the second time via the software click (because the user clicked yes to confirm),
*    return to the idle state and return true to allow the Ajax action.
*/

var uk=uk||{}; uk.co=uk.co||{}; uk.co.salientsoft=uk.co.salientsoft||{};
uk.co.salientsoft.ConfirmActionDialog = function () {
    this.confirmInProgress = false; //initialise to the idle state
    this.confirmMode = false; //set no confirmation needed initially (e.g. not in edit mode)
};

uk.co.salientsoft.ConfirmActionDialog.prototype.show = function() {
    this.dialogWidget.show();
};

uk.co.salientsoft.ConfirmActionDialog.prototype.hide = function() {
    this.dialogWidget.hide();
};

uk.co.salientsoft.ConfirmActionDialog.prototype.confirm = function(elementId) {
    if (this.confirmMode && !this.confirmInProgress) {
        var escapedId = ‘#’ + elementId.replace(/:/g, ‘\\3A ‘);
        this.element = jQuery(escapedId);
        this.dialogWidget.show();
        this.confirmInProgress = true;
        return false; //first time in, cancel the Primefaces Ajax action until confirmed
    }
    else {
        /*
         * Either we are not in confirm mode (e.g. edit mode is off),
         * or we are in confirm mode and this is the second time in, called as a result of a confirm.
         * Either way we allow the Primefaces Ajax action, and return to the idle state.
         */
        this.confirmInProgress = false;
        return true;
    }
};

uk.co.salientsoft.ConfirmActionDialog.prototype.performAction = function() {  
    this.dialogWidget.hide();
    this.element.click(); //This will cause confirm() to be called again; this time the Ajax action will be allowed.
};

uk.co.salientsoft.ConfirmActionDialog.prototype.cancelAction = function() {  
    this.dialogWidget.hide();
    this.confirmInProgress = false; //we will not be performing the Ajax action, so return to the idle state
};

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 »

December 6th, 2010
10:45 am
Customising the Primefaces Breadcrumb control

Posted under JSF
Tags , , , , ,

This control has a preview mode which dynamically shrinks and expands the links on hover. Nice idea, but it’s rather slow to render the page on load when there are many entries, as the initial compress is done by jQuery so lots of JavaScript going on.

My preference when a lot of entries are present would be to render the control statically, and instead of dynamically expanding the entries, to have them permanently compressed but with tooltips (= html title attribute) to show the full entry on hover (as suggested here in Jenifer Tidwell’s excellent book Designing Interfaces). This would be much faster.

Unfortunately, the current control does not support that – the menu item children that make up the breadcrumb entries do have a ‘help text’ attribute which looks like it should render a title/tooltip but this does not work.

I would like at some point to make a custom version. The best way would probably be to make it a new different control, possibly in my own library but at least as an additional Primefaces control that I could easily deploy on new released without having to customise code.

Chapter 11 of Core JavaServer Faces has a good example walkthrough of a full custom component development, which helps to give a background. Customising the breadcrumb looks easy as it is just a question of enhancing existing code by adding some additional attributes and changing the encoding in the renderer to encode titles and do a static breadcrumb. The following functionality would be needed:-

ToolTipMode attribute to turn on the new mode. Other modes would possibly still be present  – no point in disabling them unless I have too. If it looks like carrying the old modes is a lot of baggage I could create a stripped down control using the old one as a framework, and only support the new modes I require. As I am keeping the existing control there is no need to duplicate its features if I don’t need to. As the rendering for the new control should be straightforward, I could even rip out all the jQuery stuff and just use some of the existing CSS techniques for rendering it, but statically. I would duplicate the classes and give my own names.

In the new mode, all entries would have a hover title.

  • ToolTipCompressThreshold attribute would be a character count for the whole breadcrumb which would trigger compression of all entries equally. This is obviously a static guess figure, but it avoids having to do any client side stuff in JavaScript and so will render fast.
  • ToolTipMaxLength attribute would be a notional maximum length in pixels for the breadcrumb (set separately from the css). When the compress threshold has been reached, this attribute would be divided by the number of breadcrumb entries to be rendered, and the resulting value used as the maximum pixel size for rendering each entry (it would be good to allow them to be shorter if they don’t use all the space, so it does not look odd). Rather than strip out characters from the entries, I would just hide the excess width via a hidden overflow in a div (as the existing control does now).

The source is all available in the source or bundled zip download. Note the following from a first look:-

  • BreadCrumb.java contains the properties for all the attributes – all driven from an enum with custom methods in it (note the technique, could use this as it enforces consistency and avoids typos etc.)
  • BreadCrumbRenderer.java renders all the html and js calls.
  • BreadCrumbTag.java – note that Primefaces 2.2M1 had this file, but 2.2RC2 does not have it. Check/compare the code – they either simplified it or it has gone missing from the latest release. I could base my version on either.
  • MenuItem.java – will probably want to look at this as each BreadCrumb item is a menu item.
  • primefaces-i.taglib.xml and primefaces-p.taglib.xml are the taglibs – I will need to define a taglib for mine (see core Javaserver faces again). Note that these are in the deployed jar but not in the sources jar.
  • faces-config.xml – defines all the component classes. As with the taglibs this is in the deployed jar but not the sources jar.

No Comments »

December 1st, 2010
11:26 am
List of useful Bookmarklets

Posted under HTML
Tags , , ,

This page here contains a number of useful commonly used bookmarklets.

Just drag them and drop them on your favourites bar to make them easily accessible.

My favourites are the following browser window resizing bookmarklets – useful for web development testing.

NoteThese Bookmarklets use the JavaScript Window.resizeTo() method which is not supported in Opera and Chrome.
Also, IE8 only supports these when a single tab is open. Use F12 in IE to do this if you have multiple tabs open.
Firefox has the best overall support for them as they will still work with multiple tabs open.

Resize to 240×320 (pocketPC)

Resize to 544×372 (webTV)

Resize to 640×480

Resize to 800×600

Resize to 1024×768

Resize to 1280×1024

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 »