Primefaces weird behaviour with datatable, overlayPanel and filter/sort - jsf

I'm having some trouble with primefaces datatables and dynamic overlay for each row.
Basically I have a main datatable containing a certain number of rows. For each row, I have a button and a linked overlay that, when clicked, shows a small datatable in the overlay (it lists the lasts changes concerning the row).
Here is the simplified code:
//the main datatable
<p:dataTable id="terminauxDataTable" var="terminalBean" widgetVar="dataTableWidgetVar" value="#{cc.attrs.listAllTerminals}"
filteredValue="#{cc.attrs.listFilteredTerminals}">
...
<p:columnGroup>
<p:row>
<p:commandButton id="changelogButton"
type="button"/>
<p:overlayPanel id="changelogPanel" for="changelogButton"
dynamic="true">
//the small datatable for each row
<p:dataTable var="changelog" value="#{terminalBean.lastChangelogs}">
<p:column headerText="#{msg['commun.changelog.table.label.date']}">
<h:outputText value="#{changelog.dateHr}">
<f:converter converterId="dateTimeConverter" />
</h:outputText>
</p:column>
... some other colums
</p:dataTable>
</p:overlayPanel>
</p:row></p:column></p:datatable>
everything works fine at first sight. When I click on any button i do get the content that I want to load. it's supposed to look like this :
http://i.stack.imgur.com/n9GrW.png
(sorry, cannot post image directly)
Problems start to arise as soon as I make any filtering od sorting on the main datatable.
If a specific button was never clicked since page load, the overlay and the contained datatable are load successfuly. It dosen't matter if I applied a filter or a sort.
If I already clicked a button at least once and then I apply a sort/filter, if I try to click again on the button the overlay is shown but is empty, there is no datatable inside.
here is an exemple of a click after a filter on a button that was clicked before :
http://i.stack.imgur.com/eLGLo.png
Do you have any lead on how to address this problem ?
Thank you

Using the overlay opitimally with a datatable, requires a little different approach as can be seen in the PrimeFaces showcase of the overlayPanel
You need to put the overlayPanel outside the datatable, put a updatable container in there, e.g. an outputPanel and on the button in your column, update the panel and call the show function of the overlay panel with a specific parameter. Something like this:
<p:dataTable>
....
<p:column style="width:32px;text-align: center">
<p:commandButton update=":form:terminalDetail" oncomplete="PF('myTerminalOP').show('#{component.clientId}')" icon="ui-icon-search" title="Details">
<f:setPropertyActionListener value="#{selectedTerminal}" target="#{mySelectionView.selectedTerminal}" />
</p:commandButton>
</p:column>
</p:dataTable>
<p:overlayPanel widgetVar="myOP" showEffect="fade" hideEffect="fade" dismissable="false" showCloseIcon="true">
<p:outputPanel id="terminalDetail" style="text-align:center;">
...
</p:outputPanel>
</p:overlayPanel>
(Disclaimer: done on my phone, so typos may be there)

On latest version of primeng we can do this.
Triggering the event based on element #id. By doing the toggle from typescript, I was able to pin point the element I need to attach the overlay panel to.
HTML:
<div>
<p-table>
<--loop the data-->
<button pButton (click)="showOP($event, rowwiseData, 'button'+rowIndex)" [id]="'button'+rowIndex"></button>
</p-table>
</div>
<--outside of the table add the overlay menu-->
<p-menu #menu [popup]="true" [model]="panelData"></p-menu>
TS File:
panelData: any;
#ViewChild('menu', {static: false}) op: OverlayPanel;
<!--Call and point the event to the correct element using unique #id-->
showOverlayPanel($event: MouseEvent, rowwiseData: any, id:string) {
this.panelData = rowwiseData;
this.op.toggle($event,document.getElementById(id));
}

Related

Primefaces - Customize p:confirmDialog content conditionally

