Как узнать идентификатор клиента компонента для обновления / рендеринга ajax? Не удается найти компонент с выражением «foo», на который ссылается «bar»

143

Следующий код вдохновлен PrimeFaces DataGrid + DataTable Обучение и положить в <p:tab>из <p:tabView>проживания в <p:layoutUnit>из <p:layout>. Вот внутренняя часть кода (начиная с p:tabкомпонента); внешняя часть тривиальна.

<p:tabView id="tabs">
    <p:tab id="search" title="Search">                        
        <h:form id="insTable">
            <p:dataTable id="table" var="lndInstrument" value="#{instrumentBean.instruments}">
                <p:column>
                    <p:commandLink id="select" update="insTable:display" oncomplete="dlg.show()">
                        <f:setPropertyActionListener value="#{lndInstrument}" 
                                        target="#{instrumentBean.selectedInstrument}" />
                        <h:outputText value="#{lndInstrument.name}" />
                    </p:commandLink>                                    
                </p:column>
            </p:dataTable>
            <p:dialog id="dlg" modal="true" widgetVar="dlg">
                <h:panelGrid id="display">
                    <h:outputText value="Name:" />
                    <h:outputText value="#{instrumentBean.selectedInstrument.name}" />
                </h:panelGrid>
            </p:dialog>                            
        </h:form>
    </p:tab>
</p:tabView>

Когда я нажимаю <p:commandLink>, код перестает работать и выдает сообщение:

Не удается найти компонент с выражением «insTable: display», на который ссылается «tabs: insTable: select».

Когда я пытаюсь использовать то же самое <f:ajax>, он терпит неудачу с другим сообщением, в основном говорящим то же самое:

<f:ajax> содержит неизвестный идентификатор «insTable: display» не может найти его в контексте компонента «tabs: insTable: select»

Как это вызвано и как я могу решить эту проблему?

периссф
источник

Ответы:

318

Посмотрите в выводе HTML фактический идентификатор клиента

Вам нужно просмотреть сгенерированный HTML-вывод, чтобы узнать правильный идентификатор клиента. Откройте страницу в браузере, щелкните правой кнопкой мыши и просмотрите исходный код . Найдите HTML-представление интересующего компонента JSF и возьмите его в idкачестве идентификатора клиента. Вы можете использовать его абсолютным или относительным способом в зависимости от текущего контейнера именования. См. Следующую главу.

Примечание: если он содержит индекс итерации, например :0:, :1:и т. Д. (Потому что он находится внутри итерационного компонента), тогда вам необходимо понимать, что обновление определенного раунда итерации не всегда поддерживается. См. Более подробную информацию в нижней части ответа.

Запоминать NamingContainerкомпоненты и всегда давать им фиксированный идентификатор

Если компонент, на который вы хотите ссылаться с помощью процесса / выполнения / обновления / рендеринга ajax, находится внутри одного и того же NamingContainerродителя, просто укажите его собственный идентификатор.

<h:form id="form">
    <p:commandLink update="result"> <!-- OK! -->
    <h:panelGroup id="result" />
</h:form>

Если он не находится внутри одного и того же NamingContainer, вам необходимо указать его, используя абсолютный идентификатор клиента. Абсолютный идентификатор клиента начинается с NamingContainerсимвола-разделителя, который используется по умолчанию :.

<h:form id="form">
    <p:commandLink update="result"> <!-- FAIL! -->
</h:form>
<h:panelGroup id="result" />
<h:form id="form">
    <p:commandLink update=":result"> <!-- OK! -->
</h:form>
<h:panelGroup id="result" />
<h:form id="form">
    <p:commandLink update=":result"> <!-- FAIL! -->
</h:form>
<h:form id="otherform">
    <h:panelGroup id="result" />
</h:form>
<h:form id="form">
    <p:commandLink update=":otherform:result"> <!-- OK! -->
</h:form>
<h:form id="otherform">
    <h:panelGroup id="result" />
</h:form>

NamingContainerкомпоненты, например <h:form>, <h:dataTable>, <p:tabView>, <cc:implementation>(таким образом, все составные компоненты) и т.д. Вы узнаете их легко, посмотрев на сгенерированном выводе HTML, их ID будет предварять сгенерированный идентификатор клиента всех дочерних компонентов. Обратите внимание: если у них нет фиксированного идентификатора, JSF будет использовать автоматически сгенерированный идентификатор в j_idXXXформате. Вы должны полностью избегать этого, давая им фиксированный идентификатор. В OmniFacesNoAutoGeneratedIdViewHandler может быть полезен в этом процессе разработки.

