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>
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 new to react js. Here, I am trying to sort the data when the user clicks on the icons.
<th scope="col">Technology <i className="fa fa-fw fa-sort sort-icon"></i></th>
So, Now I have data which is in the form of an array of objects.
In this, I have 5 columns and sort icon is on every column. So, How do I Implement this thing using the react?
I want to sort using the alphabetical order.
My data looks like,
[{
"id": "5b7d4a566c5fd00507501051",
"hrmsJdId": null,
"companyId": null,
"jdName": "Senior/ Lead UI Developer",
"jobDescription": null,
"technology": java,
}, {
"id": "5b7fb04d6c5fd004efdb826f",
"hrmsJdId": null,
"companyId": null,
"jdName": "Content Innovation Lead",
"jobDescription": null,
"technology": css
}, {
"id": "5b7fb0b26c5fd004efdb8271",
"hrmsJdId": null,
"companyId": null,
"jdName": "Urgent Opening for DSP Engineer/ Senior Engineer for",
"jobDescription": null,
"technology": react,
}]
<td>{item.technology}</td>
<td>17</td>
<td title={item.jdName} className="jd-name-container justify-content-center align-items-center">
<div className="jdName">{item.jdName}</div>
{(key + 1 === 1) && <div className="badge new-badge badge-warning-custom">New</div>}
</td>
This is how I render the data. Now,
By default, I sort it using the
<tbody className="text-center">
{props.jobList && props.jobList && props.jobList.length > 0 && props.jobList.sort((a, b) => b.createdAt - a.createdAt).map((item, key) => {
return (
<tr key={key}>
<td align="center"> <input type="checkbox" name="myTextEditBox" value="checked" /></td>
<td>{item.technology}</td>
<td>17</td>
<td title={item.jdName} className="jd-name-container justify-content-center align-items-center">
<div className="jdName">{item.jdName}</div>
{(key + 1 === 1) && <div className="badge new-badge badge-warning-custom">New</div>}
</td>
<td>30</td>
<td>30</td>
<td>
</tbody>
So, How do I implement this ?
What I did is ,
<th scope="col">Technology<i className="fa fa-fw fa-sort sort-icon" onClick={props.sortAscending('Technology')}></i></th>
then in container
sortData = (key,event) => {
console.log("key is,", key);
this.props.sortAscending(key);
}
<UserJobsTabel jobList={filteredList} sortAscending={this.sortData} />
passed as a props.
Now in action,
export const sortAscending = (type) => {
return {
type: "SORT_ASCENDING",
payload: type
}
}
In reducer,
case FETCHING_JOBDESCRIPTION_SUCCESS:
return {
...state,
jobList: action.data.jobData ? action.data.jobData.sort((a, b) => b.createdAt - a.createdAt) : action.data.jobData,
yesterDayScore: action.data.yesterdayScore,
todayScore: action.data.todayScore,
error: false,
}
case "SORT_ASCENDING":
const { sortKey } = action.payload;
const jobList = [ ...state.jobList ]
.sort((a, b) => a[sortKey].localeCompare(b[sortKey]));
return { ...state, jobList };
×
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I am getting this error.
Use localeCompare() to sort in alphabetical order.
Have the sorting done in reducer while the component just dispatches the "sort" action. Every re-sort will cause a component re-render for jobList prop (in mapStateToProps) is updated.
In reducer:
const initialState = {
jobList: [],
};
export const jobList = (state = initialState, action) => {
switch(action.type) {
case Action.SORT_ALPHA_ASC:
const { sortKey } = action.payload;
const jobList = [ ...state.jobList ]
.sort((a, b) => a[sortKey].localeCompare(b[sortKey]));
return { ...state, jobList };
default:
return state;
}
}
In your component:
// alphaSort will dispatch action: SORT_ALPHA_ASC
const { jobList, alphaSort } = this.props;
if (! jobList || jobList.length < 1) {
// no job list
return null;
}
return (
<table>
<thead>
{ /* TODO: use th/td */ }
<SomeIcon name='asc-technology' onClick={() => alphaSort('technology')} />
<SomeIcon name='asc-jdname' onClick={() => alphaSort('jdName')} />
{ /* TODO: other fields */ }
</thead>
<tbody>
{
jobList.map((job) => ({
<tr key={job.id}>
<td name="technology">{job.technology}</td>
<td title={job.jdName}>
<div className="jdName">{job.jdName}</div>
</td>
{ /* TODO: other fields */ }
</tr>
}))
}
</tbody>
</table>
);
NOTE:
descending alpha sort and other fields will be for the OP to continue. :)
React Js arrays have a sort function:
['b', 'a'].sort((e1, e2) => e1.id < e2.id ? 1 : - 1)
If the lambda function returns 0, it does nothing. Otherwise, the two elements in the array will be sorted by the sign of the returned value.
If there are duplicate IDs in the database then the function will return 0.
To swap the order, change either the sign of the return value or the "smaller than" operator to a greater than operator.
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;
}
I have the following HTML code with vue.js bindings. The code shows a list of items in a table.
<table id="test-vue">
<tr v-for="(item, index) in items">
<td>
<input type="text" v-model="item.foreignId" />
</td>
<td>
<div v-if="canApproveItem(item, foreignValues)">
// UI allowing to approve the item
</div>
</td>
</tr>
</table>
The Vue app is defined as:
new Vue({
el: "#test-vue",
data: function() {
return {
items: [
{
foreignId: '4943',
}
],
foreignValues: [
{ id: '2424', canApprove: false },
{ id: '4943', canApprove: true }
],
computed: {
canApproveItem: function(item, foreignValues) {
let foreign = foreignValues(obj => {
return obj.id === item.foreignId;
});
if (foreign) {
return foreign;
} else {
return false;
}
}
}
})
The goal is to show the approve UI only for items which links to a "foreign" whose canApprove property is set to true. The user can edit the foreignId and the user interface should reflect the user-made change of the foreignId values.
If I run the above, the item argument of the canApproveItem function is the Vue object and foreignValues is undefined. I also get a Error in render: "TypeError: canApproveItem is not a function" error in the console.
How to do it properly?
This sounds like a method. You shouldn't pass in foreignValues, as you can access that from the method itself.
<table id="test-vue">
<tr v-for="(item, index) in items">
<td>
<input type="text" v-model="item.foreignId" />
</td>
<td>
<div v-if="canApproveItem(item)">
// UI allowing to approve the item
</div>
</td>
</tr>
</table>
and
new Vue({
// data and stuff here...
methods: {
canApproveItem(item) {
return this.foreignValues.findIndex(obj => obj.id === item.foreignId) !== -1;
}
}
})
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
})
}
}