How to properly move helper components into children components in Vuetify? - vue.js

I have a component with a v-card in it which looks like this:
<!-- CurrentFile.vue -->
<v-card color="primary">
<v-card-title class="secondary--text text-wrap">Flashing title</v-card-title>
<v-card-subtitle>
<span class="secondary--text text-h6">A fancy subtitle</span>
</v-card-subtitle>
<v-card-text class="mt-n3">
...
</v-card-text>
</v-card>
While refactoring, I wanted to create a new component that would encapsulate both v-card-title and v-card-subtitle helper components, since they use the same text color.
<!-- CurrentFile.vue -->
<v-card color="primary">
<my-new-header textColor="secondary--text" title="Flashing title" subtitle="A fancy subtitle"/>
<v-card-text class="mt-n3">
...
</v-card-text>
</v-card>
I went on to create this child component
<!-- MyNewHeader.vue -->
<template>
<span>
<v-card-title class="text-wrap" :class="textColor">{{ title }}</v-card-title>
<v-card-subtitle>
<span class="text-h6" :class="textColor">{{ subtitle }}</span>
</v-card-subtitle>
</span>
</template>
Issue
With the inclusion of a <span> tag, the use of v-card-title and v-card-subtitle components is almost irrelevant. It seems to me that both containers lose their properties (margins and paddings).
Of course, it's logical, since it adds an undesired level of hierarchy. Once the child component is injected, the structure ends up looking like this:
<v-card>
<span>
<v-card-title/>
<v-card-subtitle>
<span/>
</v-card-subtitle>
</span>
<v-card-text/>
</v-card>
I would very much remove that <span> tag to avoid all these problems, but there must only be one element at the template root.
Is there another way of doing what I want to do that I'm missing? Or maybe is it overkill to want that many sub-components?

There are 3 possible solutions for your issue:
Using fragments - https://github.com/privatenumber/vue-frag
Using render function instead of a component template - https://v2.vuejs.org/v2/guide/render-function.html
Using CSS display: contents to effectively replace the wrapper with its content - https://developer.mozilla.org/en-US/docs/Web/CSS/display-box

Related

When to split your code into components in Nuxt?

I'm coding a project in Vue, Nuxt and vuetify as UI library.
In my index page I have to show a lot of static contents and images with slides and carousels. I have coded it and everything works fine but my index.vue file in pages directory looks messy and I feel there is too many lines of code in there.
For instance I have the following code for showing a carousel in index.vue:
<v-carousel
height="90vh"
vertical
vertical-delimiters="right"
interval="9000"
cycle
:show-arrows="false"
hide-delimiter-background
>
<v-carousel-item
v-for="(item,i) in carouselItems" :key="i"
:src="item.src"
reverse-transition="scroll-y-transition"
transition="scroll-y-transition"
>
<div
class="d-flex transition-fade-in white--text"
style="height: 100%;background: linear-gradient(rgba(0,0,0,.1), rgba(0,0,0,.9));"
>
<v-row
class="fill-height pb-9 mx-5 flex-column"
align="start"
justify="end"
>
<div class="text-h3 white--text d-block mb-2">
{{ item.title }}
</div>
<div class="text-h6 white--text mx-1 text-justify"> {{ item.description }} </div>
</v-row>
</div>
</v-carousel-item>
</v-carousel>
Now my question is should I separate this code into a component and pass the options and items as props to that component or should I leave it as it is and it's OK to have a page component with many lines of code?
It all depends on you, the way you work and how you/your team feel like about splitting into components. There is no standard as of to say something like "a component needs to be at max 300 lines" or something alike.
It also comes down to logic with your components, make them do 1 thing well rather than too much at the same time. But don't over-engineer it by having 50 lines of components that could totally be a single component neither because it will also introduce some complexity that is not needed.
Just code and split when you feel like it's doing too much or it's to big. I usually have components from 5 lines of code up to 300~400 as a maximum depending of their complexity/implementation.

Problem with placing v-app-bar content in container?

I need to place content inside v-app-bar inside container, so It goes in one line with other page content. All content inside app should have max width for each breakpoint instead of full page width. Placing all content iside container don't solve problem.
I marked with red box on screenshot where content should be.
Hey I am having the same issue. I came up with a rough work around, my question is here incase you found an answer as well.
Make vuetify app bar items align with <v-container> body content
My solution looks like so:
The colors show the nav bar width adjusted to match the body. The code looks like so:
<template>
<v-sheet color="red">
<v-container class="pa-0">
<v-app-bar
dense
flat
color="blue accent-4"
>
<v-btn icon>
<v-icon>mdi-home-outline</v-icon>
</v-btn>
<v-divider inset vertical></v-divider>
<v-btn text :key="item.id" v-for="item in quickLinks" v-text="item.text"></v-btn>
<v-spacer></v-spacer>
<v-btn text v-text="'Sign In'"></v-btn>
<v-btn text v-text="'Register'"></v-btn>
</v-app-bar>
</v-container>
</v-sheet>
</template>
For others looking to only constrain the content of the v-app-bar, I found a good example over at https://vuetifyjs.com/en/examples/wireframes/constrained/ (as Ari pointed out in the comment for the main question):
<template>
<v-app-bar app>
<v-container class="pa-0 fill-height">
<!-- [...] -->
</v-container>
</v-app-bar>
</template>
I got mine to work and also keep the navbar background extended to the edge of the screen. You can put a container inside the app-bar but it messes with the flexbox of the items so you just have to put a v-row inside for them to align properly.
<template>
<nav class="toolbar" align="center">
<v-app-bar app>
<v-container>
<v-row align="center">
<v-app-bar-title>
<!-- Title-->
</v-app-bar-title>
<div>
<!-- Left side content -->
</div>
<v-spacer />
<div>
<!-- Right side content -->
</div>
</v-row>
</v-container>
</v-app-bar>
</nav>
</template>
<style scoped>
.v-container {
max-width: 60% !important;
}
</style>

