You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.
| Online: | 10 Members of 4089 |
| Forum: Seam Users |
05. Mar 2008, 14:58 CET | Link |
Hi all,
I'm trying to pick up Seam but as my JSF knowledge is patchy, I'm hitting a few problems. I was wondering if anyone could help me with the below simple issue. It's not really Seam related. It's my lack of JSF that is probably the problem here.
I'm trying to implement a table like the one in here in the same reference. But I'm also going to try and add sortable columns.
So what I have so far are two classes: - ItemHome: Containing all my persistance calls (remove, update etc.) - ItemBrowser: Containing all my code for navigating the table, sorting rows etc. My @DataModel and @DataModelSelection items also live in here.
I can plug the ItemBrowser into my page using something like below:
<rich:dataTable id="itemTable" var="i" value="#{browseItems.results}" rendered="#{browseItems.rowCount >0}">
<h:column>
<!-- Column here -->
</h:column>
<h:column>
<!-- Column here -->
</h:column>
<h:column>
<!-- Actions here -->
<s:link action="#{itemHome.remove}">
<!-- f:param with i.id here? -->
Delete
</s:link>
</h:column>
</rich:dataTable>
But I have no idea how I can pass my current item instance over to ItemHome so that it can be removed. Can anyone help me fill this gap in my knowledge?
Thanks, Lee
You can drop the @DataModelSelection (which is confusing IMO) and as you're using @DataModel you can pass your as a parameter <s:link action="#{browseItems.remove(i)}">
Note sure if you can pass it to the ItemHome like that though as the ItemHome doesn't know about the @DataModel. You can always have a method in your BrowseItems that calls a delete method in ItemHome. Try both. It would be interesting to see if you can get the first way working.
Cheers,
Damian.
Lee,
There are a variety of ways to accomplish this. If you are using @DataModel, a @DataModelSelection can be applied to a field that identifies the row the user was on when they click the remove link.
Another way, is to use f:param's as children of the link.
And last, but not least, Seam has extensions to EL which allow for parameters to be passed to method expressions.
So if the remove method looks like
public String remove(T selectedResult) // can be a void method too! { // remove it return "success"; }Then the xhtml can be coded like this:
<rich:dataTable id="itemTable" var="i" value="#{browseItems.results}" rendered="#{browseItems.rowCount >0}"> ... <h:column> <s:link action="#{itemHome.remove(i)}">Delete</s:link> </h:column> </rich:dataTable>The last one is the approach that we normally take.
Good Luck!
Remember that this is the most evil way possible how you can abuse s:link action. Yes, some examples might even do this, we need to document it better.
This is a GET request that is neither safe nor idempotent. Would you like it if the Googlebot on it?
The correct way is to use DELETE requests, but since browsers don't support that, we all should use overloaded POST. In other words, h:commandButton/Link with a no-argument remove() action, in a POST form that wraps the datatable, with @DataModel/@DataModelSelection on the backend.
Finally, what you have shown is not really , that would be:
<s:link action="#{itemHome.remove(i.id)}">Delete</s:link>Because i.getId().toString() is evaluated at render-time. What you have shown is called . There is an open JIRA issues that discusses removing this particular mis-feature.
Check out my weblog or have a look at the books I wrote.
It's useful for hiding the ids of your entities though.
Well thanks for the responses all. I didn't realise I could pass variables to my methods now. As that's evil, I'll leave that as the backup plan :)
Although I've got the @DataModel and @DataModelSelection set, those are set in the ItemBrowserclass and not in the ItemHome class (where I am trying to call remove) I don't think that's going to work.
So I'll take a stab at the child parameter. I gave that a quick once over earlier but never tried it. If that fails, guess I'll be taking the evil route till I figure it out.
Christian,
We used h:commandLinks which are POSTS so the GET issue wasn't really an issue.
As far as the magic tricks part - this was on Seam1.2.1.GA so it may no longer apply - the whole reason we did it was because when we used DataModels we had numerous other issues that resulted:
So...since we had a deadline, we took the lazy approach, gave up, and did the workaround :)
Thanks, Christian!
Sorry to come back on this, but could you please explain what's is wrong with the code
This is definitively an intuitive way of doing things - and it's very useful! So if it's inherently wrong, what's the best way of passing a table entry as parameter? By systematically using the Id of the object (in the case of an entity bean) as you suggest?
Thanks in advance for making things clearer,
Sylvain.
Use a commandLink and then instead of passing in i, pass in the primary key or ID of i.
After looking at our code again, we passed in the key, not the actual JPA object.
Both. It should be clear why GET is supposed to be safe and idempotent. As for the loop variable, think about it like this:
The loop variable is a temporary variable, it's only available during RENDER RESPONSE when your datatable is rendered. If you trigger another request, be it GET or POST, it's not going to be there. So there are several tricks how you can transport the you clicked on into this subsequent request.
One is with @DataModelSelection and a POST request. The selection index will be part of the POST request body as a parameter (you can see that if you look at the rendered table HTML and the h:commandLink that triggers the POST with Javascript). On the server during request processing, Seam/JSF pull the item at this index out of the datamodel. (Which needs to be in a scope that lasts longer than a single request! PAGE or CONVERSATION are common choices.) It then injects it into your @DataModelSelection property. That's the clean solution because it uses overloaded POST for actions that might not be idempotent nor safe (which is what clients expect from POST).
The second option is s:link with an action that looks like it is passing the loop variable the next GET request. Now, that is of course not what is happening. It's the same trick as before, just with a GET. Seam attaches a magic request parameter onto your s:link URL that says . It's just a convenient shortcut. But it's conceptually wrong to use GET for that, so that is why we are discussing removing it.
In my experience, people have trouble with this because they are not fully aware of the JSF lifecycle and don't realize the scope of variables, especially variables that are only available during response rendering. There is no way how you can pass between request . You can pass identifiers or keys (or whatever you like to call them) between requests as parameters and you need to make sure that the subsequent request can pull the stuff out of some data store that is in the right scope (data model, list, whatever) when that key needs to be resolved.
Check out my weblog or have a look at the books I wrote.
I must admit this is my case...
Of course, it was surprising to be able to pass loop variables as an argument to EL expression. But since things were working like that... I thought that was some JSF/Seam white magic. So half by laziness, half by lack of time, I didn't look into the details! I think many people are just like me...
Now, I know that's not the good way of doing things!
So, thank you for your answers,
Sylvain.
Are you also deprecating this magic for the idempotent case of linking to a child view with an s:link and a method EL expression with a loop variable?
<h:dataTable value="#{someBean.children}" var="child"> <h:column> <s:link value="edit" action="#{anAction.editChild(child)}"/> </h:column> </h:dataTable>I see this case as equivalent to the use of @DataModelSelection, but easier to read (to me) in both the view and controller. In addition it allows right-click-open-in-new-window, which h:commandLink would not. In the case of editing children in a table, our users often expect to be able to do that in a new window.
I'm ok switching back to @DataModelSelection for this case if that's what the Seam team is suggesting as the best practice moving forward. (I definitely think Seam needs some best practice use-cases, so whatever they are, I'll code to them.)
I tend to cringe at the idea of passing identifiers around explicitly; should I reconsider this position? Or is using an EL method parameter just not the right way to hide the identifiers (as opposed to @DataModel and friends)?
Which issue is this? I found jbseam://1734 and jbseam://2391, but they're closed.
Luke Maurer
Honestly, I think that URLs with primary keys in them is just about the only truly semantically correct way to use HTTP.
Learn more about Web Beans...
I'm speaking approximately, of course: it doesn't have to be the primary key, just some stable unique key of the entity.
Learn more about Web Beans...
Am I wrong, or that's the RESTful way of doing things? Or at least it enforce a resource oriented architecture (ROA). Does this advice apply to all POST requests as well as GET requests?
From the user point of view, it could be of great benefits to have URLs with primary keys: they will have an unique identifier (the URL) for any operation on that resource. But that require a browser supporting http methods like PUT or DELETE. That's currently not the case.
Maybe your problem with is related to security concerns. What if someone change the URL in such way that give him access to an other resource?
Since the solution based on @DataModelSelection use index on the list, as far as I understand, there's no way for the user to access resources other than those present in the corresponding @DataModel. This is a way of doing things.
In the other hand, using identifiers in the URL requires to check if the user should really have access to the given resource before processing each request. This is a more way of doing things. The good news is in that case you now have bookmarkable URL.
Restful architectures are stateless. And Seam is a stateful framework. So by re-reading this post again and again, I came to the conclusion that, in order to have the best of both worlds, we should:
Even more: I feel like if any Seam conversation should begin with a GET request on a . In other words, that any in an application should be a resource. I'm not sure I'm clear here (it's still a little bit confuse in my mind;). But I'm sure there's something to dig there...
Sylvain.
Security is not obscurity, so that needs to be resolved anyway with additional checks. No difference between providing the key/index of a datamodel item that I'm not supposed to select, or providing a database identifier I'm not allowed o load. Of course it feels if the parameter in question is in the request body (POST) and not in the request URI (GET). But it's really the same thing.
No, this is not a good idea. You always need to check a request with your security policy, no matter how it is made.
Check out my weblog or have a look at the books I wrote.
I agree with that. And of course, using POST is no more secure than using GET.
Yet, I'm not sure to understand that: To my mind, there is a difference:
So, if I allow my user to select an item in a list, and if that selection is returned as an index in that list, the only thing I have to check is if the is not out of bounds. I don't think I need to check if the user has enough permissions to access that item, since that was done when I build up the list?
But maybe I missed something, so thanks in advance to point it out!
Sylvain.
Not necessarily. You could have view(item) and remove(item) actions that are rendered conditionally and different for each row. I might tamper with the request and pass the index of the one I'm allowed to view into the remove action. So really, no difference on the backend wrt authorization checking.
Check out my weblog or have a look at the books I wrote.
Of course you're right! That's exactly the point I missed: I was stuck all the time with the idea of one action per row :(
Thank you to have taken time to answer me!
Sylvain
Oh, certainly - it's the part that makes me squeamish. I've spent too long dealing with methods like . You had to know that Foo, Bar, and Baz were the expected types, and trust that people were careful. More often then not, the method would grab the objects by id, do some logic, then forward them on to other methods ... by id. This wasn't a Seam project, but it was using Hibernate, so there really wasn't any excuse.
But of course this code was well within the pretty, magical dreamworld of Java and Hibernate, so perhaps I should lower my expectations for HTTP. I do like the ways that Seam lets me forget the low-level messiness, though (goodbye HTTPSession!). I guess I'm hearing that needing to deal with ids remains a reality in this case (where EL method parameters are used).
Luke Maurer
After reviewing all comments above, I found nobody mentioned the method below:
<h:commandLink value="View" action="#{entityHome.viewEntity}" immediate="true"> <f:setPropertyActionListener value="#{i}" target="#{entityHome.instance}"/> </h:commandLink>Anybody can give some advice on this method comparing methods discussed above? Thanks a lot.