Angular 6 filter multiple table column - javascript

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

Related

Move v-for logic to computed property

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>

Issues implementing a search function "TypeError: data.filter is not a function"

EDIT: heres the console.log(data) the data is displaying fine, it's just not filtering the data properly..]
!https://imgur.com/a/SsEDAKj!
EDIT 2: this.state.items is an array.
I'm trying to implement a search function, that allows the user to search through the data brought back from the API. I keep getting the following error:
"TypeError: data.filter is not a function"
constructor(){
super();
this.state ={
items: [],
sessions: [],
loading: true,
search: '',
direction: 'asc',
filteredPosts: [],
query: ''
}
this.onSort = this.onSort.bind(this);
this.searchTerm = this.searchTerm.bind(this);
//this.filteredPosts = this.filteredPosts.bind(this);
}
searchTerm =(event) =>{
const query = event.target.value;
this.setState({query}, () => this.filterData());
}
filterData(){
let data = this.state.items;
let src = this.state.query;
data = data.filter(function(data){
return data.indexOf(src) != -1;
});
this.setState({filteredPosts: data});
console.log(this.state.filteredPosts);
}
async getTalks(){
const response = await fetch ('PRIVATE_API');
const data = await response.json();
//console.log(data);
this.setState({items: data});
}
async componentDidMount(){
this.getTalks();
}
render(){
return (
<div className="container-fluid m-0">
<div className="row h-100">
<div className="col-12 ml-0"><h2>Conference Talks</h2>
<p>You can search the talks via title, speaker or tags</p>
<input className="form-control mb-2" id="search" type="text" onChange={this.searchTerm} placeholder="search"></input></div>
<table className="table table-hover table-dark">
<thead>
</thead>
<tbody id ="list">
{this.state.items.map(function(item,index){
return(
<tr key = {index}>
<th data-title="id"scope="row">{item.id}</th>
<td data-title="title" style={{cursor: "pointer"}} title data-original-title={"Click to sort"} data-toggle="title" >{item.title}</td>
<td data-title="description" id="desc">{item.description}</td>
<td data-title ="speaker">{item.speaker}</td>
<td >{item.session}</td>
<td >{item.tags}</td>
<td >{item.time}</td>
<td >{avg}</td>
I can't figure out the right direction to go in when trying to filter through the data that is pulled. What mistakes am I making? ( I have only included relevent code)
Have you run a check to see if your data is there? It's possible the items are not available on first render, causing this problem. Maybe something like
{this.state.items.length > 0 && this.state.items.map(function(item,index){
//rest of your code here
Also, I noticed you are bringing in a loading variable. If this is working you could do
if(loading){ return (<LoadingComponent />}else{ //regular return statement
The issue was I wasn't returning the object attribute.
data = data.filter(function(data){
return **data.title**.toLowerCase().indexOf(src) !== -1;
});

How can i add and remove a specific key of a object when using object.keys?

I'm doing a leaderboard for a game. The userInfo array that i am using contains fetched data from a json table. The array contains id, username, bestattempts and besttime along with the data from each category.
This table currently have 4 columns with and all the data from the json table is on table rows. I need to have the table not showing the "id" column, and instead showing a new "Rank" first column with a autoincrement row data [1,2,3,4,5] (Like on a leaderboard).
How can i make that happen with Object.keys working?
import React from "react";
import "./LeaderBoard.css";
const Head = ({keys, head}) => {
return (
<thead>
<tr>
{
keys.map(key =><th key={key}>{head[key] || key}</th> )
}
</tr>
</thead>
)
}
const Row = ({row}) => {
const keys = Object.keys(row)
return (
<tr key={row.id}>
{
keys.map(key => <td key={key}>{row[key]}</td>)
}
</tr> )
}
const LeaderBoard = ({usersInfo, head}) => {
const newArray = usersInfo.map(({ id, ...rest }, index) => ({ Rank: index + 1, ...rest }) )
newArray.sort(function sortByAttempts(a, b) {
return (a.bestattempts - b.bestattempts) && (a.besttime - b.besttime)
});
const keys = Object.keys(newArray[0])
return (
<div className="Leaderboard-wrapper">
<table>
<Head keys={keys} head={head}/>
<tbody>
{
newArray.map((row) => <Row row={row}/>)
}
</tbody>
</table></div>
)
};
export default LeaderBoard;

How do I search through multiple fields in Vue.js 2

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

Angular 2 filtering

I've tried to filter in Angular 2 app in version alpha 22. I've tried many ways how to do it but nothing works...
<table class="tabulka">
<tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th>
</tr>
<tr *for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>
Please help! How do you do filtering in angular 2 using typescript?
In angular 1.4.x it works this way:
<table class="tabulka">
<tr ng-repeat="x in datas| filter:searchText|filter:{Aplication:search}| filter:{Person:podle}">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td>{{x.Priority}}</td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td>
</tr>
</table>
In angular 2.0.0-beta.0, you'll need to implement a pipe that transform the array depending on your application needs,
#Pipe({
name: 'search'
})
export class SearchTextPipe implements PipeTransform {
transform(value: any[] , args: any[]) {
const searchText = args[0];
const field = args[1];
if (!searchText) {
return value;
}
return value.filter ((item) => {
if (field) {
return item[field].includes(searchText);
}
return _(item)
.values()
.includes( searchText );
})
}
}
Then you can use it in other components:
#Component({
...
pipes: [SearchTextPipe]
})
and in the template:
*ngFor="#item of list | search:searchInput:field"
Go to the console and type
ng generate pipe filter
Then go edit the newly created file (src/app/filter.pipe.ts) and replace
transform(value: any, args?: any): any {
return null;
}
by
transform(value: any, args?: any): any {
if (!value || !args) return value;
if (typeof args == "string"){
return value.filter(item => item.toLowerCase().indexOf(args.toLowerCase()) !== -1);
} else {
let key = Object.keys(args)[0];
return value.filter(item => item[key].toLowerCase().indexOf(args[key].toLowerCase()) !== -1);
}
}
Usage
Now, you can use your filter as follow
// app.component.ts
list = ["Hello", "Hi and hello", "Bonjour"];
list_of_objects = [
{ id: 0, content: "Hello" },
{ id: 1, content: "Hi and hello" },
{ id: 2, content: "Bonjour" }
];
// app.component.html
<p>Simple array</p>
<ul>
<li *ngFor="let item of list | filter:'hello' ">{{ item }}</li>
</ul>
<p>Array of JSONs</p>
<ul>
<li *ngFor="let item of list_of_objects | filter:{ content:'hello' } ">{{ item.title }}</li>
</ul>
And all of that will display :
Simple array
Hello
Hi and hello
Array of JSONs
Hello
Hi and hello
You have to write *ng-for="#x of datas" and *ng-if="x.Priority == 1""
<table class="tabulka"> <tr>
<th>ID</th><th>Typ</th><th>Priorita</th><th>Aplikace</th><th>Souhrn</th><th>Hlásil</th><th>Stav</th><th>Termín</th><th>Akce</th> </tr> <tr *ng-for="#x of datas">
<td>{{x.ID}}</td>
<td>{{x.Type}}</td>
<td *ng-if="x.Priority == 1" ><img src="./img/red.png"></td>
<td *ng-if="x.Priority == 0"></td>
<td>{{x.Aplication}}</td>
<td>{{x.Summary}}</td>
<td>{{x.Person}}</td>
<td>{{x.State}}</td>
<td>{{x.Date}}</td>
<td class="edit" id="{{x.ID}}">Upravit</td> </tr>
I played around with the code below. i was looking for the same search function. But for now this fits my needs. Still want to find a way the make the pipe dynamic. Maybe you find the solutions for this one.
import {Pipe} from 'angular2/core';
#Pipe({
name: 'filterByDone',
pure: false,
})
export class SearchPipe {
transform (value, [queryString]) {
if (value==null) {
return null;
}
return value.filter((todo)=> todo.done !== '1')
}
}
Basicly, add a function to trigger search box textvalue changed. Inside the function, create a for loop to add matched data.
TextChanged(searchText){
var filteredList = [];
For(let item of this.oldList){
If(item.contains(seaechText))
FilteredList.push(item);
}
This.oldList = filteredList;
}
}

Categories