Filter lightning:datatable with select-picklist and multiple apex classes - datatable

i try to filter datatable results by clicking a lightning:select value. If I have the value "Alle" it should show all values and filter the datatable when clicking any other value. Do you have an idea how to adjust the js controller and the helper so it works? I already tried it by using Case but it isnt supported in the developer console. I made a second apex class to contain all values and the other one which is for the values when a picklist value is selected
public class MediathekSearchController {
#AuraEnabled(cacheable=true)
public static List<List<sObject>> getSearchResult(String searchKey, String KategorieValue){
String findStr = '*'+searchKey+'*';
Boolean isEmptySearch = String.isEmpty(searchKey);
List<List<sObject>> searchResult = [FIND : findStr
IN ALL FIELDS RETURNING
Mediathek__c (Id, Name, Bezeichnung__c, Typ__c, Zielgruppe__c, Umfang__c, Bezeichnung_Link__c, Bezeichnung_Search__c)];
return searchResult;
}
public static List<List<sObject>> getFilteredSearchResult(String searchKey, String KategorieValue){
String findStr = '*'+searchKey+'*';
Boolean isEmptySearch = String.isEmpty(searchKey);
List<List<sObject>> searchResultFiltered = [FIND : findStr
IN ALL FIELDS RETURNING
Mediathek__c (Id, Name, Bezeichnung__c, Typ__c, Zielgruppe__c, Umfang__c, Bezeichnung_Link__c, Bezeichnung_Search__c WHERE Typ__c=:KategorieValue)];
return searchResultFiltered;
}
}
Component:
<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="MediathekSearchController">
<!-- handlers-->
<aura:handler name="init" value="{!this}" action="{!c.init}"/>
<!-- attributes -->
<aura:attribute name="showSearchResults" type="Boolean" default="false"/>
<aura:attribute name="searchKey" type="String"/>
<aura:attribute name="mediathekList" type="List" default="Mediathek[]"/>
<aura:attribute name="mediathekColumns" type="List"/>
<aura:attribute name="KategorieValue" type="String" default="Alle"/>
<div class= "slds-box">
<div class="slds-grid slds-wrap" >
<div class="slds-size_12-of-12">
<lightning:layout multipleRows="true">
<lightning:layoutItem size="8">
<lightning:input name="searchKey" placeholder="Suchbegriff einfügen" value="{!v.searchKey}" onkeydown="{!c.search}"/>
</lightning:layoutItem>
<lightning:layoutItem size="2">
<lightning:button variant="brand" label="Suchen" title="Search" onclick="{!c.search}" class="customButton" />
</lightning:layoutItem>
<lightning:layoutItem size="2">
<lightning:select aura:id="KategorieValue" name="Kategorie" label="Kategorie" required="false" value="{!v.KategorieValue}">
<option value="Alle">Alle</option>
<option value="Formular">Formular</option>
<option value="Merkblatt">Merkblatt</option>
<option value="Flyer">Flyer</option>
<option value="Infobroschüre">Infobroschüre</option>
</lightning:select>
</lightning:layoutItem>
</lightning:layout>
</div>
</div>
<div class="haha">
<aura:if isTrue="{!v.showSearchResults}">
<lightning:layout multipleRows="true">
<lightning:layoutItem padding="around-small" size="12" >
<lightning:datatable keyField="id"
data="{!v.mediathekList}"
columns="{!v.mediathekColumns}"
hideCheckboxColumn="true"/>
</lightning:layoutItem>
</lightning:layout>
</aura:if>
</div>
</div>
</aura:component>
JS Controller
({
init: function (component, event, helper){
component.set('v.mediathekColumns', [
{label: 'Bezeichnung', fieldName: 'Bezeichnung_Search__c', type: 'url' , fixedWidth: 395,
typeAttributes: {label: { fieldName: 'Bezeichnung__c' }, target: '_blank'}
},
{label: 'Typ', fieldName: 'Typ__c', type: 'text', fixedWidth: 116,
},
{label: 'Zielgruppe', fieldName: 'Zielgruppe__c', type: 'text', fixedWidth: 116,
},
{label: 'Umfang', fieldName: 'Umfang__c', type: 'text', fixedWidth: 112,
},
]);
var action = component.get("c.getSearchResult");
action.setCallback(this, function(response){
var state = response.getState();
if (state === "SUCCESS") {
var records =response.getReturnValue();
records.forEach(function(record){
record.BEZ = record.Bezeichnung__c;
});
component.set("v.mediathekList", records);
}
});
$A.enqueueAction(action);
},
search : function(component, event, helper) {
helper.getSearchResultsFromApex(component, event, helper);
component.set("v.showSearchResults",true);
} })
Helper
({
getSearchResultsFromApex : function(component, event, helper){
var action = component.get("c.getSearchResult");
action.setParams({ searchKey : searchStr, KategorieValue : kategoryVal});
// Create a callback that is executed after
// the server-side action returns
action.setCallback(this, function(response) {
var state = response.getState();
if (state === "SUCCESS") {
var result = response.getReturnValue();
result.forEach(function(result){
result.Name = '/'+result.Id;
});
// SOSL will always return the list in the order they were queried
component.set("v.mediathekList",result[0]);
}
else if (state === "INCOMPLETE") {
// do something
}
else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " +
errors[0].message);
}
} else {
console.log("Unknown error");
}
}
});
$A.enqueueAction(action);
}
})

