aspose file tools*
The moose likes JSF and the fly likes getting object instance inside nested h:dataTable Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login
JavaRanch » Java Forums » Java » JSF
Bookmark "getting object instance inside nested h:dataTable" Watch "getting object instance inside nested h:dataTable" New topic
Author

getting object instance inside nested h:dataTable

Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Hi everyone,

Does anyone know how can I possible get hold of the instance that is involved in value change event? This could have been an easy task if I'm using only one dataTable. But in this case it is inside a dataTable which is inside on another dataTable as shown in the code below.

I just want to get hold of the instance involved during the event. Normally we could just have UIData bound to the dataTable and use the getRowData() method to get the involved object instance in an event but it doesn't return the right instance if the component is inside a nested dataTable.






Much appreciated and thanks.

-- Frankie

Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

You can get the changed component using the getComponent() method applied to the ValueChangeEvent. That will be the Menu Control. It sounds like you are interested in getting the item's datatable model row. To get the immediately enclosing datatable component, chase up the Menu Control's parent chain until you find it. To get the outer datatable component, repeat the process from there. You should be able to invoke getRowData or getRowIndex to acquire the appropriate model data.

OR, you could just cheat:

Expect to have to do some casting to make that compile. And yes, you get the (users) current row of the outer model (groups) current row. That's not a typo.


Customer surveys are for companies who didn't pay proper attention to begin with.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Tim Holloway wrote:It sounds like you are interested in getting the item's datatable model row. To get the immediately enclosing datatable component, chase up the Menu Control's parent chain until you find it. To get the outer datatable component, repeat the process from there. You should be able to invoke getRowData or getRowIndex to acquire the appropriate model data.

OR, you could just cheat:



Hi Tim,

Thanks but I'm interested in getting the row that the menu control is representing, which is the user (not List of users), and not the model for the datatable which is a group.

Here's how my domain model looks like:


I'm not sure what do you mean by this:

this.getGroups.getDataModel().getCurrentRow().getCurrentRow()

I wasn't using some sort of DataModel structure. I've tried using UIData for binding but if it was declared as UIData myTable then calling myTable.getRowData() inside the ValueChangeEvent listener will give me the instance of the group and not the user instance that the valueChangeEvent takes care of, which I am interested in.

To visualize this my markup codes:

outer dataTable.value = List<Group> groups
inner dataTable.value = group.users
menu control value = user.type

now if that menu control's value has change, I want to get hold of the "user" instance and not the "group" instance nor the "List<user>" instance.

It seems that h:dataTables weren't designed to be nested together.

Thanks again. I hope to hear from you again.

-- Frankie
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

One of the reasons why JSF required a dataModel for a dataTable was that the dataModel not only locates the data, it also holds the context for enumerating rows in the data and for determining what row was selected when a control in the dataTable was invoked.

I apparently missed it, but I've been led to believe that JSF2 allows you to map a collection directly to a dataTable without the intermediary of a dataModel. But since the context is still needed, I suspect that what actually happens is that the datamodel is still there, just constructed and bound invisibly by the JSF framework according to default model rules. The dataTable is the View, and the View has no real concept of what row you're working with. The dataModel is a decorator for the actual data that supports the dataTable's Controller.

You can definitely nest tables if you explicitly use dataModels, however. Each row in the top-level dataModel can contain 0 or more dataModels for any dataTables displayed in that row of the outer table. This can be repeated as many levels as the users will tolerate.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
After a few experiments with the dataTable it seems that inner dataTables returns the wrong row. It always get the last row of the list. Say we have 5 groups and each group has 4 users. The method getDataRow() when invoked on a bound UIData will always return users from group 5 regardless which inner dataTable triggered the event. That is expected because there's only one instance that is bound to inner dataTable when we have 5 groups to consider. So I tried making a list of wrapper that contains the group and a UIDdata to be bound to a h:dataTable per group. Guess what, it doesn't work. It keeps on throwing NullPointerException.
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

If you're always getting the last row, then you're asking the question at the wrong time and place. What you've basically done is latch onto the dataModel after it's done iterating and it's parked at the final row in the loop. You need to ask when the row of interest is being generated. In other words, bind to the row, not to the table.

This mechanism does work when applied properly. I've done it before.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Tim Holloway wrote: You need to ask when the row of interest is being generated.


