Vuetify Navigation Drawer Drag To Resize - vue.js

So I have gotten it to where the the 'drag to resize' works - it just feels a little laggy... does anyone know why this may be, and how to fix it?
I have tried forcing a refresh using vm.$forceUpdate() but that did not seem to do anything..
The CodePen can be found here.
EDIT: Added demo code working solution to this question/post. This way if something happens to the CodePen, we still have working demo code.
new Vue({
el: "#app",
data: () => {
return {
navigation: {
shown: false,
width: 200,
borderSize: 3
}
};
},
computed: {
direction() {
return this.navigation.shown === false ? "Open" : "Closed";
}
},
methods: {
setBorderWidth() {
let i = this.$refs.drawer.$el.querySelector(
".v-navigation-drawer__border"
);
i.style.width = this.navigation.borderSize + "px";
i.style.cursor = "ew-resize";
i.style.backgroundColor = "red";
},
setEvents() {
const minSize = this.navigation.borderSize;
const el = this.$refs.drawer.$el;
const drawerBorder = el.querySelector(".v-navigation-drawer__border");
const vm = this;
const direction = el.classList.contains("v-navigation-drawer--right") ?
"right" :
"left";
function resize(e) {
document.body.style.cursor = "ew-resize";
let f =
direction === "right" ?
document.body.scrollWidth - e.clientX :
e.clientX;
el.style.width = f + "px";
}
drawerBorder.addEventListener(
"mousedown",
(e) => {
if (e.offsetX < minSize) {
el.style.transition = "initial";
document.addEventListener("mousemove", resize, false);
}
},
false
);
document.addEventListener(
"mouseup",
() => {
el.style.transition = "";
this.navigation.width = el.style.width;
document.body.style.cursor = "";
document.removeEventListener("mousemove", resize, false);
},
false
);
}
},
mounted() {
this.setBorderWidth();
this.setEvents();
}
});
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" />
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.6/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<v-navigation-drawer ref="drawer" app right hide-overlay :width="navigation.width" v-model="navigation.shown">
<v-toolbar color="primary">
<v-toolbar-title class="headline text-uppercase">
<span>t a</span><span class="font-weight-light"> b s </span>
</v-toolbar-title>
</v-toolbar>
<v-tabs>
<v-tab v-for="n in 3" :key="n">
Item {{ n }}
</v-tab>
<v-tab-item v-for="n in 3" :key="n">
<v-card flat>
<v-card-text>Content for tab {{ n }} would go here</v-card-text>
</v-card>
</v-tab-item>
</v-tabs>
</v-navigation-drawer>
<v-container>
<v-layout justify-center>
<v-btn #click="navigation.shown = !navigation.shown">Toggle {{ direction }}</v-btn>
</v-layout>
<v-layout justify-center>
<p>Once the navigation drawer is opened, drag it's border to resize (highlited in red)</p>
</v-layout>
</v-container>
</v-app>
</div>

Thats because of transition effect on navigation drawer. set transition to initial at mouse down, then release that on mouse up.
at mousedown add
el.style.transition ='initial';
at mouseup add
el.style.transition ='';
Codepen : https://codepen.io/dagalti/pen/ywRNYx

Related

Vue/Vuetify - breakpoints based on component size

