VueJS 3 create/pass hyperscript to template at runtime? - vue.js

I wanted to try something for performance/convenience purposes, I understand the gains will be minimal but understanding how/if/why this works would also just be helpful to learn.
I have a some custom data types (defined as classes) that are used to identify certain properties throughout my application. I want to use a static function on the type to define a display function. (stripped down) Example:
class Email extends String{
static display = (value) => {
return `<a href='mailto${value}'>${value}</a>`;
}
}
Call it like you do:
Email.display("test#test.com");
And that works in the template, so long as it’s in a v-html attribute. This is perfectly acceptable.
It’s probably important to specify I’m working with Vue-CLI and single-file components, so all that sweet hyperscript gets created at compile time.
But it got me thinking, is there a way I can pass a freshly-created hyperscript to the template at render? Preferably in a way that works in the {{mustache}} if at all possible.
I tried doing it with h but that just displays the ol’ [object Object].
class Email extends String{
static display = (value) => {
return h('a', {innerHtml: value});
}
}
Update: also tried
I thought maybe going around the Vue render functions could get the job done, but they don't seem to like document fragments either.
static display = (value) => {
var fragment = document.createDocumentFragment();
var a = document.createElement('a');
a.textContent = value;
fragment.appendChild(a);
return fragment;
}
Question
Is there a way create hyperscript at runtime and utilize it in a vue template? Bonus points if it works in {{mustache}} and v-html.

Generally component templates and render functions that use JSX or h (hyperscript) are mutually exclusive.
It's really possible to do this, in this case display is actually functional component, and it needs to be output as any other dynamic component:
setup() {
const display = (props) => {
return h(...);
};
return { display };
}
and
<component :is="display" :value="..."/>
The return of display is a hierarchy of vnode objects, they can't be used as is in v-html without being previously rendered to HTML.

Related

Passing data to an ancestor component vue3

I have a multi-step form in Vue3 that has the following structure:
<Formroot />
...
<formPage2 />
...
<postcodeWidget />
I have a data structure in my formRoot that holds all the field values from the child components and then uses them to make an external API call and present a result.
I use Props to pass the data down to the child components and then emit from the children to the parent.
The issue is, my autocomplete widget - which pulls from an external api - does all the autocomplete in the setup() function. I cannot figure out the best way to communicate input from that widget back up to the formRoot component.
I tried emitting from the widget but I can't access the instance from within setup, and I can't seem to access the data from setup variables within an instance method.
For example, I have a function called changePostcode that fires on input to the field:
methods: {
changePostcode(e){
//I have tried calling the input event:
this.$emit('update:postcode', e.target.value)
//I have tried accessing my setup variable:
this.$emit('update:postcode', this.selectedPostcode) //or postcode.value this is the actull value I want to emit.
//these dont work.They return nothing.
},
}
my selectedPostcode variable is set in the setup() function as follows:
setup() {
...
let selectedPostcode = ref('')
let searchTerm = ref('')
...
// searchTerm is used in a filter with data from an external API to offer suggestions. This is the ultimate source of the "location" object
const selectPostcode = (location) => {
selectedPostcode.value = location.postcode
searchTerm.value = location.locality
}
return {
searchTerm,
...
selectPostcode,
selectedPostcode,
...
}
}
I have a locality and a postcode variable because I want to populate the input with a "locality" that includes the full name of the suburb while I want to emit only the post/zip code.
My setup does a bunch of other work including calling and api for a list of suburb and filtering on user input to make suggestions. That all works fine.
In summary,
A multi step form
One step includes a nested component that needs to pass data up to the root ancestor
I cannot seem to access/emit data from setup() back up to the ancestor element
What is the right way to do this? It seems like it should be a pretty common use case.
I looked into provide/inject as well but I also couldn't understand how to send data back up to the ancestor only down to the child.
The ancestor could provide a function (e.g., a setter) that the nested component could inject to communicate a value back to the ancestor:
// RootAncestor.vue
<script setup>
import { ref, provide } from 'vue'
const postCode = ref('initial value')
const setPostCode = value => {
postCode.value = value
}
provide('setPostCode', setPostCode)
</script>
// NestedComponent.vue
<script setup>
import { inject } from 'vue'
const setPostCode = inject('setPostCode')
const save = () => {
setPostCode('12345')
}
</script>
demo

Vue.js extend component and data updates