Not sure what the problem is to be honest?
#AuraEnabled(cacheable=true)
public static List<List<sObject>> getSearchResult(String searchKey, String KategorieValue){
String findStr = '*'+searchKey+'*';
List<List<sObject>> searchResult;
if(String.isBlank(kategorieValue)) || kategorieValue == 'Alle'){
searchResult = [FIND :findStr IN ALL FIELDS
RETURNING Mediathek__c (Id, Name, Bezeichnung__c, Typ__c, Zielgruppe__c, Umfang__c, Bezeichnung_Link__c, Bezeichnung_Search__c)];
} else {
searchResult = [FIND :findStr IN ALL FIELDS
RETURNING Mediathek__c (Id, Name, Bezeichnung__c, Typ__c, Zielgruppe__c, Umfang__c, Bezeichnung_Link__c, Bezeichnung_Search__c
WHERE Typ__c=:KategorieValue)];
}
return searchResult;
}
If you feel really fancy you could look into dynamic soql or maybe using real data categories for it (more than just a picklist) but plain old if should be enough?

Related

React hooks one checkbox checked to uncheck other checkbox

Hi I have a parent component which contains a bunch child components which are checkboxes.
parent component is something like this inside:
const [items, setItems] = useState([
{
id:1,
selected: false,
},
{
id:2,
selected: false,
}
]);
const changeSelected = (id) =>
{
items.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
}
return(
<div>
{items.map((item)=>{
<Child item={item} changeSelected={changeSelected}/>
})}
</div>
)
and in the child component, it has something like this inside:
return(
<div>
<input type="checkbox" checked={props.item.selected} onChange={()=>{props.changeSelected(props.item.id)}} />
</div>
)
I know partially this isnt working is because useState is async but I dont know what to do to make it work, or if I should try a different approach? Thank you
You can refactor your function to this:
const changeSelected = (id) => {
setItems(prev => ([...prev, {id, selected: !prev.filter(x => x.id === id)[0].selected}]))
}
Obviously I forgot to setItem according to the two answers I received. But I think the right way to do it is to make a deep copy of my existing items, and setItems again. For my future reference, here is what I have now working (an example):
let newItems = [...items];
newItems.forEach((item)=>
{
if (item.id === id)
{
item.selected = !item.selected;
}
else{
item.selected = false;
}
})
setProducts(newItems);
You are not updating the state anywhere. You should make a copy of the array and then change the object you want to:
const changeSelected = (id) =>
{ let newItems = [];
items.forEach((item)=>
{
if (item.id === id)
{
newItems.push({ id : item.id , selected : item.selected });
}
else{
newItems.push({ id : item.id , selected : false });
}
})
setItems(newItems);
}
Call setItems to set it.
Note:
Not related to the question but you should use unique keys when iterating over list.
{items.map((item)=>{
<Child key={item.id} item={item} changeSelected={changeSelected}/>
})}

Error when trying to update in react-redux

