Monday, April 19, 2010

Icefaces custom slider component

Icefaces open source edition does not come with a Slider UI component. I've created a custom component that is based on scriptaculous (http://wiki.github.com/madrobby/scriptaculous/slider) javascript widget. This currently supports onChange event and also background images for the track of the slider. This could be easily modified to support any style you would want through css.

SlideEvent.java class

package com.hasika;

import javax.faces.component.*;

public class SlideEvent extends FacesEvent {

    private static final long serialVersionUID = 1L;
    private int sliderNewValue;
    private int sliderOldValue;
    
    public SlideEvent(UIComponent component, int sliderNewValue) {
        super(component);
        setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
        this.sliderNewValue = sliderNewValue;
    }
         
    public int getSliderNewValue() {
        return sliderNewValue;
    }

    public void setSliderNewValue(int sliderNewValue) {
        this.sliderNewValue = sliderNewValue;
    }

    public int getSliderOldValue() {
        return sliderOldValue;
    }

    public void setSliderOldValue(int sliderOldValue) {
        this.sliderOldValue = sliderOldValue;
    }

    @Override
    public boolean isAppropriateListener(FacesListener arg0) {
        return false;
    }

    @Override
    public void processListener(FacesListener arg0) {
        
    }

}


Slider.java class

package com.hasika;
import javax.el.*;
import javax.faces.component.html.*;
import javax.faces.context.*;
import javax.faces.event.*;

import com.icesoft.faces.context.effects.*;

public class Slider extends HtmlPanelGroup {
    
    public static final String RENDERER_TYPE = "com.hasika.Slider";
    public static final String COMPONENT_FAMILY = "com.hasika.Slider";
    
    private MethodExpression slideListener;
    private Boolean slider;
    private String axis;
    private String trackStyleClass;
    private String needleStyleClass;
    private Integer sliderValue;
    private String range;
    private String needleValues;
    private String handleImage;
    private String imageDir;
    private String trackImages;
    private Boolean partialSubmit;
    private Integer increment;
    private Integer minimum;
    private Integer maximum;
    
    public Slider() {
        super();
        setRendererType(RENDERER_TYPE);
    }
    
    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    public void processDecodes(javax.faces.context.FacesContext context) {       
          decode(context);
    }
    
    @Override
    public Object saveState(FacesContext context) {
        Object values[] = new Object[16];
        values[0] = super.saveState(context);
        values[1] = saveAttachedState(context, slideListener);
        values[2] = slider;
        values[3] = axis;
        values[4] = sliderValue;
        values[5] = range;
        values[6] = needleValues;
        values[7] = handleImage;
        values[8] = increment;
        values[9] = minimum;
        values[10] = maximum;
        values[11] = trackStyleClass;
        values[12] = needleStyleClass;
        values[13] = imageDir;
        values[14] = trackImages;
        values[15] = partialSubmit;
        
        return values;
    }
    
