Conditionally return a Vue component from methods - vue.js

I have the following code:
Vue.component('abc', {
...
template: `
<checkbox label="unrelated" />
...
<div>{{renderTypeValue(type)}}</div>
`
methods: {
renderTypeValue(type, val) {
if(type === "number") return parseInt(val, 10);
if(type === "text") return val;
//some others...
if(type === "checkbox") return ???
I'd like to return a Vue component here. I use the same checkbox above, but I can't figure out the syntax to be able to return it from the Vue method.

Related

Translate inside a computed with vuejs

I want to translate the status in this code:
computed: {
variant() {
if (status === this.$t("vue.pending") || this.$t("vue.not_contract")) {
return "warning";
} else if (status === this.$t("vue.confirmed")) {
return "success";
} else if (status === this.$t("vue.expired") || this.$t("vue.declined")) {
return "error";
} else {
return "dark";
}
},
},
i tried put this.$t but it doesn't works.
the template is :
<div class="mt-2">
<b-badge :variant="variant">
{{ contract.status }}</b-badge>
</div>
Can someone help me?
Console return this error:
[vue-i18n] Value of key 'vue.not_contract' is not a string or function !
[vue-i18n] Cannot translate the value of keypath ‘vue.not_contract’. Use the value of keypath as default.
I arranged like this:
template:
<div class="mt-2">
<b-badge :variant="variant">
{{ $t(contract.status) }}</b-badge>
</div>
Script:
computed:{
variant() {
if (status === "pending" || "not_contract") {
return "warning";
} else if (status === "confirmed") {
return "success";
} else if (status === "expired" || "declined"){
return "error";
} else {
return "dark";
}
},
}
Make sure you declare status in your data section, even if its value is undefined, or it will not be reactive.
data(){
return {
status: undefined
// or status: 'pending', etc
}
}

How to run a specific function if the component has been called by a specific component and not by other components in Vue?

I have a component called select-diagnosis which is used by many different components.
When select-diagnosis is called by a specific component called PtdTreatment, it needs to run a specific function inside the fetchDiagnosis function, while when called by other components it will not run that specific function.
The fetchDiagnosis needs to understand that select-diagnosis component has been called by the PtdTreatment component.
How to do something like that?
This is the code from PtdTreatment component:
<el-form-item
label="diagnosis"
prop="dimission_diagnosis"
v-if="form.data_dimission">
<select-diagnosis
v-model="form.diagnosis_dimission"
:defaultValue="_.get(record, 'clean_diagnosis_dimission')"
/>
</el-form-item>
And this is the select-diagnosis component:
<template>
<el-select
v-bind="$attrs"
:value="value"
#change="onChange"
#clear="onClear"
clearable
filterable
remote
:remote-method="fetchDiagnosis"
:loading="loadingSelect"
>
<el-option
v-for="item in items"
:key="`diagnosis-${item.id}`"
:label="item.code + ' - ' + item.description"
:value="item.code"
>
</el-option>
</el-select>
</template>
<script>
export default {
name: "SelectDiagnosis",
inheritAttrs: false,
props: ["value", "defaultValue"],
data() {
return {
loadingSelect: false,
items: []
};
},
methods: {
fetchDiagnosis(query) {
const valid = query !== "" && query.length > 2;
if (!valid) return;
this.loadingSelect = true;
let params = { string: query };
axios
.get("/config/diagnosi", { params })
.then(({ data }) => {
//pseudo code
// if this component is called by **select-diagnosis** then do this
this.items = data.filter(diagnosi => {
const code = diagnosi.codice.replace(/\b0+/g, "");
if (code.length >= 4) {
return diagnosi;
}
});
// else do this
this.items = data;
})
.finally(() => (this.loadingSelect = false));
},
onChange(x) {
this.$emit("input", x);
},
onClear() {
this.$emit("input", null);
this.items = [];
}
},
watch: {
defaultValue: {
immediate: true,
handler(newVal, oldVal) {
if (newVal && oldVal === undefined) {
this.items = [newVal];
this.$emit("input", newVal.codice);
}
}
}
}
};
</script>
There are a number of ways to accomplish this, the two that come to mind immediately use props.
You could pass a filterDiagnoses boolean prop to select-diagnosis. If it's true, run the filter logic.
<select-diagnosis v-model="form.diagnosis_dimission" :defaultValue="_.get(record, 'clean_diagnosis_dimission')" :filterDiagnoses="true" />
You could invert control to the parent function and expose a filterFn callback prop - the parent function passes a function prop to the child that the child will call upon fetching the diagnoses (this feels cleaner and more extensible):
/* in the PtdTreatment component */
/* ... */
methods: {
filterDiagnosis(data) {
// filter data
},
}
/* in the PtdTreatment template */
<select-diagnosis v-model="form.diagnosis_dimission" :defaultValue="_.get(record, 'clean_diagnosis_dimission')" :filterFn="filterDiagnosis" />
/* in the select-diagnosis component */
fetchDiagnosis(query) {
const valid = query !== "" && query.length > 2;
if (!valid) return;
this.loadingSelect = true;
let params = { string: query };
axios
.get("/config/diagnosis", { params })
.then(({ data }) => {
if (this.filterFn) {
this.items = this.filterFn(data);
} else {
this.items = data;
}
})
.finally(() => (this.loadingSelect = false));
},
}
You can set a prop on the child component which specifies the 'identity' of the parent component, then test for that in the child:
<select-diagnosis
v-model="form.diagnosis_dimission"
:defaultValue="_.get(record, 'clean_diagnosis_dimission')"
parent="PtdTreatment"
/>
Then in the child (simplified example):
export default {
props: ["value", "defaultValue", "parent"],
methods: {
fetchDiagnosis(query) {
if (this.parent === "PtdTreatment") {
// Parent-specific code here
}
}
}
}

Run componentDidUpdate only on changes within the Component

I'm trying to learn StencilJs and have created an "editable text" Component like this.
import { Component, h, Prop, Element } from '#stencil/core';
#Component({
tag: 'app-input',
styleUrl: 'app-input.scss',
shadow: true,
})
export class AppInput {
#Element() el: HTMLElement;
#Prop() editMode = false;
#Prop() value: string;
private textInput: HTMLInputElement;
private label: HTMLDivElement;
componentDidUpdate() {
if (this.textInput) {
this.textInput.focus();
} else {
this.label.focus();
}
}
eventHandler(event: KeyboardEvent | FocusEvent): void {
if (event instanceof KeyboardEvent) {
if (this.editMode) {
if (event.code === 'Enter') {
this.value = (event.target as HTMLInputElement).value;
this.editMode = false;
} else if (event.code === 'Escape') {
this.editMode = false;
}
} else {
if (['Space', 'Enter'].some(key => key === event.code)) {
this.editMode = true;
}
}
} else if (event instanceof FocusEvent) {
this.editMode = false;
}
}
render() {
if (this.editMode) {
return (
<div>
<input
type="text"
ref={el => this.textInput = el as HTMLInputElement}
value={ this.value }
onKeyDown={(event) => this.eventHandler(event)}
onBlur={(event) => this.eventHandler(event)}></input>
</div>
)
} else {
return (
<div
tabindex="0"
ref={el => this.label = el as HTMLDivElement}
onKeyDown={(event) => this.eventHandler(event)}
onClick={() => this.editMode = true} >{ this.value }</div>
);
}
}
}
The problem is that if a parent component updates then so does this and componentDidUpdate runs, setting focus when it shouldn't. Is there a way I can tell (maybe by custom decorators) componentDidUpdate to only run if the update was triggered from within this component? Or is there another way to go about it?

Vuetify v-alert trying to render if method value returns true

Trying to render the v-alert if the value returns true in the method. Currently, nothing is displaying, what am I missing?
My Code:
<v-container>
<v-row style="margin:0px;">
<template v-if="isTestBusy()">
<v-alert type="info" color="#fb8c00" style="font-size: 14px;">
test initiated, waiting for test results
</v-alert>
</template>
</v-row>
</v-container>
mounted () {
this.pingTimer = setInterval(async function () {
that.pendingTests = await new Promise(function (resolve) {
resolve(utils.getPendingTests(that.accountnumber, that.testType))
})
var arrayLength = that.pendingTests.Table1.length;
for (var i = 0; i < arrayLength; i++) {
if (that.pendingTests.Table1[i].DiaType === that.pgType) {
that.isTestBusy(that.pendingTests.Table1[i].CPEId)
}
}
}, 5000)
},
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
return true
}
if ((retVal === 200) && (retValRes === '')) {
return true
}
return false
} catch (error) {
return false
}
},
}
Just extra information the method and mounted is working. Its just the HTML part I am uncertain about of what exactly needs to be done to make it render.
v-alert has its own value attribute that does this and you won't need the template with v-if.
try this:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" :value="isTestBusy()" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
also you need to call your method. it's probably best if you use watch: and call your method whenever your table changes and create a boolean variable inside your data() and put the returned value of your method inside it and have your alert's value attribute to work with it. (and Vue will react to changes to variable defined inside data).
do it like:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" :value="busy" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
data() {
busy: false,
}
watch: {
pendingTests: {
deep: true,
handler: function(val) {
//put some if here to match your situation and then call your method like:
this.busy = this.isTestBusy(yourParams)
}
},
}
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
return true
}
if ((retVal === 200) && (retValRes === '')) {
return true
}
return false
} catch (error) {
return false
}
},
}
optional: I suggest you also use the transition attribute with v-alert if you want. it just makes it look better.
Fixed it:
<v-alert type="info" color="#fb8c00" style="font-size: 14px;" v-if="isTestBusy" transition="scale-transition">
test initiated, waiting for test results
</v-alert>
data () {
return {
isTestBusy: false
}
},
mounted () {
this.pingTimer = setInterval(async function () {
that.pendingTests = await new Promise(function (resolve) {
resolve(utils.getPendingTests(that.accountnumber, that.testType))
})
var arrayLength = that.pendingTests.Table1.length;
for (var i = 0; i < arrayLength; i++) {
if (that.pendingTests.Table1[i].DiaType === that.pgType) {
that.isTestBusy(that.pendingTests.Table1[i].CPEId)
}
}
}, 5000)
},
methods : {
isTestBusy(cpe) {
try {
let tmp = this.pendingTests.Table1
// console.log(tmp)
let retVal = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step2ResponseCode
//console.log(retVal)
let retValRes = tmp.find(x => x.CPEId === cpe && x.DiaType === this.pgType).Step4Result
//console.log(retValRes)
if (retVal === 0) {
this.busyPingTest = true
return true
}
if ((retVal === 200) && (retValRes === '')) {
this.busyPingTest = faslse
return true
}
return false
} catch (error) {
return false
}
},
}

