Vue Reactivity issue, setting array element property - vue.js

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);

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).

Vue2: Can I pass an optional (global) filter into a reusable component?

I am quite new to Vue.
I am working on a table as component, which is supposed to be a lot of times. So far, so good, but now I want to use a filter, which can be optional passed into it.
That is how I "call" the table:
<table
:headers="headers"
:items="some.data"
></table>
data () {
return {
headers: [
{ title: 'date', value: ['date'], filter: 'truncate(0, 10, '...')' },
]
}
}
Here is my table component
<template>
<div>
<table class="table">
<thead>
<tr>
<!-- <th v-for="header in headers" :key="header.id" scope="col">{{ header.title }}</th> -->
<th v-for="header in headers" :key="header.id" scope="col">
{{ header.title }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items" :key="item.id" scope="col">
<td v-for="header in headers" :key="header.id" scope="row">
<!-- {{item}} -->
<span v-for="val in header.value" :key="val.id">
{{item[val] | truncate(0, 10, '...') }}
</span>
<!-- {{header.filter}} -->
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script >
export default {
name: 'Table',
props: {
headers: Array,
items: Array
}
}
</script>
My global filter:
Vue.filter('truncate', function (text, start, truncLength, clamp = '') {
return text.slice(start, truncLength) + clamp
// return text.slice(0, stop) + (stop < text.length ? clamp || ' ...' : '')
})
I was hopping, to add by that an optional filter (via v-if I would chech for it). So far I can render the filter as string ... but not execute it.
Even if I put the filter in the span, it does not work (it says then, "text.slice is not a function" in the console.
I was not successful with googling it, because with filters/filter it is mostly about how to use .filter(...) on data as JS method, but not as Vue filter.
Any advise?
A filter is a function that runs inside JSX template in html. Example of how to create custom Vue.js filter
Vue.filter('ssn', function (ssn) { // ssn filter
return ssn.replace(/(\d{3})(\d{2})(\d{4})/, '$1-$2-$3');
});
Using it
{{stringToTrans | ssn}}
To use a filter outside this you can use a computed property or standard function like so
convertedDateString() { // computed
return this.$moment(this.dateString).format('MM/DD/YYYY')
}
convertedDateString(dateString) { // method
return this.$moment(dateString).format('MM/DD/YYYY')
}

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

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

VueJS - One component but make requests to 2 different addresses

I have a component MainTable that renders a table in which I display Tasks:
({title: String, body: String, etc.}).
This component also triggers created method hook to make a request to my API to http://localhost:3000/api/v1/tasks.
I want to have a button with a link (route) to all complete_tasks where I want to also render the same table (hence I made it a component) with all complete tasks.
This time however, I want this component to make a request to a different address to http://localhost:3000/api/v1/tasks/complete_tasks (to fetch all complete tasks).
Is it possible to achieve this?
//App.vue
<template>
<div class="container">
<div class="row">
<h2>Todos</h2>
<mainTable></mainTable>
</div>
</div>
</template>
<script>
import MainTable from './components/MainTable.vue'
export default {
components: {
'mainTable': MainTable
},
methods: {
}
}
</script>
//MainTable.vue
<template>
<table class="table table-sm table-striped">
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
<th><select-all-button></select-all-button></th>
<th><finishButton></finishButton></th>
<br>
<th><removeAllButton></removeAllButton></th>
</tr>
</thead>
<tbody>
<tr v-for="task in tasks">
<td>{{ task.title }}</td>
<td>{{ task.content }}</td>
<td>Show</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</tbody>
</table>
</template>
<script>
import SelectAllButton from './buttons/SelectAll.vue';
import FinishButton from './buttons/FinishButton.vue';
import RemoveAllButton from './buttons/RemoveAllButton.vue';
export default {
components: {
'select-all-button': SelectAllButton,
'finishButton': FinishButton,
'removeAllButton': RemoveAllButton
},
data(){
return {
tasks: []
}
},
created(){
this.$http.get('tasks').then(function(data){
this.tasks = data.body.slice(0,20);
})
}
}
</script>
yes, of course, you can do it
you can make a method trigger on click
when you press the button make a request to your endpoint and get the data
and update your object tasks with new data
Is that clear?

How to remove ??? characters on database query result with Laravel

I am have an issue that sounds new and don't know what is happening. I am retrieving data from database with Laravel eloquent.
In my controller:
public function index()
{
$suppliers = Supplier::all();
return response()->json($suppliers);
}
Its working just fine, the problem is the result object however is appearing as shown below with leading ???. All query results in the entire project is rendering just this way. I have tried to trim the project witn trim() but I realize this wont work on object.
����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[{"id":1,"first_name":"Ronald","last_name":"Wekesa","mobile_number":"0717245218","work_phone":"07140000","phone_code":254,"fax":null,"email":"email#yaho.com","postal_address":"P.o
Box
323","town":"Kitale","zip":30200,"phisical_address":"Kiminini","company":"Wafutech","created_at":"2018-03-05
11:52:30","updated_at":"2018-03-05
11:52:30"},{"id":2,"first_name":"Ronald","last_name":"Wekesa","mobile_number":"0725645544","work_phone":"070025400","phone_code":2....
when I render the result in my vuejs front end as shown below I am getting blank loop, I mean the loop runs but with empty bullet list. I am suspecting this might have something with this fun behavior.
//My View component
<template>
<div>
<h3>Suppliers</h3>
<div class="row">
<div class="col-md-10"></div>
<div class="col-md-2">
<router-link :to="{ name: 'create-supplier'}" class="btn btn-primary">Add Supplier</router-link>
</div>
</div><br />
<table class="table table-hover table-striped table-responsive">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Company</td>
<td>Mobile Phone</td>
</tr>
</thead>
<tbody>
<tr v-for="supplier in suppliers">
<td>{{ supplier.first_name }}</td>
<td>{{ supplier.last_name }}</td>
<td>{{ supplier.company }}</td>
<td>{{ supplier.mobile_number }}</td>
<td><router-link :to="{name: 'edit-supplier', params: { id: supplier.id }}" class="btn btn-primary">Edit</router-link></td>
<td><button class="btn btn-danger" v-on:click="deleteSupplier(supplier.id)">Delete</button></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name:'displayAsset',
data(){
return{
suppliers: []
}
},
created: function()
{
this.fetchSuppliers();
},
methods: {
fetchSuppliers()
{
let uri = 'http://localhost/ims/public/api/suppliers';
this.axios.get(uri).then((response) => {
this.suppliers = response.data;
});
},
deleteSupplier(id)
{
let uri = `http://localhost/ims/public/api/suppliers/${id}`;
this.suppliers.splice(id, 1);
this.axios.delete(uri);
}
}
}
</script>
I have tried to re-install laravel but no difference. I have also re-installed my xampp server with the latest version but wan't lucky either. What is causing this and how can I resolve it. Thanks in advance.