As far as I can see, Vuetify allows you to define breakpoints based on viewport size. Is it also possible to define breakpoints based on the size of the component? E.g.
when several components are shown on an overview page, it would use a more "compact" layout to be able to show the components side by side on large screens.
when only one component is shown it could take up more space (on large screens).
the "compact" layout could also be used when only one component is shown on a small screen.
on small screens, the overview page could show several components vertically rather than side by side.
Or can you recommend a better approach to this?
As far as I know, there is no such feature in Vuetify. However, you can always hide v-col containing the component and other columns will take up the freed-up space.
For example:
https://codepen.io/olegnaumov/pen/rNpzvEX
Since you cannot rely on global breakpoints (at least for changes based on the number of components), you can pass your component a layout prop that dictates the internal layout of the component.
Widget component
<template>
<div>
<div>Widget</div>
<v-row :class="{ 'flex-column': layout === 'vertical' }">
<v-col> Column 1 </v-col>
<v-col> Column 2 </v-col>
<v-col v-if="layout !== 'compact'"> Column 3 </v-col>
<v-col v-if="layout !== 'compact'"> Column 4 </v-col>
</v-row>
</div>
</template>
<script>
export default {
name: "Widget",
props: {
layout: {
type: String,
default: "normal", // Options: ['normal', 'vertical', 'compact']
},
},
};
</script>
Then, you can make use of two directives provided by Vuetify to compute the layout of the child components and parent container: v-mutate to watch for changes in the number of children (using the child modifier) and v-resize to watch for resize of the app.
Parent component
<template>
<v-app v-resize="onResize">
<v-container fluid>
<v-row
ref="widget_container"
v-mutate.child="onMutate"
:class="{ 'flex-column': layout_container === 'vertical' }"
>
<template v-for="n in show">
<v-col :key="n">
<widget :layout="layout_widget"></widget>
</v-col>
</template>
</v-row>
</v-container>
</v-app>
</template>
<script>
import Widget from "/src/components/widget.vue";
export default {
name: "App",
components: {
Widget,
},
data() {
return {
show: 2, // Any other method to change the number of widgets shown
// See the codesanbox for one of them
container_width: null,
widget_width: null,
// Tweak below values accordingly
container_breackpoints: {
400: "vertical",
900: "normal",
},
widget_breackpoints: {
200: "compact",
500: "normal",
},
};
},
computed: {
layout_container() {
let breackpoint = Object.keys(this.container_breackpoints)
.sort((a, b) => b - a)
.find((k) => this.container_width > k);
return this.container_breackpoints[breackpoint] || "normal";
},
layout_widget() {
if (this.layout_container === "vertical") return "vertical";
let breackpoint = Object.keys(this.widget_breackpoints)
.sort((a, b) => b - a)
.find((k) => this.widget_width > k);
return this.widget_breackpoints[breackpoint] || "normal";
},
},
methods: {
setLayout() {
this.container_width = this.$refs.widget_container.clientWidth;
this.widget_width =
this.container_width / this.$refs.widget_container.children.length;
},
onMutate() {
this.setLayout();
},
onResize() {
this.setLayout();
},
},
};
</script>
Note, this is not a production-ready solution, but rather a proof of concept showing the use of v-mutate and v-resize for this purpose.
You can see this Codesanbox for demonstration.
You can write your own, using useResizeObserver and applying size classes on the element, which can in turn be used to modify the CSS rules to itself and its descendants:
Example:
const { createApp, ref } = Vue;
const { createVuetify } = Vuetify;
const vuetify = createVuetify();
const { useResizeObserver } = VueUse;
const app = createApp({
setup() {
const el = ref(null)
const size = ref('')
useResizeObserver(el, (entries) => {
const { width } = entries[0].contentRect;
size.value =
width < 600
? "xs"
: width < 960
? "sm"
: width < 1264
? "md"
: width < 1904
? "lg"
: "xl";
});
return { el, size };
},
});
app.use(vuetify).mount("#app");
.xs span {
color: orange;
}
.sm span {
color: red;
}
.md span {
color: blue;
}
.lg span {
color: green;
}
<script src="https://cdn.jsdelivr.net/npm/vue#3.2.45/dist/vue.global.prod.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#3.1.1/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#3.1.1/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/#vueuse/shared"></script>
<script src="https://unpkg.com/#vueuse/core"></script>
<div id="app">
<v-app>
<v-main class="h-100">
<v-row no-gutters class="h-100" style="background: #f5f5f5">
<v-col
class="h-100"
cols="12"
sm="6"
lg="9"
>
<v-card class="h-100 pa-2" :class="size" ref="el">
v-card: {{ size }} <br>
window:
<span class="d-inline-block d-sm-none">xs</span>
<span class="d-none d-sm-inline-block d-md-none">sm</span>
<span class="d-none d-md-inline-block d-lg-none">md</span>
<span class="d-none d-lg-inline-block d-xl-none">lg</span>
<span class="d-none d-xl-inline-block">xl</span>
</v-card>
</v-col>
</v-row>
</v-main>
</v-app>
</div>