Если вы знаете, что найти javadoc по рассматриваемому UIComponentвопросу, вы также можете просто проверить там, реализует ли он NamingContainerинтерфейс или нет. Например, HtmlForm( тег UIComponentпозади <h:form>) показывает, что он реализует NamingContainer, но HtmlPanelGroup( тег UIComponentпозади <h:panelGroup>) не показывает его, поэтому он не реализует NamingContainer. Вот javadoc всех стандартных компонентов и javadoc PrimeFaces .

Решение твоей проблемы

Итак, в вашем случае:

<p:tabView id="tabs"><!-- This is a NamingContainer -->
    <p:tab id="search"><!-- This is NOT a NamingContainer -->
        <h:form id="insTable"><!-- This is a NamingContainer -->
            <p:dialog id="dlg"><!-- This is NOT a NamingContainer -->
                <h:panelGrid id="display">

Сгенерированный вывод HTML <h:panelGrid id="display">выглядит следующим образом:

<table id="tabs:insTable:display">

Вам нужно взять именно это в idкачестве идентификатора клиента, а затем использовать префикс с помощью :в update:

<p:commandLink update=":tabs:insTable:display">

Обращение за пределами include / tagfile / complex

Если эта командная ссылка находится внутри файла include / tagfile, а цель находится за его пределами, и, таким образом, вы не обязательно знаете идентификатор родительского контейнера именования текущего контейнера именования, вы можете динамически ссылаться на него UIComponent#getNamingContainer()следующим образом:

<p:commandLink update=":#{component.namingContainer.parent.namingContainer.clientId}:display">

Или, если эта командная ссылка находится внутри составного компонента, а цель находится вне его:

<p:commandLink update=":#{cc.parent.namingContainer.clientId}:display">

Или, если и ссылка на команду, и цель находятся внутри одного составного компонента:

<p:commandLink update=":#{cc.clientId}:display">

См. Также Получить идентификатор родительского контейнера именования в шаблоне для атрибута render / update

Как работает под одеялом

Все это определяется как «поисковое выражение» в в UIComponent#findComponent()Javadoc :

