I am trying to search or filter through 3 fields firstname, lastname and email in my Vue.js 2 application. I understand that Vue 2 does not come with a built in filter method unlike in Vue 1, hence I created a custom method which is only able to filter through just one field. How do I extend this to multiple fields? I have tried something like this filterBy(list, value1, value2, value3) but it does not work.
This is my code
<template>
<div class="customers container">
<input class="form-control" placeholder="Enter Last Name" v-
model="filterInput">
<br />
<table class="table table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="customer in filterBy(customers, filterInput)">
<td>{{customer.first_name}}</td>
<td>{{customer.last_name}}</td>
<td>{{customer.email}}</td>
<td><router-link class="btn btn-default" v-bind:to="'/customer/'+customer.id">View</router-link></td></tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'customers',
data () {
return {
customers: [],
filterInput:'',
}
},
methods: {
fetchCustomers(){
this.$http.get('http://slimapp.dev/api/customers')
.then(function(response){
this.customers = (response.body);
});
},
filterBy(list, value){
value = value.charAt(0).toUpperCase() + value.slice(1);
return list.filter(function(customer){
return customer.last_name.indexOf(value) > -1;
});
},
},
created: function(){
if (this.$route.params.alert) {
this.alert = $route.params.alert
}
this.fetchCustomers();
},
updated: function(){
this.fetchCustomers();
},
components: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
Extend your filterBy method to check more then just last_name
filterBy(list, value){
value = value.charAt(0).toUpperCase() + value.slice(1);
return list.filter(function(customer){
return customer.first_name.indexOf(value) > -1 ||
customer.last_name.indexOf(value) > -1 ||
customer.email.indexOf(value) > -1
});
},
But you can use computed to provide filtered results (it might perform better because it caches computations)
computed: {
filteredList() {
const value= this.filterInput.charAt(0).toUpperCase() + this.filterInput.slice(1);
return this.customers.filter(function(customer){
return customer.first_name.indexOf(value) > -1 ||
customer.last_name.indexOf(value) > -1 ||
customer.email.indexOf(value) > -1
})
}
}
and use it in your template
<tr v-for="customer in filteredList">
...
</tr>
The above method finds all strings STARTING with the word you are looking for and ignores all middle-sentence words.
This means that if you have a customer like Vincent Van Patten you will only find it by searching for Vincent or Vincent(space)Van. If you search for the word Van or Patten it will return an empty search because you are using indexOf inside filter.
This is why I would rather use JS includes():
computed: {
filteredList() {
const value= this.filterInput.charAt(0).toUpperCase() + this.filterInput.slice(1);
return this.customers.filter(function(customer){
return customer.first_name.includes(value) ||
customer.last_name.includes(value) ||
customer.email.includes(value)
})
}
}
Any search like Van or Patten will now match
just to make it more flexibel do some lowercase:
computed: {
filteredList() {
const value = this.filterInput.toLowerCase().slice(1);
return this.customers.filter(function(customer){
return customer.first_name.toLowerCase().indexOf(value) > -1 ||
customer.last_name.toLowerCase().indexOf(value) > -1 ||
customer.email.toLowerCase().indexOf(value) > -1
})
}
}
Related
so i try to make a multiple grid radio with condition it select one radio button and save it as json with key: question and value: column where the radio buttoin selected
<template>
<v-radio-group class="mt-0" v-model="answer" :rules="answerRule">
<thead>
<tr>
<th>Pilihan</th>
<th v-for="(option, keys) in columns" :key="keys + 'A'">
{{ option.value }}
</th>
</tr>
<tr v-for="(option, keys) in rowsCols" :key="keys + 'C'">
<th>{{ option.value }}</th>
<td
v-for="(optioncol, keys) in option.columns"
:key="keys + 'B'"
>
<v-radio-group
v-model="answer"
#change="update"
:rules="answerRule"
>
<v-radio
class="radioA"
solo
v-bind:key="option.id"
:value="optioncol"
>
</v-radio>
</v-radio-group>
</td>
</tr>
</thead>
<tbody></tbody>
</v-radio-group>
</template>
here how it looks in the browser but it selected all row
[![enter image description here][1]][1]
here is my script on how to load the data and try to save the json...
export default {
props: ['question'],
data() {
return {
rows: this.question.options,
answer: [],
columns: this.question.optionsColumns,
answerRule: [],
}
},
methods: {
async update() {
try {
let payload = {
questionId: this.question.id,
value: this.answer,
questionName: this.question.question,
}
//update question options
await this.$store.commit('answers/update', payload)
} catch (err) {
this.$store.commit('alerts/show', {
type: 'error',
message: err.response
? this.$t(err.response.data.message)
: this.$t('SERVER_ERROR'),
})
}
},
},
beforeMount() {
if (this.question.required) {
this.answerRule.push(
(v) => v.length > 0 || this.$t('QUESTION_REQUIRED')
)
}
},
}
</script>
Any help on that? because i've try and still cannot figure it out
Here is the Data that i already change into new format :
row:[{id:1,value:"asdasd",columns:[a,b,c]},{id:2,value:"asdasd",columns:[a,b,c]}{id:3,value:"asdasd"}{id:1,value:"asdasd",columns:[a,b,c]}]
yet i still got the same problem
[1]: https://i.stack.imgur.com/6fsIF.png
I am trying to make form project. When person submmit to personal info to form. I want add this info to Table. But I get this error when I am adding info to table.
Uncaught Error: Objects are not valid as a React child (found: object with keys {inputValue}). If you meant to render a collection of children, use an array instead.
I know this is related to const newToTable part. But I don't know how to make it right. Please help me, ıf you know how can ı solve this problem.
This is my contact.js
const buttonOnClick = () => {
if (inputValue === "" || emailValue === "" || phoneNumberValue === "") {
setShowModal(false);
} else {
setShowModal(true);
// setInputValue("");
//setEmailValue("");
//setPhoneValue("");
// sa
}
const newToTable = [...addFormData, { fullName:{inputValue},email:{emailValue}, phoneNumber:{phoneNumberValue},country:{countryValue} }];
setAddFormData(newToTable);
console.log(`Form submitted, ${showModal}`);
}
This is my AddTable.js
<div className="app-container">
<form>
<Table>
<thead>
<tr>
<th>Full Name</th>
<th>Email </th>
<th>Phone Number</th>
<th>Country</th>
</tr>
</thead>
<tbody>
{addFormData.map((addForm) => (
<tr>
<td>{addForm.fullName}</td>
<td>{addForm.email}</td>
<td>{addForm.phoneNumber}</td>
<td>{addForm.counry}</td>
</tr>
))}
</tbody>
</Table>
</form>
</div>
You've accidently wrapped your form values in objects here:
const newToTable = [...addFormData, { fullName:{inputValue},email:{emailValue}, phoneNumber:{phoneNumberValue},country:{countryValue} }];
For example fullName:{inputValue} will evalute to fullName: { inputValue: 'the value' }
Instead you need:
const newToTable = [
...addFormData,
{
fullName: inputValue,
email: emailValue,
phoneNumber: phoneNumberValue,
country: countryValue,
},
];
This is what the error means by Objects are not valid as a React child - when it tries to render your table, the values being passed are objects such as { inputValue: 'the value' } (this is the (found: object with keys {inputValue}) part of the error - an object with inputValue as a key).
With my code is nothing wrong (i hope), although I feel it could be better written. I tried move logic from to computed property but unsuccessfully, i think table structure is not correct, but I'm out of ideas. Anyone can help ?
Unfortunately "tabl" comes from the server and i cant changes this variable
<template>
<movable-div>
<template #header>
<div class="header">
<h3>{{ name }}</h3>
<div #mousedown.stop="dragMouseDown">
<input type="text" v-model="search" placeholder="Search..." />
</div>
<div class="button-group">
<svg width="1.2em" height="1.2em" viewBox="0 0 10240 10240" #click="toggleTable()" :class="[showTable ? 'go' : 'back']">
<path some long svg code... />
</svg>
<p #click="showTableAttributes()">X</p>
</div>
</div>
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)">
</p>
</td>
</tr>
</table>
</template>
</movable-div>
</template>
<script>
export default {
props: ['olMap', 'LayerStyleName', 'name'],
data() {
return {
tableHead: null,
rows: null,
table: {
ColumnList: [{name: "ex1"},{name: "ex2"}],
Name: "Example",
RowList: [{Original:[{Key: "ex1", Value: "exampleValue"}]},
{Original:[{Key: "ex2", Value: "exampleValue"}]}]
},
showTable: true,
layer: null,
filteredRow: [],
search: null,
};
},
mounted() {
this.rows = this.table.RowList;
this.tableHead = this.table.ColumnList.filter((item) => item.Name !== 'geometry');
this.search = '';
},
inject: ['showTableAttributes'],
methods: {
toggleTable() {
this.showTable = !this.showTable;
},
zoomToFeatureExtent(value) {
let extent = value
.filter((item) => item.Key === 'geometry')
.map((item) => item.Value);
let view = this.olMap.getView();
view.fit(extent[0], this.olMap.getSize());
let res = view.getResolution();
if (res < 0.5) {
view.setResolution(0.9);
}
},
},
watch: {
search: function (val) {
this.filteredRow = [];
for (let row of this.rows) {
if (row.Original.map((obj) => obj.Value.toString().includes(val))
.filter((i) => (i === true ? i : null))
.join()) {
this.filteredRow.push(row.Original);
} else null;
}
},
},
};
</script>
It's good practice (IMHO) to have the template void of any complex logic. It makes the code more maintainable since you don't have functionality split between your template and your script. It also allows for better performance if you can offload methods to cached variables which prevents static parts of code from re-calculating needlessly.
The following is a good example of improvement potential
<tr
v-for="row in filteredRow"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="item in tableHead" :key="item.Name">
<p
v-html="row.filter((obj) => obj.Key === item.Name)
.map((item) => item.Value)
.join()
.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)"
></p>
</td>
</tr>
I find reading this in the template is harder than in the script block (but YMMV), but performance wise you're doing extra loops. This script here does 3 loops: rows(filteredRow), columns(tableHead), then rows again(row.filter).
If you move the logic to a computed, you can simplify the logic and improve the performance. A computed will keep the data cached and update as needed, so if you change the value of search it will re-compute, but if an unrelated variable changes then it wouldn't, and the template wouldn't have to recalculate the values again. In your code, it seems like there's no much for other values that might change, but good practice anyway.
here's what that might look like (untested code)
computed: {
tableData() {
return this.filteredRow.map(row => {
const cols = [];
this.tableHead.forEach(item => {
let value = "";
if (col.Name === row.Key) {
let value = item.Value.replace(search, `<span style='color:#1D7CA7'><b>${search}</b></span>`)
}
cols.push(value)
});
return {...row, cols};
})
}
},
<table v-if="showTable">
<tr>
<th v-for="head in tableHead" :key="head.Name">
{{ head.Name }}
</th>
</tr>
<tr
v-for="row in tableData"
:key="row.Key"
class="data"
#click="zoomToFeatureExtent(row)"
>
<td v-for="(cell, i) in row.cols" :key="i">
<p v-html="cell"></p>
</td>
</tr>
</table>
I want to show/hide my table data. when I checked the checkboxes and on after a button click I want to hide them and vice versa.
I am able to put checkbox value to another inactiveList as an array but not able to bind to button.
this is my component file.....
<button class="btn btn-lg btn-primary" v-on:click="seen(inactiveList)">hide/show</button>
<table class="row-border hover">
<tr>
<th>Id</th>
<th>Title</th>
<th>UserId</th>
<th>Change Detail</th>
<th>Add Detail</th>
</tr>
<tr v-for = "(item,index) in paginate " :key= "index">
<td>{{item.id}}</td>
<td>{{item.title}}</td>
<td>{{item.userId}}</td>
<td><button class="btn btn-primary" >Edit</button></td>
<td><button class="btn btn-primary" >Add</button></td>
<td> <input type="checkbox" name="vehicle1"
v-on:change="list" v-bind:value="item" v-model="inactiveList"><br></td>
</tr>
</table>
<ul>
<li v-for="pageNumber in totalPages" v-if="Math.abs(pageNumber - currentPage) < 3
|| pageNumber == totalPages || pageNumber == 1">
<a v-bind:key="pageNumber" href="#" #click="setPage(pageNumber)"
:class="{current: currentPage === pageNumber,
last: (pageNumber == totalPages && Math.abs(pageNumber - currentPage) > 3),
first:(pageNumber == 1 && Math.abs(pageNumber - currentPage) > 3)}">{{ pageNumber }}</a>
</li>
</ul>
</div>
export default {
name: 'component2',
data(){
return{
currentPage: 1,
itemsPerPage: 10,
resultCount: 0,
inactiveList:[],
selected:false,
}
},
computed: {
...mapState([
'posts',
'loading'
]),
totalPages: function() {
return Math.ceil(this.resultCount / this.itemsPerPage)
},
paginate: function() {
debugger
if (!this.posts || this.posts.length != this.posts.length) {
return
}
this.resultCount = this.posts.length
if (this.currentPage >= this.totalPages) {
this.currentPage = this.totalPages
}
var index = this.currentPage * this.itemsPerPage - this.itemsPerPage
return this.posts.slice(index, index + this.itemsPerPage)
},
},
methods: {
setPage: function(pageNumber) {
this.currentPage = pageNumber
},
list: function(){
if(this.selected==false)
{
this.paginate.push= this.inactiveList;
return this.inactiveList
}
},
seen: function(inactiveList){
var index = this.inactiveList.length;
for(var i=0;i<index;i++)
{
this.inactiveList[i].id;
console.log(inactiveList[i].id)
}
},
},
mounted() {
this.$store.dispatch('loadPost');
},
}
</script>
this is my store.js file.....
import Vue from 'vue'
import Vuex from 'vuex'
import axios from "axios";
Vue.use(Vuex,axios);
export const store = new Vuex.Store({
state: {
posts:[],
loading: true
},
mutations:{
set_post(state,posts){
state.posts=posts;
},
changeLoadingState(state, loading) {
state.loading = loading
}
},
actions:{
loadPost({commit})
{
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
let post = response.data
console.log(post)
commit('set_post',post);
commit('changeLoadingState', false)
})
.catch(error => {
console.log(error)
this.errored = true
})
},
}
})
If I unterstood you correctly, you want to toggle visibility of each row and a possibility to unhide all hidden items.
To do that you should give each item a boolean flag if its currently visible or not.
You can store that in your data directly on each item as a property or make create an array based on index, if it's sorted data, or a map, if the sort of items happens to change anyway.
data() {
return {
useVisibility: false,
items: {} // id -> item
};
},
Don't forget to populate the map regarding this example code.
Now as you have provided the state you have to bind the inputs to each items boolean flag
<input type="checkbox" name="item.name" #change="setVisibility(item.id, $event)" :value="item.visible" />
Provide a method to change the visibility flag
setVisibility(id, event) {
Vue.set(this.items, id, { ...this.items[id], visible: event.target.checked})
}
Read up on the reactivity system if you don't know why I used Vue.set here. But maybe there is a better way to update the flag anyways.
To unhide all hidden items you just need to iterate over all items and set the visible flag to true with an v-on:click handler.
Write a computed property to get a list of visible items
computed: {
visibleItems() {
if (this.useVisibility) {
// filter unchecked boxes
return Object.values(this.items).filter(item => item.visible)
// to filter checked boxes use the invert
// return Object.values(this.items).filter(item => !item.visible)
} else {
return Object.values(this.items)
}
}
}
And render the list with a v-for. Notice. Use the id of the item so the VDOM can do its work properly.
<tr v-for="(item, index) in visibleItems" :key="item.id">
...
<input type="checkbox" name="item.name" #change="setVisibility(item.id, $event)" :checked="item.visible" />
</tr>
A button that hides/shows all checked items.
<button #click="useVisibility = !useVisibility"/>
See this demo
I have a table with bank details and I want to filter all across the table column based on the search field input. But its not working, can someone please help me debug the issue. When I'm typing something on the search field then the entire table data is disappearing.
<div>
<div>
<select (change)="OnSelectedCity($event)">
<option *ngFor="let cityObj of cityList" [value]="cityObj.value">{{cityObj.displayValue}}</option>
</select>
<input type="text" [(ngModel)]="filterText" [formControl]="filterInput" />
</div>
<div>
<table>
<thead>
<tr>
<th *ngFor="let header of tableHeader">{{header}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let bank of bankList | filterdata: filterText">
<td>{{bank.ifsc}}</td>
<td>{{bank.bank_id}}</td>
<td>{{bank.branch}}</td>
<td>{{bank.address}}</td>
<td>{{bank.city}}</td>
<td>{{bank.district}}</td>
<td>{{bank.state}}</td>
</tr>
</tbody>
</table>
</div>
</div>
transform(items: Array<any>, searchText: string): any {
if (searchText !== undefined) {
return items.filter(item => {
const filter = Object.keys(item);
filter.forEach(element => {
if (item[element].toLowerCase().indexOf(searchText.toLowerCase()) === -1) {
return false;
}
return true;
});
});
} else {
return items;
}
}
You just have a simple error in your pipe. The error lies in the .forEach call, because it's not possible to stop or break a forEach loop, see the docs.
After the adjustment your pipe could look like this:
transform(items: Array<any>, searchText: string): any {
if (searchText) {
return items.filter(item => {
const filter = Object.keys(item);
// Array.some() returns true if at least one entry meets the given condition
return filter.some(
key => item[key].toLowerCase().indexOf(searchText.toLowerCase()) !== -1
)
});
}
return items;
}