Can't add Object to an Array

I'm having an empty array for my breadcrumb and wanna fill it with every step i take in my category tree.
But as i said in Title, it won't add or to be more precise, it add but won't show!!
it wont show on my template! it wont show on console! only when i console.log("bread", Array.from(this.breadcrumbs)) it shows in console.
how can i fill my this.breadcrumbs with the category obj that i send trough an event!!
my category structure is like this: {id: 1, title: "Cat1"}
here is the code of page:
my main problem is that i'm checking if the breadcrumbs has length show it, but since i get empty arr even after push, my breadcrumb section just show the all.
Update:
i removed the if statements in my template, breadcrumb section and i when i click a cat it's title will be shown on breadcrumb but length and array still empty even as trying to display on template!! ( {{breadcrumb}} and {{breadcrumb.length}} are []and0` ).
BTW i'm on Nuxtjs 2.13
<template>
<div class="ma-4">
<!-- Breadcrumb -->
<div class="d-flex">
<template v-if="breadcrumbs.length">
<span role="button" #click.prevent="getAll()">{{lang.all}} > </span>
<span role="button" v-for="(br, index) in newBreadCrumb" :key="br.id" #click.prevent="goBread(br, index)">{{br.title}} > </span>
<span>{{breadcrumbs[breadcrumbs.length - 1]}}</span>
</template>
<template v-else>
<span>{{lang.all}}</span>
</template>
</div>
<!--btn add-->
<addcatbtn v-if="showBtn" #clicked="addNewCat()" />
<!--cards-->
<categorylistcard v-for="(category, index) in cmsCat"
:key="category.id"
:cat="category"
:index="index"
#addnewsub="addNewCat()"
/>
<!-- dialog -->
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline" v-if="editId">{{lang.edit}} </span>
<span class="headline" v-else>{{lang.addcat}} </span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field :label="lang.title" outlined v-model="title"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn class="text_main_color theme__cta__color px-5" #click="closeDialog()">{{lang.close}}</v-btn>
<v-btn class="text_main_color theme__btn__s px-5" #click="insertData()">{{lang.save}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<style>
</style>
<script>
import addcatbtn from '~/components/global/cms/addcatbtn'
const categorylistcard = ()=>import('~/components/' + process.env.SITE_DIRECTION + '/cms/categorylistcard')
export default {
layout:'cms',
components:{
'categorylistcard': categorylistcard,
'addcatbtn': addcatbtn
},
data(){
return{
dialog: false,
editId: 0,
newId: 0,
title: null,
catList: [],
editIndex: 0,
parentId: 0,
editSubCat: false,
showBtn: true,
onSubCat: false,
breadcrumbs: []
}
},
methods:{
addNewCat(){
this.dialog = true
this.title = null
},
closeDialog(){
this.dialog = false
this.title = null
this.editId = 0
this.editIndex = 0
},
getAll(){
this.$fetch()
this.showBtn = true
this.onSubCat = false
this.breadcrumbs = []
},
goBread(cat, index){
this.breadcrumbs.lenght = index
$nuxt.$emit('gobread',cat)
},
async insertData(){
if(this.notEmpty(this.title)){
if(this.editId){
let response = await this.axiosGet(`category/update/${this.editId}/${this.title}`)
if(this.resOk(response.status)){
const data = {index: this.editIndex , title: this.title}
if(this.editSubCat){
this.updateCmsSubCat(data)
}else{
this.updateCmsCat(data)
}
$nuxt.$emit('editedcat', {id: this.editId, title: this.title})
this.closeDialog()
}
}else{
if(this.onSubCat){
let response = await this.axiosGet(`category/insertsub/${this.newId}/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
console.log('insertsub')
const data = {id: this.newId*2 , title: this.title}
this.addCmsSubCat(data)
this.closeDialog()
}
}else{
let response = await this.axiosGet(`category/insert/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
const data = {id: 34 , title: this.title}
this.addCmsCat(data)
this.closeDialog()
}
}
}
}
}
},
async fetch(){
let response = await this.axiosGet(`categories/admin/0/1`)
if(this.notEmpty(response.data)){
this.setCmsCat(response.data.items)
}
},
computed:{
newBreadCrumb(){
let x = this.breadcrumbs
return x.splice(this.breadcrumbs.lenght-1,1)
}
},
created(){
this.$nuxt.$on('deletecat', (index)=>{
this.removeCmsCat(index)
})
this.$nuxt.$on('editcat', (category, index)=>{
this.title = category.title
this.editId = category.id
this.editIndex = index
this.dialog = true
})
this.$nuxt.$on('setparid', (id)=>{
this.parentId = id
})
this.$nuxt.$on('editsub', ()=>{
this.editSubCat = true
})
this.$nuxt.$on('showsub', (cat)=>{
this.newId = cat.id
this.showBtn = !this.showBtn
this.onSubCat = !this.onSubCat
})
this.$nuxt.$on('addbreadcrumb', (category)=>{
this.breadcrumbs.push(category) // category:{id: 1, title: "Cat1"}
console.log('cat: ')
console.log(category) // get the obj
console.log('bread: ')
console.log(this.breadcrumbs) // get empty array
})
}
}
</script>
In your computed, you are calling splice. That mutates the object. A big no-no in vue is to mutate your state from a computed property.
Try to create a new object and return it, by calling slice first:
const copy = x.slice()
copy.splice(this.breadcrumbs.length-1,1)
return copy
Also, you have a typo lenght instead of length