Conditional List Rendering with Vuex state

Initially I have a list of animations stored in Cards. Each card has a few tags. From a top filter menu I want to be able to display only Cards, that match with the filter that has been set. A vuex state holds the information of all currently applied filters.
My markup looks like this:
<div class="Feed__cards">
<Card
v-for="(card, index) in filteredCards"
:key="card.id"
:id="card.id"
:name="card.name"
:tags="card.tags"
:description="card.description"
:video="card.video"
:valueset="getValueSet(index)"
class="Feed__card"
>
</Card>
In my methods I wanted to do something like this (activeTagsElements is a computed property, mapState from Vuex):
compare(tags) {
tags.forEach(tag => {
if(this.activeTagsElements.includes(tag)){
return true
}
})
},
getAllAnimations() {
this.$axios.get('/api/animations/all')
.then(response => {
this.allCards = response.data;
this.allMaxIndex = response.data.length - 1;
response.data.forEach((animation, index) => {
this.getTags(animation.id, index, this.allCards, this.allMaxIndex, 'all');
});
}).catch((error) => {
console.log(error)
})
},
getTags(id, index, slot, max, type) {
this.$axios.get('/api/animationtags/' + id)
.then(response => {
slot[index].tags = response.data.map(tag => {
return tag.name;
});
if(index == max && type == 'initial'){
this.initialLoad = true;
} else if(index == max && type == 'all') {
this.allLoad = true;
}
}).catch((error) => {
console.log(error);
})
}
I also tried watching the change of the vuex state but couldn't get to the point of how to get the actual tags from each element to compare it to the vuex state.
Any hints are very appreciated.
The vue way is to create a computed property of the filtered tasks. Then you just v-for throught them.
<Card
v-for="card in filteredAnimations"
:key="card.id"
:id="card.id"
:name="card.name"
:tags="card.tags"
>
</Card>
This should work, and is efficient as it will only rerun filteredTags if either Vuex store changes or the activeElements from your filter changes.
<script>
export default {
data() {
return {
activeTagsElements: []
}
},
computed: {
animations() {
return this.$store.animations
},
filteredAnimations() {
return this.animations.filter(animation => {
return this.activeTagsElements.includes(animation)
})
}
}
}
</script>