How to keep search working on modified render columns DataTables.js - datatables

How to keep search working on modified render columns DataTables.js ?
please check below snippet, try to search a test3 / a test4 it will resulting No matching records found.
const someData = [
{ id: 1, keyword: "keyword a", value: ["a test1", "a test2", "a test3", "a test4"]}
];
$(document).ready( function () {
$('#table').DataTable({
data: someData,
columns: [
{
data: "keyword",
render: function( data, type, row, meta ) {
var value = `${row.keyword}`;
return value;
}
},
{
data: "value",
render: function( data, type, row, meta ) {
var value = "";
if ( data.length > 2 ) {
data.forEach( function(v, i) {
if ( i < 2 ) value += `${v}<br/>`;
else if ( i == 2 ) value += `. . . . .`
});
} else {
for (var i=0; i<3; i++ ) {
value += data[i] ? `${data[i]}<br/>`: `<br/>`
}
}
return value;
}
}
]
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<link href="//cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css" rel="stylesheet"/>
<table id="table">
<thead>
<tr>
<th>Keyword</th>
<th>Value</th>
</tr>
</thead>
</table>
Any help would be greatly appreciated.

for now i'm using this hacky way to make it work, i'm add row with original value and set it visible: false on columnDefs, if there a right way to do it i'll accept it as an answer.
const someData = [
{ id: 1, keyword: "keyword a", value: ["a test1", "a test2", "a test3", "a test4"]}
];
$(document).ready( function () {
$('#table').DataTable({
data: someData,
columns: [
{
data: "keyword",
render: function( data, type, row, meta ) {
var value = `${row.keyword}`;
return value;
}
},
{
data: "value",
render: function( data, type, row, meta ) {
var value = "";
if ( data.length > 2 ) {
data.forEach( function(v, i) {
if ( i < 2 ) value += `${v}<br/>`;
else if ( i == 2 ) value += `. . . . .`
});
} else {
for (var i=0; i<3; i++ ) {
value += data[i] ? `${data[i]}<br/>`: `<br/>`
}
}
return value;
}
},
{
data: "value"
}
],
columnDefs: [
{
targets: 2,
visible: false
}
]
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<link href="//cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css" rel="stylesheet"/>
<table id="table">
<thead>
<tr>
<th>Keyword</th>
<th>Value</th>
<th>Value</th>
</tr>
</thead>
</table>

Related

How to generate table with rowspan and colspan automatically from json?

I retrive data about different plans from an api and I would like to show a nice formated table, where the same feature on a different plan are merged with a colspan, and a feature that requires 2 rows are merged with a rowspan. But I've been through 3-4 iterations and I always face a different problem. I need your help :)
I looked around and I could find a solution for the rowspan or the colspan but I was never successful in merging both solutions.
Similar question:
How to use dynamic table rowspan in vue.js?
The objective:
Current setup:
<table>
<thead>
<tr>
<th></th>
<th v-for="planInfo in data.plans" class="bg-primary">
<h4 class="mb-0 text-light">
{{ planInfo.name }}
</h4>
</th>
</tr>
</thead>
<tbody>
<template v-for="(_row, rowId) in data.rows" :key="_row.key">
<tr>
<th v-if="remconId === 0" :rowspan="_row.rowspan">{{ remcon }}</th>
<th v-else>{{ _row.name }}</th>
<template class="txt-color" v-for="(planFeat, planFeatId) in objectToArray(data.plans, data.rows[rowId].key)" :key="planFeatId">
<td class="txt-color" v-if="_row.key !== 'remote_conn'">
<span v-if="planFeat" v-html="planFeat"></span>
</td>
<template v-else="_row.key === 'remote_conn'">
<td>{{ planFeat }}</td>
</template>
</template>
</tr>
<!-- <tr v-for="(remcon, remconId) in plan.values.remote_conn">
</tr> -->
</template>
</tbody>
</table>
<script setup lang="ts">
const data = {
rows: [
{
name: "Feature A",
key: "feata"
},
{
name: "Feature B",
key: "featb"
},
{
name: "Feature C",
key: "featc",
rowSpan: 2,
},
{
name: "Feature D",
key: "featd"
},
],
plans: [
{
"name": "Plan 1",
"feata": "yes",
"featb": 5,
"featc": {
"value1": 10,
"value2": 5
},
"featd": "no",
},
{
"name": "Plan 2",
"feata": "no",
"featb": 10,
"featc": {
"value1": 0,
"value2": 1
},
"featd": "no",
},
]
}
const objectToArray = (objArr, key) => {
console.log("Looking for ", key, objArr)
return Array.from(objArr, plan => plan.values[key])
}
</script>
Another type of attempt
Some scripts I've been trying below. Unoptimized, probably stupid code, from a differently formatted json where the features are stored in an array, but it wasn't very intuitive:
// Check if the next TD have the same value, if so, increment colSpan by one
let sameCount = 0;
const colspanCount = (i, j, isRecursion = false) => {
console.log(`Col ${j}, Row ${i}`)
if(!isRecursion) {
sameCount = 1;
} else {
console.log(`Is recursion, ${j} ${i}`)
}
// Is j in-range?
if(j >= data.plans.items.length - 1) {
console.log(`${j+1} is out of range, max is ${attr.value.plans.items.length - 1}`)
// This is the last column, there is nothing after that. return 1
if(isRecursion) return false;
} else
// Next value is the same as this one, check the next one
if(attr.value.plans.items[j].features[i] === attr.value.plans.items[j+1].features[i]) {
sameCount++;
console.log(`${i} is the same. ${attr.value.plans.items[j].features[i]} = ${attr.value.plans.items[j+1].features[i]}`);
let nextIsSame = colspanCount(i, j+1, true);
if(nextIsSame) {
if(isRecursion) return true;
} else {
if(isRecursion) return false;
}
}
console.log(`== End, ${sameCount}`)
return sameCount;
}
// Check if we need to add an additional TD
// Don't if the previous TD have the same value
let isFirstVar = true;
const isFirst = (i, j) => {
console.log(`Col ${j}, Row ${i}`)
isFirstVar = true;
// Is j in-range?
if(j <= 0) {
console.log(`${j-1} is out of range`)
return isFirstVar;
// This is the last column, there is nothing after that. return 1
} else
// Next value is the same as this one, check the next one
if(attr.value.plans.items[j].features[i] === attr.value.plans.items[j-1].features[i]) {
isFirstVar = false;
console.log(`${i} is the same. ${attr.value.plans.items[j].features[i]} = ${attr.value.plans.items[j-1].features[i]}`);
}
// if(i <= items.length - 1 && j <= items[i].length - 1) {
// console.log(i, j)
// }
// if((len - 1 < j++) && items[i][j] == items[i][j++]) {
// return 2;
// }
console.log(`== End is it first? ${isFirstVar}`)
return isFirstVar;
}
The json data that goes with the above script:
const attr = ref({
plans: {
features: [
"feata",
"featb",
"featc",
{
name: "featC",
rowspan: 2
}
],
items: [
{
name: "",
features: [
"feata_value", "featb_value", "featc1_value", "featc2_value",
],
},
]
},
})
I would use those 2 functions in the TD like so, where i is the "plan" and j the the "feature" number in a loop:
<td class="txt-color"
:colspan="colspanCount(i, j)" v-if="isFirst(i, j)">
<span v-if="planFeat" v-html="planFeat"></span>
</td>
But I couldn't make this works with rowspan, as the next feature would be on the same row as another one...

How to trigger a component transition when the components data has loaded in vue js?

I have a fade-in transition that is working with all my components, the problem is a few of my components that are making api calls - transition in before the data is fully loaded.
So if I have a table with each row being populated with data from the api call, the table headers will transition initially and then a few seconds later - many rows with data will suddenly appear. What I want is for the table/data to fade-in. How can I trigger or delay the transition until the job_execs array gets populated with data from the API call?
views/releases.vue
<script>
import NavBar from "../components/NavBar.vue";
import Releases from "../components/releases/Releases.vue";
import Footer from "../components/Footer.vue";
export default {
name: "releases",
data() {
return {
loading: true
};
},
components: {
NavBar,
Releases,
Footer
},
};
</script>
<template>
<div id="vue-main">
<NavBar></NavBar>
<h1><b>Releases</b></h1>
<transition name="fade" appear mode="out-in">
<Releases></Releases>
</transition>
<Footer></Footer>
</div>
</template>
components/releases/Releases.vue
<template>
<div class="releases">
<table>
<template>
<tr>
<td><b>Version</b></td>
<td><b>Platform</b></td>
<td><b>Status</b></td>
</tr>
<tr v-for="(item, index) in orderedReleases">
<td :style="tdStyle">{{ item.version }}</td>
<td :style="tdStyle">{{ item.platform }}</td>
<td :style="tdStyle">{{ item.status }}</td>
</tr>
</template>
</table>
</div>
</template>
<script>
import moment from "moment";
import sortBy from "lodash/sortBy";
export default {
name: "Releases",
props: ["loading"],
data() {
return {
job_execs: []
};
},
computed: {
orderedReleases: function() {
let newlist = this.job_execs.sort(this.naturalCompare).reverse()
for ( var i = 0; i < newlist.length; i++) {
if (typeof newlist[i].version === "string") {
if (newlist[i].version.startsWith("iPad")) {
console.log(newlist[i].version)
newlist.splice(i,1);
i--;
}
}
}
return newlist;
},
},
methods: {
calculateDuration: function(time_start, time_end) {
this.theDuration = moment.duration(time_end.diff(time_start));
if (this.theDuration.seconds() == 0) {
this.cleanDuration = "N/A";
} else {
this.cleanDuration =
this.theDuration.hours() +
" hrs " +
this.theDuration.minutes() +
" min " +
this.theDuration.seconds() +
" sec";
}
return this.cleanDuration;
},
naturalCompare: function(a, b) {
var ax = [], bx = [];
a.version.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]) });
b.version.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]) });
while(ax.length && bx.length) {
var an = ax.shift();
var bn = bx.shift();
var nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
if(nn) return nn;
}
return ax.length - bx.length;
}
},
created() {
this.jobExecEndpoint = process.env.VUE_APP_UATU_URL + "/api/v1/release/";
fetch(this.jobExecEndpoint)
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.length; i++) {
this.cleanStartTime = moment(body[i].start_date);
this.job_execs.push({
version: body[i].version,
status: body[i].status.name,
start: this.cleanStartTime.format("LLL")
});
}
})
.catch(err => {
console.log("Error Fetching:", this.jobExecEndpoint, err);
return { failure: this.jobExecEndpoint, reason: err };
});
}
};
</script>
<style>
</style>
Add a v-if inside your Releases component on the div tag like so:
<div class="releases" v-if="job_execs"></div>
and change your data object like this:
data() {
return {
job_execs: null
};
},
The pattern I use:
loading: smth = null
loaded: smth = [...]
empty state: smth.length === 0
This way you don't need a separate loading property.

