How to have two v-model directive inside ValidationProvider in VeeValidate? - html

I want to create a custom Date Picker component with Vue version 2 and VeeValidate. I am using vuetify and v-menu component from there: https://vuetifyjs.com/en/components/menus/ . The problem is that ValidationProvider which is responsible for validating fields accepts only one v-model inside of it and I have two: one for triggering v-menu and second for gathering value to v-text-field from v-date-picker. When I remove this v-model from v-menu validation works but I cannot close menu after I choose some values in date-picker.
<ValidationProvider v-slot="{ validate, errors }">
<label>...</label>
<v-menu v-model="menu">
<template v-slot:activator="{ on, attrs }">
<v-text-field
v-bind="attrs"
:value="formatDate"
readonly
v-on="on"
#input="$emit('input', $event) || validate($event)"
/>
</template>
<v-date-picker
:value="value"
no-title
#input="$emit('input', $event) || validate($event)"
#change="menu = false"
/>
</v-menu>
<span
v-if="errors.length > 0"
>{{ errors[0] }}</span>
</ValidationProvider>

Related

Quasar, Input Clearable, Show "clear" button only when in focus

I'm using q-input from Quasar framework. I made the "clear" button from this website -> https://quasar.dev/vue-components/input#clearable
How to make the "clear" button is hidden when there is no focus in the "input" field.
And make the "clear" button is displayed when you focus in the "input" field. (using Quasar framework)
The "clear" button: is a small icon of a cross that shows in the input field.
HTML
<div id="q-app" style="min-height: 100vh;">
<div class="q-pa-md">
<div class="q-gutter-y-md column" style="max-width: 300px">
<!-- equivalent -->
<q-input color="orange" filled v-model="text" label="Label">
<template v-if="text" v-slot:append>
<q-icon name="cancel" #click.stop.prevent="text = null" class="cursor-pointer"></q-icon>
</template>
</q-input>
</div>
<div class="q-gutter-y-md column" style="max-width: 300px">
<!-- equivalent -->
<q-input color="orange" filled v-model="text" label="Label">
<template v-if="text" v-slot:append>
<q-icon name="cancel" #click.stop.prevent="text = null" class="cursor-pointer"></q-icon>
</template>
</q-input>
</div>
<div class="q-gutter-y-md column" style="max-width: 300px">
<!-- equivalent -->
<q-input color="orange" filled v-model="text" label="Label">
<template v-if="text" v-slot:append>
<q-icon name="cancel" #click.stop.prevent="text = null" class="cursor-pointer"></q-icon>
</template>
</q-input>
</div>
</div>
</div>
JS + Quasar
const { ref } = Vue
const app = Vue.createApp({
setup () {
return {
text: ref('Some text')
}
}
})
app.use(Quasar, { config: {} })
app.mount('#q-app')
Now the input fields look like this (The "clear" button is displayed regardless of focus):
I want it to look like this (The "clear" button only in the field in which there is focus)
How can this be done ?
Please refer following code.
<q-input color="orange" filled v-model="text" label="Label" #focus="focus=true" #blur="focus=false">
<template v-slot:append>
<q-icon name="cancel" v-if="text && focus" #click.stop.prevent="text = null"
class="cursor-pointer"></q-icon>
</template>
</q-input>
https://codepen.io/Pratik__007/pen/RwBoLQb

Passing slot into slot in Vue.js

