Related
I have found an excellent example for creating URL filters. I would like the results to be in a separate component though. In this example, all the code is in one file. Can someone help me to move the results to a separate component and also ensure the URL filters work when the different selections are made?
<template>
<div id="app">
<div id="app">
<h2>Items</h2>
<p>
<input type="search" placeholder="Filter by name" v-model="filter" />
<input
type="checkbox"
value="person"
id="personType"
v-model="typeFilter"
/>
<label for="personType">Only People</label>
<input type="checkbox" value="cat" id="catType" v-model="typeFilter" />
<label for="catType">Only Cats</label>
<input type="checkbox" value="dog" id="dogType" v-model="typeFilter" />
<label for="dogType">Only Dogs</label>
</p>
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
</div>
</div>
<!-- <Result v-for="item in items" :key="item" /> -->
</template>
<script>
//import Result from "#/components/Result.vue";
export default {
components: {
//Result,
},
data: function () {
return {
// allItems: ITEMS,
filter: "",
typeFilter: [],
ITEMS: [
{ name: "Ray", type: "person" },
{ name: "Lindy", type: "person" },
{ name: "Jacob", type: "person" },
{ name: "Lynn", type: "person" },
{ name: "Noah", type: "person" },
{ name: "Jane", type: "person" },
{ name: "Maisie", type: "person" },
{ name: "Carol", type: "person" },
{ name: "Ashton", type: "person" },
{ name: "Weston", type: "person" },
{ name: "Sammy", type: "cat" },
{ name: "Aleese", type: "cat" },
{ name: "Luna", type: "cat" },
{ name: "Pig", type: "cat" },
{ name: "Cayenne", type: "dog" },
],
};
},
created() {
let qp = new URLSearchParams(window.location.search);
let f = qp.get("filter");
if (f) this.filter = qp.get("filter");
let tf = qp.get("typeFilter");
if (tf) this.typeFilter = tf.split(",");
},
computed: {
items() {
this.updateURL();
return this.ITEMS.filter((a) => {
if (
this.filter !== "" &&
a.name.toLowerCase().indexOf(this.filter.toLowerCase()) === -1
)
return false;
if (this.typeFilter.length && !this.typeFilter.includes(a.type))
return false;
return true;
});
},
},
methods: {
updateURL() {
let qp = new URLSearchParams();
if (this.filter !== "") qp.set("filter", this.filter);
if (this.typeFilter.length) qp.set("typeFilter", this.typeFilter);
history.replaceState(null, null, "?" + qp.toString());
},
},
};
</script>
You need to read about props in component (vuejs.org/v2/guide/components-props.html).
I wrote you an example but not tested, hope it will be helpful.
<template>
<div id="app">
<h2>Items</h2>
<p>
<input type="search" placeholder="Filter by name" v-model="filter" />
<input
type="checkbox"
value="person"
id="personType"
v-model="typeFilter"
/>
<label for="personType">Only People</label>
<input type="checkbox" value="cat" id="catType" v-model="typeFilter" />
<label for="catType">Only Cats</label>
<input type="checkbox" value="dog" id="dogType" v-model="typeFilter" />
<label for="dogType">Only Dogs</label>
</p>
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
<Result v-bind:items="filteredItems" />
</div>
</template>
<script>
import Result from "#/components/Result.vue";
export default {
components: {
Result,
},
data: function () {
return {
filter: "",
typeFilter: [],
items: [
{ name: "Ray", type: "person" },
{ name: "Lindy", type: "person" },
{ name: "Jacob", type: "person" },
{ name: "Lynn", type: "person" },
{ name: "Noah", type: "person" },
{ name: "Jane", type: "person" },
{ name: "Maisie", type: "person" },
{ name: "Carol", type: "person" },
{ name: "Ashton", type: "person" },
{ name: "Weston", type: "person" },
{ name: "Sammy", type: "cat" },
{ name: "Aleese", type: "cat" },
{ name: "Luna", type: "cat" },
{ name: "Pig", type: "cat" },
{ name: "Cayenne", type: "dog" },
],
};
},
created() {
let qp = new URLSearchParams(window.location.search);
let f = qp.get("filter");
if (f) {
this.filter = qp.get("filter");
}
let tf = qp.get("typeFilter");
if (tf) {
this.typeFilter = tf.split(",");
}
},
computed: {
filteredItems() {
this.updateURL();
return this.items.filter((item) => item.name.toLowerCase().includes(this.filter.toLowerCase()));
},
},
methods: {
updateURL() {
let qp = new URLSearchParams();
if (this.filter !== "") {
qp.set("filter", this.filter);
}
if (this.typeFilter.length) {
qp.set("typeFilter", this.typeFilter);
}
history.replaceState(null, null, "?" + qp.toString());
},
},
};
</script>
<template>
<div class="result">
<ul>
<li v-for="item in items" :key="item">
{{ item.name }} ({{ item.type }})
</li>
</ul>
</div>
</template>
<script>
export default {
props: ['items'],
};
</script>
I have two components, an Input and a Select, the select show options depend on what you write in the Input, but if the option selected isn't showing in the new options, select the default option or the first option "select option"
When I select an option and then change the options in the Select and the option selected is not in the new options, the Select shows in blank, but when I change the option selected to "", the Select doesn't change to it(Select option that has value ""), I don't know why but if I change to other option the Select changes to it...
Example:
select option 2 > write 3 characters in the input
as you can see the varaible option_selected that is binding with the select change to "", but the select dosn't change to "Select option"
Link on the documentation for this example:
Component custom events
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: ""},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", "");
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: ""
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected=''">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
Wow, this was a tricky one to drill down into.
What is "value"
So <select> element has no HTML value attribute, but there is a el.value property provided by the DOM API on HTMLSelectElement that gives you the selected option's value. Vue provides a binding to this value property (to be used via v-model). And that's why we can simply use :value in the <select> element.
Problem with your code:
When 'option 2' is selected, and then removed from the DOM via optionsFiltered, it sets the <select> element into an invalid state. In invalid state the element's el.value returns '' (an empty string) (Note: this is DOM API, not Vue). Now your watcher on is_min_length is triggered and emits an update:option_selected event with value '' (an empty string). As you know, Vue is reactive. Since el.value is already '', I imagine Vue does not see any need to update the DOM, and hence never calls el.value = ''. (Setting el.value to empty string even though its already an empty string does give desired behaviour, DOM API seems quite robust, its Vue that's not calling it).
Solution(s):
The easiest way would be to set the default "Select option" value to something other that '' so that it doesn't clash with the invalid state's ''. For e.g. you can set it to 0. Or maybe a string 'none'.
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: "0"},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "0")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", '0');
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: "0"
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected='0'">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
Another solution is to call the el.value = '' yourself alongside emitting the event, but I don't recommend it as it makes your code harder to understand for fellow Vue developers (you should adhere good coding practices even if you're the only one working on the project):
const input_text = {
name: "input-min-length",
props: {
text: String,
is_min_length: Boolean
},
emits: ["update:text", "update:is_min_length"],
computed: {
is_min(){
return this.text.length >= 3;
}
},
watch: {
is_min(new_value){
this.$emit("update:is_min_length", this.is_min);
}
},
template: `<div><input type="text" :value="text" #input="$emit('update:text', $event.target.value)" /></div>`
};
const select_options = {
name: "select-options",
props: {
option_selected: String,
is_min_length: Boolean
},
emits: ["update:option_selected"],
data() {
return {
options: [
{text: "Select option", value: ""},
{text: "option 1", value: "1", is_min_length: true},
{text: "option 2", value: "2"},
{text: "option 3", value: "3", is_min_length: true},
]
};
},
computed: {
options_filtered(){
if(this.is_min_length == false) return this.options;
return this.options.filter((option) => option.is_min_length == true || option.value == "")
}
},
watch: {
is_min_length(){
const is_presented = this.options_filtered.some((option) => option.value == this.option_selected)
if (is_presented == false){
setTimeout(() => {
this.$emit("update:option_selected", "");
document.getElementById("myselect").value = "";
}, 0); // I try to use setTimeout, to see if it changes at all
}
}
},
template: `
<select id="myselect" :value="option_selected" #change="$emit('update:option_selected', $event.target.value)">
<option v-for="option in options_filtered" :value="option.value" :key="option.value">
{{ option.text }}
</option>
</select>
`
}
const app = {
components:{
"input-min-length": input_text,
"select-options": select_options
},
data() {
return {
text: "",
is_min_length: false,
option_selected: ""
}
},
template: `
<div>
<input-min-length v-model:text="text" v-model:is_min_length="is_min_length" />
<select-options v-model:option_selected="option_selected" :is_min_length="is_min_length" /><br>
<button #click="option_selected='1'">change to opt 1</button><br>
<button #click="option_selected=''">change to opt ""</button><br>
<div>
<strong>DATA:</strong><br>
<strong>text:</strong> "{{text}}"<br>
<strong>is_min_length:</strong> {{is_min_length}}<br>
<strong>option_selected:</strong> "{{option_selected}}"
</div>
</div>`
}
Vue.createApp(app)
.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
I have a selectbox component. I want to reused it in other components.I'm able to reused the selectbox component in other components by adding v-model directive on custom input component but unable to validate selectbox component from other components by using vuetify validation rules.
Test.vue
<v-form ref="form" class="mx-4" v-model="valid">
<Selectbox :name="ward_no" :validationRule="required()" v-model="test.ward_no"/>
<v-btn primary v-on:click="save" class="primary">Submit</v-btn>
export default {
data() {
return {
test: {
ward_no: ''
},
valid: false,
required(propertyType) {
return v => (v && v.length > 0) || `${propertyType} is required`;
},
};
}
Selectbox.vue
<select #change="$emit('input', $event.target.value)" >
<option
v-for="opt in options"
:key="opt.value"
:value="opt.value"
:selected="value === opt.value"
>
{{errorMessage}}
{{ opt.label || "No label" }}
</option>
</select>
export default {
props: {
label: {
type: String,
required: true
},
validationRule: {
type: String,
default: 'text',
validate: (val) => {
// we only cover these types in our input components.
return['requred'].indexOf(val) !== -1;
}
},
name: {
type: String
},
value: {
type: String,
required: true
}
},
data() {
return {
errorMessage: '',
option: "lorem",
options: [
{ label: "lorem", value: "lorem" },
{ label: "ipsum", value: "ipsum" }
]
};
},
methods:{
checkValidationRule(){
if(this.validationRule!="")
{
return this.validationRule.split('|').filter(function(item) {
return item();
})
// this.errorMessage!= ""?this.errorMessage + " | ":"";
}
},
required() {
this.errorMessage!= ""?this.errorMessage + " | ":"";
this.errorMessage += name+" is required";
},
}
};
Does anyone know how to actually display the text value from multiple dependent select fields?
I need option 1 to list a set of parent options. When an Option 1 is selected, it will determine the values available in Option 2 select field.
I then want the text value associated with the selected option to be displayed below the select field.
<template>
<div>
<panel class="main-content">
<div>
<b-form-select v-model="selected" :options="options" class="mb-3" />
<div>Selected: <strong>{{ selected }}</strong></div>
</div>
<div>
<b-form-select v-model="selected" :options="options" class="mb-3" />
<div>Selected: <strong>{{ selected }}</strong></div>
</div>
</panel>
</div>
</template>
<script>
export default {
data () {
return {
selected: null,
options: [
{ value: null, name: 'opt', text: 'Select One' },
{ value: 'option1', name: 'mys', text: 'Carbohydrates' },
{ value: 'option2', name: 'hkong', text: 'Fibre' },
{ value: 'option3', name: 'spore', text: 'Fat' }
]
}
}
}
</script>
Kindly assist since it's something new for me in VueJs.
This is working in VueJS (tested). I was a little off about how the b-form-select element works, but this will give you the results you need.
If you are building the options manually, maybe just make a JSON file or a Component that returns these sets of options and subOptions and import it rather than putting all the options into your template (Cleaner Code).
<script>
export default {
data () {
return {
option1Selected: null,
option2Selected: null,
options: [
{
value: null,
name: 'opt',
text: 'Select One',
},
{
value: 'option1',
name: 'mys',
text:'Carbohydrates',
},
{
value: 'option2',
name: 'hkong',
text: 'Fibre',
},
{
value: 'option3',
name: 'spore',
text: 'Fat',
},
],
subOptions: {
option1: [
{
value: null,
name: 'opt',
text: 'Select One',
},
{
value: 'option1-1',
name: 'o1-1Name',
text: 'Option 1-1 Text',
},
{
value: 'option1-2',
name: 'o1-2Name',
text: 'Option 1-2 Text',
},
{
value: 'option1-3',
name: 'o1-3Name',
text: 'Option 1-3 Text',
},
],
option2: [
{
value: null,
name: 'opt',
text: 'Select One',
},
{
value: 'option2-1',
name: 'o2-1Name',
text: 'Option 2-1 Text',
},
{
value: 'option2-2',
name: 'o2-2Name',
text: 'Option 2-2 Text',
},
{
value: 'option2-3',
name: 'o2-3Name',
text: 'Option 2-3 Text',
},
],
option3: [
{
value: null,
name: 'opt',
text: 'Select One',
},
{
value: 'option3-1',
name: 'o3-1Name',
text: 'Option 3-1 Text',
},
{
value: 'option3-2',
name: 'o3-2Name',
text: 'Option 3-2 Text',
},
{
value: 'option3-3',
name: 'o3-3Name',
text: 'Option 3-3 Text',
},
],
}
}
},
computed: {
option1text () {
for (const key in this.options) {
if (this.options[key].value === this.option1Selected) {
return this.options[key].text;
}
}
},
option2text () {
if (this.option1Selected != null) {
for (const key in this.subOptions[this.option1Selected]) {
if (this.subOptions[this.option1Selected][key].value === this.option2Selected) {
return this.subOptions[this.option1Selected][key].text;
}
}
}
}
},
}
</script>
<template>
<div>
<panel class="main-content">
<div>
<b-form-select v-model="option1Selected" :options="options" class="mb-3" />
<div>Selected: <strong>{{ option1text }}</strong></div>
</div>
<div v-if="option1Selected != null">
<b-form-select v-model="option2Selected" :options="subOptions[option1Selected]" class="mb-3" />
<div>Selected: <strong>{{ option2text }}</strong></div>
</div>
</panel>
</div>
</template>
Note I have updated code to properly reflect exactly what you were asking for in the question: the Text value of the option.
I have a custom input where I recieve a value prop and emit a input event on the input event. I can use this custom input without problems with a model, now I'm creating a custom password input that I initialize as a custom input but I can't bind the model using value and input event handlers (passing them to the custom input). How can I approach this?
Custom Input:
My program model > custom input (value and input event handler) : works
My program model > custom password (value and input event handler) > custom input: doesn't work.
Code:
Input.vue:
<template>
<div class="form-group">
<label for="" v-if="typeof label !== 'undefined'">{{ label }}</label>
<!-- GROUP -->
<template v-if="isGroup">
<div class="input-group">
<!-- PREPEND -->
<div v-if="hasPrepend" class="input-group-prepend"
:class="{'inside bg-transparent' : prependInside, 'pointer': prependPointer}"
#click="clickPrepend">
<span class="input-group-text"
:class="{'bg-transparent' : prependInside}">
<i aria-hidden="true"
v-if="prependType === 'icon'"
:class="'fa fa-' + prependContent"></i>
<template v-if="prependType === 'text'">{{ prependContent }}</template>
</span>
</div>
<!-- INPUT -->
<input class="form-control"
:type="type"
:class="generatedInputClass"
:readonly="readonly"
:disabled="disabled"
:value="value"
#input="inputEvent"
#change="onChange">
<!-- APPEND -->
<div v-if="hasAppend" class="input-group-append"
:class="{'inside bg-transparent' : appendInside, 'pointer': appendPointer}"
#click="clickAppend">
<span class="input-group-text"
:class="{'bg-transparent' : appendInside}">
<i aria-hidden="true"
v-if="appendType === 'icon'"
:class="'fa fa-' + appendContent"></i>
<template v-if="appendType === 'text'">{{ appendContent }}</template>
</span>
</div>
</div>
</template>
<!-- INPUT -->
<template v-else>
<input class="form-control"
:type="type"
:class="generatedInputClass"
:readonly="readonly"
:disabled="disabled"
:value="value"
#input="inputEvent"
#change="onChange"
>
</template>
<small class="form-text"
v-if="typeof helpText !== 'undefined'"
:class="generatedHelperClass">
{{ helpText }}
</small>
</div>
</template>
<script>
export default {
name: 'InputGroup',
props: {
value: String,
label: String,
helpText: String,
size: String,
prependContent: String,
appendContent: String,
prependType: {
type: String,
default: 'icon',
},
appendType: {
type: String,
default: 'icon',
},
prependInside: {
type: Boolean,
default: false,
},
appendInside: {
type: Boolean,
default: false,
},
prependPointer: {
type: Boolean,
default: false,
},
appendPointer: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'text',
},
valid: {
type: Boolean,
default: null,
},
},
watch: {
valid() {
},
},
computed: {
isGroup() {
return this.hasPrepend || this.hasAppend;
},
hasPrepend() {
return typeof this.prependContent !== 'undefined';
},
hasAppend() {
return typeof this.appendContent !== 'undefined';
},
generatedInputClass() {
const size = typeof this.size !== 'undefined' ? `form-control-${this.size}` : '';
let valid = '';
if (this.valid !== null) {
valid = this.valid ? 'is-valid' : 'is-invalid';
}
return `${size} ${valid}`;
},
generatedHelperClass() {
let valid = 'text-muted';
if (this.valid !== null) {
valid = this.valid ? 'valid-feedback' : 'invalid-feedback';
}
return `${valid}`;
},
},
methods: {
inputEvent(e) {
this.$emit('input', e.target.value);
},
clickPrepend(e) {
this.$emit('click-prepend', e);
},
clickAppend(e) {
this.$emit('click-append', e);
},
onChange(e) {
this.$emit('change', this.value, e);
},
},
};
</script>
Password.vue:
<template>
<div>
<app-input
:label="label"
:type="type"
prepend-content="lock"
:append-content="passwordIcon"
:append-inside="true"
:append-pointer="true"
#click-append="tooglePassword"
:value="value"
#input="inputEvent">
</app-input>
</div>
</template>
<script>
import Input from './Input';
export default {
name: 'Password',
components: {
appInput: Input,
},
props: {
value: String,
label: {
type: String,
default: 'ContraseƱa',
},
readonly: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
valid: {
type: Boolean,
default: null,
},
},
data() {
return {
pass: '',
type: 'password',
};
},
computed: {
passwordIcon() {
return this.type === 'password' ? 'eye' : 'eye-slash';
},
},
methods: {
tooglePassword() {
this.type = this.type === 'password' ? 'text' : 'password';
},
inputEvent(e) {
this.$emit('input', e.target.value);
},
},
};
</script>
The thing is the input event your Password listens form app-input component, has as value the actual string value already, not the element (that you'd have to call e.target.value to get the string value)
In other words, in Password.vue, instead of:
inputEvent(e) {
this.$emit('input', e.target.value);
},
Do:
inputEvent(e) {
this.$emit('input', e);
},
CodeSandbox demo here.