Vue.js - Bootstrap Vue Popover interpreting HTML - javascript

I would like a popover to display one or multiple hyperlinks when clicked on. I am able to get other Bootstrap elements to interpret HTML by using the v-html argument although it is not working in this case.
Here's my code:
<template>
<div>
<b-button
:id="popover"
size="sm"
>
Button
</b-button>
<b-popover
:target="popover"
triggers="focus"
v-html="actions"
>
{{ actions }}
</b-popover>
</div>
</template>
<script>
export default {
computed: {
actions() {
return [
`Google<br>`,
`Youtube<br>`
].join('')
}
}
}
</script>

Remove the binding sign : on target and id then change them to popover1 then create a nested div with v-html directive which has as value actions :
<template>
<div>
<b-button
id="popover1"
size="sm"
>
Button
</b-button>
<b-popover
target="popover1"
triggers="focus"
>
<div v-html="actions"></div>
</b-popover>
</div>
</template>
<script>
export default {
computed: {
actions() {
return [
`Google<br>`,
`Youtube<br>`
].join('')
}
}
}
</script>
If the id and target attributes are bound to a property you should keep the binding sign.

Related

Change attribute of this button inside v-for loop in Vue.js

I'm using a Vue.js v-for loop to output a table of information, each with their own action buttons like so using Element UI library.
<template>
<div class="card">
<div class="card-header">
<tool-bar></tool-bar>
</div>
<div class="card-body">
<el-table :data="orders" v-loading="loading" current-row-key="index" empty-text="No products found">
<el-table-column type="expand">
<template slot-scope="props">
<el-tabs>
<el-tab-pane label="Order Items">
<ul>
<li v-for="(product, index) in orders[props.$index].products" :key="index">{{ product.name }}</li>
</ul>
</el-tab-pane>
<el-tab-pane label="Customer Details">Customer Details
<!-- <p v-for="(customer, index) in orders[props.$index].order.billing_address" :key="index">{{ customer.index }}</p> -->
</el-tab-pane>
</el-tabs>
</template>
</el-table-column>
<el-table-column label="Order ID" prop="order.id"></el-table-column>
<el-table-column label="Customer" prop="order.billing_address.first_name"></el-table-column>
<el-table-column label="Due Time" prop="order.due_time"></el-table-column>
<el-table-column
align="right">
<template slot="header" slot-scope="scope">
<el-input
v-model="search"
placeholder="Type to search"/>
</template>
<template slot-scope="scope">
<el-button size="mini" type="success" :disabled="orders[scope.$index].checked_in" :loading="false" :ref="'btn-' + scope.$index" #click="checkInOrder(scope.$index, scope.row)">{{ (orders[scope.$index].checked_in) ? 'Checked in' : 'Check in' }}</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
</template>
<script>
import Toolbar from '../components/Toolbar.vue';
export default {
components: {
'tool-bar': Toolbar
},
mounted() {
this.fetchOrders();
},
data () {
return {
search: '',
}
},
computed: {
loading() {
return this.$store.getters.loading;
},
orders() {
return this.$store.getters.orders;
}
},
methods: {
fetchOrders() {
this.$store.dispatch('fetchOrders')
.then(res => {
})
.catch(err => {
})
},
checkInOrder(index, row) {
this.$refs['btn-' + index].loading = true;
axios.post('/order/update', {
id: row.order.id,
status: 'bakery',
products: row.products
})
.then(res => {
this.$refs['btn-' + index].loading = false;
})
.catch(err => {
this.$refs['btn-' + index].loading = false;
})
}
}
}
</script>
When I click one of the buttons, I want to be able to set the :loading attribute of the clicked button to true as well as change the button label to Loading... until a given Ajax request is completed.
I used the :ref attribute on the button and, when the button is clicked, I alter the attribute as follows:
checkInOrder(index, row) {
this.$refs['btn-' + index].loading = true;
}
This seems to work fine, but the console is throwing a warning, so I want to find out the way to achieve this.
The warning I get is this:
I believe it's prompting you to link :loading to a property, which you can set, rather than mutating the element prop directly. Thus, when you call checkInOrder() you could just update the boolean property that :loading is linked to.
I believe this question is relevant and will help you fix this issue.

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>