You mean by implementing DataModel? Or perhaps creating a ListDataModel instance and then add listeners to it? I've tried it. Yes it will iterate through all the rows but you will never be given a chance to know if that particular row is where the SelectItem has changed its value. I want to get hold of the instance where the Menu Control has changed its value. So if the group3.user2.type has change its value through that MenuControl, I should be able to get a handle for that instance. But alas, I've tried everything I could but no luck.

Thanks

-- Frankie
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

Frankie Fuentes wrote:
Tim Holloway wrote: You need to ask when the row of interest is being generated.


You mean by implementing DataModel? Or perhaps creating a ListDataModel instance and then add listeners to it? I've tried it. Yes it will iterate through all the rows but you will never be given a chance to know if that particular row is where the SelectItem has changed its value. I want to get hold of the instance where the Menu Control has changed its value. So if the group3.user2.type has change its value through that MenuControl, I should be able to get a handle for that instance. But alas, I've tried everything I could but no luck.

Thanks

-- Frankie


That is not true. Were it not so, even the simpler version where there's only one table wouldn't work. And I've got more tables with embedded controls and listeners in them than I can count.

Every time a datatable row is rendered, part of what's written out to the web page is a marker that indicates what row in the table it is. This is the information that feeds back so that, for example, a click on a commandLink labelled "delete" on that row can fire a general "doDelete" action method which will know what item to delete because it queries the currentRow() method of the table's dataModel.

Embedding one table in a row of another table means that there's a more complex tree, but each inner row has a unique ID that can be used to ensure that you know exactly which row of both the outer and inner tables you're dealing with. That ID is a composite of the row ID of the inner table and the row ID of the outer table, as well as several other useful items and JSF knows exactly how to decode them.

Don't make things harder than they need to be! JSF is designed to make common tasks simple, and while 2-level tables aren't trivial, they're not rocket science, either.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Tim Holloway wrote:
That is not true. Were it not so, even the simpler version where there's only one table wouldn't work. And I've got more tables with embedded controls and listeners in them than I can count.

Every time a datatable row is rendered, part of what's written out to the web page is a marker that indicates what row in the table it is. This is the information that feeds back so that, for example, a click on a commandLink labelled "delete" on that row can fire a general "doDelete" action method which will know what item to delete because it queries the currentRow() method of the table's dataModel.

Embedding one table in a row of another table means that there's a more complex tree, but each inner row has a unique ID that can be used to ensure that you know exactly which row of both the outer and inner tables you're dealing with. That ID is a composite of the row ID of the inner table and the row ID of the outer table, as well as several other useful items and JSF knows exactly how to decode them.

Don't make things harder than they need to be! JSF is designed to make common tasks simple, and while 2-level tables aren't trivial, they're not rocket science, either.


Thanks for making things clearer. However I can't see any method named "getCurrentRow()". Are we talking about the same version of JSF? I'm using JSF 1.2 and JSP as the view technology (not facelets). There's only a few methods available (http://download.oracle.com/docs/cd/E17824_01/dsc_docs/docs/jscreator/apis/jsf/javax/faces/model/ListDataModel.html). Calling myUIData.getRowData() doesn't return the correct row either. Creating a list of UIData instances for each list of users of groups isn't possible either. In my experience, it's only working properly when the dataTable is not nested into another dataTable.

I totally agree with you that things shouldn't be harder than they need to be and this case is just so against it.

Thanks again.


Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

If you're using JSF 1.2 and attempting to bind the data arrays directly to the dataTable control without a dataModel, no wonder you're having problems. JSF 1.2 requires an explicit dataModel as an intermediary. Facelets doesn't factor in here at all.

And yes, its "currentRow()" not "getCurrentRow()". I always forget.

To make something like a dropdown list in a 2-level table work, you'd need something like the following:



I'm using the model constructors that wrap the data. You could also use the no-argument DataModel constructor form followed by a setWrappedData() method call.

OK. Now if you bind a ChangeListener to the control that binds to the innerRow selectedValue property, the listener can get the outer row by invoking currentRow() on the outerModel object.

Object outerRowObj = outerModel.currentRow();

The returned object from currentRow will be the OuterRow object that contains all the values for the outer table row. Which for simplicity's sake is just the tableModel for the innerTable, but in real life may contain other tableModels, SelectItem collections and primitive values (which don't require model intermediaries).

So, more correctly:

OuterRow outerRow = (OuterRow) outerModel.currentRow();

and:

DataModel innerModel = outerRow.getInnerModel();

Now peel the onion down another layer:

InnerRow innerRow = (InnerRow) innerModel.currentRow();

