Comment trouver l'ID client du composant pour la mise à jour / le rendu ajax? Impossible de trouver le composant avec l'expression "foo" référencé à partir de "bar"

140

Le code suivant est inspiré des didacticiels PrimeFaces DataGrid + DataTable et placé dans a <p:tab>of a <p:tabView>résidant dans a <p:layoutUnit>of a <p:layout>. Voici la partie interne du code (à partir du p:tabcomposant); 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 <p:commandLink>, le code cesse de fonctionner et donne le message:

Impossible de trouver le composant avec l'expression "insTable: display" référencé à partir de "onglets: insTable: select".

Lorsque j'essaye la même utilisation <f:ajax>, cela échoue avec un message différent indiquant essentiellement la même chose:

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

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

perissf
la source

Réponses:

313

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 . Recherchez la représentation HTML du composant JSF qui vous intéresse et prenez-la idcomme ID client. Vous pouvez l'utiliser de manière absolue ou relative en fonction du conteneur de dénomination actuel. Voir le chapitre suivant.

Note: si elle arrive à contenir l' itération index comme :0:, :1:, etc (parce qu'il est à l' intérieur d' un composant itérer), alors vous devez réaliser que la mise à jour d' un cycle d'itération spécifique ne sont pas toujours pris en charge. Voir le bas de la réponse pour plus de détails à ce sujet.

Mémorisez les NamingContainercomposants 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 NamingContainerparent, alors référencez simplement son propre ID.

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

Si ce n'est pas le même NamingContainer, vous devez le référencer à l'aide d'un ID client absolu. Un ID client absolu commence par le NamingContainercaractère de séparation, 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>

NamingContainercomposants sont par exemple <h:form>, <h:dataTable>, <p:tabView>, <cc:implementation>(ainsi, tous les composants composites), etc. Vous les reconnaissent facilement en regardant la sortie HTML généré, leur carte d' identité sera préfixé au client ID généré de tous les composants de l' enfant. Notez que lorsqu'ils n'ont pas d'ID fixe, JSF utilisera un ID généré automatiquement au j_idXXXformat. Vous devez absolument éviter cela en leur donnant un identifiant fixe. Les OmniFacesNoAutoGeneratedIdViewHandler peuvent être utiles à cet égard pendant le développement.

Si vous savez trouver le javadoc de l'objet UIComponenten question, vous pouvez également simplement vérifier s'il implémente l' NamingContainerinterface ou non. Par exemple, HtmlForm(la balise UIComponentderrière <h:form>) montre qu'elle implémente NamingContainer, mais HtmlPanelGroup(la balise UIComponentderrière <h:panelGroup>) ne l'affiche pas, donc elle ne l'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 cela idcomme ID client, puis préfixer avec :pour une utilisation dans update:

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

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

Si ce lien de commande est à l'intérieur d'un include / tagfile et que la cible est à l'extérieur de celui-ci, et que vous ne connaissez donc pas nécessairement l'ID du conteneur de dénomination parent du conteneur de dénomination actuel, vous pouvez le référencer dynamiquement via UIComponent#getNamingContainer()comme ceci:

<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 sont à l'intérieur du même composant composite:

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

Voir aussi Obtenir l'ID du conteneur de dénomination parent dans le modèle pour l'attribut de rendu / mise à jour

Comment ça marche sous les couvertures

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

