Flexible layout with hide/show of panel - vue.js

We want to set up a UI like this, where A cards are shown by default, and if needed B shows on the right side:
Before:
┌────────────────────┐
│┌───┐┌───┐┌───┐┌───┐│
││A ││A ││A ││A ││
│└───┘└───┘└───┘└───┘│
└────────────────────┘
After:
┌────────────────┐┌──┐
│┌──┐┌──┐┌──┐┌──┐││ │
││A ││A ││A ││A │││B │
│└──┘└──┘└──┘└──┘││ │
└────────────────┘└──┘
So note in the beginning, the container for the A cards take up the full width, then when B shows, the A cards shrink a bit in width, and B takes up about 1/6 of the width.
This is how v-container, v-layout, v-flex etc. are set up right now:
<v-container fluid>
<v-layout>
<v-flex>
<v-card>
<v-container fluid grid-list-xl>
<v-layout row wrap>
<v-flex xs6 sm4 md3 lg2 xl1 v-for="record in records" :key="record.id">
<v-card flat tile>
<!-- "A" code -->
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
<v-flex v-show="showPanel">
<v-card>
<!-- "B" code -->
</v-card>
</v-flex>
</v-layout>
</v-container>
The way the code runs now is that it shows "Before" okay, but for "After" it shows just a thin column for B like 1/12 of the width of the screen, and the As squeeze in just a bit. Anything I do to change the width of B just screws up the layout completely, like takes 50% of the width, or has As showing in three or fewer columns instead of four.
I think my structure might be fundamentally wrong, but I'm having a hard time wrapping my head around how v-container, v-layout, and v-flex are supposed to work, and the documentation is not helpful.

Confirmed it was the fluid in the line <v-container fluid grid-list-xl> that was causing troubles. Once I removed it, layout was as expected. The problem couldn't be duplicated in CodePen likely because the template was rendered in another environment with its own layout rules, so whatever conflict there was showed up only in that context.

Related

What am I missing about the vuetify grid-system?

I am having issues with getting the vuetify grid-system to work properly. I have installed vuetify, added it as a plugin to my app and imported it into my file. When I use its <v-container> or <v-row>- tags, a grid shows up, so that should all be working. What is not working however, is the "linking" of columns to rows. I believe that I have the code set up correctly, as it is basically a copy of the tutorial-code on the vuetify website. When I run the file, the columns are children of the rows, as expected, but are shown below eachother, not next to eachother. I have two rows which I do expect to be shown below eachother, but I want the columns of each row to be shown horizontally to eachother, inside the row.
Here is how I want it to look like, it is a screenshot of an example from the vuetify docs:
What I end up with however, looks like this:
I will also provide my code here:
<template>
<v-container class="grey lighten-5">
<v-row
v-for="n in 2"
:key="n"
:class="n === 1 ? 'mb-6' : ''"
no-gutters>
<v-col
v-for="k in n + 1"
:key="k">
<v-card
class="pa-2"
outlined
tile>
{{ k }} of {{ n + 1 }}
</v-card>
</v-col>
</v-row>
</v-container>
</template>
I really hope someone can help me, as I am very stuck and feel like I have tried everything.
I appreciate every answer! :)
EDIT: I just saw that the console tells me that "[Vue warn]: Unknown custom element: <v-col> - did you register the component correctly? For recursive components, make sure to provide the "name" option.". That seems strage to me, as there are indeed rows and columns shown on the rendered site. Does anyone have an idea on how to tackle this issue?

How to cache filtered out elements to prevent re-rendering inside a v-for?

I’ve been working on a Vue.js application that manages user’s notes and allows to easily look them up by title, tags, etc. So the primary view of a logged-in user is a list view of Card components. I’m using v-lazy and group-transition components to enhance usability. Everything works fine, but…
When the set of notes becomes larger (100+ Cards), one can notice (especially on mobile devices) that filtering slows down. After some debugging I found that the filtering calculations are not the heavy part but the re-rendering of filtered components is the real problem.
Let’s say, with the following scenario:
there's a list of 150 Card components
the user scrolled down to the end of the page, so that 150 Cards are in fact rendered and one of them contains tag: tag-1
if the user clicks on tag-1, 149 Cards get filered out, the 1 result
remains Vvue debug tool shows that 149 Card components get destroyed as the computed method visibleCards contains only one element)
if the user unclicks the tag-1, all 149 Cards are re-rendered again
(Vue debug tool shows that 149 Cards are fully re-created again)
Below, there’s the meat of my Card list displaying view:
<v-row justify="center" dense class="px-sm-2">
<transition-group
name="list"
tag="div"
class="cardListTransition col-12 pa-0"
>
<v-row
:id="'columnid-' + index"
v-for="(card, index) in visibleCards"
:key="card.id + ':' + card.created"
justify="center"
class="snipSingleRow"
>
<v-col lg="10" xl="8" class="pt-0 mb-lg-4 mb-xl-4">
<v-sheet min-height="50" class="fill-height" color="transparent">
<!-- 'threshold: indicates at what percentage of the target's visibility -->
<!-- the observer's callback should be executed. -->
<v-lazy
v-model="card.active"
:options="{
threshold: 0.8,
}"
class="fill-height"
transition="list"
>
<Card :card="card" />
</v-lazy>
</v-sheet>
</v-col>
</v-row>
</transition-group>
</v-row>
where: visibleCards is a result of a computed method inside of which filtering occurs.
Is there any way - I’ve experimented with v-once or keep-alive, etc. with no help - to save / cache the filtered out components to prevent them from re-rendering once the filters are cleared?
PS.
Stripping down the list i.e. removing v-lazy or group-transition doesn’t solve the problem.
I’d appreciate any help / suggestions or further reading on how to solve my problem.