How to load placeholder in cropper after upload

I'm using vue-cropper 4.1 from cropperjs with Nuxt 2.13. when my cropper load it shows a placeholder, after choosing image and uploading it, the cropper still shows the chosen image, but i wanna load the placeholder again. what should i do? i tried replace() but didn't work. here is my code:
note: there are some this in my code that u may not find the reference. they are globally added to my project via mixins. and editProduct is a state in my vuex that is added globally same as it's action.
<template>
<div class="pt-6">
<v-row class="ma-0">
<v-col cols="12" sm="12" md="5" class="pa-0" >
<v-card flat class="pb-4" max-width="450px">
<client-only>
<vue-cropper
ref="cropper"
:aspect-ratio="1/1"
:src="url"
dragMode="move"
/>
</client-only>
<v-card flat class="d-flex justify-center theme__main__color my-2">
<v-btn text icon class="mx-1 text_main_color" #click.prevent="rotate(-45)">
<v-icon>mdi-rotate-left</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" ref="flipX" #click.prevent="flipX">
<v-icon>mdi-flip-horizontal</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" #click.prevent="reset">
<v-icon>mdi-border-all-variant</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" ref="flipY" #click.prevent="flipY">
<v-icon>mdi-flip-vertical</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" #click.prevent="rotate(45)">
<v-icon>mdi-rotate-right</v-icon>
</v-btn>
<a style="display:none;" ref="flipX" href="#" #click.prevent="flipX"></a>
<a style="display:none;" ref="flipY" href="#" #click.prevent="flipY"></a>
</v-card>
<v-list container inline class="transparent text-center pa-0">
<v-list-item class="px-0">
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__w text_main_color"
:loading="isSelecting"
#click="onChooseClick"
>{{lang.chooseimage}}</v-btn>
</v-list-item>
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__s text_main_color"
#click.prevent="uploadImage"
:disabled="isUploading || !selectedFile"
>{{lang.upload}}</v-btn>
<input
ref="uploader"
class="d-none"
type="file"
accept="image/*"
#change="onFileChanged"
>
</v-list-item>
</v-list-item>
</v-list>
</v-card>
</v-col>
<v-col cols="12" sm="12" md="7" class="pa-0" v-if="storedImg">
<v-row class="ma-0">
<!-- img 1 -->
<v-col cols="12" sm="6" md="4" class="pa-1 mb-4" v-for="(image, index) in storedImg" :key="index">
<v-card flat>
<div tile class="rounded-r felx float-right text_main_color theme__btn__p pa-1" height="100%">
<div><v-icon class="text_main_color" medium>mdi-sort-variant</v-icon></div>
<div><span class="my-1 productlist__btn__sort">Hold to order</span></div>
</div>
<v-img class="theme__main__color rounded-l" max-width="141" :src="imagePathCreator(image.url)"></v-img>
<v-btn text class="theme__cta__color text_main_color mt-2" min-width="141" #click.prevent="deleteImg(index, image.id)">{{lang.delete}}</v-btn>
</v-card>
</v-col>
</v-row>
</v-col>
<!-- btn -->
<addproductbtn :section="section" />
</v-row>
</div>
</template>
<style>
</style>
<script>
import addproductbtn from '~/components/global/cms/addproductbtn'
import {ALERT_TYPE, ALERT_METHOD} from '~/plugins/constants.js'
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';
export default {
components:{
VueCropper,
'addproductbtn': addproductbtn
},
props:['err'],
data(){
return{
isSelecting: false,
selectedFile: null,
url: null,
placeholder: '/images/placeholder/place-800.png',
section: 'img',
pImages: [],
pId: null,
cropData: null,
isUploading: false
}
},
computed:{
storedImg(){
return this.editProduct.images
}
},
methods:{
// ***** Cropper Methods ***** \\
flipX() {
const dom = this.$refs.flipX;
let scale = dom.getAttribute('data-scale');
scale = scale ? -scale : -1;
this.$refs.cropper.scaleX(scale);
dom.setAttribute('data-scale', scale);
},
flipY() {
const dom = this.$refs.flipY;
let scale = dom.getAttribute('data-scale');
scale = scale ? -scale : -1;
this.$refs.cropper.scaleY(scale);
dom.setAttribute('data-scale', scale);
},
reset() {
this.$refs.cropper.reset();
},
rotate(deg) {
this.$refs.cropper.rotate(deg);
},
getData(){
this.cropData = this.$refs.cropper.getData(true);
},
// ***** Component Methods ***** \\
onChooseClick(){
this.isSelecting = true
window.addEventListener('focus', () => {
this.isSelecting = false
}, { once: true })
this.$refs.uploader.click()
},
onFileChanged(e) {
// **** CROPPER CHOOSE FILE **** //
this.selectedFile = e.target.files[0];
if (this.selectedFile.type.indexOf('image/') === -1) {
this.noty(ALERT_TYPE[0],lang.errimagefile);
return;
}
if (typeof FileReader === 'function') {
const reader = new FileReader();
reader.onload = (event) => {
this.url = event.target.result;
// rebuild cropperjs with the updated source
this.$refs.cropper.replace(event.target.result);
};
reader.readAsDataURL(this.selectedFile);
} else {
this.noty(ALERT_TYPE[0],lang.something_wrong);
}
},
async uploadImage(){
this.getData()
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.pId) &&
this.notEmpty(this.cropData)){
this.isUploading = true
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.pId)
data.append('w', this.cropData.width)
data.append('h', this.cropData.height)
data.append('x', this.cropData.x)
data.append('y', this.cropData.y)
data.append('r', this.cropData.rotate)
data.append('sx', this.cropData.scaleX)
data.append('sy', this.cropData.scaleY)
let response = await this.axiosPost('product/createproimg', data)
if(this.resOk(response.status)){
const newImages = {"id": response.data.id,"url": response.data.url}
this.setEditProductImg(newImages)
this.selectedFile = null
}
}
this.isUploading = false
},
},
created(){
this.url = this.placeholder
}
}
</script>
After searching and reading the document on Cropperjs i finally found the solution.
after image is uploaded i used distroy() and then replace() like this:
async uploadImage(){
this.getData()
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.editProduct.pId) && this.notEmpty(this.cropData)){
// this.isUploading = true
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.editProduct.pId)
data.append('w', this.cropData.width)
data.append('h', this.cropData.height)
data.append('x', this.cropData.x)
data.append('y', this.cropData.y)
data.append('r', this.cropData.rotate)
data.append('sx', this.cropData.scaleX)
data.append('sy', this.cropData.scaleY)
let response = await this.axiosPost('product/createproimg', data)
if(this.resOk(response.status)){
const newImages = {"id": response.data.id,"url": response.data.url}
this.setEditProductImg(newImages)
this.myArray.unshift(newImages)
this.originalArray.push(newImages)
this.selectedFile = null
//*********** THIS PART REPLACE CROPPER WHITH MY PLACEHOLDER IMAGE **************\\
this.$refs.cropper.destroy()
this.$refs.cropper.replace(this.placeholder)
//**** this.placeholder is the placeholder.jpg file path in my static folder ****\\
this.isUploading = false
}else{
this.isUploading = false
}
}
this.isUploading = false
},

