Wednesday 18 July 2012

Disabling button with JavaScript in ADF Faces

Disabling ADF Faces UI component on the client-side could be tricky sometimes. This post on Jobinesh's blog shows an example on how to disable af:inputText through javascript. You basically need to set unsecure="disabled" and clientComponent="true". But if you use button.setProperty('disabled', true) on a commandButton, your button will still look like undisabled (though it won't invoke action event), proper style class and 'disabled' attribute will not be applied to the button element.
To properly disable button you can use script like this:
setButtonDisabled = function (button, disabled) {
    if (button instanceof AdfRichCommandButton) {
        button.setProperty('disabled', disabled);
        AdfDomUtils.addOrRemoveCSSClassName(disabled,
            AdfRichUIPeer.getDomElementForComponent(button),
            AdfRichUIPeer.DISABLED_STYLECLASS);
        var buttonDom = button.getPeer().getButtonElement(button);
        if (disabled) {
            buttonDom.setAttribute('disabled', 'disabled');
        } else {
            buttonDom.removeAttribute('disabled');
        }       
    }
}
After setting 'disabled' property to 'true', I'm adding/removing proper style class and adding/removing 'disabled' html attribute on <button> element.

Thursday 5 July 2012

ADF client behavior in iterator or table

Following the post about how to create client behaviors I have another tip. Suppose you need to put behavior inside an iterator or a table and you need to evaluate it for each row, like is:
<af:iterator var="row" value="...">
    <af:outputText value="...">
        <ca:customBehavior value="#{row.val}"
    </af:outputText>
</af:iterator>
In this situation the implementation method described in mentioned above post will not work.
Actually, if you look at the implementation of class BehaviorTag you will see that there exists another way to define behavior: through getBehaviorExpression(ELContext) method. Here is a snippet from BehaviorTag:
...
ValueExpression behaviorExpression = getBehaviorExpression(component);
if (behaviorExpression != null) {
    cls.addBehavior(behaviorExpression);
} else {
    String behavior = getBehavior(component);
    if (behavior != null) {
    cls.addBehavior(behavior);
}
...
First, it tries getBehaviorsExpression method and if the method returns null it calls getBehavior.
Notice, that getBehaviorExpression returns a ValueExpression object.
Now, if we want our behavior to be properly evaluated we have to implement getBehaviorExpression, and the suggested way to do this is by providing implementation of ValueExpressionBase class:
@Override
protected ValueExpression getBehaviorExpression(UIComponent uIComponent) {
    return new CustomBehaviorVE(value);
}

private static class CustomBehaviorVE extends ValueExpressionBase {

    private ValueExpression value;
    
    public CustomBehaviorVE(ValueExpression value) {
        super();
        this.value = value;
    }

    public Object getValue(ELContext elContext) {
        String val = value == null ? "" : 
            ComponentUtils.resolveString(value.getValue(elContext));
        return "new CuCustomBehavior('" + val + "')";
    }
}
In the method getValue of our ValueExpression we, basically, do the same we've done in getBehavior method before. Except that expression evaluation context ELContext is provided by method argument.
That's it. Now your behavior will properly work in an iterator, and it will be added to every client object (though there is still only one instance of Behavior's class on server).

Tuesday 3 July 2012

Creating custom client behavior

ADF's client behaviors is a wonderful feature. It allows to add custom javascript code to any component it is attached to (refer to list of default ADF behaviors if you don't know what they can do). Unfortunately, they are not in public API, but you can use ADF's internal classes (at your own risk...).
Below I will describe how to create your own client behavior in 11.1.1 release. I will focus on how to create appropriate classes and files and not the actual javascript code been added.

1. Create a tag handler class, that extends oracle.adfinternal.view.faces.taglib.behaviors.BehaviorTag:
package components.behaviors;
import javax.faces.component.UIComponent;
import oracle.adfinternal.view.faces.taglib.behaviors.BehaviorTag;

public class CustomBehavior extends BehaviorTag {
    public CustomBehavior() {
        super();
    }
    protected String getBehavior(UIComponent uIComponent) {
        return null;
    }
}
Method getBehavior must return a piece of javascript code that calls client constructor (we will implement it later).

