Make own component from Quasar component



  • Hello,

    Maybe my question is not really Quasar specific but I did not find any answer till now.

    I want to make my own component from Quasar components. For example I have this in my vue file:

    <q-select filter filter-placeholder="select" :options="[{label: 'a1', value: 'a1'},{label: 'a2', value: 'a2'},{label: 'a3', value: 'a3'}]" v-model="filtera"/>
    <q-select filter filter-placeholder="select" :options="[{label: 'b1', value: 'b1'},{label: 'b2', value: 'b2'},{label: 'b3', value: 'b3'}]" v-model="filterb"/>
    

    As you can see I use two q-select component with different arrays but with the same class definitions (filter and filter-placeholder). I would like to make a new component where these two class style added and I would like to use the new component in my vue like this:

    <m-select :options="[{label: 'a1', value: 'a1'},{label: 'a2', value: 'a2'},{label: 'a3', value: 'a3'}]" v-model="filtera"/>
    <m-select :options="[{label: 'b1', value: 'b1'},{label: 'b2', value: 'b2'},{label: 'b3', value: 'b3'}]" v-model="filterb"/>
    

    So I would write only the difference.

    I tried everything: mixins and extends as well but nothing works. This is my “almost” solution:

    <template>
    <q-select filter filter-placeholder=“select”/>
    </template>

    <script>
    import {
    QSelect
    } from ‘quasar’

    export default {
    name: ‘m-select’,
    components: {
    QSelect
    },
    mixins: [QSelect]
    }
    </script>

    But this is not working because I have warnings that I need to use required props “value” and “options”.

    What is the solution? Is it possible in Vue.js?

    Thanks



  • I’m sure this is not only possible but very powerful.
    My way of doing this is quite messy so I’m joining my voice to yours, hoping that @rstoenescu will find the time to share a preferred/optimal way of extending quasar locally.
    IMHO this will also help foster more component PRs.



  • Some kind of @extend single file component would be ideal. Pretty sure Raz already does this internally, so its a matter of learning how it is done…

    The q-side-link is an extension of q-item. From the source, looks like Raz uses Vue mixins. This may be what you are looking for



  • Yeah, Quasar uses mixins quite a bit.

    Scott



  • I am in need of making a pin pad component for easy entry of user pins. Seems like it would make use of a combination of existing Quasar components.

    Maybe then some tutorial on using the mixins and/or making deriving “quasar” components from exiting ones.

    I agree if with @spectrolite about the commmunity contributing compenents. As the library of Q components grows Quasar will gain more momentum and thus be able to attract more funding. It’s a win for @rstoenescu to show teach us how to fish.



  • As far as I know, there is no way in Vue.js to really inherit from components, because Vue uses composition over inheritance. One option to write reusable components is mixins. A Mixin is basically a Vue component which is merged into your component. For example, you always want to print out foo bar in a dozen of your components. Instead of manually writing created () => { console.log('foo bar') } in each component you could define a mixin:

    export const FooBarMixin {
      created: function () {
        console.log('foo bar')
      }
    }
    

    And in each of your components you would write the following:

    import FooBarMixin from 'FooBarMixin'
    
    export default {
      // Your normal component stuff
      mixins: [FooBarMixin]
    }
    

    Now your component is merged with the mixin and prints out foo bar.

    But you can’t use mixins to extend the functionality of a whole single file component. If you use a Quasar component as a mixin, all the internal methods and data would be merged into your component, but the template would not. So you had to manually copy the template from that Quasar component into your component, which would not make it update safe.

    But how do you achieve the outcome @losika asked for?
    The solution is simple. Here you do not want to extend a Quasar component, instead, you want to wrap it, to hide some of the implementation details.
    So let’s write such a wrapping component:

    <template>
        <q-select :value="value" :options="options" @change="handleChange" filter filter-placeholder="select"/>
    </template>
    
    <script>
      import { QSelect } from 'quasar'
    
      export default {
        props: ['value', 'options'],
        methods: {
          handleChange (newVal) {
            this.$emit('input', newVal)
          }
        },
        components: {
          QSelect
        }
      }
    </script>
    

    Note that we are passing value to the QSelect component and we are listening for the change event on the QSelect. This is because v-model="foo" is just syntactic sugar for :value="foo" @input="foo = $event.target.value". Also note, that in order to pass additional props that are defined on QInput to the inner component, each of them has to be explicitly defined on the wrapping components and passed to the QInput.

    So often when you just want to use Quasar components to build another component you are using composition and not inheritance. For example @dgk in your example, you do not need to change something on existing components, but you want to build a new component based on Quasar components. So let’s say we build up your pin pad based on QBtn:

    <template>
        <div>
            <div v-for="row in 3" class="row justify-center">
                <div v-for="col in 3" class="col-auto">
                    <q-btn @click="handleClick((row-1)*3 + col)" :disabled="disabled">
                        {{ (row-1)*3 + col }}
                    </q-btn>
                </div>
            </div>
        </div>
    </template>
    
    <script>
      import { QBtn } from 'quasar'
    
      export default {
        data () {
          return {
            pin: ''
          }
        },
        methods: {
          handleClick (digit) {
            this.pin += digit
          }
        },
        components: { QBtn }
      }
    </script>
    

    Hopefully, that clarifies a bit when to use mixins and when to just build your component on top of other components. If I am missing something please let me know :slight_smile:



  • I don’t know if this bb allows it, but this last post by @a47ae should be pinned !



  • Thank you @spectrolite :slight_smile:
    If I have the time to, I can open up a new topic in the Show&Tell channel where I could post this and also elaborate on some other things like using Stylus variables.



  • @a47ae I’m sure @rstoenescu would greatly appreciate it, and most of it would probably end up in the docs at some point.
    Go for it !



  • This is still work in progress and I will update this If I have time to, but this should be a good starting point:
    http://forum.quasar-framework.org/topic/696/how-to-building-components-with-quasar


  • Admin

    @a47ae Let’s make a doc page with this! Want me to create one that you can edit?



  • @rstoenescu I am really glad that all of you find this guide helpful and would love if this could be added as an official doc page.
    But I am on vacation until next week, so I do not have time to look into it, but maybe you could already take the existing post and next week I will update it and maybe rewrite some stuff. :slight_smile:


  • Admin

    @a47ae enjoy your vacation! sure, please do ping me when you get back so we can get this in.



  • Thanks :slight_smile:
    Will write you as soon as I am back!



  • It works like a charm! Thank you very much.



  • I have to correct myself. I works when the parent component declared as single file component (like QSelect, QBtn, etc. you can see in source code). But if the parent component created in js file (like QList) then it does not work for me. What works is the following:

    import { QList } from ‘quasar’

    export default {
    name: ‘m-list’,
    mixins: [ QList ],
    props: {
    noBorder: { default: true },
    separator: { default: true }
    }
    }

    It there a better solution for that?

    And another problem is with refs. a47ae’s solution does not work on QModal. First of all you have to copy methods without that it will complain on ‘open’ mehtods:

    <template>
    <q-modal noBackdropDismiss noEscDismiss v-bind:content-css="{width: ‘600px’, height: ‘800px’}"/>
    </template>

    <script>
    import { QModal } from ‘quasar’

    export default {
    name: ‘m-modal’,
    components: { QModal },
    methods: Object.assign({}, QModal.methods)
    }
    </script>

    But after that the problem is with $refs.content. The error is “Cannot set property ‘scrollTop’ of undefined”. You can see that in QModal’s setTimeout function there is a reference to this.$refs.content what returns undefined. Is there any way to copy refs?


  • Admin

    @losika use a mixin instead of declaring QModal as component in your own wrapper.


Log in to reply
 

Looks like your connection to Quasar Framework was lost, please wait while we try to reconnect.