How to inject Vuetify into my custom vue plugin - vue.js

I would like to create a Vue plugin with a function which programatically renders a Vue component. That component depends on Vuetify. Everything works fine if I use vanilla HTML/CSS in that component, but using Vuetify-related things in there (e.g. a ) does not work. I assume that I didn't inject vuetify itself into the component correctly.
In my custom component, I tried importing every Vuetify component separately, but without success. I also tried creating the component with the syntax: new Vue({vuetify}), but also without success.
import MyCustomComponent from '#/components/MyCustomComponent'
import vuetify from '#/plugins/vuetify';
export default {
install(Vue, options) {
function renderMyCustomComponent() {
const CustomComponent= Vue.extend(MyCustomComponent)
Vue.use(vuetify)
let instance = new CustomComponent()
instance.$mount()
document.body.appendChild(instance.$el)
}
Vue.prototype.$renderMyComponent = renderMyCustomComponent
}
}
The error message indicates, that vuetify (or at least some of it's properties) are not available in my component
[Vue warn]: Error in getter for watcher "isDark": "TypeError: Cannot read property 'dark' of undefined"
HINT/EDIT: I am using Vuetify 2.0. The way Vuetify is injected into the app changed a little bit. Here's the code of my vuetify plugin file:
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';
import de from 'vuetify/es5/locale/de';
Vue.use(Vuetify)
export default new Vuetify({
theme: {
themes: {
light: {
primary: '#3f51b5',
secondary: '#b0bec5',
accent: '#8c9eff',
error: '#b71c1c'
},
},
},
});

Not sure if you solved this issue, but I had the same problem where Vuetify in a plugin would not be initialized correctly.
Vuetify documentation states that you need to define a vuetify option when creating your vue instance:
new Vue({
vuetify,
}).$mount('#app')
Fortunately, custom Vue plugins has an options parameter that we can use.
Here is the code that consumes your plugin:
const options = {}; // add other options here! (vuex, router etc.)
Vue.use(YourCustomPlugin, options);
new Vue(options).$mount('#app');
And here is your plugin code:
import vuetify from "./src/plugins/vuetify";
export default {
install(Vue, options) { // options is undefined unless you pass the options param!
Vue.component('my-custom-component', MyCustomComponent);
Vue.use(Vuetify);
options.vuetify = vuetify;
}
};
The vuetify module is very simple:
import Vuetify from "vuetify";
import "vuetify/dist/vuetify.min.css";
const opts = {}
export default new Vuetify(opts);

The problem is that you actualy don't export plugin itself in '#/plugins/vuetify';
import MyCustomComponent from '#/components/MyCustomComponent'
import Vuetify from 'vuetify';
export default {
install(Vue, options) {
function renderMyCustomComponent() {
Vue.use(Vuetify)
const CustomComponent= Vue.extend(MyCustomComponent)
let instance = new CustomComponent()
instance.$mount()
document.body.appendChild(instance.$el)
}
Vue.prototype.$renderMyComponent = renderMyCustomComponent
}
}

Related

How to use uploadAreaMask slot component in vue formulate in Nuxt

I tried by configuring my own component as below in plugins. But my component haven't overrided default component.Please help.
import Vue from "vue";
import VueFormulate from "#braid/vue-formulate";
import FileUploadArea from "~/components/app/FileUploadArea";
// Register your component with vue
Vue.component("FileUploadArea", FileUploadArea);
// Let Vue Formulate know which slot you want to override for a given type
Vue.use(VueFormulate, {
library: {
// the `type` of input you’re targeting.
file: {
slotComponents: {
uploadAreaMask: "FileUploadArea",
},
},
},
});

How can i make all v-text-field components outlined by default in nuxt/vuetify module

i'm using the nuxt/vuetify module and would like to make all v-text-fields components outlined.
Try to create and register plugin which register new vue component, that extends vuetify VTextField component.
import Vue from 'vue';
import { VTextField } from "vuetify/lib"
Vue.component('mTextField', {
extends: VTextField,
props: {
outlined: {
type: Boolean,
default: true
}
}
})
But always catch error while try to use mTextField component
Unexpected token 'export'
How can i make all v-text-fields components outlined?
Add transpile section in nuxt.config.js with 'vuetify/lib' worked for me
build: {
transpile: ['vuetify/lib']
},

Vue 2: How to unit test component that uses Chart.js (vue-chart-3)

I have a vue2 project that uses ClassComponents and Chart.js (via vue-chart-3). I now have a simple component that wraps a DoughnutChart to manage data and stuff.
DBOverviewDoughnut.vue
<template>
<div>
<p>test</p>
<DoughnutChart ref="doughnutRef" :chartData="sharesData"></DoughnutChart>
</div>
</template>
<script lang="ts">
import Component from 'vue-class-component';
import Vue from 'vue';
import { DoughnutChart, ExtractComponentData } from 'vue-chart-3';
import { Prop, Ref } from 'vue-property-decorator';
import { ChartData } from 'chart.js';
#Component({ components: { DoughnutChart } })
export default class DBOverviewDoughnut extends Vue {
#Prop()
private sharesData!: ChartData<'doughnut'>;
#Ref()
private readonly doughnutRef!: ExtractComponentData<typeof DoughnutChart>;
created(): void {
this.assignColors();
}
mounted(): void {
if (this.doughnutRef.chartInstance) {
console.log(this.doughnutRef.chartInstance.data);
}
}
assignColors(): void {
this.sharesData.datasets[0].backgroundColor = [
'#77CEFF',
'#0079AF',
'#123E6B',
'#97B0C4',
'#A5C8ED',
];
}
}
</script>
Starting the program it will work fine and I can access the chartInstance inside the mounted hook.
But now I want to unit test my component. I thought on setting the propsData which will be the input data for the chart.
DBOverviewDoughnut.spec.ts
import DBOverviewDoughnut from '#/components/dashboard/DBOverviewDoughnut.vue';
import { mount, Wrapper } from '#vue/test-utils';
import { Share } from '#/Share';
describe('DBOverviewDoughnut', () => {
let cut: Wrapper<DBOverviewDoughnut>;
it('should render the correct amount of sections', () => {
cut = mount(DBOverviewDoughnut, {
propsData: {
sharesData: {
labels: ['TestShare1', 'TestShare2', 'TestShare3'],
datasets: [{ data: [11, 22, 33] }]
}
}
});
const chart = cut.findComponent({ ref: 'doughnutRef' });
console.log(chart);
});
});
Using shallowMount() doesn't seem to work, because I only get this from logging (no chartInstance and its properties as in the production code):
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: [],
selector: { ref: 'doughnutRef' }
}
So I thought maybe I have to mount the component because the DoughnutChart is also a wrapper around the Chart.js charts. But when using mount() I get the following error:
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: `createElement()` has been called outside of render function.
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function."
found in
---> <DoughnutChart>
<DBOverviewDoughnut>
<Root>
I don't really know what I'm doing wrong. I registered the VueCompostionAPI in my main.ts:
import Vue from 'vue';
import { Chart, registerables } from 'chart.js';
import App from './App.vue';
import router from './router';
import store from './store';
import VueCompositionAPI from '#vue/composition-api';
Chart.register(...registerables);
Vue.use(VueCompositionAPI);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
Following this post doesn't solve the problem either.
Anyone got an idea what's going wrong? I'm a bit confused if the error has to do with my test setup or with the installation of chart.js or compositionApi.
You need to use VueCompositionAPI inside your spec as well when you mount the component. You can do this by creating a local Vue instance inside your spec, adding VueCompositionAPI as a plugin to the instance and using the instance when you mount the component. https://vue-test-utils.vuejs.org/api/options.html#localvue
Using localVue is really what I should have thought about. This and installing the canvas-package works, that I get additional information about my Ref-Element. However I still have to figure out what to do with it.
#AdriHM I want to test if the rendered chat gets the correct data I guess. Or if it displays it correctly (e.g. display the correct amount of sections) But the longer I think about it the less I'm sure it's the right thing to test. I don't want to test the Chart.js API though.

