How to render an html string with vuejs inside in Nuxt3 / Vue3 - vuejs3

I'm trying to write a component in Nuxt3 which will allow me to output a string of html (that contains vue elements).
Here is what I have so far for the component / plugin
plugins/RenderVueString.js
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.component('RenderVueString', {
props: ['html'],
render(h) {
return h({
template: `<div class="RenderVueString">${this.html}</div>`,
})
}
})
})
And then in pages/index.vue
<template>
<RenderVueString :html="vueHTML" />
</template>
<script>
export default {
data() {
return: {
vueHTML: `<div>This is some vue HTML {{testVar}} <a #click="testFunction()">Run Function</a></div>`,
testVar: 'Var Value Here'
}
},
methods: {
testFunction() {
console.log('test function ran');
}
}
}
</script>
I get this error: TypeError: h is not a function
So I tried adding this to the top of the plugins/RenderVueString:
import {h} from 'vue';
After that there is no console errors, but nothing renders.
I did try rendering something simple with h like this: h('div', 'Hello') and it did output that, but I can't figure out how to output complex html with embedded Vue.

Was able to figure this out by adding the following to nuxt.config.ts
hooks: {
'vite:extendConfig': (config, {isClient, isServer}) => {
if(isClient) {
config.resolve.alias.vue = 'vue/dist/vue.esm-bundler'
}
}
},
nitro: {
commonJS: {
dynamicRequireTargets: [
'./node_modules/#vue/compiler-core',
'./node_modules/#vue/compiler-dom',
'./node_modules/#vue/compiler-ssr',
'./node_modules/vue/server-renderer',
'./node_modules/vue'
]
},
},
alias: {
'#vue/compiler-core': '#vue/compiler-core',
'#vue/compiler-dom': '#vue/compiler-dom',
'#vue/compiler-ssr': '#vue/compiler-ssr',
'vue/server-renderer': 'vue/server-renderer',
'estree-walker': 'estree-walker',
'#babel/parser': '#babel/parser'
},
And then in the plugins/RenderVueString.js
import { h, compile } from 'vue';
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.component('RenderVueString', {
props: ['html'],
render() {
return h(compile(this.html), {$emit: this.$emit});
}
})
})
Allows this on the a template:
<template>
<RenderVueString :html="vueHTML" #runFunction="testFunction()" />
</template>
<script>
export default {
data() {
return: {
vueHTML: `<div>This is some vue HTML <a #click="$emit('runFunction')">Run Function</a></div>`,
}
},
methods: {
testFunction() {
console.log('test function ran');
}
}
}
</script>
I could also pass in variables as props into the RenderVueString component if needed.
This type of functionality is very useful if you're trying to allow some advanced coding from items being pulled from a database / CMS.

Related

Event #change is fire but cant get it in Vue Component Verte