vuetify tabs component doesnt work correctly with flex animation

I'm trying to get the v-tabs to work with my expand menu.
Basically when I click the toggle open, the right side menu will slide out, and inside this menu I want to use the tabs component from vuetify.
It doesn't seem to work, when clicking on the tabs, it's jumping all over the places.
It starts to work correctly when I resize the window manually. Any help please?
Here's the codepen
codepen.io/anon/pen/WmKQLp
You should be able to use a Navigation Drawer without any custom styling needed... (Vuetify has built in components for what you're trying to accomplish)..
Here is a 'quick and dirty' pseudo example showing how you can accomplish this:
Codepen Example can be found here. updated with resizing ability
EDIT:
If you did want to use your custom CSS, you will need to add an additional custom CSS class - this is happening because of the translate, among other Vuetify styles conflicting with your custom CSS...
As outlined here, add this class: (I highly advise against doing this)
.v-tabs__container {
transform: translateX(0px)!important;
}
HTML
<div id="app">
<v-app>
<v-navigation-drawer app right width="550" v-model="navigation.shown">
<v-toolbar color="primary">
<v-toolbar-title class="headline text-uppercase">
<span>t a</span><span class="font-weight-light"> B S </span>
</v-toolbar-title>
</v-toolbar>
<v-tabs>
<v-tab v-for="n in 3" :key="n">
Item {{ n }}
</v-tab>
<v-tab-item v-for="n in 3" :key="n">
<v-card flat>
<v-card-text>Content for tab {{ n }} would go here</v-card-text>
</v-card>
</v-tab-item>
</v-tabs>
</v-navigation-drawer>
<v-layout justify-center>
<v-btn #click="navigation.shown = !navigation.shown">Toggle {{ direction }}</v-btn>
</v-layout>
</v-app>
</div>
JS/Vue
new Vue({
el: "#app",
data: () => {
return {
navigation: {
shown: false,
}
};
},
computed: {
direction() {
return this.navigation.shown === false ? "Open" : "Closed"
}
},
});
EDIT: (with resizing ability)
HTML:
<div id="app">
<v-app>
<v-navigation-drawer
ref="drawer"
app
right
:width="navigation.width"
v-model="navigation.shown"
>
<v-toolbar color="primary">
<v-toolbar-title class="headline text-uppercase">
<span>t a</span><span class="font-weight-light"> b s </span>
</v-toolbar-title>
</v-toolbar>
<v-tabs>
<v-tab v-for="n in 3" :key="n">
Item {{ n }}
</v-tab>
<v-tab-item v-for="n in 3" :key="n">
<v-card flat>
<v-card-text>Content for tab {{ n }} would go here</v-card-text>
</v-card>
</v-tab-item>
</v-tabs>
</v-navigation-drawer>
<v-layout justify-center>
<v-btn #click="navigation.shown = !navigation.shown">Toggle {{ direction }}</v-btn>
</v-layout>
</v-app>
</div>
JS/Vue:
new Vue({
el: "#app",
data: () => {
return {
navigation: {
shown: false,
width: 550,
borderSize: 3
}
};
},
computed: {
direction() {
return this.navigation.shown === false ? "Open" : "Closed";
}
},
methods: {
setBorderWidth() {
let i = this.$refs.drawer.$el.querySelector(
".v-navigation-drawer__border"
);
i.style.width = this.navigation.borderSize + "px";
i.style.cursor = "ew-resize";
},
setEvents() {
const minSize = this.navigation.borderSize;
const el = this.$refs.drawer.$el;
const drawerBorder = el.querySelector(".v-navigation-drawer__border");
const vm = this;
const direction = el.classList.contains("v-navigation-drawer--right")
? "right"
: "left";
function resize(e) {
document.body.style.cursor = "ew-resize";
let f = direction === "right"
? document.body.scrollWidth - e.clientX
: e.clientX;
el.style.width = parseInt(f) + "px";
}
drawerBorder.addEventListener(
"mousedown",
function(e) {
if (e.offsetX < minSize) {
el.style.transition = "initial";
document.addEventListener("mousemove", resize, false);
}
},
false
);
document.addEventListener(
"mouseup",
function() {
el.style.transition = "";
vm.navigation.width = el.style.width;
document.body.style.cursor = "";
document.removeEventListener("mousemove", resize, false);
},
false
);
}
},
mounted() {
this.setBorderWidth();
this.setEvents();
}
});

Vue Random Router Component

{
path: '/link_1',
name: 'link_1',
component: () => import('./views/Link1.vue')
},
It is possible to have it one path like /link_1 but every time when go to this route load different component.
Like: First time when go to /link_1 load Link1.vue and second time when user go to /link_1 load and display Link2.vue.
You can use a combination of watch and <component> to render a dynamic component each time the link is clicked.
For example, this generates 100 components named component1 through component100, rendering one at random each time the <router-link></router-link> is clicked:
Vue.use(VueRouter)
const router = new VueRouter({
routes: [{
path: '/random/:id'
}]
})
const components = Array.from(Array(100), (x, i) => {
return {
name: `component${ i+ 1 }`,
props: ['lorem'],
template: `
<v-card>
<v-card-title>
<v-avatar>
<span class="blue-grey--text headline">${i + 1}</span>
</v-avatar>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<v-container fluid>
<v-layout justify-center>
<v-flex>
<span class="subheader" v-html="lorem"></span>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
</v-card>
`
}
}).reduce((carry, c) => {
carry[c.name] = c
return carry
}, {})
new Vue({
el: '#app',
components,
router,
computed: {
current() {
return `component${this.cid}`
}
},
data() {
return {
cid: 1,
lorem: 'What mystery does the next page hold?'
}
},
watch: {
'$route': {
handler: function() {
let id = this.cid
while (this.cid === id) {
id = Math.floor(Math.random() * 100) + 1
}
this.cid = id
fetch('https://baconipsum.com/api/?type=all-meat&paras=3&format=html').then(res => res.text()).then(data => {
this.lorem = data
})
}
}
}
})
<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script>
<script src="https://unpkg.com/vue-router#3.0.2/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-toolbar app>
<v-toolbar-items>
<v-btn :to="`/random/${cid}`" color="deep-orange darken-4" dark>Click Me</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-content>
<v-slide-x-transition leave-absolute mode="out-in">
<component :is="current" :lorem="lorem"></component>
</v-slide-x-transition>
</v-content>
</v-container>
</v-app>
</div>