vue js error:" For recursive components, make sure to provide the "name" option." when recursively create table

I create a dynamic HTML table by vue js which get data from server. The data includes 'columns', which is a Array with server objects including the table header(key is title), 'q', is the value type of each column generated from database table column name, 'content", the value display method. e.g content='content":'', the cell will display like ''.
HTML Part
<div id="app3">
<table class="table table-bordered">
<thead>
<tr>
<th scope="col" v-for="c in columns" v-text="c.title"></th>
</tr>
</thead>
<tbody>
<vue-cell:rows="rows" :columns="columns">
</vue-row>
</tbody>
</table>
</div>
Vue js part
<script>
var columns =[
{'q': "id",
'title': 'id',
'content":'<input type="checkbox">'
},
{'q': "type",
'title': 'Type',
'content":''
},
]
var rows=[
{'id': 1,
'type':"Server",
},
{'id': 2,
'type':"PC",
},
]
create component to loop rows and column
Vue.component('vue-cell', {
name:'inputBox',
props: {
rows: Array,
columns: Array,
},
render: function (createElement) {
var self = this
return createElement('tr', self.rows.map(function(row) {
return createElement('td', self.columns.map(function(column) {
// comlum.q = id or type ...
// column['content'] = <input type="checkbox">
//assign row.id as defalut value
if (column['content'].includes('checkbox')) {
return createElement('input', {
attrs: {
type: "checkbox",
},
domProps: {
// column.q may not be 'id' maybe type
// get the value of type or id in a row.
value: row[column.q],
}
})
} else {
return row[column.q];}
}))
}))
},
})
Create a new Vue
new Vue ({
data: function() {
return {
columns: [],
rows:[],
}
},
created: function() {
var self = this;
axios.("/web/asset-json.html").then(function (response) {
self.columns = [...response.data.columns];
self.rows = [...response.data.rows]
})
}
})
</script>

