Programmatically control which components should be ajax-updated - jsf

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" />

Related

Form not submitting on a page with fragment 'rendered' conditions and ui:param

I'm creating a some sort of online shop with JSF. I have a product.xhtml page that displays the product by ID. I'm passing that ID as a param (.../product.xhtml?id=3) and I'm getting the ID with <ui:param name="productID" value="#{request.getParameter('id')}" />
That all works well.
Next, I'm showing and hiding certain elements in the page with <f:subview> (I've used <ui:fragment> before). The reason is that if the user deletes the ?id=3 ID parameter, the page will show an error (eg. code <f:subview id="main" rendered="#{productID != null and productID != ''}">). Another reason is that if the product belongs to the buyer, the BUY button will not appear and if the user is not authenticated the BUY button will not appear.
The problem is with the BUY button. It is in a form and the action of the button is just a simple test method (for now) from the CDI bean that prints something to the server console and redirects the user. Unfortunately, this does not work. The page (/product.xhtml) gets reloaded with no ID param.
I've tried several things like this and this and nothing is working.
I've tried using the ViewScoped and SessionScoped for my CDI bean instead of RequestScoped, but that does nothing. The ViewScoped fails to build.
I've also changed the <ui:fragment> to <f:subview>
Here's some code..
CDI bean controller
#Named
#RequestScoped
public class ProductManager {
...
public String buy(Product product) {
FacesContext context = FacesContext.getCurrentInstance();
try {
HttpSession session = Util.getSession();
User buyer = (User)session.getAttribute("user");
Date date = new Date();
System.out.println("TEST DATA: ");
System.out.println("sale product: "+product.getTitle());
System.out.println("sale buyer: "+buyer.getUsername());
System.out.println("sale date: "+date);
}
catch(Exception ex) {
System.err.println("ProductManager#buy -> "+ex.getMessage());
}
context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "An error occured", null));
return "index";
}
...
}
Products.xhtml
...
<ui:param name="productID" value="#{request.getParameter('id')}" />
...
<f:subview id="buyBtn" rendered="#{user.username != login.username and login.regUser}">
<h:form style="margin-top: 30px;">
<b:navCommandLink styleClass="btn btn-info btn-block" disabled="#{!product.status}" value="Buy" action="#{productManager.buy(product)}"></b:navCommandLink>
</h:form>
</f:subview>
...
I can provide full code if needed.
What I'm expecting the code to do is that whenever I click on the BUY button, I'll get redirected to my page and the TEST DATA will be printed on server console.
After many. many attempts I solved this. I cannot find the exact explanation, as I read a ton of articles on this topic.
I used OmniFaces. Next in products.xhtml I changed h:form to o:form (from OmniFaces) and have these two set to true includeRequestParams="true" includeViewParams="true". My bean then became org.omnifaces.cdi.ViewScoped
This is also a great article

Keep non submitted values, when pressing "more-Button"

First, my scenario:
I have a form including several input fields. Some of them are hidden by default, because they are not really often needed:
<h:form>
<!-- with validators -->
<h:inputText>...</h:inputText>
<h:inputText>...</h:inputText>
<!-- show this fields only when the user wants to -->
<h:panelGroup rendered="#{!bean.hide}">
<h:inputText>...</h:inputText>
<h:inputText>...</h:inputText>
</h:panelGroup>
<h:commandButton value="Show / hide more fields" actionListener="#{bean.toogleHide}" />
<h:commandButton value="Submit" />
</h:form>
This would be the bean:
#SessionScoped
#ManagedBean
public class Bean {
// with getter
private boolean hide = true;
public void toogleHide(ActionEvent event) {
hide = !hide;
}
}
What i want to do:
Assume, a user fills the first to input fields with valid / invalid data. Now he wants to see the other inputfields that are hidden by default. So would press the command button "show / hide more fields".
=> is there a way the keep the non submitted values when pressing the "show / hide more fields" button?
My suggestion would be to always send the "hidden fields" from your JSF page, but make them display: none by default, using CSS. Then hide/unhide them with javascript only, e.g. jQuery or a small JS script on the page. The whole will be much more responsive, plus you don't have the issue you're fighting with.

How to count the number of times a h:outputLink was clicked?

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>.

How to evaluate a FacesComponent property inside a custom component

