I have a requirement that after typing certain content in an tag, pressing enter will do search function.
It running well normally like:
<input
onChange={this.onInputChange}
onKeyPress={this.onSearch}
/>
onInputChange = (e) => {
console.log(2);
this.setState({
searchText: e.target.value
})
}
onSearch = (e) => {
console.log(1);
if (e.which === 13) {
search(this.state.searchText); // some search api ...
}
}
But if user Enter really quickly, like 0.1s, the this.state.searchText is not get updated properly.
This is not just caused by setState is async method, but the onKeyPress is trigger before onChange.
is there any idea to deal with this issue?
So I can't really understand why you use two separate functions.
First of all, if you only use searchText for the two functions you could just do:
HTML
<input
onKeyPress={this.onKeyPress} />
JS
onKeyPress = e => {
if(e.which === 13) {
// Send Query
search(e.target.value);
}
}
And even if you needed searchText somewhere else you could just do:
onKeyPress = e => {
let value = e.target.value;
if(e.which === 13) {
// Send Query
search(value);
} else this.setState({searchText: value});
}
If I missed something please tell me ^^
<input
onChange={this.onInputChange}
onKeyDown={this.onSearch}
/>
onInputChange = (e) => {
this.setState({
searchText: e.target.value
})
}
onSearch = (e) => {
if (e.keyCode === 13) {
search(this.state.searchText); // some search api ...
}
}
<input
ref={(input) => this.selectVal = input}
onKeyPress={(e) => e.which === 13 ?this.onSearch():''}
/>
onSearch = () => {
console.log("value",this.selectVal.value);
// search(this.input.current.value); // some search api ...
}
try this way
Related
I just start working on existing code and my task is to place a spinner in auto completion but not sure where exactly to put isLoading = true and isLoading = false in my Typescript. I tried to put all over the place but some reason the spinner icon is still not showing when I try to search some data that store in the backend.
It kinda look like this project https://stackblitz.com/edit/angular-material-autocomplete-async2 and I tried to copy but the spinner icon is still not showing in my project when I start typing. any suggestion or help? thanks
isLoading = false;
#Input() set workspace(ws: Workspace) {
this._workspace = ws;
if (ws && ws.tags) {
this.superTags = ws.tags.filter(tag => {
return tag.type == 1;
});
}
}
constructor(private tagService: TagService) {
this.mapper();
}
ngOnInit(): void {
this.tagService.getAllTagsByType('super').subscribe((superTags) => {
if (superTags)
this.allsuperTags = superTags;
this.allsuperTags.forEach(superTag => {
this.allSuperTagNames.push(superTag.tag);
});
})
}
private _filter(value: string): String[] {
if (value.length > 0) {
const filterValue = value.toLowerCase();
return this.allSuperTagNames.filter(tag => tag.toLowerCase().indexOf(filterValue) === 0);
}
}
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value;
if (event1 === null) {
input == event.input;
value == event.value;
}
else {
input == event1.option.value;
value == event1.option.value;
}
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toLowerCase() === value.toLowerCase()) && !this.superTags.find((f) => f.tag.toLowerCase() === value.toLowerCase()))
{
this.superTags.push({ tag: value.trim().toLowerCase(), type: TagType.super });
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
}
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
mapper() {
this.filteredSuperTags = this.tagCtrl.valueChanges.pipe(
startWith(null),
map((tag: string | null) => tag ? this._filter(tag) : this.allSuperTagNames.slice()));
}
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngIf="isLoading" class="is-Loading">
<mat-spinner diameter="20"></mat-spinner>
</mat-option>
<ng-container *ngIf="!isLoading">
<mat-option *ngFor="let tag of filteredSuperTags | async" [value]="tag">
{{tag}}
</mat-option>
</ng-container>
</mat-autocomplete>
It seems like the code which you added only doing the synchronous operation. Even though you subscribed to the form-control, the tags are being filtered locally from pre-loaded data, and the time taken will be very little. To really show the spinner, you may either need to call an API or add some delay to mock the filter method as observable as shown in this example
How can I create an observable with a delay
This way you can show the spinner during that delay.
I have input where I user can search/type a data and I'm wondering how I can make the user ONLY able to search what was already provided from the backend and forbid them from creating new data.
so in my backend I've "Chart" and "Map" words and I figuring out a way to make the user able to search only this. If I user type other than this and press enter, nothing will happen.
Right now, if the user type other text than this two and press enter, it create a new data and push it to the backend.
I don't want to hard code like this (input == "Chart" || input == "Map") since we will be adding more data in the backend later.
super <= data type like "Chart and Map"
<div>
<input matInput #input [formControl]="tagCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="add($event,null)">
</div>
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
if (event1 == null) {
const input = event.input;
const value = event.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
// Add Tag
if ((value || '').trim()) {
this.superTags.push({ tag: value.trim(), type: TagType.super });
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
else {
const input = event1.option;
const value = event1.option.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
}
any recommendation or help will be really appreciated.
Let's say you have suggestions array that populated by user search.
Listen on autocomplete optionActivated event, when true onSelected event will fired with Enter, bypass add event.
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value?.trim();
if (!value || this._optionActivated) {
return;
}
// Check if value from create event (Enter, Comma) already exists on search result.
const suggested = this.suggestions.find(item => item?.id && item.title === value);
if (suggested) {
console.log("Already exists on search result", suggested)
// add to selection list.
} else {
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => {
console.log("added", tag)
// add to selection list.
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
optionActivated(event: MatAutocompleteActivatedEvent) {
this._optionActivated = !!event.option;
}
optionClosed() {
this._optionActivated = false;
}
Template.html:
<mat-autocomplete
#auto="matAutocomplete"
(closed)="optionClosed()"
(optionActivated)="optionActivated($event)"
(optionSelected)="onSelected($event)"
>
<mat-option *ngFor="let tag of suggestions" [value]="tag">
{{ tag.title }}
</mat-option>
</mat-autocomplete>
I have input where I user can search/type a data and I'm wondering how I can make the user ONLY able to search what was already provided from the backend and forbid them from creating new data.
so in my backend I've "Chart" and "Map" words and I figuring out a way to make the user able to search only this. If I user type other than this and press enter, nothing will happen.
Right now, if the user type other text than this two and press enter, it create a new data and push it to the backend.
I don't want to hard code like this (input == "Chart" || input == "Map") since we will be adding more data in the backend later.
super <= data type like "Chart and Map"
<div>
<input matInput #input [formControl]="tagCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" (matChipInputTokenEnd)="add($event,null)">
</div>
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let tag of filteredSuperTags | async" [value]="tag">
{{tag}}
</mat-option>
</mat-autocomplete>
tagCtrl = new FormControl();
superTags: Tag[] = [];
filteredSuperTags: Observable<String[]>;
allsuperTags: Array<Tag> = [];
allSuperTagNames: Array<String> = new Array<String>();
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
if (event1 == null) {
const input = event.input;
const value = event.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
else {
const input = event1.option;
const value = event1.option.value;
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{this.superTags.push({ tag: value.trim(), type: TagType.super }); } }
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
}
any recommendation or help will be really appreciated.
Your backend was adding the option no matter what because you were calling the service before verifying if the value existed. If its a form, its super weird to call the backend everytime you select something in a typeahead. In my opinion it should be done once when everything is filled properly or on some kind of submit event.
I just moved the service call inside the verification and removed a if that was only used to assign the input and the value but was duplicating about 10 lines. Now you have an if assigning the value and then followed by the content of the previous if.
add(event: MatChipInputEvent, event1: MatAutocompleteSelectedEvent): void {
const input = event.input;
const value = event.value;
if (event1 === null) {
input = event.input;
value = event.value;
else {
input = event1.option;
value = event1.option.value;
}
if ((value || '').trim()) {
if (this.allSuperTagNames.find((f) => f.toUpperCase() === value.toUpperCase()))
{
this.superTags.push({ tag: value.trim(), type: TagType.super });
this.tagService.addTag(this._workspace.guid, 'workspace', value).subscribe((tag) => console.log("added", tag));
this.snackbar.open(input.value + " has been added as super tag.", " ", { duration: 2500 });
}
}
// Reset the input value
if (input) {
input.value = '';
}
this.tagCtrl.setValue(null);
}
Long story short:
<form action="example.com/" method="get">
<input type="hidden" name="q" value="one,two,">
<input type="text" name="q">
</form>
The goal is that, when the user inputs e.g. "three", the website
example.com/?q=one,two,three
is called, instead of example.com/?q=one,two,&q=three.
A solution without JavaScript would be ideal, but I suspect that's not possible.
Thank you so much!
If you don't mind using an array then you can try using this solution
<form action="example.com/" method="GET">
<input type="hidden" name="q[]" value="one">
<input type="hidden" name="q[]" value="two">
<input type="text" name="q[]">
<input type="submit" name="submit">
</form>
this way you will get an array of values on submit then you can handle it on server side. But if you just still want to use your method then Javascript is required. With javascript you can get formdata then append the user input to the form then send it using ajax.
Yes, it's not possible without using Javascript as far I know.
it's better if you handle this at the backend.
But, if you really want to do at the front-end, you can do as follows (With vanilla Javascript).
document.addEventListener("DOMContentLoaded", function(){
let form = document.getElementById('form');
let query = '';
let valueObj = {};
if(form){
form.addEventListener('submit', (e) => {
e.preventDefault();
let exceptinput = ['submit','reset','button','file','image'];
let allElem = e.srcElement;
if(allElem.length > 0){
createValueObj(allElem, valueObj, exceptinput).then(data => {
console.log(data);
query = serialize(data);
window.location = 'http://www.example.com/?' + query;
}).catch(err => {
console.log(err);
})
}
});
}
let serialize = (obj) => {
var str = [];
for (var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
let insertValueToObj = (key, value, obj) => {
if(obj[key]){
obj[key] = obj[key]+','+ value;
}else{
obj[key] = value;
}
return obj;
}
let createValueObj = (arr, obj, exceptinput) => {
return new Promise((resolve, reject)=>{
for (let index = 0; index < arr.length; index++) {
let isProperInput = exceptinput.includes(arr[index].type);
if(!isProperInput) {
let key = arr[index].name;
let value = arr[index].value.trim();
obj = insertValueToObj(key, value, obj);
}
if(index == (arr.length -1)){
resolve(obj);
}
}
});
}
});
thanks.
I have 3 users Admin, Supervisor and Student. What I want to do is, Admin adn supervisor can edit and delete students data while student can only delete and edit his own data. He can only view other's data.
I get roles for user in json like below:
Admin: ["Administrator"]
Supervisor: ["Supervisor", "Guest"]
Student: ["Student", "Guest"]
Below is what I am trying to do:
Exhibits.component.ts
getCurrentUser() {
this.userService.getCurrent()
.then(
(response) => {
this.currentUserId = response.id;
for (let role of response.roles) {
if (role === 'Administrator') {
this.canEdit = true;
} else if (role === 'Supervisor') {
this.canEdit = true;
} else if (role === 'Student') {
this.canEdit = false;
}
}
}
).catch(
(error) => console.log(error)
);
}
Exhibits.component.html
<div *ngIf="canEdit && this.currentUserId === exhibit.userId">
<button md-icon-button click-stop-propagation color="primary" [routerLink]="['/mobile-content/exhibits/edit', exhibit.id]"
title="{{ 'edit' | translate }}">
<md-icon>{{ !inDeletedPage ? 'edit' : 'remove_red_eye'}}</md-icon>
</button>
<button md-icon-button click-stop-propagation color="warn" (click)="deleteExhibit(exhibit)" *ngIf="!exhibit.used && !inDeletedPage"
title="{{ 'delete' | translate }}">
<md-icon>delete_forever</md-icon>
</button>
</div>
I am trying to show Exhibits which i got in array according to userId. It means, in exhibits json response, I am getting "userId" which i am trying to match with current user's userId. Oly thing is student can only see delete and edit option for his created exhibit but admin and supervisor can see edit and delete option for all users created exhibits.
Can anyone help me to figure this out?
First, I would suggest to converting this to an enum on both your front and back end, as opposed to relying on string matching.
But judging from your code, if I'm reading correctly, no student would ever be able to have an edit and delete button because you're always setting to false on that user type.
Your second problem is going to be in your *ngIf that states the following:
*ngIf="canEdit && this.currentUserId === exhibit.userId"
This is going to result in these buttons always being hidden at unwanted times because even on administrators and other users you need the conditional of the user ids matching to evaluate to true. You also should not need to specify this in a template.
Personally, I would do something more like this.
getCurrentUser() {
this.userService.getCurrent()
.then(
(response) => {
this.currentUserId = response.id;
for (let role of response.roles) {
if (role === 'Administrator') {
this.canEdit = true;
} else if (role === 'Supervisor') {
this.canEdit = true;
} else if (role === 'Student') {
if (this.currentUserId === this.exhibit.userId) {
this.canEdit = true;
} else {
this.canEdit = false;
}
}
}
}
).catch(
(error) => console.log(error)
);
}
You would then be able to just change your template *ngIf to:
*ngIf="canEdit"
As an aside, you may also want to change your checking of the role to a switch statement, it is more performant and will make your code cleaner.
OR you could do this, which would accomplish the same thing.
getCurrentUser() {
this.userService.getCurrent()
.then(
(response) => {
this.currentUserId = response.id;
for (let role of response.roles) {
if (role === 'Administrator') {
this.canEdit = true;
} else if (role === 'Supervisor') {
this.canEdit = true;
}
}
}
).catch(
(error) => console.log(error)
);
}
Template code would be:
*ngIf="canEdit || this.currentUserId === exhibit.userId"