I am trying to update my data in redux but I get an error when I have more than one value in the state.
How I am transferring data into the AllPalletes component below:
<Route exact path='/' render ={(routeProps) => <AllPalletes data = {this.props.palleteNames} />} />
The AllPalletes component, where I am setting up the edit form:
class connectingPalletes extends Component {
render () {
console.log(this.props)
return (
<div>
<Menu inverted>
<Menu.Item header>Home</Menu.Item>
<Menu.Item as = {Link} to = '/createpalette'>Create a Palette</Menu.Item>
</Menu>
<Container>
<Card.Group itemsPerRow={4}>
{this.props.data.map((card) => {
let cardName = card.Name.replace(/\s+/g, '-').toLowerCase()
return (
<Card key = {card._id}>
<Image src = 'https://images.pexels.com/photos/1212406/pexels-photo-1212406.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500' wrapped ui={false}/>
<Card.Content>
<Grid>
<Grid.Column floated = 'left' width ={7}>
{card.edit? (
<PaletteEditForm {...card}/>
) : (
<Card.Header as = {Link} to = {`/palette/${cardName}`}>{card.Name}</Card.Header>
)}
</Grid.Column>
<Grid.Column floated = 'right' width = {5}>
<Icon name = 'pencil' />
<Icon name = 'trash' onClick = {() => this.props.dispatch(removePalette({id: card._id}))}/>
</Grid.Column>
</Grid>
</Card.Content>
</Card>
)
})}
</Card.Group>
<Divider></Divider>
<Divider hidden></Divider>
<Grid centered columns={1}>
<Button as = {Link} to = '/testing'>Go Back</Button>
</Grid>
</Container>
</div>
)
}
}
const AllPalletes = connect()(connectingPalletes)
export default AllPalletes
And here is the edit form:
class EditForm extends Component {
constructor(props) {
super(props)
this.state = {
paletteName: this.props.Name
}
}
handleChange = (e) => {
const val = e.target.value,
s_name = e.target.name
this.setState (() => {
return {
[s_name]: val,
}
})
}
handleSubmit = () => {
let updates = {Name: this.state.paletteName, edit: false}
this.props.dispatch(editPalette(this.props._id, updates))
}
render() {
console.log(this.props)
return (
<Form onSubmit = {this.handleSubmit}>
<Input type = 'text' name = 'paletteName' value = {this.state.paletteName} onChange={this.handleChange} />
</Form>
)
}
}
const PaletteEditForm = connect()(EditForm)
export default PaletteEditForm
My Reducer:
import uuid from 'uuid/v1'
const paletteDefault = [{
Name: "Material UI",
myArray: [],
_id: uuid(),
edit: false
}, {
Name: "Splash UI",
myArray: [],
_id: uuid(),
edit: true
}]
const PaletteReducers = (state = paletteDefault, action) => {
console.log(action)
switch(action.type) {
case 'ADD_PALETTE':
return [...state, action.palette]
case 'REMOVE_PALETTE':
return state.filter(x => x._id !== action.id)
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
default:
return state
}
}
export default PaletteReducers
My Action
// EDIT_PALETTE
const editPalette = (id, updates) => ({
type: 'EDIT_PALETTE',
id,
updates
})
export {addPalette, removePalette, editPalette}
I have a feeling that the problem could be in how I have set up the reducer case.
The edit dispatch only works when I have one value in the state. Otherwise, I am getting this error:
Uncaught TypeError: Cannot read property 'Name' of undefined
at AllPalletes.js:23
Please help..
I found the error. I had not give a return value in the 'EDIT_PALETTE' case, after the if-statement. It was
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
})
And instead should be:
case 'EDIT_PALETTE':
return state.map((palette) => {
if(palette._id === action.id) {
return {
...palette,
...action.updates
}
}
return palette
})

Select All mat option and deselect All