can't catch the changed event
my template code:
<template>
<verte #change="changeColor(1)"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { verte }
methods: {
changeColor(id) {
console.log(id)
},
}
</script>
in vue inspector event #change starts..
tried changing this line
from import Verte from 'verte';
to import VertePicker from 'verte';
...
but as a result, only the input event fires, but I just can’t catch the event on changed
maybe something else is needed to correctly trigger the event? and could this be a component bug?
Verte is not supporting change event.
You can call changeColor as below.
<template>
<verte v-model="colorVal"></verte>
</template>
<script>
import Verte from 'verte';
export default {
components: { Verte },
data: () => ({
colorVal: ''
}),
watch: {
// whenever colorVal changes, this function will run
colorVal(currentColor, previousColor) {
// you can call changeColor function here.
changeColor(currentColor);
}
},
methods: {
changeColor(id) {
console.log(id)
},
}
</script>

Writing a custom directive on Vuejs 3, composition API in order to detect outside click

my first time trying directive with vue js3.
My goal: To detect a click outside the component with the directive and Vuejs 3, composition API.
My expected result: To change a Boolean value in each click outside the component 'CustomeMultiSelect.vue'.
My actual result: In any click, the Boolean value is changed.
Here is my 'App.vue'
<script lang="ts">
import CustomeMultiSelect from "./components/CustomeMultiSelect.vue";
import { ref } from "vue";
export default {
components: {
CustomeMultiSelect,
},
directives: {
"click-outside": {
mounted: function (el, binding) {
console.log("in directive");
const ourClickEventHandler = (event) => {
if (!el.contains(event.target) && el !== event.target) {
binding.value(event);
}
};
el.__vueClickEventHandler__ = ourClickEventHandler;
document.addEventListener("click", ourClickEventHandler);
},
unmounted: function (el) {
document.removeEventListener("click", el.__vueClickEventHandler__);
},
},
},
setup() {
let bool = ref(true);
function test() {
bool.value = !bool.value;
console.log(bool.value);
}
return {
test,
};
},
};
</script>
<template>
<div v-click-outside="test">
<CustomeMultiSelect/>
</div>
</template>
I defined directive that on 'mounted' Hook will attached event 'click' to each element in the screen -> 'v-click-outside' on <'CustomeMultiSelect.vue'/>
Component 'CustomeMultiSelect.vue' is a child component of 'App.vue'.
('CustomeMultiSelect.vue' has 3 childs).

img not loading from :src

I am getting the image location from firestore and would like to show the image using v-bind:src. You can find my code below:
<b-avatar v-bind:src = "profilepic" class="mr-5" size="8em"></b-avatar>
my methods can be found below:
export default {
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
I am confident that this.profilepic is storing the correct path as if i were to manually type in the path, it will show. I am suspecting that the page loaded before path could be retrieve from firestore. How can i work around this? Thank you in advance.
I have tried hardcoding the path directly to the data and it works fine. The code can be found below:
data() {
return {
uid: "",
profilepic: "*my firebase storage path*",
}
},
With that im not really sure why isnt it still showing
In the script below the template tags, you need to make sure to include the image, and of course, instead of putting my path, put your image's path!
<script>
export default {
data() {
return {
files: {
my_pic: require('/src/assets/img/avatars/logo.png')
}
};
},
}
};
</script>
Then in your and where you want to put your image, you need to put it in this format
<img :src="files.my_pic">
Let me know if this helps or if you want me to expand more.
Try waiting for the uuid to get retrieved:
<template>
<img height="200" v-bind:src = "profilepic" />
</template>
<script>
export default {
data() {
return {
uuid: undefined,
profilepic: undefined
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL()
.then(imgURL => {
this.profilepic = imgURL;
})
},
getuuid(){
return new Promise((resolve, reject) => {
var user = fb.auth().currentUser;
if(user == null) reject()
if (user) resolve(user.uid)
})
}
},
created() {
this.getuuid()
.then(uuid => this.uuid = uuid)
.then(() => {
this.getprofilepic();
})
}
};
</script>
As you can see in this example, it does not matter how long it takes for the URL to load: Vue SFC Playground
When Vue Loader compiles the <template> blocks in SFCs, it also converts any encountered asset URLs into webpack module requests.
For example, the following template snippet:
<img src="../image.png">
will be compiled into:
createElement('img', {
attrs: {
src: require('../image.png') // this is now a module request
}
})
By default the following tag/attribute combinations are transformed, and can be configured using the transformAssetUrls option.
{
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
Step 1: Create vue.config.js
module.exports = {
productionSourceMap: false,
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options['transformAssetUrls'] = {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href',
'b-avatar': 'src',
'b-img': 'src',
'b-img-lazy': ['src', 'blank-src'],
'b-card': 'img-src',
'b-card-img': 'src',
'b-card-img-lazy': ['src', 'blank-src'],
'b-carousel-slide': 'img-src',
'b-embed': 'src'
}
return options
})
}
}
Step 2: Inside main.js import vue.config
import '../vue.config'
Step 3: Create your html template
<template>
<b-avatar :src="profilepic" class="mr-5" size="8em"></b-avatar>
</template>
<script>
import { BAvatar } from 'bootstrap-vue'
export default {
name: 'bootstrap-image-avatar',
components: {
'b-avatar': BAvatar
},
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
</script>

Use injected variables (nuxt.firebase) in composition api

I'm using the composi api in my Vue project and the nuxt.js firebase module, I would like to call variables injected into modules, such as $ fireAuth, but I didn't find a solution.
Below is a small code training of how I would like it to work:
export default createComponent({
setup(_props, { root }) {
root.$fireAuth= ..
}
}
// or
export default createComponent({
setup(_props, { root , $fireAuth }) {
}
}
I have a work-around for this and it works! (For now.)
Create a dummy component (ex. AppFirebase.vue)
<template></template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
created() {
this.$emit("init", this.$fire);
},
});
</script>
Accessing NuxtFireInstance (ex. SomeComponent.vue)
<template>
<fire #init="initFB"></fire>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
} from "#nuxtjs/composition-api";
import fire from "#/components/AppFirebase.vue";
export default defineComponent({
components: { fire },
setup() {
let _fire: any = reactive({});
const initFB = (fire: any) => {
_fire = fire;
};
const signout = async () => {
try {
await _fire.auth.signOut().then(() => {
// do something
});
} catch (error) {
console.log(error);
}
};
return {
initFB,
_fire,
signout,
};
},
});
</script>
Rickroll if you got it working!

