how to use template in vue3 instead of native template? - vue.js

I'm tring to make a SelectBox component
<Options>
<Option
v-for="person in people"
:value="person"
>
{{ person.name }}
</Option>
</Options>
And I want Option to be a dynamic component:
<component :is="props.as || 'li'">
<slot />
</component>
So I can custom content in Option (not only with <li> tag), for example use div or template to render
<Options>
<Option
v-for="person in people"
:value="person"
as="template"
>
<li>
<span>{{ person.name }}</span>
<span>other</span>
</li>
</Option>
</Options>
But if prop.as is 'template', it's rendered in html native template and can't see it in browser
I expect the result:
Vue document show some detail
But it can't work for me to add any director on it, like this
<component
:is="props.as || 'li'"
v-if="true"
>
Anyone can help me?

You probably shouldn't be using the component tag for what you're trying to do.
According to the Vue docs, here are the options of what you can pass for the is prop:
The actual component to render is determined by the is prop.
When is is a string, it could be either an HTML tag name or a component's registered name.
Alternatively, is can also be directly bound to the definition of a component.
You are trying to pass a template instead of a Vue component, which component is not designed to work with. It is unclear exactly what you are trying to do, but you can probably use a slot to accomplish it. Otherwise, to get the component tag working, define a Vue component and pass that in.

Related

Display translation on a component in Vuejs

I want to show a translation on the title of a component.
Here is the HTML code:
<user-card
:totalUser="totalUsers"
color="primary"
icon="UserIcon"
user-title="Total users"
/>
On my user-card component I have this:
<b-card class="text-center">
<b-avatar
:variant="`light-${color}`"
class="mb-1"
size="45"
>
<feather-icon
:icon="icon"
size="21"
/>
</b-avatar>
<div class="truncate">
<h2 class="mb-25 font-weight-bolder">
{{ totalUser}}
</h2>
<span>{{ user-title }}</span>
</div>
</b-card>
And to use translation I have this syntax where I get the translated terms from the JSON file:
{{$t("Total users")}}
How can I implement this on the user-title?
Have a look at this, I have tried to replicate your scenario in code sandbox.
sample app
What you are doing wrong is that $t is a function that accepts variable name which has an actual message in it, first you have to define a variable e.g totalUserTitle: 'Total users' for multiple languages like I did in index.js, and then you can use it as $t(totalUserTitle).
Just use v-bind and pass the expression to your user-card component directly:
<user-card
...
:user-title="$t('Total users')"
/>
You're actually already using this syntax in multiple places, this directive just tells Vue to "dynamically bind one or more attributes, or a component prop to an expression", which is exactly what you're looking for here.
This will evaluate $t('Total users') as an expression and then pass the result to your component as a prop.

VueJS 2 - Re-useable parent component that can accept different children

I have a parent component, SearchComponent:
<template>
<div>
<div class="relative pl-8 pr-10 rounded bg-white border focus-within:bg-white focus-within:ring-1">
<input v-focus #keyup.escape="clearSearch" #keyup="doSearch" v-model="searchTerm"
class="w-full ml-4 h-12 pl-1 text-gray-700 text-lg rounded-full border-0 bg-transparent focus:outline-none placeholder-gray-400"
placeholder="Search..." autocomplete="off">
</div>
<ul class="bg-white mt-4">
<quick-search-item v-for="item in searchResults" :key="item.id" :item-data="item.itemData">
</quick-search-item>
</ul>
</div>
</template>
This is responsible for receiving user input and getting results from an ajax call, handling errors etc. and generating the result list.
What I'd like to do is to make this generic so that instead of having a quick-search-item child component I can pass in different types of child component (like car-search-item, person-search-item etc.) depending on the context of where the user is in the app and what they're searching for
I've read a number of tutorials and I couldn't find quite what I'm trying to do. This may mean I'm approaching this in the wrong way - but if anyone could point me in the right direction, or has a better approach, I'd be very grateful.
Thanks,
Lenny.
I would try to make use of the <slot> element. Check out the documentation here
<parent-component>
<slot></slot>
</parent-component>
Hope this can put you on the right path.
Schalk Pretorius was quite right: slots are the answer to this, specifically scoped slots. I found the Vue docs a little confusing as it refers to getting data from the child component and I wanted to do it the other way around, so as an aide memoire for myself and to help anyone else here's what I did:
In my parent component I defined the slot like this:
<slot name="results" v-bind:items="searchResults">
</slot>
The v-bind binds searchResults (a data item in the parent component) to the value 'items'. 'items' then becomes available in the slot.
In my child component I have a simple property setup called items:
props: {
items: {type: Array},
},
Then to hook it all together in my Blade file I did this:
<search-component endpoint="{{ route('quick_search.index') }}">
<template v-slot:results="props">
<food-results :items="props.items">
</food-results>
</template>
</search-component>
This creates the search-component. Inside that -as I'm using named slots - we need a template and use v-slot to tell it which slot to use (results), then the ="props" exposes all the properties we've defined on that slot (in this case just one, 'items').
Inside the template we put our child component and then we can bind items to props.items which will be the searchResults in our parent component.
I'm happy to have this working and I can now create lots of different results components while reusing the search component - and at least I learnt something today!
Cheers,
Lenny.

Validate vue-select with vee-validate

