v-calendar datepicker span wrapper issue - javascript

I am using the v-calendar datepicker and it works well, however when I use a template to create custom html, it wraps the output in a span. Is there anyway to get rid of this span as it is hard to style as no class is put onto it - and it also makes the html invalid as I have divs in the template
Run the below snippet and inspect the date text - you'll see the span around the template content
new Vue({
el: "#app",
data() {
return {
singleDate: null,
dateMasks: {
input: 'DD/MM/YYYY',
},
};
},
});
#import url 'https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.min.css';
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.umd.min.js"></script>
<div id='app'>
<v-date-picker v-model="singleDate" color="orange" :masks="dateMasks" :disabled-dates="{ weekdays: [1, 7] }">
<template #default="{ inputValue, togglePopover }">
<div class="popout__column">
<div class="form__row filter__date-range-row">
<label class="form__label" #click="togglePopover">
<span class="form__label-text">Date from</span>
<span class="form__label-date material-icons--after"><span class="form__label-value">{{ inputValue }}</span></span>
</label>
</div>
</div>
</template>
</v-date-picker>
</div>

Related

BootstrapVue form tags input v-model

I have a b-form-tag like this:
<b-form-group label="Search" label-for="search">
<b-form-tags
id="search"
v-model="search"
separator=","
remove-on-delete
tag-variant="primary"
tag-pills
placeholder="Search here..."
></b-form-tags>
</b-form-group>
And in the data section:
data() {
return {
search: []
}
}
In the search variable only tags will be stored, buy I also need to access to the current typing text of the input and bind it to one variable in data. I know it must be done using inputAttrs or inputHandlers but I don't know how?
You can use custom inputs. This will force you to recreate some functionality for clearing the input and adding tags. Here is a demo where I've simplified the docs' advanced example. It initializes the tag value, reimplements adding tags with Enter, and shows setting v-model programatically:
new Vue({
el: "#app",
data() {
return {
newTag: 'starting text',
value: []
}
},
methods: {
resetInputValue() {
this.newTag = ''
},
setTag(text) {
this.newTag = text;
}
}
});
<div id="app">
<b-form-tags
v-model="value"
#input="resetInputValue()"
tag-variant="success"
class="mb-2 mt-2"
placeholder="Enter a new tag value and click Add"
>
<template v-slot="{tags, inputId, placeholder, addTag, removeTag }">
<b-input-group>
<!-- Always bind the id to the input so that it can be focused when needed -->
<b-form-input
v-model="newTag"
:id="inputId"
:placeholder="placeholder"
#keydown.enter="addTag(newTag)"
></b-form-input>
<b-input-group-append>
<b-button #click="addTag(newTag)" variant="primary">Add</b-button>
</b-input-group-append>
</b-input-group>
<div v-if="tags.length" class="d-inline-block" style="font-size: 1.5rem;">
<b-form-tag
v-for="tag in tags"
#remove="removeTag(tag)"
:key="tag"
:title="tag"
class="mr-1"
>{{ tag }}</b-form-tag>
</div>
<b-form-text v-else>
There are no tags specified. Add a new tag above.
</b-form-text>
</template>
</b-form-tags>
<div>Text from `v-model`: {{ newTag }}</div>
<div><button #click="setTag('programatically')">Set v-model programatically</button></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js"></script>
<script src="https://unpkg.com/vue#2.6.12/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.19.0/bootstrap-vue.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.19.0/bootstrap-vue.min.css" rel="stylesheet" />

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 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.

Vue.js dynamic two way data binding between parent and child components

I'm trying to use a combination of v-for and v-model to get a two-way data bind for some input forms. I want to dynamically create child components. Currently, I don't see the child component update the parent's data object.
My template looks like this
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range in ranges"
:box-index="$index"
v-bind:data.sync="range">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input v-model="data" type="text">
</div>
</template>
and the js this
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
data: {}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
I've also made a js fiddle as well https://jsfiddle.net/8mdso9fj/96/
Note: the use of an array complicates things: you cannot modify an alias (the v-for variable).
One approach that isn't mentioned often is to catch the native input event as it bubbles up to the component. This can be a little simpler than having to propagate Vue events up the chain, as long as you know there's an element issuing a native input or change event somewhere in your component. I'm using change for this example, so you won't see it happen until you leave the field. Due to the array issue, I have to use splice to have Vue notice the change to an element.
Vue.component('time-range', {
template: '#time-range',
props: ['data']
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data="range"
:key="index"
#change.native="(event) => ranges.splice(index, 1, event.target.value)">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text">
</div>
</template>
To use the .sync modifier, the child component has to emit an update:variablename event that the parent will catch and do its magic. In this case, variablename is data. You still have to use the array-subscripting notation, because you still can't modify a v-for alias variable, but Vue is smart about .sync on the array element, so there's no messy splice.
Vue.component('time-range', {
template: '#time-range',
props: ['data'],
methods: {
emitUpdate(event) {
this.$emit('update:data', event.target.value);
}
}
})
new Vue({
el: '#app',
data: {
ranges: [],
},
methods: {
addRange: function () {
this.ranges.push('')
},
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div class="container" id="app">
<div class="row">
Parent Val
{{ ranges }}
</div>
<div class="row">
<button
v-on:click="addRange"
type="button"
class="btn btn-outline-secondary">Add time-range
</button>
</div>
<time-range
v-for="range, index in ranges"
:data.sync="ranges[index]"
:key="index">
</time-range>
</div>
<template id="time-range">
<div class="row">
<input :value="data" type="text" #change="emitUpdate">
</div>
</template>

Declare reactive data properties in vue.js?

I have a very basic vue.js app:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
}
})
The following html works fine:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<span v-for="msg in messages">${msg}</span>
</div>
</div>
</div>
However very similar html block does not:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea id="chat_area" readonly="" rows="20">
<span v-for="msg in messages">${msg}</span>
</textarea>
</div>
</div>
</div>
[Vue warn]: Property or method "msg" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
I'm using Vue v2.3.3. What could be the problem?
As documentation says, interpolation in textareas won't work, so you need to use v-model.
If the only thing you want to do is to display html inside textarea, you could in theory use an ugly workaround by wrapping your array fields inside a function and set that function as textarea v-model:
var app = new Vue({
el: '#app',
delimiters: ['${', '}'],
data: {
messages: [
'Hello from Vue!',
'Other line...'
]
},
computed:{
multiLineMessages: function(){
var result = "";
for(var message of this.messages){
result += '<span>' + message + '</span>'
}
return result;
}
}
});
template part:
<div class="container" id="app">
<div class="row" style="">
<div class="col-md-8 offset-md-2">
<textarea v-model="multiLineMessages" placeholder="add multiple lines">
</textarea>
</div>
</div>
</div>
It's more like a proof that it's doable but I highly don't recommend using it anywhere, as html shouldn't be generated this way (especially larger chunks of it).
jsFiddle preview

Categories