web-dev-qa-db-fra.com

Comment trouver l'ID client du composant pour ajax update / render? Impossible de trouver le composant avec l'expression "foo" référencée depuis "bar"

Le code suivant est inspiré des didacticiels PrimeFaces DataGrid + DataTable et est placé dans un <p:tab> d'un <p:tabView> résidant dans un <p:layoutUnit> d'un <p:layout>. Voici la partie interne du code (à partir du composant p:tab); la partie extérieure est triviale.

<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>

Lorsque je clique sur le <p:commandLink>, le code cesse de fonctionner et donne le message suivant:

Impossible de trouver le composant avec l'expression "insTable: display" référencée depuis "tabs: insTable: select".

Lorsque j'essaie la même chose avec <f:ajax>, il échoue avec un message différent qui dit essentiellement la même chose:

<f:ajax> contient un identifiant inconnu "insTable: display" ne peut pas le localiser dans le contexte du composant "onglets: insTable: select"

Comment cela est-il causé et comment puis-je le résoudre?

126
perissf

Rechercher dans la sortie HTML l'ID client réel

Vous devez regarder dans la sortie HTML générée pour trouver le bon ID client. Ouvrez la page dans le navigateur, faites un clic droit et Voir la source . Localisez la représentation HTML du composant JSF qui vous intéresse et prenez son id comme ID client. Vous pouvez l'utiliser de manière absolue ou relative en fonction du conteneur de nommage actuel. Voir le chapitre suivant.

