I want to move the JSF AJAX triggering code written with f:ajax, f:event and the h:commandLink (with its action attribute) to Javascript by using the jsf.util.ajax. - Why ? Because to avoid globally written handler functions to handle call backs. We are migrating the JS code to AMD Require JS and JSF isn't playing very well
Previously, the code to trigger AJAX calls from the view XHTML file in JSF was written like this :
<h:commandLink id="elemID" tabindex="-1" rendered="#{something.isEnabled ('showSomeLink')}">
<f:ajax render="#{someBean.getElemId('LinkID')}" onevent="renderAjax" />
<jsft:event type="ajax"> someBean.someMethod(); </jsft:event>
</h:commandLink>
The above markup gets rendered in HTML as follows:
<a href="#" onclick="jsf.util.chain(document.getElementById('elemID'), event,'jsf.ajax.request(\'elemID\',event,{render:\'LinkID\',onevent:renderAjax,\'javax.faces.behavior.event\':\'action\'})'); return false;" id="elemID" name="elemID" tabindex="-1"></a>
Then, a click event was triggered on this link to execute the AJAX and the action.
The jsft:event
was used which behaves similarly to its counterpart f:event
.
I do not want to use this because it forces to define JS functions globally. In this case, renderAjax() in the onevent attribute of f:ajax
As you can see, This relies on a globally defined function called renderAjax()
as the callback function to execute something and render on the UI side.
The trouble is we are moving into AMD and require JS and we have no scope of defining any globals in our code (we can but we do not want to). Even if we did, this ajax call gets triggered much before Require AMD loads
So I attempted to re-write this ajax call from the client side in JS, inside an AMD module, like this (from the generated HTML snippet's onclick mentioned above)
(Point to note, I did not know how to mention the action attribute in this JS code.)
jsf.util.chain(document.getElementById('elemID'), event,'jsf.ajax.request(\'elemID\',event,{render:\'LinkID\',onevent:renderAjax,\'javax.faces.behavior.event\':\'action\'})');
Once I wrote the above code in JS, I removed the f:ajax
(since the render
and the onevent
attributes came inside the JS code itself ) and the jsft:event
from the view, in the XHTML file. Once I removed this, it began causing a continuous reload of the page, as an infinite loop. I had to stop the server and put back the code to its old state.
Next, I thought h:commandLink
was the issue and decided to replace h:commandLink
with a normally rendered anchor tag via the h:outputLink
.
Unfortunately this doesn't have an action attribute. The point is, although the element is rendered,
jsf.util.chain(document.getElementById('elemID'), event,'jsf.ajax.request(\'elemID\',event,{render:\'LinkID\',onevent:renderAjax,\'javax.faces.behavior.event\':\'action\'})');
Since my code with h:outputLink
has no action attribute, it doesnt execute the method in the managed Bean.
Suppose I included the h:commandLink
, without the jsft:event
and the f:ajax
, then it causes an infinite page reload.
I even added the action
attribute to the h:commandLink
after removing the jsft:event
and f:ajax
. I made it work with the below update.
<h:commandLink id="elemID" tabindex="-1" rendered="#{something.isEnabled ('showSomeLink')}" action="#{someBean.someMethod()}">
<f:ajax />
</h:commandLink>
and then using the following in JS:
jsf.util.chain(document.getElementById('elemID'), event,'jsf.ajax.request(\'elemID\',event,{render:\'LinkID\',onevent:renderAjax,\'javax.faces.behavior.event\':\'action\'})');
So far it works. But the problem is my renderAjax is still globally defined. Now i move my JS function into a require AMD module. The function renderAjax is no longer globally available and I move the jsf ajax code also into my module and access it as:
jsf.util.chain(document.getElementById('elemID'), event,'jsf.ajax.request(\'elemID\',event,{render:\'LinkID\',onevent:_t.renderAjax,\'javax.faces.behavior.event\':\'action\'})');
Note, _t.renderAjax() is now inside a module in Require JS. The Error i get now is _t is undefined. I guess it expects to see only global functions. The fun part is it automatically creates a wrapper function around the code. Is there any way to fix this?
(function(event
/**/) {
jsf.ajax.request('elemID',event,{render:'LinkID',onevent:_t.renderAjax,'javax.faces.behavior.event':'action'})
})
If someone could help me fix this, it would be extremely helpful.
Ok. I figured this out. I'm not sure if this is the right method. But it works.
<h:commandLink id="elemID" tabindex="-1" rendered="#{something.isEnabled ('showSomeLink')}"> <f:ajax render="#{someBean.getElemId('LinkID')}" onevent="renderAjax" /> <jsft:event type="ajax"> someBean.someMethod(); </jsft:event> </h:commandLink>
Now create a simple XHTML file called ajax.xhtml that serves as a template.
Create another file called needHelp.xhtml that uses the above file ajax.xhtml as the template
Both steps (1) and (2) have been shown in an image below: http://i61.tinypic.com/2rgypsi.png
This completes the setup. Our Ajax response is now in the JSON format and written to the response stream.
Now, in the required AMD require Module,
define('checkChannel', ['jQuery', 'ajaxHandlers'], function($, ajaxHandlers){
(function checkChannel(){
$.ajax({
url : baseURL+"/needHelp.jsf",
dataType : "json",
type: "POST",
success : ajaxHandlers.renderAjax
});
})();
})
The success handler is used to update whatever DOM element needed, based on the response flags. In this case, it would update the element with id="LinkID", as defined in the original code snippet.
Now, the renderAjax is well wrapped inside an AMD module and is no longer required to be defined globally. Now there is no need to use the and or have dummy links created via to simulate a click and trigger AJAX in JSF. If the view state needs to be maintained, it can always be got and updated in a similar manner.
Also, baseURL is the basehref of your app. For example, if the page was www.example.com/tool/index.jsf, then the baseUrl would be www.example.com/tool/. So, the AJAX URL would be www.example.com/tool/needHelp.jsf
PS: pardon the external image links as stackoverflow did not let me post pics / more links as i needed atleast 10 reputation to post. I had to use external images as I had some trouble formatting the code.