Using v-for and v-show to hide/show additional text inside an p element - vue.js

I have this html code
<tr v-for="(help, index) in helps">
<td scope="row">{{ help.ID }}</td>
<td scope="row">{{ help.Date | formatDateWithTime }}</td>
<td>
<p #click="fullTextFun(index)" v-show="help.FullText">{{ help.Text.substring(0,16) + '...' }}</p>
<p #click="fullTextFun(index)" v-show="!help.FullText">{{ help.Text }}</p>
</td>
</tr>
I want to be able to show the full text when someone click on the current p element. This is my vue function
fullTextFun: function(index) {
this.helps[index].FullText = !this.helps[index].FullText;
},
It doesn't work. I also tried to do it using this code
<span #click="fullTextFun(help)" v-show="help.FullText">{{ help.Text.substring(0,16) + '...' }}</span>
fullTextFun: function(item) {
item.FullText = !item.FullText;
},
But again without any luck. It seems the v-show function don't care about the status of help.FullText
When I load the data I don't have FullText variable in my helps array. I don't know if this is the problem
This is what it is inside my helps variable when first loaded
[{"ID":"2","Date":"2019-05-15
17:27:29","Text":"randomText"},{"ID":"4","Date":"2019-05-17
09:53:59","Text":"some text"}]

It might be Vue reactivity issue.
fullTextFun: function(index) {
this.helps[index].FullText = !this.helps[index].FullText;
this.helps = JSON.parse(JSON.stringify(this.helps))
}
Basically Vue only updates if you change the reference.
When you update this.helps[index].FullText, this.helps still points to old object reference, and Vue can't recognize the change.
Another solution is using Vue.set
Vue.set(this.helps[index], 'FullText', !this.helps[index].FullText)
You can read more at Vue document

Related

search bar filter using Vue js

I am trying to filter a table that get data from api and I tried this solution but it doesnt work.
I couldnt find where the problem is and if I pass the search input event listener
and here is my table component :
<template>
<table class="mt-12 border-2 border-gray-600">
<thead>
<tr>
<th v-for="header in data.headers" :key="header" class="text-left border-l-2 border-gray-600 border-b-2 border-gray-600 bg-red-400 ">{{ header }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(rows, index) in data.rows" :key="index">
<td v-for="row in rows" :key="row" class="border-l-2 border-gray-600" >{{ row }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
data: Object,
default: {}
}
}
</script>
<style src="../assets/tailwind.css"/>
My question:
If anyone can help me to define my problem and solve this ?
Thanks for help.
You can use your data as computed where you pass it as a prop.
<BaseTable :data='data' />
Here instead of using like this create a computed which can be filteredData.
<BaseTable :data='filteredData' />
and in your props you can simply filter it or just send the data as it is.
computed: {
filteredData() {
if(this.search) {
// filter your data as you want and return
}
else // return your main data
}
}
Here is a working simple example:
https://codesandbox.io/s/filterlist-example-vdwhg?file=/src/App.vue
And change your include to includes.
what error you are getting ? I think you are using this.search inside filteredRows function which is not a vue instance property. it should be this.data.search. The search is used with V-model as well so you should declare it outside data (JSON object).

Vue Reactivity issue, setting array element property

I'm having a reactivity issue in following example. I can't find what I'm doing wrong. Am I setting the vue data correctly or do I need to do something else?
I have an object model as follows;
export default {
data () {
return {
filteredSkillTiers: [{
name: '',
categories: [{
name: '',
recipes: [{ name: '', profit: '' }]
}]
}],
recipeFilterText: ''
}
}
In created() method, I fill this filteredSkillTiers with real data. When I check as console.log(this.FilteredSkillTiers), it seems fine.
And, in my template, I have a button with #click="CalculateRecipe(i, j, k) which seems to be working perfect.
Here is my template;
<div
v-for="(skilltier,i) in filteredSkillTiers"
:key="i"
>
<div
v-if="isThereAtLeastOneFilteredRecipeInSkillTier(skilltier)"
>
<h3> {{ skilltier.name }} </h3>
<div
v-for="(category,j) in skilltier.categories"
:key="j"
>
<div
v-if="isThereAtLeastOneFilteredRecipeInCategory(category)"
>
<v-simple-table
dense
class="mt-3"
>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">{{ category.name }}</th>
<th class="text-left">Click to Calculate</th>
<th class="text-left">Estimated Profit</th>
</tr>
</thead>
<tbody>
<tr v-for="(recipe,k) in category.recipes" :key="k">
<template
v-if="recipe.name.toLowerCase().includes(recipeFilterText.toLowerCase())"
>
<td>{{ recipe.name }}</td>
<td>
<v-btn
dense
small
#click="CalculateRecipe(i, j, k)"
>
Calculate
</v-btn>
</td>
<td>{{ filteredSkillTiers[i].categories[j].recipes[k].profit }}</td>
</template>
</tr>
</tbody>
</template>
</v-simple-table>
</div>
</div>
</div>
</div>
And here is my method;
CalculateRecipe (skilltierIndex, categoryIndex, recipeIndex) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('profitResult')
}, 50)
}).then((profit) => {
this.filteredSkillTiers[skilltierIndex].categories[categoryIndex].recipes[recipeIndex].profit = 'new Profit'
console.log(this.filteredSkillTiers[skilltierIndex].categories[categoryIndex].recipes[recipeIndex].profit)
})
},
When I log to console, I can see that I'm modifying the object correctly. But my updated value is not reflected in the rendered page.
There is this thing I suspect, if I update an irrevelant component in this page (an overlaying loading image component), I can see that rendered table gets updated. I want to avoid that because updating a component returns me to top of the page.
<td>{{ filteredSkillTiers[i].categories[j].recipes[k].profit }}</td>
This profit property seems not reactive. I'm really confused and sorry that I couldn't clear the code more, Thanks.
Here's the article describing how exactly reactivity works in Vue 2.x. And yes, (in that version) you should never update a property of tracked object directly (unless you actually want the changes not to be tracked immediately).
One way (mentioned in that article) is using Vue.set() helper function. For example:
Vue.set(this.filteredSkillTiers[skilltierIndex]
.categories[categoryIndex].recipes[recipeIndex], 'profit', 'new Profit');
You might consider making this code far less verbose by passing recipe object inside CalculateRecipe function as a parameter (instead of those indexes), then just using this line:
Vue.set(recipe, 'profit', promiseResolutionValue);