Remarque: s'il contient des index d'itération tels que :0:, :1:, etc. (car il fait partie d'un composant itératif), vous devez vous rendre compte que la mise à jour d'un cycle d'itération spécifique n'est pas toujours prise en charge. Voir le bas de la réponse pour plus de détails à ce sujet.

Mémorisez les composants NamingContainer et donnez-leur toujours un identifiant fixe

Si un composant que vous souhaitez référencer par ajax process/execute/update/render se trouve dans le même NamingContainer parent, faites simplement référence à son propre ID.

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

Si n'est pas dans le même NamingContainer, vous devez le référencer à l'aide d'un ID client absolu. Un ID client absolu commence par le caractère de séparation NamingContainer, qui est par défaut :.

<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 les composants sont par exemple <h:form>, <h:dataTable>, <p:tabView>, <cc:implementation> (ainsi, tous les composants composites), etc. Vous les reconnaissez facilement par En regardant la sortie HTML générée, leur ID sera ajouté au numéro de client généré de tous les composants enfants. Notez que lorsqu'ils n'ont pas d'identifiant fixe, JSF utilisera alors un identifiant généré automatiquement au format j_idXXX. Vous devez absolument éviter cela en leur donnant un identifiant fixe. Les OmniFaces NoAutoGeneratedIdViewHandler peuvent être utiles à cet égard pendant le développement.

Si vous savez trouver le javadoc de la variable UIComponent en question, vous pouvez également vérifier si elle implémente l'interface NamingContainer ou non. Par exemple, HtmlForm (le UIComponent derrière la balise <h:form>) indique qu'il implémente NamingContainer, mais le HtmlPanelGroup (le UIComponent derrière le <h:panelGroup> tag) le fait pas le montrer, donc il n’implémente pas NamingContainer. Voici le javadoc de tous les composants standard et voici le javadoc de PrimeFaces .

Résoudre votre problème

Donc dans votre cas de:

<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">

La sortie HTML générée de <h:panelGrid id="display"> ressemble à ceci:

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

Vous devez prendre exactement id comme ID client, puis préfixer : pour pouvoir être utilisé dans update:

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

Référencement extérieur include/tagfile/composite

Si ce lien de commande se trouve dans un fichier include/tag et que la cible se trouve en dehors de celui-ci et que vous ne connaissez donc pas nécessairement l'ID du conteneur de noms parent du conteneur de noms actuel, vous pouvez le référencer de manière dynamique via UIComponent#getNamingContainer(), comme suit:

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

Ou, si ce lien de commande est à l'intérieur d'un composant composite et que la cible est à l'extérieur de celui-ci:

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

Ou, si le lien de commande et la cible se trouvent dans le même composant composite:

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

Voir aussi Obtenir l'identifiant du conteneur de dénomination parent dans le modèle pour l'attribut render/update

Comment ça marche sous les couvertures

Ce tout est spécifié comme "expression de recherche" dans le UIComponent#findComponent() javadoc :

Une expression de recherche consiste soit en un identifiant (qui correspond exactement à la propriété id d'un UIComponent, soit en une série de tels identifiants liés par la valeur du caractère UINamingContainer#getSeparatorChar. L'algorithme de recherche devrait fonctionner comme suit, bien que d'autres alogrithmes puissent être utilisés tant que le résultat final est le même:

  • Identifiez la variable UIComponent qui servira de base à la recherche en s'arrêtant dès que l'une des conditions suivantes est remplie:
    • Si l'expression de recherche commence par le caractère de séparation (appelée expression de recherche "absolue"), la base sera la racine UIComponent de l'arborescence des composants. Le caractère de séparation principal sera supprimé et le reste de l'expression de recherche sera traité comme une expression de recherche "relative", comme décrit ci-dessous.
    • Sinon, si ce UIComponent est un NamingContainer, il servira de base.
    • Sinon, recherchez les parents de ce composant. Si un NamingContainer est rencontré, ce sera la base.
    • Sinon (si aucun NamingContainer n'est rencontré), la racine UIComponent sera la base.
  • L'expression de recherche (éventuellement modifiée à l'étape précédente) est maintenant une expression de recherche "relative" qui sera utilisée pour localiser le composant (le cas échéant) ayant un identifiant correspondant, dans la portée du composant de base. Le match se déroule comme suit:
    • Si l'expression de recherche est un identificateur simple, cette valeur est comparée à la propriété id, puis récursive à travers les facettes et les enfants de la base UIComponent (sauf que si un descendant NamingContainer est trouvé, ses propres facettes et enfants ne sont pas recherchés).
    • Si l'expression de recherche comprend plus d'un identifiant séparé par le caractère de séparation, le premier identifiant est utilisé pour localiser un NamingContainer à l'aide des règles du point précédent. Ensuite, la méthode findComponent() de cette NamingContainer sera appelée, en transmettant le reste de l'expression de recherche.

Notez que PrimeFaces respecte également les spécifications JSF, mais RichFaces utilise "quelques exceptions supplémentaires" .

"reRender" utilise l'algorithme UIComponent.findComponent() (avec quelques exceptions supplémentaires) pour rechercher le composant dans l'arborescence des composants.

Ces exceptions supplémentaires ne sont décrites nulle part en détail, mais il est connu que les ID de composant relatifs (c'est-à-dire ceux ne commençant pas par :) ne sont pas seulement recherchés dans le contexte du parent le plus proche, NamingContainer, mais également dans tous les autres composants NamingContainer du fichier. même vue (ce qui est un travail relativement coûteux en passant).

Ne jamais utiliser prependId="false"

Si tout cela ne fonctionne toujours pas, vérifiez si vous n'utilisez pas <h:form prependId="false">. Cela échouera lors du traitement de la soumission et du rendu par ajax. Voir aussi cette question connexe: IForm avec prependId = "false" rompt <f: ajax render> .

Référence à un tour d'itération spécifique de composants itératifs

Pendant longtemps, il n'a pas été possible de référencer un élément itéré spécifique dans des composants itératifs tels que <ui:repeat> et <h:dataTable> comme ceci:

<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>

Cependant, depuis Mojarra 2.2.5, le <f:ajax> a commencé à le prendre en charge (il a simplement cessé de le valider; ainsi, vous ne seriez plus jamais confronté à l'exception mentionnée dans la question; un autre correctif d'amélioration est prévu ultérieurement).

Cela ne fonctionne pas encore dans les versions actuelles de MyFaces 2.2.7 et PrimeFaces 5.2. Le support pourrait venir dans les futures versions. En attendant, votre meilleur choix est de mettre à jour le composant itératif lui-même, ou un parent s'il ne rend pas le code HTML, comme <ui:repeat>.

Lorsque vous utilisez PrimeFaces, considérez les expressions de recherche ou les sélecteurs

Expressions de recherche PrimeFaces vous permet de référencer des composants via des expressions de recherche d'arborescence de composants JSF. JSF a plusieurs éléments intégrés:

  • @this: composant actuel
  • @form: parent UIForm
  • @all: document entier
  • @none: rien

PrimeFaces a amélioré cela avec de nouveaux mots-clés et un support d'expression composite:

  • @parent: composant parent
  • @namingcontainer: parent UINamingContainer
  • @widgetVar(name): composant identifié par widgetVar donné

Vous pouvez également mélanger ces mots-clés dans des expressions composites telles que @form:@parent, @this:@parent:@parent, etc.

PrimeFaces Selectors (PFS) comme dans @(.someclass) vous permet de référencer des composants via la syntaxe de sélecteur jQuery CSS. Par exemple. référencer des composants ayant tous une classe de style commune dans la sortie HTML. Ceci est particulièrement utile au cas où vous auriez besoin de référencer "beaucoup de" composants. Cela requiert uniquement que les composants cibles aient tous un ID client dans la sortie HTML (fixe ou générée automatiquement, peu importe). Voir aussi Comment fonctionnent les sélecteurs PrimeFaces dans update = "@ (. MyClass)"?

289
BalusC

tout d'abord: pour autant que je sache, placer un dialogue dans un tabview est une mauvaise pratique ... vous feriez mieux de le supprimer ...

et maintenant à votre question:

désolé, il m'a fallu un peu de temps pour comprendre ce que vous vouliez implémenter,

fait moi-même à mon application web tout à l'heure, et cela fonctionne

comme je l'ai dit avant de placer le p: dialogue hors du `p: tabView,

quittez le dialogue p: comme vous l'aviez initialement suggéré:

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

et le p: commandlink devrait ressembler à ceci (tout ce que j'ai fait est de changer l'attribut update)

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

la même chose fonctionne dans mon application Web, et si cela ne fonctionne pas pour vous, alors je suppose qu'il y a quelque chose qui ne va pas dans votre code de bean Java ...

9
Daniel

C'est parce que l'onglet est également un conteneur de nommage ... votre mise à jour devrait être update="Search:insTable:display" Ce que vous pouvez faire est également de placer votre boîte de dialogue en dehors du formulaire et toujours à l'intérieur de l'onglet alors: update="Search:display"

5
Lyrion

Essayez de changer update="insTable:display" en update="display". Je crois que vous ne pouvez pas préfixer l'identifiant avec l'identifiant de formulaire comme ça.

0
Mr.J4mes

Je sais que BalusC a déjà répondu à cette question, mais voici une astuce que j'utilise pour que le conteneur me dise le bon clientId.

  1. Supprimez la mise à jour de votre composant qui ne fonctionne pas
  2. Placez un composant temporaire avec une mise à jour fictive dans le composant que vous tentiez de mettre à jour
  3. si vous cliquez sur la page, l'erreur d'exception de servlet vous indiquera le bon ID client à référencer.
  4. Supprimez le composant fictif et mettez le clientId correct dans la mise à jour d'origine

Voici un exemple de code, car mes mots ne le décrivent peut-être pas le mieux.

<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"

Supprimez la mise à jour défaillante de ce composant

 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">

Ajouter un composant dans le composant de l'id que vous essayez de mettre à jour en utilisant une mise à jour qui échouera

   <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>

Hit cette page et voir l'erreur. L'erreur est la suivante: javax.servlet.ServletException: composant introuvable pour l'expression "BogusUpdate" référencé depuis onglets: insTable: BogusButton

Donc, le bon clientId à utiliser serait alors le gras et l'id du conteneur cible (à afficher dans ce cas)

tabs:insTable:display
0
jeff