Выражение поиска состоит либо из идентификатора (который согласован точно против собственности идентификатора UIComponentили серии таких идентификаторов , связанных по UINamingContainer#getSeparatorCharзначению символов. Алгоритм поиска должен работает следующим образом , хотя альтернативные alogrithms могут быть использованы до тех пор , как конечный результат тот же:

  • Определите, UIComponentчто будет основой для поиска, остановив его, как только будет выполнено одно из следующих условий:
    • Если поисковое выражение начинается с символа-разделителя (называемого «абсолютным» поисковым выражением), базой будет корень UIComponentдерева компонентов. Начальный символ-разделитель будет удален, а оставшаяся часть поискового выражения будет рассматриваться как «относительное» поисковое выражение, как описано ниже.
    • В противном случае, если это UIComponentявляется NamingContainerон будет служить в качестве основы.
    • В противном случае найдите родителей этого компонента. Если NamingContainerвстречается, то это будет база.
    • В противном случае (если нет NamingContainer) корень UIComponentбудет базой.
  • Выражение поиска (возможно, измененное на предыдущем шаге) теперь является «относительным» выражением поиска, которое будет использоваться для поиска компонента (если есть), имеющего совпадающий идентификатор, в пределах области действия базового компонента. Матч проводится следующим образом:
    • Если поисковое выражение является простым идентификатором, это значение сравнивается со свойством id, а затем рекурсивно проходит через фасеты и дочерние элементы базы UIComponent(за исключением того, что если потомок NamingContainerнайден, его собственные фасеты и дочерние элементы не ищутся).
    • Если поисковое выражение включает более одного идентификатора, разделенного символом-разделителем, первый идентификатор используется для поиска NamingContainerпо правилам в предыдущем пункте маркера. Затем будет вызван findComponent()метод this NamingContainer, передав остаток поискового выражения.

Обратите внимание, что PrimeFaces также придерживается спецификации JSF, но RichFaces использует «некоторые дополнительные исключения» .

«reRender» использует UIComponent.findComponent()алгоритм (с некоторыми дополнительными исключениями) для поиска компонента в дереве компонентов.

Эти дополнительные исключения нигде подробно не описаны, но известно, что относительные идентификаторы компонентов (то есть те, которые не начинаются с :) ищутся не только в контексте ближайшего родителя NamingContainer, но и во всех других NamingContainerкомпонентах в том же представлении (что является относительно кстати дорогая работа).

Никогда не использовать prependId="false"

Если все это по-прежнему не работает, проверьте, не используете ли вы <h:form prependId="false">. Это завершится ошибкой во время обработки отправки и рендеринга ajax. См. Также этот связанный вопрос: UIForm с prependId = "false" breaks <f: ajax render> .

Ссылка на конкретный итерационный раунд повторяющихся компонентов

Это было в течение длительного времени не представляется возможным ссылаться на конкретный элемент итерация в переборе компонентов , таких как <ui:repeat>и <h:dataTable>как так:

<h:form id="form">
    <ui:repeat id="list" value="#{['one','two','three']}" var="item">
        <h:outputText id="item" value="#{item}" /><br/>
    </ui:repeat>

    <h:commandButton value="Update second item">
        <f:ajax render=":form:list:1:item" />
    </h:commandButton>
</h:form>

Однако, поскольку Mojarra 2.2.5 <f:ajax>начал его поддерживать (он просто прекратил его проверку; таким образом, вы больше никогда не столкнетесь с упомянутым исключением в вопросе; позже для этого планируется другое исправление улучшения).

Это пока не работает только в текущих версиях MyFaces 2.2.7 и PrimeFaces 5.2. Поддержка может появиться в будущих версиях. Между тем, лучше всего обновить сам повторяющийся компонент или родительский компонент, если он не отображает HTML, например <ui:repeat>.

При использовании PrimeFaces учитывайте поисковые выражения или селекторы.

PrimeFaces Search Expressions позволяет ссылаться на компоненты через выражения поиска в дереве компонентов JSF. JSF имеет несколько встроенных функций:

  • @this: текущий компонент
  • @form: родитель UIForm
  • @all: весь документ
  • @none: ничего

PrimeFaces улучшил это с помощью новых ключевых слов и поддержки составных выражений:

  • @parent: родительский компонент
  • @namingcontainer: родитель UINamingContainer
  • @widgetVar(name): компонент, идентифицированный данным widgetVar

Вы также можете смешивать эти ключевые слова в составных выражениях, таких как @form:@parent, @this:@parent:@parentи т. Д.

PrimeFaces Selectors (PFS), как в, @(.someclass)позволяет ссылаться на компоненты с помощью синтаксиса селектора jQuery CSS. Например, ссылки на компоненты, имеющие все общие классы стилей в выводе HTML. Это особенно полезно, если вам нужно сослаться на "много" компонентов. Это только требует, чтобы целевые компоненты имели весь идентификатор клиента в выводе HTML (фиксированный или автоматически сгенерированный, не имеет значения). См. Также Как работают селекторы PrimeFaces, как в update = "@ (. MyClass)"?

BalusC
источник
@jack: Просто прочтите javadoc: docs.oracle.com/javaee/6/api/javax/faces/component/… Начиная с JSF 2.0, он стал настраиваемым вместо константы.
BalusC
Разве SEPARATOR_CHAR не устарел? Можете ли вы привести пример вызова вложенного компонента, например: context.getViewRoot().findComponent(":inputform" + UINamingContainer.getSeparatorChar(context) + "inputtext" );Также включите xhtml-код.
jacktrades
1
Спасибо, заметка о сбое повторного рендеринга ajax внутри формы с prependId="false"сохранением моего дня.
Gaim
каково точное значение идентификатора клиента, как указано в вашем объяснении? Это то же самое, что и в JSF -> «Идентификатор на стороне клиента для этого компонента». С уважением + спасибо за вашу работу.
Стив О
1
@ antonu17: Как указано в ответе, он поддерживается только в f: ajax Mojarra.
BalusC
9

прежде всего: насколько я знаю, размещение диалога внутри tabview - плохая практика ... вам лучше вынуть его ...

а теперь к вашему вопросу:

извините, мне потребовалось некоторое время, чтобы понять, что именно вы хотели реализовать,

только что сделал в моем веб-приложении, и он работает

как я уже сказал, поместите диалог p: за пределами p: tabView,

оставьте диалог p:, как вы изначально предложили:

<p:dialog modal="true" widgetVar="dlg">
    <h:panelGrid id="display">
        <h:outputText value="Name:" />
        <h:outputText value="#{instrumentBean.selectedInstrument.name}" />
    </h:panelGrid>
</p:dialog>   

и ссылка p: command должна выглядеть так (все, что я сделал, это изменил атрибут обновления)

<p:commandLink update="display" oncomplete="dlg.show()">
    <f:setPropertyActionListener value="#{lndInstrument}" 
        target="#{instrumentBean.selectedInstrument}" />
    <h:outputText value="#{lndInstrument.name}" />
</p:commandLink>  

то же самое работает в моем веб-приложении, и если это не сработает для вас, я думаю, что что-то не так в вашем коде java bean ...

Даниэль
источник
Я рекомендую вам попробовать другие изменения, которые я написал в своем ответе (с привязкой и faces-config и другим ...), это предположительно для решения вашей «ИНФОРМАЦИЯ: не удается найти компонент ...»
Даниэль
Я снова попытался реализовать ваше второе предложение, но оно все еще не работает. Диалоговое окно открывается, но не содержит данных о выбранном элементе. В журнале отображается «Невозможно найти компонент с идентификатором« j_idt31 »в поле зрения», и я не могу отлаживать больше, чем это.
Perissf
5

Это потому, что вкладка также является контейнером именования ... ваше обновление должно быть. update="Search:insTable:display"То, что вы можете сделать, просто поместите свой диалог вне формы и все еще внутри вкладки, тогда это будет:update="Search:display"

Лирион
источник
1

Я знаю, что у BalusC уже есть отличный ответ, но вот небольшой трюк, который я использую, чтобы заставить контейнер сообщить мне правильный clientId .

  1. Удалите обновление на вашем компоненте, который не работает
  2. Поместите временный компонент с фиктивным обновлением в компонент, который вы пытались обновить.
  3. нажмите на страницу, ошибка исключения сервлета сообщит вам правильный идентификатор клиента, на который вам нужно ссылаться.
  4. Удалите фиктивный компонент и укажите правильный clientId в исходном обновлении.

Вот пример кода, поскольку мои слова могут не описать его лучше всего.

<p:tabView id="tabs">
    <p:tab id="search" title="Search">                        
        <h:form id="insTable">
            <p:dataTable id="table" var="lndInstrument" value="#{instrumentBean.instruments}">
                <p:column>
                    <p:commandLink id="select"

Удалите неудачное обновление в этом компоненте

 oncomplete="dlg.show()">
                        <f:setPropertyActionListener value="#{lndInstrument}" 
                                        target="#{instrumentBean.selectedInstrument}" />
                        <h:outputText value="#{lndInstrument.name}" />
                    </p:commandLink>                                    
                </p:column>
            </p:dataTable>
            <p:dialog id="dlg" modal="true" widgetVar="dlg">
                <h:panelGrid id="display">

Добавьте компонент в компонент с идентификатором, который вы пытаетесь обновить, используя обновление, которое не удастся

   <p:commandButton id="BogusButton" update="BogusUpdate"></p:commandButton>

                    <h:outputText value="Name:" />
                    <h:outputText value="#{instrumentBean.selectedInstrument.name}" />
                </h:panelGrid>
            </p:dialog>                            
        </h:form>
    </p:tab>
</p:tabView>

Перейдите на эту страницу и просмотрите ошибку. Ошибка: javax.servlet.ServletException: не удается найти компонент для выражения «BogusUpdate», на которое ссылается вкладки: insTable: BogusButton

Таким образом, правильный clientId для использования будет жирным шрифтом плюс идентификатор целевого контейнера (в данном случае отображается)

tabs:insTable:display
Джефф
источник
0

Попробуйте сменить update="insTable:display"на update="display". Я считаю, что вы не можете префикс идентификатора таким идентификатором формы.

Мистер J4mes
источник
2
Очень старый, но вводящий в заблуждение ответ. Обратитесь к сообщению BalusC выше, в котором четко показано префикс идентификатора компонента с идентификатором включающей формы: <h: form id = "form"> <p: commandLink update = ": otherform: result"> <! - ОК! -> </ h: form> <h: form id = "otherform"> <h: panelGroup id = "result" /> </ h: form>
J Slick