1

I have the following scenario:

JSF composite component with complex JavaScript, which I'd like to refresh partially by JavaScript with a new values from a backing bean (backing bean of a page which uses this composite component, not backing component @FacesComponent). I don't want to make full update because it's complex JavaScript plugin and it will unacceptably break UX.

I get values from backing component @FacesComponent by using Primefaces's <p:remoteCommand> with callback as described here Best method for passing Data from Java/JSF2 bean to Javascript/jQuery Components

I know that it's some abuse of JSF but would like to encapsulate all the functionality in a single unit and don't mess with a JAX-RS If you can advice another nice solution how to encapsulate such complex jQuery plugin (for sake of clarity we are talking about FullCalendar, I know that Primefaces has its own implementation of this component but its functionality insufficient for my requirement so, I was need to make my own implementation) which highly related on ajax callbacks with parameters you're highly welcome to share it here.

My question is how to update values in a backing component @FacesComponent from backing bean by using JavaScript? Currently I involved in the following chain of events:

  1. calling from Javascript <p:remoteCommand> with parameters which passed to backing component @FacesComponent to be dispatched later in AjaxBehaviorEvent

    JavaScript:

           refreshEvents([
                  {name:'start', value:start.format()}, 
                  {name:'end', value:end.format()}
           ]);
    

    JSF code:

     <p:remoteCommand name="refreshValues" oncomplete="loadValues()"  action="#{cc.refreshLocal()}" process="@this"/>
    

    Parameters which I passed stored in a backing component by using

    getStateHelper().put(...);
    
  2. jQuery event dispatched from composite component by following JavaScript code:

        var hiddenField = $(document.getElementById(variables.hiddenId));
        hiddenField.trigger("keypress");
    
  3. In composite component's overridden method public void queueEvent(FacesEvent event) I add to this AjaxBehaviorEvent property which I stored before, in a 1st step and dispatch it forward.

  4. Dispatched event from composite component "captured" in a page where composite component nested and performed process on this component:

    <p:ajax event="refreshEvent" process="@this"  listener="#{bean.refreshEvents}"/>
    

    in #{bean.refreshEvent} method I perform request to @EJB bean and load data.

  5. On callback from step 1 called again by loadValues()

    <p:remoteCommand name="loadValues" action="#{cc.getLocalData()}" oncomplete="updateValues(xhr, status, args);"/>

  6. In a backing component's method #{cc.getLocalData()} I add a callback parameter by using:

    RequestContext.getCurrentInstance().addCallbackParam("param", ...);

  7. function updateValues(xhr, status, args) from step 5 get in args this param's values and performs actual update.

So, my general question is it possible to simplify this process and how?

Thank you.

Community
  • 1
  • 1
Anatoly
  • 5,056
  • 9
  • 62
  • 136
  • 1
    Take a look at the latest PF snapshot or elite release. It has extender functionality. And if you do use PF, why not download the source, create a patch and submit it? – Kukeltje May 14 '15 at 13:27
  • There're two problems. First I'm not there yet, I've a lot to learn to make it. Second, due to policy of my job it isn't simple to share our code, not impossible but not simple. Actually I just looked at the source code. I think that all I need I can do by overriding `protected void encodeEvents(FacesContext context, Schedule schedule) `, possible it isn't such difficult as I thought, thank you for your hit. – Anatoly May 14 '15 at 13:35

1 Answers1

2

This is indeed a little overcomplicated. In total 3 ajax requests to just perform an action and a backing component passing data forth and back via the view state.

Your primary goal appears to be able to declare a bean action method as composite component attribute which should then be invoked by a <p:remoteCommand> inside the composite and return the desired model entity based on the passed-in parameters, preferably with some pre- and post-processing.

You can use <cc:attribute method-signature> in the composite interface to declare a method expression attribute:

<cc:interface componentType="yourComposite">
    <cc:attribute name="eventListener" method-signature="com.example.Entity method(java.lang.String, java.lang.String)" required="true" />
</cc:interface>

Which can be used in the template client as below:

<my:composite ... eventListener="#{bean.eventListener}" />
public Entity eventListener(String start, String end) {
    // ...
    return entity;
}

The composite implementation can look like this:

<cc:implementation>
    ...
    <p:remoteCommand name="refreshEvents" 
        process="@this" action="#{cc.processEventListener}"
        oncomplete="updateValues(xhr, status, args)" />
    ...
</cc:implementation>

The #{cc.processEventListener} method can obtain the eventListener attribute as a MethodExpression and invoke it as below, along with some pre- and post-processing:

public void processEventListener() {
    String start = getRequestParameter("start");
    String end = getRequestParameter("end");
    // ...
    MethodExpression method = (MethodExpression) getAttributes().get("eventListener");
    Entity entity = (Entity) eventListener.invoke(getFacesContext().getELContext(), new Object[] { start, end });
    // ...
    addCallbackParam("param", entityAsJSON);
}

Now it's only 1 ajax request "as usual".

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • But for this scenario I should create json in webbean and I'd like to encapsulate all this functionality (POJO -> json) in a backing component, is it possible to implement and stay with 1 ajax request? – Anatoly Jun 19 '15 at 06:41