Looping over vue Component

Hi everyone.
I have a question about looping over a component in vuejs.
I defined a component and also a template tag which contains a table that fetches some data from database(in laravel framework).now i need to loop over this component more than 10 times and don't know how to do this!.
i used v-for in template tag but didn't work
<div id="app">
<csgo><csgo>
</div>
<template id="match-id" >
<table style="border: 2px solid black">
<tr >
<th>Ticket</th>
<th>Number</th>
<th>Match Type</th>
<th>Date</th>
<th>Time</th>
<th>Price</th>
<th>Sold</th>
</tr>
<tr>
<td> <button #click='buy({{$user->profile->account_money}})' v-bind:style="objectStyle" :disabled=Bstate >BUY</button> </td>
<td><input type="number" min="1" max="10" step="1" v-model="ticketNumber"></td>
<td>{{$matches[0]->matchType}}</td>
<td>{{$matches[0]->matchDate}}</td>
<td>{{$matches[0]->matchTime}}</td>
<td>{{$matches[0]->price}}</td>
<td>#{{soldTicket}}#{{sold}}</td>
</tr>
</table>
</template>
<script>
Vue.component('csgo', {
template: '#match-id',
data: function () {
return {
money: '',
sold: '',
state: false,
Estate: false,
Bstate: false,
error: '',
ticketNumber: 1,
objectStyle: {
background: 'lightgreen'
},
}
},
props: ['matches'],
methods: {
buy: function (num) {
tempNum = num
num -= 1000
if (num < 0) {
this.money = tempNum
}
vm = this
axios.post('/matches/csgo-buy-ticket', {tickets:
Math.floor(vm.ticketNumber)}).then(function (response) {
if (typeof (response.data) == 'string') {
vm.error = response.data
vm.state = false
vm.Estate = !vm.state
vm.Bstate = false
}
else {
vm.money = response.data[0]
vm.sold = response.data[1]
vm.state = true
vm.Estate = !vm.state
vm.Bstate = true
vm.objectStyle.background = 'darkred'
}
})
},
},
computed: {
soldTicket: function () {
vm = this
axios.get('/sold-ticket').then(function (response) {
return vm.sold = response.data
})
},
account_money: function () {
var vm = this
axios.get('/user-account-money').then(function
(response) {
vm.money = response.data
})
},
},
})
new Vue({
el:'#app',
data:{
list:[''],
},
created:function () {
vm = this
axios.get('/csgo-matches').then(function (response) {
console.log(response.data)
vm.list = response.data
})
},
})
</script>