    @Override
    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        slideListener = (MethodExpression)restoreAttachedState(context, values[1]);
        slider = (Boolean) values[2];
        axis = (String) values[3];
        sliderValue = (Integer) values[4];
        range = (String) values[5];
        needleValues = (String) values[6];
        handleImage = (String) values[7];
        increment = (Integer) values[8];
        minimum = (Integer) values[9];
        maximum = (Integer) values[10];
        trackStyleClass = (String) values[11];
        needleStyleClass = (String) values[12];
        imageDir = (String) values[13];
        trackImages = (String) values[14];
        partialSubmit = (Boolean) values[15];
    }
    
    @Override
    public void broadcast(FacesEvent event) throws AbortProcessingException {
        super.broadcast(event);

        if (event instanceof SlideEvent && slideListener != null) {
            Object[] oa = {(SlideEvent) event};
            slideListener.invoke(getFacesContext().getELContext(), oa);
        }
       
    }
    
    @Override
    public void queueEvent(FacesEvent event) {
        if (event instanceof SlideEvent) {
            event.setPhaseId(PhaseId.UPDATE_MODEL_VALUES);
        }
        super.queueEvent(event);
    }
        
    public MethodExpression getSlideListener() {        
        return slideListener;
    }

    public void setSlideListener(MethodExpression slideListener) {
        this.slideListener = slideListener;
    }
    
    public boolean isSlider() {
        if (slider != null)
            return slider.booleanValue();

        ValueExpression ve = this.getValueExpression("slider");
        
        if(ve != null){
            Boolean value = (Boolean)ve.getValue(this.getFacesContext().getELContext());
            return (value.booleanValue());
        } else {
            return false;
        }          
    }

    public void setSlider(boolean slider) {
        this.slider = Boolean.valueOf(slider);
        JavascriptContext.includeLib(JavascriptContext.ICE_EXTRAS, getFacesContext());
    }

    public String getAxis() {
        return axis;
    }

    public void setAxis(String axis) {
        this.axis = axis;
    }

    public String getTrackStyleClass() {
        return trackStyleClass;
    }

    public void setTrackStyleClass(String trackStyleClass) {
        this.trackStyleClass = trackStyleClass;
    }

    public String getNeedleStyleClass() {
        return needleStyleClass;
    }

    public void setNeedleStyleClass(String needleStyleClass) {
        this.needleStyleClass = needleStyleClass;
    }

    public int getSliderValue() {
        if(sliderValue != null){
            return sliderValue.intValue();
        }
        
        ValueExpression ve = this.getValueExpression("sliderValue");
        
        if(ve != null){
            Integer value = (Integer)ve.getValue(this.getFacesContext().getELContext());
            return (value.intValue());
        } else {
            return -1;
        }
    }

    public void setSliderValue(int sliderValue) {
        this.sliderValue = new Integer(sliderValue);
    }

    public String getRange() {
        if(range != null) {
            return range;
        }
        
        ValueExpression ve = this.getValueExpression("range");
        return ve != null ? (String) ve.getValue(getFacesContext().getELContext()) : null;
    }

    public void setRange(String range) {
        this.range = range;
    }

    public String getNeedleValues() {
        if(needleValues != null) {
            return needleValues;
        }
        
        ValueExpression ve = this.getValueExpression("needle");
        return ve != null ? (String) ve.getValue(getFacesContext().getELContext()) : null;
    }

    public void setNeedleValues(String needleValues) {
        this.needleValues = needleValues;
    }

    public String getHandleImage() {
        if(handleImage != null) {
            return handleImage;
        }
        
        ValueExpression ve = this.getValueExpression("handleImage");
        return ve != null ? (String) ve.getValue(getFacesContext().getELContext()) : null;
    }

    public void setHandleImage(String handleImage) {
        this.handleImage = handleImage;
    }

    public String getImageDir() {
        if(imageDir != null) {
            return imageDir;
        }
        ValueExpression ve = this.getValueExpression("imageDir");
        return ve != null ? (String) ve.getValue(getFacesContext().getELContext()) : null;
    }

    public void setImageDir(String imageDir) {
        this.imageDir = imageDir;
    }

    public String getTrackImages() {
        if(trackImages != null) {
            return trackImages;
        }
        ValueExpression ve = this.getValueExpression("trackImages");
        return ve != null ? (String) ve.getValue(getFacesContext().getELContext()) : null;
    }

    public boolean isPartialSubmit() {
        if (partialSubmit != null)
            return partialSubmit.booleanValue();

        ValueExpression ve = this.getValueExpression("partialSubmit");
        
        if(ve != null){
            Boolean value = (Boolean)ve.getValue(this.getFacesContext().getELContext());
            return (value.booleanValue());
        } else {
            return false;
        }   
    }

    public void setPartialSubmit(boolean partialSubmit) {
        this.partialSubmit = Boolean.valueOf(partialSubmit);
    }

    public void setTrackImages(String trackImages) {
        this.trackImages = trackImages;
    }

    public int getIncrement() {
        if(increment != null){
            return increment.intValue();
        }
        
        ValueExpression ve = this.getValueExpression("increment");
        
        if(ve != null){
            Integer value = (Integer)ve.getValue(this.getFacesContext().getELContext());
            return (value.intValue());
        } else {
            return -1;
        }
    }

    public void setIncrement(int increment) {
        this.increment = new Integer(increment);
    }

    public int getMinimum() {
        if(minimum != null){
            return minimum.intValue();
        }
        
        ValueExpression ve = this.getValueExpression("minimum");
        
        if(ve != null){
            Integer value = (Integer)ve.getValue(this.getFacesContext().getELContext());
            return (value.intValue());
        } else {
            return -1;
        }
    }

    public void setMinimum(int minimum) {
        this.minimum = new Integer(minimum);
    }

    public int getMaximum() {
        if(maximum != null){
            return maximum.intValue();
        }
        
        ValueExpression ve = this.getValueExpression("maximum");
        
        if(ve != null){
            Integer value = (Integer)ve.getValue(this.getFacesContext().getELContext());
            return (value.intValue());
        } else {
            return -1;
        }
    }

    public void setMaximum(int maximum) {
        this.maximum = new Integer(maximum);
    }

    
}


