Need help creating a dynamic search form with select options for Districts, Regions and locations.
Regions select must be populated based on the District and Locations based on the Regions
The data is stored on a JSON file with the following structure:
[
{
"level": 1,
"code": 1,
"name": "District"
},
{
"level": 2,
"code": 101,
"name": "Region"
},
{
"level": 3,
"code": 10101,
"name": "Location"
}
]
here´s the complete JSON file:
https://gist.github.com/tomahock/a6c07dd255d04499d8336237e35a4827
html snippet
<select name="district" v-model="district">
<option value=''>Select District</option>
<option v-for="district in filterDistricts" :value="district.code">
{{ district.name }}
</option>
</select>
<select name="region" v-model="region">
<option value=''>Select Region</option>
<option v-for="region in filterRegions" :value="region.code">
{{ region.name }}
</option>
</select>
<select name="location" v-model="location">
<option value=''>Select Location</option>
<option v-for="location in filterLocations" :value="location.code">
{{ location.name }}
</option>
</select>
javascript snippet
data() {
return {
searchData: [],
districts: [],
regions: [],
locations: []
}
},
created(){
this.fetchData();
},
computed: {
filterDistricts() {
return this.districts = this.searchData.map(res => ({
level: res.level,
code: res.code,
name: res.name
}))
.filter( res => res.level === 1)
},
filterRegions() {
return this.regions = this.searchData.map(res => ({
level: res.level,
code: res.code,
name: res.name
}))
.filter( res => res.level === 2)
},
filterLocations() {
return this.locations = this.searchData.map(res => ({
level: res.level,
code: res.code,
name: res.name
}))
.filter( res => res.level === 3)
}
},
methods: {
fetchData(){
axios.get('http://localhost:8000/json/searchData.json')
.then((response) => (
this.searchData = response.data
))
.catch((err) => {
console.log(err)
})
}
}
I think I need to associate de code numbers, but I can´t figure out how.
Any ideas?
Thanks
First, I wouldn't bother with those map calls since you're only reproducing the same structure.
Second, I'll assume that each sub-element (region / location) relates to its parent (district / region) via a pattern where each sub's code is prefixed with the parent code, followed by a two-digits, zero-padded.
With that in mind, try this in your computed properties
filterDistricts () {
return this.searchData.filter(({ level }) => level === 1)
},
filterRegions () {
// assuming you don't want any selections until a district is chosen
if (!this.district) return []
const codeCheck = new RegExp(`^${this.district}\\d{2}$`)
return this.searchData.filter(({ level, code }) =>
level === 2 && codeCheck.test(code))
},
filterLocations () {
if (!this.region) return []
const codeCheck = new RegExp(`^${this.region}\\d{2}$`)
return this.searchData.filter(({ level, code }) =>
level === 3 && codeCheck.test(code))
}
Extra notes...
From looking at your template, it seems you should initialise your data as
data () {
return {
searchData: [],
district: null,
region: null,
location: null
}
}
Computed properties don't need to be stored in data properties so you don't need districts, regions and locations.
Your label options should also be disabled so they cannot be selected, eg
<option disabled value="">Select Region</option>
Related
I'm displaying a list of Todos using React. Each Todo has a category attribute displayed as a select element, with a value as the todo.category. The select contains a list of option mapped to each category available. When I delete a Todo that has a category set, the next Todo displays that category, even though the Todo object itself does not have a category set. If I reload the page, the select switches to the default value, as should have from the beginning. Here's my implementation:
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import actions from "../actions";
import categoriesSelector from "../reducers/categories/selectors";
const Todo = ({ todo, index }) => {
const dispatch = useDispatch();
const categories = useSelector(categoriesSelector.getCategories);
const handleDeleteTodo = () => {
dispatch(actions.deleteTodo(todo.id));
};
const handleAssignCategory = (e) => {
const categorizedTodo = { ...todo, category: e.target.value };
dispatch(actions.updateTodo(index, categorizedTodo));
};
return (
<li>
<label>{todo.title}</label>
{Array.isArray(categories) && (
<select onChange={handleAssignCategory} value={todo.category}>
<option value="No category">No category</option>
{categories.map(({ category }, index) => (
<option value={category} key={index}>
{category}
</option>
))}
</select>
)}
<button onClick={handleDeleteTodo}>Delete</button>
</li>
);
};
export default Todo;
Screenshots
List of todos, first todo has category cate1
Todos:
[
{
done: false,
title: 'todo1',
id: 70,
category: 'cate1'
},
{
done: false,
title: 'todo2',
id: 71
},
{
done: false,
title: 'todo3',
id: 72
}
],
After removing todo1:
Todos:
[
{
done: false,
title: 'todo2',
id: 71
},
{
done: false,
title: 'todo3',
id: 72
}
],
How to correctly display the "No category" value?
From my understanding you should try to store the todos array in a useState and pass it in useEffect hook for it to refresh on its on. That should render your page once you delete or add any todo and the old info won't come up. for example,
const [todo, setTodo] = useState([{ done: false, title: 'todo2', id: 71}]);
useEffect(() => {
}, [todo])
<button onClick={(id)=>handleDeleteTodo(id)}>Delete</button>
const handleDeleteTodo = (id) => {
dispatch(actions.deleteTodo(id));
};
you send spasefic id and delete to do list item
I have 2 filters on a user list. A user can select a group containing members and use the search filter to search by last name. When the user backspaces a user to look for another, this resets the groups to all users. I need this to only show the users in the selected group.
TS
updateFilter(event) {
const val = event.target.value.toLowerCase();
const temp = this.temp.filter(function (d) {
return d.lastName.toLowerCase().indexOf(val) !== -1 || !val;
});
this.rows = temp;
if (this.table) {
this.table.offset = 0;
}
}
onGroupSelected($event) {
const groupId = $event.target ? $event.target.value : $event;
if (groupId === 'none') {
this.rows = this.temp;
} else {
const groupUsers = this.groupUserMap.get(groupId);
if (groupUsers) {
this.rows = this.temp.filter((serviceUser) =>
groupUsers.includes(serviceUser.id));
} else {
this.rows = [];
}
}
// #ts-ignore
this.userSelections = this.userSelections ? this.userSelections : {};
this.userSelections.groupId = groupId;
localForage.setItem(this.username, this.userSelections);
}
HTML
<input
type='text'
class="form-control w-200px"
placeholder='Search by Last Name...'
(keyup)='updateFilter($event)'
/>
<select class="form-control w-200px" (change)="onGroupSelected($event)">
<option value="none">All service users</option>
<option *ngFor="let group of groups"
[value]="group.id"
[selected]="userSelections.groupId === group.id">
{{group.name}}
</option>
</select>
You can use ngModel with tow-way binding, to save and manipulate search filters:
<select
class="form-control w-200px"
[(ngModel)]="selectedGroup"
(change)="onGroupSelected()"
>
<option value="none">All service users</option>
<option *ngFor="let group of groups" [value]="group.id">
{{ group.name }}
</option>
</select>
<input
type="text"
class="form-control w-200px"
placeholder="Search by Last Name..."
[(ngModel)]="search"
(keyup)="updateFilter()"
/>
And in order not to lose your users table you can create a copy which will be filtered and displayed.
public initialUsers = [
{ id: 100, groupId: 1, name: 'foo' },
{ id: 101, groupId: 2, name: 'bar' },
{ id: 102, groupId: 1, name: 'john' },
{ id: 103, groupId: 2, name: 'doe' },
{ id: 104, groupId: 2, name: 'baaar' },
{ id: 105, groupId: 1, name: 'fooodoe' },
];
public filteredUsers = [];
ngOnInit(): void {
this.filteredUsers = this.initialUsers;
}
Here is a demo on stackblitz, I used a list to go fast but It's just display. You just have to replace <ul> <li></li> </ul> by your <table> ... </table>
If you would want to take an observable way of doing this, then I would suggest to make a form of your controls, ooooor just use 2 form controls instead. I chose form here as it wraps it up nicely with both form controls (search and dropdown):
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
search: [''],
group: [0] // "all" option as initial id
})
}
Then we would listen to when the form value changes and assign the filtered data to a variable, here named filteredUsers$.
this.filteredUsers$ = this.form.valueChanges.pipe(
startWith(this.form.value), // to trigger initially
// 'this.users' refers to your original users array
map((value: any) => {
// 'all' option is chosen, just filter based on search
if (value.group === 0) return this.users.filter(x => x.lastName.toLowerCase().includes(value.search))
// filter by group and search
return this.users.filter(x => {
return (x.groupId === value.group) && (x.lastName.toLowerCase().includes(value.search.toLowerCase()))
})
})
)
That is it, then we just iterate filteredUsers$ in the template:
<tr *ngFor="let user of filteredUsers$ | async">
Of course we need the form in the view and it would look like this:
<form [formGroup]="form">
<input
type="text"
placeholder="Search by Last Name..."
formControlName="search"
/>
<select formControlName="group">
<option *ngFor="let group of groups" [ngValue]="group.id">
{{ group.name }}
</option>
</select>
</form>
Here is a DEMO with the above code
I want to use an input field (or something similar) that suggests an autocomplete, based on records from a data source.
In Vue I retrieve an array from a database table containing 3000+ records:
data(){
return{
inboundRelation_Trelation_data: [],
}
},
mounted(){
axios.get('/app/wms/allRelations', {
params: {
"dbConn": this.connString,
}
})
.then(response => this.inboundRelation_Trelation_data = response.data);
},
Based on this data, I want an autocomplete input field and/or dropdown. I've found 2 approaches online.. 1:
<select v-model="form.CUSTOMER_NAME">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</select>
This populates a dropdown, but my users experience this as tedious, as they need to type their letters quickly, otherwise after a small pause (like <0.5s), the next typed letter will start a new search and the results are inconsistent.
The other approach is using a data-list:
<input list="allRelations" type="text" #focus="$event.target.select()" v-model="form.CUSTOMER_NAME">
<datalist id="allRelations">
<option v-for="(relation, index) in inboundRelation_Trelation_data" :value="relation.RELATIONCODE" v-text="relation.COMPANYNAME + ' | ' + relation.RELATIONCODE"></option>
</datalist>
This works perfectly for small amounts of data. But when dealing with 100+ records (or 3000+ in this case), the whole browser freezes upon typing a letter. For some reason this is a very resource-heavy implementation. I've found some people with similar issues, but no solutions.
At the end of the day, I just want my users to be able to search in a huge list of 3000+ records. How do I approach this?
You can use vue-autosuggest package by this github link :
https://github.com/darrenjennings/vue-autosuggest
I am using this package and my data loads as my expect.
This is the template that you can use:
<template>
<div class="autosuggest-container">
<vue-autosuggest
v-model="form.CUSTOMER_NAME"
:suggestions="filteredOptions"
#focus="focusMe"
#click="clickHandler"
#input="onInputChange"
#selected="onSelected"
:get-suggestion-value="getSuggestionValue"
:input-props="{
class: 'form-control',
id: 'autosuggest__input',
field: 'CUSTOMER_NAME',
placeholder: 'Enter customer name for auto suggest',
}"
>
<div
slot-scope="{ suggestion }"
style="display: flex; align-items: center"
>
<img
:style="{
display: 'flex',
width: '25px',
height: '25px',
borderRadius: '15px',
marginLeft: '10px',
}"
:src="suggestion.item.avatar"
/>
<div style="{ display: 'flex', color: 'navyblue'}">
{{ suggestion.item.CUSTOMER_NAME }}
</div>
</div>
</vue-autosuggest>
</div>
And the Script section:
<script>
export default {
data() {
return {
searching: false,
query: '',
selected: '',
suggestions: [],
}
},
computed: {
filteredOptions() {
return [
{
data: this.suggestions.filter((option) => {
return (
option.name.toLowerCase().indexOf(this.query.toLowerCase()) > -1
)
}),
},
]
},
},
methods: {
clickHandler() {
//
},
onSelected(item) {
this.selected = item.item
},
async onInputChange(text = '') {
this.searching = true
await this.$axios
.get(`/app/wms/allRelations`,{
params: {
"dbConn": this.connString,
})
.then((res) => {
this.suggestions = res.data.data
})
.catch((e) => console.log(e))
.finally(() => (this.searching = false))
},
getSuggestionValue(suggestion) {
return suggestion.item.name
},
focusMe(e) {
this.onInputChange()
},
},
}
</script>
If still your browser freeze, you have to change your API response limit to something like descended 10 items.
Here I have written a Vue.js code. So here I have multiple templates so for multiple templates I have written multiple components.
So now I have to display each components based on selected category.
For example in the select option if i select single family homes then step2 component should display if i select real state then step3 components should displayed.
And also how to display the entered value in preview page
*vuejs*
<script>
Vue.component(
'step1',
{
template: '#step1',
props: [
'currentStep',
'step1'
]
}
);
Vue.component(
'step2',
{
template: '#step2',
props: [
'currentStep',
'step2'
]
}
);
Vue.component(
'step3',
{
template: '#step3',
props: [
'currentStep',
'step1',
'step2'
]
}
);
Vue.component(
'step4',
{
template: '#step4',
props: [
'currentStep',
'step1',
'step2'
]
}
);
var app = new Vue(
{
el: '#app',
data: {
currentStep: 1,
step1: {
category: '',
title: '',
address: '',
city:'',
state:'',
Zip:'',
price:'',
description:'',
},
step2: {
bedrooms_p: '',
bathrooms_p: '',
square_ft_p: '',
lot_size_p: '',
total_rooms_p: '',
stories_p: '',
year_built_p: '',
h_o_a_p: '',
garages_p: '',
basement_p: '',
roof_p: '',
exterior_p: '',
cooling_p: '',
heating_p: '',
schools_p: '',
image_p: '',
image2_p: '',
image3_p: '',
image4_p: '',
},
},
ready: function() {
console.log('ready');
},
methods: {
goToStep: function(step) {
this.currentStep ++;
},
stepsback: function(step) {
this.currentStep --;
},
}
}
);
</script>
<select>
<option>single family homes</option>
<option>real state</option>
<option>IT industry</option>
</select>
Here is the solution, although it has arbitrary values, as your example code is not complete.
<step1
currentStep="some_value"
step1="another_value"
v-if="'step1' === select_value"
/>
<step2
currentStep="some_value"
step2="another_value"
v-if="'step2' === select_value"
/>
<step3
currentStep="some_value"
step1="another_value"
step2="yet_another_value"
v-if="'step3' === select_value"
/>
<step4
currentStep="some_value"
step1="another_value"
step2="yet_another_value"
v-if="'step4' === select_value"
/>
The variable select_value is the selected value of the select element in your HTML. To use it you have to bind the value of the select in the Vue model.
For example you could use inside your Vue app something like that:
var app = new Vue(
{
el: '#app',
data: function () {
return {
select_value: ''
// rest of your data properties
}
}
// rest of your component functionality
}
);
and then change your select element to something like that:
<select v-model="select_value">
<option value="step1">single family homes</option>
<option value="step2">real state</option>
<option value="step3">IT industry</option>
<option value="step4">Some Other Option</option>
</select>
By making those changes you will have at the end the result you need.
Try out to give a value to your options then using dynamic component :
<select v-model="select_value">
<option value="1">single family homes</option>
<option value="2">real state</option>
<option value="3">IT industry</option>
<option value="4">Some Other Option</option>
</select>
and :
<component currentStep="some_value" step1="another_value" step2="yet_another_value" :is="`step${select_value}`" />
This question already has answers here:
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
While the VueJS data that is populated from Axios Get method and can be confirmed by outputting the data into console, I am not able to access the data from the front end.
Here is my sample JSON ouput
{
"locations" : {
"items" : ["abc","def","ghi"],
"selectedLocation" : ""
},
"categories" : {
"items" : {
"doctor" : ["test", "test2", "test3"],
"management" : ["test1","test2","test3"]
},
"subcategories":[],
"selectedCategory":"",
"selectedSubCategory":""
}
Here is my Front End Code
<form id="vueAppJobSearchPanel" class="offset-top-10 offset-sm-top-30" action="/job-search">
<div class="group-sm group-top">
<div style="max-width: 230px;" class="group-item element-fullwidth">
<div class="form-group">
<select v-model="locations.selectedLocation" id="form-filter-location" name="loc" data-minimum-results-for-search="Infinity" class="form-control">
<option value="">{{global.job_search_panel.placeholder_title_location}}</option>
<option v-for="location in locations.items" :value="location">${location}</option>
</select>
</div>
</div>
<div style="max-width: 230px;" class="group-item element-fullwidth">
<div class="form-group">
<select v-model="categories.selectedCategory" id="form-filter-location" name="cat" data-minimum-results-for-search="Infinity" class="form-control">
<option value="">{{global.job_search_panel.placeholder_title_category}}</option>
<option v-for="(category_obj, category) in categories.items" :value="category">${category}</option>
</select>
</div>
</div>
</div></form>
Here is my VueJS and AXIOS code
const vm = new Vue({
el: '#vueAppJobSearchPanel',
delimiters: ['${', '}'],
data: {
test: {
"items" : ["abc","def","ghi"],
"selectedLocation" : ""
},
locations : {},
categories : {}
},
mounted(){
this.loadDropDown();
},
methods: {
loadDropDown : function() {
let modelName = "CustomModule1";
let apiUrl = '/api/' + modelName + '/getFields';
axios.get(apiUrl)
.then(function (response) {
this.locations = constructLocationDropDownValues(response, modelName);
this.categories = constructCategorySubCategoryDropDownValues(response, modelName);
})
.catch(function (error) {
console.log(error);
});
}
}});
this.locations inside the loadDropDown function return a valid JSON. However the JSON is not passed to the front end (i.e. HTML). When I tried to output "locations" it will return an empty {}
Ideas? Thank you
The problem is with 'this' inside the axios callback. You should use arrow function to keep the context
axios
.get(apiUrl)
.then(response => {
this.locations = constructLocationDropDownValues(response, modelName)
this.categories = constructCategorySubCategoryDropDownValues(response,modelName)
})
.catch(function (error) {
console.log(error)
})