I'm building a small application using vuejs where I'm calling a url to get some data. I need to manipulate the data before showing it. In the response I'm receiving an array of elements which has fields
client_name: "ABCD Company"
event_type: 3
type: "meeting"
venue: "Mumbai"
with_client: 1
Additionally I have a data set of event_type that looks like this:
events: [
{value: 1, label: "One-on-One meeting"},
{value: 2, label: "Group meeting"},
{value: 3, label: "Broker Roadshow"},
{value: 4, label: "Broker Conference"},
{value: 5, label: "Site Visit"},
{value: 6, label: "Only Management Meet"},
{value: 7, label: "Only IR Meeting"}
],
and with_client is true or false.
So basically my final data will look like something like this:
client_name: "ABCD Company",
event_type: "Broker Roadshow",
type: "meeting",
venue: "Mumbai",
with_client: "yes"
Currently I'm have a v-for loop that looks like this:
<tr v-for="(item, index) in meeting_data">
<td class="text-center">{{ index+1 }}</td>
<td class="text-center">{{ item.client_names }}</td>
<td class="text-center">{{ item.type }}</td>
<td class="text-center">{{ item.event_type }}</td>
<td class="text-center">{{ item.with_client }}</td>
<td class="text-center">{{ item.schedule }}</td>
<td class="text-center"><router-link :to="{name: 'interaction-update', params: {id: item.id}}"><i class="fa fa-pencil-square-o text-navy"></i></router-link></td>
<td class="text-center"><a #click.prevent="deleteInteraction(item.id)"><i class="fa fa-trash-o text-navy"></i></a></td>
</tr>
Use a computed.
This assumes your meeting_data is an array of objects. If it's an object as you suggest in your comment, then show us an example and I'll update the answer.
computed:{
formattedData(){
if (!this.meeting_data) return []
return this.meeting_data.map(d => {
return {
client_name: d.client_name,
type: d.type,
// this find could blow up if the event_type doesn't exist
event_type: this.events.find(e => e.value == d.event_type).label,
with_client: d.with_client ? "yes" : "no",
venue: d.venue
}
})
}
},
Iterate over the formatted data.
<tr v-for="(item, index) in formattedData">
Example.
Based on your pen, it would look something like this:
computed: {
tableFilter: function () {
// Do the filter
let interactions = this.model.interactions
if(this.model.interactions)
{
interactions = this.model.interactions.filter((item) =>
item.client_names.includes(this.search_by_name)
&& item.event_type.includes(this.search_by_event_type));
}
if (!interactions.length > 0) return []
// Return formatted data
return this.interactions.map(d => {
return {
client_name: d.client_name,
type: d.type,
// this find could blow up if the event_type doesn't exist
event_type: this.events.find(e => e.value == d.event_type).label,
with_client: d.with_client ? "yes" : "no",
venue: d.venue
}
})
}
}
That's obviously not a working example but gives you the structure.
Related
I am trying to sort my array of objects. I have managed to use the console to return all the titles but struggling now to perform the actual sort. I am using a select menu to switch between relevancy (default) and to sort alphabetically.
Any help is greatly appreciated
<select v-model="sortatoz" v-on:change="sortItems($event)">
<option disabled value="">Select</option>
<option value="alphabetically">Alphabetically</option>
<option value="relevance">Relevance</option>
</select>
methods: {
sortItems: function sortItems(event) {
this.sortby = event.target.value;
if (this.sortatoz === "alphabetically") {
Array.from(
document.querySelectorAll("div > h3.title")
).map((x) => x.innerText);
??What should I add here to sort???
} else {
Array.from(
document.querySelectorAll("div > h3.title")
).map((x) => x.innerText);
???
}
},
Step 1: Create HTML template with select options and table to display data with sorting functionality.
<div id="app">
<select v-model="sortatoz" #change="sortItems">
<option disabled value="">Select</option>
<option value="alphabetically">Alphabetically</option>
<option value="relevance">Relevance</option>
</select>
<table border="1">
<thead>
<tr>
<th>Title</th>
<th>Authur</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in sortedItems" :key="index">
<td>{{ item.title }}</td>
<td>{{ item.authur }}</td>
</tr>
</tbody>
</table>
</div>
Step 2: Define your model data. I created a sample model data with books title with Authur names
data() {
return {
sortatoz: "",
listTitles: [
{
title: "Cabbages and Kings",
authur: "O. Henry",
},
{
title: "Alien Corn",
authur: "Sidney Howard",
},
{
title: "The Little Foxes",
authur: "Lillian Hellman",
},
{
title: "A Time of Gifts",
authur: "Patrick Leigh Fermor",
},
{
title: "Ego Dominus Tuus",
authur: "W. B. Yeats",
},
{
title: "Antic Hay",
authur: "Aldous Huxley",
},
],
};
},
Step 3: Define your computed lifecycle. When ever changes happened in the model data automatically computed function will get updated.
computed: {
sortedItems() {
return this.listTitles;
},
},
Step 4: Define #change event for select options
methods: {
sortItems() {
if (this.sortatoz === "alphabetically") {
return this.listTitles.sort((a, b) => (a.title > b.title ? 1 : -1));
} else {
return this.listTitles.sort((a, b) => (a.title > b.title ? -1 : 1));
}
},
},
DEMO
In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?
Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>
I have some object in items array and in that objects, there is an array, I've filtered values of objects but the array that is located on it didn't filter I know its natural, but I don't know how can I map or filter the array, anyone can help me?
Here my vuex store codes:
const store = new Vuex.Store({
state: {
count: 0,
items: [
{ id: 1, name: "Jack", age: 19, favs: ["Football", "Game"] },
{ id: 2, name: "Tom", age: 20, favs: ["Basketball", "Swiming"] },
],
},
getters: {
filterItems(state) {
return state.items.filter((item) => item);
},
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
},
},
actions: {},
modules: {},
});
here html codes:
<table>
<thead>
<tr>
<th>Name:</th>
<th>Age:</th>
<th>Favs:</th>
</tr>
</thead>
<tbody v-for="(item, index) in filterItemsShow" :key="index">
<tr>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
<td>{{ item.favs }}</td>
</tr>
</tbody>
</table>
and vuejs codes:
export default {
name: "App",
computed: {
filterItemsShow() {
return this.$store.getters.filterItems;
},
},
I found the solution, instead of this line code :
<td>{{ item.favs }}</td>
add below line code:
<td v-for="(i,index) in item.favs" :key="index">{{ i }}</td>
Instead of
<td>{{ item.favs }}</td>
use
<td>{{ filterFavs(item.favs) }}</td>
In methods
filterFavs(favs) {
let filteredFavs = favs.filter(fav => // filter with your criterias);
return filteredFavs.join(", ")
}
So i've this data
data: () => ({
products: [
{ id: 1, name: "Prod 1", price: 2, stock: 5 },
{ id: 2, name: "Prod 2", price: 3, stock: 6 }
]
})
Template
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#input="handleInputStock($event.target.value)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</tbody>
</table>
So what I want is that when the user hit delete/backspace from the stock input field the value cannot be empty (blank) or it must be greater than or equal to zero. but without changing the products.stock value. this is because I need the product.stock value to compare with the changed value (stock input field) before sending to the server. So if stock value is equal to product.stock don't send to server otherwise send and update stock value.
so here's what i've done so far.
prevent the stock value empty but not working
handleInputStock(value) {
return +value.replace(/[^0-9]/g, "");
},
update stock
updateStock(stock, productId) {
const productStock = this.products.find(product => product.id == productId).stock;
if (!(stock == productStock)) {
// do ajax
}
},
onlyNumber
onlyNumber(e) {
const charCode = e.which ? e.which : event.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
e.preventDefault();
}
},
Personally this feels like a higher level question to which your flow of product editing needs tweaking. Here is what I can think of:
User enters all the information.
User hits submit button.
Check whether of not the stock count is empty or 0.
Return an error message if it is.
Submit and update otherwise.
It might be worth looking into vuelidate that handles such validation in JavaScript. Meanwhile, we are also coming up with a tool called CRUDS DS (a WIP) that handles such situation with ease.
The best way is to create a ProductComponent and watch every product separately inside its own component, as shown below:
Product.vue
<ProductComponent
v-for="product in products"
:product="product"
:key="product.id" />
ProductComponent.vue
<template>
<tr>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</template>
<script>
export default {
props: {
product: {
type: Object,
default: {},
},
},
data: () => ({ actual_stock: "" })
// this is for handle stock cannot be empty or GTE:0
// also you dont need handleInputStock anymore
watch: {
product: {
handler(val) {
this.actual_stock = val.stock;
},
immediate: true,
},
"product.stock": function (newVal, oldVal) {
this.product.stock = +newVal;
},
},
methods: {
updateStock(stock, productId) {
if (!(stock == this.actual_stock)) {
// do ajax
}
}
}
}
</script>
If you want to handle it on parent side, you may use $emit to send an event upwards.
Can we have two versions of products? One for the server, one for v-models.
var server_products = [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
//...
data: () => ({
products = server_products
})
updateStock(stock, productId) {
server_products.forEach((product) => {
if(product.id === productId && stock !== product.stock){
product.stock = stock
// do ajax
}
})
},
//...
If not than you can use vue's watch property so vue finds changes to the array for you.
//...
data: () => ({
products: [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
}),
watch: {
'products': {
handler: function(newValue) {
// do ajax
},
deep: true
}
}
//...
Basically I have five objects in an array, in every object, there is an attribute called isLastRow with boolean value.
I want to have a table with multiple tbody tag, when the item.isLastRow is true, the next index would create new tbody with tr. Obviously, my current attempt just iterates the items and render five tbody, the expect result would be just two tbody. The first tbody will have two tr and the last tbody has three tr.
let items = [
{
id: 0,
text: a,
isLastRow: false,
},
{
id: 1,
text: a,
isLastRow: true,
},
{
id: 2,
text: b,
isLastRow: false,
},
{
id: 3,
text: b,
isLastRow: false,
},
{
id: 4,
text: b,
isLastRow: true,
},
]
<template v-for="item in items">
<tbody :key="item.id">
<tr>
<td>
{{ item.text }}
</td>
</tr>
</tbody>
</template>
This is the result I expect to be
<table>
<tbody>
<tr>
<td rowspan="2"></td>
<td>a</td>
</tr>
<tr>
<td>a</td>
</tr>
</tbody>
<tbody>
<tr>
<td rowspan="3"></td>
<td>b</td>
</tr>
<tr>
<td>b</td>
</tr>
<tr>
<td>b</td>
</tr>
</tbody>
</table>
You are going to have to use a computed property to format your data structure to be something like
[
[
{ id: 0, text: a, isLastRow: false },
{ id: 1, text: a, isLastRow: true },
],
[
{ id: 2, text: b, isLastRow: false },
{ id: 3, text: b, isLastRow: false },
{ id: 4, text: b, isLastRow: true },
],
];
e.g.,
computed: {
getFormatedItems() {
const sections = [[]];
let index = 0;
this.items.forEach(item => {
if (item.lastRow) {
index++;
section[index] = [];
}
section[index].push(item);
});
},
},
Then you can write you template like
<template v-for="(section, index) in getFormatedItems">
<tbody :key="index">
<tr v-for="item in section" :key="item.id">
<td>{{item.text}}</td>
</tr>
</tbody>
</template>