Oncomplete JavaScript does not re-evaluate EL expression at time of execution - javascript

I have a Primefaces dialog that contains a 'Save' button with the following oncomplete rules:
<p:dialog id="dialogId" widgetVar="dialogWidget">
...
<p:commandButton value="Save" async="true"
oncomplete="if (!args.validationFailed && #{xp:hasNoMessagesToDisplay(dialogId)}) dialogWidget.hide()"
partialSubmit="true" process="dialog"/>
</p:dialog>
#{xp:hasNoMessagesToDisplay} is a custom EL function that checks to see if there are any FacesMessages associated for the dialogId specified. The problem is that the evaluation of this function only takes place when HTML is rendered for the dialog (e.g. on initial page load). I need the function to be evaluated when the oncomplete Javascript is evaluted (i.e. on the fly).
Is it possible to extend the Primefaces "args" Javascript object to include additional info, or what are alternative approaches?

Got an idea from this post which gave me a solution:
Use FacesContext.getCurrentInstance().validationFailed(); when the FacesMessages for the dialog are created to indicate JSF validation failure so
oncomplete="if (!args.validationFailed) dialogwidget.hide()"
is sufficient. I'm not sure what implications this has, guess I'll just have to wait and see....

Related

Disable a button without changing its appearance

I implemented a dynamic enable/disable function in a page thanks to a javascript function calling a backing java bean method that sets to true/false a boolean, which is then used to disable (or not) a primefaces commandLink button.
Everything works fine, but I wanted to know if instead of changing the appearance of the button when is it disabled, I could keep the regular appearance (and even better, printing an alert message when trying to click it without having to render another web element).
Here are the simplified pieces of code:
Javascript function:
function enableSubmit(){
jQuery(element).click(function(){
if (condition){
rc_enable();
} else {
rc_disable();
}
});
}
And the primefaces commandButton together with the remoteCommands:
<p:remoteCommand name="rc_disable" update="submitButton" actionListener="#{mappenBean.setDisabled}" />
<p:remoteCommand name="rc_enable" update="submitButton" actionListener="#{mappenBean.setEnabled}" />
<h:commandLink id="submitButton" action="#{mappenBean.updateFund}" styleClass="FormButton" rendered="#{!sitzungBean.gedruckt}" disabled="#{!mappenBean.enabled}">
...[action listeners etc.]
<h:outputText value="Eingaben übernehmen" />
</h:commandLink>
The java bean functions simply set the boolean to true or false.
It seems that I cannot implement a javascript click() function to display an alert if the disabled button is clicked, because once disabled it is not recognized anymore.
The primefaces version is 2.2.1 (i know...) and JSF is 2.1
Any help will be welcome, thanks a lot!
you can add css class to the button to be disabled and disable mouse events
.disabled-button{
pointer-events: none;
}
it wont change the appearance but it will do the trick
You can put a validate function (client side) to check if the condition is true/false and then show the message or submit the action.
First remove the disabled="#{!mappenBean.enabled}"
The button will be always enable. But it will not be submitted if the condition is false.
Add an onclick="return validate();" to your commandLink
Create a javascript function and a dialog for the message.
<script type= "text/javascript">
function validate(){
if(condition){
dialogMessage.show(); // or an alert
return true;
}else{
return false;
}
</script>
Your xhtml will be something like this:
<h:commandLink id="submitButton" action="#{mappenBean.updateFund}" styleClass="FormButton" rendered="#{!sitzungBean.gedruckt}" onclick="return validate();" >
...[action listeners etc.]
<h:outputText value="Eingaben übernehmen" />
</h:commandLink>
I finally managed to do so by adding a click handler on the commandLink with the following code:
onclick="if(#{!mappenBean.enabled}) {alert('test'); return false;}"
However, as Kukeltje pointed out:
But keep in mind that this is a fake and insecure way of disabling. People with a browser developer tool can easily circumvent this. There is a very good and valid reason JSF does all this serverside (never trust the client). It might better be done the other way around. Disable it server side and via css 'enable' it visually and clickable client side!!!
I will keep it running that way for now as it was urgent and I will try and implement the right way of doing it.
Thanks a lot to everyone for your help!

jsf validation before js and js before bean action

JSF / PrimeFaces 3.5
I need when clicking on p:commandButton to check the validation first of all (input text required=true)
if validationFail == false then
Call the popup dialog from js :
else
show requiredMessage from inputText (this field is mandatory...)
I have tried with oncomplete but it calls my bean and after the js popup dialog. I dont want it.
I need in this order : click p:button -> check validation -> if not fails -> show primefaces dialog.
if fails after validation-> render message
My xhtml :
<p:commandButton id="btnSalvar"
value="abc"
action="#{notaFiscalManagedBean.salvar}"
oncomplete="if (args.validationFailed) return true; else return showPF_DiagBox()"
in my showPF dialog I call bean method. if OK clicked by user.
It is better to user RequestContext of primefaces which allows user to execute javascript which is set from managed bean. You can use it by modifying your method at #{notaFiscalManagedBean.salvar} as shown below.
public String salvar(){
boolean valid=true;
//Do your validation here
if(valid){
RequestContext.getCurrentInstance().execute("showPF_DiagBox()");
}
}
If you want to do the validation on client side before submitting the request to the server then just do the following change in your code,
<p:commandButton id="btnSalvar"
value="abc"
action="#{notaFiscalManagedBean.salvar}"
onclick="if(validationFailed()){return false}"
oncomplete="showPF_DiagBox()"/>
Also write down a javascript function to do validations
function validationFailed(){
//Check various conditions based on component validations and return whether validatoin failed or not
}
Try this:
<p:commandButton id="btnSalvar"
value="abc"
action="#{notaFiscalManagedBean.salvar}"
update="#form"
render="#form"/>
By adding 'render' and 'update' attributed your form will have to reload, and then process all the validations inside of it's form.
Hope this can help you, good luck!
In my oncomplete commandButton I got what I wanted getting from #BalusC answer (How to find indication of a Validation error (required="true") while doing ajax command) and #Tuukka Mustonen (JSF 2.0 AJAX: Call a bean method from javascript with jsf.ajax.request (or some other way)) and making some adjustments to fit my needs.
This way, if there are any validation errors or any converters do be validated, they are rendered at screen first of all. If there are not validation errors so I execute my js function and inside it I fire my bean method if needed.
thanks for everybody ! :)

