Partage
  • Partager sur Facebook
  • Partager sur Twitter

[VueJs] récupérer les éléments enfants.

Interaction entre éléments

Sujet résolu
    15 février 2019 à 14:55:55

    Bonjour,
    Je débute sur VueJs et j'aurai besoin de quelques éclaircissements.
     
    Je vous explique ce que j’essaie de faire:

    Pour mon travail je me suis fait assigner la tache de créer un générateur de formulaire sur notre application en symfony 2.8 (coté back) ainsi que de mettre en place un framework front. Ne connaissant pas trop les framework front mais ayant déjà fait un module en vuejs je me suis dit que continuer à l'utiliser pouvais être intéressant.

    J'ai donc fait ma génération de formulaire (ça fonctionne basiquement) avec graphql pour récupérer mes données ainsi que apploQuery et  VueJs, donc, pour la partie affichage.

    J'ai donc trois niveaux de template:
    -Mon template liste qui créer le form et génère la création des Input
    -Mon template Input qui en fonction des paramètre de chaque champ va créer soit un un select2 soit d'autres champs de différents types
    -Mon template Select qui génère un select2.

    Mes questions sont les suivantes:
    -Comment, dans mon template liste, je peux récupérer les éléments inputs? 
    -Quel est la manière la plus propre de générer une interaction entre mes éléments Inputs?
    -De manière général avez-vous quelque chose a redire a mon code? je prend tous vos conseils.

    Mon code:

        template de ma liste:

    <template>
        <ApolloQuery
                :query="require('../graphql/CmsForm.gql')"
                :variables="{name,type,cliId,lanId,id}"
        >
            <template slot-scope="{ result: { loading, error, data } }">
                <!-- Loading -->
                <div v-if="loading" class="loading apollo">Loading...</div>
                <!-- Error -->
                <div v-else-if="error" class="error apollo">An error occured</div>
                <!-- Result -->
                <div style="width: 200px;" v-else-if="data" class="result apollo">
                    <form :name="formName" method="post" action="./test">
                        <div v-for="win of data.CmsForm.CmsSadmWindows" v-if="win.type !== null && win.flHidden !== true" :key="win.id" class="SadmWindow">
                            <Input ref="inputs" v-bind:formName="formName"  v-bind:inputEntry="win"></Input>
                        </div>
                        <input type="hidden" :name="formName+'[_token]'" :value="data.CmsForm.token" />
                        <input type="hidden" name="_params" :value="data.CmsForm.params" />
                        <input type="submit">
                    </form>
                </div>
    
                <!-- No result -->
                <div v-else class="no-result apollo">No result :(</div>
            </template>
        </ApolloQuery>
    </template>
    <script>
        import Input from "./Input.vue"
        export default {
            name: "ListeCmssadmWindows",
            props: ['name', 'type','cliId','lanId','id'],
    
            mounted: function() {
                var vm = this;
                this.$watch('data', function() {
                    this.$refs.inputs.load()
                });
    
                //return undefined
                console.log(this.$refs.inputs)
                this.$nextTick(function() {
                    //return undefined
                    console.log(vm.$refs.inputs)
                })
            },
            data(){
                return {
                    formName: (this.id == null ? 'deltaRM_Form_'+ this.name.charAt(0).toUpperCase() + this.name.slice(1) : 'deltaRM_Form_'+ this.name.charAt(0).toUpperCase() + this.name.slice(1) + '_Edit')
                }
            },
            components: {
                Input
            }
        }
    </script>
    
    <style scoped>
    
    </style>

        template de mes inputs:

    <template>
        <div v-if="inputEntry.representation && inputEntry.representation.toLowerCase() === 'select2' && JSON.parse(inputEntry.jsonListe).length >0">
            <!--<vSelect :value="inputEntry.jsonListe ? JSON.parse(inputEntry.jsonListe).filter(function(item){return item.id == inputEntry.value}) : null" multiple :options="JSON.parse(inputEntry.jsonListe)"></vSelect>-->
    
            <Select :options="JSON.parse(inputEntry.jsonListe)" :name="inputName" v-model="inputEntry.value">
            </Select>
        </div>
        <div v-else-if="inputEntry.representation && (inputEntry.representation.toLowerCase() === 'text' || inputEntry.representation.toLowerCase() === 'texte')">
            <input :name="inputName" type="text" :value="inputEntry.value"/>
        </div>
        <div v-else-if="inputEntry.representation && (inputEntry.representation.toLowerCase() === 'textarea' || inputEntry.representation.toLowerCase() === 'textearea')">
            <CKEditor :name="inputName" tag-name="textarea" :editor="editor"  v-model="inputEntry.value" :config="editorConfig"></CKEditor>
        </div>
        <div v-else-if="inputEntry.representation && inputEntry.representation.toLowerCase() === 'date'">
            <datepicker :name="inputName" :value="inputEntry.value" :format="'dd/MM/yyyy'" :language="fr"></datepicker>
        </div>
        <div v-else-if="inputEntry.representation && inputEntry.representation.toLowerCase() === 'float'">
            <vue-autonumeric
                :name="inputName"
                v-model="inputEntry.value"
                :options="{
                    digitGroupSeparator: ' ',
                    decimalCharacter: ',',
                    decimalCharacterAlternative: '.',
                    currencySymbolPlacement: 's',
                    roundingMethod: 'U',
                    minimumValue: '0',
                    unformatOnSubmit: true
                }"
            ></vue-autonumeric>
        </div>
        <div v-else-if="inputEntry.representation && inputEntry.representation.toLowerCase() === 'integer'">
            <vue-autonumeric
                :name="inputName"
                v-model="inputEntry.value"
                :options="{
                    digitGroupSeparator: ' ',
                    decimalCharacter: ',',
                    decimalCharacterAlternative: '.',
                    currencySymbolPlacement: 's',
                    roundingMethod: 'U',
                    minimumValue: '0',
                    decimalPlaces: '0',
                    unformatOnSubmit: true
                }"
            ></vue-autonumeric>
        </div>
        <div v-else-if="inputEntry.representation && inputEntry.representation.toLowerCase() === 'checkbox'">
            <input  type="checkbox"/>
        </div>
    
    </template>
    
    <script>
        import vSelect from 'vue-select'
        import CKEditor from '@ckeditor/ckeditor5-vue';
        import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
        import VueAutonumeric from '../../node_modules/vue-autonumeric/src/components/VueAutonumeric.vue';
        import Datepicker from 'vuejs-datepicker';
        import {en, fr} from 'vuejs-datepicker/dist/locale';
        import Select from "./Select.vue";
        export default {
            name: "Input",
            props: {
                inputEntry: Object,
                formName:  String
            },
            components: {
                vSelect:vSelect,
                CKEditor:CKEditor.component,
                VueAutonumeric:VueAutonumeric,
                Datepicker:Datepicker,
                Select:Select,
    
            },
            computed: {
                inputName: function() {
                    return this.formName+'['+this.inputEntry.nomDoctrine+']';
                }
            },
            data() {
                return {
                    selected:null,
                    fr:fr,
                    en:en,
                    editor: ClassicEditor,
                    editorConfig: {
                        // The configuration of the editor.
                    },
                };
            }
        }
    </script>
    
    <style scoped>
    
    </style>

        template de mon select:

    <template>
        <select v-bind:name="name">
            <slot></slot>
        </select>
    </template>
    
    <script>
        export default {
            name: "Select",
            props: ['options', 'value','name'],
            template: '#select2-template',
            mounted: function () {
                var vm = this;
                $(this.$el)
                // init select2
                    .select2({ data: this.optionsForSelect2(this.options) })
                    .val(this.value)
                    .trigger('change')
    
            },
            watch: {
                options: function (options) {
                    // update options
                    $(this.$el).empty().select2({ data: this.optionsForSelect2(options) })
                }
            },
            destroyed: function () {
                $(this.$el).off().select2('destroy')
            },
            methods: {
                optionsForSelect2: function(options) {
                    var old_key = "label";
                    var new_key = "text";
                    for(var i = 0; i < options.length; i++) {
                        if (old_key !== new_key) {
                            Object.defineProperty(options[i], new_key,
                                Object.getOwnPropertyDescriptor(options[i], old_key));
                            delete options[i][old_key];
                        }
                    }
                    return options;
                }
            }
        }
    </script>
    
    <style scoped>
        html, body {
            font: 13px/18px sans-serif;
        }
        select {
            min-width: 300px;
        }
    </style>



    Merci d'avance et n'hésiter surtout pas à critiquer mon code.


    Edit: je précise juste que ce qu'il y a dans mounted de ma liste n'est que le reste de mes tentative de récupérer mes inputs mais en vain xD

    Edit2: En retirant de la méthode mounted de la liste le this.$watch et le this.$nextTick j'arrive bien a récupérer mes élémenents mais je récupère aussi ceux qui ne sont pas sensés être créé.

    la méthode mounted de ma liste est donc maintenant :

    mounted: function() {
        var vm = this;
    
        //return bien ma liste
        console.log(this.$refs)
    
    }


    Edit3: bon ba finalement je comprend pas.... Dans la méthode mounted de ma liste, le console.log(this.$refs) renvoie bien un object :

    {}

    inputs: Array(10)

    0: VueComponent {_uid: 5, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}

    1: VueComponent {_uid: 6, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}

    .....

    9: VueComponent {_uid: 28, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}

    length: 10

    __proto__: Array(0)

    __proto__: Object


    mais lorsque je regarde spécifiquement  console.log(this.$refs['inputs'])  ou  console.log(this.$refs.inputs)   j'ai undefined T-T

    -
    Edité par coolswing 15 février 2019 à 16:35:42

    • Partager sur Facebook
    • Partager sur Twitter
      16 février 2019 à 2:45:29

      Bonjour,

      Pour partager un état entre plusieurs composants, tu as deux options :

      soit tu met les données et les méthodes pour les transformer dans le parent et tu les passent en props aux enfants

      soit tu utilise un gestionnaire d'état, comme https://vuex.vuejs.org/

      • Partager sur Facebook
      • Partager sur Twitter
        18 février 2019 à 10:22:56

        Donc la propriété ref ne sert pas du tout a ce que j'essaye de faire? A quoi sert t'elle alors?
        Edit: ok ça fonctionne sur un onclick:


        <input v-on:click="readRefs()" type="button">
        
        methods: {
            readRefs: function () {
                console.log(this.$refs)
                console.log(this.$refs.inputs)
            }
        },

        et avec un setTimeout ça fonctionne aussi dans mounted.... je comprend pas trop l’intérêt de mounted si tout n'est pas charger et qu'il faut encore attendre 200 ms... 

        mounted: function() {
            var vm = this
            setTimeout(function() {
                console.log(vm.$refs.inputs)
            }, 200);
        },



        -
        Edité par coolswing 18 février 2019 à 11:48:36

        • Partager sur Facebook
        • Partager sur Twitter
          18 février 2019 à 23:50:08

          si j'ai bien compris, tu veux passer les foncions du parent aux enfants, ça se fais avec les props. Tu peux aller chercher les infos dans les enfants avec une ref, mais ça rends le code moins maintenable. Les stores sont mieux.
          • Partager sur Facebook
          • Partager sur Twitter
            19 février 2019 à 11:03:17

            non ce n'est pas vraiment ma demande.
            J'avais déja compris qu'on pouvais passer une fonction en props mais comment faire pour interagir avec un autre élément?

            Exemple je veux qu'un champs select recharge ses options en fonction de la sélection d'un autre select.

            Donc effectivement je peux faire:


            <Input @change="reloadOtherField()">

            mais j'aurai besoin de passer le deuxième champs en paramètre de la fonction et ça semble pas possible de faire ça (surtout dans mon cas)

            <Input @click="reloadOtherField(otherField)">


            • Partager sur Facebook
            • Partager sur Twitter
              19 février 2019 à 12:29:18

              Le plus simple c'est de gérer tout ton formulaire dans un seul composant. Sinon, tu peux au moins gérer l'état de ton formulaire dans le composant 'racine' de ce formulaire avec tous les handlers, et tu passe tout ça en props aux enfants. Sinon, tu peux utiliser un gestionnaire d'état du genre de VueX (c'est la meilleur solution si tu veux split ton formulaire).

              Pour ton exemple, au lieu de passer ton champs en option de la fonction, tu peux aller le chercher dans ta méthode. Pour update un component, il faut faire changer ses props ou son état (ou forcer l'update mais c'est laid).

              • Partager sur Facebook
              • Partager sur Twitter
                19 février 2019 à 14:53:06

                Ok, je vais me renseigner sur Vuex, merci.
                Sinon pour récupérer mes élément dans mounted j'ai trouvé une solution:


                <div v-for="win of data.CmsForm.CmsSadmWindows" :key="win.id" class="SadmWindow">
                    <Input class="inputs" ref="inputs" v-bind:formName="formName"  v-bind:inputEntry="win"></Input>
                </div>
                methods: {
                    $_xsl__watchRefs: function (targetNode, refsSelector) {
                        return new Promise(function(resolve) {
                            let observer = new MutationObserver(callback)
                            let config = { childList: true, subtree: true }
                            observer.observe(targetNode, config)
                
                            function callback(mutationsList, observer) {
                                mutationsList.forEach(mutation => {
                                    if (mutation.type == 'childList') {
                                        let refs = targetNode.querySelectorAll(refsSelector)
                                        if (refs.length !== 0) {
                                            observer.disconnect()
                                            resolve(Array.from(refs))
                                        }
                                    }
                                })
                            }
                        })
                    }
                },
                
                mounted: function() {
                    var vm = this
                    this.$_xsl__watchRefs(this.$el, '.inputs').then(refs => {
                //je récupère bien mes inputs içi console.log(vm.$refs.inputs) }) },




                • Partager sur Facebook
                • Partager sur Twitter
                  19 février 2019 à 15:59:39

                  Sinon tu met une fonction qui proviens des props dans le mounted des enfants. Si tu part sur ce genre de fonctionnement et que tu retravaille sur ton formulaire dans 3 mois, tu vas galérer.
                  • Partager sur Facebook
                  • Partager sur Twitter
                    19 février 2019 à 17:28:40

                    De ce que j'ai lu le mounted du parent ce déclenche après le mounted des enfants donc si dans le mounted du parent ça fonctionne pas, ça ne devrait pas fonctionner chez les enfants (à moins que j'ai mal compris)
                    • Partager sur Facebook
                    • Partager sur Twitter
                      20 février 2019 à 9:55:59

                      Tu as mal compris, c'est le parent qui est monté en premier, ensuite c'est le tour des enfants.
                      • Partager sur Facebook
                      • Partager sur Twitter
                        20 février 2019 à 10:35:04

                        A oui! Je viens de tester ça, c'est très étrange surtout quand cherchant je ne tombe que sur des articles ou des postes qui disent l'inverse....:

                        "There’s nothing weird or wrong about it, it all follows from the lifecylce logically.
                        beforeCreate() and created() of the parent run first.
                        Then the parent’s template is being rendered, which means the child components get created
                        so now the children’s beforeCreate() and created() hooks execute respecitvely.
                        these child components mount to DOM elements, which calls their beforeMount() and mounted() hooks
                        and only then, after the parent’s template has finished, can the parent be mounted to the DOM, so finally the parent’s beforeMount() and mounted() hooks are called.
                        END"

                        cf:  https://forum.vuejs.org/t/order-of-lifecycle-hooks-for-parent-and-child/6681
                        • Partager sur Facebook
                        • Partager sur Twitter
                          20 février 2019 à 11:52:58

                          my bad, c'est bien le mounted des enfants en premier. Le premier état de ton composant ne créer pas d'inputs ?
                          • Partager sur Facebook
                          • Partager sur Twitter
                            20 février 2019 à 16:27:14

                            Bonjour,

                            Je ne suis pas sûr de voir le problème. Les données transmises aux enfants sont sensées être...transmises. Donc quand un enfant veut modifier quelque chose, il émet un event (c'est exactement ce qu'il se passe lorsqu'on utilise un v-model, c'est juste caché. Il remonte l'info au parent qui modifie lui-même la valeur, jamais dans l'autre sens).

                            Du coup je ne vois pas trop le problème. Par exemple, si un select doit avoir des valeurs en fonction d'un autre champ select, alors c'est probablement que ce n'est pas à lui de calculer quelles options il doit afficher, mais au parent de le faire avant de lui fournir (généralement via une computed quand c'est possible). Tenter de bypasser ce fonctionnement est une très, très mauvaise idée.

                            Si ce fonctionnement hiérarchique ne marche pas bien parce que tu as beaucoup de données transverses, alors un store pattern comme Vuex me semble plus indiqué. :)

                            • Partager sur Facebook
                            • Partager sur Twitter
                            /!\ Si je cesse de répondre c'est parce que vous êtes venus poster sans avoir suivi les cours de base sur le sujet. /!\
                              20 février 2019 à 17:11:21

                              Ne pouvant pas citer ou éditer car les boutons n'apparaissent pas, je m'excuse par avance pour les fautes d'orthographes de mes posts.

                              Il n'y avais pas vraiment de "problème" juste des questions de compréhension sur le fonctionnement et les bonnes pratiques de Vuejs.

                              Le seul problème qui est très largement contournable (donc pas bloquant) était: pourquoi dans le mounted du parent je n'arrive pas a récupérer mes enfants avec l'attribut :ref? mais je suppose que c'est a cause du v-for.

                              Je suis actuellement en train de me taper les tutos de Vue et Vuex de grafikart et effectivement cela me semble bien indiqué.

                              "my bad, c'est bien le mounted des enfants en premier. Le premier état de ton composant ne créer pas d'inputs ?"
                              J'ai tester à coup de console log dans le mounted du parent et des enfants, le mounted du parent semble bien se lancer avant celui des enfants. il semble donc que tu avais raison

                              • Partager sur Facebook
                              • Partager sur Twitter
                                21 février 2019 à 10:56:58

                                J'ai relu la doc de vue du coup, et tu avais raison, c'est bien created parent -> created enfant -> mounted enfant -> mounted parent. C'est étrange, et je ne sais pas trop quoi en penser =/
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  21 février 2019 à 11:19:18

                                  C'est surtout que je ne vois pas dans quel cas tu veux avoir accès à un enfant via ref, à moins de déclencher une validation de formulaire ou ce genre (et encore je pense que ça se contourne). Vu que Vue est """data driven""", les composants enfants ne sont finalement que des représentations graphiques des données qu'on leur donne en général. :)

                                  Vuex (le tuto officiel sur le site de Vue est très bien pour comprendre, leur doc est nickel) est un ajout qui peut paraitre un peu overkill si le projet est petit, mais ça aide pas mal quand tu as des données partagées en transverse. Comme dit le proverbe, "c'est comme des lunettes : le jour où vous en aurez besoin, vous le saurez." Donc si tu trouves que ça peut t'être utile, libre à toi de t'en servir :)

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  /!\ Si je cesse de répondre c'est parce que vous êtes venus poster sans avoir suivi les cours de base sur le sujet. /!\

                                  [VueJs] récupérer les éléments enfants.

                                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                  • Editeur
                                  • Markdown