Conditional navbar based on a user authentication status

I'm trying to conditionally display navbar elements of a navigation component based on the onAuthStateChanged Firebase function.
<template>
<navbar dark position="top" class="default-color" scrolling>
<mdb-navbar-brand href="#/" style="font-weight: bolder;">
Test
</mdb-navbar-brand>
<navbar-collapse>
<navbar-nav left>
<navbar-item href="#/" waves-fixed>Home</navbar-item>
<navbar-item href="#/css" waves-fixed>About</navbar-item>
<navbar-item href="#/jobs" waves-fixed>Jobs</navbar-item>
<navbar-item href="#/advanced" waves-fixed>Profile</navbar-item>
</navbar-nav>
<navbar-nav right>
<router-link to="/signup"><button v-if="!user" type="button" class="btn btn-primary">Signup</button></router-link>
<router-link to="/login"><button v-if="!user" type="button" class="btn btn-primary">Login</button></router-link>
<p><a v-if="user" #click="logout">Logout</a></p>
</navbar-nav>
</navbar-collapse>
</navbar>
</template>
<script>
import Navbar from '#/components/Navbar.vue';
import NavbarItem from '#/components/NavbarItem.vue';
import NavbarNav from '#/components/NavbarNav.vue';
import NavbarCollapse from '#/components/NavbarCollapse.vue';
import mdbNavbarBrand from '#/components/NavbarBrand.vue';
import firebase from 'firebase';
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
logout() {
firebase.auth().signOut()
.then(() => {
this.$router.push({path: '/'});
});
},
created() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
}
};
</script>
Unfortunately, for some reason, the onAuthStateChanged is not working. I also tried to simply display the user in the console from the component perspective, but it's not working as well:
console.log(firebase.auth().currentUser);
Thanks in advance for any hints.
I just wanted to point out another option. Renaud Tarnec's answer is correct but there is a second solution.
You can use the arrow function syntax. With arrow functions the context doesnt change so there is no need to set vm = this before the function since this will still work inside the function. I'm a huge fan of lambda/arrow functions and see no reason not to use them.
Renaud Tarnec's should be the accepted answer but just wanted to offer a second option :)
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
};
If you want to call firebase.auth().onAuthStateChanged() in the created lifecycle hook you should do as follows:
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
var vm = this;
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
vm.user = user;
} else {
vm.user = null;
}
});
}
};
The way you do it, you are declaring created as a "standard" component method.

Resources