How to add two different search box for two different column in bootstrap-vue table - bootstrap-vue

I need two search boxes for two different table columns, both search boxes are work like "AND" conditions.
Here is the image what I want to achieve

The best way is NOT to worry about those two columns in the table - just handle the data that fills up those columns (and the table):
new Vue({
el: "#app",
computed: {
filteredItems() {
return this.items.filter(e => {
return Object.keys(this.filters).every(el => {
// .toLowerCase() makes this filter case insensitive
return this.filters[el] === '' || String(e[el]).toLowerCase().indexOf(this.filters[el].toLowerCase()) !== -1
})
})
}
},
data() {
return {
gradeOptions: ['', 'A', 'B', 'C'],
filters: {
age: '',
first_name: '',
last_name: '',
grade: ''
},
items: [{
age: 40,
first_name: 'Dickerson',
last_name: 'Macdonald',
grade: 'A'
},
{
age: 21,
first_name: 'Larsen',
last_name: 'Shaw',
grade: 'B'
},
{
age: 21,
first_name: 'Larsen',
last_name: 'Shawn',
grade: 'C'
},
{
age: 89,
first_name: 'Geneva',
last_name: 'Wilson',
grade: 'B'
},
{
age: 38,
first_name: 'Jami',
last_name: 'Carney',
grade: 'A'
}
]
}
}
})
<!-- Load required Bootstrap and BootstrapVue CSS -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<!-- Load polyfills to support older browsers -->
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<!-- Load the following for BootstrapVueIcons support -->
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app">
<b-container>
<b-row>
<b-col>
<b-form-input v-model="filters.age" placeholder="Filter age"></b-form-input>
</b-col>
<b-col>
<b-form-input v-model="filters.first_name" placeholder="Filter firstname"></b-form-input>
</b-col>
<b-col>
<b-form-input v-model="filters.last_name" placeholder="Filter lastname"></b-form-input>
</b-col>
<b-col>
<b-form-select v-model="filters.grade" :options="gradeOptions"></b-form-select>
</b-col>
</b-row>
<b-row>
<b-col>
<div>
<b-table striped hover :items="filteredItems"></b-table>
</div>
</b-col>
</b-row>
</b-container>
</div>
In my snippet, the filteredItems computed property returns only those objects that satisfy the corresponding filter (or if the corresponding filter is empty). The every function ensures that the filters are bound by an AND logic.
I use the filteredItems to fill up the table (not the items directly), thus I have a better control over what data set should appear in the table.
The main point is: you shouldn't filter two columns of the table, you should filter the data behind the table - with filtered data the table works as it should.

Related

How to set validation for UNIQUE fields in MongoDB, Vuejs?

I'm new to MongoDB and Vuejs. I'm not sure if it is possible that I was trying to set validation for Unique value, but somehow it does not work.
My defined schema from back-end:
const uuid = require("uuid");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let informationSchema = new Schema ({
_id: {type: String, default: uuid.v1},
firstName: {
type: String, required:true, trim: true, unique: true
},
lastName: {
type: String, required:true, trim: true, unique: true
}
});
module.exports = mongoose.model('informations', informationSchema)
My action form in Vuejs from Front-end:
<template>
<div class="row justify-content-center">
<div class="col-md-6">
<h3 class="text-center">Create Name</h3>
<form #submit.prevent="handleSubmitForm">
<div class="form-group">
<label>First Name</label>
<input type="text" class="form-control" v-model="name.firstName" required>
</div>
<div class="form-group">
<label>Last Name</label>
<input type="text" class="form-control" v-model="name.lastName" required>
</div>
<button class="btn btn-danger mt-3">Create</button>
</form>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
name: {
firstName: '',
lastName: ''
}
}
},
methods: {
handleSubmitForm() {
let apiURL = 'http://localhost:3000/name';
axios.post(apiURL, this.student).then(() => {
console.log(res)
}).catch(error => {
console.log(error)
});
}
}
}
</script>
My 1st inserted data: [{"firstName":"ABC", "lastName": "DCF"}]
I want to insert the 2nd one with data : firstName = "ABC", but last name = "XYZ". The error for validation occured because of validation (Unique) set in the defined schema from back-end).
My expected output:
Is there any possible ways to do:
If I insert the 1st data, it will work.
If I insert the 2nd data same as the 1st data, the error for validation will occur.
If I insert the 3rd data that is different from lastName only, it will work.
Thank you so much.
First of all, Mongo and Vue are completly separated, there is no relation.
Answering your question, the validation fail because you are telling mongo "Set firstName and lastName as unique fields". But not "Set these fields unique TOGETHER". So when you add a firstname, the second attemp try to add an existing firstName and fails.
So you can create a unique index:
informationSchema.index({ firstName: 1, lastName: 1 }, { unique: true });
And remove unique constraint in the schema.

