How to get the component triggered in parent vue? - javascript

I am using Laravel 6 (mix) + Vue.js 2.
I am creating a multi-lang dictionary where users can add words and for each word they can add multiple definitions with their translations.
Inside index.blade.php
<div class="modal fade" id="modal-addItem">
<form>
<div class="card" v-for="(value, index) in nbDefinitions">
<div class="card-header">#{{ value }}.</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-3">English</dt>
<dd class="col-sm-9">
<autocomplete data-language="english" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
<dt class="col-sm-3">French</dt>
<dd class="col-sm-9">
<autocomplete data-language="french" :search="searchDefinition" :get-result-value="getDefinitionResultValue" #submit="handleSearchDefinitionSubmit"></autocomplete>
</dd>
</dl>
</div>
</div>
<div class="row">
<button type="button" class="btn btn-primary" id="addItems-addDefinition" #click="addDefinition">+ Add definition</button>
</div>
</form>
</div>
Inside myCustom.js
new Vue({
el: '#modal-addItem',
data: {
nbDefinitions: 0,
nbSentencesPerDef: [],
translatedDefinitions: []
},
methods: {
addDefinition: function() {
this.nbDefinitions++;
this.nbSentencesPerDef.push(1);
this.translatedDefinitions.push({
english: null,
french: null
});
},
searchDefinition: function (input) {
// How can I know which <autocomplete> is triggered?
return new Promise(resolve => {
if (input.length < 3) { return resolve([]); }
fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => {
resolve(responseJson.definitions);
})
})
},
getDefinitionResultValue: function(result) {
// How can I know which <autocomplete> is triggered?
let definition = result.definition;
let item = result.item.name;
return `${item} - ${definition}`;
},
handleSearchDefinitionSubmit: function(result) {
// How can I know which <autocomplete> is triggered?
console.log(this);
}
}
});
I am using autocomplete which is an external component loaded globally (in Laravel main app.js) https://autocomplete.trevoreyre.com/#/
My question is: How can I know inside the methods “addDefinition”, “searchDefinition” and “handleSearchDefinitionSubmit” what is the child component who was triggered? Because I have 3 autocomplete components inside my Vue object, and this refers to the root (in my case the html modal), so I have no idea which autocomplete child was triggered. Also this.$refs is empty.
Maybe it is an architectural issue, but I don’t have enough experience to know how to get it done.

Component itself doesn't support this directly (by passing itself as a parameter of functions for example). But luckily in case of functions passed as props we can use JS feature called "closure" - instead of just function name, call a function which returns another function. In case of events, we can use the feature of Vue allowing us to access special '$event' value when defining handlers.
Like this:
before: <autocomplete data-language="english" :search="searchDefinition" #submit="handleSearchDefinitionSubmit" />
after: <autocomplete data-language="english" :search="getSearchDefinitionFunc('english')" #submit="handleSearchDefinitionSubmit($event, 'english')" />
...and change your methods like this:
getSearchDefinitionFunc(lang) {
return input => this.searchDefinition(input, lang);
},
searchDefinition: function(input, lang) {
console.debug(`searchDefinition called with "${input}" and "${lang}"`);
if (input.length < 3) {
return Promise.resolve([]);
}
// ...don't create new Promise, just return an existing one
return fetch(`/api/v1/definitions?search=${encodeURI(input)}`)
.then(response => response.json())
.then(responseJson => responseJson.definitions);
},
handleSearchDefinitionSubmit: function(result, lang) {
console.log(`Submit: ${lang}`);
}
I also refactored your code a bit as creating new promise is not necessary.
You can find working example here
Could be refactored even more by defining your languages in component data (['english', 'french']) and generating each row using v-for

Related

How to update a row with contenteditable in Vue?

I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>

How to parse JSON data into HTML elemnets