Vuetify Storybook remapInternalIcon issue

Using Vuetify 2 and Storybook 6 (source https://github.com/lydonchandra/vuetify2storybook6 )
The component renders fine, but keep getting this error TypeError because vm.$vuetify.icons is undefined, when rendering component for first time.
Not sure which storybook-vuetify initialization bridge did I miss ?
TypeError: Cannot read property 'component' of undefined
at remapInternalIcon (vuetify.js:44048)
at VueComponent.getIcon (vuetify.js:16881)
at Proxy.render (vuetify.js:17009)
at VueComponent.Vue._render (vue.esm.js:3557)
at VueComponent.updateComponent (vue.esm.js:4075)
at Watcher.get (vue.esm.js:4488)
at new Watcher (vue.esm.js:4477)
function remapInternalIcon(vm, iconName) {
// Look for custom component in the configuration
var component = vm.$vuetify.icons.component; // <-- issue here when rendering for first time
if (iconName.startsWith('$')) {
// Get the target icon name
src/plugins/vuetify.ts
import Vue from "vue";
import Vuetify from "vuetify/lib";
import { UserVuetifyPreset } from "vuetify";
Vue.use(Vuetify);
export const options: UserVuetifyPreset = {
icons: {
iconfont: "mdiSvg"
}
};
export default new Vuetify(options);
Workaround for now is to set addon-essentials.docs to false. (Ref.
https://github.com/storybookjs/storybook/issues/7593)
file: .storybook/main.js
...
"addons": [
"#storybook/addon-links",
{
name: "#storybook/addon-essentials",
options: {
docs: false
}
}
],
...
If you don't want to disable addon-essentials.docs, you can add the following style in .storybook/preview-head.html
<style>
.sb-errordisplay {
display: none !important;
}
</style>
Another workaround without having to disable addon-essentials or adding any styles in the preview-head.html file you can import Vuetify at the top of your .stories.js (or .stories.ts) file like so e.g.
import vuetify from '#/plugins/vuetify'
then when you declare your storybook Template, pass in your vuetify object
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { YourComponent },
vuetify, // <-- Very important line
template: `<YourComponent />`
})
I found this workaround in this thread Cannot read property 'mobile' of undefined - Vue/Vuetify/Storybook

How can I set up moment.js in the vuetify?

I using vuetify : https://vuetifyjs.com/en/
I want to use moment.js. So I read this reference : https://www.npmjs.com/package/vue-moment
I had run npm install vue-moment
I'm still confused to put this script Vue.use(require('vue-moment'));
In the vuetify, there exist two file : main.js and index.js
main.js like this :
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/index'
import './registerServiceWorker'
import vuetify from './plugins/vuetify'
Vue.config.productionTip = false
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')
index.js like this :
import Vue from 'vue';
import Vuex from 'vuex';
import dataStore from './modules/data-store';
import createLogger from "vuex/dist/logger";
Vue.use(Vuex);
const debug = process.env.VUE_APP_DEBUG !== "production";
export default new Vuex.Store({
modules: {
dataStore
},
strict: debug,
plugins: debug ? [createLogger()] : []
});
where do i put Vue.use(require('vue-moment'));?
I try to put it in the main.js, but if i call my vue component, there exist error : ReferenceError: moment is not defined
My vue component like this :
<template>
...
</template>
<script>
export default {
mounted() {
let a = moment("2012-02", "YYYY-MM").daysInMonth();
console.log(a)
}
};
</script>
I found this at the bottom of the vue-moment npm page
vue-moment attaches the momentjs instance to your Vue app as
this.$moment.
This allows you to call the static methods momentjs provides.
So you should be able to use your original configuration of vue-moment and do this in your mounted() method
mounted() {
let a = this.$moment("2012-02", "YYYY-MM").daysInMonth();
console.log(a)
}
notice this.$moment
And for the set up of vue-moment you should place this in your main.js file
main.js
Vue.use(require('vue-moment'))
=========================================================================
GLOBAL
If you want to use moment with Vue globally you can create an Instance Proprety
main.js
import moment from 'moment'
Vue.prototype.moment = moment
In your component you then call this.moment in your methods or computed properties. In your mounted section it would look like this
mounted() {
let a = this.moment("2012-02", "YYYY-MM").daysInMonth();
console.log(a)
}
COMPONENT
If you just want to use moment in a component you can include directly like this
<script>
import moment from 'moment'
export default {
mounted(){
let a = moment("2012-02", "YYYY-MM").daysInMonth();
console.log(a)
}
}
</script>