Vue.js 3 v-model on form element got undefined type error on missing data from REST Call

Coming from Svelte and being new to Vue.js 3, I encounter a situation that I found a little bit confusing. This is a quite simple scenario that you will get into all the time when dealing with REST call on no-sql backend... partial data set. Let me explain with this simple example:
<template>
<div v-if="info">
<form>
<input type="text" v-model="info.firstname">
<input type="text" v-model="info.lastname">
<input type="text" v-model="info.address.street">
<input type="text" v-model="info.address.city">
<input type="text" v-model="info.address.postalCode">
<button type="submit">Submit</button>
</form>
</div>
</template>
<script>
export default {
created() {
//Imagine a rest call here...
this.info = {
firstname: "Micheal",
lastname: "Jordan",
}
},
data() {
return {
info: {
firstname: '',
lastname: '',
address: {
city: '',
street: '',
postalCode: ''
},
}
}
}
}
</script>
This will get you a " Uncaught (in promise) TypeError: $data.info.address is undefined " because of the missing address block. But functionally speaking, the address is optional and will not be return if use MongoDB or DynamoDB backend. And has I said at the beginning, in Svelte, I simply apply a spread operator on my info object and the "missing" data is no concern. If I add the missing address section in my "simulated" API call, everythings fine.
What am I missing here?! (remember I'm starting to learn Vue here ;-)
Well... I don't why I didn't think of it faster...
....
this.info = {...this.info,
firstname: "Micheal",
lastname: "Jordan",
}
....
Simply use the spread operator! :-)

Vue.js/Vuetify - Autocomplete issue when pre-loading option in a v-select

I'm setting the selected option of a v-select using v-model, so when I open the screen it already comes with the result. That's working fine until I set autocomplete property in v-select tag. When I do that the option is chosen but the select doesn't show it.
Is there any issue with the autocomplete feature or it's the standard behavior for the component?
Vue.use(Vuetify);
var app = new Vue({
el: '#app',
data() {
return {
selected: 2,
country: [{
text: 'USA',
value: 1
}, {
text: 'Canada',
value: 2
}, {
text: 'England',
value: 3
}, {
text: 'France',
value: 4
}]
}
}
})
<script src="https://unpkg.com/vue#2.4.1/dist/vue.js"></script>
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vuetify/dist/vuetify.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<div id="app">
<v-app>
<v-card class="grey lighten-4 elevation-0">
<v-card-text>
<v-container fluid>
<v-layout row wrap>
<v-flex xs4>
<v-flex>
<v-subheader>Origin Country</v-subheader>
</v-flex>
<v-flex>
<v-select
:items="country"
v-model="selected"
label="Select"
class="input-group--focused"
single-line
bottom
autocomplete>
</v-select>
</v-flex>
</v-flex>
<v-flex>
Selected: {{ selected }}
</v-flex>
</v-layout>
</v-container>
</v-card-text>
</v-card>
</v-app>
</div>
Here it is jsfiddle link: https://jsfiddle.net/vcmiranda/huj9L4bq/
Thanks.
I test your code, vuetify will inject its classes after yours, after delete this line in v-select, it works.
class="input-group--focused"
Alternatively, using 'id' instead of 'class' is another option.

How to use array of objects for controls in Reactive Forms

