How to run component's method via HTML in Vue2 - javascript

I have a .vue file, ServiceList, which imports the component Information.vue. I would like to run Information's code in a loop in ServiceList's template like so:
ServiceList.vue
<template>
<div>
<h1>Headline</h1>
<div v-for="service in services">
<h2>{{ service.name }}</h2>
<information v-bind:service="service"/>
</div>
</div>
</template>
<script>
import Information from './Information'
... (more components here)
export default {
components: {
Information,
... (more components here)
},
... (rest of export here)
}
</script>
This is what Information looks like:
Information.vue
<template>
<div>
<p v-bind:id="'info'+service.id"/>
</div>
</template>
<script>
export default {
props:['service'],
data: function () {
return {
services: this.$store.state.Services.data
}
},
methods: {
getInfo: function (service) {
var info = '<b>Servicename:</b> <br>';
info += service.name;
... (method adds more to 'info' here)
document.getElementById('info'+service.id).innerHTML = info;
}
}
}
</script>
I have tried to do stuff like
<template>
<div>
<p v-bind:id="'info'+service.id"/>
{{ getInfo(service) }}
</div>
</template>
but it never seems to work. Weird thing is that when I make it a button,
<template>
<div>
<button v-on:click="getInfo(service)">GET INFO</button>
<p v-bind:id="'info'+service.id"/>
</div>
</template>
it works perfectly! But I don't want a button, I just want it to appear.

You don't need to manipulate DOM in Vue js for such trivial case, just add all you want to template and remove getInfo method, example:
Information.vue
<template>
<div>
<p>
<b>Servicename:</b> <br>
{{ service.name }}<br>
<b>Another Field:</b> <br>
{{ service.anotherField }}<br>
<b>Another Field 2 :</b> <br>
{{ service.anotherField2 }}<br>
</p>
</div>
</template>
OR if you really want to work with html do this:
Information.vue
<template>
<div>
<p v-html="getInfo(service)"/>
</div>
</template>
<script>
export default {
props:['service'],
data: function () {
return {
services: this.$store.state.Services.data
}
},
methods: {
getInfo: function (service) {
if (!service) return '';
var info = '<b>Servicename:</b> <br>';
info += service.name;
... (method adds more to 'info' here)
return info;
}
}
}

Related

Vue.js slots - how to retrieve slot content in computed properties

I have a problem with vue.js slots. On one hand I need to display the slot code. On the other hand I need to use it in a textarea to send it to external source.
main.vue
<template>
<div class="main">
<my-code>
<template v-slot:css-code>
#custom-css {
width: 300px
height: 200px;
}
</template>
<template v-slot:html-code>
<ul id="custom-css">
<li> aaa </li>
<li> bbb </li>
<li> ccc </li>
</ul>
</template>
</my-code>
</div>
</template>
my-code.vue
<template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"><slot name="css-code"></slot></component>
<slot name="html-code"></slot>
<!-- send the code -->
<form method="post" action="https://my-external-service.com/">
<textarea name="html">{{theHTML}}</textarea>
<textarea name="css">{{theCSS}}</textarea>
<input type="submit">
</form>
</div>
</template>
<script>
export default {
name: 'myCode',
props: {
codeId: String,
},
computed: {
theHTML() {
return this.$slots['html-code']; /* The problem is here, it returns vNodes. */
},
theCSS() {
return this.$slots['css-code'][0].text;
},
}
}
</script>
The issues is that vue doesn't turn the slot content. It's an array of <VNode> elements. Is there a way to use slots inside the textarea. Or a way to retrieve slot content in the theHTML() computed property.
NOTE: I use this component in vuePress.
You need to create a custom component or a custom function to render VNode to html directly. I think that will be the simplest solution.
vnode to html.vue
<script>
export default {
props: ["vnode"],
render(createElement) {
return createElement("template", [this.vnode]);
},
mounted() {
this.$emit(
"html",
[...this.$el.childNodes].map((n) => n.outerHTML).join("\n")
);
},
};
</script>
Then you can use it to your component
template>
<div class="my-code">
<!-- display the code -->
<component :is="'style'" :name="codeId"
><slot name="css-code"></slot
></component>
<slot name="html-code"></slot>
<!-- send the code -->
<Vnode :vnode="theHTML" #html="html = $event" />
<form method="post" action="https://my-external-service.com/">
<textarea name="html" v-model="html"></textarea>
<textarea name="css" v-model="theCSS"></textarea>
<input type="submit" />
</form>
</div>
</template>
<script>
import Vnode from "./vnode-to-html";
export default {
name: "myCode",
components: {
Vnode,
},
props: {
codeId: String,
},
data() {
return {
html: "", // add this property to get the plain HTML
};
},
computed: {
theHTML() {
return this.$slots[
"html-code"
]
},
theCSS() {
return this.$slots["css-code"][0].text;
},
},
};
</script>
this thread might help How to pass html template as props to Vue component

