default style



  • Hi,
    how can I set default props?
    for example I want that all q-card components make bordered



  • Do you mean like the right QCard here? https://codepen.io/smolinari/pen/BaBmPLm?&editable=true&editors=111

    Scott



  • @matroskin you need to learn component composition https://vuejs.org/v2/guide/components.html. wrap your q-card component with bordered prop on, and use that component.
    ie.

    // MyBorderedCard.vue
    <template>
      <q-card bordered .... />
    ...
    // SomeVue.vue
      <my-bordered-card />
      <my-bordered-card />
      <my-bordered-card />
    ...
    


  • thanks, that is the way



  • I recently ran into the same issue, of wanting to impose consistent style options with multiple Quasar component types throughout my app. For example I might want q-input components to default to dense and outlined, and q-btn components to default to dense and flat. Since Quasar components use props for configuring many aspects of components, you can’t just use CSS for this.

    I tried the approach mentioned above of wrapping components, but found this to be problematic. To use QInput as an example, what I really want is a component with the same API as QInput, including the same props, events, slots and methods, but just with different defaults for some of the props.
    To achieve this by wrapping I would have to make my wrapper forward all of these things to the wrapped component. For props this is simple using v-bind="$attrs" like this:

    // MyInput
    <template>
      <q-input v-bind="$attrs" .../>
    ...
    

    However I’m not aware of an equivalently trivial method for forwarding all events, slots and methods to the wrapped component. This means when I write a wrapper, I have to choose between either doing a non-trivial amount of work, or not replicating the API of the wrapped component. For simple components like QInput this might still not be a big deal, but it adds up when I start wanting to do the same thing for multiple components, and especially for complex components like QTable.

    The approach I came up with instead was to create a defaults.js file with an object for each component I want to customize, like this:

    // defaults.js
    
    // default prop values for QInput
    export const inputDefaults = {
      outlined: true,
      dense: true
    }
    
    // default prop values for QBtn
    export const btnDefaults = {
      flat: true
    }
    
    // .. Etc for any other components you want to customize
    

    To make it easy to access these defaults I make a boot file like this:

    import * as defaults from 'src/defaults'
    
    export default async ({ Vue }) => {
      Vue.prototype.$defs = defaults
    }
    

    Now in templates I can make a component use the defaults simply by adding a v-bind="$defs.componentTypeDefaults" attribute, like:

    <template>
      <div>
        <q-input 
          v-bind="$defs.inputDefaults"
          label="The Label"
          ... other props
        />
    
        <q-btn
          v-bind="$defs.btnDefaults"
          ... other props
        />
      </div>
    </template>
    

    It’s not quite as convenient to use as custom wrapper components, but on the other hand it’s much easier to customize a large number of components, and I’m not losing access to any of the functionality of the Quasar components. I still have access to all the events, slots and methods of the Quasar components without having to explicitly forward them in my wrappers.

    What I wish for is that Quasar would add this functionality so that component prop defaults could be configured in quasar.conf.js. I’d like for there to be a componentDefaults section in quasar.conf.js that allows configuring component defaults just like I’m doing in defaults.js.



  • @ajenkins you are on right track. very basic wrapper eg

    <some-component
      ...
      v-on="$listeners"
      v-bind="$attrs"
    >
      <template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
        <slot :name="slot" v-bind="scope"/>
      </template>
    </some-component>
    


  • @metalsadman Thanks for that. That’s closer to a transparent wrapper than I realized was easily possible. That still leaves out methods, but it’s much less common for me to want to call methods on components anyway, so that’s not a big deal.

    I still prefer my solution of just using the Quasar components directly rather than having to write wrappers for everything though. It seems to me that it would be the rule rather than the exception to want to override the default style props for form components in a consistent way throughout an application. Expecting every app developer to write their own wrapper components to solve this problem seems like a clunky solution, especially since there’s no standardized way to do that, so every developer is going to end up doing it slightly differently.



  • Just to help anyone finding this thread who wants the solution, I just tried @metalsadman’s solution for writing a QInput wrapper. This worked fine for a wrapper that overrides the outlined and dense props but otherwise has the same API as QInput.

    <template>
      <q-input
        v-bind="attrs"
        v-on="$listeners">
        <template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
          <slot :name="slot" v-bind="scope"/>
        </template>
      </q-input>
    </template>
    
    <script>
    export default {
      name: 'MyInput',
      computed: {
        attrs () {
          return {
            // Put any prop value overrides here, before the ...this.$attrs line
            outlined: true,
            dense: true,
            ...this.$attrs
          }
        }
      }
    }
    </script>
    


  • It was just pointed out to me in another thread that you can actually extend Vue components instead of wrapping them, which I think solves most of my concerns with wrapping. https://forum.quasar-framework.org/post/21630

    Basically, instead of writing a wrapper like above, use extends like this:

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


  • This Stackoverflow guy says ‘extend’ is a bad idea in Vue ( the last post):

    I’d avoid the “extends” feature of Vue, simply because it is a poorly named method of Vue. It doesn’t really extend anything, not in the case of inheritance. What it does is exactly what the mixin does, it merges the two components together. It has nothing to do with the template code, which isn’t extensible either. The “extend” Vue method should have been called “merge”.

    https://stackoverflow.com/questions/35964116

    Any thoughts?



  • I don’t know how VueJS run under the hood, but what you achieve with extends is more or less the same as adding a mixin to a component.

    The only difference I see is the fact that mixins are abstract components, you never use them as-it. It’s just a fragment. In inheritance, you may found some cases where you instantiate the parent. In the @ajenkins example, you can have QInput and MyInput together on the same app. So it depends on your app, or what you need to achieve, etc. both ways have pros and cons.



  • @dobbel I’m not sure I understand the Stackoverflow guy’s objections. I just tested out extends and it works. It may be true that you can’t “extend” the template code, so it doesn’t exactly correspond to subclassing in an OO language, which seems to be the Stackoverlow guy’s objection, but that seems like a fairly pedantic objection IMO. Extending solves the problem in this case. I just tested this and it works as expected:

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

    I can use my-input just like q-input, and it supports the same props, events, slots, and methods. The only difference is outlined and dense default to true.



  • One slight disadvantage I found with using extends vs the solution I described initially above is that if you’re using an IDE, you lose autocomplete for component props, at least IntelliJ family editors. For example if I do

    <q-input
       err<tab>
    

    The editor will suggest the error-message, error and no-error-icon props, but when using my-input, I don’t get these suggestions. This could work in principle, but the Vue plugin for IntelliJ apparently doesn’t understand extends.



  • I’d use whichever is appropriate, extend or wrapping.



  • This is a valid use case for which a more elegant option from the framework would be helpful.

    For our requirement, we wanted all controls to be flat and bordered, so we went ahead with the following solution:

    // commattribs.js
    export default {
       flat: true,
       bordered: true
    }
    

    then customized individual components that we use to make use of the above attributes:

    <template>
      <q-card
        v-bind="attrs"
        v-on="$listeners"
      >
        <template
          v-for="(_, slot) of $scopedSlots"
          v-slot:[slot]="scope"
        >
          <slot
            :name="slot"
            v-bind="scope"
          />
        </template>
      </q-card>
    </template>
    
    <script>
    import cattribs from './comm-attribs'
    export default {
      name: 'ACard',
      computed: {
        attrs () {
          return {
            ...cattribs,
            ...this.$attrs
          }
        }
      }
    }
    </script>
    

    so if we needed to customize all components with some other attribute, we change them up in commattribs.js



  • But, what if you wanted only some of your components to be a certain way?

    Scott


Log in to reply