No More Posting New Topics!

If you have a question or an issue, please start a thread in our Github Discussions Forum.
This forum is closed for new threads/ topics.

Navigation

    Quasar Framework

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search

    [Solved] Customized component issue: For recursive components, make sure to provide the "name" option

    Framework
    3
    22
    2708
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • S
      Stanley last edited by Stanley

      Dear Quasar team,

      Could you please have a look of my issue?
      I reproduce this issue in the link https://codesandbox.io/s/quirky-williamson-ucdw9
      and the demo can be seen here https://ucdw9.sse.codesandbox.io/pur-ords-q01

      I customized a component called select-option which is used as search condition.
      I created three vue files under folder components.
      The first one is “pur-ord-listq01.vue” which is a page to search purchase order, here a define two search condition which is implemented by component select-option. (from second file select-option.vue).

      If user click the search button, it will popup a search dialog (the third file search-help.vue). In the above of this dialog, I wanna add search condition (select-option) again which is used to restrict the search result. But it throws error in the Console:
      “[Vue warn]: Unknown custom element: <select-option> - did you register the component correctly? For recursive components, make sure to provide the “name” option.”

      It’s kind of recursive call because in “pur-ord-listq01.vue”, it calls “select-option.vue”, then it goes to “search-help.vue” and in this file it calls “select-option.vue” again.
      I have no idea how to fix it, thanks a lot for your help!

      beets 1 Reply Last reply Reply Quote 0
      • beets
        beets @Stanley last edited by

        @Stanley It looks like the error is because SelectOption includes SearchHelp, which includes SelectOption, which includes SearchHelp…

        Can you instead move this part:

            <search-help
              :open="shlpOpen"
              :fieldName="fieldName"
              @callBack="onShlpCallBack"
            />
        

        into pur-ord-listq01.vue, and emit an event from select-option to open it? That should solve the problem.

        S 1 Reply Last reply Reply Quote 0
        • S
          Stanley @beets last edited by

          @beets Thanks for your proposal.
          I folked the code to new link https://codesandbox.io/s/silly-leaf-rgotu
          and the demo link is https://rgotu.sse.codesandbox.io/pur-ords-q01
          Now you can see the select option (poType) is displayed in the search help dialog.

          However, if I wanna popup another search help dialog for poType, how can I do it?
          It seems the recursive call can’t be avoided. (I did it on line 41 in file “search-help.vue”)
          And again, the same error occurs in the Console:
          [Vue warn]: Unknown custom element: <search-help> - did you register the component correctly? For recursive components, make sure to provide the “name” option.

          Thanks for your help!

          beets 1 Reply Last reply Reply Quote 0
          • beets
            beets @Stanley last edited by

            @Stanley Okay, I misunderstood and throught you would never need to open the dialog recursively. I forked your original codepen to make it work: https://codesandbox.io/s/funny-curran-zm0yw

            What I did was:

            • Create a new boot file, modal.js
            • Added it to the boot section in quasar.conf.js, and also added the Dialog Plugin
            • The boot file adds a method to Vue.prototype called $showModal which can be called from anywhere
            • I had to change search-help.vue a bit, to follow the custom dialog template: https://quasar.dev/quasar-plugins/dialog#Invoking-custom-component
            • Now, seach-option.vue calls the dialog like this: this.$showModal({ fieldname: this.fieldname }).onOk(this.onShlpCallBack)
            • The dialog can return a payload if needed, I just returned a simple object for demonstration.
            S dobbel 2 Replies Last reply Reply Quote 0
            • S
              Stanley @beets last edited by Stanley

              @beets Wow, it’s fantastic! I learned a lot.

              Actually, before seeing your reply, I also solved how to deal with recursive call.
              See this link: https://codesandbox.io/s/eloquent-frost-8vquu

              But I don’t know what’s the disadvantage of my way, could you please evaluate it?
              1). From your step 1 to 3, I register my global component in file “/router/index.js”. Is this ok?
              2). For step 4, it’s great I like this way because it’s more like of quasar way.
              Just one question how it exit recursive call? As you can see my file “search-help.vue” line 43, I add the “v-if” condition to exit the recursive call.

              Thank you so much!

              beets 1 Reply Last reply Reply Quote 0
              • beets
                beets @Stanley last edited by

                @Stanley

                From your step 1 to 3, I register my global component in file “/router/index.js”. Is this ok?

                Normally you would use a boot file for registering global components:

                // src/boot/components.js 
                // make sure to add 'components' to quasar.conf.js boot section
                import SelectOption from '../components/select-option.vue'
                import SearchHelp from '../components/search-help.vue'
                
                export default async ({ app, router, store, Vue }) => {
                  Vue.component('SelectOption', SelectOption)
                  Vue.component('SearchHelp', SearchHelp)
                }
                

                It’s not much different than placing it in the routes.js file, except more clean as components don’t really belong in the router file.

                Just one question how it exit recursive call? As you can see my file “search-help.vue” line 43, I add the “v-if” condition to exit the recursive call.

                I’m not quite sure what you’re asking here. Does either your example or mine behave correctly? Or are you trying to make it so when you hit cancel all dialogs close?

                S 1 Reply Last reply Reply Quote 0
                • S
                  Stanley @beets last edited by

                  @beets Both are working correctly for the recursive call and I don’t wanna hit cancel to close all dialogs.
                  Regarding my code (using QDialog component), I checked the vue doc https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components, it has to add “v-if” to avoid “max stack size exceeded” error. That’s why add line 44 in file “search-help.vue”.
                  So for your way (using QDialog plugin), however I didn’t find such code to avoid infinite recursive call. Maybe for this way, it’s no need to worry about it.

                  Regards

                  beets 1 Reply Last reply Reply Quote 0
                  • S
                    Stanley last edited by

                    @beets Now I encounter another issue of integrating i18n, could you please have a look?
                    For below code, it can’t work and the error message is "Error in render: “TypeError: Cannot read property ‘t’ of undefined”.
                    However, it works when using QDialog component.
                    Thanks a lot!

                    <template>
                      <q-dialog ref="dialog" @show="onShow" @hide="onHide">
                        <q-layout view="Lhh lpR fff" container class="bg-white">
                          <q-header class="bg-primary">
                            <q-toolbar>
                              <q-toolbar-title>
                                {{ $i18n.t('sa.searchHelpDialog') }}
                              </q-toolbar-title>
                    
                    beets 1 Reply Last reply Reply Quote 0
                    • beets
                      beets @Stanley last edited by

                      @Stanley I think it has something to do with the parent property here:

                      export default async ({ app, router, store, Vue }) => {
                        Vue.prototype.$showModal = (props) => {
                          return Dialog.create({
                            ...props,
                            component: SearchHelp,
                            parent: app.$root
                          })
                        }
                      }
                      

                      Try changing app.$root to router.app.$root and see if it works, that’s what I have in my codebase.

                      S 1 Reply Last reply Reply Quote 1
                      • beets
                        beets @Stanley last edited by

                        @Stanley said in Customized component issue: For recursive components, make sure to provide the "name" option:

                        Regarding my code (using QDialog component), I checked the vue doc https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components, it has to add “v-if” to avoid “max stack size exceeded” error. That’s why add line 44 in file “search-help.vue”.
                        So for your way (using QDialog plugin), however I didn’t find such code to avoid infinite recursive call. Maybe for this way, it’s no need to worry about it.

                        Okay, yes I see what you mean. You’re correct, with the dialog plugin there’s no need for a recursive check since the dialog gets called from outside any component.

                        1 Reply Last reply Reply Quote 0
                        • S
                          Stanley @beets last edited by

                          @beets Super!

                          1 Reply Last reply Reply Quote 0
                          • S
                            Stanley last edited by

                            @beets Thank you so much! Without your help, I can’t do it.

                            beets 1 Reply Last reply Reply Quote 0
                            • beets
                              beets @Stanley last edited by

                              @Stanley No problem, glad I can help. So did router.app.$root work? I’m not sure exactly why it doesn’t work with app.$root, maybe there’s something I’m missing as well.

                              S 1 Reply Last reply Reply Quote 0
                              • S
                                Stanley @beets last edited by Stanley

                                @beets Yes, it works with router.app.$root. However, as you know, there are two boot files on my side, axios and i18n and axios works with app.$root.
                                Is it because the different way of definition? (see below link)
                                https://quasar.dev/quasar-cli/boot-files#Axios
                                https://quasar.dev/quasar-cli/boot-files#vue-i18n

                                1 Reply Last reply Reply Quote 0
                                • dobbel
                                  dobbel @beets last edited by dobbel

                                  @beets

                                  Create a new boot file, modal.js

                                  I was looking at your custom dialog boot file. And I was wondering why this is not working: Instead of assigning the dialog to Vue.prototype.$showModal I tried to assign it to showModel. I did that so I could use import showModel in other files.

                                  boot/model.js

                                  import {Dialog} from 'quasar'
                                  import SearchHelp from 'components/search-help'
                                  
                                  let showModel = null
                                  
                                  export default async ({app, router, store, Vue}) => {
                                    showModel = (props) => {
                                      return Dialog.create({
                                        ...props,
                                        component: SearchHelp,
                                        parent: app.$root
                                      })
                                    }
                                  }
                                  
                                  export { showModel }
                                  
                                  

                                  Some other file ( like search-help.vue ) :

                                  import showModal from "src/boot/modal.js"
                                  ...
                                  showModal({ fieldname: this.fieldname }).onOk(this.onShlpCallBack)
                                  
                                  beets 1 Reply Last reply Reply Quote 0
                                  • beets
                                    beets @dobbel last edited by beets

                                    @dobbel It might just be this line:

                                    import showModal from "src/boot/modal.js"
                                    

                                    change it to:

                                    import {showModal} from "src/boot/modal.js"
                                    

                                    Edit: that would be because the boot function is the default export, while showModal is a named export.

                                    dobbel 1 Reply Last reply Reply Quote 0
                                    • dobbel
                                      dobbel @beets last edited by dobbel

                                      @beets

                                      correct I needed to change the import like you suggested.

                                      Also I needed to change the model.js. I had to introduce a temp variable instead of showModel inside the default export .

                                      import {Dialog} from 'quasar'
                                      import SearchHelp from 'components/search-help'
                                      
                                      let showModel = null
                                      
                                      export default async ({app, router, store, Vue}) => {
                                        let temp = (props) => {
                                          return Dialog.create({
                                            ...props,
                                            component: SearchHelp,
                                            parent: app.$root
                                          })
                                        }
                                        showModel= temp
                                      }
                                      
                                      export { showModel }
                                      

                                      Thanks for the quick response!

                                      S 1 Reply Last reply Reply Quote 0
                                      • S
                                        Stanley @dobbel last edited by Stanley

                                        @dobbel It seems it has to import “model.js” every file if you want to use it.
                                        If it is, I would prefer the previous way because it is a “real” global component.
                                        Just like axios, i18n, after define them in boot file, I can call it everywhere without importing anything.
                                        How do you think?

                                        dobbel 1 Reply Last reply Reply Quote 0
                                        • dobbel
                                          dobbel @Stanley last edited by

                                          @Stanley

                                          That’s correct you’ll have to import it in every file. But generally globals are considered a bad practice.

                                          1 Reply Last reply Reply Quote 0
                                          • beets
                                            beets last edited by beets

                                            @Stanley @dobbel Here’s another snippet I use when I want to be able to show different modals:

                                            import Error from 'components/modals/error'
                                            import Print from 'components/modals/print'
                                            import CategoryFilter from 'components/modals/category-filter'
                                            import Debug from 'components/modals/debug'
                                            
                                            const modals = {
                                              'error': Error,
                                              'print': Print,
                                              'category-filter': CategoryFilter,
                                              // 'debug': () => Debug, // Edited to fix this line below, was bad copy / paste
                                              'debug': Debug,
                                            }
                                            
                                            export default async ({ app, router, store, Vue }) => {
                                              Vue.prototype.$showModal = (type, props) => {
                                                if(!modals.hasOwnProperty(type)) {
                                                  throw new Error('invalid modal')
                                                }
                                                return Dialog.create({
                                                  ...props,
                                                  component: modals[type],
                                                  parent: app.$root
                                                })
                                              }
                                            }
                                            
                                            

                                            Then I can use it like $showModal('error', { ...props }).

                                            Regarding global vs importing, I attach quite a lot to vue prototype, things like $api, $logger, $ui (which is where my showModal method lives, among other things.) All of that is done via an init boot file, so there isn’t the question of where all of these objects came from (one of the arguments against global usually.)

                                            If you’re using SSR, there are issues with both methods here as they’re written, which you can fix it with the methods described by this awesome article: https://dev.to/quasar/quasar-ssr-using-cookies-with-other-libs-services-4nkl . With this method, you do end up calling it via $showModal instead of an import. If you don’t use SSR, either method is fine really, I just prefer the convenience of not having to import it.

                                            dobbel 1 Reply Last reply Reply Quote 1
                                            • First post
                                              Last post