vue-draggable not able to disable

I wrote a component that is shared throughout my application, in some places I need the dragging/sorting and some do not want it there. I pass a prop to my component called disableDraggable and based on that it should disable, unfortunately it does not do anything, how can I disable the draggable?
I should note I tried both the options object syntax and also a simple :disable , here is the relevant code:
<draggable v-model="copyOfQuestions" #end="$emit('updateQuestionsOrder', copyOfQuestions)" :options="{disable : disableDraggable}">
// or :disable="disableDraggable"
<v-card flat class="list_outer_block" v-for="q in questions" :key="q.question_id">
<v-card-text class="pa-0">
<v-layout justify-start align-center>
<v-flex initial-xs px-2 py-3 class="handle minwdth-0" :title="$t('general.drag_for_reorder')">
<v-icon class="text--secondary text--lighten-3">$vuetify.icons.drag_indicator</v-icon>
</v-flex>
....
props: ['questions', 'disableDraggable'],
How can I disable the draggable functionality?
I should note that vue-draggable (what I am using) supposedly has the same api as SortableJs
It should be :disabled and NOT :disable.
<draggable v-model="copyOfQuestions" #end="$emit('updateQuestionsOrder', copyOfQuestions)" :options="{disabled : disableDraggable}">
Reference:
https://github.com/SortableJS/Vue.Draggable/blob/17bdd4b8b2ab4f4df45dd76edf1afec864ec0936/example/debug-components/slot-example.vue

Vuetify Unable to locate target when using attach on v-autocomplete

I want to use autocomplete from Vuetify and I am facing issues there because on my website I have one of the outer divs position: relative the dropdown part of the autocompelete, which is position: absolute, is attaching itself not to the bottom of the input but in random place.
Autocomplete has a prop attach which Specifies which DOM element that this component should detach to. Use either a CSS selector string or an object reference to the element. so I thought I use that and set it to class of my input.
And this works but it causes warning in the console
[Vuetify] Unable to locate target v-autocomplete
found in
---> <VMenu>
<VAutocomplete>
<VCard>
<VApp>
<Root>
Here the link where I reproduced the console warning.
If you are not using v-app component in App.vue, make sure to add data-app attribute to the div with the id app in App.vue.
The result will be like the following:
<template>
<div id="app" data-app>
.... All components, routers, views here ...
</div>
</template>
This worked for me:
<div id="app">
<v-app id="inspire">
<v-card>
<v-card-title class="headline font-weight-regular blue-grey white--text">Profile</v-card-title>
<v-card-text>
<v-subheader class="pa-0">Where do you live?</v-subheader>
<v-autocomplete
v-model="model"
:hint="!isEditing ? 'Click the icon to edit' : 'Click the icon to save'"
:items="states"
:readonly="!isEditing"
:label="`State — ${isEditing ? 'Editable' : 'Readonly'}`"
persistent-hint
prepend-icon="mdi-city"
:attach="'#attach'"
>
<template v-slot:append-outer>
<div id="attach"></div>
<v-slide-x-reverse-transition
mode="out-in"
>
<v-icon
:key="`icon-${isEditing}`"
:color="isEditing ? 'success' : 'info'"
#click="isEditing = !isEditing"
v-text="isEditing ? 'mdi-check-outline' : 'mdi-circle-edit-outline'"
></v-icon>
</v-slide-x-reverse-transition>
</template>
</v-autocomplete>
</v-card-text>
</v-card>
</v-app>
</div>

Add custom attributes to Vuetify tags

How I can add custom attributes to Vuetify tags. I want add some Schema.org attributes to my site.
I have template like this:
<template>
<v-container>
<v-layout row wrap >
<v-flex xs12>
<h1 class="content-title display-1">{{ post.title }}</h1>
</v-flex>
<v-flex xs12>
<div class="content-body" v-html="getMarkdown(post.text)"></div>
</v-flex>
</v-layout>
</v-container>
</template>
Vuetify will usually “pass on” your custom attributes, unless they collide with something that's in their API. In other words, this will work (it's not clear from your question what you tried that didn't):
<v-btn itemprop="thing" />
However, a more generic and stable solution would probably be to add that markup to a wrapping element, or to create your own component that wraps the Vuetify component. Or use <meta> tags (yes, they are allowed in the body).