Trigger JavaScript action after Datatable is loaded

In a JSF 2.1 + PrimeFaces 3.2 web application, I need to trigger a JavaScript function after a p:dataTable is loaded. I know that there is no such event in this component, so I have to find a workaround.
In order to better understand the scenario, on page load the dataTable is not rendered. It is rendered after a successful login:
<p:commandButton value="Login" update=":aComponentHoldingMyDataTable"
action="#{loginBean.login}"
oncomplete="handleLoginRequest(xhr, status, args)"/>
As you can see from the above code, I have a JavaScript hook after the successful login, if it can be of any help. The update attribute forces the dataTable's rendered attribute to revaluate:
<p:dataTable var="person" value="#{myBean.lazyModel}" rendered="#{p:userPrincipal() != null}" />
After the datatable is loaded, I need to run a JavaScript function on each row item, in order to subscribe to a cometD topic.
In theory I could use the oncomplete attribute of the login Button for triggering a property from myBean in order to retrieve once again the values to be displayed in the dataTable, but it doesn't seem very elegant.
The JavaScript function should do something with the rowKey of each row of the dataTable:
function javaScriptFunctionToBeTriggered(rowKey) {
// do something
}
The javascript method from the oncomplete attribute is called after the ajax request has finished and thus after the dataTable is loaded.
So you can do the following:
<p:commandButton ... oncomplete="doSomething()"/>
and everything should work fine.
if the page reload, try to call in the document ready:
$(document).ready(function() {
doSomething();
});

Server-Side callback function to solve JS-CF execution order dependency?