I have a list of objects (let's call each object a record object), shown through a <p:datatable> component. Each record has a delete button column.
When a user clicks on the delete button of the record, a <p:confirmDialog> is shown, asking for user confirmation. What I want is to customize the content of the confirmDialog under corresponding circumstances (for example show/do not show a checkbox concerning the value of a property of the record, f.e if record.isPersonal, show the checkbox, else not.
Unfortunately, that does not seem to be working as the checkbox is always shown in case the first record satisfies the condition, and the opposite in case it does not. After some research I found out that especially in previous Primefaces versions, they used to use the "JS way" (creating two separate confirm dialogs and proportionally use PF('widgeName').show(), but I would like to know whether any out of the box solution exists in Primefaces 7.0 version which I use through the <p:confirm> tag or something else. Code example below:
<p:dataTable id="recordsTable" lazy="true" value="#{myBean.myList.records}" var="record...">
<p:column styleClass="deleteColumn">
<p:commandButton ...>
<p:confirm .../>
</p:commandButton>
<p:confirmDialog widgetVar="delete_record_dialog" global="true" showEffect="fade" hideEffect="fade">
<p:selectBooleanCheckbox
rendered="#{record.isPersonal}"
....>
</p:selectBooleanCheckbox>
<p:commandButton value="#{msg.yes}" type="button"
styleClass="ui-confirmdialog-yes" icon="pi pi-check"
/>
<p:commandButton value="#{msg.no}" type="button" styleClass="ui-confirmdialog-no"
icon="pi pi-times"
/>
</p:confirmDialog>
</p:column>
</p:dataTable>
Thanks in advance!
I think rather than using ConfirmDialog, you may have to revert to building you own custom dialog, either using p:dialog or using the dialog framework. Using dialog framework you can create a simple confirm dialog box and can pass the data into the dialog programatically - e.g. a flag based on the current row to indicate if the checkbox should be shown. The dialog framework also gives an easy way to return data back from the dialog to the calling page using the dialogReturn ajax event.
You could use the <p:confirmDialog message=""/> to conditionally display different messages like this:
<p:confirmDialog widgetVar="delete_record_dialog"
header="Record delete"
message="Are you sure you want to delete #{record.isPersonal ? 'your personal' : 'this'} record?">
<h:form id="recordDeleteForm">
<p:commandButton value="#{msg.yes}" update=":tableForm"
oncomplete="PF('deleteDialog').hide(); PF('recordsTableWidgetVar').filter()"/>
<p:commandButton value="#{msg.no}" type="button"
onclick="PF('delete_record_dialog').hide()"/>
</h:form>
</p:confirmDialog>
If you want to fit in more content then you could use <f:facet name="message">...</f:facet/>
See also:
<p:confirmDialog> with a parameter message

Adding command button in p:schedule header

How do i add a "custom" button in p:schedule? I tried doing it with the help of a header facet like in the p:dataTable component, but to no avail.
Sorry for being unclear initially, it also probably didnt help that someone butchered my question by editting it's title, here are more details with snippets of code.
I want to do somthing similar to this:
<p:tab title="#{bean.name}" value="bean" >
<f:facet name="title">
<h:outputText value="#{bean.name}"/>
<p:selectBooleanButton styleClass="...">
<p:ajax converter="... />
</p:selectBooleanButton>
</f:facet>
</p:tab>
The code is creating a tab (for an accordionpanel) with a text+button in it instead of the default text only.
I am looking for the same behaviour in a p:schedule component.
<p:schedule value="#{scheduleView.lazyEventModel}" leftHeaderTemplate="today" centerHeaderTemplate="prev, title, next" rightHeaderTemplate="month, agendaWeek" >
<f:facet name="header">
<h:outputText value="#{bean.name}"/>
<p:commandButton styleClass="..."/>
</f:facet>
</p:schedule>
The same add a button to the header principle, except this time on schedule. And of course since schedule does not have any facets available for overwriting this approach does not work.
Finally, my question is: How do I add a button in the p:schedule header like in the picture above?
P.S: Forgive me for confusing you people with datatable, used it because it supports multiple facets (header, footer).
Client side it is all html that can be manipulated with javascript (jquery if you like).
So if you create your schedule and button like this:
<h:panelGroup id="mySchedule">
<p:schedule value="#{scheduleView.lazyEventModel}" leftHeaderTemplate="today" centerHeaderTemplate="prev, title, next" rightHeaderTemplate="month, agendaWeek"/>
<h:outputText value="#{bean.name}" id="scheduleOutputText"/>
<p:commandButton styleClass="..." id="scheduleCommandButton"/>
</h:panelGroup>
You can move the h:outputText and p:commandButton into the schedule with jquery dom manipulation.
$("#scheduleCommandButton").insertBefore(".fc-left");
$("#scheduleOutputText").insertBefore("#scheduleCommandButton");
If you put this script inside the panelGroup and update the panelGroup each time you'd normally update the schedule via ajax, the button and text should be added moved again in an update.
<h:panelGroup id="mySchedule">
<p:schedule value="#{scheduleView.lazyEventModel}" leftHeaderTemplate="today" centerHeaderTemplate="prev, title, next" rightHeaderTemplate="month, agendaWeek"/>
<h:outputText value="#{bean.name}" id="scheduleOutputText"/>
<p:commandButton styleClass="..." id="scheduleCommandButton"/>
$("#scheduleCommandButton").insertBefore(".fc-left");
$("#scheduleOutputText").insertBefore("#scheduleCommandButton");
</h:panelGroup>
You could also put the text and button in a panelgroup and move that by dom manipulation. Just make sure you use the right selectors (I did not prepended the form id to the selectors above). You can also use class selectors instead of id's. That is up to you.
But make sure you use this with care. As #JasperDeVries mentioned in a comment, it is easy to break things. Use it just sparsely, carefully and only as a last resort

<p:overlayPanel> shows blank white after updating via command link/button

I am looking for solution that p:overlayPanel content should update it self while clicking commendButton or commendLink. But its showing blank white p:overlayPanel when i click the button. The updating with the p:overlayPanel id in that button or link. I have tried many ways nothing worked out. Is my code wrong?
Please suggest some solution.
Here is my code :
<ui:repeat id="profiles" value="#{items.lstProfiles}" var="profile">
<p:commandLink id="profileLink" value="show" update="moviePanel"
actionListener="#{dashBoardController.showProfileOverlay}"/>
<p:overlayPanel id="moviePanel" for="profileLink" hideEffect="fade"
dynamic="true" style="width:300px; height: 150px;">
<h:panelGrid columns="2">
<h:outputText value="Name : "/>
<h:outputText value="#{dashBoardController.selectedProfile.name}"/>
</h:panelGrid>
</p:overlayPanel>
</ui:repeat>
Usually dynamic="true" should do the job but some times it fails.So to update the contents inside the Overlay.
There are many ways to do this by tweaking Jquery.
Simplest one I can think of is:
Since ui:repeat generates dynamic ID for p:overlayPanels you can update by providing unique css class name from every iteration.
In Primefaces you can select a component using css class also using: #(.myclass).
And so you can update that component also:- update="#(.mystyle)"
Example:
<h:form>
<ui:repeat var="patient" value="#{dataCenter.patientList}">
<p:commandLink value="#{patient.firstName}" id="patientNameLnk" update="#(.overlay-class-#{patient.patientId})"/><br/>
<p:overlayPanel for="patientNameLnk">
<h:panelGroup styleClass="overlay-class-#{patient.patientId}">
#{patient.firstName}
#{patient.lastName}
#{patient.dob}
</h:panelGroup>
</p:overlayPanel>
</ui:repeat>
</h:form>
In the above example css class name for every h:panelGroup will be generated as overlay-class-<PATIENT_ID> so every h:panelGroup will have unique class name(if patientId is unique).
Note: Note that I'm updating h:panelGroup inside p:overlayPanel because if you update p:overlayPanel then it might start flickering(it happened to me while executing above example).

Can 'update' attribute update two components simultanously?

I have a question. Is that possible to update two components at a time?
I am trying a code like this:
<h:panelGroup id="pickList">
<p:panel rendered="#{customCalender.visible}" widgetVar="searchTableBox">
//Some codes.....
<p:commandButton value="Cancel" update="pickList" actionListener="#{customCalender.closeList}" style="background:#25A6E1;color:red;font-family:'Helvetica Neue',sans-serif;font-size:10px;border-radius:4px;" />
<p:commandButton value="Save" update="custDataTablePanel" actionListener="#{customCalender.saveTargetList}" style="background:#25A6E1;color:red;font-family:'Helvetica Neue',sans-serif;font-size:10px;border-radius:4px;"/>
</p:panel>
</h:panelGroup>
....
.....
<h:panelGroup id="custDataTablePanel">
<p:panel rendered="#{customCalender.dataTableVisible}">
..
..
</p:panel>
</h:panelGroup>
Now I want when I click on the Save button it hides the <h:panelGroup id="pickList"> and displays the <h:panelGroup id="custDataTablePanel"> so I have two boolean values to control their visibility. but I need to update two of these panels. One I did with update="custDataTablePanel" it displays the data table after the button click.(in the method saveTargetList I updated the visibility of the custDataTablePanel to true.) but cant manage to hide the panel pickList.
So I was wandering is there any way to hide and show these two panels in one button click.
Please suggest.
You can use many elements in the update attribute separated by a space
<p:commandButton update="element1 element2"/>
also you can update the whole form by using update="#form"

Primefaces blockUI to block a DataTable doesn't work when the DataTable is updated via AJAX [duplicate]

I am trying to create a datatable that displays a blockUI whenever it is busy, and I have been mostly successful. It now grays out and shows "Loading..." whenever I click either of two commandButtons, sort the datatable by clicking on a header, or page through the datatable. You can see the code for it below.
The problem is that after I have used one of the commandButtons (which runs an ajax update on the blocked element), subsequent actions do not trigger the blockUI (until I refresh the page). For example:
Load page
Click a datatable header - blockUI appears until table is finished sorting
Click one of the datatable page navigation buttons - blockUI appears until the page is loaded
Click one of the commandButtons - blockUI appears until the button's actionListener has finished
Click a datatable header - table sorts, but blockUI does not appear.
Click one of the datatable page navigation buttons - page loads, but blockUI does not appear
Click one of the commandButtons - actionListener runs and table updates, but blockUI does not appear
Reload the page - everything works properly again
Changing the commandButtons' update="" attribute to ajax="false" causes the sorting/paging to always display the blockUI, but the commandButtons to never display the blockUI.
Any ideas?
<div class="buttonDiv">
<p:commandButton ... update="resultsPanel" id="submitButton" ... />
...
<p:commandButton ... update="resultsPanel" id="resetScenarioButton" ... />
</div>
<p:panel header="Results Grid" id="resultsPanel">
...
<p:dataTable ... id="VAResults" ... >
...
</p:dataTable>
....
</p:panel>
<p:blockUI block="resultsPanel" trigger="submitButton, resetScenarioButton, VAResults">
Loading...
</p:blockUI>
The trigger attribute binds jQuery listeners on the specified elements. However if you update an element the binding gets lost. I don't know if it works, but you could try moving the <p:blockUI inside the resultsPanel. I suspect that when you update the panel the blockUI gets updated too and thus re-binding the listener to the data table.
<p:panel header="Results Grid" id="resultsPanel">
...
<p:dataTable ... id="VAResults" ... >
...
</p:dataTable>
....
<p:blockUI block="resultsPanel" trigger="submitButton, resetScenarioButton, VAResults">
Loading...
</p:blockUI>
</p:panel>
I've had the same problem and kind of simillar scenario:
<p:dataTable>
....
<p:ajax event="rowSelect" update="buttons" global="false" onstart="blockMessageButtons.show();" oncomplete="blockMessageButtons.hide();"/>
</p:dataTable>
<p:outputPanel layout="block" id="buttons">
<!-- content to be blocked -->
</p:outputPanel>
<p:blockUI block="buttons" widgetVar="blockMessageButtons"/>
The problem was that panel buttons was both updated by ajax, and blocked by blockUI. I had to divide it in two:
<p:dataTable>
....
<p:ajax event="rowSelect" update="buttons-content" global="false" onstart="blockMessageButtons.show();" oncomplete="blockMessageButtons.hide();"/>
</p:dataTable>
<p:outputPanel layout="block" id="buttons-container">
<p:outputPanel layout="block" id="buttons-content">
<!-- content to be blocked -->
</p:outputPanel>
</p:outputPanel>
<p:blockUI block="buttons-container" widgetVar="blockMessageButtons"/>
#siebz0r already provided the explanation why blockUI stops working when a component is updated.
I am using a one blockUI element in the template for all other pages and thus don't want to include more blockUI elements.
If the blockUI element is updated as well one does not need to move the blockUI inside the component that will be updated.
Here is an example:
<p:panel id="surroundingPanel">
...
<p:commandButton value="ButtonName" styleClass="blockUi"
action="actionToBeExecuted" update=":surroundingPanel :blockUiBinding" />
</p:panel>
<p:outputPanel id="blockUiBinding">
<p:blockUI block=":elementToBeBlocked" trigger="#(.blockUi)">
Loading ...
</p:blockUI>
</p:outputPanel>
The element blockUiBinding can be located anywhere, as long as it can be updated. It is wrapping the blockUI element, because blockUI generates at least two different divs. So when the wrapping element is updated also the blockUI will be updated.

Resources