Configure component prop defaults



  • I’d like to propose adding the ability to configure default prop values for components, possibly in quasar.conf.js. This would especially be useful for style props. For example in my app I might want QInput components to default to outlined and dense. Since Quasar uses props instead of CSS classes for a lot of style customization, there isn’t an equivalent of an application-wide CSS file to configure these defaults. The current solution I’ve seen recommended a couple of times on this forum is to write wrappers for components that override the defaults, but in my opinion this is an overly heavyweight solution when all you want to do is override some prop defaults.

    I’d like to propose a componentDefaults section in quasar.conf.js, where you could put things like:

    // quasar.conf.js
    ...
        componentDefaults: {
          // Override QInput default prop values
          input: {
            outlined: true,
            dense: true
          },
          // Override QBtn default prop values
          btn: {
            flat: true,
            class: ['bg-primary', 'text-white']
          }
          // ... etc for any other components you want to customize
        }
    

    This configuration object would be made available to Quasar components. The idea would be that components could have a hard-coded default prop value, which can be overridden by the component configuration, which in turn is overridden by explicitly passed in props. I can imagine it might not make sense to allow all props to be configured this way, but it certainly seems to make sense for the style-related props.



  • From my understanding of quasar and some answers I got here, I think the way to achieve your idea is just to make your own components by extending them, setting whatever props you need to set and use them on your project.



  • @jraez As I mentioned in my post, I am aware of the recommendation to wrap components. However I think that is an overly heavyweight solution when you just want to override some prop defaults. Let me work through an example to demonstrate why.

    Say I want to make it so in my app, QInput components default to having the outlined and dense properties set to true. Let me specify the requirements I have for my modified QInput.

    • The outlined and dense props should default to true
    • It accepts all the same props as QInput
    • It emits all the same events as QInput
    • It has all the same slots as QInput
    • It has all the same methods as QInput

    Basically, I want the exact same API as QInput, but just with some different prop defaults. The first two of these requirements are easy to implement. Something like this would work.

    <template>
       <q-input
         v-bind="$attrs"
        :outlined="outlined"
        :dense="dense"
      />
    </template>
    
    <script>
    export default {
      name: 'MyInput',
      props: {
        outlined: {
          type: Boolean,
          default: true
        },
        dense: {
          type: Boolean,
          default: true
        }
      }
    }
    </script>
    

    The v-bind="$attrs" line is all it takes to make my wrapper component accept all the same props as q-input. However there is no equivalent easy way to forward all events, slots and methods to the wrapped component. So in practice I’m probably going to compromise on my ideal requirements, and only add slots, events and methods to my wrapper as I run into a need for them. This adds extra work and provides a less ideal experience than if I could use the Quasar components directly.

    To be clear, I’m all for writing wrapper components for cases where I actually want to significantly alter or enhance the functionality of a component. I just found that wanting to use vanilla components, just with different defaults for some props, was a common use case for me, especially since Quasar uses props for a lot of the visual configuration of components. For these cases wrapping seems like overkill.



  • oh, no, don’t wrap components, extends them.

    <script>
    import QInput from 'quasar'
    export default {
      name: 'MyInput',
      extends: QInput
      props: {
        outlined: {
          type: Boolean,
          default: true
        },
        dense: {
          type: Boolean,
          default: true
        }
      }
     }
    </script>
    

    Then you have to import MyInput or declare globally and use it instead of QInput. This way you have the default behavior you want, you keep all QInput features and you can even remove outlined or dense manually.



  • @jraez Thank you! I didn’t know about extend. That’s exactly what I wanted.



  • @jraez that’s amazing. Setting the same property values( like dense outlined ect) for certain q-components is something you do over and over again in every quasar project.

    Then why is everybody so focused on a composition solution for this problem (composition has it’s uses but will ‘cripple’ the ‘extended’ component, explained in the posts above) and something simple as extending is never mentioned ( Quasar docs)? Are there some untold cave pits with extending vue/quasar components this way? Just really curious …



  • @ajenkins you’re welcome.

    @dobbel Well, it’s only my guess but it’s related to people’s knowledge of OOP, not really quasar or VueJS. After many years of programming, whatever the language, framework, or such, you’ll always land on coding fundamentals 🙂 Maybe there’s a downside using inheritance but I’d never face it (in a JS/VueJS/Quasar context).



  • Coz both have their uses, in wrapping you can fiddle with the template, which you cant using extend, unless you want to break it. In this case extend is appropriate since you are just changing the props.



  • For extending my own component template I use pug. I can create the template ‘template’ in parent with block and override what I need in children. The downside is to use 2 files one for Vue component, one for pug template, and include manually the pug into the component each time.



  • @jraez That sounds like a very flexible solution! Would you want to share an example of extending a quasar component with ‘extend’ combined with overriding a part of the parent template with pug?



  • In my case, I use it to create custom QDdialog and inject them through the Dialog plugin. The idea is to have a standard design with an icon + title, some action buttons, and the content.

    I changed the ProductDialog to something out of my own scope because it’ll be too complex for an example.

    The base dialog

    <script>
    export default {
      props: ['name'],
      data: () => ({
        config: {
          title: '',
          position: 'standard',
          translateTitle: true,
          cssClass: 'custom-dialog',
          icon: 'fas fa-exclamation-triangle'
        }
      }),
      methods: {
        // following method is REQUIRED
        // (don't change its name --> "show")
        show () {
          this.$refs.dialog.show()
        },
    
        // following method is REQUIRED
        // (don't change its name --> "hide")
        hide () {
          this.$refs.dialog.hide()
        },
    
        onDialogHide () {
          // required to be emitted
          // when QDialog emits "hide" event
          this.$emit('hide')
        },
    
        onOKClick () {
          // on OK, it is REQUIRED to
          // emit "ok" event (with optional payload)
          // before hiding the QDialog
          this.$emit('ok')
          // or with payload: this.$emit('ok', { ... })
    
          // then hiding dialog
          this.hide()
        },
    
        onCancelClick () {
          // we just need to hide dialog
          this.hide()
        }
      }
    }
    </script>
    <style>
    .custom-dialog {
      width: 600px;
    }
    </style>
    

    The dialog template:

    q-dialog(ref="dialog" @hide="onDialogHide" :persistent="true" :position="config.position")
      q-card(:class="config.cssClass")
        block wrapper
          block title
            q-card-section.row.items-center.text-h4
              q-icon(:name="config.icon").q-mr-md
              .text-capitalize {{ config.translateTitle ? $t(config.title) : config.title}}
              q-space
              q-icon(@click="onCancelClick" name="fas fa-times").cursor-pointer.q-ml-md
          block separator
            q-separator(inset)
          q-card-section(:horizontal="config.horizontal")
            block content
        q-card-actions.justify-end
          block actions
            q-btn(color="primary" flat :label="$t('application.actions.cancel')" @click="onCancelClick")
            q-btn(color="primary" :text-color="$theme.colors.primaryText" :label="$t('application.actions.save')" @click="onOKClick")
    

    The my dialog implement:

      <template lang="pug">
      extends ../../UI/Dialog/DialogBase
      block content
        q-card-section.col-4
          .text-body2 {{ $t('application.product.list') }}
          q-scroll-area(style="height: 700px;")
            q-list.q-mt-md
              q-item(v-for="product in products" :key="product.id" dense)
                q-item-section(side)
                  q-btn(flat round icon="fas fa-times" @click="remove(product)")
                q-item-section(side)
                  q-btn(flat round icon="fas fa-edit" @click="edit(product)")
        q-separator(vertical)
        q-card-section.col
          .text-body2.q-mb-sm  {{ $t('application.product.info') }}
          .row.q-gutter-none 
             q-editor(v-model="product.description")
      block actions
        q-btn(color="primary" flat :label="$t('application.actions.reset')" @click="reset")
        q-btn(color="primary" :text-color="$theme.colors.primaryText" :label="$t('application.actions.save')" @click="save")
    </template>
    <script>
    import DialogBase from '../../UI/Dialog/DialogBase'
    export default {
      name: 'ProductManagerDialog',
      extends: DialogBase,
      data: () => ({
        config: {
          title: 'application.product.manager.title',
          cssClass: 'project-product-manager',
          icon: 'fas fa-calendar-alt',
          horizontal: true
        },
        products: []
        product: null
      }),
      methods: {
        edit (product) {
          this.product = product
        },
        async remove (product) {
           // remove product from products and save to the backend
        }
      },
      async mounted () {
        this.products = await ProductService.list()
      }
    }
    </script>
    <style>
      .project-product-manager {
        min-width: 1100px;
      }
    </style>
    


  • @jraez Thanks for the sample code. Do you like to use Pug to code (Quasar templates) or is it necessary because your want to override templates?



  • @dobbel I use pug because it makes the template more readable to me (less “verbose” in fact), so I’ve started using it before the need to override templates. Then it helps me with overriding and composition, so I stick to it.


Log in to reply