Lately, I've been working on a dynamic form project but is stop with a custom component problem. What I have:
A faces component for formField:
#FacesComponent(value = "formField")
public class FormFieldCompositeComponent {
private boolean email;
}
A custom component jsf:
<o:validator for="email_text" validatorId="emailValidator" disabled="#{not cc.email}" />
OR
<c:if test="#{not cc.email}">
<f:validator for="email_text" validatorId="emailValidator"></f:validator>
</c:if>
OR
<f:validator disabled="#{not cc.email}" for="email_text" validatorId="emailValidator"></f:validator>
And the validator:
#FacesValidator("emailValidator")
public class EmailValidator implements Validator { }
My problems are:
1.) If I use an ordinary f:validator, like the one I use above and then use c:if to enable/disable it, then it will not work. According to some articles I've read it's because f:validator validates on build time, not on render time.
2.) If I use o:validator, it works but the problem is every time you hit submit a new line of invalid email error is added to p:messages. Example I clicked submit button 3 times, then I get 3 times the email error.
Any idea?
More info (anatomy of the project)
Example I have a page user with field email, it will include the following custom components:
+user.xhtml
+formPanel
+formField (this is where the validator is defined)
+formButtons (the action button)
+p:messages is defined
user.xhtml
<formPanel>
<formField field="email" />
<formButtons />
</formPanel>
Command button is like (formButtons):
<p:commandButton id="saveButton" rendered="#{cc.attrs.edit}"
value="#{messages['action.save']}"
action="#{cc.attrs.backingBean.saveOrUpdate()}" icon="ui-icon-check"
ajax="#{cc.attrs.ajaxSubmit}">
<c:if test="#{cc.attrs.backingBean.lcid != null}">
<f:param name="cid" value="#{cc.attrs.backingBean.lcid}" />
</c:if>
</p:commandButton>
The p:messages as defined on formPanel:
<p:messages id="formMessages" showDetail="true" showSummary="false" redisplay="false"></p:messages>
Note:
1.) What I've noticed is that the validator is called n times, where n is the number of submit or click done.
xhtml - https://github.com/czetsuya/crud-faces/blob/master/crud-faces/src/main/webapp/administration/user/user.xhtml
the tags - https://github.com/czetsuya/crud-faces/tree/master/crud-faces/src/main/webapp/resources/tags
bean component - https://github.com/czetsuya/crud-faces/tree/master/crud-faces/src/main/java/org/manaty/view/composite
Seems like there's no chance for the f:validator so I push through o:validator and come up with a workaround. Need to catch if the error is already in the FacesMessages list:
boolean match = false;
for (FacesMessage fm : context.getMessageList()) {
if (fm.getDetail().equals(message)
|| fm.getSummary().equals(message)) {
match = true;
break;
}
}
if (!match) {
throw new ValidatorException(facesMessage);
}

Navigate after successfull action on commandButton

I am building a JSF application using Primefaces mobile (0.9.3 with Primefaces 3.5).
I hava a login page, on this login page there is a login button defined as followed:
<p:commandButton value="#{msg['label.login']}" action="#{loginBean.login}" update="usernameMsg" />
The loginBean.login method is defined as followed:
public String login() {
try {
//do login
return "#loggedInTarget?transition=fade";
} catch (UserNotValidException e) {
//User not valid, display exception
FacesContext
.getCurrentInstance()
.addMessage(
"loginForm:username",
new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"",
messageService
.getMessage("error.message.loginNotSuccessfull")));
}
return null;
}
In my main xhtml (the same in which the button is defined), I have defined the following addtional views:
<pm:view id="registerTarget">
<ui:include
src="/WEB-INF/c52bc19d58a64ae4bd12dec187a2d4b7/register.xhtml" />
</pm:view>
<pm:view id="loggedInTarget">
TEST
</pm:view>
I do it that way because I want to use transitions within Primefaces mobile. To get to the register page I use a p:button which works fine:
<p:button value="#{msg['label.register']}"
href="#registerTarget?transition=fade" />
Why is my login button not working? I also tried to put in "#loggedInTarget?transition=fade" directly as action, but this didn't work either :(
I need the action, because I have to check if the user credentials are valid. If they are valid I want to display my loggedInTarget.
How can I achieve it?
Thanks in advance!
EDIT:
The button is inside a form.
The navigation case outcome value must represent a view ID, not an URL (for sure not a hash fragment). Try with <p:button outcome="...">, and you'll see that it fails over there as well.
Your closest bet is sending a redirect.
public void login() throws IOException {
// ...
externalContext.redirect("#loggedInTarget?transition=fade");
// ...
}
This is also exactly what the <p:button href="..."> does under the covers: causing a GET request on the given URL.

Resources