Vue checkbox not updating with data change

Having problems with my data in Vue updating the UI, but only checkboxes seem to have this problem.
I'm trying to allow only one checkbox to be checked at one time, therefore I'm setting all the checkboxes to false upon clicking one of them. However, this works in the data but isn't rendered correctly in the checkboxes.
HTML:
<table class="buildings-modify--table table-full table-spacing">
<thead>
<tr>
<th>Name</th>
<th>Primary</th>
<th>Address</th>
<th>City/Town</th>
<th>Province</th>
<th>Postal Code</th>
<th>Phone</th>
<th>Active</th>
</tr>
</thead>
<tbody>
<tr v-for="landlord in selectedLandlords" :key="landlord.id">
<td>{{ landlord.name }}</td>
<td>
<input type="checkbox" :value="landlord.is_primary" #change.prevent="makePrimaryLandlord(landlord)">
</td>
<td>{{ landlord.address }}</td>
<td>{{ landlord.city }}</td>
<td>{{ landlord.province}}</td>
<td>{{ landlord.postal_code}}</td>
<td>{{ landlord.phone }}</td>
<td>{{ landlord.is_active ? "Yes" : "No" }}</td>
</tr>
</tbody>
Vue Code:
export default {
data: function() {
return {
selectedLandlords: []
}
},
methods: {
makePrimaryLandlord: function(landlord) {
this.selectedLandlords = this.selectedLandlords.map(item => {
item.is_primary = false; return item});
}
}
}
}
Only the checkbox appears to have an issue. If I change say the name, or a text value with a filtered array setting them all to a specific value they change but the checkboxes data change doesn't reflect in the UI, however the data in Vue is correct.
From Official docs
text and textarea elements use value property and input event;
checkboxes and radiobuttons use checked property and change event;
select fields use value as a prop and change as an event.
Use :input-value instead of :value
I can't exactly make it up from your code, but are you sure the property is_primary is available in the objects on load of the viewmodel? So for example this.selectedLandlords.push({ Name:'John', is_primary: false, ...}) should contain the is_primary field to make it reactive.
If not, you should use Vue.set(item, 'is_primary', false) to make it reactive.
try using the key attributes to re-render the checkbox.
reference: https://michaelnthiessen.com/force-re-render/