I have scenario as below:
I want to achieve is:
When user click on All then all options shall be selected and when user click All again then all options shall be deselcted.
If All option is checked and user click any other checkbox than All then All and clicked checkbox shall be deselected.
When user selects 4 options one by one then All shall be selected.
HTML file
<mat-select placeholder="User Type" formControlName="UserType" multiple>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
TS file
this.searchUserForm = this.fb.group({
userType: new FormControl('')
});
userTypeFilters = [
{
key: 1, value: 'Value 1',
},
{
key: 2, value: 'Value 2',
},
{
key: 3, value: 'Value 3',
},
{
key: 4, value: 'Value 4',
}
]
toggleAllSelection() {
if (this.allSelected.selected) {
this.searchUserForm.controls.userType
.patchValue([...this.userTypeFilters.map(item => item.key), 0]);
} else {
this.searchUserForm.controls.userType.patchValue([]);
}
}
Now, how to achieve 2nd and 3rd point
Stackblitz is: https://stackblitz.com/edit/angular-material-with-angular-v5-znfehg?file=app/app.component.html
Use code as below create function on click each mat-option and select()/deselect() all option:
See stackblitz:https://stackblitz.com/edit/angular-material-with-angular-v5-jsgvx6?file=app/app.component.html
TS:
togglePerOne(all){
if (this.allSelected.selected) {
this.allSelected.deselect();
return false;
}
if(this.searchUserForm.controls.userType.value.length==this.userTypeFilters.length)
this.allSelected.select();
}
toggleAllSelection() {
if (this.allSelected.selected) {
this.searchUserForm.controls.userType
.patchValue([...this.userTypeFilters.map(item => item.key), 0]);
} else {
this.searchUserForm.controls.userType.patchValue([]);
}
}
HTML:
<form [formGroup]="searchUserForm" fxFlex fxLayout="column" autocomplete="off" style="margin: 30px">
<mat-select placeholder="User Type" formControlName="userType" multiple>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key" (click)="togglePerOne(allSelected.viewValue)">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
</form>
Simply you can do it without adding a new option to your data source by adding a checkbox.
See the: Demo
import { Component, VERSION, ViewChild } from '#angular/core';
import { FormControl } from '#angular/forms';
import { MatSelect } from '#angular/material/select';
import { MatOption } from '#angular/material/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
#ViewChild('select') select: MatSelect;
allSelected=false;
foods: any[] = [
{value: 'steak-0', viewValue: 'Steak'},
{value: 'pizza-1', viewValue: 'Pizza'},
{value: 'tacos-2', viewValue: 'Tacos'}
];
toggleAllSelection() {
if (this.allSelected) {
this.select.options.forEach((item: MatOption) => item.select());
} else {
this.select.options.forEach((item: MatOption) => item.deselect());
}
}
optionClick() {
let newStatus = true;
this.select.options.forEach((item: MatOption) => {
if (!item.selected) {
newStatus = false;
}
});
this.allSelected = newStatus;
}
}
.select-all{
margin: 5px 17px;
}
<mat-form-field>
<mat-label>Favorite food</mat-label>
<mat-select #select multiple>
<div class="select-all">
<mat-checkbox [(ngModel)]="allSelected"
[ngModelOptions]="{standalone: true}"
(change)="toggleAllSelection()">Select All</mat-checkbox>
</div>
<mat-option (click)="optionClick()" *ngFor="let food of foods" [value]="food.value">
{{food.viewValue}}
</mat-option>
</mat-select>
</mat-form-field>
Another way to do this is with the #ViewChild selector to get the mat-select component and troggle the mat-options items selected or unselected. We need also a variable to save the selected actual status to select or unselect all the elements on every click. Hope will help.
import {MatOption, MatSelect} from "#angular/material";
export class ExampleAllSelector {
myFormControl = new FormControl();
elements: any[] = [];
allSelected = false;
#ViewChild('mySel') skillSel: MatSelect;
constructor() {}
toggleAllSelection() {
this.allSelected = !this.allSelected; // to control select-unselect
if (this.allSelected) {
this.skillSel.options.forEach( (item : MatOption) => item.select());
} else {
this.skillSel.options.forEach( (item : MatOption) => {item.deselect()});
}
this.skillSel.close();
}
}
<mat-select #mySel placeholder="Example" [formControl]="myFormControl" multiple>
<mat-option [value]="0" (click)="toggleAllSelection()">All items</mat-option>
<mat-option *ngFor="let element of elements" [value]="element">{{skill.name}}</mat-option>
</mat-select>
Here is an example of how to extend a material option component.
See stackblitz Demo
Component:
import { ChangeDetectorRef, Component, ElementRef, HostListener, HostBinding, Inject, Input, OnDestroy, OnInit, Optional } from '#angular/core';
import { MAT_OPTION_PARENT_COMPONENT, MatOptgroup, MatOption, MatOptionParentComponent } from '#angular/material/core';
import { AbstractControl } from '#angular/forms';
import { MatPseudoCheckboxState } from '#angular/material/core/selection/pseudo-checkbox/pseudo-checkbox';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
#Component({
selector: 'app-select-all-option',
templateUrl: './select-all-option.component.html',
styleUrls: ['./select-all-option.component.css']
})
export class SelectAllOptionComponent extends MatOption implements OnInit, OnDestroy {
protected unsubscribe: Subject<any>;
#Input() control: AbstractControl;
#Input() title: string;
#Input() values: any[] = [];
#HostBinding('class') cssClass = 'mat-option';
#HostListener('click') toggleSelection(): void {
this. _selectViaInteraction();
this.control.setValue(this.selected ? this.values : []);
}
constructor(elementRef: ElementRef<HTMLElement>,
changeDetectorRef: ChangeDetectorRef,
#Optional() #Inject(MAT_OPTION_PARENT_COMPONENT) parent: MatOptionParentComponent,
#Optional() group: MatOptgroup) {
super(elementRef, changeDetectorRef, parent, group);
this.title = 'Select All';
}
ngOnInit(): void {
this.unsubscribe = new Subject<any>();
this.refresh();
this.control.valueChanges
.pipe(takeUntil(this.unsubscribe))
.subscribe(() => {
this.refresh();
});
}
ngOnDestroy(): void {
super.ngOnDestroy();
this.unsubscribe.next();
this.unsubscribe.complete();
}
get selectedItemsCount(): number {
return this.control && Array.isArray(this.control.value) ? this.control.value.filter(el => el !== null).length : 0;
}
get selectedAll(): boolean {
return this.selectedItemsCount === this.values.length;
}
get selectedPartially(): boolean {
const selectedItemsCount = this.selectedItemsCount;
return selectedItemsCount > 0 && selectedItemsCount < this.values.length;
}
get checkboxState(): MatPseudoCheckboxState {
let state: MatPseudoCheckboxState = 'unchecked';
if (this.selectedAll) {
state = 'checked';
} else if (this.selectedPartially) {
state = 'indeterminate';
}
return state;
}
refresh(): void {
if (this.selectedItemsCount > 0) {
this.select();
} else {
this.deselect();
}
}
}
HTML:
<mat-pseudo-checkbox class="mat-option-pseudo-checkbox"
[state]="checkboxState"
[disabled]="disabled"
[ngClass]="selected ? 'bg-accent': ''">
</mat-pseudo-checkbox>
<span class="mat-option-text">
{{title}}
</span>
<div class="mat-option-ripple" mat-ripple
[matRippleTrigger]="_getHostElement()"
[matRippleDisabled]="disabled || disableRipple">
</div>
CSS:
.bg-accent {
background-color: #2196f3 !important;
}
Another possible solution:
using <mat-select [(value)]="selectedValues" in the template and set the selectedValues via toggle function in the component.
Working Stackblitz Demo.
Component
export class AppComponent {
selectedValues: any;
allSelected = false;
public displayDashboardValues = [
{key:'0', valuePositionType: 'undefined', viewValue:'Select all'},
{key:'1', valuePositionType: 'profit-loss-area', viewValue:'result'},
{key:'2', valuePositionType: 'cash-area', viewValue:'cash'},
{key:'3', valuePositionType: 'balance-area', viewValue:'balance'},
{key:'4', valuePositionType: 'staff-area' ,viewValue:'staff'},
{key:'5', valuePositionType: 'divisions-area', viewValue:'divisions'},
{key:'6', valuePositionType: 'commisions-area', viewValue:'commisions'},
];
toggleAllSelection() {
this.allSelected = !this.allSelected;
this.selectedValues = this.allSelected ? this.displayDashboardValues : [];
}
}
Template
<mat-select [(value)]="selectedValues" (selectionChange)="selectionChange($event)" formControlName="dashboardValue" multiple>
<mat-option [value]="displayDashboardValues[0]" (click)="toggleAllSelection()">{{ displayDashboardValues[0].viewValue }}</mat-option>
<mat-divider></mat-divider>
<div *ngFor="let dashboardPosition of displayDashboardValues">
<mat-option class="dashboard-select-option" *ngIf="dashboardPosition.key>0" [value]="dashboardPosition">
{{ dashboardPosition.viewValue }}
</mat-option>
</div>
</mat-select>
There are some problems with other answers. The most important one is that they're listening to the click event which is not complete (user can select an option via space key on the keyboard).
I've created a component that solves all the problems:
#Component({
selector: 'app-multi-select',
templateUrl: './multi-select.component.html',
styleUrls: ['./multi-select.component.scss'],
})
export class MultiSelectComponent<V> implements OnInit {
readonly _ALL_SELECTED = '__ALL_SELECTED__' as const;
#Input() options: ReadonlyArray<{ value: V; name: string }> = [];
#Input('selectControl') _selectControl!: FormControl | AbstractControl | null | undefined;
get selectControl(): FormControl {
return this._selectControl as FormControl;
}
#Input() label: string = '';
#Input() hasSelectAllOption = false;
selectedValues: (V | '__ALL_SELECTED__')[] = [];
constructor() {}
ngOnInit(): void {}
onSelectAllOptions({ isUserInput, source: { selected } }: MatOptionSelectionChange) {
if (!isUserInput) return;
this.setValues(selected ? this.options.map(o => o.value) : []);
}
private setValues(values: (V | '__ALL_SELECTED__')[]) {
const hasAllOptions = ArrayUtils.arraysAreSame(
values,
this.options.map(o => o.value),
);
if (!values.includes(this._ALL_SELECTED)) {
if (hasAllOptions) {
values = [...values, this._ALL_SELECTED];
}
} else if (!hasAllOptions) {
values = values.filter(o => o !== this._ALL_SELECTED);
}
setTimeout(() => {
this.selectedValues = values;
});
this.selectControl.setValue(values.filter(o => (o as any) !== this._ALL_SELECTED));
}
onSelectOtherOptions({ isUserInput, source: { selected, value } }: MatOptionSelectionChange) {
if (!isUserInput) return;
this.setValues(
selected ? [...this.selectedValues, value] : this.selectedValues.filter(o => o !== value),
);
}
}
<mat-form-field>
<mat-label>Choose some options</mat-label>
<mat-select multiple [value]="selectedValues">
<mat-option
*ngFor="let d of options"
[value]="d.value"
(onSelectionChange)="onSelectOtherOptions($event)"
>
{{ d.name }}
</mat-option>
<mat-option
*ngIf="hasSelectAllOption"
[value]="_ALL_SELECTED"
(onSelectionChange)="onSelectAllOptions($event)"
>
Select all
</mat-option>
</mat-select>
</mat-form-field>