2. If you want your behavior to accept some attributes, add fields of type ValueExpression and setters for them:
package components.behaviors;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import oracle.adfinternal.view.faces.taglib.behaviors.BehaviorTag;

public class CustomBehavior extends BehaviorTag {
  
    private ValueExpression value;
  
    public CustomBehavior() {
        super();
    }

    protected String getBehavior(UIComponent uIComponent) {
        return null;
    }

    public void setValue(ValueExpression value) {
        this.value = value;
    }
}
3. Now create a JSF tag in yout taglib, that points to our tag handler class. Add all needed attributes:
<tag>
  <name>customBehavior</name>
  <tag-class>components.behaviors.CustomBehavior</tag-class>
  <body-content>JSP</body-content>
  <attribute>
    <name>value</name>
    <deferred-value/>
  </attribute>
</tag>
4. Now we should create a javascript object, that will define our custom behavior.
Create a *.js file (in the same package "components.behaviors" or any other). You can name it anything you want, but it's better to name it the same as containing object. It is also suggested, that javascript object name has a prefix denoting your application (because these names are global in javascript). For example, all ADF objects' names start with "Adf": AdfDomUtils, AdfUIComponent, etc.
Let's name our object CaCustomBehavior (I picked prefix 'Ca'), create file src/component/behaviors/CaCustomBehavior.js. For now let's leave it empty and create an ADF js-feature.

5. Open META-INF/adf-js-features.xml file in the project that holds your taglib. (create one if it does not exist). Add a "feature" tag to the "features" element. Like this:
<?xml version="1.0" encoding="windows-1251" ?>
<features xmlns="http://xmlns.oracle.com/adf/faces/feature">
  <feature>
    <feature-name>CaCustomBehavior</feature-name>
    <feature-class>components/behaviors/CaCustomBehavior.js</feature-class>
  </feature>
</features>
feature-name preferably must correspond to your javascript object's constructor (that we are yet to create).

6. Let's tell ADF to load the js-feature we created, when our behavior is being rendered. To do this we must override method getFeatureDependency() in tag-handler class CustomBehavior to return the name of the js-feature we just created:
    @Override
    protected String getFeatureDependency() {
        return "CaCustomBehavior";
    }

7. Let's add some basic code in CaCustomBehavior.js:
CaCustomBehavior.InitClass = function(){
    // initialize class (e.g. create constants)
}

// create subclass
AdfObject.createSubclass(CaCustomBehavior, AdfClientBehavior);

// constructor
function CaCustomBehavior(value) {
    this.Init(value);
}

// object initialization
CaCustomBehavior.prototype.Init = function(value){
    AcHighlightBehavior.superclass.Init.call(this);
    this._value = value;
}

// initialize with component
CaCustomBehavior.prototype.initialize = function(component){
    // TODO: implement behavior logic here
}
Notice that our constructor accepts 1 argument - "value". But, essentially, your behavior's constructor can have any number of arguments. When there is too many of them, you could pass one object with a multiple fields, holding your arguments.
I will not describe every line of upper code here, main function you should notice is initialize(component). This function is called every time a component appears on the page. This is, actually, the place to add your custom behavior. For example, you can register event listeners on a component. You can set it to something like alert(this._value) to test that your behavior is loaded. I suggest to do it before actually writing client-side code.

8. And, finally, the last part. Define in your tag-handler how to create a client-side behavior object by implement getBehavior method. It must return a call to javascript object's constructor with "new", and without a semicolon(or you will get a javascript exception). Something like this: new CaCustomBehavior('arg').
In our getBehavior we will also calculate ValueExpression value:
    protected String getBehavior(UIComponent uIComponent) {
        ELContext elContext = FacesContext.getCurrentInstance().getELContext();
        String val = value == null ? "" : ComponentUtils.resolveString(value.getValue(elContext));
        return "new CuCustomBehavior('" + val + "')";
    }

And that's it. You can do a lot of things with a client behaviors, once you get accustomed with them, there are really useful, and also provide easy re-usability. (instead of javascript resources and clientListeners). Some things I've done with behaviors: hints inside inputText, custom autosuggest (maybe I will write post with this examples, especially if someone asks =) ) and many more...
Please leave your comments or questions. Say if this was useful to you.