I have a JSON data with HTML.
Like this:
"elements":[
{
"element":".dyno-text",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
How will I parse this JSON data to Bootstrap Layout Design for example: Button will come to real.
Thanks
Uses Vue.component to assembly JSON as one component may be one solution.
But you may need to adjust the HTML template in JSON. Because for supporting some features such as onclick, binding class, it will be one serious headache.
Below is one demo which may provide you some ideas how to reach your goal.
new Vue ({
el:'#app',
data () {
return {
"elements":[
{
"element":"dyno-text",
"value":"This fun here.<br> <button type='button' #click='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success',
"methods": {
// changed onclick to #click, if you still like to use 'onclick' in the template, you have to define window.changeTheme
changeTheme: function(obj) {console.log('clicked')}
}
}
]
}
},
methods: {
createComponent(element) {
/*window.changeTheme = function () {
console.log('clicked by onclick')
}*/
return Vue.component(element.element, {
template: `<div ref="test">${element.value}</div>`,
mounted: function () {
this.$nextTick(() => {
this.$refs.test.querySelector('button.btn').classList.add(element.class)
// or adjust your template in JSON like `<button :class="classes"/>`, then binds element.class to data property=classes
})
},
methods: element.methods
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div class="container">
<div v-for="(item, index) in elements" :key="index">
<component :is="createComponent(item)"/>
</div>
</div>
</div>
You can do something like this; I changed the element in order to create a known htmlElement, so what you do here is to iterate your array of elements and you insert them inside the body, set the value, and toggle the class.
--Edit--
Cleanner solution thanks to pointing it out supercool
Documentation of classList
let elements=[
{
"element":"div",
"value":"This fun here.<br> <button type='button' onclick='changeTheme(this)' data-theme='sketchy' class='theme-link btn btn-light'>Sketchy</button>",
"class": 'text-success'
}
]
elements.forEach((elemen,i)=>{
let createdElement= document.createElement(elemen.element)
createdElement.innerHTML = elemen.value
createdElement.classList.toggle(elemen.class)
document.body.appendChild(createdElement)
})

How to properly delete popup

I have a simple popUp component that I use in my entire app, it get's called by emitting an event on click. There are two types of popUps, success and danger. The success popUp should disappear on it's own after 5 seconds, the danger should get closed when clicked on the x sign. Currently it works the way I have created it, but if the user creates more than one danger popUp, then a success one and again a danger one, the danger one disappears after 5 seconds and not the success one. How can I make so that my success popUp disappears properly after 5 seconds? I am calling it here like this but it seems that it does not delete them correctly:
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
Here is my code:
<template>
<div class="popUp-wrapper">
<div
v-for="item in allItems"
:key="item.id"
:class="['popUp', `popUp--type--${item.newPopUpType}`]"
>
<div class="popUp-side">
<p class="exclamation-mark">!</p>
</div>
<h5 class="popUp-message">{{item.message}}</h5>
<div class="popUp-side">
<p class="closing-x" #click="closePopUp(item)" v-if="item.newPopUpType
=== 'danger'">X</p>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
allItems: []
}),
methods: {
closePopUp(item) {
const index = this.allItems.indexOf(item);
this.allItems.splice(index, 1);
},
onPopUpCall(obj) {
var newPopUp = {
newPopUpType: obj.type,
message: obj.message,
id: obj.id
};
if(obj.type === 'success') {
setTimeout(this.closePopUp, 5000);
}
this.allItems.push(newPopUp);
}
},
created() {
this.$root.$on('call-popUp', this.onPopUpCall);
},
destroyed() {
this.$root.$off('call-popUp', this.onPopUpCall);
}
};
</script>
closePopUp requires an item argument based on your code which you are not providing.
Try this:
if(obj.type === 'success') {
setTimeout(() => this.closePopUp(newPopUp), 5000);
}

Converting from get request from JQuery to Vue.js

I am new to js and would like to convert my JQuery-based js to Vue. I want to send a get request and output back the data. What is the best way of doing this?
Here is the html:
<div>
<div>
<input type="text" id="var1" placeholder="Search...">
</div>
<div>
<button id="submit">Submit</button>
</div>
</div>
<div>
<p>Results</p>
<p id="results"></p>
</div>
Below is my js:
$(document).read(function() {
$('#var1').keypress(function(e) {
if (e.keyCode == 13)
('#submit').click();
});
});
$(document).ready(function() {
$("#submit").click(function() {
var var1 = document.getElementById("var1").value
// sends get request to URL
$.getJSON("URL" + var1, function(search, status) {
console.log(search);
// cont.
$("#results").text(search.results);
});
});
});
EDIT: Here is what I have so far with axios:
function performGetRequest() {
var var1 = document.getElementById('var1').value;
axios.get('URL', {
params: {
id: var1
}
})
.then(function (response) {
console.log(search);
})
}
I am not sure if the above code is correct or how to factor in keypress and click-- is there a simple way to do that?
Well, I am not sure what you want to do with this ajax call, but hopefully this may help you. Vue is data driven, so I always try to focus on that aspect. So this is an example of how you can access and input and send the data using axios.
<div>
<div>
<input v-model='input' type="text" placeholder="Search...">
</div>
<div>
<button #click="searchInput()">Submit</button>
</div>
</div>
<div>
<p>Results</p>
<p >{{ result }}</p>
</div>
you must have those models in your data
// Your data
data() {
return {
input: '',
result: '',
}
}
and your method will look something like this.
searchInput() {
axios({
method: 'GET',
url: url + this.input,
}).then(response => {
this.result = response.data;
}).catch(error => {
//handle error
})
}
So this is a very basic example. you can do the same process in different ways, you could pass the input to the method or loop over the results, but the idea is taking advantage of Vue.js data driven system and think data first.
Hopefully this will help you, remember to escape your input and add necessary validations. Good luck

Vuejs set method return to template

I'm new to Vue and I'm stuck at the moment. For the practice I'm making an app for episode checklist for series. The first part of the app searches series and add one of them to a database. Result for the search gives me a result like this: https://i.stack.imgur.com/QuOfc.png
Heres my code with template and script:
<template>
<div class="series">
<ul>
<li v-for="item in series" :key="item.id">
<img :src="image_url+item.poster_path"/>
<div class="info">
{{item.name}}
<br/>
<h5>{{item.id}}</h5>
Start Date: {{item.first_air_date}}
<br/>
{{getEpisodeNumber(item.id)}}
<br/>
{{getSeasonNumber(item.id)}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "series",
props: ["series"],
data() {
return {
image_url: "https://image.tmdb.org/t/p/w500",
api_key: {-api key-},
episode_url: "https://api.themoviedb.org/3/tv/",
}
},
methods: {
async getEpisodeNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_episodes })
return await json
},
async getSeasonNumber(showID) {
const json = await fetch(this.episode_url + showID + this.api_key)
.then((res) => { return res.json() })
.then((res) => { return res.number_of_seasons })
return await json;
}
},
}
</script>
Methods should return to me a number but they return an object, probably promise object. But when I try to console.log the data in the methods they print a value(int). I need reach this value but I'm stuck. I tried to sort of thinks but it fails every time.
I just create a new component called show and pass item.id to this component. In show component, I use another fetch() to get show data again and now it works like I want.

Categories

Resources