Une expression de recherche se compose soit d'un identificateur (qui correspond exactement à la propriété id de a UIComponent, soit d'une série d'identificateurs liés par la UINamingContainer#getSeparatorCharvaleur du caractère. L'algorithme de recherche doit fonctionner comme suit, bien que d'autres algorithmes puissent être utilisés tant que le le résultat final est le même:

  • Identifiez le UIComponentqui sera la base de la recherche, en vous arrêtant dès que l'une des conditions suivantes est remplie:
    • Si l'expression de recherche commence par le caractère séparateur (appelé expression de recherche "absolue"), la base sera la racine UIComponentde l'arborescence des composants. Le caractère séparateur de tête sera supprimé et le reste de l'expression de recherche sera traité comme une expression de recherche "relative" comme décrit ci-dessous.
    • Sinon, s'il UIComponents'agit d'un, NamingContaineril servira de base.
    • Sinon, recherchez les parents de ce composant. Si un NamingContainerest rencontré, ce sera la base.
    • Sinon (si aucun NamingContainern'est rencontré), la racine UIComponentsera 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) qui a un identifiant qui correspond, dans la portée du composant de base. Le match se déroule comme suit:
    • Si l'expression de recherche est un identifiant simple, cette valeur est comparée à la propriété id, puis récursivement à travers les facettes et les enfants de la base UIComponent(sauf que si un descendant NamingContainerest trouvé, ses propres facettes et enfants ne sont pas recherchés).
    • Si l'expression de recherche comprend plusieurs identifiants séparés par le caractère de séparation, le premier identifiant est utilisé pour localiser un NamingContainerpar les règles de la puce précédente. Ensuite, la findComponent()méthode NamingContainersera appelée, en passant le reste de l'expression de recherche.

Notez que PrimeFaces adhère également à la spécification JSF, mais RichFaces utilise «quelques exceptions supplémentaires» .

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

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

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 pendant le traitement de la soumission et du rendu ajax. Voir aussi cette question connexe: UIForm avec prependId = "false" breaks <f: ajax render> .

Référencement d'un cycle 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 comme <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 que Mojarra 2.2.5 a <f:ajax>commencé à le supporter (il a simplement cessé de le valider; ainsi, vous ne feriez plus jamais face à l'exception mentionnée dans la question; un autre correctif d'amélioration est prévu pour cela plus tard).

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 pari est de mettre à jour le composant itératif lui-même, ou un parent au cas où il ne rendrait pas le HTML, comme <ui:repeat>.

Lorsque vous utilisez PrimeFaces, pensez aux expressions de recherche ou aux sélecteurs

PrimeFaces Search Expressions vous permet de référencer des composants via des expressions de recherche d'arborescence de composants JSF. JSF a plusieurs 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 la prise en charge des expressions composites:

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

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

Les sélecteurs PrimeFaces (PFS) comme dans @(.someclass)vous permettent de référencer des composants via la syntaxe du sélecteur CSS jQuery. 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 faire référence à «un grand nombre» de composants. Cela nécessite uniquement que les composants cibles aient tous un ID client dans la sortie HTML (fixe ou généré automatiquement, peu importe). Voir aussi Comment fonctionnent les sélecteurs PrimeFaces comme dans update = "@ (. MyClass)"?

BalusC
la source
@jack: Il suffit de lire javadoc: docs.oracle.com/javaee/6/api/javax/faces/component/… Depuis JSF 2.0, il est devenu configurable au lieu d'une constante.
BalusC
SEPARATOR_CHAR n'est-il pas obsolète? Pouvez-vous donner un exemple sur la façon d'appeler un composant imbriqué, par exemple: context.getViewRoot().findComponent(":inputform" + UINamingContainer.getSeparatorChar(context) + "inputtext" );Veuillez également inclure le code xhtml.
jacktrades
1
Merci, note sur l'échec du rendu ajax à l'intérieur du formulaire avec prependId="false"sauvé ma journée.
Gaim
Quelle est la signification exacte de l'identifiant client comme indiqué dans votre explication? Est-ce le même que dans JSF -> "L'identifiant côté client pour ce composant". salutations + merci pour votre travail.
Steve Oh du
1
@ antonu17: Comme indiqué dans la réponse, il n'est pris en charge que dans f: ajax de Mojarra.
BalusC
9

tout d'abord: pour autant que je sache, placer une boîte de dialogue dans une vue tabulaire est une mauvaise pratique ... vous feriez mieux de la retirer ...

et maintenant à votre question:

désolé, il m'a fallu un certain temps pour obtenir exactement ce que vous vouliez mettre en œuvre,

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

comme je l'ai dit avant, placez le p: dialogue à côté du `p: tabView,

laissez la boîte de 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 de mise à jour)

<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 java bean ...

Daniel
la source
Je vous recommande d'essayer les autres changements que j'ai écrits dans ma réponse (avec la liaison et les faces-config et autres ...) cela suppose de résoudre votre "INFO: Impossible de trouver compone ..."
Daniel
J'ai de nouveau essayé de mettre en œuvre votre deuxième suggestion, mais cela ne fonctionne toujours pas. La boîte de dialogue s'ouvre mais ne contient pas les données de l'élément sélectionné. Le journal affiche "Impossible de trouver le composant avec l'identifiant" j_idt31 "dans la vue", et je ne suis pas en mesure de déboguer plus que cela.
perissf
5

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

Lyrion
la source
0

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

Monsieur J4mes
la source
2
Réponse très ancienne mais trompeuse. Reportez-vous au post de BalusC ci-dessus, montrant clairement le préfixe de l'ID d'un composant avec l'ID du formulaire inclus: <h: form id = "form"> <p: commandLink update = ": otherform: result"> <! - OK! -> </ h: form> <h: form id = "otherform"> <h: panelGroup id = "result" /> </ h: form>
J Slick
0

Je sais que cela a déjà une excellente réponse de BalusC, mais voici une petite 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. Mettez un composant temporaire avec une mise à jour bidon dans le composant que vous essayiez de mettre à jour
  3. appuyez sur la page, l'erreur d'exception de servlet vous indiquera l'ID client correct que vous devez référencer.
  4. Supprimez le faux composant et placez 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 dans 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">

Ajoutez un composant dans le composant de l'ID que vous essayez de mettre à jour à l'aide d'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>

Accédez à cette page et affichez l'erreur. L'erreur est: javax.servlet.ServletException: Impossible de trouver le composant pour l'expression "BogusUpdate" référencé à partir des onglets: insTable: BogusButton

Ainsi, le clientId correct à utiliser serait alors le gras plus l'ID du conteneur cible (affiché dans ce cas)

tabs:insTable:display
Jeff
la source