Which then permits you to access the selectedValue object via innerRow.getSelectedValue();

This code should work properly in both a valueChangeListener and in an action method.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Tim Holloway wrote:

And yes, its "currentRow()" not "getCurrentRow()". I always forget.


I really tried to look for some method named "currentRow()" or somethign similar to it but I can only find getRowData() to be the most relevant method. Am I missing something? ListDataModel doesn't have currentRow() method. DataModel doesn't have currentRow() method as well. The same with UIData. Three of them don't have the currentRow() nor the getCurrentRow() method. But all of them have getRowData().

I'm going to show you exactly what I did that is closest to your idea.



Look closer at the SampleBackingBean where I always get either the wrong row or an exception.

Thanks

- Frankie
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

ARRRGHH!!!

I'm incompetent. It's "getRowData()". I'm just obsessed with "currentRow()" for some reason. getRowIndex() or WHATEVER it's called also works.

Let me look more closely at your code. There's 1 or 2 things I'm not sure about there.

Mark Anthony Ranullo
Greenhorn

Joined: Dec 21, 2010
Posts: 6
I had this exact problem before. Tim is partially right, you could actually use getRowIndex() instead of using getRowData(). In your case I would code it in this way:



I think the inner data tables can't reliably return the correct instance using the getDataRow() but still can manage to return the correct index of the row that triggered the event. So I guess you could only use getRowIndex().

Cheers.
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

Actually, that wouldn't make sense. getDataRow() returns the results of getWrappedData().get(getRowIndex()).

I'm working on a similar issue right now as it happens. I'm attempting to add a new function to a webapp and part of that function is to steal a construct that's very similar to what's being discussed that has been running in production for about 6 months now.

Which is why I refuse to admit that there's anything funny about nested DataTables.
Mark Anthony Ranullo
Greenhorn

Joined: Dec 21, 2010
Posts: 6
That's right it doesn't make sense at all. Only if getWrappedData() could possibly return the correct instance for inner data tables then getWrappedData().get( getRowIndex() ) should be fine.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Thanks Tim and Mark. I guess that leaves me no choice but to use the indices. But I really wonder why getRowData() doesn't work and just how exactly such problem should be solved the "right" way. I mean it is so counter intuitive. I was really expecting that getRowData() should work out of the box in any case. And I'm really confused on why binding multiple UIData isn't working for nested dataTables. It seems to me that during component construction (JSP's doStartTag() of htmlDataTable or whatever) the variables declared in var isn't available to inner components. I'm getting NullPointerException if I tried binding multiple UIData's to inner dataTables. If that could work maybe getRowData() would work properly. That's the reason why I suggested that maybe JSP view here somehow factors in.
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

Hint: the "right" way in JSF is almost always the simple way. The minute you start messing around with any javax.faces resources other than the datamodel packages, the odds go way up that you're doing things the hard (and fragile) way.

I think one of your basic problems is that you don't have a clear understanding of when code is being executed versus when you're dealing with static object definitions. For example, when you click on a commandlink/button in a datatable, the looping part of the processing is long since over. What the "command" does is reference the static identity of that particular object. That static identity is what's used to set the currentRow information on the dataModel as seen by the Action processor. Or, in the case of nested tables, each of the datamodels.

And just to reinforce the concept, I do want to repeat that EACH inner table has its own datamodel. There's not an "outer datamode" and an "inner datamodel". There's an "outer datamodel" and within it is a collection of row data, and each row data item has its own separate inner datamodel instance that reflects that particular inner table.

Like I said, it's not excessively complex, so when it starts to look complex, that means that you've wandered off-course.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
I feel the same way you do. I actually hate complexities or taking the hard and fragile way. But do I really had a choice if I was running out of ideas? I was just trying some other ways. I was not expecting getRowIndex() to work knowing getRowData() doesn't work. I've shown you my code and I can't see any reasons why getRowData() shouldn't work other than my hunches. I tried using your approach that is to use datamodels but unfortunately it didn't work for me and that made me really wonder hard how you made it work in your project.

Although I can use indices to solve this problem, as Mark suggested, I still want to understand exactly why my code didn't work. Plus using indices, doesn't feel right; getRowData() feels more simple and elegant but it just doesn't work.
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

Not much more I can say. Sounds like something simple and obvious went wrong in your code, but without actually laying hands on it, I can't venture a guess. I do a LOT of this stuff - spent the last week doing things even nastier than that and hardly noticed the effort.