I'm trying to pass slot into the slot but child component which requires passed down slot doesn't see it.
In the child (TheTable) I have table component from Core UI for Vue (CDataTable) which requires certain slots which I want to pass from a parent:
<CDataTable
class="overflow-auto"
:items="this.items"
:fields="fieldData"
:sorter="{ external: true, resetable: true }"
:sorter-value="this.sorterValue"
:table-filter="{ external: true, lazy: false, placeholder: 'Enter here', label: 'Search:'}"
:responsive="false"
:loading="this.loading"
:hover="true"
:items-per-page-select="true"
:items-per-page="this.perPage"
#pagination-change="paginationChanged"
#update:sorter-value="sorterUpdated"
#update:table-filter-value="filterUpdated"
>
<slot name="requiredTableFields"></slot>
</CDataTable>
In the parent component I have:
<TheTable
v-bind:fields="fields"
v-bind:endpoint-url="endpointUrl"
>
<template slot="requiredTableFields">
<td slot="name" slot-scope="{item}">
<div>{{ item['attributes.email'] }}</div>
<div class="small text-muted">
{{ item['attributes.firstname'] }} {{ item['attributes.lastname'] }}
</div>
</td>
<td slot="registered" slot-scope="{item}">
<template v-if="item['attributes.created_at']">{{ item['attributes.created_at'] }}</template>
<template v-else>Not setup yet</template>
</td>
</template>
</TheTable>
Is there any way to make it work?
Cheers,
Casper
Merging the underlying slots into a single requiredTableFields slot isn't going to work because there's no (easy) way to break the child slots back out once they've been merged.
Instead you can just keep the slots separate:
<TheTable ...>
<template v-slot:name="{ item }">
<td>
...
</td>
</template >
<template v-slot:registered="{ item }">
<td>
...
</td>
</template>
</TheTable>
This is passing two scoped slots, name and registered, into TheTable.
Assuming you don't want to hard-code the slot names into TheTable you'd then need to iterate over the $scopedSlots to include them dynamically.
<CDataTable ...>
<template v-for="(x, slotName) in $scopedSlots" v-slot:[slotName]="context">
<slot :name="slotName" v-bind="context" />
</template>
</CDataTable>
Some notes on this:
x is not used, we just need to loop over the slot names.
The 'data' associated with the scoped slot is referred to as context and is just passed on.
If the slots weren't scoped slots it'd be slightly different. We'd iterate over $slots instead and remove all the parts that refer to context.
There is a : at the start of the :name attribute as we want to pass a dynamic name. It is an unfortunate coincidence that one of the slots in the original question is also called name, potentially leading to some confusion here.
The v-bind="context" part is analogous to the JavaScript spread operator, if that makes it any clearer. Think of it as attributes = { name: slotName, ...context }.
Below is a complete example illustrating this technique outlined above. It doesn't use CDataTable but the core principle for passing on the slots is exactly the same.
const Comp2 = {
template: `
<div>
<h4>Left</h4>
<div><slot name="top" item="Red" /></div>
<h4>Right</h4>
<div><slot name="bottom" item="Green" /></div>
</div>
`
}
const Comp1 = {
template: `
<div>
<comp2>
<template v-for="(x, slotName) in $scopedSlots" v-slot:[slotName]="context">
<slot :name="slotName" v-bind="context" />
</template>
</comp2>
</div>
`,
components: {
Comp2
}
}
new Vue({
el: '#app',
components: {
Comp1
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<comp1>
<template v-slot:top="{ item }">
<button>Slot 1 - {{ item }}</button>
</template>
<template v-slot:bottom="{ item }">
<button>Slot 2 - {{ item }}</button>
</template>
</comp1>
</div>

Reference ngFor value in component

I have a nested ngFor statement. I need to retrieve the value of my first ngFor on button click.
I have tried the following:
use template reference variable
use attribute binding
use Input decorator
This is my code:
<mat-expansion-panel *ngFor="let item of Datasource;">
<mat-expansion-panel-header style="display:flex" class="mat-row">
{{item.Header}}
</mat-expansion-panel-header>
<mat-selection-list [(ngModel)]="selectedOptions">
<mat-list-option *ngFor="let line of item.match; let i= index;" [value]="line">
<div class="container-name">
<div class="col-6">{{i}} - {line.user.Name }} vs {{ line.user.Address }}</div>
</mat-list-option>
</mat-selection-list>
<div style="text-align:center; padding: 20px">
<button mat-raised-button color="primary" (click)="submit()" type="submit">Add</button>
</div>
</mat-expansion-panel>
Can this be achieved?
Well, you need to clone that object properties first. As that object is linked to the template, when you manipulate it, it is manipulated on template too. You can use var obj = Object.assign({}, actual obj) and then do the manipulation on obj instead of actual one. Then it will not get affected in template. Hope it helps.

How can I repeat a piece of HTML multiple times without ngFor and without another #Component?

I want to repeat a piece of HTML, multiple times in my template.
But I want it to be repeated at different places on my page. This means that ngFor is not the solution as the pieces would be repeated directly one after the other.
A 'working solution' would be to define a specific #Component for my repeated HTML, and do something like that :
<p>Whatever html</p>
<my-repeated-html></my-repeated-html>
<h4>Whatever</h4>
<my-repeated-html></my-repeated-html>
But I find it overkill to create a dedicated component for doing something like that, it has no functional meaning and is only required by the HTML structure I want to set up.
Is there really nothing in ng2 template engine to allow me to define an "inner template" and use it wherever I need it in the current template?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
The recently added ngTemplateOutlet might be what you want
<template [ngTemplateOutlet]="templateRefExpression" [ngOutletContext]="objectExpression"></template>
It can currently be used like
<template #templateRef>
<pre>{{self | json }}</pre>
</template>
<template [ngTemplateOutlet]="templateRef"></template>
A template can also be passed to a child component to be rendered there
#Component({
selector: 'some-child',
providers: [],
template: `
<div>
<h2>Child</h2>
<template [ngTemplateOutlet]="template" ></template>
<template [ngTemplateOutlet]="template" ></template>
</div>
`,
directives: []
})
export class Child {
#ContentChild(TemplateRef) template:TemplateRef;
}
to be used like
<some-child>
<template>
<pre>{{self | json }}</pre>
</template>
</some-child>
stackblitz example
Another Plunker example
that uses data passed as
<template [ngTemplateOutlet]="..." [ngOutletContext]="templateData"
This way ngOutletContext can be used in the template like
<template let-image="image">
{{image}}
where image is a property of templateData
If $implicit is used
<template [ngTemplateOutlet]="..." [ngOutletContext]="{$implicit: templateData}"
the ngOutletContext can be used in the template like
<template let-item>
{{item}}
<campaign-channels-list (onItemSelected)="_onItemSelected($event)" [customTemplate]="customTemplate" (onDragComplete)="_onDragComplete($event)" [items]="m_blockList"></campaign-channels-list>
<template #customTemplate let-item>
<a href="#" [attr.data-block_id]="item.blockID">
<i class="fa {{item.blockFontAwesome}}"></i>
<span>{{item.blockName}}</span>
<i class="dragch fa fa-arrows-v"></i>
<span class="lengthTimer hidden-xs">
{{item.length | FormatSecondsPipe}}
</span>
</a>
</template>
and in rx component:
<div class="sortableList">
<li (click)="_onItemSelected(item, $event, i)" *ngFor="let item of m_items; let i = index" class="listItems list-group-item" [ngClass]="{'selectedItem': m_selectedIdx == i}">
<template [ngTemplateOutlet]="customTemplate" [ngOutletContext]="{$implicit: item}">
</template>
</li>
</div>
pay attention to:
[ngOutletContext]="{$implicit: item}"
as well as
<template #customTemplate let-item>

