I have a form that adds a new line on a button click. The new line must check for logic independently. In this case, it's chacking the first 2 digits of a barcode and associating it to a data set to see if it matches and return the appropriate value or "nothing found". I can't seem to get this right. First, it's not really evaluating at all. It only gives me "No Agency found" and second, it's doing it for all fields (because they all have the same v-model on a new line add). How can I achieve this so that it evaluates correctly and independently from each other?
Here's the relevant code in my template and script
<div id="q-app" class="q-pa-lg">
<div class="col-6">
<div v-for="(barcodefield, index) in barcodefields" :key="index">
<div class="flex q-pt-lg">
<div class="row flex-center">
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Starting Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.start" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-3">
<div class="column">
<div class="row q-pr-lg items-center">
<label class="text-weight-medium">Ending Roll #:</label>
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName" ref="bcentry"></q-input>
</div>
</div>
</div>
<div class="col-5">
<div class="column">
<label class="text-weight-medium">
Agency:
</label>
<div v-if="agencyName" style="min-height: 40px">
{{ agencyName }}
</div>
<div v-else style="min-height: 40px"></div>
</div>
</div>
<div class="col-1">
<div class="block float-right">
<q-btn v-if="index + 1 === barcodefields.length" #click="addLine" icon="add" color="primary" round />
<q-btn v-else style="min-width: 42px"/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
export default {
data() {
return {
barcodefields: [],
barcodeprefixes: {
"10": "Boston",
"11": "New York",
"13": "Houston",
"14": "Connecticut",
"16": "SIA",
"17": "Colorado",
"18": "Chicago",
"19": "Washington",
},
barcodefield: {
star: "",
end: ""
},
agencyName: "",
};
},
methods: {
addLine() {
this.barcodefields.push({
start: null,
end: null
});
},
showAgencyName() {
var str = this.barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
},
},
mounted() {
this.addLine();
}
}
Here is a codepen for you.
There are several things going on here:
First, as Simon points out, don't name loop variables the same thing as a top-level data element. Instead of <div v-for="(barcodefield, index) in barcodefields" :key="index">, do <div v-for="(item, index) in barcodefields" :key="index">. Then update all the barcodefield.start and barcodfield.end references to item.start and item.end.
Then, you need to get each item to have its own agencyName, instead of all of them referring to the same data.
Update showAgencyName to this:
showAgencyName(item) {
var str = item.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
item.agencyName = "Agency not found";
} else {
item.agencyName = this.barcodeprefixes[res];
}
},
Then you can call it like this: #blur="showAgencyName(item)"
And use it in the html like so:
<div v-if="item.agencyName" style="min-height: 40px">
{{ item.agencyName }}
</div>
(And you can get rid of the top-level barcodefield in the data object, because it's not used anymore.)
Fiddle here:
https://jsfiddle.net/ebbishop/7r1pqx9f/
First you should change name of the for loop variable named "barcodefield", beacause you already have one in your data structure
Second, i would personnaly use a function {{ getAgencyName(b) }} instead of {{ agencyName }} otherwise you will have same agency name for all lines
There are a couple of problem with this.
First, you have a typo in the barcodefield data object. You have "star" instead of "start".
Secondly in the showAgency method you are referencing the this.barcodefield properties but that doesn't exist.
What you can do is pass the index of the barcodefield to the showAgencyName method, and use that inside the method to get the desired barcodefield from the barcodefields array.
In your html:
<q-input outlined square dense maxlength="24"
v-model.trim="barcodefield.end" #blur="showAgencyName(index)" ref="bcentry"></q-input>
and the showAgencyName method:
showAgencyName(index) {
const barcodefield = this.barcodefields[index]
var str = barcodefield.end;
var res = str.substring(0, 2);
if (this.barcodeprefixes[res] == undefined) {
this.agencyName = "Agency not found";
} else {
this.agencyName = this.barcodeprefixes[res];
}
}
UPDATE:
There is another problem that I didn't notice at first. The agencyName is overwritten every time you add a new barcodefield since it is kind of a global value.
I update the Codepen with the simplest solution I could think of. Return the name of the agency from the showAgencyName and use that to print it on the interface. There are many possible other solutions to this (for example add the name the the barcodefields object in the array).
Here is a working Codepen
Related
I am struggling on something that I thought would first be easy to solve.
Imagine a page loading with multiple text areas as:
<div v-for="(item, index) in articles" class="bg-white shadow-lg sm:rounded-lg gap-3 mt-2" :key="index">
<h1>{{item.title}}</h1>
<textarea v-model="form5.fullArticle" :value="item.paragraph" id="topicDescription" :rows="5" cols="80">
</textarea>
</div>
When the v-for loop runs, I am getting a lot of item.paragraph and item.title data and would like to save them to an array.
Problem 1:
I cannot work out how to preload {{item.paragraph}} into a text area. It looks like :value="" is not accepted. and I also tried this <textarea>{{item.paragraph}}</textarea> but no luck (I did not expect this behavior).
Problem 2:
How can I save in my v-for the {item.title}} and {{item.paragraph}} into a new array (but group them together).
Any idea please?
Thanks!
Try to use just v-model (it is the shortcut of :value="text"
#input="event => text = event.target.value") :
const app = Vue.createApp({
data() {
return {
articles: [{id: 1, title: "AAA", paragraph: "aaa"}, {id: 2, title: "BBB", paragraph: "bbb"},{id: 3, title: "CCC", paragraph: "ccc"}],
newArray: []
};
},
methods: {
saveArt(item) {
const found = this.newArray.find(a => a.id === item.id)
if (!found) this.newArray.push(item)
}
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(item, index) in articles" class="bg-white shadow-lg sm:rounded-lg gap-3 mt-2" :key="index">
<h5>{{item.title}}</h5>
<textarea v-model="item.paragraph" id="topicDescription" :rows="3" cols="30">
</textarea>
<button #click="saveArt(item)">Add</button>
</div>
<p>new array</p>
{{ newArray }}
</div>
I have data of nested objects in which I am getting data of my requirement, Now I want to loop through that object and render on UI, But I am not getting Idea how to do that as the UI is fully Dynamically dependent on data.
My data
const countData = {
"current_month": {
"total_employes": 6,
"ariving": "+3",
"exiting": "-1",
"current_month": "April 2020",
"__typename": "CurrentMonthTotalEmp"
},
"previous_month": {
"total_employes": "3",
"arrived": "+2",
"exited": "-2",
"previous_month": "March 2020",
"__typename": "PrevMonthTotalEmp"
},
"__typename": "CurPrevMonthEmps"
}
to make it as array I doing this
const finalData =Object.entries(countData);
Now I want to loop this
please check my code-sandbox for full code
here in my code-sandbox I am rendering statically with HTML
Most of your React applications will use data to render a UI. That's what React excels in.
Step 1: Create a reusable component
You'll have to create a React component which receives the props for each month.
(total_employees, ariving, exiting and current_month) and renders them correctly.
for example:
const MonthComponent = ({ total_employees, ariving, exiting, current_month }) => {
//above return you can alter your data however you want using normal javascript
return (
//in 'return' you can return HTML or JSX to render your component.
<div>
<p>{total_employees}</p>
<p>{ariving}</p>
<p>{exiting}</p>
<p>{current_month}</p>
</div>
);
};
Step 2: Loop over your data and render your reusable component
Now in your parent component you can loop over your array of data.
const ParentComponent = () => {
const countData = {
"current_month": {
"total_employes": 6,
"ariving": "+3",
"exiting": "-1",
"current_month": "April 2020",
"__typename": "CurrentMonthTotalEmp"
},
"previous_month": {
"total_employes": "3",
"arrived": "+2",
"exited": "-2",
"previous_month": "March 2020",
"__typename": "PrevMonthTotalEmp"
},
"__typename": "CurPrevMonthEmps"
}
const months = Object.keys(countData); // ["current_month", "previous_month"]
return (
months.map(month => (
// map over months and render your reusable component for each month
<MonthComponent {...countData[month]} />
))
);
};
Note: Spreading over ...countData[month] is a shorthand property to pass every key-value pair of countData[month] as a prop. I could also have written:
<MonthComponent
total_employees={countData[month].total_employees}
arrived={countData[month].arrived}
exited={countData[month].exited}
previous_month={countData[month].previous_month}
/>
There is a lot of code duplication, we want to reduce that (DRY Principle). First, find the common code that abstractly describes your UI, i.e. a component that has a month/year label, some arrive/exit fields & labels, and an employee count. Convert what you want displayed to a component that takes these "standardized" props.
const MonthData = ({
arrive,
arriveLabel,
exit,
exitLabel,
totalEmployees,
month,
}) => (
<Fragment>
<label className="monthYr" align="left">
{month}
</label>
<div className="row countDiv">
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 total">
<label className="totalHeading">Total employees</label>
<div className="totalCount">{totalEmployees}</div>
</div>
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<button className="btn btn-outline-secondary button_Count form-control">
{arriveLabel}
<span className="badge badge-pill badge-primary ml-2">
{arrive}
</span>
</button>
<button className="btn btn-outline-secondary form-control">
{exitLabel}
<span className="badge badge-pill badge-primary ml-2">
{exit}
</span>
</button>
</div>
</div>
</Fragment>
);
I don't think I'd map these as you have different labeling for previous vs. current months, and you only ever display 2 months at a time. Just destructure from the countData the two months' data.
const { current_month, previous_month } = countData;
return (
<div className="row container-fluid">
<div className="form-control graphHeading"> Manpower Graph</div>
<div className="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<div className="row widthContainer">
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<MonthData
arrive={previous_month.arrived}
arriveLabel="arrived"
exit={previous_month.exited}
exitLabel="exited"
month={previous_month.previous_month}
totalEmployees={previous_month.total_employees}
/>
</div>
<div className="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<MonthData
arrive={current_month.arriving}
arriveLabel="arriving"
exit={current_month.exiting}
exitLabel="exiting"
month={current_month.current_month}
totalEmployees={current_month.total_employees}
/>
</div>
</div>
</div>
</div>
);
You can do something like this in your JSX code:
{finalData.map(value => (
<div>{value.something}</div>
))}
you can use :
{
Object.keys(countData).map(key=>{
const month = countData[key]
return(
//you have access to month
<div>{month.total_employes}</div>
);
})
}
First, you need to convert the countData into a proper structure over which we can run our loop. to do that you need to change how you convert it to array to the following
const finalData = Object.values(countData)
After doing so we can now loop over the finalData variable using a map function like this.
{finalData.map(data => (
<div>{data.total_employes}</div>
<div>{data.ariving}</div>
))}
Moreover to handle missing key/values in the object you can do the following
{finalData.map(data => (
<div>{data.total_employes ? data.total_employes : 'NA'}</div>
<div>{data.ariving ? data.ariving : 'NA'}</div>
))}
Hope this helps
I created a sample in codepen with local data. Hoping it still works for you for troubleshooting but I am actually using vuex and an API endpoint that contains the data. I just can't share the API of course.
Anyway, so I am retrieving a collection of application numbers from an API and displaying them in removable chips. The form (not shown in the example) has a field that I can add more applications to this list. That part works fine. My problem is removing an application.
When someone enters an application by mistake, the user can click on the X in the chip to remove it and a manager can come and approve the removal. That part I haven't got to yet but I believe I can do that once I get there as long as I figure this small part first.
In order to remove the right application, I need to capture the one that the user clicked on so I can pass it to the API and then I can pop() it from the state in a mutation. Notice that I am succesfully capturing the right application in a console.log, but I can't capture it in the modal dialog. How can I do this?
<div id="q-app">
<div class="q-pa-md">
<span v-for="(batch, index) in applications" :key="index">
<q-chip removable dense outline color="grey-9" #remove="remove(batch.value)">
{{batch.value}}
</q-chip>
<!-- Manager Approval Dialog -->
<q-dialog v-model="removeApplication" persistent>
<q-card class="q-pa-lg" style="width: 600px">
<q-card-section class="row justify-center items-center">
<span class="q-ml-sm">
Enter your manager credentials to remove application number: {{batch.value}} from the current batch.
</span>
<q-form #submit="onSubmit" class="q-gutter-md q-mt-md" style="width: 100%">
<div class="row items-start justify-center">
<label class="col-4 text-weight-medium form-label">Admin Username:</label>
<div class="col-8">
<q-input
outlined
v-model="username"
color="cts-purple-faint"
bg-color="cts-purple-faint"
square
dense
type="text"
id="username">
</q-input>
</div>
</div>
<div class="row items-start justify-center">
<label class="col-4 text-weight-medium form-label">Admin Password:</label>
<div class="col-8">
<q-input
outlined
color="cts-purple-faint"
bg-color="cts-purple-faint"
square
dense
type="password"
v-model="password">
</q-input>
</div>
</div>
</q-form>
</q-card-section>
<q-card-actions align="right" class="q-pa-lg">
<q-btn label="Decline" color="secondary" v-close-popup></q-btn>
<q-btn label="Approve" color="primary" type="submit" v-close-popup></q-btn>
</q-card-actions>
</q-card>
</q-dialog>
</span>
</div>
</div>
In my script
new Vue({
el: '#q-app',
data() {
return {
appsinbatch:{
applications:[
{"value": 741000, "selected": true },
{"value": 742000, "selected": true },
{"value": 743000, "selected": true }
]
},
username: "",
password: "",
removeApplication: false,
}
},
computed: {
applications() {
return this.appsinbatch.applications;
},
},
methods: {
onSubmit() {
//remove the application selected
},
remove (applications) {
console.log(`${applications} has been removed`)
this.removeApplication = true
},
}
})
Here is the codepen playground Thanks in advance!
You have a one-to-one relationship of chip to form. when you click on any of the chips, it toggles the last added form/card. Instead, you should have one form and reuse a single form.
So for this solution, I used a computed and the model. I'm not familiar with quasar, but haven't found a way to toggle visibility based on whether an object is set, and I think it requires use of a model with a boolean value. So I wrapped the card content with a v-if as-well. This was needed, because otherwise selectedApplication.value will be rendered even if the selectedApplication is null.
<!--
Forked from:
https://quasar.dev/vue-components/chip#Example--Outline
-->
<div id="q-app">
<div class="q-pa-md">
<q-chip removable dense outline color="grey-9"
#remove="remove(index)"
v-for="(batch, index) in applications"
:key="index"
>{{batch.value}}</q-chip>
<!-- Manager Approval Dialog -->
<q-dialog v-model="removeApplication" persistent>
<q-card class="q-pa-lg" style="width: 600px" v-if="selectedApplication">
<q-card-section class="row justify-center items-center">
<span class="q-ml-sm">
Enter your manager credentials to remove application number: {{selectedApplication.value}} from the current batch.
</span>
<q-form #submit="onSubmit" class="q-gutter-md q-mt-md" style="width: 100%">
<div class="row items-start justify-center">
<label class="col-4 text-weight-medium form-label">Admin Username:</label>
<div class="col-8">
<q-input
outlined
v-model="username"
color="cts-purple-faint"
bg-color="cts-purple-faint"
square
dense
type="text"
id="username">
</q-input>
</div>
</div>
<div class="row items-start justify-center">
<label class="col-4 text-weight-medium form-label">Admin Password:</label>
<div class="col-8">
<q-input
outlined
color="cts-purple-faint"
bg-color="cts-purple-faint"
square
dense
type="password"
v-model="password">
</q-input>
</div>
</div>
</q-form>
</q-card-section>
<q-card-actions align="right" class="q-pa-lg">
<q-btn label="Decline" color="secondary" v-close-popup></q-btn>
<q-btn label="Approve" color="primary" type="submit" v-close-popup></q-btn>
</q-card-actions>
</q-card>
</q-dialog>
</div>
</div>
new Vue({
el: '#q-app',
data() {
return {
appsinbatch:{
applications:[
{"value": 741000, "selected": true },
{"value": 742000, "selected": true },
{"value": 743000, "selected": true }
]
},
selection: null,
username: "",
password: "",
removeApplication: false
}
},
computed: {
applications() {
return this.appsinbatch.applications;
},
selectedApplication() {
if (Number.isInteger(this.selection) && this.selection < this.applications.length){
this.removeApplication = true;
return this.applications[this.selection];
}
this.removeApplication = false;
return false
},
},
methods: {
onSubmit() {
//remove the application selected
},
remove (index) {
this.selection = index;
var application = this.applications[index]
this.selection = index;
console.log(`${application.value} has been removed`)
this.removeApplication = true
},
}
})
I am having a weird issue with my single file Vue component where when I update an unrelated variable (Vue.js variable), all of my inputs (stuff I typed in, not the elements themselves.) disappear.
I have worked with Vue single file components for a few months now and I have never ran into something like this. Here is the weird part, the variable gets updated successfully as expected, but if I include the variable inside of the template at all that is when all the inputs disappear.
The function is looking up 'agents', then letting the user know how many records have been found and whether or not he/she would like to view them. If the user clicks on the "View" link, then they are shown a bootstrap-modal which shows them the records so that they could select one.
Here is what I have already tried:
Removing all ids from the inputs and using only refs="" to get the values.
changing the 'agents' variable name. Thought maybe it was conflicting with some rogue global or something.
Double checked that the parent component and this component was not being re-rendered. I did that by putting console.log() comments in the mounted() function and as expected it is only rendering once.
Watched the key using Vue dev tools extension to make sure the key was not being changed somehow.
Executed the searchAgent() function in a setTimeout(()=>{},5000) to see whether my use of _.debounce was causing issues.
Used jquery to fetch the values from the inputs instead of refs.
Assign the new records to a local variable agentsArray, then pass that into a function which assigns it to the vue variable 'agents' (its basically a needlessly longer route to the same thing but I thought WHY NOT TRY IT)
Double checked all my uses of 'this' to make sure that I was not accidentally using the wrong this and causing some unknown bug.
Using V-model, but using that doesn't help because I would still have to include the 'agents' inside of the modal in the template.
Using a v-if statement to render the modal HTML in the template only after 'agents' is not an empty array.
Update: Based on a suggestion, removed the function from inside of $(document).ready() inside of the mounted() function.
Template:
<template>
<div class="Q mb-0">
<i class="far fa-question-circle"></i>
<center>
<p class="display-1">{{title}}</p>
{{prefix}} is Representing Themselves Skip This Step.
<div id="searchResults" class="hidden" style="margin-top:5px;">
<a id="searchResultsText" class="SkipStepStyle"></a>
<a
id="viewSearchResults"
style="font-weight: bold;"
class="hidden SkipStepStyle"
v-on:click="displayAgents"
>
View
</a>
</div>
<form class="mt-2 BuyerSellerAgentInfo">
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="NameFirst"
type="text"
:name="prefix+'sAgent_NameFirst'"
placeholder="FIRST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameFirst'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="NameLast"
type="text"
:name="prefix+'sAgent_NameLast'"
placeholder="LAST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameLast'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Email"
type="text"
:name="prefix+'sAgent_Email'"
placeholder="EMAIL ADDRESS"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Email'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="Phone"
type="text"
:name="prefix+'sAgent_Phone'"
maxlength="14"
placeholder="PHONE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Phone'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Brokerage"
type="text"
:name="prefix+'sAgent_Brokerage'"
placeholder="AGENT'S BROKERAGE"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Brokerage'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="License"
type="text"
:name="prefix+'sAgent_License'"
placeholder="AGENT'S LICENSE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_License'].Answer"
>
</div>
</div>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_ID'"
:value="currentAnswers[prefix+'sAgent_ID'].Answer || '1'"
>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_BrokerageID'"
:value="currentAnswers[prefix+'sAgent_BrokerageID'].Answer || '1'"
>
</form>
</center>
<div v-if="agents.length > 0" class="modal" id="AgentPopup">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body">
<center>
<h5 class="d-inline-block mb-3">Select {{prefix}}'s Agent:</h5>
</center>
<button v-on:click="displayCategories" type="button" class="close shadow" data-dismiss="modal">×</button>
<ul>
<li v-for="agent in agents">{{ agent.NameFull || agent.NameFirst+' '+agent.NameLast }}</li>
<li class="border-0">{{prefix}}’s agent is not in this list</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Script:
import _ from 'lodash';
export default {
name: "AgentInformation",
props: {
friendlyIndex: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
answerChoices:{
type: Array,
default: () => []
},
currentAnswers: {
type: Object,
default: () => {},
},
prefix: {
type: String,
default: '',
},
token: {
type: String,
default: '',
},
},
methods: {
debounceFunction(func,timer){
let vueObject = this;
return _.debounce(()=>{
vueObject[func]();
},timer);
},
displayCategories(){
$('.categories').show();
},
displayAgents(){
$('.categories').hide();
$('#AgentPopup').modal({backdrop:'static',keyboard:false});
},
searchAgent() {
let vueObject = this;
console.log('calling searchAgent()');
let agentSearchRoute = correctVuexRouteURL(vueObject.$store.getters.routeName('search.agent'));
if (!agentSearchRoute) genericError('Agent Search Route Not Found. Error code: a-s-001');
else
{
let dataObject = {
NameFirst: this.$refs.NameFirst.value,
NameLast: this.$refs.NameLast.value,
Email: this.$refs.Email.value,
Phone: this.$refs.Phone.value,
License: this.$refs.License.value,
_token: this.token,
};
console.log(dataObject);
vueObject.$http.post(agentSearchRoute, dataObject).then((r) => {
let status = r.body.status;
if (status == 'success')
{
vueObject.agents = r.body.agents;
let searchResultsContainer = $('#searchResults');
let searchResultsText = $('#searchResultsText');
let viewSearchResultsLink = $('#viewSearchResults');
let agentCount =
vueObject.agents.length;
searchResultsContainer.removeClass('hidden');
if(agentCount > 0)
{
let rText = agentCount > 1 ? 'records' :
'record';
searchResultsText.text(agentCount+' '+rText+'
found.');
viewSearchResultsLink.removeClass('hidden');
}
else
{
if (!viewSearchResultsLink.hasClass('hidden'))
viewSearchResultsLink.addClass('hidden');
searchResultsText.text('No records found.');
}
}
});
}
},
},
data(){
return {
agents: [],
}
},
mounted() {
let vueObject = this;
console.log('mounted');
$(document).ready(function(){
$('#phone').mask('(###)-###-####');
$('.AnswerChoice').on('input', () => {
let searchAgent =
vueObject.debounceFunction('searchAgent',500);
searchAgent();
});
});
}
}
It seems that the issue is the template does not like the 'agents' variable to be inside of it. When I remove the modal container or just the references to 'agents' it works as expected. If I change the variable name it does not solve the issue.
Any thoughts on the solution? Am I missing something blatantly obvious and stupid?!
Edit: Something I forgot to add, I don't think affects this in any way but it is worth mentioning. This component is rendered dynamically inside of the parent.
Rendering the component:
<component
v-for="(component,i) in selectedView"
:is="component['Component']"
v-bind="bindAttributes(component)"
:key="component.ID"
>
</component>
Changing agents will cause the whole template to be re-run. Not just the bits that mention agents, everything in that template will be updated.
When a user types into one of your <input> elements you aren't storing that value anywhere. You've got a :value to poke the value in but you aren't updating it when the value changes. The result will be that when Vue re-renders everything it will jump back to its original value.
You should be able to confirm this by setting the initial values within currentAnswers to be something other than empty. You should find that whenever agents changes it jumps back to those initial values.
The solution is just to ensure that your data is kept in sync with what the user types in. Typically this would be done using v-model but that's a bit tricky in this case because you're using a prop for the values and you shouldn't really be mutating a prop (one-way data flow). Instead you should use events to communicate the required changes up to whichever component owns that data.
Here is a simple test case to demonstrate the issue in isolation:
new Vue({
el: '#app',
data () {
return {
count: 0,
value: 'initial'
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<input :value="value">
<button #click="count++">Click count: {{ count }}</button>
</div>
I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>