I have an event handler which updates state when a button is clicked which then renders the state to the page. The way this is rendered is not correct and is breaking my UI. My question is, Is it possible to apply formatting directly to the following event handler?
I have attempted to create a nested array so only 1 state is updated but no joy.
see this video for formatting issue I am having: https://www.screencast.com/t/HksUkk7g3G
I have also posted previously about this with the full code here.
React Nested Array
handleAddTelephone = () => {
this.setState({
telephoneType: this.state.telephoneType.concat([{ name: "" }]),
telephone: this.state.telephone.concat([{ name: "" }])
});
};
I need to format each state update. Something like...
handleAddTelephone = () => {
this.setState({
<span>telephoneType: this.state.telephoneType.concat([{ name: "" }])</span>,
<span>telephone: this.state.telephone.concat([{ name: "" }])</span>
});
};
This is my render function. The call to state needs to be around the MDBRow (Bootstrap row class). JSX will not allow this and Im currently using 2 separate calls, one for telephoneType and another for telephone.
<MDBRow className="grey-text no-gutters my-2">
{this.state.telephoneType.map((tt, ttidx) => (
<MDBCol key={ttidx} md="4" className="mr-2">
<select
defaultValue={tt.name}
onChange={this.handleTelephoneTypeChange(ttidx)}
className="browser-default custom-select">
<option value="Mobile">Mobile</option>
<option value="Landline">Landline</option>
<option value="Work">Work</option>
</select>
</MDBCol>
))}
{this.state.telephone.map((tn, tnidx) => (
<MDBCol key={tnidx} md="7" className="d-flex align-items-center">
<input
value={tn.name}
onChange={this.handleTelephoneChange(tnidx)}
placeholder={`Telephone No. #${tnidx + 1}`}
className="form-control"
/>
<MDBIcon icon="minus-circle"
className="mr-0 ml-2 red-text"
onClick={this.handleRemoveTelephone(tnidx)} />
</MDBCol>
))}
</MDBRow>
and the button...
<div className="btn-add" onClick={this.handleAddTelephone}>
<MDBIcon className="mr-1" icon="plus-square" />Add Telephone</div>
<br />
and array...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
/* telephone: {
type: [{ name: "" }],
number: [{ name: "" }]
}, */
telephoneType: [{ name: "" }],
telephone: [{ name: "" }],
emailType: [{ email: "" }],
email: [{ email: "" }]
};
}
Related
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.
I was using buefy <b-autocomplete> component and there is one property called v-model which is binding values to the input field
now I wanna bind Full Name into the field but the data consist with list[index].first_name and list[index].last_name, and the index is from a v-for loop.
Since v-model cannot bind a function (it has specific index so I cannot just concat it on computed then pass it on) so it's either v-model="list[index].first_name" or v-model="list[index].last_name"
How do I make it bind's these two?
You need a settable computed for full name, and you can v-model that. You just have to decide on a rule for where extra spaces go and what to do if there is no space.
new Vue({
el: '#app',
data: {
firstName: 'Joe',
lastName: 'Smith'
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(newValue) {
const m = newValue.match(/(\S*)\s+(.*)/);
this.firstName = m[1];
this.lastName = m[2];
}
}
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
First: {{firstName}}<br>
Last: {{lastName}}<br>
Full Name: <input v-model="fullName">
</div>
I am not sure if i get the question,but i am assuming that you have a list of names and last names and you want to give the ability to user to change those proprties of list.For more See the example in action
The "html" part
<div id="app">
<template v-for="item in list" :key="list.id">
<input type="text" :value="item.name" #input="changeList($event, item.id, 'name')">
<input type="text" :value="item.last_name" #input="changeList($event, item.id, 'last-name')">
Your full name is {{item.name}} {{item.last_name}}
<hr>
</template>
</div>
The "javascript(vue)" part
new Vue({
el: "#app",
data: {
list: [
{ id: 1, name: "name1", last_name: 'last_name 1' },
{ id: 2, name: "name2", last_name: 'last_name 2' },
{ id: 3, name: "name3", last_name: 'last_name 3' },
{ id: 4, name: "name4", last_name: 'last_name 4' }
]
},
methods: {
changeList(event, id, property) {
let value = event.target.value
for (item of this.list) {
if (item.id === id) {
if(property === 'name') {
item.name = value
}else if (property === 'last-name') {
item.last_name = value
}
}
}
}
}
})
As it's been said you can't use 2 values in v-model
But if you want to use <b-autocomplete> that means you already have the data and you can compute it in any way you want.
If you have an array of user objects (with firstname and lastname for example) you can do something like:
data() {
return {
users: [
//...
],
computedUsers: [],
}
},
mounted() {
this.users.forEach(user => {
this.computedUsers.push(user.firstname + ' ' + user.lastname)
})
}
and use this new array in the filteredDataArray (from the Buefy autocomplete example)
Fiddle example here
I am dynamically generating HTML Form based on a JSON. I am using the official example provided here: https://angular.io/guide/dynamic-form
I am trying to extend this example to have other controls in my dynamic form (textarea, radio button, checkbox, etc.). I have been able to achieve all controls, except for checkbox. Though the checkbox is visible on the screen, the value is not captured in its enclosing form.
My Checkbox definition goes like this:
import { QuestionBase } from './question-base-model';
export interface Values{
label: string;
value: string;
isChecked: boolean;
}
export class CheckBoxQuestion extends QuestionBase<boolean> {
controlType = 'checkbox';
options: Values[] = [];
constructor(options: {} = {}) {
super(options);
this.options = options['options'] || [];
}
}
My JSON for checkbox is like this:
{
key: 'sideDish',
label: 'Side dish',
type: 'checkbox',
order: 10,
name: 'sides',
options: [
{value: 'fries', isChecked: true, label: 'French Fries'},
{value: 'cheese', isChecked: false, label: 'Cheese'},
{value: 'sauce', isChecked: true, label: 'Tomato Sauce'}
]
}
And my Dynamic HTML template is as below:
<div [formGroup]="form" >
<label [attr.for]="question.key">{{question.label}}</label>
<div [ngSwitch]="question.controlType">
<!-- Checkbox -->
<div *ngSwitchCase="'checkbox'">
<span *ngFor="let opt of question.options; let i = index">
<input type="checkbox"
[formControlName]="question.key"
[id]="i+'_'+question.key"
[value]="opt.value">
<label [attr.for]="i+'_'+question.key">{{opt.label}}</label>
</span>
</div>
</div>
The FormControl code goes like this (no edits made here from the original example):
toFormGroup(questions: QuestionBase<any>[] ) {
let group: any = {};
questions.forEach(question => {
let validatorsArray = [];
if(question.required) validatorsArray.push(Validators.required);
group[question.key] = validatorsArray.length > 0 ? new FormControl(question.value || '', Validators.compose(validatorsArray)) : new FormControl(question.value || '');
});
return new FormGroup(group);
This method is consumed in the init function:
ngOnInit() {
this.form = this.qcs.toFormGroup(this.questions);
}
When I hit on Submit button, I am printing the "form.value". Rest all the controls prints with the value I have set in the form. But, an empty string as value for this checkbox. What am I doing wrong?
Thanks in advance!
I have implemented FormGroup in angular2 project to render form and i have used formArray for getting nested array data
form.component.html
<div class="col-md-12" formArrayName="social_profiles">
<div *ngFor="let social of resumeForm.controls.social_profiles.controls; let i=index" class="panel panel-default m-t-10">
<div class="panel-heading" style="min-height: 30px;">
<span class="glyphicon glyphicon-remove pull-right" *ngIf="resumeForm.controls.social_profiles.controls.length > 1" (click)="removeSocialProfiles(i)"></span>
</div>
<div class="panel-body" [formGroupName]="i">
<social-profile [group]="resumeForm.controls.social_profiles.controls[i]"></social-profile>
</div>
</div>
</div>
form.component.ts
export class FormComponent implements OnInit {
public resumeForm: FormGroup;
constructor(private formBuilder: FormBuilder) {}
ngOnInit() {
this.resumeForm = this.formBuilder.group({
name: ['', Validators.required],
label: ['',],
email: [''],
phone: [''],
social_profiles: this.formBuilder.array([])
});
this.addSocialProfiles();
}
initSocialProfiles() {
return this.formBuilder.group({
network: [''],
url: ['']
});
}
addSocialProfiles() {
const control = <FormArray>this.resumeForm.controls['social_profiles'];
const addrCtrl = this.initSocialProfiles();
control.push(addrCtrl);
}
removeSocialProfiles(i: number) {
const control = <FormArray>this.resumeForm.controls['social_profiles'];
control.removeAt(i);
}
}
Where social-profile is child form for array
social-profile.component.html
<div [formGroup]="socialForm">
<div class="col-md-6">
<input type="text" class="form-control" placeholder="Enter Network" formControlName="network">
</div>
<div class="col-md-6">
<input type="text" class="form-control" placeholder="Enter Network URL" formControlName="url">
</div>
</div>
social-profile.component.ts
export class SocialProfileComponent {
#Input('group')
public socialForm: FormGroup;
}
Get JSON from Service callback
{
"basics_info": {
"name": "Joh Doe",
"label": "Programmer",
"Social_profiles": [{
"network": "Twitter",
"url": "https://www.twitter.com/kumarrishikesh12"
}, {
"network": "Facebook",
"url": "https://www.facebook.com/kumarrishikesh12"
}]
}
}
Add data Form working perfect but how can i display existing nested array of json (which is get from api) in form on edit time on page init ? Like below image
Here let's assume you have extracted the content from the object basics_info, i.e:
and assigned the JSON to a variable data, so that you end up with this content of data:
{
"name": "Joh Doe",
"label": "Programmer",
"Social_profiles": [{
"network": "Twitter",
"url": "https://www.twitter.com/kumarrishikesh12"
}, {
"network": "Facebook",
"url": "https://www.facebook.com/kumarrishikesh12"
}]
}
Then let's patch the values. You can patch the values after you have received the JSON (or build the form) after receiving data. Here I patch values after form has been built.
Below I like for clarification call another method inside patchValues, which actually sets the values.
patchValues() {
const control = <FormArray>this.resumeForm.controls['social_profiles'];
this.data.Social_profiles.forEach(x => { // iterate the array
control.push(this.patch(x.network, x.url)) // push values
});
}
patch(network, url) {
return this.fb.group({
network: [network],
url: [url]
});
}
The child should catch these changes just fine. Here's a demo for you, the variables are different, since I used an existing form I had. But the logic is absolutely the same.
Plunker
I've a search box and multiple categories links available in the HTML page.
as soon as user input in searchBox, i want to show:
p in Products | filter {serachedText}
When user clicks a category hyperlink, i want to show
p in Products | filter {categoryID}
But because serachedText is still avialble, result showing for
p in Products | filter {categoryID} | filter {serachedText}
Is there any way i can clear the serachedText as soon as user clicks on anylink.
That would be really easy to do in angularjs.
In html.
<input ng-model="mytext">
<a href ng-click="mytext=''">Clear Text</a>
jsfiddle is here.
Your filter expression is wrong.
if your data is a JSON array having category and name properties like so:
self.Products = [
{ category: 1, name: 'Pencil' },
{ category: 1, name: 'Notebook' },
{ category: 2, name: 'Kitten' }
];
And you are binding the following things for the selected category and search text:
self.category = 1;
self.searchText = 'pen';
You could create a complex filter expression like so:
filter: { category: vm.category | name: vm.searchText }
This will filter both on category and searchText or in combination.
To clear out the searchText, you can watch if category changes using $scope.$watch and when it changes, clear up the searchText.
Take a look at the example below or at http://plnkr.co/edit/OEDvOn. In the example, my filter expression is a bit more complicated since the selected category is actually an object containing value and name properties for the selected category, thus I need to add .value to get the right thing to pass to the filter.
Another point: For doing client side filtering, this is fine, but if you are filtering on server side, I'd rather get the filtering done on a service layer and just returned the filtered result instead of all possible data... save bandwidth and transfer time.
(function(undefined) {
'use strict';
angular.module('myApp',[]);
angular.module('myApp')
.controller('searchCtrl', searchCtrl);
searchCtrl.$inject = ['$log', '$scope'];
function searchCtrl($log, $scope) {
/* jshint validthis: true */
var self = this;
self.searchText = undefined;
self.categories = [
{ value: undefined, name: 'All' },
{ value: 1, name: 'Fruit' },
{ value: 2, name: 'Snacks' },
{ value: 3, name: 'Flower' },
{ value: 4, name: 'Pet' },
{ value: 5, name: 'Stationary' }
];
self.category = self.categories[0];
self.data = [
{ category: 1, name: 'Apple' },
{ category: 1, name: 'Grapes' },
{ category: 2, name: 'Dorito' },
{ category: 2, name: 'KitKat' },
{ category: 3, name: 'Roses' },
{ category: 3, name: 'Orchid' },
{ category: 4, name: 'Hamster' },
{ category: 4, name: 'Kitten' },
{ category: 5, name: 'Pencil' },
{ category: 5, name: 'Notebook' }
];
$scope.$watch(function() { return self.category; }, function(val, old) {
self.searchText = undefined;
});
}
}());
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="searchCtrl as vm">
<div class="form-group">
<label>Category</label>
<select class="form-control" ng-options="cat.name for cat in vm.categories" ng-model="vm.category">
</select>
</div>
<div class="input-group">
<input class="form-control" type="textbox" ng-model="vm.searchText" placeholder="Search text here..." />
<span class="input-group-btn">
<button type="button" class="btn btn-primary">
<i class="glyphicon glyphicon-search"></i> Search</button>
</span>
</div>
<div class="well well-sm" style="margin-top:20px">
<ul ng-repeat="item in vm.data | filter:{category:vm.category.value, name:vm.searchText}">
<li>{{item.name}}</li>
</ul>
</div>
</div>
I'd suggest directive will be the best option
MarkUp
<input ng-model="mytext">
<a href ng-click="mytext=''" clear="mytext">Clear Text</a>
Directive
app.directive('a', function() {
return {
restrict: 'E',
scope: {
clear: '='
},
compile: function(scope, ele, attrs) {
ele.on('click', function() {
scope.$apply(function() {
scope.clear = undefined;
})
})
}
}
})