primefaces icon indicating copy to clipboard operation
primefaces copied to clipboard

OverlayPanel: Race condition with embedded components on AJAX-update

Open fanste opened this issue 4 years ago • 14 comments

1) Environment

  • PrimeFaces version: 8.0

2) Expected behavior

Components (e.g. p:selectOneMenu) inside a dynamic overlay (e.g. p:overlayPanel) with appendTo should not double the found entries when the jq property of the component widget is resolved

3) Actual behavior

Due to the appendTo the DOM of the dynamic overlay may survive the AJAX update which effectively doubles the DOM for a short time. The first instance is at the original position (before it is moved to its appendTo destination) which was added during the AJAX update and the second instance is the old one which may be outside the updated area.

The SelectOneMenu is refreshed before the OverlayPanel. But the old DOM of the OverlayPanel is removed during the refresh of the OverlayPanel (PrimeFaces.utils.registerDynamicOverlay > widget.addRefreshListener). Therefore the SelectOneMenu finds two DOM representation of its widget for SelectOneMenu#jqId.

4) Steps to reproduce

  • Press button Alert state ofSelectOneMenu Output should be size = 1 and label = 1
  • Press the button Update OP
  • Press button Alert state ofSelectOneMenu Output should be size = 2 and label = 11 which is wrong

5) Sample XHTML

<h:form>
    <p:commandButton value="Update OP" process="@this pnlTest" update="pnlTest" />
    <h:panelGroup id="pnlTest">
        <p:commandButton id="btnTest" value="Open Overlay" type="button" />
        <p:overlayPanel id="opTest" for="btnTest" appendTo="@(body)">
            <p:selectOneMenu value="#{testBean.selectedTest}" widgetVar="somTest">
                <f:selectItem itemLabel="1" itemValue="1" />
            </p:selectOneMenu>
        </p:overlayPanel>
    </h:panelGroup>

    <p:commandButton value="Alert state ofSelectOneMenu" type="button" onclick="alert('Size of somTest#jq = ' + PF('somTest').jq.length + '\nSelected Label = ' + PF('somTest').getSelectedLabel())" />
</h:form>

6) Sample bean

@ViewScoped
@Named
public class TestBean implements Serializable {
    private Integer _selectedTest;

    public Integer getSelectedTest() {
        return _selectedTest;
    }
    public void setSelectedTest(Integer selectedTest) {
        _selectedTest = selectedTest;
    }
}

I will try to provide a test case

fanste avatar Mar 19 '20 22:03 fanste

Reproducer: https://github.com/fanste/primefaces-test/tree/test-5710

fanste avatar Mar 19 '20 22:03 fanste

Excellent analysis and thanks for the example.

melloware avatar Mar 20 '20 12:03 melloware

@fanste maybe I am doing something wrong but I can't reproduce your issue on PF11? Attached it my reproducer I got from your post above just updated for PF11. Can you try this? pf-5710.zip

melloware avatar Mar 15 '22 19:03 melloware

appendTo="@(body)" is missing for your p:overlayPanel.

fanste avatar Mar 16 '22 10:03 fanste

Ahhh yep reproduced now pf-5710.zip .

melloware avatar Mar 16 '22 12:03 melloware

The way to fix this right now without being invasive is to add a onstart="PF('overlayPanel').destroy();" to your command button updating the OP. This fixes this specific issue but its a workaround not a fix

<h:form id="frm">
	<p:commandButton id="exports" value="Update OP" process="@this pnlTest" update="pnlTest" onstart="PF('overlayPanel').destroy();" />
	<h:panelGroup id="pnlTest">
		<p:commandButton id="btnTest" value="Open Overlay" type="button" />
		<p:overlayPanel id="opTest" widgetVar="overlayPanel" for="btnTest" appendTo="@(body)">
			<p:selectOneMenu value="#{testBean.selectedTest}" widgetVar="somTest">
				<f:selectItem itemLabel="1" itemValue="1" />
			</p:selectOneMenu>
		</p:overlayPanel>
	</h:panelGroup>
	<p:commandButton value="Alert state ofSelectOneMenu" type="button" onclick="alert('Size of somTest#jq = ' + PF('somTest').jq.length + '\nSelected Label = ' + PF('somTest').getSelectedLabel())" />
</h:form>

melloware avatar Mar 16 '22 12:03 melloware

That would definitively work. Another workaround could be to wrap the contents of the overlay in an h:panelGroup and update that instead of the whole Overlay Panel.

A long term fix could be one of:

  1. Add a cleanup-script-tag before components like Overlay Panel
  2. Improve the ajax framework, to recognize updates of such components and run the cleanup internally - that needs to work for clientside updates as well as serverside triggered updates.

fanste avatar Mar 16 '22 12:03 fanste

Agreed but your #2 is a much more invasive fix and would be tricky to test all scenarios but that would be the right place to fix it.

melloware avatar Mar 16 '22 20:03 melloware

I proposed a PR some time ago in #3951 fixing updates of appended components.

VsevolodGolovanov avatar Dec 19 '23 19:12 VsevolodGolovanov

@VsevolodGolovanov feel free to revive it if you want?

melloware avatar Dec 19 '23 19:12 melloware

I found an elegant fix to this particular issue if you want to review @VsevolodGolovanov @fanste

melloware avatar Jan 05 '24 15:01 melloware

in 14.0.0+ this will be a new property destroyWithTarget="true"

<p:overlayPanel id="opTest" for="btnTest" appendTo="@(body)" destroyWithTarget="true">

melloware avatar Feb 09 '24 13:02 melloware

@melloware , nice! But a bit unexpected that you have to opt in - is there a downside?

VsevolodGolovanov avatar Feb 09 '24 19:02 VsevolodGolovanov

Yep the downside is here: https://github.com/primefaces/primefaces/issues/11409

Too many scenarios that could break.

melloware avatar Feb 09 '24 19:02 melloware