How to show newly data added?

I want to ask why the newly added data does not display in the template, but it does show the newly added data in using console.log (see the ViewPost.vue).
This is the result after I add new post: result.png. Someone knows how to achieve this?
Here is my parent component:
<template>
<section>
//more codes here
<ViewPost v-for="data in postData" :key="data.post_id" :data="data" />
</section>
</template>
<script>
import { mapState } from 'vuex';
export default {
components: {ViewPost},
computed: {
...mapState({
postData: state => state.post.datas,
}),
}
//more codes here
};
</script>
And below is the ViewPost.vue
<template>
<span>
<div class="card mb-10">
<h1>body</h1>
{{ data.post_body }}
</div>
</span>
</template>
<script>
export default {
props: {
data: {},
},
created() {
console.log(this.data);
},
};
index as a key may work fine.
<template>
<section>
//more codes here
<ViewPost v-for="(data,index) in postData" :key="index" :data="data" />
</section>
</template>
<script>
import { mapState } from 'vuex';
export default {
components: {ViewPost},
computed: {
...mapState({
postData: state => state.post.datas,
}),
}
//more codes here
};
</script>
ViewPost.vue
<template>
<span>
<div class="card mb-10">
<h1>body</h1>
{{ data.post_body || data.body }}
</div>
</span>
</template>
<script>
export default {
props: ['data']
created() {
console.log(this.data);
},
};

push is not a function vuejs

I'm making an app with VueJS and Laravel. I'm getting an error, the push is not a function when I clicked an add to cart button. Everything is working here fine but methods addToCart gives error push is not a function. when I first click add to cart button it gives that error and once I refresh the page I can see a product in cart and again if click adds to cart button this time error is not seen, works perfectly. when cart[] is empty it gives error push is not a function, but when cart[] has at least one element I don't get that error.
Any help would be greatly appreciated.
productlist.vue
<template>
<div class="col-md-7">
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{ product.name }}</h5>
<p class="card-text">{{ product.price }}
</p>
<button class="btn btn-primary" #click="addProductToCart(product)">Add to cart</button>
</div>
</div>
</div>
</template>
<script type="text/javascript">
export default{
props:['product'],
data(){
return{
}
},
methods:{
addProductToCart(product){
axios.post('/products/create',{
product : product
}).then((response)=>{
console.log(response)
this.$emit('addedToCart',product)
});
}
}
}
</script>
cart.vue
<template>
<div class="col-md-4">
<li v-for="(item,index) in cart">
{{ item.name }}-{{ item.price }}
<button #click="removeitem(index)">Remove</button>
</li>
</div>
</template>
<script type="text/javascript">
export default{
props:['cart'],
}
</script>
Main.vue
<template>
<div>
<div class="col-md-7" v-for="product in products">
<Productlist :product="product" #addedToCart="addedToCart"></Productlist>
</div>
<Cart :cart="cart" ></Cart>
</div>
</template>
<script type="text/javascript">
import Productlist from './Productlist';
import Cart from './Cart';
export default{
data(){
return{
products:[],
cart: [ ]
}
},
mounted() {
//get all products and show in page
axios.get('/products')
.then((response)=>{
this.products = response.data;
});
// get only those products that are added to cart
axios.get('/list')
.then((response)=>{
this.cart= response.data;
console.log(response)
});
},
methods:{
addedToCart(product){
this.cart.push(product)
}
},
components:{Productlist,Cart}
}
</script>
i don't sure if this will resolve your problem but is unnecessary do:
<button class="btn btn-primary" #click="addProductToCart(product)">Add to cart</button>
because you have product as prop of the component, should be #click="addProductToCart" without problem.
and your method should be so:
addProductToCart() {
axios
.post('/products/create', {
product: this.product,
})
.then(response => {
console.log(response);
this.$emit('addedToCart', this.product);
});
}
One thing more, use kebab-case to call the key string when you emit to the parent component:
this.$emit('addedToCart', this.product);
replace it with:
this.$emit('added-to-cart', this.product);
Then in your parent component you have:
<Productlist :product="product" #addedToCart="addedToCart"></Productlist>
replace it with:
<Productlist :product="product" #added-to-cart="addedToCart"></Productlist>
I guess this last things will resolve your problem according the Vue documentation.

Is there a vue.js equivalent of ngTemplateOutlet?

