Customizing Primefaces 3: Extending Renderkit

Working on a current Project needed a very rich UI in terms of custom behaviors e.g. showing a "* Required" image icon next to a UIInput component or showing two columns format for Radio buttons along with some info help text to be shown underneath each Radio button option.


Choosing Primefaces 3 for this POC, as the UI component library has come from a long drive other *faces libraries. There were couple of things I loved about Primefaces (but please let me know if I missed any other features), but I think biggest
1. one I liked was the concept of "WidgetVar" on the UIComponents. It gives you the control right back on the UI/Client side without making a Server roundtrip which I guess I didnt see in other famous *faces libraries ( may be I only missed).
2. Second is obvious JSF 2 features.
3. And trust me so thin and rich set of  components. ( hats off to Cagatay. Guys, trust me I'm not an agent for Primefaces, though looks good Job ;-) )

But I'm still learning something new about JSF 2/Primefaces/RIA everyday.

The WidgetVar helped me (I'll soon be updating that code as well) doing client side Tab navigation, client side button toggling, dialog box show/hide.

Here I'll try to show the Custom rendering I was able to achieve by extending Primefaces renderkit. First case was to show "* Required" image icon next to certain UIInput components ( I'll try to customize it further).

I've just taken Primefaces showcase application and extended that for my POC.

Note: Looks like I'm not seeing an option to attach WAR file for this project. Please let me know in case you need the code for this sample project.








And to achieve that, I had extend org.primefaces.component.selectonemenu.SelectOneMenuRenderer and with below config in faces-config.xml.

<renderer>
   <component-family>org.primefaces.component</component-family>
   <renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type>
   <renderer-class>com.sample.eduadmin.extendedTags.ExSelectOneMenuRenderer</renderer-class>
  </renderer>

And here is the code snippet from the ExSelectOneMenuRenderer.

public void encodeEnd(FacesContext context, UIComponent component)
    throws IOException {
        super.encodeEnd(context, component);   
        System.out.println(" ExSelectOneMenuRenderer encodeEnd() this "+this);        
        try{
            FacesContext facesContext = FacesContext.getCurrentInstance ();  
//            ServletContext servletContext = (ServletContext) facesContext.getExternalContext ().getContext(); 
            if(ApplicationUtils.getInstance().get(component.getClientId()) == null){
                System.out.println(" ApplicationUtils.getInstance().get(component.getClientId()) "+
                        ApplicationUtils.getInstance().get(component.getClientId()));
              
                ResponseWriter writer = context.getResponseWriter();                           
                writer.startElement("div", component);       
                writer.writeAttribute("id", component.getClientId()+":"+"selectMenuCstDivId", "id");                
               
                writer.writeAttribute("class", "click_here_required successIconCss", null);
                writer.startElement("span", null);     
                writer.endElement("span");               
                writer.endElement("div");
                 
                ApplicationUtils.getInstance().put(component.getClientId(), "true");
            }else{
               
            }
        }catch(Exception ex){    
            ex.printStackTrace();
        }


And below is the one for Two columns render layout for Radio buttons. I tried different use cases and wanted to make sure that even if there is big text in radio buttons, the rendering still should look good. Good that it worked.

<h:panelGrid columns="1">   
            <h:panelGroup>     
                <h:outputLabel for="edLevelRadio" value="#{i18n['edLevels']}" />        
            </h:panelGroup>
        </h:panelGrid>   
       
        <h:panelGrid columns="4" width="100%" border="1">       
                <p:selectOneRadio id="edLevelRadio"     
                         required="true"
                         layout="twoColumns" 
                         value="#{institutionBean.edLevelId}">
                     <f:selectItems value="#{institutionBean.edLevelsList}"/>                
                </p:selectOneRadio>
        </h:panelGrid> 

public class CustomSelectItem extends SelectItem {
   
    public CustomSelectItem(String value, String label, String subLabel){
        super(value,label);
        this.subLabel = subLabel;
    }
   
    //private String itemDesc
    private String subLabel;

    public String getSubLabel() {
        return subLabel;
    }

    public void setSubLabel(String subLabel) {
        this.subLabel = subLabel;
    }
   
}

public List<SelectItem> getEdLevelsList(){  
        if (_edLevelsList == null) {  
            _edLevelsList = new ArrayList<SelectItem>();       
           
            _edLevelsList.add(new CustomSelectItem("item1",  
                    "This is item1 from ItemsSet which contains Items This is item1 from ItemsSet <br/> which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item1)</b>")); 
            _edLevelsList.add(new CustomSelectItem("item2",
                    "This is item2 from ItemsSet which contains Items This is item1 from ItemsSet <br/> which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item2)</b>"));       
            _edLevelsList.add(new CustomSelectItem("item3",
                    "This is item3 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item3)</b>"));   
            _edLevelsList.add(new CustomSelectItem("item4",
                    "This is item4 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item4)</b>"));
            _edLevelsList.add(new CustomSelectItem("item5",   
                    "This is item5 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item5)</b>")); 
            _edLevelsList.add(new CustomSelectItem("item6", 
                    "This is item6 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items <br/> This is item1 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item6)</b>"));         
            _edLevelsList.add(new CustomSelectItem("item7",    
                    "This is item7 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item7)</b>"));    
            _edLevelsList.add(new CustomSelectItem("item8",
                    "This is item8 from ItemsSet which contains Items", 
                    "<b>((Custom Text) Help Info: This is the help info for Item8)</b>"));     
            _edLevelsList.add(new CustomSelectItem("item9", 
                    "This is item9 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item9)</b>"));   
            _edLevelsList.add(new CustomSelectItem("item10",
                    "This is item10 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item10)</b>"));
            _edLevelsList.add(new CustomSelectItem("item11",
                    "This is item11 from ItemsSet which contains Items",
                    "<b>((Custom Text) Help Info: This is the help info for Item11)</b>"));
        }                
        return _edLevelsList;                        
    }

And for this I had to extend org.primefaces.component.selectoneradio.SelectOneRadioRenderer and put the custom class in the same package as the method "encodeSelectItems" is protected but I'd to override that method.


protected void encodeSelectItems(FacesContext context, SelectOneRadio radio) throws IOException {
}

<renderer>
   <component-family>org.primefaces.component</component-family>
   <renderer-type>org.primefaces.component.SelectOneRadioRenderer</renderer-type>
   <renderer-class>org.primefaces.component.selectoneradio.ExSelectOneRadioRenderer</renderer-class>
  </renderer>

And the code snippet ( this was a little painful).

protected void encodeSelectItems(FacesContext context, SelectOneRadio radio) throws IOException {
        try{
            System.out.println(" ExSelectOneRadioRenderer encodeSelectItems() ");
            ResponseWriter writer = context.getResponseWriter();
            List<SelectItem> selectItems = getSelectItems(context, radio);
            Converter converter = getConverter(context, radio);
            Object value = radio.getValue();
            String layout = radio.getLayout();
            boolean pageDirectionTwoColumns = layout != null && layout.equals("twoColumns");
            boolean pageDirection = layout != null && layout.equals("pageDirection");
   
            if(pageDirectionTwoColumns){   
                Iterator<SelectItem> selectItemsItr = selectItems.iterator();   
                while(selectItemsItr.hasNext()){
                      writer.startElement("tr", null);
                     
                          SelectItem selectItem1 = null;
                          SelectItem selectItem2 = null;
                         
                          if(selectItemsItr.hasNext()){
                              writer.startElement("td", null);
                              writer.startElement("table", null);
                             
                                  selectItem1 = selectItemsItr.next();
                                  Object itemValue1 = selectItem1.getValue();
                                  String itemLabel1 = selectItem1.getLabel();
                                 
                                  writer.startElement("tr", null);
                                  encodeOption(context, radio, value, converter, itemLabel1, itemValue1);
                                  writer.endElement("tr");
                                 
                                  CustomSelectItem cstselectItem1 = (CustomSelectItem)selectItem1;
                                 
                                  if(cstselectItem1.getSubLabel() != null){
                                      writer.startElement("tr", null);
                                      String itemSubLabel1 = cstselectItem1.getSubLabel();
                                     
                                      writer.startElement("td", null);
                                      writer.endElement("td");
                                     
                                      writer.startElement("td", null);
                                      writer.write(itemSubLabel1);
                                      writer.endElement("td");
                                     
                                      writer.endElement("tr");
                                  }
                              writer.endElement("table");  
                              writer.endElement("td");
                          }  
                          if(selectItemsItr.hasNext()){ 
                              writer.startElement("td", null);
                              writer.startElement("table", null);
                                  selectItem2 = selectItemsItr.next();
                                  Object itemValue2 = selectItem2.getValue();
                                  String itemLabel2 = selectItem2.getLabel();
                                 
                                  writer.startElement("tr", null);
                                  encodeOption(context, radio, value, converter, itemLabel2, itemValue2);
                                  writer.endElement("tr");
                                   
                                  CustomSelectItem cstselectItem2 = (CustomSelectItem)selectItem2;
                                  if(cstselectItem2.getSubLabel() != null){ 
                                     
                                      writer.startElement("tr", null);
                                        String itemSubLabel2 = cstselectItem2.getSubLabel(); 
                                       
                                       writer.startElement("td", null);
                                      writer.endElement("td");
                                     
                                      writer.startElement("td", null);
                                      writer.write(itemSubLabel2);
                                      writer.endElement("td");
                                      
                                      writer.endElement("tr");      
                                  } 
                              writer.endElement("table");        
                              writer.endElement("td");   
                          }
                      writer.endElement("tr");
                }  
            }else{  
                for(SelectItem selectItem : selectItems) {                
                    Object itemValue = selectItem.getValue();
                    String itemLabel = selectItem.getLabel();
                    if(pageDirection) { 
                        writer.startElement("tr", null);
                    }   
                    encodeOption(context, radio, value, converter, itemLabel, itemValue);  
                    if(pageDirection) {   
                        writer.endElement("tr");
                    }
                }
            }
        }catch(Exception ex){
            ex.printStackTrace();    
        }      
    }

protected void encodeOption(FacesContext context, UIInput component, Object componentValue, Converter converter, String label, Object value) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        SelectOneRadio radio = (SelectOneRadio) component;
        String formattedValue = getOptionAsString(context, component, converter, value);
        String clientId = component.getClientId(context);
        String containerClientId = component.getContainerClientId(context);
        boolean checked = componentValue != null && componentValue.equals(value);
        boolean disabled = radio.isDisabled();
 
        writer.startElement("td", null);
        writer.writeAttribute("valign", "top", null); // custom code
 
        String styleClass = "ui-radiobutton ui-widget";
        if(disabled) {
            styleClass += " ui-state-disabled";
        }

        writer.startElement("div", null);
        writer.writeAttribute("class", styleClass, null);

        encodeOptionInput(context, radio, clientId, containerClientId, checked, disabled, label, formattedValue);
        encodeOptionOutput(context, radio, checked);

        writer.endElement("div");
        writer.endElement("td");

        writer.startElement("td", null);
        encodeOptionLabel(context, radio, containerClientId, label);
        writer.endElement("td");
    }

I wanted to share this with you all as I found it a little tough to find good example showing how to extend Renderkits in JSF. I'll still try to keep this blog up to date based on my work and updates from you in case my information is misleading.

Thanks for reading...

14 comments:

  1. Thank you Vikas, I was looking for something like this. It was very helpful

    ReplyDelete
  2. Thank you for this blog it will be helpful if you can send me sample code ertugrultas@gmail.com

    ReplyDelete
  3. @maniacneron, I'll try to find that code and send it you.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Thank you for this tutorial. Can you send me sample code, it will be helpful. My email is picopixel@gmail.com

    ReplyDelete
  6. Thank you very much!
    It works perfectly with SelectOneRadio but not Schedule. I'm still trying to work with the Schedule.

    ReplyDelete
  7. Hi, can you send me the sample code on my email id rahul_606@yahoo.com Thanks in advance....

    ReplyDelete
  8. Very thanks for your tutorial. Can you help me and send the sample code to my email : zahrakarimi181@gmail.com

    ReplyDelete
  9. Excellent blog. This is the one i am looking for.
    Please send me source code sample to elan_as@yahoo.com.

    ReplyDelete
  10. Fantabulous! This is probably the only article worth the subject, done well. Looked @ n places before reaching this place.

    Can you please share the code kanwarmanish@gmail.com

    Thanks
    Manish

    ReplyDelete
  11. Good work !! Can you please share your code.
    Please also try to use some kind of syntax highlighter and formatting to make your posts more attractive and useful.

    ReplyDelete
  12. thanks for your effort, put my mind to the right path of searching

    ReplyDelete