I'm using vue.js extends for the first time. I have a component that extends another and it needs to read the root components data to update the status in its own component.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
So I might not be going about this the right way if the extended component doesn't update when the root does. For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
Is this the expected behaviour and is there a way I can send the updated data down to the extended component?
Sample code:
<a inline-component>
<input type="text" v-model="myArray" />
<button v-on:click="saveData">Save</button>
</a>
<b inline-component>
<div v-if="myArray.length > 0">On Target</div>
</b>
var a = Vue.component('a', {
data: function () {
return {
myArray: [],
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
vm.myArray = response.data;
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
I have a component that extends another and it needs to read the root components data to update the status in its own component.
For the purposes of my answer I'm going to assume that you have two component definitions, A and B, and B extends A. I assume that when you say root you just mean A.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
Rendering is not really relevant here. The data properties are set up when a component instance is created. Typically rendering will happen just after creation but merging any data happens much earlier in the component life-cycle. Even if the component isn't rendered the data will still be initialised.
No copying takes place. Let's consider a data function on component A:
data () {
return {
myArray: []
}
}
Every time this function is invoked it is going to return a new object, each containing a new array. This is precisely what happens if you create an instance of A directly. For each instance, Vue will call this function and get a new object defining the data. Generally that's what you'd want, rather that having components sharing data.
Now let's consider B. That might define its own data function. When an instance of B is created Vue will call the data function for both A and B and then merge the objects. No copying takes place, just merging. If you want to know more about how Vue handles merging in general see the documentation but for data the strategy is pretty simple. Properties from both objects will be combined with B taking precedence over A if there's a clash of property names. There is no recursive merging of properties.
So the idea of updating 'a property in the root component' is not particularly well-defined. You might be thinking of it as a bit like a prototype chain, where modifying a superclass would impact the subclass, but that isn't what's going on here. The data functions are invoked when the component is created and that's that. There isn't a lasting link back to the component definition like there is with a prototype chain.
If you really want all your component instances to share the same data value then it can be done, you just need to make sure that the data function is returning the same object/array every time. e.g.
const myArray = []
export default {
name: 'A',
data () {
return {
myArray
}
}
}
Written this way all instances of A will share the same array for myArray. So long as B doesn't define it's own value for myArray it will share it too.
For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
I'm struggling a bit to understand what that means. It seems there are lots of assumptions about things being shared, single instances here. It's not entirely clear how you update the 'root' given it's a component definition and not a component instance.
If possible you should use a computed property for this. That would be inherited by B. Each instance of A (or B) would have their own value for this computed property, which might be a little wasteful if they're all going to be the same, but it's probably still the best way to go.
You could in theory use a watch. That should be inherited too but keep in mind it would be manipulating values for that particular instance.
Reading between the lines a little, if you wanted to update something on the 'root' so that it magically appeared in the subcomponents you could use the same shared reference-type trickery that I demonstrated earlier for myArray. You may need to be careful with how you update it though. If, for example, you used a watch you might find the you end up updating the same object many times, once for each instance of the component.
Update:
Based on the code you've posted it could be made to work something like this:
var myArray = [];
var a = Vue.component('a', {
data: function () {
return {
myArray: myArray // Note: using the same, shared array
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
// Note: Updating the array, not replacing it
var myArray = vm.myArray;
myArray.splice(0, myArray.length);
myArray.push.apply(myArray, response.data);
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
Your example didn't include any ES6 so I've refrained from using it but it would be a bit simpler if that were available.
The example above works by sharing the same array between all instances of the component and then mutating that instance. Assigning a new array to that property won't work as it would only update that particular component instance.
However, all that said, this is increasingly looking like a case where you should give up on trickery and just use the Vuex store instead.

explain vue-router component as a function

I have seen in several different places the following type of route definition:
{ path : '/dashboard',
component: { render (c) { return c('router-view') }},
children:[{
path:"",
component: Dashboard
}]
},
I am trying to understand how this is different then
{ path : '/dashboard',
component: Dashboard
},
I think it is related to the optional addition of child routs (e.g. /dashboard/user) so that and the children array here just explains that the Dashboard component renders the path /dashboard whereas if I had the second piece of code then it can only render /dashboard.
What I do want to know is what exactly this does
component: { render (c) { return c('router-view') }},
I assume this is some form of a degenerated component but I don't understand what exactly does it do and how.
In Vue, a component is created using an object containing its configuration.
The simplest possible component may look something like this
componentConfig = {
template: '<div>test</div>'
};
Vue.component('test', componentConfig);
In some cases, a developer might not want to use template, and would want to create element from scratch using pure Javascript. That's where render function comes in.
Vue recommends using templates to build your HTML in the vast majority
of cases. There are situations however, where you really need the full
programmatic power of JavaScript. That’s where you can use the render
function, a closer-to-the-compiler alternative to templates.
from https://v2.vuejs.org/v2/guide/render-function.html#Basics
To change the example above to using render function:
componentConfig = {
render: function(createElement) {
return createElement('div', 'test')
}
};
Vue.component('test', componentConfig);
They would produce the exact same result:
https://codepen.io/jacobgoh101/pen/ZoKwKb?editors=1010
https://codepen.io/jacobgoh101/pen/PemVmy?editors=1010
In other words, render function is simply an alternative to using template.
{
component: {
render(c) {
return c('router-view')
}
}
}
is equal to
{
component: {
render(createElement) {
return createElement('router-view')
}
}
}
is equal to
{
component: {
template: `<router-view></router-view>`
}
}
Because render function is closer-to-the-compiler, it's faster compared to using template. That's probably why the author of your code does it this way.
I don't know the rest of your code, but it looks like this might be an implementation of the vue-router Lazy Loading functionality. Basically, Vue + Webpack are going to split your code into chunks and only load those chunks whenever the user attempts to navigate to those routes, rather than loading them all and creating a bigger bundle to download than necessary.
When building apps with a bundler, the JavaScript bundle can become quite large, and thus affect the page load time. It would be more efficient if we can split each route's components into a separate chunk, and only load them when the route is visited.
Combining Vue's async component feature and webpack's code splitting feature, it's trivially easy to lazy-load route components.

Lazy loading images in Vue/Laravel

I am trying to use jQuery Lazy Loading in my Laravel/Vue project but I am struggling to get an image to appear in my Vue component. I have the following img block which I thought would work:
<img v-if="vehicle.photo_path != null" :data-original="'/storage/vehicles/' + vehicle.photo_path" class="lazy" height="180" width="150"/>
I did find this other question on here - Static image src in Vue.js template - however when I try that method I get this: Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead.
So I switched back to the v-bind method but all I am getting is a white box with a grey border - no image. If I v-bind on the src attribute however I can see the image correctly.
I know I have implemented the Lazy Loading plugin correctly as I can successfully call it elsewhere on my site (such as on a blade view), but I'm not sure where I am going wrong. Thank you.
Try moving the call to $("img.lazy").lazyload() into the mounted() method on your Vue instance. I just had a similar issue with Vue and jQuery Lazyload and that solved it for me. For example:
var app = new Vue({
el: ...
data() ...
computed: ...
methods: ...
mounted() {
$("img.lazy").lazyload()
}
})
I found many modules on the internet, but I like to avoid modules when I can. So I came up with something else, I don't know if it's the best, but it works and I didn't see any performances loss, I lazy load all the images when the page loads. You may prefer on scroll if you have lots of them, but I guess you'll figure this out if my answer fits your needs.
You'll need vueX for this, but I'll avoid the set up as this is not replying to your question.
If, like me, you have some kind of Index.vue component which main usage is to initiate all the child components (I use to do that for vue-router for instance), place a mounted() function in it :
mounted(){
const ctx = this;
// You could use this method, but if you reload the page, the cache of the browser won't allow your JS to trigger .onload method, so better use what's after.
/*window.onload = function(){
console.log('page loaded, can load images');
ctx.$store.dispatch('setPageLoaded', true);
}*/
let interval = setInterval(function() {
if(document.readyState === 'complete') {
clearInterval(interval);
ctx.$store.dispatch('setPageLoaded', true);
}
}, 100);
}
=> On the page load, I just set a page_load variable in the store to true.
Then, in any component you'd like to lazy load the image, just use 2 computeds (I used a mixin that I include in my components so I avoid repeating some code) :
computed: {
page_loaded(){
return this.$store.getters.getPageLoaded;
},
image(){
if(this.page_loaded){
console.log('starting loading image');
if(this.product.picture){
return resizedPicture(this.product.picture, this.width, this.height);
}else if(this.product.hasOwnProperty('additional_pictures') && this.product.additional_pictures.length){
return resizedPicture(this.product.additional_pictures[0], this.width, this.height);
}
return '';
}
}
}
page_loaded() goal is to return the store page_loaded variable I talk about.
image() goal is to actually load the image.
I used that for inline CSS background-image property, so my HTML looks like this :
<span v-if="page_loaded" class="image" :style="'background-image:url('+image+')'" :width="1200" :height="900"></span>
I hope it'll help, but as I said, feel free guys to tell me if it's not optimized.

Passing props to child component Cyclejs

I m studying CycleJs and I m looking for a proper way to handle passing props to child component.
Actually, I m having the following stuff :
import {div, input} from '#cycle/dom'
export function App(sources) {
const inputOnChange$ = sources.DOM.select('input').events('input')
const streamofResult = inputOnChange$
.map(e => e.target.value)
.startWith('')
.map(defaultInput => {
const title = Title({value: defaultInput})
return div([
title,
input({attrs: {type: 'text'}})
])
})
const sinks = {DOM: streamofResult}
return sinks
}
export function Title(sources) {
return div(sources.value)
}
It simply allows to make some inputs, and to display it in a child component called Title.
I think I should use a stream to handle passing props to my child.
But I don't understand why it would be a better solution in this simple to use a stream instead of a primitive ?
There is something that I probably have not understood.
You haven't misunderstood anything. There is no right answer. If you know for a fact you'll never want to change the props after initialization then you could pass the props as a primitive, but the more common convention is to send a props$ since it's not much costlier to do something like O.of(x) vs x (assuming RxJS) and using streams everywhere is consistent with the philosophy of the framework. Additionally, there are occasions when you'll want to change the properties dynamically after component initialization, where a stream is appropriate.
Keeping a consistent props or props$ convention for all your components can make reading the code easier since you won't have to think, "Does this component use a primitive or a stream for props...?"