SliderHandler.java class
package com.hasika;
import com.sun.facelets.tag.*;

public class SliderHandler extends ComponentHandler {

    public SliderHandler(ComponentConfig config) {
        super(config);
    }
    
    protected MetaRuleset createMetaRuleset(Class type) {
        MetaRuleset m = super.createMetaRuleset(type);
        if( tag.getNamespace() != null && tag.getNamespace().equals("http://hasika.com/facelets")) {
            if( tag.getLocalName().equals("slider") ) {
                m.addRule( new MethodRule("slideListener", null, new Class[] {SlideEvent.class}) );
            }            
        }
        return m;
    }

}


SliderRenderer.java class

package com.hasika;

import java.io.*;
import java.util.*;

import javax.el.*;
import javax.faces.component.*;
import javax.faces.context.*;

import org.w3c.dom.*;

import com.icesoft.faces.context.*;
import com.icesoft.faces.context.effects.*;
import com.icesoft.faces.renderkit.dom_html_basic.*;
import com.icesoft.util.pooling.*;

public class SliderRenderer extends DomBasicRenderer {
    
    //private final static Logger logger = Logger.getLogger();
        
    public void encodeBegin(FacesContext facesContext, UIComponent uiComponent)
    throws IOException {
        try {
           
            String clientId = uiComponent.getClientId(facesContext);
            Slider sliderPanel = (Slider) uiComponent;
            DOMContext domContext = DOMContext.attachDOMContext(facesContext, uiComponent);
           
            if (!domContext.isInitialized()) {
                
                UIComponent form = findForm(uiComponent);
                String formId = form.getClientId(facesContext);
                
                Element rootSpan = domContext.createElement(HTML.DIV_ELEM);
                
                setRootElementId(facesContext, rootSpan, uiComponent);
                domContext.setRootNode(rootSpan);
                
                Element trackDiv = domContext.createElement(HTML.DIV_ELEM);
                trackDiv.setAttribute(HTML.ID_ATTR, ClientIdPool.get(clientId + "track"));
                trackDiv.setAttribute(HTML.CLASS_ATTR, sliderPanel.getTrackStyleClass());
                
                String imageDir = sliderPanel.getImageDir() == null ? "" : sliderPanel.getImageDir();
                
                String [] trackImages = null;
                if(sliderPanel.getTrackImages() != null) {
                    trackImages = sliderPanel.getTrackImages().split(",");
                    if(trackImages != null) {
                        String trackImg = "";
                        try {
                            trackImg = trackImages[sliderPanel.getSliderValue()];
                        } catch (Exception e) {
                            trackImg = trackImages[0];
                        }
                        trackDiv.setAttribute(HTML.STYLE_ATTR, "background-image:url(" + imageDir + "/" + trackImg +");background-repeat:no-repeat;background-position:center;");
                        
                    }
                }
                                
                Element needleDiv = domContext.createElement(HTML.DIV_ELEM);
                needleDiv.setAttribute(HTML.ID_ATTR, ClientIdPool.get(clientId + "needle"));
                needleDiv.setAttribute(HTML.CLASS_ATTR, sliderPanel.getNeedleStyleClass());
                
                if(sliderPanel.getHandleImage() != null) {
                    Element needleImg = domContext.createElement(HTML.IMG_ELEM);
                    needleImg.setAttribute(HTML.SRC_ATTR, imageDir + "/" + sliderPanel.getHandleImage());
                    needleDiv.appendChild(needleImg);
                }
                               
                trackDiv.appendChild(needleDiv);
                                
                rootSpan.appendChild(trackDiv);
                         
                String hiddenId = ClientIdPool.get(formId + "slider_hidden");
               
                Element hiddenField = domContext.createElement(HTML.INPUT_ELEM);
                hiddenField.setAttribute(HTML.NAME_ATTR, hiddenId);
                hiddenField.setAttribute(HTML.ID_ATTR, hiddenId);
                hiddenField.setAttribute(HTML.TYPE_ATTR, "hidden");
                hiddenField.setAttribute(HTML.VALUE_ATTR, "");

                rootSpan.appendChild(hiddenField);
                                
                String call = addSliderJavascriptCalls(sliderPanel, facesContext, clientId, hiddenId, formId);
                
                if(call != null && call.length() > 0 ) {
                    Element scriptDiv = domContext.createRootElement(HTML.DIV_ELEM);
                    
                    Element script = domContext.createElement(HTML.SCRIPT_ELEM);
                    script.setAttribute(HTML.TYPE_ATTR, "text/javascript");
                    script.appendChild(domContext.createTextNode(call));
                    scriptDiv.appendChild(script);
                }
                                      
                domContext.stepInto(uiComponent);
    
            }

        } catch (Exception e) {
            //logger.error("Error rendering slider component", e);
        }
    }
    
