VueJS with Bootstrap icons for sort - vue.js

I'm sorting column data by click on the table header.
I have added the Bootstrap icons based on the v-show condition(s).
Earlier, the sorting worked on the click of the table header but now it should work on the click of icon which are not visible now to the right of 'Present' column.
Need help on this. The origin of issue is on Line 13 in HTML:
<span class="glyphicon glyphicon-sort" v-show="toolAttribute != activeColumn"></span>
https://jsfiddle.net/L5p0ngdu/2/
new Vue({
el: '#app',
data: {
results: {
toolAttribute: [{attributeName: "Present", order: 1},{attributeName: "Present", order: 1},{attributeName: "Present", order: 1}],
device: [
{deviceName: "Device Name 1",
info: [{value: true}, {value: false}, {value: true}]},
{deviceName: "Device Name 2",
info: [{value: false}, {value: false}, {value: false}]},
{deviceName: "Device Name 3",
info: [{value: true}, {value: true}, {value: true}]},
{deviceName: "Device Name 4",
info: [{value: false}, {value: true}, {value: false}]},
{deviceName: "Device Name 5",
info: [{value: true}, {value: true}, {value: true}]}
]
},
activeColumn: {},
currentSort:['deviceName'],
currentSortDir:'asc'
},
computed:{
sortedResults:function() {
return this.results.device.sort(function(a,b){
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
this.currentSort.forEach(x => {
a = a[x];
b = b[x];
})
if(a< b) return -1 * modifier;
if(a > b) return 1 * modifier;
return 0;
}.bind(this));
}
},
methods:{
flasecond(index){
let res = false
this.results.device[index].info.forEach(info=> {
if(!info.value) res = true
})
return res
},
sort:function(s) {
//if s == current sort, reverse
if(s.join('') === this.currentSort.join('')) {
this.currentSortDir = this.currentSortDir==='asc'?'desc':'asc';
}
this.currentSort = s;
},
}
})
.falseclass{
background:red;
color:white;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<div id="app">
<table >
<tr>
<th rowspan="2" #click="sort(['deviceName'])">Device Info</th>
</tr>
<tr>
<th v-for="(toolAttribute, index) in results.toolAttribute" :key="index" #click="activeColumn = toolAttribute" :class="{active: toolAttribute == activeColumn}">{{toolAttribute.attributeName}}
<span #click="sort(['info', index, 'value']); toolAttribute.order = toolAttribute.order * (-1)" :class="toolAttribute.order > 0 ? 'glyphicon glyphicon-chevron-down' : 'glyphicon glyphicon-chevron-up'" v-show="toolAttribute == activeColumn"></span>
<span class="glyphicon glyphicon-sort" v-show="toolAttribute != activeColumn"></span></th>
</tr>
<tr v-for="(device, index) in sortedResults" >
<td :class="{'falseclass' : flasecond(index)}">{{ device.deviceName }}</td>
<td v-for="info in device.info" :class="{'falseclass' : !info.value}">{{info.value}}</td>
</tr>
</table>
</div>

In your jsfiddle I see that you are using Bootstrap 4. When Bootstrap migrated to v4 the Glyphicons icon font has been dropped. I'd suggest you to take a look at one of these free alternatives:
FontAwesome
Octicons

You could try this
i have made some changes to jsfiddle https://jsfiddle.net/thanseeh/tqy93meL/13/
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div id="app">
<table >
<tr>
<th rowspan="2" #click="sort(['deviceName'])">Device Info</th>
</tr>
<tr>
<th v-for="(toolAttribute, index) in results.toolAttribute" :key="index" #click="activeColumn = toolAttribute" :class="{active: toolAttribute == activeColumn}">{{toolAttribute.attributeName}}
<span #click="sort(['info', index, 'value']); toolAttribute.order = toolAttribute.order * (-1)" :class="toolAttribute.order > 0 ? 'glyphicon glyphicon-chevron-down' : 'glyphicon glyphicon-chevron-up'" v-show="toolAttribute == activeColumn"></span>
<span class="glyphicon glyphicon-sort" v-show="toolAttribute != activeColumn"></span></th>
</tr>
<tr v-for="(device, index) in sortedResults" >
<td :class="{'falseclass' : flasecond(index)}">{{ device.deviceName }}</td>
<td v-for="info in device.info" :class="{'falseclass' : !info.value}">{{info.value}}</td>
</tr>
</table>
</div>
</body>

Related

Multiple select inputs in table header with unique models

I am receiving data from the backend that takes the following format
[
[
[ "123", "21/11/2013", "Data", "Data" ],
[ "234", "22/11/2013", "Data", "Data" ],
[ "345", "12/09/2018", "Data", "Data" ],
],
[
[ "123", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data" ],
[ "234", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data" ],
[ "345", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data" ]
]
]
Each fileData represents a table, so in the above example it should produce two tables. The data inside contains a tables rows, so each table above has two rows.
In order to achieve this I am doing something like the following.
<table class="table" v-for="(file, index) in fileData" :key="index">
<tbody>
<tr v-for="(row, index2) in file":key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
</table>
This all seems to work fine. However, the data I am using does not have headers, but I need to prodive a header for each column that contains a select. As such I have added the following
<table class="table" v-for="(file, index) in fileData" :key="index">
<thead>
<tr>
<th scope="col" v-for="(col, index2) in file[index]" :key="index2">
<b-form-select v-model="options.value" :options="options"></b-form-select>
</th>
</tr>
</thead>
</table>
Once again this seems to work. My problem is that I want the user to define what a column represents, using the select. At the moment, if I select something, they all change.
I have produced this Fiddle as an example https://jsfiddle.net/mhyv62bt/1/
How can I make the selects independent, and is it also possible to remove an option once selected?
Thanks
This seems to produce the correct number of header columns for each table.
Update
I have a slightly different setup so trying to fit it in with my project. As such, I created the file THeadSelect.vue
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in this.length_"
:key="index"
>
<select
v-model="headers[index]">
<option disabled value="">
Please select one
</option>
<option
v-if="headers[index]"
selected
>
{{headers[index]}}
</option>
<option
v-for="option in filteredOptions"
:key="option"
>
{{option}}
</option>
</select>
</th>
</tr>
</thead>
</template>
<script>
export default {
mounted () {
this.$emit('update:headers',
this.headers
.concat(Array.from({ length: this.length_ }, _ => ''))
.slice()
)
},
props: {
options: {
type: Array,
required: true
},
length: Number,
headers: {
type: Array,
required: true
}
},
computed: {
length_: {
get () {
return this.length || this.options.length
},
set (l) {
this.$emit('update:length', l)
}
},
filteredOptions () {
return this.options.filter(
option => !this.headers.includes(option)
)
}
}
}
</script>
I am then trying to use this within my page
<template>
<div>
<b-form
novalidate
#submit.stop.prevent=""
>
<div class="row">
<div class="col-12">
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead
is="THeadSelect"
:options="['option1', 'option2', 'option3']"
:headers.sync="headers"
></thead>
<tbody>
<tr v-for="(row, index2) in file" :key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</b-form>
</div>
</template>
<script>
import { THeadSelect } from '#/components/common/THeadSelect'
export default {
components: {
THeadSelect
},
computed: {
fileData () {
return this.$store.getters.fileData
}
},
data () {
return {
headers: [],
length: 10,
}
}
}
</script>
It is a bit messed up though. Only 3 selects are being displayed for each table. Additionally, if I select an option in table 1, it selects the same option in table 2. If you check out my original fiddle you can see the initial data I am trying to work with, so there will always be two tables.
<div id='app'>
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead>
<tr>
<th scope="col" v-for="(col, index2) in file[index]" :key="index2+index">
<b-form-select v-model="selectedValue[index+index2]" :options="options">
<template v-slot:first>
<b-form-select-option :value="null" disabled>Ignore</b-form-select-option>
</template>
</b-form-select>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index2) in file" :key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
data: {
selectedValue: [],
mappedColumns: [],
selected: null,
options: [{
value: '1',
text: 'Option 1'
},
{
value: '2',
text: 'Option 2'
},
{
value: '3',
text: 'Option 3'
}
],
You are using a single value in v-model for all the dropdowns. so, once you change a single dropdown. All of them gets changed.
Try the above solution, where I declared a new array in data which is selectedValue
You can keep the data of which dropdown is selected in this array
I guess you can make this a component
The header:
Possible usage:
<thead
is="THeadSelect"
:options="header_row"
:length="length /*defaults to options.length*/"
:headers.sync="headers"
></thead>
The ComponentOptions
//*.js
const THeadSelect = {
template: "#theadselect",
mounted(){
// make sure headers is populated
this.$emit("update:headers",
this.headers
.concat(Array.from({length:this.length_}, _=>""))
.slice()
)
},
props: {
options: {
type: Array,
required: true,
},
length: Number,
headers: {
type: Array,
required: true
}
},
computed:{
length_:{
get(){
return this.length || this.options.length
},
set(l){
this.$emit("update:length",l)
}
},
filteredOptions(){
return this.options.filter(
option => !this.headers.includes(option)
)
}
}
}
The template
// *.html
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in length_"
:key="index"
>
<select
v-model="headers[index]">
<option disabled value="">
Please select one
</option>
<option
v-if="headers[index]"
selected
>
{{headers[index]}}
</option>
<option
v-for="option in filteredOptions"
:key="option"
>
{{option}}
</option>
</select>
</th>
</tr>
</thead>
</template>
Example
const THeadSelect = {
template: "#theadselect",
mounted(){
// make sure headers is populated
this.$emit("update:headers",
this.headers
.concat(Array.from({length:this.length_}, _=>""))
.slice()
)
},
props: {
options: {
type: Array,
required: true,
},
length: Number,
headers: {
type: Array,
required: true
}
},
computed:{
length_:{
get(){
return this.length || this.options.length
},
set(l){
this.$emit("update:length",l)
}
},
filteredOptions(){
return this.options.filter(
option => !this.headers.includes(option)
)
}
}
}
new Vue({
components: {THeadSelect},
data(){
return {
headers: [],
length: 10
}
},
template: "#root"
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="theadselect">
<thead>
<tr>
<th
v-for="(i,index) in length_"
:key="index"
>
<select
v-model="headers[index]">
<option value="">
Please select one
</option>
<option
v-if="headers[index]"
selected
>
{{headers[index]}}
</option>
<option
v-for="option in filteredOptions"
:key="option"
>
{{option}}
</option>
</select>
</th>
</tr>
</thead>
</template>
<template id="root">
<div>
<table>
<caption>Sample usage with select</caption>
<thead
is="THeadSelect"
:options="['option1', 'option2', 'option3']"
:headers.sync="headers"
></thead>
<tbody>
<tr>
<td
v-for="(prop, index) in headers"
:key="prop+index"
>
{{ prop || '?'}}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<table id="app"></table>
For the body one can think about the value of the headers array. Maybe put array indeces or object properties instead of currently option values
So for multiple tables one can think about:
<template id="table">
<table
v-for="(table, index) in tables"
:key="'table-'+index"
is="TableSelect"
:headers="table[0]"
:rows="table.slice(1)"
>
</table>
</template>
And for TableSelect:
const TableSelect = {
props: ["headers", "rows"],
template: "#table-select",
data(){
return {
selectedHeaders: []
}
},
computed(){
mappedRows(){
return this.rows
.map(row=> row.map(
(cell, index) => ({[headers[index]]: cell})
).reduce((obj, val) => Object.assign(obj, val))
)}
}
}
<template id="table-select">
<table>
<thead
is="THeadSelect"
:options="headers"
:headers.sync="selectedHeaders"
></thead>
<tbody>
<tr
v-for="(row, index) in mappedRows"
:key="'row-'+index"
>
<td
v-for="cell in selectedHeaders"
:key="cell+index"
>
{{cell && row[cell || ""]}}
</td>
</tr>
</tbody>
</table>
</template>
there are errors in above code but due to lazyness and missing linter on so i will let it be - as it provides the basic idea.
And a running example on codesandbox:
https://lbm8l.csb.app/
Use v-on:change and a function instead of v-model Here is the solution for individual selection
new Vue({
el: "#app",
data: {
mappedColumns: [],
selected: null,
options: [{
value: '1',
text: 'Option 1'
},
{
value: '2',
text: 'Option 2'
},
{
value: '3',
text: 'Option 3'
}
],
fileData: [
[
["123", "21/11/2013", "Data", "Data"],
["234", "22/11/2013", "Data", "Data"],
["345", "12/09/2018", "Data", "Data"],
],
[
["123", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
],
["234", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
],
["345", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data", "Data",
"Data"
]
]
]
},
methods: {
getSelectedItem(a, b, c) {
console.log(a, b, c);
}
}
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/tether#1.4.7/dist/css/tether.min.css">
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.15.0/dist/bootstrap-vue.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/tether#1.4.7/dist/js/tether.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.15.0/dist/bootstrap-vue.js"></script>
<title>Document</title>
<style>
#app {
padding: 20px;
height: 500px;
}
</style>
</head>
<body>
<div id='app'>
<table class="table table-bordered" v-for="(file, index) in fileData" :key="index">
<thead>
<tr>
<th scope="col" v-for="(col, index2) in file[index]" :key="index2">
<b-form-select v-on:change="getSelectedItem($event,index,index2)" :options="options">
<template v-slot:first>
<b-form-select-option :value="null" disabled>Ignore</b-form-select-option>
</template>
</b-form-select>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index2) in file" :key="index2">
<td v-for="(data, index3) in row" :key="index3">
{{ data }}
</td>
</tr>
</tbody>
</table>
</div>
<script src="table.js"></script>
</body>
</html>

How to pass a component to render in props in Vue Js?

I have a situation where i need to render data cell synamically
where tableProps contain all columns and dataProps.
tableProps: {
cols: [{
cellProps: {
class: "as"
},
cellRenderer: ((data) => {
return <a onlick = {
this.onDataClick
}
class = "btn btn-link" > {
data.name
} < /a>
}).bind(this),
dataKey: "name",
dataType: String,
label: "Name",
sortable: true
}
],
enableSelect: true,
onPageChange: this.onPageChange,
onSelect: (selectedRow) => console.log("selectedRow", selectedRow),
onSelectAll: (data) => console.log("slectAllClick", data),
page: 0,
rowProps: {
onClick: (event, rowData) => {
this.onClick(rowData);
}
},
rowsPerPage: 5,
title: "Nutrition"
}
There is a cell renderer where data can be passed to render custom data like buttons anchor etc..
the solution has been found, instead of sending a function, scoped slots can be used to render dynamic contents for each cell. Thank you for showing interest.
**Table.Vue(child, generic-table)**
<table class="table table-bordered">
<thead>
<tr>
<th v-for="col in options.cols" :key="col.id">
<template v-if="col.colRenderer">
{{col.colRenderer(col)}}
</template>
<template v-else>
{{col.label}}
</template>
</th>
</tr>
</thead>
<tbody>
<tr v-for="datum in data" :key="datum.id" #click="(e)=> options.rowProps.onClick ? options.rowProps.onClick(e, datum): ''">
<td v-for="col in options.cols" :key="col.id" #click="()=> col.onClick ? col.onClick(datum[col.dataKey]): ''">
<template v-if="col.cellSlot">
<slot :name="col.cellSlot.name" :data="datum[col.dataKey]"/>
</template>
<template v-else>
{{datum[col.dataKey]}}
</template>
</td>
</tr>
</tbody>
</table>
**Calling component(Parent, with Custom Data cells)**
<v-table
:name="carePlanName"
:options="tableProps"
:total-count="totalCount"
:data="users" >
<div
slot=""
slot-scope="slotProps">
<!-- Define a custom template for CellData Data -->
<!-- `slotProps` to customize each todo. -->
<span v-if="slotProps">✓
<button>{{ slotProps.name }}</button>
</span>
</div>
</v-table>

Reusing a form with Vue.js

I have a page with a list of things. I want the user to be able to click on an item in the list and open a modal dialog with a relatively complicated form with data for each item. I've implemented the form using Vue.js, and so far, it works, but I can't figure out how to get the Vue to switch from the data for one item to the data for another item, or do something equivalent to that.
The examples make it look like components are for small things and not entire forms. Here's the JavaScript from a JS fiddle with a hastily-coded example that's kind of similar to what I want to do.
HTML:
<table>
<thead>
<tr>
<th scope="col">Item</th>
<th scope="col">Data</th>
</tr>
</thead>
<tbody>
<tr data-identifier="1">
<td><a>Item 1</a></td>
<td class="json">{ "id": "1", "formData": { "whichOption": "1", "optionOneSetting": "ONE AYE", "optionTwoSetting": null, "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="2">
<td><a>Item 2</a></td>
<td class="json">{ "id": "2", "formData": { "whichOption": "2", "optionOneSetting": null, optionTwoSetting": "two-b", "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="3">
<td><a>Item 3</a></td>
<td class="json">{ "id": "3", "formData": { "whichOption": "2", "optionOneSetting": null, "optionTwoSetting": "two-c", "optionThreeSetting": null } }</td>
</tr>
<tr data-identifier="4">
<td><a>Item 4</a></td>
<td class="json">{ "id": "4", "formData": { "whichOption": "3", "optionOneSetting": null, "optionTwoSetting": null, "optionThreeSetting": "true" } }</td>
</tr>
<tr data-identifier="5">
<td><a>Item 5</a></td>
<td class="json">{ "id": "5", "formData": { "whichOption": "1", "optionOneSetting": "ONE AYE AGAIN", "optionThreeSetting": null } }</td>
</tr>
</tbody>
</table>
<div id="app" class="hidden">
<select v-model="formData.whichOption">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<label v-if="formData.whichOption==1">1-A: <input type="text" v-model="formData.optionOneSetting" /></label>
<div v-if="formData.whichOption==2">
<label><input type="radio" name="optionTwoSetting" value="two-a" v-model="formData.optionTwoSetting" /> 2-A</label>
<label><input type="radio" name="optionTwoSetting" value="two-b" v-model="formData.optionTwoSetting" /> 2-B</label>
<label><input type="radio" name="optionTwoSetting" value="two-c" v-model="formData.optionTwoSetting" /> 2-C</label>
</div>
<div v-if="formData.whichOption==3">
<label><input type="checkbox" name="optionThreeSetting" value="three" v-model="formData.optionThreeSetting" /> 3-A</label>
</div>
<button v-on:click="doMagic">Do a Thing</button>
</div>
JavaScript:
$(document).ready(function() {
$("a").click(function() {
var data = $(this).closest("tr").find("td.json").text();
data = JSON.parse(data);
if (!document.vue) {
document.vue = new Vue({
el: "#app"
, data: data
, methods: {
doMagic: function(event) {
var $tr = $("tr[data-identifier=" + this.id + "]");
console.log($.extend({}, this));
$tr.find(".json").text(JSON.stringify({
"id": this.id
, "formData": this.formData
}));
}
}
})
$("#app").removeClass("hidden");
}
else {
// This isn't expected to work, but it's effectively what I'd like to happen.
document.vue.data = data;
}
})
})
https://jsfiddle.net/don01001100/eywraw8t/370396/

Vuejs2- Avoid repetition of select field options using vee-validate

Iam using vee-validate plugin for validation. In my form, there is a select field in the table. Rows will be added dynamically in the table. I don't want to select the same select(Description column) option again and again Image. Hence I want to throw an error like "Selected description already exists in a table" this using vee-validate. Kindly help me to solve this.
Here is my code:
<template>
<div>
<b-card>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th style="width: 330px;">Description</th>
<th style="width: 130px;" class="text-right">Charges</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows" :key="row.qty">
<td>
{{ index +1 }}
</td>
<td>
<select class="form-control" v-model="row.billChgDesc" v-validate="'required|check'" :name="'billChgDesc' + index" data-vv-as="Description" #change="checkRepetation">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
<span v-show=" errors.has('billChgDesc' + index)" class="is-danger">{{ errors.first('billChgDesc' + index) }}</span>
</td>
<td>
<input class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index" data-vv-as="Charges" >
<span v-show=" errors.has('charges' + index)" class="is-danger">{{ errors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges" number readonly />
<input type="hidden" :value="row.qty * row.charges * row.tax / 100" number/>
</td>
<td>
<button class="btn btn-primary btn-sm" #click="addRow(index)"><i class="fa fa-plus"></i></button>
<button class="btn btn-danger btn-sm" #click="removeRow(index)"><i class="fa fa-minus"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">TOTAL</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery" number/></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</b-card>
</div>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
data: function () {
return {
billChgDescOpt: [
{ value: '', text: 'Select' },
{ value: 'M', text: 'Maintenance Fee'},
{ value: 'W', text: 'Water Charges'},
{ value: 'P', text: 'Penalty Fee'},
],
rows: [
{qty: 5, billChgDesc: '', charges: 55.20, tax: 10},
{qty: 19, billChgDesc: '', charges: 1255.20, tax: 20},
],
grandtotal: 0,
delivery: 40,
selectArr:[]
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
checkRepetation:function(){
this.$validator.extend('check', {
getMessage: field => '* Slected ' + field + ' already exists',
validate: function(value){
selectArr.push(value);
}
})
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
Thanks in advance.
You're on the right track, but a couple changes need to be made. When you call this.$validator.extend, that only needs to be done once - when your component is created. It attaches the check method to the validator, so then every time you have the attribute v-validate="'required|check'" in your HTML, it will run that check method.
In your check validator, you need to answer the question "is this value already selected". The answer is to go through the this.rows and see if any of them have the same billChgDesc property. Because this is in Vue, by the time the validator gets run, the row in question already does have that value, so you want to check if MORE than one row have that value. So, something like this:
mounted() {
var self = this;
this.$validator.extend('check', {
getMessage: field => '* Selected ' + field + ' already exists',
validate: function(value){
return (self.rows.filter(function(v){
return v.billChgDesc == value;
}).length <= 1);
}
});
}
This validator returns true if only one item has the given value. I'm using the built-in filter method of Array (see docs).
You can see an example of this all working here: https://jsfiddle.net/ryleyb/f9q50wx4/1/

yadcf - datatable with ajax serverSide and external filter

I develop an application and I used my DataTable and Yadcf.
I have lots of data in my database, so I am used to search in ajax.
I need to filter above the table but now the search is done only on the first page of the table. How to do a search on the whole picture?
My code :
JS
var oTable;
oTable = $('#table-announcement').DataTable({
"processing": true,
"serverSide": true,
"bServerSide": true,
"responsive": true,
"stateSave": true,
"autoWidth": false,
"bJQueryUI": true,
"bStateSave": true,
//"ajax": "/app_dev.php/admin/avantages/announcement/datatable/add/ajax",
ajax: {
url: "/app_dev.php/admin/avantages/announcement/datatable/add/ajax",
type: "POST"
},
"sAjaxDataProp": "data",
"pageLength": 10,
"paging": true,
"searching": true,
"bFilter": true,
"order": [[ 1, 'desc' ]],
"columnDefs": [{
"targets": 'no-sort', // no sort cols
"orderable": false
}],
"language": {
"lengthMenu": $lengthMenu,
"zeroRecords": $zeroRecords,
"info": $info,
"infoEmpty": $infoEmpty,
"infoFiltered": $infoFiltered,
"searchPlaceholder": $searchPlaceholder,
"search":$search,
"sProcessing": $sProcessing,
"oPaginate": {
"sFirst": $sFirst,
"sLast": $sLast,
"sNext": $sNext,
"sPrevious": $sPrevious
}
},
"columns":[
{"data": "announcement"},
{"data": "category"},
{"data": "from"},
{"data": "created_at"},
{"data": "validationDate"},
{"data": "priority"},
{"data": "remainingValidate"},
{"data": "status"},
{"data": "nbcontacts"},
{"data": "htmlActions"}
]
});
yadcf.init(oTable, [
{column_number : 0,
filter_type: "text",
filter_container_id: 'external_filter_title',
filter_default_label: "Titre"
},
{ column_number : 1,
select_type: 'select2',
filter_container_id: 'external_filter_category',
filter_default_label: "Rubrique"
},
{column_number : 2,
filter_type: "text",
filter_container_id: 'external_filter_user_announcement',
filter_default_label: "De"
},
{column_number : 5,
select_type: 'select2',
filter_container_id: 'external_filter_priority',
filter_default_label: "Urgence"
},
{column_number : 7,
select_type: 'select2',
column_data_type: "html",
html_data_type: "text",
filter_container_id: 'external_filter_status',
filter_default_label: "Etat"
}
],{ externally_triggered: true});
Html
<div class="filter-block tiny-full">
<div class="row">
<div class="col-xs-12 tiny-full list-count">
<div>
<span><b>{{ nbannouncement }}</b></span> {{ 'message.linkingAnnoucement.list.label_mer1'|trans }} <span><b>{{ nbAnnouncementsPending }}</b></span> {{ 'message.linkingAnnoucement.list.label_mer2'|trans }}
</div>
<div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 filter-content">
<div class="filter-table">
<div class="filter-table-cell">
<div class="label_filter">{{ 'message.linkingAnnoucement.list.filter.filter_announcement'|trans }} </div><div class="filter"><span id="external_filter_title"></span></div>
</div>
<div class="filter-table-cell">
<div class="label_filter">{{ 'message.linkingAnnoucement.list.filter.filter_category'|trans }} </div><div class="filter"><span id="external_filter_category"></span></div>
</div>
<div class="filter-table-cell">
<div class="label_filter">{{ 'message.linkingAnnoucement.list.filter.filter_from'|trans }} </div><div class="filter"><span id="external_filter_user_announcement"></span></div>
</div>
<div class="filter-table-cell">
<div class="label_filter">{{ 'message.linkingAnnoucement.list.filter.filter_priority'|trans }} </div><div class="filter"><span id="external_filter_priority"></span></div>
</div>
<div class="filter-table-cell">
<div class="label_filter">{{ 'message.linkingAnnoucement.list.filter.filter_state'|trans }} </div><div class="filter"><span id="external_filter_status"></span></div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<table class="table table-striped hover dataTable no-footer" id="table-announcement">
<thead>
<tr>
<th class="col-md-3">
{{ 'message.linkingAnnoucement.list.tab.tab_announcement'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_category'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_from'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_created_date'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_diffusion_date'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_priority'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_time_remaining'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_state'|trans }}
</th>
<th class="col-md-1">
{{ 'message.linkingAnnoucement.list.tab.tab_nb_contact'|trans }}
</th>
<th class="col-md-1 no-sort">
</th>
</tr>
</thead>
<tbody class="panel-body">
</tbody>
</table>
</div>
</div>
My symfony controler
public function paginateAction(Request $request)
{
$this->initImgManager();
$formatter = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::NONE, \IntlDateFormatter::NONE);
$formatter->setPattern("d MMMM YYYY");
$this->formatter = $formatter;
$length = $request->get('length');
$length = $length && ($length!=-1)?$length:0;
$start = $request->get('start');
$start = $length?($start && ($start!=-1)?$start:0)/$length:0;
$search = $request->get('search');
$filters = [
'query' => #$search['value']
];
$announcements = $this->getDoctrine()->getRepository('AppBundle:Announcement\Announcement')->search(
$filters, $start, $length
);
$output = array(
'data' => array(),
'recordsFiltered' => count($this->getDoctrine()->getRepository('AppBundle:Announcement\Announcement')->search($filters, 0, false)),
'recordsTotal' => count($this->getDoctrine()->getRepository('AppBundle:Announcement\Announcement')->search(array(), 0, false))
);
/**
* #var Announcement $announcement
*/
foreach ($announcements as $announcement)
{
// first image announcement
if ($announcement->getPicturesFiles()->count() > 0)
{
$image = $announcement->getPicturesFiles()->first()->getFilePath();
$handlingAnnouncementFirst = $this->imageHandlingManager->open($image)->zoomCrop(75, 75, 'transparent')->guess(100);
$imageAnnouncementFirst = $this->assetsHelper->getUrl($handlingAnnouncementFirst);
$htmlAnnouncement = '<figure>
<img src="'.$imageAnnouncementFirst.'">
<figcaption>
'.$announcement->getTitle().'
</figcaption>
</figure>';
}
else
{
$htmlAnnouncement = '<figure>
<figcaption>
'.$announcement->getTitle().'
</figcaption>
</figure>';
}
// image user from
if (!empty($announcement->getUsers()->getFile()))
{
$image = $announcement->getUsers()->getFile()->getFilePath();
$handlingUser = $this->imageHandlingManager->open($image)->zoomCrop(45, 45, 'transparent')->guess(100);
$imageUser = $this->assetsHelper->getUrl($handlingUser);
$htmlUserFrom = '<div style="float:left;">
<figure>
<img class="img-circle" src="'.$imageUser.'">
</figure>
</div>
<div style="float:left;">
'.$announcement->getUsers()->getFirstname().' '.$announcement->getUsers()->getLastname().'
</div>';
}
else
{
$htmlUserFrom = '<div style="float:left;">
<div class="job-resume-img-author"></div>
</div>
<div style="float:left;">
'.$announcement->getUsers()->getFirstname().' '.$announcement->getUsers()->getLastname().'
</div>';
}
// remaining time
if (!empty($announcement->getDuration()->getDuration()) && !empty($announcement->getValidationDate()))
{
/** #var AppExtension $twigExtensionService */
$twigExtensionService = $this->get('app.twig_extension');
$requestAttributes = $this->container->get('request')->attributes;
$remainingValidateAnnouncement = $twigExtensionService->remainingtimeFilter($announcement->getDuration()->getDuration(), $announcement->getValidationDate()->format('Y-m-d H:i:s'), $announcement->getId());
}
else
{
$remainingValidateAnnouncement = "-";
}
// status
$htmlStatus = '<span class="label '.$announcement->getStatus()->getClassStatus().'">
'.$announcement->getStatus()->getName().'
</span>';
// actions
$translator = $this->get('translator');
$tradAction = $translator->trans('message.common.btn.lib_action');
$tradView = $translator->trans('message.common.btn.lib_action');
$urlView = $this->generateUrl('avantages_backend_mer_edit', array("id" => $announcement->getId()));
$htmlActions = '<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
'.$tradAction.'
<i class="fa fa-chevron-down" aria-hidden="true"></i>
</button>
<ul class="dropdown-menu pull-right text-left">
<li>'.$tradView.'</li>
</ul>
</div>';
$output['data'][] = [
'announcement' => $htmlAnnouncement,
'category' => $announcement->getCategory()->getName(),
'from' => $htmlUserFrom,
'created_at' => $this->formatter->format($announcement->getCreatedDate()),
'validationDate' => (!empty($announcement->getValidationDate()) ? $this->formatter->format($announcement->getValidationDate()):'-'),
'priority' => $announcement->getPriority()->getName(),
'remainingValidate' => $remainingValidateAnnouncement,
'status' => $htmlStatus,
'nbcontacts' => $announcement->getLinkingsannouncements()->count(),
'htmlActions' => $htmlActions,
];
}
return new Response(json_encode($output), 200, ['Content-Type' => 'application/json']);
Since you are using "bServerSide": true you must implement the filtering logic on your server side just like in the showcase page - Server side source example , the showcase is written in JAVA, but you can get the general idea from the source code that can be found on github, in general you have to read the filtered values from the request (JAVA)
Just like you read the global filter String globalSearch = req.getParameter("search[value]");
You will have to read filtered value for each column (for example first/second coulmns)
String sSearch_0 = req.getParameter("columns[0][search][value]");
String sSearch_1 = req.getParameter("columns[1][search][value]");
Also, in case that you want to populate your select / auto_complete filters with values you have to add to your current JSON the following attributes yadcf_data_0 / yadcf_data_1 / etc'
where each attribute contains a list of strings
For example:
"yadcf_data_0":["KHTML","Webkit","Trident","Misc","Other browsers","Tasman","Presto","Gecko"],
Again, you should read the notes on the showcase page