I need to dynamic create textarea for forms. I have the following model:
this.fields = {
isRequired: true,
type: {
options: [
{
label: 'Option 1',
value: '1'
},
{
label: 'Option 2',
value: '2'
}
]
}
};
And form:
this.userForm = this.fb.group({
isRequired: [this.fields.isRequired, Validators.required],
//... here a lot of other controls
type: this.fb.group({
options: this.fb.array(this.fields.type.options),
})
});
Part of template:
<div formGroupName="type">
<div formArrayName="options">
<div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
<textarea [formControlName]="i"></textarea>
</div>
</div>
</div>
So, as you can see I have an array of objects and I want to use label property to show it in a textarea. Now I see [object Object]. If I change options to a simple string array, like: ['Option 1', 'Option 2'], then all works fine. But I need to use objects. So, instead of:
<textarea [formControlName]="i"></textarea>
I have tried:
<textarea [formControlName]="option[i].label"></textarea>
But, it doesn't work.
How can I use an array of objects?
This is Plunkr
You need to add a FormGroup, which contains your label and value. This also means that the object created by the form, is of the same build as your fields object.
ngOnInit() {
// build form
this.userForm = this.fb.group({
type: this.fb.group({
options: this.fb.array([]) // create empty form array
})
});
// patch the values from your object
this.patch();
}
After that we patch the value with the method called in your OnInit:
patch() {
const control = <FormArray>this.userForm.get('type.options');
this.fields.type.options.forEach(x => {
control.push(this.patchValues(x.label, x.value))
});
}
// assign the values
patchValues(label, value) {
return this.fb.group({
label: [label],
value: [value]
})
}
Finally, here is a
Demo
The answer from AJT_82 was so useful to me, I thought I would share how I reused his code and built a similar example - one that might have a more common use case, which is inviting several people to sign-up at once. Like this:
I thought this might help others, so that's why I am adding it here.
You can see the form is a simple array of text inputs for emails, with a custom validator loaded on each one. You can see the JSON structure in the screenshot - see the pre line in the template (thanks to AJT), a very useful idea whilst developing to see if your model and controls are wired up!
So first, declare the objects we need. Note that 3 empty strings are the model data (which we will bind to the text inputs):
public form: FormGroup;
private control: FormArray;
private emailsModel = { emails: ['','','']} // the model, ready to hold the emails
private fb : FormBuilder;
The constructor is clean (for easier testing, just inject my userService to send the form data to after submit):
constructor(
private _userService: UserService,
) {}
The form is built in the init method, including storing a reference to the emailsArray control itself so we can check later whether its children (the actual inputs) are touched and if so, do they have errors:
ngOnInit() {
this.fb = new FormBuilder;
this.form = this.fb.group({
emailsArray: this.fb.array([])
});
this.control = <FormArray>this.form.controls['emailsArray'];
this.patch();
}
private patch(): void {
// iterate the object model and extra values, binding them to the controls
this.emailsModel.emails.forEach((item) => {
this.control.push(this.patchValues(item));
})
}
This is what builds each input control (of type AbstracControl) with the validator:
private patchValues(item): AbstractControl {
return this.fb.group({
email: [item, Validators.compose([emailValidator])]
})
}
The 2 helper methods to check if the input was touched and if the validator raised an error (see the template to see how they are used - notice I pass the index value of the array from the *ngFor in the template):
private hasError(i):boolean {
// const control = <FormArray>this.form.controls['emailsArray'];
return this.control.controls[i].get('email').hasError('invalidEmail');
}
private isTouched(i):boolean {
// const control = <FormArray>this.form.controls['emailsArray'];
return this.control.controls[i].get('email').touched;
}
Here's the validator:
export function emailValidator(control: FormControl): { [key: string]: any } {
var emailRegexp = /[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,3}$/;
if (control.value && !emailRegexp.test(control.value)) {
return { invalidEmail: true };
}
}
And the template:
<form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" class="text-left">
<div formArrayName="emailsArray">
<div *ngFor="let child of form.controls.emailsArray.controls; let i=index">
<div class="form-group" formGroupName="{{i}}">
<input formControlName="email"
class="form-control checking-field"
placeholder="Email" type="text">
<span class="help-block" *ngIf="isTouched(i)">
<span class="text-danger"
*ngIf="hasError(i)">Invalid email address
</span>
</span>
</div>
</div>
</div>
<pre>{{form.value | json }}</pre>
<div class="form-group text-center">
<button class="btn btn-main btn-block" type="submit">INVITE</button>
</div>
</form>
For what it's worth, I had started with this awful mess - but if you look at the code below, you might more easily understand the code above!
public form: FormGroup;
public email1: AbstractControl;
public email2: AbstractControl;
public email3: AbstractControl;
public email4: AbstractControl;
public email5: AbstractControl;
constructor(
fb: FormBuilder
) {
this.form = fb.group({
'email1': ['', Validators.compose([emailValidator])],
'email2': ['', Validators.compose([emailValidator])],
'email3': ['', Validators.compose([emailValidator])],
'email4': ['', Validators.compose([emailValidator])],
'email5': ['', Validators.compose([emailValidator])],
});
this.email1 = this.form.controls['email1'];
this.email2 = this.form.controls['email2'];
this.email3 = this.form.controls['email3'];
this.email4 = this.form.controls['email4'];
this.email5 = this.form.controls['email5'];
}
and the above used 5 of these divs in the template - not very DRY!
<div class="form-group">
<input [formControl]="email1" class="form-control checking-field" placeholder="Email" type="text">
<span class="help-block" *ngIf="form.get('email1').touched">
<span class="text-danger" *ngIf="form.get('email1').hasError('invalidEmail')">Invalid email address</span>
</span>
</div>
I guess it's not possible with FormControlName.
You could use ngModel .. take a look at your modified plunker:
http://plnkr.co/edit/0DXSIUY22D6Qlvv0HF0D?p=preview
#Component({
selector: 'my-app',
template: `
<hr>
<form [formGroup]="userForm" (ngSubmit)="submit(userForm.value)">
<input type="checkbox" formControlName="isRequired"> Required Field
<div formGroupName="type">
<div formArrayName="options">
<div *ngFor="let option of userForm.controls.type.controls.options.controls; let i=index">
<label>{{ option.value.label }}</label><br />
<!-- change your textarea -->
<textarea [name]="i" [(ngModel)]="option.value.value" [ngModelOptions]="{standalone: true}" ></textarea>
</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
<br>
<pre>{{userForm.value | json }}</pre>
`,
})
export class App {
name:string;
userForm: FormGroup;
fields:any;
ngOnInit() {
this.fields = {
isRequired: true,
type: {
options: [
{
label: 'Option 1',
value: '1'
},
{
label: 'Option 2',
value: '2'
}
]
}
};
this.userForm = this.fb.group({
isRequired: [this.fields.isRequired, Validators.required],
//... here a lot of other controls
type: this.fb.group({
// .. added map-function
options: this.fb.array(this.fields.type.options.map(o => new FormControl(o))),
})
});
}
submit(value) {
console.log(value);
}
constructor(private fb: FormBuilder) { }
addNumber() {
const control = <FormArray>this.userForm.controls['numbers'];
control.push(new FormControl())
}
}
You may try this
Typescript:
ngOnInit(): void {
this.user = this.fb.group({
Title: ['1'],
FirstName: ['', Validators.required],
LastName: ['', Validators.required],
ContactNumbers: this.fb.array([
this.fb.group({
PhoneNumber: ['', [Validators.required]],
IsPrimary: [true],
ContactTypeId: [1]
})
]),
Emails: this.fb.array([
this.fb.group({
Email: ['', [Validators.required, Validators.email]],
IsPrimary: [true]
})
]),
Address: this.fb.group({
Address1: ['', Validators.required],
Address2: [''],
Town: [''],
State: ['UP'],
Country: [{ value: 'India', disabled: true }],
Zip: ['', Validators.required]
})
});
}
get Emails() {
return this.landlord.get('Emails') as FormArray;
}
Add and remove
addMoreEmail(index: number) {
if (index == 0) {
this.Emails.push(this.fb.group({ Email: ['', [Validators.required, Validators.email]], IsPrimary: [false] }));
} else {
this.Emails.removeAt(this.Emails.value.indexOf(index));
}
}
HTML
<form [formGroup]="user"
<div formArrayName="Emails">
<div *ngFor="let email of Emails.controls; let i=index">
<div class="row" [formGroup]="email">
<div class="col-sm-10">
<div class="form-group">
<label for="i" class="label">Email</label>
<input type="email" nbInput fullWidth id="i" placeholder="Email"
formControlName="Email" required>
</div>
</div>
<div class="col-sm-2 position-relative">
<nb-icon icon="{{i==0?'plus':'minus'}}-round" pack="ion"
(click)="addMoreEmail(i)">
</nb-icon>
</div>
</div>
</div>
</div></div>