I'm new to VueJS.
I'm trying to validate vue-select using vee-validate.
I've tried to validate it manually but of course its not a good approach.
So, I tried to use vuelidate but couldn't get the desired result.
Now i'm trying to use vee-validate. Validation works fine as desired
but the issue is v-model.
I created a global variable product, to calculate the length of array, and passed it in v-model. So that when Its empty product's value will be zero and i can return desired result from vee-validation.
Here's the .vue html part.
<ValidationObserver>
<form #submit.prevent="add">
<div class="row row-xs mx-0">
<label class="col-sm-4 form-control-label">
<span class="tx-danger">*</span> Add product(s):
</label>
<div class="col-sm-8 mg-t-10 mg-sm-t-0">
<ValidationProvider rules="required" v-slot="{ errors }">
<v-select
name="product"
placeholder="Add product(s)"
:options="availableProducts" <-- here is the options array
:reduce="name => name"
label="name"
#input="setSelected"
v-model="product" <-- this calculates length and pass it to vee **extends**
>
</v-select>
<div v-for="error in errors" :key="error"> {{ error }} </div>
</ValidationProvider>
</div>
<!-- col-8 -->
</div>
</form>
</ValidationObserver>
Here's validation.js file
import { extend } from 'vee-validate';
extend('required', value => {
console.log(value);
return value > 0;
});
I don't want this product value there. I know its not a good approach as well. I can't pass whole array to v-model because then I can't push options in it. I can't pass a single option to v-model as well then I won't get desired result.
All I want to validate v-select when options array is empty. Any suggestions?
Veevalidate doesn't validate directly on select elements. This is my workaround.
You should create a v-field "hidden" input and a visible select v-model element. The veevalidate will take place on the v-field.
Here is an example.
<v-field type="text" class="form-control disabled" name="expirationMonth" v-model="expirationMonth" :rules="isRequired" style="display:none;"></v-field>
<select v-model="expirationMonthUI" class="form-control" #click="synchExpirationMonthUI">
<option value="January">January</option>
<option value="February">February</option>
<option value="March">March</option>
<option value="April">April</option>
<option value="May">May</option>
<option value="June">June</option>
<option value="July">July</option>
<option value="August">August</option>
<option value="September">September</option>
<option value="October">October</option>
<option value="November">November</option>
<option value="December">December</option>
</select>
<error-message name="expirationMonth"></error-message>
Then add this to your methods to synch both together.
synchExpirationMonthUI() {
this.expirationMonth = this.expirationMonthUI;
}
I have found a way of doing this, with the Rendering Complex Fields with Scoped Slots from the Vee-Validate documentation. And using the bindings from Vue Select, it looks something like this:
<Field name="supportType" v-slot="{ field }" v-model="supportType">
<v-select :options="mediaTypes" label="name" :reduce="mediaType => mediaType.id" v-bind="field">
</v-select>
</Field>
As you can see, I am using here the name, v-slot and v-model for the Field from Vee-Validate, as normal. But the v-slot is very important as it carries the information from Vue Select to Vee-Validate, or at least I think so.
On the other hand I use the options, label, reduce and v-bind from Vue Select, these I use to handle the information. So with the :options I select my dataset, with label I tell Vue Select which label to select and show from the dataset, with :reduce I tell Vue Select what will be the value of the select tag and finally use v-bind to bind the value of the select to the Vee-Validate field. So the information used on the :reduce property will be displayed on the v-model="supportType".
I tested it with a button and it worked. And I liked this way so it is not that messy and I can use the validation and other things as usual.
Hope this helps anyone.
PD: I am using Vue 3, and the latest package of both Vee-Validate and Vue Select, as of today.

Is it possible to find a Vue component based on its "el"?

Suppose I have this component:
<b-select id="selectfield" :src="['a', 'b']"></b-select>
Which renders into:
<select id="selectfield">
<option value="a">a</option>
<option value="b">b</option>
</select>
Can I "find" the instance for my b-select component based on its "el" (which in this case would be its id)?
I don't know if it's possible with the "public" API but you can get the Vue instance from the __vue__ DOM property of any element that is a root mount for a component.
document.getElementById('selectfield').__vue__
As to whether or not you should use it.. Vue's author says the Vue devtools rely on it, so it's unlikely to change. See https://github.com/vuejs/vue/issues/5621

Passing Form Data Between Components & Vue Routing

I am trying to create a step-by-step form using Components & Routing. If there is a better or easier approach to do this, please feel free to suggest, since I am new to Vue.js.
I have a and 3 templates.
<template id="step-1">
<h1>Welcome to Form</h1>
</template>
<template id="step-2">
<label>Name:</label>
<input type="text" name="name" v-model="name" />
<br />
<label>Email:</label>
<input type="email" name="email" v-model="email" />
</template>
<template id="step-3">
<p>Review:</p>
<!-- Display Step 2 Form Values -->
{{ name }}
{{ email }}
<button>Submit</button>
</template>
What I want to do is, display the input values on #step-3, and on a button click, submit the form via an ajax call.
You can view the Fiddle from here: https://jsfiddle.net/j7mwc9wk/
One way to do this is for all three components use the same data object. For the purposes of this bit of code it can be a simple javascript object. A bit more sophisticated approach is to use Vuex an official Vue data store.
You could also have these three components have the same parent in which case name and email would be properties of parent data method and therefore accessible to all children. I don't know how this would work with Vue router but it should be fine.