URLMapping pattern vs query parameters

Splash Forums PrettyFaces Users URLMapping pattern vs query parameters

Tagged: 

This topic contains 15 replies, has 4 voices, and was last updated by  Christian Kaltepoth 6 years, 2 months ago.

Viewing 15 posts - 1 through 15 (of 16 total)
  • Author
    Posts
  • #18003

    tmanning
    Participant

    I seem to be confused about how the @URLMapping annotation works, and I hope someone can help me clarify this.

    JBossAS7 (JSF Mojarra 2.0.4), PrettyFaces 3.3.0, SeamFaces 3.0.2, virtually empty faces-config.xml.

    I have a backing bean annotated like this:

    @SuppressWarnings("serial")
    @URLMapping(id = "manageAccount", pattern="/account/manageAccount/#{ iid : manageAccount.id}", viewId = "/account/manageAccount.jsf")
    @Named
    @ViewScoped
    public class ManageAccount implements Serializable {
    private Account account;
    private String id;

    //also getters and setters for id field

    @URLAction
    public void load() {
    if (id != null) {
    account = crudService.find(Account.class, id);
    } else {
    account = new Account();
    }
    }
    }

    So if I directly hit the url: http://localhost:8080/account/manageAccount/1 it loads up the account and shows it to me. No problem. Interesting note: in the PF docs it says “Please note that the bean properties bound to path parameters must never be null as they are required to build the URL for form postbacks.”, but then in the doc examples it shows something like what I have above, which does bind the path parameter to a bean property that can be null.

    Regardless, I also have a search page where I can select an account to manage (the backing bean knows which one is selected via ajax magic). In it, I have some buttons specified, none of which work:

    <h:commandButton value=”EDIT1″ action=”#{findAccount.editSelection}”/>

    //This currently is hardcoded to return the string “manageAccount/1” and does nothing.

    <h:commandButton value=”EDIT2″ action=”#{findAccount.editSelection2}”/>

    //This currently is hardcoded to return the string “manageAccount/1?faces-redirect=true” and does nothing.

    <h:commandButton value=”EDIT” action=”manageAccount”>

    <f:param name=”iid” value=”#{findAccount.selectedItem.id}” />

    </h:commandButton>

    //This navigates properly but the manageAccount.id field is not set by the time the load() method is called. Also, a faces-redirect would be nice here.

    So… Can anyone tell me what I’m doing wrong? I like that I can hit the manageAccount page directly, appending the ID of an account that I want to manage, but I can’t seem to figure out how to get there from another JSF page.

    web.xml:

    <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
    </servlet-mapping>

    Thanks…

    #21204

    mbenson
    Participant

    Hi,

    In my (admittedly limited) experience, the version you tried with f:param should work; however you need to use “pretty:manageAccount” for your action.

    HTH,

    Matt

    #21205

    tmanning
    Participant

    Thanks for the reply… I tried that next….

    Backing Bean:

    @URLMapping(id = “manageAccount”, pattern=”/account/manageAccount/#{iid : manageAccount.id}”, viewId = “/account/manageAccount.jsf”)

    Link:

    <h:commandButton value=”EDIT3″ action=”pretty:manageAccount”>

    <f:param name=”iid” value=”#{findAccount.selectedItem.id}” />

    </h:commandButton>

    and was rewarded with this:

    com.ocpsoft.pretty.PrettyException: PrettyFaces: Exception occurred while building URL for MappingId < manageAccount >, Required value < #{manageAccount.id} > was null

    So I guess that’s the bit where you have to bind to a property that’s not null. I thought about binding it to the findAccount page like so:

    @URLMapping(id = “manageAccount”, pattern=”/account/manageAccount/#{iid : findAccount.selectedItem.id}”, viewId = “/account/manageAccount.jsf”)

    But that wouldn’t work either, because then I couldn’t hit this page without going through findAccount first to se the selected item’s id (and that page would have to be session scoped?)

    #21206

    mbenson
    Participant

    You’re right… sorry about that. It’s simple enough to build those URLs using pretty:link. h:commandButton is more of a PITA. I would say that typically this isn’t the kind of action you’d expect to see taken from a commandButton. It is entirely possible that I am simply missing something, but I would think you’d be better off using pretty:link for these, and styling accordingly if they just. have. to. look like buttons.

    The other thing you could do is implement an actionListener to handle f:param on commandButton. It is feasible to look for the ‘iis’ parameter on a UICommand that fires an ActionEvent, look for a pretty UrlMapping matching the UICommand’s actionExpression, find the corresponding PathParameter on the mapping, apply your UIParameter value to the PathParameter’s expression… such that when prettyFaces gets around to creating the pretty URL, it injects the value you’ve just sent via your commandButton. That said, I can’t think why the nested parameter shouldn’t work, so again, maybe we’re *both* missing something. Make sure you are using the latest version available, and for good measure I’d give the current snapshot a whirl.

    HTH,

    Matt

    #21207

    tmanning
    Participant

    Hmmm…. I’ll try the latest snapshot I guess. Your other suggestion seems like way too much work for a button, especially since this isn’t the only time this is going to come up ;) Unfortunately, one requirement is to use the PrimeFaces commandButton (instead of the h:commandButton), otherwise I would probably have just gone with the pretty:link approach.

    Thanks again.

    #21208

    Why not just call an action method that sets the value of ManageAccount.id and returns “pretty:managedAccount”?

    That should perform a navigation whether you want to use either a commandButton or a commandLink.

    http://ocpsoft.com/docs/prettyfaces/3.3.0/en-US/html_single/#navigation.actions

    However, I do recommend using <pretty:link> or <h:link> instead, since those are more fitted toward this use-case. If you need to use a button, you could also consider <pretty:urlbuffer> and some JavaScript.

    #21209

    I think Lincoln is right. If you want to use a h:commandButton you should call an action that sets the target property in your bean correctly and then redirects to your page. PrettyFaces will then be able to create the correct link for the mapping. Something like this:

    <h:commandButton value="EDIT" action="#{findAccount.show}" />

    public class FindAccount {

    @Inject
    private ManageAccount manageAccount;

    public String show() {
    manageAccount.setId( selectedItem.getId() );
    return "pretty:manageAccount";
    }

    }

    You could even do something similar without an action:

    <h:commandButton value="EDIT" action="pretty:manageAccount">
    <f:setPropertyActionListener target="#{manageAccount.id}" value="#{findAccount.selectedItem.id}" />
    </h:commandButton>

    But both methods will created a needless postback followed by a redirect to the correct URL. You won’t need the ugly postback if you use just a standard HTML link like this:

    <pretty:link mappingId="manageAccount">
    <f:param value="#{findAccount.selectedItem.id}"/>
    Click me!
    </pretty:link>

    Hope this helps! :)

    Christian

    #21210

    tmanning
    Participant

    You’ve raised some interesting points; for some reason I hadn’t thought of those alternatives. I guess I was hoping that the FindAccount backing bean wouldn’t have a direct reference to the ManageAccount bean – but then again, why not? Since I didn’t design the UI, and apparently a button is required, I’ll have to go with one of the solutions that allows it though.

    I’m still a bit puzzled as to why this didn’t work:

    <h:commandButton value=”EDIT3″ action=”pretty:manageAccount”>

    <f:param name=”iid” value=”#{findAccount.selectedItem.id}” />

    </h:commandButton>

    Maybe if I have time I’ll dive into the code and figure that out.

    Thanks very much for the new perspectives! It was very helpful.

    #21211

    mbenson
    Participant

    I’m pretty sure the reason PrettyFaces doesn’t support f:params on commandButtons is that the URL handling happens too late for this to work. However, it would seem doable to have a pretty:commandButton that would accomplish this. Lincoln/Christian, want to comment on “why not pretty:commandButton extends h:commandButton?”

    #21212

    Let me explain a bit:

    As you know PrettyFaces tries to encourage users to build RESTful URLs for their applications. The advantage of such URLs is that they are mostly stateless. This means that you can copy an paste the link to another browser, bookmark it, and so on. They work independently from other pages.

    Additionally I think that applications should link to RESTful URLs using standard HTML links. This has a ton of advantages. The link URL will be created during rendering time. So there will be no need for postbacks when clicking them. Clicking these links will for example even work if the the user doesn’t use the browser for a long time which will typically cause the session and the view to expire (ViewExpiredException). You can even right-click the link and let the browser open it in a new tab! As you see you will get many advantages if you prefer standard HTML links over postbacks.

    The JSF h:commandButton and h:commandLink are both designed to trigger actions on the server side which will require a postback to the server. But this isn’t required if you simply want to navigate to another page.

    The pretty:link component (and h:link) will simply render something like this:

    <a href="/account/manageAccount/123">EDIT</a>

    This works very fine. The problem is that buttons behave entirely different. They are designed to submit forms, which we want to prevent. You could argue that we could render something like this:

    <form action="/account/manageAccount/123" method="get">
    <input type="submit" value="Edit">
    </form>

    But typically you will have h:form components on your page so that this new form will be embedded in another form, which will break both forms in some browsers.

    The only possibility I see would be to render something like this:

    <input type="submit" value="Edit" onclick="window.location='/account/manageAccount/123'; return false;">

    I know that this doesn’t look nice and I’m unsure if something like this would actually work in all cases.

    What do you think? Opinions?

    #21213

    mbenson
    Participant

    Christian,

    I’m not sure what you meant when you said that you want to prevent submitting forms. That notwithstanding, I think I see the larger disconnect here. :) Consider the following:

    I have defined URLMappings a and b. View a contains a simple form which I want to submit, then (assuming no validation, etc., errors reported) go to view b. What I, as well as the OP, seem to be wanting to do is use pretty mappings consistently throughout our JSF application, thus h:commandButton action=”pretty:b”. Now say that b has a bean-mapped [path|query] parameter. The options now are:

    1. Use f:param with action=”viewId from mapping b” (more or less duplicating the pretty mapping)

    2. Use f:param with a logical outcome, mapped in faces-config.xml to the viewid from mapping b (again, more or less duplicating the pretty mapping)

    3. Use an action method that sets the bean property and returns pretty:b (what Lincoln suggests; I have of course done this in places where I had _other_ stuff to do that was most simply expressed in Java code).

    The duplication element makes options 1 and 2 unattractive and hopefully bears no further explanation. Option 3, as I have parenthetically hinted, seems like overkill for a simple situation. An additional option with regard to mapping a commandButton’s action to e.g. “pretty:b” is to add actionListeners to set the bean properties needed to create the final URL. This certainly works but feels a little ungainly.

    What I plan to do personally is: create a SystemEventListener that responds to the PostConstructApplicationEvent by wrapping the default ActionListener with a pretty ActionListener that will check UICommand components for pretty mapping @actions in conjunction with attached UIParameters and attempt to handle these by setting the bean properties (and/or request parameters if needed/possible, haven’t researched that end yet).

    Matt

    #21214

    Hey Matt,

    thanks for your detailed response. I’ll try to make my point more clear.

    Actually I think there are two different types of navigation that you may want to perform:

    1. Sometimes you may want to click a button and want an action method on the server to be executed to do some stuff. After the action method has done its job you want to navigate to some other page. A typical example is a “delete” button. You want to execute server side code to delete the entry from the database and then redirect the user to some other page.
    2. In the other case you don’t want to execute any server side code and just send the user to a new page. A typical example is a “details” button. You click on the “details” button of an item and want the user to get redirected to a page containing all the details of this item.

      I actually think that the example of the thread author belongs to the second type. There is no need to execute any server side code if the user clicks on the edit button (at least it is my understanding of the usecase). He should just get redirected to the “edit” page of the account. Of cause the required data for the “edit” page has to be loaded from the DB and all the stuff, but this should be done by the editing page using a page action or something like this so that the page works independently from other pages.

      Because you don’t want to execute code on the server you actually don’t need to do a post back and could create just a standard HTML link which sends the user to the editing page. As described in my previous post this currently works only well with HTML links.

      But if you want to execute some action on the server when the button is clicked (think of the “delete entry” example) a commandButton or commandLink make sense. The action method can do its job and the only remaining question is how to redirect the user to the correct page. The typical PrettyFaces solution for this is that what Lincoln suggested. Set the properties of the target mapping and return the correct mapping id. PrettyFaces will then build the correct URL by reading all values out of the bean.

      I think it is really important to understand the difference between the two types of usecases I described. Either your primary goal is to get the user to another page (use pretty:link for creating a HTML link) or you want some action to be executed and redirect the user to some other page as a result (use commandButton).

      What do you think? Does this make sense for you?

      Christian

      #21215

      If you really need a button. Just create a simple HTML button, use <pretty:urlbuffer> as I suggested above, and add JavaScript so that the button sends the browser to the URL created by the urlbuffer.

      Otherwise, you could use <f:setPropertyActionListener> in conjunction with <h:commandButton>, as Christian suggested, but it’s a pretty expensive way to do a simple page-transition.

      #21216

      mbenson
      Participant

      Absolutely. I managed to lose sight of the OP’s usecase. ;) I still think there is merit to the usecase I presented, wherein you want a form submit but nothing more, although I realize this may be a relatively uncommon desire. If/when I get my default ActionListener approach working I’ll present it here for public scrutiny.

      br,

      Matt

      #21217

      tmanning
      Participant

      Great thread. Let me clarify my use-case for you, since that’s come up a few times :)

      In this case I’m selecting an Account to “manage” from a paged DataTable component showing a few dozen accounts, and then clicking the “Edit” button under the table. There will be additional buttons, too (e.g. Delete). The app designers want these to be buttons rather than links, and I agree with them (that would look a bit weird, actually, selecting an item in the table and then clicking a hyperlink). If the session times out before the button is clicked then they can’t edit/delete the Account anyway, so that’s a non-issue. The fact that this results in a form submission is irrelevant, really, and could be beneficial – being able to hook into that method may be required on some pages (there are more than just Account entities being managed in this app, and we want to use a similar pattern for all the datatable pages). Sure, figuring out which item in the datatable has been selected could all be done client-side I suppose, but there’s no technical reason why it has to be.

      I’ve implemented the first suggestion and it works quite well (inject the ManageAccount bean into the FindAccount bean, and in the action method “editSelection” I set the ManageAccount bean’s ID parameter, then return “pretty:manageAccount”). I was concerned originally that the beans’ scopes would prevent this from working, but apparently it’s fine.

      Thanks!

    Viewing 15 posts - 1 through 15 (of 16 total)

    You must be logged in to reply to this topic.

    Comments are closed.