I have a select, where the user's servers are rendered. Each server has several security groups. I need to show the user on select, only the server that does not have that security group. However, in my select, it is repeating the name of the server for each different security group.
Example: I have the server test01 it has the security groups: novo09, default, relow.
When I select the security group hello01, I want to show the server test01 only once in select, and not 2 times, it is showing 2 times because I have 2 groups already included in server = novo09, relow.
server payload:
{id: "b9c7e32a-bf99-4250-83cb-13523f9c1604", name: "test01", flavor: {…}, securityGroups: [{name: 'relow'}, {name: 'Novo09'} ]}
My component code:
public selectedGroupName: string = '';
ngOnInit(): void {
let counter = 0;
forkJoin(
this.serverService.getServer(),
this.securityGroupService.getSecurityGroups())
.pipe(takeWhile(() => this.alive),
concatMap((data) => {
this.servers = data[0].map((item) => ({
...item,
securityGroups: this.serverService.getServerById(item.id)
.pipe(map(server => server["security_groups"]))
}))
this.securityGroups = data[1].map((item) => ({
...item,
expanded: false,
}))
return this.servers.map((server) => server.securityGroups);
}), concatMap(items => items))
.subscribe((data: any) => {
this.servers[counter].securityGroups = data.filter(group => group.name !== 'default');
counter++;
})
this.form = this.formBuilder.group({
server: new FormControl('')
})
}
public openAttachModal(content, securitGroupName: string) {
this.selectedGroupName = securitGroupName;
}
My Html code:
<ng-container *ngFor="let group of securityGroups">
<div class="user-info__basic">
<h5 class="mb-0">{{group.name}}</h5>
</div>
<button type="button" class="btn btn-primary" ngbTooltip="Attach Group" triggers="hover"
(click)="openAttachModal(attachModal, group.name)"><i class="fas fa-plus"></i></button>
</ng-container>
<ng-container *ngFor="let server of servers">
<ng-container *ngFor="let secGroup of server.securityGroups">
<ng-container *ngIf="secGroup.name !== selectedGroupName">
<option [value]="server.id">
{{server.name}}
</option>
</ng-container>
</ng-container>
<ng-container *ngIf="server.securityGroups.length == 0">
<option [value]="server.id">
{{server.name}}
</option>
</ng-container>
</ng-container>
image result of select
https://imgur.com/7K2G7HF
When the user selects the security group, you could trigger an event to extract the list of servers where that security group is not present. Something like:
const servers = servers.filter(server => !server.securityGroups.find(secGroup => secGroup.id === selectedSecurityGroup.id));
// Where selectedSecurityGroup is the security group the user selected
And you could avoid adding a server twice:
servers = servers.filter((server, index, self) => self.indexOf(server) === index);
This filter will keep only the first instance of each server.
Related
I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>
In my app, I have a list where the user can add to and delete elements from it. My problem is, when I click an element (it can be in the middle, at the end etc.), it deletes the first element of the list. And when I refresh the page, I can see the previously 'deleted' elements. Like I haven't deleted anything. Here is my code. What's wrong with it and how should I fix it?
HTML:
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row)">block</mat-icon>
</button>
TS:
deleteWorkItem(row: IProduct, index: number) {
let supplierProduct: ISupplierProduct = {
Supplier: {
SupplierId: this.SupplierId
},
Product: {
ProductId: row.ProductId
}
};
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = 'Ürünü silmek istiyor musunuz?';
this.confirmDialogRef.afterClosed().subscribe(result => {
if (result) {
this._service.update('Supplier/DeleteSupplierProduct', supplierProduct).subscribe(response => {
this._customNotificationService.Show('Ürün silindi', 'Tamam', 2);
});
let tempData = this.dataSource.data.slice(0);
tempData.splice(index, 1);
this.dataSource = new MatTableDataSource(tempData);
this.EditIndex = undefined;
this._products = this.dataSource.data;
this.ProductChange.emit(this._products);
}
});
}
You don't seem to pass index into deleteWorkItem method.
You need to declare a template variable within *ngFor as follows:
<div *ngFor="let row of data; let i = index">
...
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row, i)">block</mat-icon>
</button>
</div>
I need to get the item ID of the selected item, In my case the user types in the input and gets results from an API in form of array that iterates the <Option> as below.
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Select Invoices"
defaultValue={[]}
onChange={handle_select_invoices}
optionLabelProp="label"
onSearch={search_invoice_by_number}
>
{
invoices.map((el,index) => {
return <Option key={el.invoice_id} value={el.invoice_number}></Option>
})
}
</Select>
When user select an option, the handle_select_invoices is fired. It takes two params value and key.
const handle_select_invoices =(value,key) => {
console.log(' ************** IDS ****************')
console.log(key)
}
function search_invoice_by_number(value) {
var data={'invoice_number':value};
axios.post('http://localhost:4000/get_invoice_by_number',data).then(
response => {
if(response.data.length > 0){
set_invoices(response.data);
}else{
set_invoices([]);
}
},error =>{
Swal.fire({
title: 'Error!',
text: 'Please Contact your software developer',
icon: 'error',
confirmButtonText: 'OK'
})
}
)
}
The problem
When user selects multiple items, the console.log shows an empty Json elements and only the last element in the array is filled.
What is wrong in the code that leads to this result?
Alright, I think I understand what you mean. Here is how I suggest you do it. Use a variable in state that keeps track of selectedValues. In select onChange just set them in state like handleChange = values => setSelectedValues(values). In search, after you get the new data from the API, filter the selectedValues like so:
set_invoices(response.data);
const values = selectedValues.filter(value =>
data.map(i => i.invoice_number).includes(value)
);
setSelectedValues(values); // filter out the values that do not exist in the new data
and your select would contain an additional prop value={selectedValues}.
Here is a working example with some dummy data: https://codesandbox.io/s/pedantic-carson-xwydd?file=/src/App.js:699-805
SAMPLE https://stackblitz.com/edit/usjgwp?file=index.html
I want to show a number of kendo dropdownlist(s) on a page. The exact number depends on an API call. This API call will give me an array of stakeholder objects. Stakeholder objects have the following properties: Id, name, type, role and isSelected.
The number of dropdownlist that has to be shown on this page should be equal to the number of unique type values in the API response array. i.e,
numberOfDropdowns = stakeholders.map(a => a.type).distinct().count().
Now, each dropdown will have a datasource based on the type property. i.e, For a dropdown for type = 1, dataSource will be stakeholders.filter(s => s.type == 1).
Also the default values in the dropdowns will be based on the isSelected property. For every type, only one object will have isSelected = true.
I have achieved these things by using the following code:
<template>
<div
v-if="selectedStakeholders.length > 0"
v-for="(stakeholderLabel, index) in stakeholderLabels"
:key="stakeholderLabel.Key"
>
<label>{{ stakeholderLabel.Value }}:</label>
<kendo-dropdownlist
v-model="selectedStakeholders[index].Id"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
></kendo-dropdownlist>
<button #click="updateStakeholders">Update form</button>
</div>
</template>
<script>
import STAKEHOLDER_SERVICE from "somePath";
export default {
name: "someName",
props: {
value1: String,
value2: String,
},
data() {
return {
payload: {
value1: this.value1,
value2: this.value2
},
stakeholders: [],
selectedStakeholders: [],
stakeholderLabels: [] // [{Key: 1, Value: "Stakeholder1"}, {Key: 2, Value: "Stakeholder2"}, ... ]
};
},
mounted: async function() {
await this.setStakeholderLabels();
await this.setStakeholderDataSource();
this.setSelectedStakeholdersArray();
},
methods: {
async setStakeholderLabels() {
let kvPairs = await STAKEHOLDER_SERVICE.getStakeholderLabels();
kvPairs = kvPairs.sort((kv1, kv2) => (kv1.Key > kv2.Key ? 1 : -1));
kvPairs.forEach(kvPair => this.stakeholderLabels.push(kvPair));
},
async setStakeholderDataSource() {
this.stakeholders = await STAKEHOLDER_SERVICE.getStakeholders(
this.payload
);
}
setSelectedStakeholdersArray() {
const selectedStakeholders = this.stakeholders
.filter(s => s.isSelected === true)
.sort((s1, s2) => (s1.type > s2.type ? 1 : -1));
selectedStakeholders.forEach(selectedStakeholder =>
this.selectedStakeholders.push(selectedStakeholder)
);
},
async updateStakeholders() {
console.log(this.selectedStakeholders);
}
}
};
</script>
The problem is that I am not able to change the selection in the dropdownlist the selection always remains the same as the default selected values. Even when I choose a different option in any dropdownlist, the selection does not actually change.
I've also tried binding like this:
<kendo-dropdownlist
v-model="selectedStakeholders[index]"
value-primitive="false"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
></kendo-dropdownlist>
If I bind like this, I am able to change selection but then the default selection does not happen, the first option is always the selection option i.e, default selection is not based on the isSelected property.
My requirement is that I have to show the dropdown with some default selections, allow the user to choose different options in all the different dropdowns and then retrieve all the selection then the update button is clicked.
UPDATE:
When I use the first method for binding, The Id property of objects in the selectedStakeholders array is actually changing, but it does not reflect on the UI, i.e, on the UI, the selected option is always the default option even when user changes selection.
Also when I subscribe to the change and select events, I see that only select event is being triggered, change event never triggers.
So it turns out that it was a Vue.js limitation (or a JS limitation which vue inherited),
Link
I had to explicitly change the values in selectedStakeholders array like this:
<template>
<div
v-if="selectedStakeholders.length > 0"
v-for="(stakeholderLabel, index) in stakeholderLabels"
:key="stakeholderLabel.Key"
>
<label>{{ stakeholderLabel.Value }}:</label>
<kendo-dropdownlist
v-model="selectedStakeholders[index].Id"
:data-source="stakeholders.filter(s => s.type == stakeholderLabel.Key)"
data-text-field="name"
data-value-field="Id"
#select="selected"
></kendo-dropdownlist>
<button #click="updateStakeholders">Update form</button>
</div>
</template>
And in methods:
selected(e) {
const stakeholderTypeId = e.dataItem.type;
const selectedStakeholderIndexForTypeId = this.selectedStakeholders.findIndex(
s => s.type == stakeholderTypeId
);
this.$set(
this.selectedStakeholders,
selectedStakeholderIndexForTypeId,
e.dataItem
);
}
I have a select dropdown:
<select class="form-control" id="teamSelect" [disabled]="!isSubmitButtonDisabled()" [(ngModel)]="selectedTeam" (ngModelChange)="onTeamChange($event)" style="width: 50%;">
<option *ngFor="let team of teams" [ngValue]="team">{{ team.text }}</option>
</select>
I populate it in ngOnit like this:
ngOnInit() {
this._microService.getTeams()
.subscribe(team => {
this.teams = team.map(m => { return { text: m.name, value: m.name } as Select; });
this.teams.unshift( { text: '', value: ''} as Select)
}
);
}
No problems!
Later I have button click that should add a new item to the dropdown...
onCreateTeam(team: string = ''): void {
if(team != '') {
let t = { text: team, value: team } as Select;
this.teams.push(t);
this.selectedTeam = t;
}
}
The dropdown gets populated but then I can't select anything. Is there a way to update the array in let team of teams from my component so the DOM gets the new option, and still allows a selection?
No errors are thrown in the Console.