Vuetify: v-text-field for days:hours:minutes?

I was curious to know if there is any chance of formatting a v-text-field so that it not only allows hours and minutes to be chosen, but also days.
I’m currently looking at the Vuetify documentation for text-fields, which shows the following solution:
https://vuetifyjs.com/en/components/text-fields/#icon-events
<v-text-field
label="Label Text"
value="12:30:00"
type="time"
suffix="PST"
></v-text-field>
What I want to achieve is to be able to choose days:hours:minutes, instead of hours:minutes:seconds.
Is there any way to manipulate the text-field in this manner?
Thanks!
Vuetify doesn't provide one component that does both, but you could use one each of types date and time:
<v-text-field
v-model="vdate"
label="DATE"
type="date"
></v-text-field>
<v-text-field
v-model="vtime"
label="TIME"
type="time"
suffix="EST"
></v-text-field>
Here's a demo

Change column width in grid in vuetify

I have three columns under the row, each column has the cols value as 4. I want to reduce the width of the "col1", I tried to change the cols value from 4 to 3 but the column is getting smaller than I expected. So I want a cols value between 3 to 4(consider cols value to be 3.5). Is there a way I could acheive this?
<div id="app">
<v-app id="inspire">
<v-container class="grey lighten-5">
<v-row no-gutters>
<v-col cols="4">
<v-card>col1</v-card>
</v-col>
<v-col cols="4">
<v-card>col2</v-card>
</v-col>
<v-col cols="4">
<v-card>col3</v-card>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
As #Matthias indicated, the Vuetify grid is based on Bootstrap but is implemented with the flexbox grid system. Each "row" will always have 12 equally-sized "columns" whose width is based on the width of the grid container (although a v-col element can span multiple "columns", e.g. <v-col cols="3>). In addition, there is a "gutter" (empty space, like a margin) between each column, and there is a margin around the row. Therefore the width of 1 column is:
one-col-width = (container-width - ((2 * margin-width) + (11 * gutter-width))) / 12
If your v-col spans multiple columns, then the width of the column is:
col-width = (one-col-width * cols) + (gutter-width * (cols - 1))
The width of the grid container is either the calculated width of the parent element (the one that contains the grid) or the page (if it is a full-width layout).
In order to control the width of a column, your options are limited.
Option #1--Change the gutter/margin width
The default gutter width is 24px. You can change this by setting the dense property on v-row, which reduces the gutter width to 12px. You can also set the no-gutters prop on v-row which removes the gutters completely. This can make a significant difference since 11 * 24px == 264px.
Option #2--Set the fluid prop on the v-container
Setting the fluid prop on the v-container surrounding the grid will "extend the container across all viewport and device sizes." This removes horizontal constraints on your grid, essentially widening all of the columns.
Option #3--Use built-in margin/padding utilities
The auto margin helper utilities and spacing utilities are a set of classes you can add to your v-col elements to adjust their margins. For example, adding mr-4 as in <v-col class="mr-4"> will apply a 16px right margin. There are also others like mx-auto. This will adjust the effective space between columns, but the columns themselves will still take up the same amount of space.
Bottom line, you don't have complete freedom, but you can tweak your layout to accomplish some flexibility.

Use fallback content of slot only if condition is met

I would like to know if there is a way to do what I'm trying to describe below:
Let's suppose we have a component with a slot, and a fallback content has been defined.
When using this component elsewhere, I would like to have the following behavior:
<TheComponent>
<template v-slot:name="{ data }">
<fallback v-if="condition(data)"/>
</template>
</TheComponent>
I suppose the fallback tag (or similar) does not exists (at least, I didn't find it...). So I suppose I'm thinking the wrong way, but I can't find a solution to my problem.
The thing is that I can't alter the TheComponent as it is provided by an external library, and I don't want to re-create the content.
If it can help, in fact, I'm trying to hide the expand button to prevent to expand a row in a Vuetify data-table, depending on if the row has something to show or not in it's expanded part. So I would like to write something that behave like:
<v-data-table :items="items" show-expand ...>
<template v-slot:item.data-table-expand="{ item }">
<!-- Here I want the default behavior only if my item respects some condition -->
<fallback v-if="condition(item)"/>
<!-- Otherwise, I don't want to display the button that expands the row -->
</template>
</v-data-table>
Thank you in advance for your help.
After quite a lot of "googling" I don't think its possible. IMHO your best bet is to replicate the default slot content Vuetify generates and put it under your own condition (v-if="item.description" in my example):
<v-data-table :headers="headers" :items="people" show-expand>
<template v-slot:expanded-item="{ item, headers }">
<td :colspan="headers.length">{{ item.description }}</td>
</template>
<template v-slot:item.data-table-expand="{ item, isExpanded, expand }">
<v-icon
v-if="item.description"
:class="['v-data-table__expand-icon', { 'v-data-table__expand-icon--active': isExpanded }]"
#click.stop="expand(!isExpanded)"
>$expand</v-icon>
</template>
</v-data-table>
I understand this solution is brittle and can break anytime Vuetify change something but i don't think there is better solution now...
Example