Kendo Grid Custom Reordering

I am using Kendo Grid UI. The following is an example of the same.
<!DOCTYPE html>
<html>
<head>
<title></title>
<link href="http://cdn.kendostatic.com/2013.3.1324/styles/kendo.common.min.css" rel="stylesheet" />
<link href="http://cdn.kendostatic.com/2013.3.1324/styles/kendo.rtl.min.css" rel="stylesheet" />
<link href="http://cdn.kendostatic.com/2013.3.1324/styles/kendo.silver.min.css" rel="stylesheet" />
<link href="http://cdn.kendostatic.com/2013.3.1324/styles/kendo.dataviz.min.css" rel="stylesheet" />
<link href="http://cdn.kendostatic.com/2013.3.1324/styles/kendo.dataviz.silver.min.css" rel="stylesheet" />
<link href="/kendo-ui/content/shared/styles/examples.css" rel="stylesheet" />
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2012.1.515/js/kendo.all.min.js"></script>
</head>
<body>
<div id="main">
<h1 id="exampleTitle">
<span class="exampleIcon gridIcon"></span>
<strong>Grid /</strong> Column resizing </h1>
<div id="theme-list-container"></div>
<div id="exampleWrap">
<script>preventFOUC()</script>
<div id="example" class="k-content">
<div id="grid"></div>
<script>
$(document).ready(function() {
gridDataSource = new kendo.data.DataSource({
transport: {
read: "http://demos.telerik.com/kendo-ui/service/Northwind.svc/Orders"
},
});
$("#grid").kendoGrid({
dataSource: gridDataSource,
scrollable: true,
resizable: true,
columns: [
{
field: "OrderID",
title: "ID"
}, {
field: "OrderDate",
title: "Order Date"
},
{
field: "ShipCountry",
title: "Ship Country"
},
{
field: "ShipCity",
title: "Ship City"
},
{
field: "ShipName",
title: "Ship Name"
},
{
field: "ShippedDate",
title: "Shipped Date"
}
]
});
});
</script>
</div>
</div>
</div>
I want a customized reorder on columns. I have disabled drag and drop on OrderID. But columns other than OrderID can be reordered and these columns can be placed before OrderID column.
Is there a way where I can disable dropping of columns before OrderID?
You should do it in two steps:
Disable dropping into first column.
Disable dragging first column.
For the first part after creating the grid you can do:
$("th:nth(0)", "#grid").data("kendoDropTarget").destroy();
This gets from a grid which identifier is grid and the first head cell th:nth(0) the KendoUI DropTarget and destroys it (no longer a valid drop target).
For the second part, you should define a dragstart event that checks that you are dragging the first column and if so, you do not allow to drag it.
$("#grid").data("kendoDraggable").bind("dragstart", function(e) {
if (e.currentTarget.text() === "ID") {
e.preventDefault();
}
});
NOTE: Here I detected the first column asking for its text (ID) but you might change it to check for its position in the list of th in the grid and if so invoke preventDefault.
Check it running here: http://jsfiddle.net/OnaBai/jzZ4u/1/
check this for more elegant implementation:
kendo.ui.Grid.fn._reorderable = function (reorderable) {
return function () {
reorderable.call(this);
var dropTargets = $(this.element).find('th.disable-reorder');
dropTargets.each(function (idx, item) {
$(item).data("kendoDropTarget").destroy();
});
var draggable = $(this.element).data("kendoDraggable");
if (draggable) {
draggable.bind("dragstart", function (e) {
if ($(e.currentTarget).hasClass("disable-reorder"))
e.preventDefault();
});
}
}
}(kendo.ui.Grid.fn._reorderable);
where .disable-reorder class is for disabling column