convert select to vue-select with dynamic data (Laravel & Vuejs)

I have dynamic products list to create an invoice. Now I want to search the product from select->option list. I found a possible solution like Vue-select in vuejs but I could not understand how to convert my existing code to get benefit from Vue-select. Would someone help me please, how should I write code in 'select' such that I can search product at a time from the list?
My existing code is -
<td>
<select id="orderproductId" ref="selectOrderProduct" class="form-control input-sm" #change="setOrderProducts($event)">
<option>Choose Product ...</option>
<option :value="product.id + '_' + product.product_name" v-for="product in invProducts">#{{ product.product_name }}</option>
</select>
</td>
And I want to convert it something like -
<v-select :options="options"></v-select>
So that, I can search products also if I have many products. And My script file is -
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#poOrder',
data: {
orderEntry: {
id: 1,
product_name: '',
quantity: 1,
price: 0,
total: 0,
},
orderDetail: [],
grandTotal: 0,
invProducts: [],
invProducts: [
#foreach ($productRecords as $invProduct)
{
id:{{ $invProduct['id'] }},
product_name:'{{ $invProduct['product_name'] }}',
},
#endforeach
],
},
methods: {
setOrderProducts: function(event) {
//alert('fired');
var self = this;
var valueArr = event.target.value.split('_');
var selectProductId = valueArr[0];
var selectProductName = valueArr[1];
self.orderEntry.id = selectProductId;
self.orderEntry.product_name = selectProductName;
$('#invQuantity').select();
},
addMoreOrderFields:function(orderEntry) {
var self = this;
if(orderEntry.product_name && orderEntry.quantity && orderEntry.price > 0) {
self.orderDetail.push({
id: orderEntry.id,
product_name: orderEntry.product_name,
quantity: orderEntry.quantity,
price: orderEntry.price,
total: orderEntry.total,
});
self.orderEntry = {
id: 1,
product_name:'',
productId: 0,
quantity: 1,
price: 0,
total: 0,
}
$('#orderproductId').focus();
self.calculateGrandTotal();
} else {
$('#warningModal').modal();
}
this.$refs.selectOrderProduct.focus();
},
removeOrderField:function(removeOrderDetail) {
var self = this;
var index = self.orderDetail.indexOf(removeOrderDetail);
self.orderDetail.splice(index, 1);
self.calculateGrandTotal();
},
calculateGrandTotal:function() {
var self = this;
self.grandTotal = 0;
self.totalPrice = 0;
self.totalQuantity = 0;
self.orderDetail.map(function(order){
self.totalQuantity += parseInt(order.quantity);
self.totalPrice += parseInt(order.price);
self.grandTotal += parseInt(order.total);
});
},
setTotalPrice:function(event){
var self = this;
//self.netTotalPrice();
self.netTotalPrice;
}
},
computed: {
netTotalPrice: function(){
var self = this;
var netTotalPriceValue = self.orderEntry.quantity * self.orderEntry.price;
var netTotalPriceInDecimal = netTotalPriceValue.toFixed(2);
self.orderEntry.total = netTotalPriceInDecimal;
return netTotalPriceInDecimal;
}
}
});
Assuming that invProducts is an array of product objects and each product object has a product_name property, try this snippet.
<v-select #input="selectChange()" :label="product_name" :options="invProducts" v-model="selectedProduct">
</v-select>
Create a new data property called selectedProduct and bind it to the vue-select component. So, whenever the selection in the vue-select changes, the value of selectedProduct also changes. In addition to this, #input event can be used to trigger a method in your component. You can get the selected product in that method and do further actions within that event listener.
methods: {
selectChange : function(){
console.log(this.selectedProduct);
//do futher processing
}

Angular 2 form validations start date <= end date

I'm trying to add validations such that the end date can't be before the start date. Unfortunately I have no idea how to do that, and I didn't find any helpful advice in the internet so far. My form looks like this:
editAndUpdateForm(tageler: Tageler) {
this.tageler = tageler;
this.tagelerForm = this.fb.group({
title: [this.tageler.title, Validators.required],
text: this.tageler.text,
group: [[this.tageler.group], Validators.required],
date_start: new Date(this.tageler.start).toISOString().slice(0, 10),
date_end: new Date(this.tageler.end).toISOString().slice(0, 10),
...
});
this.tagelerForm.valueChanges
.subscribe(data => this.onValueChanged(data));
}
My validations so far:
onValueChanged(data?: any) {
if (!this.tagelerForm) {
return;
}
const form = this.tagelerForm;
for (const field in this.formErrors) {
// clear previous error message (if any)
this.formErrors[field] = '';
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
validationMessages = {
'title': {
'required': 'Geben Sie bitte einen Namen ein.',
},
'group': {
'required': 'Wählen Sie bitte eine Gruppe aus.'
},
'bringAlong': {
'required': 'Bitte Feld ausfüllen.'
},
'uniform': {
'required': 'Bitte Feld ausfüllen.'
},
};
formErrors = {
'title': 'Geben Sie bitte einen Namen ein.',
'group': 'Wählen Sie bitte eine Gruppe aus.',
'bringAlong': 'Bitte Feld ausfüllen',
'uniform': 'Bitte Feld ausfüllen',
};
The the form-controls 'date_start' & 'date_end' contain a date-string of the form 'dd.MM.yyyy', and I want 'date_end' to be bigger or equal 'date_start'.
I'd like to directly display the error message (my html code looks like this:)
<label for="formControlName_date_end" class="col-3 col-form-label">Ende:</label>
<div class="col-5">
<input id="formControlName_date_end" class="form-control" formControlName="date_end" type="date" value="{{tageler.end | date: 'yyyy-MM-dd'}}">
</div>
<div *ngIf="formErrors.date_end" class="alert alert-danger">
{{ formErrors.date_end }}
</div>
Could someone help me?
Thanks!
Based on the answer of santiagomaldonado I have created a generic ValidatorFn that can be used in multiple Reactive Forms with a dynamic return value.
export class DateValidators {
static dateLessThan(dateField1: string, dateField2: string, validatorField: { [key: string]: boolean }): ValidatorFn {
return (c: AbstractControl): { [key: string]: boolean } | null => {
const date1 = c.get(dateField1).value;
const date2 = c.get(dateField2).value;
if ((date1 !== null && date2 !== null) && date1 > date2) {
return validatorField;
}
return null;
};
}
}
Import the validator and use it like this in your formgroup validators.
this.form = this.fb.group({
loadDate: null,
deliveryDate: null,
}, { validator: Validators.compose([
DateValidators.dateLessThan('loadDate', 'deliveryDate', { 'loaddate': true }),
DateValidators.dateLessThan('cargoLoadDate', 'cargoDeliveryDate', { 'cargoloaddate': true })
])});
Now you can use the validation in HTML.
<md-error *ngIf="form.hasError('loaddate')">Load date must be before delivery date</md-error>
You can also do it with Reactive Forms.
The FormBuilder API lets you add custom validators.
Valid keys for the extra parameter map are validator and asyncValidator
Example:
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'reactive-form',
templateUrl: './reactive-form.html'
})
export class ReactiveFormComponent {
form: FormGroup
constructor(private fb: FormBuilder){
this.createForm();
}
createForm() {
this.form = this.fb.group({
dateTo: ['', Validators.required ],
dateFrom: ['', Validators.required ]
}, {validator: this.dateLessThan('dateFrom', 'dateTo')});
}
dateLessThan(from: string, to: string) {
return (group: FormGroup): {[key: string]: any} => {
let f = group.controls[from];
let t = group.controls[to];
if (f.value > t.value) {
return {
dates: "Date from should be less than Date to"
};
}
return {};
}
}
}
Note that I'm comparing the values of the inputs date and from with >, but by default this would be comparing strings.
In the live example I'm using angular-date-value-accessor and importing the directive useValueAsDate.
<input formControlName="dateFrom" type="date" useValueAsDate />
With this directive group.controls[from].value and group.controls[to].value returns Date and then I can compare them with <.
Live example in plunkr
Credits to Dave's answer
create a form group . Let the controls be a part of form group .
new FormGroup({
startDate: this.fb.group({
dateInput: [{value: ""}, startDateValidator()]
}),
endDate: this.fb.group({
dateInput: ["", endDateValidator()]
})
}, startDateCannotBeLessThanEndDateValidator());
startDateCannotBeLessThanEndDateValidator(formGroup: FormGroup) {
let startDate = formGroup.get("startDate");
let endDate = formGroup.get("endDate");
// compare 2 dates
}
we cant do it in validation because we need two control values that is startdate and enddate for comparison. So it is better to compare two dates in your component
error:any={isError:false,errorMessage:''};
compareTwoDates(){
if(new Date(this.form.controls['date_end'].value)<new Date(this.form.controls['date_start'].value)){
this.error={isError:true,errorMessage:'End Date can't before start date'};
}
}
In your html
<label for="formControlName_date_end" class="col-3 col-form-label">Ende:</label>
<div class="col-5">
<input id="formControlName_date_end" class="form-control" formControlName="date_end" type="date" value="{{tageler.end | date: 'yyyy-MM-dd'}}" (blur)="compareTwoDates()">
</div>
<div *ngIf="error.isError" class="alert alert-danger">
{{ error.errorMessage }}
</div>
I am using moment, and in angular 7 to compare and validate dates, i use this function:
datesValidator(date1: any, date2: any): {[key:string]:any} | null {
return (group: FormGroup): { [key: string]: any } | null => {
let start = group.controls[date1].value;
let end = group.controls[date2].value;
let datum1 = _moment(start).startOf('day');
let datum2 = _moment(end).startOf('day');
if (_moment(datum1).isSameOrAfter(datum2)) {
this.alert.red("Error: wrong period!"); //popup message
return { 'error': 'Wrong period!' };
}
return null; //period is ok, return null
};
}
Mine is angular7 + ngprime(for calendar)
(*if you don't want ngprime just replace calendar part to others.)
Refer below code for date validation.
My code has additional validation that
once start date is selected, I block previous days in end data's calendar
so that the end date will be always later than that.
if you don't want to block date, delete [minDate] part.
it is also working.
> Component
export class test implements OnInit {
constructor() { }
defaultDate: Date = new Date();
checkDate = true;
form: FormGroup;
today: Date = new Date(); //StartDate for startDatetime
startDate: Date = new Date(); //StartDate for endDatetime
initFormControls(): void {
this.today.setDate(this.today.getDate());
this.startDate.setDate(this.today.getDate()); //or start date + 1 day
this.form = new FormGroup({
startDatetime: new FormControl('', [Validators.required]),
endDatetime: new FormControl('', [Validators.required])
},
{ validators: this.checkDateValidation });
}
checkDateValidation: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
try {
let startingDatefield = control.get('startDatetime').value;
this.startDate = new Date(startingDatefield); //new Date(startingDatefield).getDate()
let endingDatefield = control.get('endDatetime').value;
if (this.today >= startingDatefield) { //compare to current date
console.log("Please set a Start Date that is on or after Current Date and Time.");
return { 'effectiveStartDatetime': false };
} else
if (startingDatefield > endingDatefield && endingDatefield) {
console.log("Please set an End Date and Time that is after the Start Date and Time.");
return {};
} else {
return {};
}
} catch (err) {
}
};
onSubmit() {
//if form is not valid
if (!this.form.valid) {
console.log(" Please fill in all the mandatory fields");
// do sth
return;
}
//if form is valid
//do sth
}
ngOnInit() {
this.initFormControls();
}
> HTML
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div>
<div>
<p-button type="submit" label="submit"></p-button>
</div>
</div>
<div>
<p>Start Date/Time"</p>
<div>
<p-calendar formControlName="startDatetime" appendTo="body" showTime="true" hourFormat="24" stepMinute="30"
showSeconds="false" dateFormat="yy-mm-dd" [minDate]="today"></p-calendar>
<div
*ngIf="form.get('startDatetime').invalid && (form.get('startDatetime').dirty || form.get('startDatetime').touched)">
<div *ngIf="form.get('startDatetime').hasError('required')">
</div>
</div>
</div>
<p>End Date/Time</p>
<div>
<p-calendar formControlName="endDatetime" appendTo="body" showTime="true" hourFormat="24" stepMinute="30"
showSeconds="false" dateFormat="yy-mm-dd" [minDate]="startDate"></p-calendar>
<div *ngIf="form.get('endDatetime').invalid && (form.get('endDatetime').dirty || form.get('endDatetime').touched)">
<div *ngIf="!checkDate || form.get('endDatetime').hasError('required')">
</div>
</div>
</div>
</div>
</form>

Resources