Vue.js doesn't render table

My template is:
<tbody id="deliveries-table">
<tr v-for="item in deliveries">
<td class="table-view-item__col"></td>
<td class="table-view-item__col" v-bind:class="{ table-view-item__col--extra-status: item.exclamation }"></td>
<td class="table-view-item__col">{{item.number}}</td>
<td class="table-view-item__col">{{item.sender_full_name}}</td>
<td class="table-view-item__col" v-if="item.courier_profile_url">{{item.courier_full_name}}</td>
<td class="table-view-item__col" v-if="item.delivery_provider_url">{{item.delivery_provider_name}}</td>
<td class="table-view-item__col">
<span style="font-weight: 900">{{item.get_status_display}}</span><br>
<span>{{item.date_state_updated}}</span>
</td>
</tr>
</tbody>
My javascript code for render a lot of prepared data is:
var monitorActiveDeliveries = new ActiveDeliveries();
monitorActiveDeliveries.fillTable(allDeliveries);
class ActiveDeliveries {
constructor() {
this.table = new Vue({
el: '#deliveries-table',
data: {
deliveries: []
}
});
}
fillTable (d) {
this.table.deliveries = d;
}
}
But after script starts any render into tbody, i have just empty place in HTML.
Where i got some wrong?
First, although you can instantiate your Vue app on the <table> tag, usually you want just a single Vue instance on the whole page, so it might be better to make the Vue instance on one main div/body tag.
Second, I think your code could work (I don't know what your deliveries objects should look like...), but your fillTable() method is probably not getting called, i.e. deliveries are empty.
I made this working example based on your code: http://jsfiddle.net/wmh29mds/
Life is easier than it seems.
I got a mistake into this directive:
v-bind:class="{ table-view-item__col--extra-status: item.exclamation }"
I just forgot single quotas into class name, next variant is working:
v-bind:class="{ 'table-view-item__col--extra-status': item.exclamation }"

"You are binding v-model directly to a v-for iteration alias"

Just run into this error I hadn't encountered before: "You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead." I am a little puzzled, as I don't appear to be doing anythong wrong. The only difference from other v-for loops I've used before is that this one is a little simpler, in that it's simply looping through an array of strings, rather than objects:
<tr v-for="(run, index) in this.settings.runs">
<td>
<text-field :name="'run'+index" v-model="run"/>
</td>
<td>
<button #click.prevent="removeRun(index)">X</button>
</td>
</tr>
The error message would seem to suggest that I need to actually make things a little more complicated, and use objects instead of simple strings, which somehow doesn't seem right to me. Am I missing something?
Since you're using v-model, you expect to be able to update the run value from the input field (text-field is a component based on text input field, I assume).
The message is telling you that you can't directly modify a v-for alias (which run is). Instead you can use index to refer to the desired element. You would similarly use index in removeRun.
new Vue({
el: '#app',
data: {
settings: {
runs: [1, 2, 3]
}
},
methods: {
removeRun: function(i) {
console.log("Remove", i);
this.settings.runs.splice(i,1);
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.18/vue.js"></script>
<table id="app">
<tr v-for="(run, index) in settings.runs">
<td>
<input type="text" :name="'run'+index" v-model="settings.runs[index]" />
</td>
<td>
<button #click.prevent="removeRun(index)">X</button>
</td>
<td>{{run}}</td>
</tr>
</table>