How to run component's method via HTML in Vue2

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;
}
}
}

Dynamically add a child component in Vue JS

I need some help in Vue JS and Laravel with adding a child vue component.
I have a parent component called "wrapper" and some child components called like "show-1", "show-2", "show-3" ... etc.
Parent component:
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<component
is="view"
></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
An exmaple child component like "show-1":
<template>
<div> show-1 </div>
</template>
This code below is in blade for rendering wrapper component with a dynamic child component name:
<wrapper
title="Example"
view="show-1"
></wrapper>
This code is not working but if i change the parent view data "show-1" instead of empty, it works. why ?
When I change the view prop, child vue component should be changed too. How could I do this ?
I want to pass the view attribute to parent component dynamically.
You can use :is attribute. You can read more about it here:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the same mount point and dynamically switch between
multiple components using the reserved element and
dynamically bind to its is attribute....
<template>
<div class="card border-light">
<div class="card-header">
<h5 class="title">{{ title }}</h5>
</div>
<div class="card-body">
<!-- make sure to use : -->
<component v-if="view" :is="view"></component >
</div>
<div class="card-footer"></div>
</div>
</template>
<script>
export default {
props : ['title'],
data() {
return {
view : ''
}
}
}
</script>
#Eduardo has the right answer. To add to it, import your components into the parent and switch between them via a data property:
...
<component :is="current"></component >
...
data: {
current: 'show1'
},
components: {
show1: Show1Component,
show2: Show2Component,
show3: Show3Component
}
The key is to bind the component using the name of the dynamic component.
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components

How to use BootstrapVue - Modal inside v-for loop?

I am trying to use BootstrapVue - Modal inside v-for loop and the only problem is with modal directive ( v-b-modal.modal1 ) on a modal button. modal1 should be the name of modal id and since I am using a loop I am passing index in modal for example modal + index, but I don't know how to change buttons directive to be v-b-modal-modal1 ... v-b-modal-modal5.
This is modal component
<template>
<div>
//This v-b-modal.modal1 should be same as modalId
<b-btn v-b-modal.modal1>Banka: {{ data.offer.client_name }}</b-btn>
<!-- Modal Component -->
<b-modal :id="modalId" title="Oferta">
<p clas="my-4">Kampanja: {{ data.offer.campaign_name }}</p>
<p clas="my-4">Norma e interesit: {{ data.offer.interest_rate_nominal }}</p>
<p clas="my-4">*Shpenzimet Administrative: {{ data.offer.admin_fee }}</p>
<p clas="my-4">Kësti Mujor: {{ data.offer.monthly_payment }}</p>
</b-modal>
</div>
</template>
<script>
export default {
props: ['data'],
computed:{
modalId(){
return 'modal' + this.data.i;
}
}
}
</script>
Here is a method that uses modal
<tbody>
<tr v-for="(offer, i) in offers">
<td>
<app-show-details :data="{offer, i}"></app-show-details>
</td>
</tr>
</tbody>
BootStrapVue provides more than one pattern to achieve modal, so you don't have to insist on directive modifiers, I think using directive value fits your requirement here perfectly. Check sample code below.
new Vue({
el: '#app',
methods: {
modalId(i) {
return 'modal' + i;
}
}
});
// or check jsfiddle here: https://jsfiddle.net/5sv805ho/
<script src="https://unpkg.com/vue#2.5.2/dist/vue.min.js"></script>
<link href="https://unpkg.com/bootstrap#next/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app">
<div v-for="i in 3">
<b-btn v-b-modal="modalId(i)">Launch demo modal</b-btn>
<b-modal :id="'modal' + i" title="Bootstrap-Vue">
<p clas="my-4">Hello from modal {{ i }}!</p>
</b-modal>
</div>
</div>
By the way, you can also use show() and hide() component methods.

Categories