Does vue.js have an equivalent of Angular's *ngTemplateOutlet directive? Let's say I have some components defined like this:
<template>
<div id="independentComponent">
Hello, {{firstName}}!
</div>
</template>
<script>
export default {
name: "independentComponent",
props: ['firstName']
}
</script>
...
<template>
<div id="someChildComponent">
<slot></slot>
<span>Let's get started.</span>
</div>
</template>
<script>
export default {
name: "someChildComponent"
}
</script>
I want to be able to do something like this:
<template>
<div id="parentComponent">
<template #indepdentInstance>
<independentComponent :firstName="firstName" />
</template>
<someChildComponent>
<template #indepdentInstance></template>
</someChildComponent>
</div>
</template>
<script>
export default {
name: "parentComponent",
components: {
someChildComponent,
independentComponent
},
data() {
return {
firstName: "Bob"
}
}
}
</script>
In Angular, I could accomplish this with
<div id="parentComponent">
<someChildComponent>
<ng-container *ngTemplateOutlet="independentInstance"></ng-container>
</someChildComponent>
<ng-template #independentInstance>
<independentComponent [firstName]="firstName"></independentComponent>
</ng-template>
</div>
But it looks like Vue requires the element to be written to the DOM exactly where it is in the template. Is there any way to reference an element inline and use that to pass to another component as a slot?
You cannot reuse templates like ngTemplateOutlet, but can combine idea of $refs, v-pre and runtime template compiling with v-runtime-template to achieve this.
First, create reusable template (<ng-template #independentInstance>):
<div ref="independentInstance" v-show="false">
<template v-pre> <!-- v-pre disable compiling content of template -->
<div> <!-- We need this div, because only one root element allowed in templates -->
<h2>Reusable template</h2>
<input type="text" v-model="testContext.readWriteVar">
<input type="text" v-model="readOnlyVar">
<progress-bar></progress-bar>
</div>
</template>
</div>
Now, you can reuse independentInstance template:
<v-runtime-template
:template="$refs.independentInstance.innerHTML"
v-if="$refs.independentInstance">
</v-runtime-template>
But keep in mind that you cannot modify readOnlyVar from inside independentInstancetemplate - vue will warn you with:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "readOnlyVar"
But you can wrap it in object and it will work:
#Component({
components: {
VRuntimeTemplate
}
})
export default class ObjectList extends Vue {
reusableContext = {
readWriteVar: '...'
};
readOnlyVar = '...';
}
You could try Portal vue written by LinusBorg a core Vue team member.
PortalVue is a set of two components that allow you to render a
component's template (or a part of it) anywhere in the document - even
outside the part controlled by your Vue App!
Sample code:
<template>
<div id="parentComponent">
<portal to="independentInstance">
<!-- This slot content will be rendered wherever the <portal-target>
with name 'independentInstance' is located. -->
<independent-component :first-name="firstName" />
</portal>
<some-child-component>
<portal-target name="independentInstance">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
</some-child-component>
</div>
</template>
There is also a vue-simple-portal written by the same author that is smaller but that mounts the component to end of body element.
My answer from #NekitoSP gave me an idea for a solution. I have implemented the sample below. It worked for me. Perhaps you want to use it as a custom component with props.
keywords: #named #template #vue
<template>
<div class="container">
<div ref="templateRef" v-if="false">write here your template content and add v-if for hide in current place</div>
....some other contents goes here
<p v-html="getTemplate('templateRef')"></p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
Vue.extend({
methods:{
getTemplate(tempRef){
return this.$refs[tempRef].innerHTML
}
}
})
</script>
X-Templates
Use an x-template. Define a script tag inside the index.html file.
The x-template then can be referenced in multiple components within the template definition as #my-template.
Run the snippet for an example.
See the Vue.js doc more information about x-templates.
Vue.component('my-firstname', {
template: '#my-template',
data() {
return {
label: 'First name'
}
}
});
Vue.component('my-lastname', {
template: '#my-template',
data() {
return {
label: 'Last name'
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-firstname></my-firstname>
<my-lastname></my-lastname>
</div>
<script type="text/x-template" id="my-template">
<div>
<label>{{ label }}</label>
<input />
</div>
</script>
Not really sure i understand your problem here, but i'll try to give you something that i will opt to do if i want to add two components in one template.
HeaderSection.vue
<template>
<div id="header_id" :style="'background:'+my_color">
welcome to my blog
</div>
</template>
<script>
export default {
props: ['my_color']
}
</script>
BodySection.vue
<template>
<div id="body_id">
body section here
</div>
</template>
<script>
export default {
}
</script>
home.vue
<template>
<div id="parentComponent">
<header-section :color="my_color" />
<body-section />
</div>
</template>
<script>
import HeaderSection from "./components/HeaderSection.vue"
import BodySection from "./components/BodySection.vue"
export default {
name: "home",
components: {
HeaderSection,
BodySection
},
data() {
return {
my_color: "red"
}
}
}
</script>

Passing props to VueJS component thru v-repeat turns up empty

I'm a bit new to VueJS. I have this Blade template, which is passed the variable $plans which contains [{id:1,name:'1st Plan'},{id:2,name:'2nd Plan'}]:
#extends('layouts.backend')
#section('content')
#if(Auth::user()->can('edit-plans'))
<edit-plans
:workouts="{!! json_encode($plans) !!}"
></edit-plans>
#endif
#endsection
Within edit.vue:
<template>
<workout
v-repeat="workouts"
></workout>
</template>
<script>
export default {
props: [
'workouts'
]
}
</script>
Within workouts.vue:
<template>
<div>{{ id }} :: {{ name }}</div>
</template>
<script>
export default {
props: [
]
}
</script>
I have these templates registered globally:
Vue.component('edit-plans', require('./components/plans/edit.vue'));
Vue.component('workout', require('./components/plans/workout.vue'));
When I compile with yarn, I simply get one line with :: in it but noid or name values showing up on two lines as expected.
when I check the Chrome Vue JS developer extension, I see this in the EditPlans component...
...but nothing in the Workouts component.
In edit.vue declare the v-for (instead of v-repeat) and pass the props. In workout.vue, declare the props so they are available in the template.
edit.vue:
<template>
<workout
v-for="workout in workouts" :id="workout.id" :name="workout.name" :key="workout.id"
></workout>
</template>
<script>
export default {
props: ['workouts']
}
</script>
workout.vue:
<template>
<div>{{ id }} :: {{ name }}</div>
</template>
<script>
export default {
props: ['id', 'name']
}
</script>
Demo:
Vue.component('edit-plans', {
template: "#edit-plans-tpl",
props: ['workouts']
});
Vue.component('workout', {
template: "#workout-tpl",
props: ['id', 'name']
});
new Vue({
el: '#app',
data: {
message: [1,2]
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<template id="edit-plans-tpl">
<div>
<workout
v-for="workout in workouts" :id="workout.id" :name="workout.name" :key="workout.id"
></workout>
</div>
</template>
<template id="workout-tpl">
<div>{{ id }} :: {{ name }}</div>
</template>
<div id="app">
<edit-plans :workouts='[{"id":1,"name":"1st Plan"}, {"id":2,"name":"2nd Plan"}]'>
</edit-plans>
</div>
Update, per comment: What if I just want to pass each full workout into the child instead of each part of the object? In other words, I'd be able to do {{ workout.id }} :: {{ workout.name }}
You can use v-bind for that. Just change the edit.vue, keep workout.vue the same as above.
edit.vue:
<template>
<workout
v-for="workout in workouts" v-bind="workout" :key="workout.id"
></workout>
</template>
<script>
export default {
props: ['workouts']
}
</script>
workout.vue: same as above.
Demo for this:
Vue.component('edit-plans', {
template: "#edit-plans-tpl",
props: ['workouts']
});
Vue.component('workout', {
template: "#workout-tpl",
props: ['id', 'name']
});
new Vue({
el: '#app',
data: {
message: [1,2]
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<template id="edit-plans-tpl">
<div>
<workout
v-for="workout in workouts" v-bind="workout" :id="workout.id"
></workout>
</div>
</template>
<template id="workout-tpl">
<div>{{ id }} :: {{ name }}</div>
</template>
<div id="app">
<edit-plans :workouts='[{"id":1,"name":"1st Plan"}, {"id":2,"name":"2nd Plan"}]'>
</edit-plans>
</div>
Update, per comment: So no matter what, I have to declare every property within the workout object? I can't just do props: ['workout'] within the workout.vue so I can do {{ workout.id }} :: {{ workout.name }}?
You can declare the workout prop in the child and pass it in the parent like :workout="workout":
edit.vue:
<template>
<workout
v-for="workout in workouts" :workout="workout" :key="workout.id"
></workout>
</template>
<script>
export default {
props: ['workouts']
}
</script>
workout.vue:
<template>
<div>{{ workout.id }} :: {{ workout.name }}</div>
</template>
<script>
export default {
props: ['workout']
}
</script>
Demo for this:
Vue.component('edit-plans', {
template: "#edit-plans-tpl",
props: ['workouts']
});
Vue.component('workout', {
template: "#workout-tpl",
props: ['workout']
});
new Vue({
el: '#app',
data: {
message: [1,2]
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.min.js"></script>
<template id="edit-plans-tpl">
<div>
<workout
v-for="workout in workouts" :workout="workout" :key="workout.id"
></workout>
</div>
</template>
<template id="workout-tpl">
<div>{{ workout.id }} :: {{ workout.name }}</div>
</template>
<div id="app">
<edit-plans :workouts='[{"id":1,"name":"1st Plan"}, {"id":2,"name":"2nd Plan"}]'>
</edit-plans>
</div>
Note: :key="workout.id" added to the loop: This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state. (thanks #channasmcs).

Categories