I would like to call a form as pop-up window (a form with some input fields and a submit button) and then read the user selected results from the session. The problem is that the mixing of JS code (pop-up window) with CF (server-side) code, as is expected, causes the process to output the session variable before the process updates it. For better understanding, hereunder is the scenario together with some relevant code snippets:
Scenario:
1. User calls ShowForm(..)
2. ShowForm(..) displays a pop-up window and waits for the user
to submit his selection
3. The result gets stored in the session
4. The function returns the user-submitted result
form.cfc
<cffunction name="ShowForm" access="public" output="true" returntype="string">
<script>
window.showModalDialog('formpage.cfm',null,"dialogHeight=400px,dialogLeft=150px");
</script>
<cfreturn session.form_result> <!--- #toFix: The return of form_result is happening before the actual form_result is set. --->
</cffunction>
formpage.cfm
<cfajaxproxy cfc="components.sess_mgr" jsclassname="JSMaskProxy">
<script>
function submitSelection(formObj)
{
for (i=0; i<intSelValue.length; i++)
result.push(intSelValue[i]);
var instCfProxy = new JSMaskProxy();
instCfProxy.setToSession(result); // updates session.form_result
//window.returnValue=result;
window.close();
}
</script>
<form name="frmDtls">
<td align="center"><input type="button" id="selectButton" name="selectButton" onClick="submitSelection(details);">
</form>
What's your take on this? How to solve this problem?
ColdFusion.navigate(..) function can have a callback function and an error handler but the thing is that the callback function can only be a client-side function. If the function could be a CF function or maybe a server-side page I think that would solve this dependency problem.
Something on the side, ideally I would love to read the value from window.showModalDialog rather than reading it from the session, but this is just a sketch and the main point here is how to overcome this JS-CF intermingling problem.
Rather than using window.showModalDialog use something like cfwindow, jQuery dialog or an extjs window. All of these have some form of callback or event listener. cfwindow has a onHide function, jQuery dialog has a close option to which a function can be assigned, ext.window has onHide, as well as event listeners.
All of these will allow you to open a new "window" and run some function when the window is hidden or closed. Those functions should all have access to anything from the window as well as being able to access the main window.
Ray Camden has a cfwindow example : http://www.coldfusionjedi.com/index.cfm/2008/9/26/Ask-a-Jedi-Another-CFWINDOW-Example
jQuery dialog has an example of what you're after : http://jqueryui.com/demos/dialog/#modal-form
Sencha's Ext.Window examples show passing values to the main window when a dialog is closed : http://dev.sencha.com/deploy/dev/examples/message-box/msg-box.html
Plenty of options there. I hope they help.
Problem solved!
The solution adopted is based on Stephen Moretti idea which is that of replacing window.showModalDialog with cfwindow call. This alone does not solve the problem. However, I worked when replacing the script tag with cfscript, and then inside the cfscript I simply do a server-side redirection to the page with cfwindow tag.

ASP.NET postback with JavaScript

I have several small divs which are utilizing jQuery draggable. These divs are placed in an UpdatePanel, and on dragstop I use the _doPostBack() JavaScript function, where I extract necessary information from the page's form.
My problem is that when I call this function, the whole page is re-loaded, but I only want the update panel to be re-loaded.
Here is a complete solution
Entire form tag of the asp.net page
<form id="form1" runat="server">
<asp:LinkButton ID="LinkButton1" runat="server" /> <%-- included to force __doPostBack javascript function to be rendered --%>
<input type="button" id="Button45" name="Button45" onclick="javascript:__doPostBack('ButtonA','')" value="clicking this will run ButtonA.Click Event Handler" /><br /><br />
<input type="button" id="Button46" name="Button46" onclick="javascript:__doPostBack('ButtonB','')" value="clicking this will run ButtonB.Click Event Handler" /><br /><br />
<asp:Button runat="server" ID="ButtonA" ClientIDMode="Static" Text="ButtonA" /><br /><br />
<asp:Button runat="server" ID="ButtonB" ClientIDMode="Static" Text="ButtonB" />
</form>
Entire Contents of the Page's Code-Behind Class
Private Sub ButtonA_Click(sender As Object, e As System.EventArgs) Handles ButtonA.Click
Response.Write("You ran the ButtonA click event")
End Sub
Private Sub ButtonB_Click(sender As Object, e As System.EventArgs) Handles ButtonB.Click
Response.Write("You ran the ButtonB click event")
End Sub
The LinkButton is included to ensure that the __doPostBack javascript function is rendered to the client. Simply having Button controls will not cause this __doPostBack function to be rendered. This function will be rendered by virtue of having a variety of controls on most ASP.NET pages, so an empty link button is typically not needed
What's going on?
Two input controls are rendered to the client:
<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />
__EVENTTARGET receives argument 1 of __doPostBack
__EVENTARGUMENT receives argument 2 of __doPostBack
The __doPostBack function is rendered out like this:
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
As you can see, it assigns the values to the hidden inputs.
When the form submits / postback occurs:
If you provided the UniqueID of the Server-Control Button whose button-click-handler you want to run (javascript:__doPostBack('ButtonB',''), then the button click handler for that button will be run.
What if I don't want to run a click handler, but want to do something else instead?
You can pass whatever you want as arguments to __doPostBack
You can then analyze the hidden input values and run specific code accordingly:
If Request.Form("__EVENTTARGET") = "DoSomethingElse" Then
Response.Write("Do Something else")
End If
Other Notes
What if I don't know the ID of the control whose click handler I want to run?
If it is not acceptable to set ClientIDMode="Static", then you can do something like this: __doPostBack('<%= myclientid.UniqueID %>', '').
Or: __doPostBack('<%= MYBUTTON.UniqueID %>','')
This will inject the unique id of the control into the javascript, should you wish it
Per Phairoh: Use this in the Page/Component just in case the panel name changes
<script type="text/javascript">
<!--
//must be global to be called by ExternalInterface
function JSFunction() {
__doPostBack('<%= myUpdatePanel.ClientID %>', '');
}
-->
</script>
Using __doPostBack directly is sooooo the 2000s. Anybody coding WebForms in 2018 uses GetPostBackEventReference
(More seriously though, adding this as an answer for completeness. Using the __doPostBack directly is bad practice (single underscore prefix typically indicates a private member and double indicates a more universal private member), though it probably won't change or become obsolete at this point. We have a fully supported mechanism in ClientScriptManager.GetPostBackEventReference.)
Assuming your btnRefresh is inside our UpdatePanel and causes a postback, you can use GetPostBackEventReference like this (inspiration):
function RefreshGrid() {
<%= ClientScript.GetPostBackEventReference(btnRefresh, String.Empty) %>;
}
While Phairoh's solution seems theoretically sound, I have also found another solution to this problem. By passing the UpdatePanels id as a paramater (event target) for the doPostBack function the update panel will post back but not the entire page.
__doPostBack('myUpdatePanelId','')
*note: second parameter is for addition event args
hope this helps someone!
EDIT: so it seems this same piece of advice was given above as i was typing :)
If anyone's having trouble with this (as I was), you can get the postback code for a button by adding the UseSubmitBehavior="false" attribute to it. If you examine the rendered source of the button, you'll see the exact javascript you need to execute. In my case it was using the name of the button rather than the id.
Have you tried passing the Update panel's client id to the __doPostBack function? My team has done this to refresh an update panel and as far as I know it worked.
__doPostBack(UpdatePanelClientID, '**Some String**');
First, don't use update panels. They are the second most evil thing that Microsoft has ever created for the web developer.
Second, if you must use update panels, try setting the UpdateMode property to Conditional. Then add a trigger to an Asp:Hidden control that you add to the page. Assign the change event as the trigger. In your dragstop event, change the value of the hidden control.
This is untested, but the theory seems sound... If this does not work, you could try the same thing with an asp:button, just set the display:none style on it and use the click event instead of the change event.
You can't call _doPostBack() because it forces submition of the form. Why don't you disable the PostBack on the UpdatePanel?

Categories