VueJS - Pull results from a separate component - vue.js

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>

Related

Search bar filter vue 3

I just want to add a search bar filter to my vue 3 project but I don't know why is not working as good as I wanted.
Here is my code:
App.vue
<template>
<div id="app">
<div class="header">
<img class="logo" alt="UOC logo" src="./assets/uoc-logo.png" />
<div class="app-name">Recipe book</div>
</div>
<search-bar #search="setSearchTerm" />
<recipe-list :recipeList="filteredData" #delete-recipe="deleteRecipe" />
<recipe-form
v-if="showModal"
#add-recipe="addRecipe"
#close-modal="showModal = false"
/>
</div>
</template>
<script>
import RecipeList from "./components/RecipeList.vue";
import RecipeForm from "./components/RecipeForm.vue";
import SearchBar from "./components/SearchBar.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "App",
components: {
RecipeList: RecipeList,
RecipeForm,
SearchBar,
},
data: () => ({
recipeList: [
{
id: 1,
servings: 4,
time: "30m",
difficulty: "Easy",
title: "Spaghetti",
ingredients: ["noodles", "tomato sauce", "cheese"],
directions: ["boil noodles", "cook noodles", "eat noodles"],
imageUrl:
"https://imagesvc.meredithcorp.io/v3/mm/image?q=60&c=sc&poi=face&w=2000&h=1000&url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F21%2F2018%2F02%2F14%2Frecetas-4115-spaghetti-boloesa-facil-2000.jpg",
},
{
id: 2,
servings: 2,
time: "15m",
difficulty: "Medium",
title: "Pizza",
ingredients: ["dough", "tomato sauce", "cheese"],
directions: ["boil dough", "cook dough", "eat pizza"],
imageUrl:
"https://www.saborusa.com/wp-content/uploads/2019/10/Animate-a-disfrutar-una-deliciosa-pizza-de-salchicha-Foto-destacada.png",
featured: true,
},
{
id: 3,
servings: 6,
time: "1h",
difficulty: "Hard",
title: "Salad",
ingredients: ["lettuce", "tomato", "cheese"],
directions: ["cut lettuce", "cut tomato", "cut cheese"],
imageUrl:
"https://www.unileverfoodsolutions.es/dam/global-ufs/mcos/SPAIN/calcmenu/recipes/ES-recipes/In-Development/american-bbq-beef-salad/main-header.jpg",
},
],
showModal: false,
recipesData: RecipeList,
filteredData: RecipeList,
}),
methods: {
deleteRecipe(id) {
this.recipeList.splice(id, 1);
},
addRecipe(recipe) {
this.recipeList.push(recipe);
},
toggleForm() {
if (this.showModal === false) {
this.showModal = true;
}
},
setSearchTerm(value) {
console.log(value);
if (value && value.length > 0) {
this.filteredData = this.recipesData.filter((i) => {
const val = value.toLowerCase();
const title = i.title && i.title.toLowerCase();
if (val && title.indexOf(val) !== -1) {
return true;
}
return false;
});
} else {
this.filteredData = this.recipesData;
}
},
},
});
</script>
SearchBar.vue
<template>
<div class="search">
<input
type="text"
placeholder="Search for a recipe"
id="search"
#change="search"
v-model="searchText"
/>
<button #click="clearSearch">Clear search</button>
<button #click="showForm">Add a new recipe</button>
</div>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "SearchBar",
data() {
return { searchText: "" };
},
methods: {
showForm() {
console.log("show");
this.$emit("show-form");
},
clearSearch() {
this.clearInput = "";
},
search() {
this.$emit("search", this.searchText);
},
},
});
</script>
The component of search bar is working but when I acces to my website the array object is broken and I can not see any recipe. I just see recipes If I find it.
Thanks for your help

Catch Event from Dropdown component not working in Parent

I am using this wonderful https://www.creative-tim.com/product/vue-black-dashboard-pro and their component Base-Dropdown has the following code:
<template>
<component :is="tag"
class="dropdown"
:class="{show:isOpen}"
#click="toggleDropDown"
v-click-outside="closeDropDown">
<slot name="title-container" :is-open="isOpen">
<component
:is="titleTag"
class="dropdown-toggle btn-rotate"
:class="titleClasses"
:aria-expanded="isOpen"
:aria-label="title || ariaLabel"
data-toggle="dropdown">
<slot name="title" :is-open="isOpen">
<i :class="icon"></i>
{{title}}
</slot>
</component>
</slot>
<ul class="dropdown-menu" :class="[{show:isOpen}, {'dropdown-menu-right': menuOnRight}, menuClasses]">
<slot></slot>
</ul>
</component>
</template>
<script>
export default {
name: "base-dropdown",
props: {
tag: {
type: String,
default: "div",
description: "Dropdown html tag (e.g div, ul etc)"
},
titleTag: {
type: String,
default: "button",
description: "Dropdown title (toggle) html tag"
},
title: {
type: String,
description: "Dropdown title",
},
icon: {
type: String,
description: "Dropdown icon"
},
titleClasses: {
type: [String, Object, Array],
description: "Title css classes"
},
menuClasses: {
type: [String, Object],
description: "Menu css classes"
},
menuOnRight: {
type: Boolean,
description: "Whether menu should appear on the right"
},
ariaLabel: String
},
data() {
return {
isOpen: false
};
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen;
this.$emit("change", this.isOpen);
},
closeDropDown() {
this.isOpen = false;
this.$emit('change', false);
}
}
};
</script>
Now in another component, let's call it, "AddAccount.vue" I am using that Base-Dropdown component, but I can't seem to catch the 'change' event. I have tried everything.
computed: {
listeners() {
return {
...this.$listeners,
change: this.onChange,
}
}
},
methods: {
onChange(evt) {
console.log(":P:")
},
or
computed: {
change(evt) {
console.log(":P:")
}
},
or
methods: {
change(evt) {
console.log(":P:")
},
Nothing gets ever caught in that component, but if I do a breakpoint in the Base-Dropdown.vue, it is being caught...!

Going back to previous object in array with back button in v-for

I have a component with a v-for div. each item has a click function access their respective children object. I need to have a back button that would refresh the v-for div but using the ParentId of the current item I'm in.
Scan view:
<template>
<div p-0 m-0>
<div v-show="!currentItem" class="scanBreadcrumbs">
<h2>Show location</h2>
</div>
<div v-for="item in items" :key="item.id" :item="item">
<SubScan
v-show="currentItem && currentItem.id === item.id"
:item="item"
></SubScan>
<p
class="locationBox"
#click="swapComponent(item)"
v-show="path.length === 0"
>
{{ item.title }}
</p>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { SubScan } from "#/components/scan";
export default {
name: "Scan",
components: {
SubScan
},
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
}
},
data: () => ({
currentItem: null,
path: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64645,
title: "Row 1.2",
items: [
{
parentId: 64645,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
SubScan component where the back button is:
<template>
<div>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join(" / ") }}</h2>
</div>
<div>
<div class="showList" v-for="item in itemChildren" :key="item.id">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
<div class="backButton">
<v-icon #click="swapPrevious(item)" class="arrow"
>fa-arrow-left</v-icon
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "SubScan",
props: {
item: {
type: Object,
required: true
}
},
data: () => ({
currentItem: null,
secondaryPath: [],
parentPath: []
}),
methods: {
swapComponent(item) {
console.log(item.parentId);
this.path.push(item.title);
this.parentPath.push(this.currentItem);
this.currentItem = item;
},
swapPrevious(item) {
console.log(item);
this.path.pop(this.currentItem.title);
this.currentItem = item.id;
}
},
computed: {
items(currentItem) {
return this.currentItem ? this.item.items : this.item.items;
},
itemChildren(currentItem) {
return this.currentItem ? this.currentItem.items : this.item.items;
},
path() {
return this.secondaryPath.concat(this.item.title);
}
}
};
</script>
I can only go back to the children of the object I clicked on in Scan view.
Thank you for your time.
I managed to fix my problem by assigning parent objects to each children. Then I
moved everything to Scan.vue for simplicity. This is my first project using Vue
so things might not be optimal. Scan.vue
<template>
<div p-0 m-0>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join("/") }}</h2>
<h2 v-if="path.length === 0">Show location</h2>
</div>
<div>
<div v-for="item in items">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
</div>
<div v-if="path.length > 0">
<div class="backButton">
<v-icon #click="swapPrevious()" class="arrow">fa-arrow-left</v-icon>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Scan",
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
this.items = this.currentItem.items;
},
assignParent(children, parent) {
children.forEach(item => {
item.Parent = parent;
var parentTitle = "";
if (parent) parentTitle = parent.title;
if (item.items) {
this.assignParent(item.items, item);
}
});
},
swapPrevious() {
if (this.currentItem.parentId === null) {
this.items = this.initialItems;
this.path.pop(this.currentItem.title);
} else {
this.currentItem = this.currentItem.Parent;
this.items = this.currentItem.items;
this.path.pop(this.currentItem.title);
}
}
},
mounted: function() {
this.assignParent(this.items, null);
this.initialItems = this.items;
},
data: () => ({
currentItem: null,
path: [],
initialItems: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64646,
title: "Row 1.2",
items: [
{
parentId: 64646,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657,
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 12313,
title: "Row 1.5",
items: [
{
parentId: 12313,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
<style lang="scss" scoped></style>

Vue - Creating dynamic params

I'm trying to create a search page (my-site.com/search) and need to pass 3 optional dynamic params to this url on click.
The issue is on click something is removing 'search' from the path and taking me to the root path - my-site.com/. I'm having trouble figuring out what's causing it.
paths.js
const tagToParam = {
postname: ':slug',
search: ':term?/:author?/:topic?'
}
const paginateParam = ':page(page\/\\d+)?'
export default {
postsPageWithSearch: (slug) => slug ? `/${slug}/${tagToParam.search}/${paginateParam}` : `/${paginateParam}`,
}
routes.js
import Search from '#/components/Search'
import paths from './paths'
{
path: paths.postsPageWithSearch('search') + '/',
component: Search,
name: 'Search',
meta: { bodyClass: 'search' },
props: route => (Object.assign(route.params, {
term: route.params.term,
author: route.params.author,
topic: route.params.topic,
page: pageFromPath(route.path)
} ))
},
Search.vue
<template>
<main class="site-content">
<div class="container">
<div class="advanced-search">
<form #click.prevent>
<input type="text"
name="searchTerm"
placeholder="Search title..."
tabindex="1"
v-model="searchTerm">
<select v-model="searchAuthor">
<option value="">All</option>
<option
v-for="author in authors"
:value="author.id"
:key="author.id">{{ author.name }}</option>
</select>
<select v-model="searchTopic">
<option value="">All</option>
<option
v-for="topic in topics"
:value="topic.id"
:key="topic.id">{{ topic.name }}</option>
</select>
<button class="button" type="submit" #click="submitSearch()">Search</button>
</form>
</div>
<section v-if="posts">
<post-item
v-for="post in posts"
:key="post.id"
:post="post"
:postmeta=false
/>
</section>
</div>
</main>
</template>
<script>
import PostItem from '#/components/template-parts/PostItem'
export default {
name: 'Search',
components: {
PostItem,
},
props: {
page: {
type: Number,
required: true
},
term: {
type: String,
required: true
},
author: {
required: true
},
topic: {
required: true
},
},
data() {
return {
postsRequest: {
type: 'quote',
params: {
per_page: 5,
search: this.term,
authors: this.author,
tags: this.topic,
page: this.page
},
showLoading: true
},
totalPages: 0,
authorsRequest: {
type: 'authors',
params: {
per_page: 100,
},
},
topicsRequest: {
type: 'tags',
params: {
per_page: 100,
},
},
searchTerm: '',
searchAuthor: '',
searchTopic: ''
}
},
computed: {
authors () {
return this.$store.getters.requestedItems(this.authorsRequest)
},
topics () {
return this.$store.getters.requestedItems(this.topicsRequest)
},
posts() {
return this.$store.getters.requestedItems(this.postsRequest)
}
},
methods: {
getAuthors() {
return this.$store.dispatch('getItems', this.authorsRequest)
},
getTopics() {
return this.$store.dispatch('getItems', this.topicsRequest)
},
getPosts() {
return this.$store.dispatch('getItems', this.postsRequest)
},
setTotalPages() {
this.totalPages = this.$store.getters.totalPages(this.postsRequest)
},
submitSearch() {
this.$router.replace({
name: "Search",
params: {
term: this.searchTerm,
author: this.searchAuthor,
tags: this.searchTopic
}
})
}
},
created() {
this.getAuthors()
this.getTopics()
this.getPosts().then(() => this.setTotalPages())
},
}
</script>
It looks like the <form> element has #click.prevent. Try putting the #click.prevent on your submit button.

Vue: v-model and input event in custom component derived of a custom component

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.