I would like to be able to trigger validation on my form prior to being allowed to select the files for upload when clicking on the "Choose" button of the fileUpload control in Primefaces. Is this possible? At present the user can click on "Choose" and "Upload" without validation kicking in. This is preventing my document from saving but the attachments are being created.
I know I could hide the fileUpload control until the form is successfully validated and saved but I would prefer to invoke validation when you click the "Choose" button if possible.
I've tried remoteCommand calls in the onStart but can't seem to get anything to force validation to occur.
It is possible to trigger form validation when p:fileUpload Choose button is clicked by combining Java Script and p:remoteCommand.
BASIC CONCEPT
intercept Choose button click event, prevent default outcome (opening of file chooser dialog) and run p:remoteCommand instead,
when p:remoteCommand completes, and if validation is OK, do not prevent Choose button clicks any more until some data on form input elements is changed again.
PROOF OF CONCEPT EXAMPLE CODE
Add this Java script in your page
<script>
var triggerValidation;
window.onload = function () {
//initially (after page is loaded) trigger validation on Choose btn click
triggerValidation = true;
//define button click listener
registerChooseBtnClick();
};
function registerChooseBtnClick() {
//var chooseBtn = document.getElementsByClassName("ui-fileupload-choose")[0];
// or if you define p:upload widgetVar you can use PF function
var chooseBtn = PF('fileUploadWidget').chooseButton[0];
chooseBtn.addEventListener('click', fnRef, false);
}
var fnRef = function (event) {
console.log("Button clicked");
if (triggerValidation) {
//prevent file browser to open
event.preventDefault();
//trigger validation via p:remoteCommand;
submitSelection();
} else {
//File browser will be opened at this point
}
};
function checkIfValidationFailed(xhr, status, args) {
if (args) {
if (args.validationFailed) {
console.log("Validation failed");
triggerValidation = true;
} else {
triggerValidation = false;
}
}
}
//call each time when form input elements (inputText, ...) change value
function forceValidation(){
triggerValidation = true;
}
</script>
and add p:remoteCommand
<p:remoteCommand
name="submitSelection" process="#form"
oncomplete="checkIfValidationFailed(xhr, status, args)" resetValues="true"/>
Also here is xhtml page for quick testing
<h:form id="form">
<p:messages autoUpdate="true"/>
<p:panelGrid columns="1">
<!--size is integer variable-->
<p:inputText id="size" maxlength="3"
value="#{yourBean.size}"
required="true" requiredMessage="Size is missing"
onchange="forceValidation();"/>
<p:fileUpload
id="upload"
widgetVar="fileUploadWidget"
fileUploadListener="#{yourBean.onUpload}"
multiple="true"
allowTypes="/(\.|\/)(jpg|png)$/" />
</p:panelGrid>
<p:remoteCommand
name="submitSelection" process="#form"
oncomplete="checkIfValidationFailed(xhr, status, args)" resetValues="true"/>
<p:commandButton
id="submitBtn" value="Sumbit" process="#form"
actionListener="#{yourBean.onSubmit()}"/>
</h:form>
Related
The issue is when I enter some negative value in the inputBox within the dialog component and the validator throws error something like "Negative numbers not allowed". The dialog now has an error input box marked with red border and I decide to click outside the dialog to close it and reset the input box, but the inputBox is not reset and if I press Esc key or Ok button then only the popupCanceledListener is called and the inputBox is reset. Below is the code which contains a popup with dialog and inputBox within it.
JSF code:
<af:popup contentDelivery="lazyUncached" autoCancel="enabled"
popupCanceledListener="#{pageFlowScope.testBean.handleResetPopup}"
childCreation="deferred" id="testPopup">
<af:dialog type="none" modal="false"
id="Dlg1">
<af:inputText label="DECIMAL PLACES"
columns="2"
validator="#{pageFlowScope.testBean.validateDecimalPlaceValue}"
value="#{pageFlowScope.testBean.decimalPlace}"
id="input1" autoSubmit="true"></af:inputText>
<f:facet name="acceptNFChange">
<af:commandButton text="OK" id="cb1"
actionListener="#{pageFlowScope.testBean.handleOkFromPopup}"
partialSubmit="true"></af:commandButton>
</f:facet>
</af:dialog>
</af:popup>
Bean code:
public void handleResetPopup(PopupCanceledEvent popupCanceledEvent) {
try {
UIComponent component = popupCanceledEvent.getComponent();
RichInputText inputText = (RichInputText)JSFUtil.findComponent(component, "input1");
inputText.resetValue();
} catch (Throwable th) {
this.handleException(th);
}
}
Problem: When clicking outside the dialog to close it and reset the inputbox, the popupCanceledListener is not invoked.
The best way is to use client listener with type popupClosed. Here is example for your code:
<af:popup contentDelivery="lazyUncached" autoCancel="enabled"
popupCanceledListener="#{pageFlowScope.testBean.handleResetPopup}"
childCreation="deferred" id="testPopup">
<af:dialog type="none" modal="false"
id="Dlg1">
<af:inputText label="DECIMAL PLACES"
columns="2"
validator="#{pageFlowScope.testBean.validateDecimalPlaceValue}"
value="#{pageFlowScope.testBean.decimalPlace}"
id="input1" autoSubmit="true"></af:inputText>
<f:facet name="acceptNFChange">
<af:commandButton text="OK" id="cb1"
actionListener="#{pageFlowScope.testBean.handleOkFromPopup}"
partialSubmit="true"></af:commandButton>
</f:facet>
</af:dialog>
<af:clientListener type="popupClosed" method="popupCloseClientListener"/>
<af:serverListener type="popupClosedEvent" method="#{pageFlowScope.testBean.handlePopupClosed}"/>
</af:popup>
then you need to write somewhere in JSF script:
<script>
function popupCloseClientListener(event) {
component = event.getSource();
AdfCustomEvent.queue(component,
"popupClosedEvent",
{payload:component.getSubmittedValue()},
true);
event.cancel();
}
</script> ]]>
In this way you can always correctly handle popup closed event from client. And implement all server logic you need.
The solution provided in this link https://community.oracle.com/thread/4112016 works fine for my scenario.
Working with JSF, I have a <ux:confirm> tag, which has a confirm button. When clicked it triggers a actionListenerEvent. The page and the objects in the faces context are updated, however I have a bootstrap accordion which is not updated. A solution would be refreshing the page, which is my question.
<ux:confirm
ok="#{message.get('Label.Sim')}"
ajax="true"
render="form-consulta"
cancel="#{message.get('Label.Nao')}"
title="#{message.get('Label.Excluir')}"
message="#{message.get('Msg.DesejaExcluirRegistro')}"
>
<f:actionListener
for="onOkClick"
binding="#{bean.excluir()}"
/>
</ux:confirm>
Ok, solved by just adding a JavaScript function on the ajax event and also calling somewhat a template method using javascript.
<f:ajax
render="#{cc.attrs.render}"
disabled="#{not cc.attrs.ajax}"
onevent="onEventConfirm"
/>
function onEventConfirm(data) {
App.ajax.onEvent(App.view.block, null, null, App.view.unblock);
var status = data.status;
switch (status) {
case "complete":
if(shouldRefreshAfterConfirmation)
this.location.reload();
break;
}
}
I have a PrimeFaces page with following code:
<pm:content id="content">
<p:dataList value="#{likeditems.likedItems}" var="item" pt:data-inset="true" paginator="true" rows="5">
<f:facet name="header">
Products you liked in the past
</f:facet>
<h:outputLink value="#{item.url}" target="_new">
<p:graphicImage name="http://example.com/my-product-mobile/f/op/img/underConstructionImage.jpg" />
<h2>#{item.title}</h2>
<p>Approx. #{item.price} (for most up-to-date price, click on this row and view the vendor's page)</p>
</h:outputLink>
<f:facet name="footer">
Products you liked in the past
</f:facet>
</p:dataList>
</pm:content>
When the user clicks on the h:outputLink, I want 2 things to happen:
A new page with URL item.url is opened in the browser.
Method likeditems.itemLinkClicked(item) is invoked (in that method I update the number of times a particular link was clicked).
First thing is already working (target="_new").
How can I implement the second one (method call for updating the number of times the link was clicked) without the first ceasing to work?
First thing is already working (target="_new").
The target should actually be _blank.
How can I implement the second one (method call for updating the number of times the link was clicked) without the first ceasing to work?
The simplest (naive) JSF-ish way would be triggering a <p:remoteCommand> on click.
<h:outputLink value="#{item.url}" target="_blank" onclick="count_#{item.id}()">
...
</h:outputLink>
<p:remoteCommand name="count_#{item.id}" action="#{likeditems.itemLinkClicked(item)}" />
But this generates lot of duplicate JS code which is not very effective. You could put it outside the data list and fiddle with function arguments. But this still won't work when the enduser rightclicks and chooses a context menu item (open in new tab, new window, new incognito window, save as, copy address, etc). This also won't work when the enduser middleclicks (default browser behavior of middleclick is "open in a new window").
At ZEEF we're using a script which changes the <a href> on click, middleclick or rightclick to an URL which invokes a servlet which updates the count and then does a window.open() on the given URL.
Given a
<h:outputLink value="#{item.url}" styleClass="tracked" target="_blank">
the relevant script should basically look like this:
// Normal click.
$(document).on("click", "a.tracked", function(event) {
var $link = $(this);
updateTrackedLink($link);
var trackingURL = $link.attr("href");
$link.attr("href", $link.data("href"));
$link.removeData("href");
window.open(trackingURL);
event.preventDefault();
});
// Middle click.
$(document).on("mouseup", "a.tracked", function(event) {
if (event.which == 2) {
updateTrackedLink($(this));
}
});
// Right click.
$(document).on("contextmenu", "a.tracked", function(event) {
updateTrackedLink($(this));
});
// Update link href to one of click count servlet, if necessary.
function updateTrackedLink($link) {
if ($link.data("href") == null) {
var url = $link.attr("href");
$link.data("href", url);
$link.attr("href", "/click?url=" + encodeURIComponent(url));
}
}
and the click servlet should look like this (request parameter validation omitted for brevity):
#WebServlet("/click")
public class ClickServlet extends HttpServlet {
#EJB
private ClickService clickService;
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = request.getParameter("url");
clickService.incrementClickCount(url);
response.sendRedirect(url);
}
}
Note that this way the target="_blank" isn't necessary. It would only be used by users who have JavaScript disabled. But then many more other things on the website wouldn't work anyway, including the above JS tracking. It this is also really your concern, then you'd better just put that click servlet URL directly in <h:outputLink value>.
I have this form
<h:form id="formId" prependId="false">
Descrizione <h:inputTextvalue="#{bean.description}" />
Prezzo: <h:inputText value="#{optionalManaged.price}" />
<a4j:commandLink styleClass="button smallButton" actionListener="#{bean.method}"
execute="formId" render="otherDiv">
+
</a4j:commandLink>
</h:form>
At the moment, pressing the a4j:commandLink stores the two input filelds' values inside my bean and calls my action listener correctly.
What I'd like to happen is that pressing enter on the second inputText does the same.
I made a naive try by calling the a4j:commandLink click() via jquery from inside the inputText. This, obviously, didn't work.
Any idea on how I could do this?
You need to detect if Enter key was pressed and click the command link programmatically. Just don't forget to set its id, as well as id of input component. The piece of JavaScript you need to add when the page has finished loading is:
document.getElementById('input').onkeypress = function(e) {
var event = e || window.event;
var code = event.which || event.keyCode;
if (code == 13) {
document.getElementById('link').click();
return false;
}
}
I have a complex form where the user fills a few fields, and has two options: generate a license file or save the changes. If the user clicks on the generate license file button without saving the changes, I render a small component with an error message asking him to save before generating the license.
To display the component with a warning message, I want to use ajax to avoid rendering the whole page just to render the warning component. Of course, if the changes were saved, then the warning message is not required and I redirect the user to another page.
I have a change listener on the changeable fields to detect when a change has been made. What I don't know is the conditional execution. The "render with ajax if unsaved OR redirect if saved" part. Here's the logic
if(saved){
redirect();
}else{
ajax.renderWarning()
}
--EDIT--
I'm going to add more info because I realized I'm leaving things too open ended.
Here's one example of an updateable field.
<h:inputText name="computername3" value="#{agreement.licenseServerBeans[2].computerId}" valueChangeListener="#{agreement.fieldChange}">
<rich:placeholder value="Add Computer ID"/>
</h:inputText>
The fieldChange() bean method
public void fieldChange(ValueChangeEvent event) {
change = true; //change is a boolean, obviously :P
}
Here's the generate license button jsf
<h:commandLink action="#{agreement.generateLicenseFile}">
<span class="pnx-btn-txt">
<h:outputText value="Generate License File" escape="false" />
</span>
</h:commandLink>
Here's the generateLicenseFile() method
public String generateLicenseFile(){
....//lots of logic stuff
return "/licenseGenerated.xhtml?faces-redirect=true";
}
Use PartialViewContext#getRenderIds() to get a mutable collection of client IDs which should be updated on the current ajax request (it's exactly the same as you'd specify in <f:ajax render>, but then in form of absolute client IDs without the : prefix):
if (saved) {
return "/licenseGenerated.xhtml?faces-redirect=true";
}
else {
FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().add("formId:messageId");
return null;
}
Returning null causes it to redisplay the same view. You can even add it as a global faces message and let the ajax command reference the <h:messages> in the render.
if (saved) {
return "/licenseGenerated.xhtml?faces-redirect=true";
}
else {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(...));
return null;
}
with
<h:messages id="messages" globalOnly="true" />
...
<f:ajax render="messages" />