简体   繁体   中英

How to call JSF backing bean method using javascript only when The page is loaded

The problem is that the back Bean Methode get called many times

well i already read this Answer by @BalusC

JSF is a server side language which runs on the webserver upon a HTTP request and produces HTML/CSS/JS code which get returned with the HTTP response. All EL expressions in form of ${} and #{} will be executed in the server side during generating the HTML output. JavaScript is a client side language which runs on the webbrowser and works on the HTML DOM tree. The HTML onclick attribute should specify a JavaScript function which will be executed in the client side on the particular HTML DOM event.

How to call JSF backing bean method only when onclick/oncomplete/on... event occurs and not on page load

so i Understand the why my back been methode get invoked many times,So how i can resolve this issue

This is my back been Methode

@ManagedBean (name="villeBean)"
@ViewScoped
public class Villes {
 public String listeVilles{
   return dao.listeVilles();
 }
}

and this is my js Code

if(localStorage['ville'] == null || localStorage['date']==null){
        localStorage.setItem('date',new Date());
        localStorage.setItem('ville',#{villeBean.listeVilles()});
}
else{ 
  var oldDate = new Date(localStorage['date']).getTime();;
  var currentDate = new Date().getTime();
  var distance =  currentDate - oldDate ;
  var days = Math.floor(distance / (1000 * 60 * 60 * 24));
  if (days >= 15){
    localStorage.setItem('date',date);
    localStorage.setItem('ville',#{villeBean.listeVilles()});
  }
} 

The issue is that the back been methode get invoked 2 times

You can use backbean jsf outputscript component to do it, when you use jsf EL inside javascript code is normal to have multiple request problems, because one is static and other dynamic.

You may put that javascript code in one js file as a function on your resource/js folder and call that function using a Managed bean like that:

    UIOutput output = new UIOutput();       
    output.setRendererType("javax.faces.resource.Script");

    // JS FILE NAME DEFINED IN resource/js folder is put here ( example: myscript.js):
    output.getAttributes().put("name", "myscript.js");
    output.getAttributes().put("library", "js");        
    facesContext.getViewRoot().addComponentResource(facesContext, output, "form");

    UIOutput script = (UIOutput) facesContext.getApplication()
            .createComponent(facesContext,
                    JAVAX_FACES_OUTPUT_COMPONENT_TYPE,
                    JAVAX_FACES_TEXT_RENDERER_TYPE);

    UIOutput outputScript = (UIOutput) facesContext.getApplication()
            .createComponent(facesContext,
                    JAVAX_FACES_OUTPUT_COMPONENT_TYPE,
                    DEFAULT_SCRIPT_RENDERER_TYPE);

    /** AT HERE YOU SHOULD CALL YOUR JAVA SCRIPT FUNCTION WITH THE PARAMETERS OF YOUR BACK BEAN

 let's say that you have created something like that:

function addToLocalStorage(ville){
if(localStorage['ville'] == null || localStorage['date']==null){
        localStorage.setItem('date',new Date());
        localStorage.setItem('ville',ville});
}
}

  So you're going to call the function from backbean like that:
*/

    String ville = villeBean.listeVilles();
    script.setValue("addToLocalStorage(" + ville + ")");

    script.setTransient(true);

    script.setId(facesContext.getViewRoot().createUniqueId());

    outputScript.getChildren().add(script);

    outputScript.setTransient(true);
    outputScript.setId(facesContext.getViewRoot().createUniqueId());

    facesContext.getViewRoot().addComponentResource(facesContext,
            outputScript, TARGET_ATTR);

Other option is to use RequestContext from Primefaces suite, that way you may call javascript functions from JSF managed beans:

RequestContext.getCurrentInstance().execute("myfunction()");

As it turned out you actually want to have a client side cache in localStorage and prevent business logic invocation if that client side cache is valid you have to go the AJAX way:

I suggest to implement a javascript rendered conditionally which updates the local storage only if needed:

<h:panelGroup id="updateLocalStorageScript" style="display: none;">
    <h:outputScript rendered="#{villeBean.updateLocalStorage}">
        localStorage.setItem('date',date);
        localStorage.setItem('ville', #{villeBean.villesList});
    </h:outputScript>
</h:panelGroup>

The update can be triggered by a command action as proposed here: How to invoke a JSF managed bean on a HTML DOM event using native JavaScript?

<h:form id="frmHidden" style="display: none;">
    <h:commandButton id="cmdDoUpdateLocalStorage" action="#{villeBean.doUpdateLocalStorage()}">
        <f:ajax execute="@this" render=":updateLocalStorageScript" />
    </h:commandButton>
</h:form>

Of course you can also use p:remoteCommand , the OmniFaces solution or other suggestions proposed in above QA.

This command action leads to the javascript being rendered and initializes the list value by invoking your business logic only once.

@ManagedBean (name="villeBean)"
@ViewScoped
public class Villes {
    private boolean updateLocalStorage;

    private String villesList;

    public void doUpdateLocalStorage() {
        updateLocalStorage = true;
        villesList = dao.listeVilles();
    }

    public boolean isUpdateLocalStorage() {
        return updateLocalStorage;
    }

    public String getVillesList() {
        return villesList;
    }

}

Trigger that command from within your javascript conditional blocks:

if(localStorage['ville'] == null || localStorage['date']==null){
    document.getElementById('frmHidden:cmdDoUpdateLocalStorage').onclick();
}
else{ 
  var oldDate = new Date(localStorage['date']).getTime();;
  var currentDate = new Date().getTime();
  var distance =  currentDate - oldDate ;
  var days = Math.floor(distance / (1000 * 60 * 60 * 24));
  if (days >= 15){
    document.getElementById('frmHidden:cmdDoUpdateLocalStorage').onclick();
  }
} 

You have two instance of your EL-Expression #{villeBean.listeVilles()} in your javascript which is both rendered (and thus invoked) server side irregardles of your javascript condition.

Change your script like this in order to have it invoked only once:

var villesList = #{villeBean.listeVilles()};
if(localStorage['ville'] == null || localStorage['date']==null){
        localStorage.setItem('date',new Date());
        localStorage.setItem('ville', villesList);
}
else{ 
  var oldDate = new Date(localStorage['date']).getTime();;
  var currentDate = new Date().getTime();
  var distance =  currentDate - oldDate ;
  var days = Math.floor(distance / (1000 * 60 * 60 * 24));
  if (days >= 15){
    localStorage.setItem('date',date);
    localStorage.setItem('ville', villesList);
  }
} 

Advantage:

  • Business logic ( dao.listeVilles() ) is invoked only once (but there's no guarantee for this...)
  • The potentially huge list is sent to the browser only once, reducing the data transmission overhead.

Never the less it's a good idea to follow advices by Ricardo and Holger from the answer to Why JSF calls getters multiple times suggesting to change your bean to:

@ManagedBean (name="villeBean")
@ViewScoped
public class Villes {

   private String villesList;

   public String listeVilles{
       if(villesList == null) {
           villesList = dao.listeVilles();
       }
       return villesList;
   }
}

Advantage:

  • Business logic ( dao.listeVilles() ) is invoked only once for arbitrary number of occurrences of #{villeBean.listeVilles()} in your xhtml file.

Risks:

  • Each occurrence of #{villeBean.listeVilles()} in your xhtml still renders and transmit the potentially huge list.
  • Your view may see old/obsolete data when working with AJAX because this is a cache never invalidated within the same view scope.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM