Why this $emit event is not working between component and parent in Vue.js? - vue.js

Playing with an API and building a simple dropdown selector in Vue, I am stuck as of why events of a component do not reach the parent. I have looked on the documentation, with no success. I have tried both this.$emit and this.$parent.$emit with no success
Code sample here:
https://jsfiddle.net/behyfnxw/
The point is that method "updated" in the parent is never called when selecting a university from the dropdown menu.
Pretty new to Vue and frontend, any feedback is more than welcome. Thanks!
<!DOCTYPE html>
<div id="app">
<div class="row">
<div class="col-lg-6 offset-lg-3">
<entity-selector :items="univs" :value="u0"></entity-selector>
<entity-selector :items="deps" :value="d0"></entity-selector>
<entity-selector :items="courses" :value="c0"></entity-selector>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
Vue.component('entity-selector', {
props: ['items', 'value'],
data: function () {
return {
selected: "u0",
}
},
created: function () {
console.log("entity selector created just now!");
},
mounted: function () {
console.log("mounted");
this.selected = this.value;
},
watch: {
selected: function (newValue) {
console.log("watch!", newValue);
this.selected = newValue;
console.log("to send event");
this.$emit("updated");
console.log("sent event");
}
},
methods: {
updated: function () {
console.log("selected item:", this.selected);
this.$emit("updated", "univ", this.selected);
console.log("selected item:", this.selected);
},
},
template:`
<p class="col-xs-12">
<select class="form-control" #click="updated" v-model="selected" v-cloak>
<option v-for="item in items" :value="item.id">{{item.fullname}}</option>
</select>
</p>
`
});</script>
<script>
var vm = new Vue({
el: '#app',
data: {
u0: "u0",
d0: "d0",
c0: "c0",
default_univ: {"id": "u0", "fullname": 'choose univ', "total": 0},
default_dep: {"id": "d0", "fullname": 'choose dep', "total": 0},
default_course: {"id": "c0", "fullname": 'choose course', "total": 0},
univs: [{"id": "u0", "fullname": 'choose univ', "total": 0}],
deps: [{"id": "d0", "fullname": 'choose dep', "total": 0}],
courses: [{"id": "c0", "fullname": 'choose course', "total": 0}],
},
created: function () {
console.log("main vue created just now!");
this.fetch_entities("gr", 1);
},
methods: {
fetch_entities: function (parent, filetypeid) {
var main_endpoint = "https://api.trelosfoititis.gr/v1";
var url = main_endpoint + "/list?parentid=" + parent + "&filetypeid=" + filetypeid;
var self = this;
axios.get(url)
.then(function (response) {
self.fetched(parent, filetypeid, response.data.response.data);
});
},
updated: function (entity_type, entity_id){
console.log("updated!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", entity_type, entity_id);
},
fetched: function (parent, filetypeid, results){
console.log("fetched", parent, filetypeid);
if(parent == "gr"){
this.univs = (results.length > 0) ? [].concat([this.default_univ], results) : [this.default_univ];
}
},
}
})
</script>
</html>

Related

How do you select a single array item from a data object and pass it to another component?

I have some data that I get from axios and pass to a Bootstrap table. In my computed properties where I declare the nameOfPerson field, I have made a click event, so that when a user clicks on the name, a modal opens. This modal also contains the data shown in the table.
However, I would like to change it so that when you click on the name of a person, ONLY the data for THAT single person gets passed to the modal. So instead of passing a prop containing data of ALL users the modal, I just want the data related to the name that I actually click on.
How would I accomplish this?
The parent:
<template>
<b-container>
<b-card class="mt-4">
<b-table
:items="dataItems"
:fields="fields"
:per-page="[5, 10]"
sort-desc
primary-key="id"
/>
</b-card>
<data-modal ref="dataModal" :selected-name="dataItems"/>
</b-container>
</template>
<script>
import {axiosComponent} from '#/axios/services';
import DataModal from '#/components/DataModal';
export default {
components: {
DataModal
},
data() {
return {
dataItems: null,
};
},
computed: {
fields() {
return [
{
key: 'nameOfperson',
label: 'name',
sortable: true
click: () => this.$refs.dataModal.show(),
},
{
key: 'ageOfPerson',
label: 'Age',
sortable: true
},
]
},
},
methods: {
load(){
axiosComponent.getData().then(result => {
this.dataItems = result.data
})
}
},
created() {
this.load()
}
};
</script>
The child (modal)
<template>
<b-modal v-model="showModal">
<div v-for="log in selectedName">
{{ log }}
</div>
</b-modal>
</template>
<script>
export default {
props: {
selectedName: Array
},
data() {
return {
showModal: false,
};
},
methods: {
show(){
this.showModal = true
}
}
};
</script>
You can use #row-selected method, take a look at following demo:
Vue.component('child', {
template: `
<b-modal v-model="showModal">
<div v-for="log in selectedName">
{{ log }}
</div>
</b-modal>
`,
props: {
selectedName: Array,
},
data() {
return {
showModal: false,
};
},
methods: {
show(){
this.showModal = true
}
}
})
new Vue({
el: "#demo",
data() {
return {
dataItems: null,
selected: null,
};
},
computed: {
fields() {
return [
{
key: 'nameOfperson',
label: 'name',
sortable: true,
},
{
key: 'ageOfPerson',
label: 'Age',
sortable: true
},
]
},
},
methods: {
load(){
// axiosComponent.getData().then(result => {
this.dataItems = [{id: 1, nameOfperson: 'aaa', ageOfPerson: 5}, {id: 2, nameOfperson: 'bbb', ageOfPerson: 25}, {id: 3, nameOfperson: 'ccc', ageOfPerson: 35}, {id: 4, nameOfperson: 'ddd', ageOfPerson: 45}]
// })
},
onRowSelected(items) {
this.selected = items
this.$refs.dataModal.show()
},
},
created() {
this.load()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="demo">
<b-container>
<b-card class="mt-4">
<b-table
:items="dataItems"
:fields="fields"
:per-page="5"
sort-desc
primary-key="id"
selectable
:select-mode="'single'"
#row-selected="onRowSelected"
/>
</b-card>
<child ref="dataModal" :selected-name="selected"></child>
</b-container>
</div>

Default props value are not selected in vue3 options api

I created a select2 wrapper in vue3 with options API everything working fine but the problem is that when getting values from calling API it's not selected the default value in the select2 option. but when I created a static array of objects it does. I don't know why it's working when it comes from the API
Parent Component
Here you can I passed the static options array in options props and my selected value is 2 and it's selected in my Select2 component, but when passed formattedCompanies it's not which is the same format as the static options array then why is not selected any reason here..?
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
:options="options"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : [],
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
Select2 Component
Here is what my select2 component look like, did I do anything wrong here, please anybody help me
<template>
<select class="form-control">
<slot/>
</select>
</template>
<script>
export default {
name: "Select2",
props: {
options: {
type: [Array, Object],
required: true
},
modelValue: [String, Number],
placeholder: {
type: String,
default: "Search"
},
allowClear: {
type: Boolean,
default: true
},
},
mounted() {
const vm = this;
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
},
watch: {
modelValue(value) { // update value
$(this.$el)
.val(value)
.trigger("change");
},
options(options) { // update options
$(this.$el)
.empty()
.select2({data: options});
},
},
destroyed() {
$(this.$el)
.off()
.select2("destroy");
}
}
</script>
Probably when this Select2 mounted there is no companies. It is empty array after that it will make API call and it it populates options field and clear all options.
Make:
companies : null,
Change it to
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
It should be like this:
<template>
<Form #submitted="store()" :processing="submitting">
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label>Company Name</label>
<Select2
v-if="formattedCompanies"
:options="formattedCompanies"
v-model="selected"
placeholder="Select Company"
/>
<ValidationError :errors="errors" error-key="name" />
</div>
</div>
</div>
</Form>
</template>
<script>
import Form from "#/components/Common/Form";
import Select2 from "#/components/Common/Select2";
export default {
components: {
Select2,
Form
},
data() {
return {
selected : 2,
companies : null,
options: [ // static array
{ id: 1, text: 'hello' },
{ id: 2, text: 'hello2' },
{ id: 3, text: 'hello3' },
{ id: 4, text: 'hello4' },
{ id: 5, text: 'hello5' },
],
}
},
mounted() {
this.getAllMedicineCompanies()
},
computed:{
formattedCompanies() {
let arr = [];
this.companies.forEach(item => {
arr.push({id: item.id, text: item.name})
});
return arr;
}
},
methods: {
getAllMedicineCompanies(){
axios.get('/api/get-data?provider=companies')
.then(({ data }) => {
this.companies = data
})
},
}
}
</script>
The problem was that my parent component and Select2 component mounted at the same time that's why my computed value is not initialized so the selected value is not selected in the option,
problem solved by setTimeOut function in mounted like this
Select2 Component
<script>
mounted() {
const vm = this;
setTimeout(() => {
$(this.$el)
.select2({ // init select2
data: this.options,
placeholder: this.placeholder,
allowClear: this.allowClear
})
.val(this.modelValue)
.trigger("change")
.on("change", function () { // emit event on change.
vm.$emit("update:modelValue", this.value);
});
}, 500)
},
</script>

Vuejs get dynamic form values

I have select and input component with made by buefy. Everything is ok till I realize how can I get the data.
I'm sort of new on vuejs. So I will be glad if you help me out.
I'm getting dynamic form from backend
So my question is how can get values these inputs and submit to backend again with getOffer() methot.
Here is my codes;
Input.vue
<template>
<b-field :label="fieldLabel">
<b-input
:name="inputName"
:type="inputType"
:maxlength="inputType == 'textarea' ? 200 : null"
></b-input>
</b-field>
</template>
<script>
export default {
name: "Input",
props: {
inputType: {
type: String,
required: true,
default: "text",
},
inputName: {
type: String,
required: true,
},
fieldLabel: {
type: String,
required: true,
}
}
};
</script>
Home.vue
<template>
<div class="container is-max-desktop wrapper">
<div v-for="element in offer" :key="element.id">
<Input
v-model="element.fieldValue"
:value="element.fieldValue"
:fieldLabel="element.fieldLabel"
:inputType="element.fieldType"
:inputName="element.fieldName"
v-if="element.fieldType != 'select'"
class="mb-3"
/>
<Select
v-model="element.fieldValue"
:fieldLabel="element.fieldLabel"
:options="element.infoRequestFormOptions"
:selectName="element.fieldName"
v-if="element.fieldType == 'select'"
class="mb-3"
/>
</div>
<b-button type="is-danger" #click="getOffer()">GET</b-button>
</div>
</template>
<script>
import axios from "axios";
import Select from "../components/Select.vue";
import Input from "../components/Input.vue";
export default {
name: "Home",
data() {
return {
offer: [],
};
},
components: {
Select,
Input,
},
methods: {
getOfferForm() {
axios({
method: "get",
url: `/GETDYNAMICFORM`,
})
.then((response) => {
this.offer = response.data;
})
.catch(() => {
this.$buefy.toast.open({
duration: 3000,
message: "oops",
position: "is-bottom",
type: "is-danger",
});
});
},
getOffer() {
console.log(this.offer);
},
},
created() {
this.getOfferForm();
},
};
</script>
Example Dynamic Form Response like;
[
{
"id": 58,
"fieldLabel": "Name Surname",
"providerLabel": "Name Surname",
"fieldName": "nmsrnm",
"fieldType": "text",
"fieldValue": null,
},
{
"id": 60,
"fieldLabel": "E-mail",
"providerLabel": "E-mail",
"fieldName": "e_mail_60",
"fieldType": "email",
"fieldValue": null,
},
{
"id": 2,
"fieldLabel": "Budget",
"providerLabel": "Budget",
"fieldName": "bdget",
"fieldType": "select",
"fieldValue": "",
"infoRequestFormOptions": [
{
"id": 1,
"orderNum": 0,
"optionValue": 0,
"optionText": "Select",
"minValue": null,
"maxValue": null
},
{
"id": 2,
"orderNum": 1,
"optionValue": 1,
"optionText": "10-30",
"minValue": 10,
"maxValue": 30
}
]
}
]

how can I update progress bar when json data change using vue.js without refresh

I try to make a boostrap dashboard page for tracing status of my python aplication running on server.
On server side, python app update data.json file when reach certain status.
On client side, vue.js handle content creation.
I have a problem when I try to update progress bar, because i need to refresh page so that progress appears.
Any suggestion how can I make live progress bar in my view without refresh?
index.html
<div class="item" v-for="item in order">>
<div class="progress">
<div class="progress-bar bg-warning" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" :style="{ width: item.completion + '%' }">
</div>
</div>
</div>
app.js
window.addEventListener('load', () => {
window.vue = new Vue({
el: '#app',
name: 'Order',
data: {
isLoading: true,
order: [],
},
created() {
fetch('./data.json')
.then((res) => { return res.json() })
.then((res) => {
this.isLoading = false;
this.order = res.order;
})
}
})
});
data.json
{
"order": [
{
"customer": "Mr. Smith",
"price": "60",
"status": "Pending",
"orders": "Something",
"completion": 40,
"isAvailable": true,
"isEligible": true
}
]
}
edit: I solve my issue with adding watcher to app.js
watch: {
order() {
this.updateorder();
}
},
methods: {
updateorder() {
fetch('./data.json?_timestamp=' + Date.now())
.then((res) => { return res.json() })
.then((res) => {
this.order = res.order;
})
Does it help?
let i = 0;
const emulateRequest = () => Promise.resolve({
"order": [
{
"customer": "Mr. Smith",
"price": "60",
"status": "Pending",
"orders": "Something",
"completion": i++,
"isAvailable": true,
"isEligible": true
}
]
});
new Vue({
el: '#app',
data: () => ({
isLoading: true,
order: [],
}),
created() {
this.load();
},
methods: {
load() {
//fetch('./data.json')
// .then((res) => { return res.json() })
emulateRequest()
.then((res) => {
this.isLoading = false;
this.order = res.order;
if (this.order.some(({ completion }) => completion !== 100)) {
setTimeout(() => {
this.load();
}, 1000);
}
})
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div id="app">
<div class="item" v-for="item in order">
<div class="progress">
<div class="progress-bar bg-warning" role="progressbar" :aria-valuenow="item.completion" aria-valuemin="0" aria-valuemax="100" :style="{ width: `${item.completion}%` }">
</div>
</div>
</div>
</div>

Filter on nested (recursive) data ( Vue 2 )

Here is an example of my JSON data :
"data":[
{
"id":01,
"name":"test",
"parent_id":null,
"children":[
{
"id":15,
"name":"subChild",
"parent_id":21,
"children":[
{
"id":148,
"name":"subSubChild",
"parent_id":22,
"children":[
....
]
}
]
}
]
},
I would like to filter this level by level. I have made this method :
computed: {
filteredData: function () {
let filterData = this.filter.toLowerCase()
return _.pickBy(this.data, (value, key) => {
return _.startsWith(value.name.toLowerCase(), filterData)
})
},
This work for only the first "level" and I tried several solutions but none worked for children.
So, I would like to be able to filter by several levels.
If you have an idea!
Thank you
A recursive function could come in handy for this particular purpose.
Try the following approach, and for better view, click on Full page link next to the Run code snippet button down below.
new Vue({
el: '#app',
data() {
return {
filter: '',
maintainStructure: false,
data: [{
"id": 01,
"name": "test",
"parent_id": null,
"children": [{
"id": 15,
"name": "subChild",
"parent_id": 21,
"children": [
{
"id": 148,
"name": "subSubChild",
"parent_id": 22,
"children": []
},
{
"id": 150,
"name": "subSubChild3",
"parent_id": 24,
"children": []
}
]
}]
}]
}
},
methods: {
$_find(items, predicate) {
let matches = [];
for (let item of items) {
if (predicate(item)) {
matches.push(item);
}
else if (item.children.length) {
let subMatches = this.$_find(item.children, predicate);
if (subMatches.length) {
if (this.maintainStructure) {
matches.push({
...item,
children: subMatches
});
}
else {
matches.push(subMatches);
}
}
}
}
return matches;
},
filterBy(item) {
return item.name.toLowerCase().startsWith(this.filter.toLowerCase());
}
},
computed: {
filteredData() {
return this.$_find(this.data, this.filterBy);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<label>Filter by <code>item.name</code>:</label>
<input v-model.trim="filter" placeholder="e.g. subsub" />
</div>
<div>
<label>
<input type="checkbox" v-model="maintainStructure" /> Maintain structure
</label>
</div>
<hr />
<pre>{{filteredData}}</pre>
</div>
Note that I'm prefixing the function with $_ to sort of mark it as private function (as recommended in this Style Guide) since we're not going to invoke it anywhere else.