One codebase, any platform!
tl;dr;#1â If you already understand the basics of composing a UI in Quasar or Vue and just want to see some of it in action with Quasarâs QBtn component, drop down to âReasons for ChangeâââSRP in components?â
tl;dr;#2âââIf you donât know about backend templating engines or donât care about inheritance of template code compared to composition, drop down to âAn example of component building with QBtnâ.
tl;dr;#3âââIf you just want to play with some code from this tutorial, check out the Sandbox demo (give the sandbox time to fire up). đ
Repo: https://github.com/quasarframework/quasar
Before we begin, this article is going to assume you have some basic knowledge about how Vue works. If not, you might get a bit confused. If you are feeling confused after reading this article, you need to learn more about Vue. And please donât be frustrated. If you donât know React or reactive UI technologies, take a step back and learn what they are about. Then come back. You wonât be disappointed!
Ok. A long lead in that was⌠letâs get going.
The whole concept of Vue is the composition (as opposed to inheritance) of components. If youâve come from a backend language, where youâve used some sort of templating engine like Smarty, Twig, Blade, erubis, hero, Velocity (and the list goes on and on), then you probably already feel at home with Vueâs <template> syntax and Single File Components. However, we need to understand this is still a different system compared to backend templating engines and their âway of thinkingâ.
Those back-end templating engines all have a top-down methodology with the capability to inherit template code. You build a page and try to think about what needs to be extracted to reduce code duplication and you try to use inheritance to extend those smaller templates, but only where their features differ slightly.
With Vue, the goal is to think of components first, i.e. the smallest reusable parts of an application, and build up from there.
From the VueJS website: âItâs common for an app to be organized into a tree of nested components.â
Also with Vue, and this point is the most important one, we are building the model inside these components. Whereas, in the world of server-side templating engines, we inject the model, which we built in some other part of the program. With server-side templating engines, weâre usually using an MVC architecture.
In Vue, the architecture is not MVC but rather MVVM architecture. We are building the model within each component, but only for that component.
If we start to make a complicated component in Vue or Quasar, our model gets complicated too. With composition, we can and should avoid that complication.
Iâm going to put something out there that might be controversial. Any Vue component template should not be verbose! If you have more than 40 to 50 lines of template code, then your template or rather your component, is doing too much. This might expand to 100 lines or so, if you are using render functions. At any rate, if youâve heard it before with MVC architecture, âthin controllers, thick modelsâ, then I say something similar goes for the MVVM and SFC architecture.
ItâsâŚ
You should have thin structures and thick models.
Theoretically, with thin structures, i.e. small templates, we donât need the inheritance of template code between components. Still, inheritance of template code between components makes for a heated topic of discussion in Vue circles. Itâs been suggested more than a couple of times to get template extension through inheritance in Vue, like in this issue. And every time a suggestion like that comes up Evan You, the creator of Vue, doesnât bite. He says an additional extension system for just the template syntax would make Vueâs current system unnecessarily harder to reason about. I agree.
Composition for the win! đ
Code reuse with Vueâs composition techniques (i.e. the slot system, mixins, component elements/objects, etc.) do seem a just bit laborious at times. And some of it, like slots, is so different to what was previously known, it catches beginners to Vue a bit off guard. I was in this boat too.
Nonetheless, if you think about what you want to get accomplished and then âbuild outâ or compose your version of components, understanding the DOM hierarchy rules (Parent / Child relationships) and keeping SRP and âthin structure/ thick modelsâ in mind, that is all you really need to achieve code reuse (which is the goal of inheritance too). The DRY principle of programming, i.e. âcode smartly once and reuse it oftenâ, can be relatively easily achieved with Vueâs composition facilities.
The aim of this tutorial is to get you thinking (more) along those lines.
Iâm repeating this again, because it is vital to understanding how to work with both Quasar and Vue. Vueâs core premise is built on_composition_, which means the action of putting things together. These âthingsâ in Vue are components. Check out the meaning of the word âcomponentâ.
Itâs Latin for âPutting togetherâ đ
This may be a no-brainer but, it is easy to start building components that arenât components. They are more like in the world of OOP, god components, doing a lot of stuff and breaking easily. It can happen quickly and why I keep mentioning SRP. Take advantage of Vueâs composition facilities and remember SRPâŚâŚalways!
As you know, Vue takes your components, your compositions and smartly puts them in a virtual DOM hierarchy, then renders that to your users and makes it all reactive. Itâs why we canât âunthinkâ hierarchies with components, because the DOM is a hierarchy. And this hierarchy doesnât matter for interactivity with the user, because when components are done right, the data manipulations, the bindings, are directly visible within the DOM, at the right place at the right time. This âreactiveâ system is what makes reactive UI libraries like Vue worldâs better than you having to find the data in the DOM to then manipulate it (ehem, like in jQuery).
Remember the other core concept of Vue and reactive programming isâŚ
âLet your data do the walking!â
đ Ok. I made that upâŚâŚ. But, itâs true! đ
There are five possible âresponsibilitiesâ or reasons why we may be touching a component to change it. Now you might be saying, um, but the âSâ in SRP is for âSingleâ. Yup, and theoretically, we can separate these 5 responsibilities into their own modules/ files for change. More importantly, a Vue component can hold them all. These reasons for change or responsibilities are also the sources as to why we would want to âcomposeâ them differently. They are:
If you are going, âum, say what?â right now. Now worries. Hopefully, youâll get an understanding of what is meant above, as we go through the tutorial.
Like with any type of creation, there needs to be a plan in place for our new button to end up as we intend it to be. You may think, âHey, itâs just a little olâ button.â Well. This tutorial will show you a custom button component in Quasar (or Vue) can be quite powerful and intricate. So, having a plan - an idea of what you want the button to do or be - is really important.
There are so many possibilities with just this one component that composing it to be something more with other Q-components or logic/ behavior, and even only using it as a stock button without having a plan, will most likely end up in a crazy, maybe even frustrating, mess.
The power of Vue lies in your easily keeping a mental model of what you want a component to do. However, that mental model also needs to be formulated before you begin âcomposingâ.
So, this is what our button should do, when it is finished (noting the top 4 âreasons for changeâ mentioned above in parenthesis). The plan itself is the 5th reason. Itâs our final objective. And yes, this example is a bit contrived, but letâs just have some fun with making new button components. đ
This is the plan for our button(s):
You must admit, that is a lot of things to consider. We are making a âsuper duper power buttonâ. đ And, maybe we will want to have button components that only do some of these things, right? Or, well, maybe not. No matter. Weâll build out the different buttons for the sake of demonstrating composition.
So, with that in mind, letâs compose 4 different buttons and our button bar.
Please note that the /components directory is our own component code and our /pages directory is where the components are consumed and demonstrated in this example.
Ok. That saidâŚ.
Although we can argue about this, adding an image to a button is a design / styling decision. Think of the places we would like to make an image âbutton-likeâ in a website. How about those avatar buttons you see so often in chats or forums, which link to a profile page, or even offer a context menu? Iâm sure your imagination is filling up with ideas, right?
Have a look at thebtn-image.vuecomponent in the components section of our sandbox(give the sandbox some time to fire up). This is a button element with an image.
Our btn-image template.
If you look into the model of the component, youâll notice weâre high-jacking some q-btn functionality. Note: this component is NOT finished!
Some familiar CSS from Quasar.
If you also look at the model (the code in the <script> tags), you can see where weâve added our props.
There are naturally a ton of other considerations we could build into this button, like linking to external pages, and there are even some bugs. If you feel adventurous, you can take the code and improve it. For this little tutorial, itâs current condition does the job.
A remark on the side. Iâm a lousy front-end developer, which this component shows. As I was testing, I found issues with my original button. And, I knew the âreasons for changeâ and they brought me right smack back to this one component. In other words, my reasons to change it (remember there are up to 5 per component) caused me to go straight to this componentâs code. This is SRP for web components in action!
Letâs build on what we just built with button #1. If there is no image, we want to show a normal Quasar button.
Have a look at the second button example, BtnOrBtnImage, in our sandbox.
The BtnOrBtnImage template code.
You will probably say, the purpose of this button is sort of nonsense, but humor me for compositionâs sake. đ And notice, weâre only adding one feature. The button swapping. Remember SRP?
Using our first Mixin
For the creation of this button and the reuse of btn-image weâve also created a mixin called btn-image-mixin . Have a look at that mixin.
The new model of our âcomposedâ image or no image button.
Weâre importing the first component we made, and the mixin for itâs properties (which includes props and a methods, but can also contain lifecycle hooks, data, basically anything defining a component). Youâll notice too that the model section is now much cleaner. In fact, this component is rather small, which is a good thing!
Also important to note, now that we have the mixin, we could refactor our btn-image component to use it. But, weâll keep it as is for now.
By structure, we mean adding something to the componentâs HTML syntax, in order to change what is going to be rendered to the user.
We simply canât avoid HTML, as itâs what the browser requires to build the DOM. In Vue, we have the <template> (or the render function) sections of the component to hold the structure. It helps "separate" the responsibility of structure from the rest of the component code. Once again, we need to remember that there are no facilities in Vue to extend template syntax.
Currently, QBtn already does a whole lot for us. We can make a button link to other parts of our app (which could be seen as adding structure and/ or behavior). We can add different icons (which could be seen as styling). We can control the text showing in the button (which could be seen as model changes), color (styling), and a whole lot more.
QBtn is basically giving us multiple possibilities to manipulate the button, according to our usage needs (and according to the data/ state we give it).
But, letâs just say for our exampleâs sake, we also want to add a tool tip on a button as a standard âaccessoryâ or feature.
For this, we want to create a new component called btn-tooltip , which stands for âButton with Tooltipâ, of course. đ
The btn-tooltip template code.
As you can see, weâve simply added the tool tip to our btn-or-btn-image component. We also have the disable feature from QTooltip in use, in order to show or hide the tooltip according to the data given, i.e. a tooltip text, which is our added prop.
Our computed properties for the tooltip.
Notice we are using computed properties, in order to control the disable prop and to control the location of the tooltip. The computed properties give our data (or the lack thereof) the power to show or hide the tooltip or put the tooltip at the top or the bottom of the button. Also notice the new mixin. Iâll leave the rest for you to investigate. đ
And lastly with the consumption of this button in the button3 page, weâve brought in the v-bind directive. This is a powerful little directive, which allows you to define all your props in your data, saving time by only writing (or automating) the props for your template once.
Or Button #3 page is using v-bind!
v-bind also allows for a easy and dynamic manipulation of prop data. Itâs a win-win directive. đ
Another feature of our button, and the last one, is going to be a visibility control. When this control is on or true, the button shows itself. When it is off, the button goes away. Think about the UX, where certain users, like admins or owners of a record, get more functionality on a record than do guests or normal users. Because they are more privileged, they get extra buttons to do more with the data. Think of how often that is needed everywhere in an application, especially when Create, Update or Delete activity is available for some users, but not others.
Out âmainâ button.
Notice weâve called the button âBtnMainâ. All the naming might be questionable throughout this tutorial and as you hopefully also know, proper naming is hard to do in programming. The intention with the name âBtnMainâ is to indicate it is the âtopâ of the button hierarchy and functionality. It means it is the most feature packed button. And theoretically, our set of new buttons could be in its own directory. The âmainâ name would lead you to that button as the top one to get all features at once (if you need them).
To allow for this control of visibility, we simply added âv-ifâ to make this feature work, along with a new prop visible. Thatâs it. But now, when we consume this button in our higher components, all we need to do is define a Boolean data property, or create a computed property or watcher, to âsignalâ the button to show or not.
Thatâs it for the features we wanted to add. If youâve also noticed, weâve done only one feature per iteration of new button. It may seem a bit repetitive, but it allows for SRP. In other words, say you only want a button with an image, then you only use that one button.
Now weâre going to bring in the other and most powerful component building tool we have at our disposal in Vue and of course, in Quasar.
The "Slot".
If youâve worked with Quasar some, youâll notice that slots are âgiven to youâ in a lot of places. I say âgiven to youâ, because the whole idea of the slot is for you to be able to âinjectâ your own template code, as youâd like it, into the componentâs slot.
Another way to look at a slot is like an API for a component, which helps us avoid that pesky want to extend template code. Yes, we must build out the slot template (which can be an HTML element too, not necessarily a template element or even a component) to make it work, and yes, that might mean extra components or HTML code, depending on what we want. But remember, we only need to build them once AND we can use our compositional methods we practiced earlier to simplify that building (or even automate it with our data! Oh yeah! đ).
Am I confusing you? Sorry. It took me some time to grasp the magnificent power, yet simplicity of Vue too. I hope this last demonstration will open your eyes (if they arenât already. đ)
For our final exercise, we are going to create a âButton Barâ. Think about how often you see where there are a set of buttons within your application. You might have a set of buttons at the top right of your application. You might have a set of buttons at the bottom of dialogs. You might have a set of buttons in the footer of a record page to do data manipulation. The list can go on and on. Weâll have a look at the use case of the usual application buttons at the top right of a community application page, like in a forum or chat. Here is our btn-bar component code.
The full btn-bar component.
As you can see, we simply have a <div>, which holds a loop to go through the button prop, which will be an array of buttons. We also have a slot for the button. Simple.
In our page to demonstrate the btn-bar component, we have our btn-main to fill that button slot. Notice we are also creating the buttons, with the inner template.
The btn-bar component and a template to fill in the slot with our btn-main component.
Amazingly simple, right? Ok. From a conceptual perspective, you might still be going âWTF?â. Slots are tricky to understand, but once you do, they are really powerful and solve a bunch of use cases. Have a good look at this usage of slots. Itâs actually a simple use case.
We can also consume the buttons singularly and as different components by using a âgeneralâ component tag and using :is to call the right button component, instead of using the btn-main component. The :is feature on the general component tag is another fantastic tool for logically selecting component types. Below is what the component could look like, if we wanted to use different button components, defined through our data.
Using component :is for dynamic components.
I hope that didnât confuse you more. If it did, try to work with slots yourself. Here is also a fiddle to help to understand them. It is a simple composition of âquestion componentsâ for a survey.
Jup. Iâm often incorrectly writing âvueâ for âviewâ these days. đ
If youâve been following closely up till now, youâve noticed that our toggling actions were only changing the data. That is the power of reactive systems / UIs.
Take a look at the data models and what we are toggling through. Each toggle changes the data model, which controls our button variations or how they are presented to the user. This is getting into the realm of meta programming, where data models form our UI experience and not necessarily the code we are writing. It is quite powerful, if done right, because it can give our users the power to make decisions on our applicationâs UI and how it works (if you are into SaaS and multi-tenancy, you know what I am getting at). Our example button with added features are somewhat a bit contrived, as mentioned earlier too. However, we were demonstrate composition.
As a last note, notice how our final btn-main component actually doesnât need much markup. This is important with Vue and Quasar and why you should avoid a lot of markup on your templates. KISS them and rememberâŚ.
Thin Structure, Thick Models! đ
There are other tools within Vue that need mentioning for compositional work.
Filters are ways to manipulate text output. They were used much more extensively in v1 of Vue, with that version having a good number of built in filters. For v2, filters were reduced a lot, simply because computed properties achieves the same thing, but with better performance. An example of a filter would be to always capitalize the first letter of a text.
How a capitalize filter would look in a component.
And this is how it would be used.
Using a filter.
Notice the â|â (pipe) character between the text variable and the capitalize filter. Thatâs how you use filters. You can also use them in v-model attributes too.
This method, in essence, is the same as a mixin. It takes the component object model and merges it into the calling component. Its name is a bit misleading. Please do not think in terms of inheritance, when using extend. I think this âmisnamingâ is also why the âextendâ method/ feature isnât at all documented in the Vue Userâs Guide. Iâve also heard Vue core devs say, just avoid using âextendâ and use mixins, simply because the mixin methodology is easier to reason about. So, just do that please. đ
I wonât get into this part of Vue much at all, because it is not meant for normal application code. The Vue docs mention this as a clear warning. Iâll just quote that warning.
provideandinjectare primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code.
âprovideâ and âinjectâ offer the plug-in author a means to share data or props among components deeper in the component hierarchy. Again, itâs mainly only for plugin usage. So, unless you are going to be writing a plug-in for Vue (or Quasar), donât worry too much about âprovideâ and âinjectâ for normal application programming.
The directive is also a code reduction tool and helper, when it comes to component composition.
In this tutorial, we were mainly concerned with the composition of âwhole componentsâ, so we didnât cover directives in any depth. Quasar solves a lot of directive concerns for you, and I mean a lot of them. Not only are there a number of awesome pre-defined directives, but there are also a lot of standard behaviours built into the components themselves. Think about all the âonâ events given to you out of the box in each component. They arenât directives per se in Quasar, but they could be.
In fact, we used one of the directives in our image button example. The âripple effectâ.
The v-ripple directive in our btn-image component.
Directives offer a means to build global effects or behaviors, which can be used on any component, like the ripple in Quasar.
So in the future, if you have a common behavior among components, which Quasar doesnât offer out of the box, you can single out that behavior and code it as a directive and just add it as an attribute to your template elements, where needed. If you want to learn more about creating directives, have a look at this video:
There you have it. With just a couple of compositional tools, you have a lot of power to âbuild upâ your UI. Again, think smallest thing to biggest âthingâ, when creating components. And remember Vue and Quasar follow the DOM hierarchy in âbuilding upâ those components. Donât start with a page, when thinking about your application. Start with the smallest elements of the page and think hard to see if even those elements can be broken down further. Build the components for them and either slot them in or compose them as they would be in the DOM hierarchy. That is all you need to do.
Oh and, donât forget.
Authored for Quasar by
Scott Molinari (aka @smolinari)
Developer, writer, teacher and student. Believes teaching to fish is better than giving a fish.
If you need more information about Quasar itself, here are a few links for your consideration:
CLI: https://github.com/quasarframework/quasar-cli
COMPONENTS: https://github.com/quasarframework/quasar
THE DOCS: https://quasar-framework.org/
DISCORD: http://chat.quasar-framework.org/
FORUM: https://forum.quasar-framework.org/
TWITTER: https://twitter.com/quasarframework
STEEM: http://steemit.com/@quasarframework