yadcf - datatable with ajax serverSide and external filter - yadcf

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

Related

Laravel 5 Datatable Search not working in all columns of the datatable

I am implementing datatables using three tables in my database.
I joined all threeofthem together (employee, person, suffix).
Datatable is reflected correctly. Butmy problem is when i search an
employee using there first name or employee number,it isworkingfine and
displays the employees base on what i search.However when i search using their
last name, middle name or suffix, it says nomatching records even if that
employee existed.
here is my bladefile
<div id="page-wrapper">
<div class="row">
<div class="page-header"><br>
<h2>Personnel Information List</h2>
<h5>Select Employee Personnel</h5>
</div>
<div class="row">
<div class="col-md-12">
<table id="EmployeeFilter" class="table table-striped table-hover">
<thead>
<tr>
<th> {{ Lang::get("employee.employee_no") }}</th>
<th> {{ Lang::get("employee.employee_name") }}</th>
<th> {{ Lang::get("form.action") }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
here is my script
<script type="text/javascript">
var oTable;
$(document).ready(function() {
oTable = $('#EmployeeFilter').DataTable( {
"sDom": "<'row'<'col-md-6'l><'col-md-6'f>r>t<'row'<'col-md-6'i><'col-md-6'p>>",
"sPaginationType": "bootstrap",
"bProcessing": true,
"bServerSide": true,
"bStateSave": true,
"sAjaxSource": "{{ URL::to('employee_report/data')}}",
"aoColumns": [
null,
null,
null,
],
"fnDrawCallback": function ( oSettings ) {},
});
});
</script>
and here is my controller that returns the data table
public function data() {
$personnel_employee_list = Employee::join('person','employee.person_id','=','person.id')
->leftJoin('suffix','person.suffix_id','=','suffix.id')
->select(array('employee.id','employee.employee_no','person.first_name', 'person.middle_name','person.last_name','suffix.suffix_name'))
->orderBy('employee.employee_no', 'ASC');
return Datatables::of($personnel_employee_list)
->add_column('actions', '<span class="glyphicon glyphicon-list-alt"></span> View Details
<input type="hidden" name="row" value="{{$id}}" id="row">')
->editColumn('first_name','{{ ucwords(strtolower($first_name." ".$middle_name." ".$last_name." ".$suffix_name)) }}')
->editColumn('first_name','{{ ucwords((str_replace("ñ","ñ",str_replace("Ñ","Ñ",$first_name))." ".str_replace("ñ","ñ",str_replace("Ñ","Ñ",$middle_name))." ".str_replace("ñ","ñ",str_replace("Ñ","Ñ",$last_name)) . " ".str_replace("ñ","ñ",str_replace("Ñ","Ñ",$suffix_name)) )) }}')
->removeColumn('id', 'middle_name', 'last_name', 'suffix_name')
->make();
}
my data table

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 loop by loop json data object in angular 5

i am going bind my local json data object in html one matcard having menu item the menu item inside two matcard such as i.e totalUser is one matcard having menuitem then menu item having Activeuser matcard, InActiveUser matCard, but this json data not comming console only how to get that one
report.json:-
{
"groupCode": 0,
"userType": 0,
"totalUsers": {
"totalUser": 33,
"userDetails": {
"activeUsers": 32,
"inActiveUsers": 1,
"lockUsers": 1
}
},
"totalGroups": {
"totalGroup": 26,
"totalProperties": 22,
"propertyDetails": {
"activeProperties": 22,
"inActiveProperties": 0
}
},
"status": 0,
"message": null
}
dasbord.ts:
this.httpService.get('../assets/report.json').subscribe(
      data => {
      this.arr=data;
console.log(this.arr);
      },
      (err: HttpErrorResponse) => {
       console.log (err.message);
      }
   );
dasborad.html:
<div>
<div>Total Users(s)</div>
<div>
{{arr|json}}
</div>
</div>
</div>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon >more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu" >
<div style="width:100%">
<div class="card border space">
<div class="header" fxLayout="row" fxLayoutAlign="space-between center">
<div class="name" fxLayout="row">
<div>
<div>Active Users(s)</div>
</div>
</div>
</div>
<div class="content">
Hello :
</div>
</div>
</div>

Passing dynamic object values to Modal Popup in VUE opening wrong ID value

I'm passing a dynamic object value into a Vue modal template but for some reason the ID that gets passed is always 1 + the selected ID, even though my console.log shows the right ID selected. It should be opening content for the ID selected.
(Also my modal button isnt closing.)
My pen is here: You can see its always trying to open the id + 1
https://codepen.io/omarel/pen/jXJVPw
VUE
// global component
Vue.component('popup',{
template: '#popup',
props: ["floorplan"]
})
//VUE connection
var floorplans = new Vue({
el:"#floorplans",
data: {
popup: false,
id: 1,
floorplans: [
{
"id": 1,
"building": "214",
"residence": "106",
"classname": "studio",
"bed": 0,
"bath": 1,
"den": 0,
"price": "$x,xxx",
"img": "floorplans/images/x.png",
"pdf": "floorplans/pdfs/x.pdf"
},
{
"id": 2,
"building": "214",
"residence": "109",
"classname": "1bed",
"bed": 1,
"bath": 1,
"den": 0,
"price": "$x,xxx",
"img": "floorplans/images/x.png",
"pdf": "floorplans/pdfs/x.pdf"
},
{
"id": 3,
"building": "214",
"residence": "208",
"classname": "1bed",
"bed": 1,
"bath": 1,
"den": 0,
"price": "$x,xxx",
"img": "floorplans/images/x.png",
"pdf": "floorplans/pdfs/x.pdf"
},
{
"id": 4,
"building": "214",
"residence": "205",
"classname": "1bed",
"bed": 1,
"bath": 1,
"den": 1,
"price": "$x,xxx",
"img": "floorplans/images/x.png",
"pdf": "floorplans/pdfs/x.pdf"
},
{
"id": 5,
"building": "210",
"residence": "303",
"classname": "2bed",
"bed": 2,
"bath": 2,
"den": 0,
"price": "$x,xxx",
"img": "floorplans/images/x.png",
"pdf": "floorplans/pdfs/x.pdf"
}
]
},
methods: {
// opennfloorplan: function(event) {
// console.log(event.id);
// }
pop: function(id){
console.log(id);
this.id = id;
console.log(this.id);
this.popup = true;
}
}
})
HTML
<section id="floorplans" class="table">
<table v-cloak class="sortable">
<thead>
<tr>
<th scope="col" class="sorttable_sorted">Residence<span id="sorttable_sortfwdind"> ▾</span></th>
<th scope="col">Bed/Bath</th>
<th scope="col">Building</th>
<th scope="col">Price</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="floorplan in floorplans" v-bind:class="floorplan.classname">
<td data-label="Residence">{{ floorplan.residence }}</td>
<td data-label="Bed/Bath">
<span v-if="floorplan.bed"> {{floorplan.bed}} BEDROOM </span>
<span v-else="floorplan.bed"> STUDIO </span>
<span v-if="floorplan.den"> + {{floorplan.den}} DEN </span>
<span v-if="floorplan.bath"> / {{floorplan.bath}} BATH</span>
</td>
<td data-label="Building">{{ floorplan.building }}</td>
<td data-label="Price">{{ floorplan.price }}</td>
<td data-label="Floor Plan">
{{ floorplan.id }}
<a v-on:click="pop(floorplan.id)" href="javascript:;" class="btn view white openfloorplan">View</a>
<a v-bind:href="floorplan.pdf" target="_blank" class="btn apply blue">Apply</a>
</td>
</tr>
</tbody>
</table>
<popup v-if="popup" :floorplan="floorplans[id]"></popup>
</section>
<template id="popup">
<transition name="popup">
<div class="popup">
<div class="content"><img width="200" height="106" />
<p>{{ floorplan.id }}</p>
<p>{{ floorplan.residence }}</p>
<button v-on:click="floorplans.$data.popup = false">button</button>
</div>
</div>
</transition>
</template>
You are using the index, not the id. floorplans[id] is just an array index. Your ids number from 1, but arrays number from zero, so floorplans[1] is the second floorplan, whose id is 2.
floorplans[id] will get the floorplan at INDEX id. Your floorplan ids start at 1 and array indexes start at 0.
<tr v-for="(floorplan, index) in floorplans" v-bind:class="floorplan.classname">
<a v-on:click="pop(index)" href="javascript:;" class="btn view white openfloorplan">View</a>

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/