    public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) throws IOException {
        validateParameters(facesContext, uiComponent, null);
        DOMContext domContext = DOMContext.getDOMContext(facesContext, uiComponent);
        domContext.stepOver();
    }
    
    public void decode(FacesContext context, UIComponent component) {
        super.decode(context, component);
        
        if (component instanceof Slider) {
            Slider slider = (Slider) component;
            
            Map requestMap = context.getExternalContext().getRequestParameterMap();
           
            UIComponent form = findForm(component);
            String formId = form.getClientId(context);
            String hdnFld = ClientIdPool.get(formId+ "slider_hidden");
            if (!requestMap.containsKey(hdnFld) || requestMap.get(hdnFld).toString().equals("")) {
                return;
            }
            
            String value = String.valueOf(requestMap.get(hdnFld));
            
            if(value == null || "".equals(value)){
                return;
            }
            
            if(slider.getSlideListener() != null){
                MethodExpression listener = slider.getSlideListener();
                
                if (listener != null) {
                    
                    SlideEvent event = new SlideEvent(component, Integer.valueOf(value));
                    slider.queueEvent(event);
                }
            }
            
        }
    }
    
        
    private String addSliderJavascriptCalls(Slider slider, FacesContext facesContext, String clientId, String hiddenId, String formId) {
               
        EffectsArguments ea = new EffectsArguments();
        ea.add("axis", slider.getAxis());
        
        if(slider.getSliderValue() >= 0) {
            ea.add("sliderValue", slider.getSliderValue());
        }
        
        if(slider.getMinimum() >= 0) {
            ea.add("minimum", slider.getMinimum());
        }
        
        if(slider.getMaximum() >= 0) {
            ea.add("maximum", slider.getMaximum());
        }
        
        if(slider.getIncrement() >= 0) {
            ea.add("increment", slider.getIncrement());
        }
        
        if(slider.getRange() != null) {
            ea.addFunction("range", "$R(" + slider.getRange() + ")");
        }
        
        if(slider.getNeedleValues() != null) {
            ea.addFunction("values", "[" + slider.getNeedleValues() + "]");
        }
         
        String sliderObject = "var slider = new Control.Slider('" + clientId + "needle','" + clientId + "track'" + ea.toString();
                
        StringBuilder onChangeFunction = new StringBuilder(" slider.options.onChange = function(value) {  ");
        onChangeFunction.append(" document.getElementById('"+ hiddenId+"').value = value; ");
        
        if(slider.isPartialSubmit()) {
            onChangeFunction.append(" iceSubmitPartial");
        } else {
            onChangeFunction.append(" iceSubmit");
        }
        onChangeFunction.append(" (document.forms['" + formId + "'], document.getElementById('"+ clientId+"'), Event.MOUSEMOVE);");
        onChangeFunction.append(" }; ");
        
        StringBuilder jsCall = new StringBuilder();
        jsCall.append("jQuery(document).ready(function($) { ");
        jsCall.append(sliderObject);
        jsCall.append(onChangeFunction);
        jsCall.append(" }); ");
        
        if(hasInitiatedSubmit(facesContext, hiddenId)) {
            JavascriptContext.addJavascriptCall(facesContext, new StringBuilder(sliderObject).append(onChangeFunction).toString());
            return "";
        }
                
        return jsCall.toString();
    }
    
    public boolean hasInitiatedSubmit(FacesContext context, String hiddenId) {
        Map requestMap = context.getExternalContext().getRequestParameterMap();

        if (requestMap.containsKey(hiddenId) && !requestMap.get(hiddenId).toString().equals("")) {
            requestMap.remove(hiddenId);
            return true;
        }
        return false;
    }
    

}


SliderBean.java class
public class SliderBean {
 
 private int sliderValue = 0;
 
 public void slideValueListener(SlideEvent event) { 
  sliderValue = event.getSliderNewValue();
  
  // do 
 }

 public int getSliderValue() {
  return sliderValue;
 }

}

In your facelets.taglib.xml file tag definition should be added

   slider
   
     com.hasika.Slider
     com.hasika.Slider
     com.hasika.SliderHandler
   
 

faces-config.xml should include

     com.hasika.Slider
     com.hasika.Slider
 
       
    
     
      com.hasika.Slider
      com.hasika.Slider     
      com.hasika.SliderRenderer
         
    

sample usage in a jspx file