Use ng-model to add class to all previous childrens Angular JS

Here is my html code:
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="item($event, key)">
<div class="bubble></div>
<p>
<span> {{ description }}</span>
</p>
</div>
This is the list of items. When we click on the item in the list - all previous elements are set as active (add class).
Here is how it's done:
$scope.item = function(event, key) {
var current;
if ( $(event.target).hasClass('bubble')){
current = $(event.target).closest('#'+ Key);
changeItem(current);
}
function changeItem(current){
$(current).addClass('active');
$(current).prevAll().addClass('active');
$(current).nextAll().removeClass('active');
}
};
Is it possible to use ng-model or something else to set the active value by default form json file? Mean, in json - we have item 3 - marked as active, so how could I add this value to the $scope.item as current? or probably use ng-model?
I have not tried it, but something like this should work.Assuming that the class has to be applied to ng-repeat div. Change your ng-repeat div to:
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="markSelected($index)" ng-class="{'active':selectedIndex<$index}">
</div>
The ng-click call a method markSelected($index) on the controller that sets the currently selected item index. The ng-class uses the current index ($index) and the selectedIndex to determine what class to apply.
The final task is to implement the function which looks like:
$scope.markSelected=function(index) {
$scope.selectedIndex=index;
}
You should stop using jquery and start to think in a more angular way.
There is a directive ng-class that is used to add or remove classes
You can find more information here : https://docs.angularjs.org/api/ng/directive/ngClass
<div ng-repeat="(key, a) in items" data-id="{{ Id }}" class="item" id="{{Key}}" ng-click="item(key)">
<div ng-class="{active : a.active, inactive : a.inactive}"></div>
<p>
<span> {{ description }}</span>
</p>
</div>
$scope.item = function(key){
$scope.items[key].active = true;
$scope.items[key].inactive = false;
}