Jquery dataTable plugin, Can I combine initialization (delete chekbox rows, and export to csv)?

I have successfully implemted the plugin, now I need functionality. There are no examples of mixed implementation, ie. delete all with selected checkbox, with csv/print. Just examples of single function implementation. Which is ok but I think there will be an application or 2 that need 2 to 3 of the plugin extentions to be a complete application.
my code, mvc 4 razor view:
#{
ViewBag.Title = "Price Comparison";
}
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title> Learning JQuery</title>
<!---CSS-->
<link href="~/Content/Site.css" rel="stylesheet" />
<!---JQuery-->
<link href="~/Content/DataTables/css/dataTables.tableTools.css" rel="stylesheet" />
<link href="~/Content/DataTables/css/dataTables.tableTools.min.css" rel="stylesheet" />
<script src="~/Scripts/DataTables/dataTables.tableTools.js"></script>
<script src="~/Scripts/DataTables/dataTables.tableTools.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$.ajax({
url: 'PricingService.asmx/GetPricing',
method: 'post',
dataType: 'json',
success: function (data) {
var table = $('#example').dataTable({
select: true,
data: data,
columns: [
{ 'data': 'Manufacturer' },
{ 'data': 'Product' },
{ 'data': 'SKU' },
{
'data': 'BarcodePrice',
'render': function (barcodeprice) {
return "$" + barcodeprice;
}
},
{
'data': 'ScansourcePrice',
'render': function (scansourceprice) {
return "$" + scansourceprice;
}
},
{
'data': 'BluestarPrice',
'render': function (bluestarprice) {
return "$" + bluestarprice;
}
},
]
});
var tableTools = new $.fn.dataTable.TableTools(table, {
'sSwfPath': '//cdn.datatables.net/tabletools/2.2.0/swf/copy_csv_xls.swf',
'aButtons': [{
'sExtends': 'copy',
'bShowAll': false
},
{
'sExtends': 'print',
'bShowAll': false
},
{
'sExtends': 'csv',
'bShowAll': false
},
{
'sExtends': 'xls',
'bShowAll': false
},
]
});
$(tableTools.fnContainer()).insertBefore('#example_wrapper')
}
});
});
</script>
</head>
<body>
<form id="frm-example" action="/nosuchpage" method="POST">
<div style="border:1px solid black; padding:3px; width:1200px">
<table id="example">
<thead>
<tr>
<th>Manufacturer</th>
<th>Product</th>
<th>SKU</th>
<th>Our Price</th>
<th>Scansource Price</th>
<th>Bluestar Price</th>
</tr>
</thead>
<tfoot>
<tr>
<th>Manufacturer</th>
<th>Product</th>
<th>SKU</th>
<th>Our Price</th>
<th>Scansource Price</th>
<th>Bluestar Price</th>
</tr>
</tfoot>
</table>
<hr>
<p>Press <b>Submit</b> and check console for URL-encoded form data that would be submitted.</p>
<p><button>Submit</button></p>
<b>Data submitted to the server:</b><br>
<pre id="example-console">
</pre>
</div>
</form>
</body>
</html>
1.I can not initialize ('#example') more than once, so how in the world can I combine the following into the ajax code in my view?
//
// Updates "Select all" control in a data table
//
function updateDataTableSelectAllCtrl(table){
var $table = table.table().node();
var $chkbox_all = $('tbody input[type="checkbox"]', $table);
var $chkbox_checked = $('tbody input[type="checkbox"]:checked', $table);
var chkbox_select_all = $('thead input[name="select_all"]', $table).get(0);
// If none of the checkboxes are checked
if($chkbox_checked.length === 0){
chkbox_select_all.checked = false;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = false;
}
// If all of the checkboxes are checked
} else if ($chkbox_checked.length === $chkbox_all.length){
chkbox_select_all.checked = true;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = false;
}
// If some of the checkboxes are checked
} else {
chkbox_select_all.checked = true;
if('indeterminate' in chkbox_select_all){
chkbox_select_all.indeterminate = true;
}
}
}
$(document).ready(function (){
// Array holding selected row IDs
var rows_selected = [];
var table = $('#example').DataTable({
'ajax': {
'url': 'ids-arrays.txt'
},
'columnDefs': [{
'targets': 0,
'searchable':false,
'orderable':false,
'className': 'dt-body-center',
'render': function (data, type, full, meta){
return '<input type="checkbox">';
}
}],
'order': [1, 'asc'],
'rowCallback': function(row, data, dataIndex){
// Get row ID
var rowId = data[0];
// If row ID is in the list of selected row IDs
if($.inArray(rowId, rows_selected) !== -1){
$(row).find('input[type="checkbox"]').prop('checked', true);
$(row).addClass('selected');
}
}
});
// Handle click on checkbox
$('#example tbody').on('click', 'input[type="checkbox"]', function(e){
var $row = $(this).closest('tr');
// Get row data
var data = table.row($row).data();
// Get row ID
var rowId = data[0];
// Determine whether row ID is in the list of selected row IDs
var index = $.inArray(rowId, rows_selected);
// If checkbox is checked and row ID is not in list of selected row IDs
if(this.checked && index === -1){
rows_selected.push(rowId);
// Otherwise, if checkbox is not checked and row ID is in list of selected row IDs
} else if (!this.checked && index !== -1){
rows_selected.splice(index, 1);
}
if(this.checked){
$row.addClass('selected');
} else {
$row.removeClass('selected');
}
// Update state of "Select all" control
updateDataTableSelectAllCtrl(table);
// Prevent click event from propagating to parent
e.stopPropagation();
});
// Handle click on table cells with checkboxes
$('#example').on('click', 'tbody td, thead th:first-child', function(e){
$(this).parent().find('input[type="checkbox"]').trigger('click');
});
// Handle click on "Select all" control
$('#example thead input[name="select_all"]').on('click', function(e){
if(this.checked){
$('#example tbody input[type="checkbox"]:not(:checked)').trigger('click');
} else {
$('#example tbody input[type="checkbox"]:checked').trigger('click');
}
// Prevent click event from propagating to parent
e.stopPropagation();
});
// Handle table draw event
table.on('draw', function(){
// Update state of "Select all" control
updateDataTableSelectAllCtrl(table);
});
// Handle form submission event
$('#frm-example').on('submit', function(e){
var form = this;
// Iterate over all selected checkboxes
$.each(rows_selected, function(index, rowId){
// Create a hidden element
$(form).append(
$('<input>')
.attr('type', 'hidden')
.attr('name', 'id[]')
.val(rowId)
);
});
// FOR DEMONSTRATION ONLY
// Output form data to a console
$('#example-console').text($(form).serialize());
console.log("Form submission", $(form).serialize());
// Remove added elements
$('input[name="id\[\]"]', form).remove();
// Prevent actual form submission
e.preventDefault();
});
});
2.This successfully added the csv and other buttons along with the search and pagination. But the csv and copy still selects all the pages in the table instead of the one displayed.I followed the documentation and used the sExtends, but it still does not work.
Any help Please?