On the other hand, JavaScript is going to kill me. What other language defines 19 different ways to tell if something's not there and each of them has at least one case where they don't work?
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Maybe you could spare me some of your stripped down code snippets so I could see exactly what you did. My codes are really simple that you could really easily spot any problems with it.

It's really simple, I did create listDataModel for each inner data table for each row of the outer dataTable. I managed to get the correct listDataModel instance alright, but getRowData() of that instance simply doesn't work. What else could possibly go wrong?
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

You've pretty much already seen what I do in the earlier examples. If you have your MVC models connected up correctly, the only other thing that I could expect to make things fail is asking for data out of context. getRowData() only works as expected in action methods and certain listeners. And if you invoked getRowData on an inner model, that inner model has to be part of the getRowData of its containing model - you can't invoke it on any of the other inner models.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
What do you mean by asking for data out of context? Is there anywhere else I should be invoking getRowData()? I'm only interested in listening to listbox's value change event. Or do you mean I can't do that during such events?

And if you invoked getRowData on an inner model, that inner model has to be part of the getRowData of its containing model - you can't invoke it on any of the other inner models.


Yes I can get the correct instance of the outer data model and that outer data model contains the inner data model. The problem is that the inner data model's getRowData() doesn't work.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Tim, are you really sure have dealt with something like this before? Nested data tables with select one menu inside the inner data table AND handle the value change event from there? Because that's really a rare use case. Most of the time you wouldn't need to handle those value change events of those list controls, just let it update the models they represent. It just so happened that I really needed to listen to that event and do "something" about it when it happens. It's really amazing how it is able to update the model but not able to give you the model when you try handling the value change event.

I tried different approaches and I can only come up with only one conclusion, that is, getDataRow() doesn't work when handling value change event of a list control that is inside an inner data table.

Thanks for staying this far. I really appreciate your patience.
Tim Holloway
Saloon Keeper

Joined: Jun 25, 2001
Posts: 15641
    
  15

Well, just today I implemented a cascading selectlist where a datatable contains 2 lists and the selection options on the second list varies depending on the selection made in the first, with a different first selection for each row in the table. It was trivial. What wasted most of my day was JavaScript to do client-side row/column totalling, and I think I'm going to rip all that out and replace it with AJAX. I'm beginning to really hate JavaScript.

What I did wasn't quite a table-in-table, but it's close. I think I actually do have a table-in-table app, just don't have time to go looking for it.

I was thinking about the context of your condition last night and I believe I did see a small knot in it, but I couldn't quite pin everything down. Like I said, I'd really need to put in under a debugger to make sure I have everything approached from the proper angle.

There is no question that the unique identify of your victim can be ascertained. If you were to obtain the HTML ID property of the control that's passed to your valueChange event, it would look something like "form:table1:X:table2:Y:select" where:

"form" would be the ID of your containing form.
"table1" would be the ID of your outer table.
X would be the outer table's row number
"table2" would be the ID of your inner table
Y would be the inner table's row number
"select" would be the simple ID of the selection list control.

In a pinch, you can parse this down and dereference the rows by brute force, but it should already have been done and available if you look in the right place.
Frankie Fuentes
Ranch Hand

Joined: Mar 28, 2010
Posts: 41
Yes I agree that it's trivial; I have plenty of those cases in my current project. I even have three nested tables and got no problems. It only started to get on my nerves when I had to handle the list control's value change event when it's inside an inner data table enclosed by another data table.

Like in your case, implementing cascaded list controls, I usually handle those by queuing it up if it's not the right time (apply request value phase) and handle it during the update model phase something like this:




I really have this strong feeling that it's really impossible to get the getRowData() method to work in my case.

I also tried that (even if I don't have to) still getRowData() didn't work.

In a pinch, you can parse this down and dereference the rows by brute force, but it should already have been done and available if you look in the right place.


If I do that then it's just like using the indices to get the instance but in a more complicated way.
Vivek Bejawar
Greenhorn

Joined: Mar 15, 2012
Posts: 2
Hi Frank,
All this issue is because of the duplicate IDs getting assigned to Inner dataTable. I have the same problem and I tried alot but could not. I merged the inner and outer classes into one, and able to manage in single data table
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: getting object instance inside nested h:dataTable
 
Similar Threads
Action Event
select a row
ajax events not triggered in dynamically included pages
Ajax Listener event valueChange seems to be firing onClick instead of onChange
Unable to call Action Methof