How to generate table with rowspan and colspan automatically from json? - vue.js

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...

Related

Sorting a vue 3 v-for table composition api

I have successfully created a table of my array of objects using the code below:
<div class="table-responsive">
<table ref="tbl" border="1" class="table">
<thead>
<tr>
<th scope="col" #click="orderby('b.property')">Property</th>
<th scope="col"> Price </th>
<th scope="col"> Checkin Date </th>
<th scope="col"> Checkout Date </th>
<th scope="col" > Beds </th>
</tr>
</thead>
<tbody>
<tr scope="row" class="table-bordered table-striped" v-for="(b, index) in properties" :key="index">
<td> {{b.property}} </td>
<td> {{b.pricePerNight}}</td>
<td> {{b.bookingStartDate}} </td>
<td> {{b.bookingEndDate}} <br> {{b.differenceInDays}} night(s) </td>
<td> {{b.beds}} </td>
</tr>
</tbody>
</table>
</div>
<script>
import {ref} from "vue";
import { projectDatabase, projectAuth, projectFunctions} from '../../firebase/config'
import ImagePreview from "../../components/ImagePreview.vue"
export default {
components: {
ImagePreview
},
setup() {
const properties = ref([]);
//reference from firebase for confirmed bookings
const Ref = projectDatabase .ref("aref").child("Accepted Bookings");
Ref.on("value", (snapshot) => {
properties.value = snapshot.val();
});
//sort table columns
const orderby = (so) =>{
desc.value = (sortKey.value == so)
sortKey.value = so
}
return {
properties,
orderby
};
},
};
</script>
Is there a way to have each column sortable alphabetically (or numerically for the numbers or dates)? I tried a simple #click function that would sort by property but that didn't work
you can create a computed property and return the sorted array.
It's just a quick demo, to give you an example.
Vue.createApp({
data() {
return {
headers: ['name', 'price'],
properties: [
{
name: 'one',
price: 21
},
{
name: 'two',
price: 3
},
{
name: 'three',
price: 5
},
{
name: 'four',
price: 120
}
],
sortDirection: 1,
sortBy: 'name'
}
},
computed: {
sortedProperties() {
const type = this.sortBy === 'name' ? 'String' : 'Number'
const direction = this.sortDirection
const head = this.sortBy
// here is the magic
return this.properties.sort(this.sortMethods(type, head, direction))
}
},
methods: {
sort(head) {
this.sortBy = head
this.sortDirection *= -1
},
sortMethods(type, head, direction) {
switch (type) {
case 'String': {
return direction === 1 ?
(a, b) => b[head] > a[head] ? -1 : a[head] > b[head] ? 1 : 0 :
(a, b) => a[head] > b[head] ? -1 : b[head] > a[head] ? 1 : 0
}
case 'Number': {
return direction === 1 ?
(a, b) => Number(b[head]) - Number(a[head]) :
(a, b) => Number(a[head]) - Number(b[head])
}
}
}
}
}).mount('#app')
th {
cursor: pointer;
}
<script src="https://unpkg.com/vue#next"></script>
<div id="app">
<table>
<thead>
<tr>
<th v-for="head in headers" #click="sort(head)">
{{ head }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(data, i) in sortedProperties" :key="data.id">
<td v-for="(head, idx) in headers" :key="head.id">
{{ data[head] }}
</td>
</tr>
</tbody>
</table>
</div>
For any one else who is stuck this is how i solved the problem from https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_sort_table_desc:
//sort table columns
const sortTable = (n) =>{
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("myTable");
switching = true;
//Set the sorting direction to ascending:
dir = "asc";
/*Make a loop that will continue until
no switching has been done:*/
while (switching) {
//start by saying: no switching is done:
switching = false;
rows = table.rows;
/*Loop through all table rows (except the
first, which contains table headers):*/
for (i = 1; i < (rows.length - 1); i++) {
//start by saying there should be no switching:
shouldSwitch = false;
/*Get the two elements you want to compare,
one from current row and one from the next:*/
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
/*check if the two rows should switch place,
based on the direction, asc or desc:*/
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch= true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
/*If a switch has been marked, make the switch
and mark that a switch has been done:*/
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
//Each time a switch is done, increase this count by 1:
switchcount ++;
} else {
/*If no switching has been done AND the direction is "asc",
set the direction to "desc" and run the while loop again.*/
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}

Standard "check-all" functionality in table

Here's a part of my grid (CRUD) component:
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set(v) {
if(v) {
this.checkedRows = [];
for(var i in this.records) {
this.checkedRows.push(this.records[i]['id'])
}
}
else {
this.checkedRows = [];
}
}
}
}
};
</script>
As you can see, I would like to achive a standard, widely used functionality: The user can check multiple rows and do some operation with the selected rows. The problem is with the "check all" checkbox on the top of the table. When I check all, then I remove the tick from only one checkbox below, it unchecks all the checkboxes on page.
I understand why its happening: When I remove a tick from on of the checkboxes below, the "this.records.length == this.checkedRows.length" condition will no longer be true, so the "allChecked" computed variable will be set to false, therefore the top checkbox will set to unchecked. The problem is: when the top checkbox will be unchecked, then all of the checkboxes will be unchecked as well, because of the "set" part of the computed variable.
Is there a clean way to solve this problem in Vue?
I'm not sure what you want to do with the checked rows, but maybe this will be better:
<b-form-checkbox :value="record['id']" v-model="record.checked" />
Then add to your objects in records a checked property.
records: [
{
id: 1,
checked: false
},
...
]
and if you need a list of checked records you might do a computed property:
computed: {
checkedRecords() {
return this.records.filter(record => record.checked);
}
}
and for checking-unchecking all you just iterate over all records:
<b-form-checkbox #change="clickedAll" />
methods: {
clickedAll(value) {
this.records = this.records.map(record => {
record.checked = value
return record
}
}
}
OK, meanwhile I solved the problem. Here's my solution. Thanks #Eggon for your help, you gave the idea to use the #change method.
<template>
<table class="MyComponent table">
<thead>
<tr>
<th width="30px">
<b-form-checkbox v-model="allChecked" #change="checkAll" />
</th>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in records" :key="index">
<td width="30px">
<b-form-checkbox :value="record['id']" v-model="checkedRows" />
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: "MyComponent",
components: {
},
props: ['config'],
data() {
return {
records: [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}, {
id: 5
}, {
id: 6
}],
checkedRows: []
}
},
methods: {
checkAll(value) {
if(!value) {
this.checkedRows = [];
return ;
}
var newCheckedRows = [];
for(var i in this.records) {
newCheckedRows.push(this.records[i].id)
}
this.checkedRows = newCheckedRows;
}
},
computed: {
allChecked: {
get() {
return this.records.length == this.checkedRows.length
},
set() {
}
}
}
};
</script>

Change ordering of a array of objects with up/down buttons

I'm having the following issue:
I want in the frondend to get a list of items based on the key 'order':
<div id="app">
<ul>
<li v-for="item in sorted">
{{item.order}} {{item.title}}
<button #click="changeOrderDown(item)">down</button>
<button #click="changeOrderUp(item)">up</button>
</li>
</ul>
</div>
based on the JSON you see below. When you click the button I want to swap out for example order 1 -> 2 and then 2 becomes 1
items: [
{
title: "test",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
I keep getting a duplicate key error cause i first change the first key and then the second. i resolved it to update the whole object at ones. that seems to work but still it doesn't behave the correct way.
computed: {
sorted() {
function compare(a, b) {
let comparison = 0;
if (a.order > b.order) {
comparison = 1;
} else if (a.order < b.order) {
comparison = -1;
}
return comparison;
}
return this.items.sort(compare)
},
},
methods: {
changeOrderDown(currentItem) {
let temp = this.items
let old_value = parseFloat(currentItem.order)
let new_value = parseFloat(currentItem.order) + 1;
console.log(old_value, new_value)
temp.filter(o => o.order === old_value)[0].order = new_value;
temp.filter(o => o.order === new_value)[0].order = old_value;
this.items = temp;
},
changeOrderUp(currentItem) {
let temp = this.items
let old_value = parseFloat(currentItem.order)
let new_value = parseFloat(currentItem.order) - 1;
console.log(old_value, new_value)
temp.filter(o => o.order === old_value)[0].order = new_value;
temp.filter(o => o.order === new_value)[0].order = old_value;
this.items = temp;
},
}
I made a codepen down below with the code from above. this is kinda a working example but it doesn't feel right. Can someone give me a push in the right direction?
https://codepen.io/frank-derks/pen/BaQVOZV
Interesting challenge. Using my Vue 2 CLI sandbox app, I came up with functionality that doesn't require an 'order' property. Here is the component code:
<template>
<div class="swap-array-objects">
<h3>SwapArrayObjects.vue</h3>
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>TITLE</th>
<th> </th>
<th> </th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.title }}</td>
<td>
<button class="btn btn-secondary btn-sm" #click="moveUp(index)">Up</button>
</td>
<td>
<button class="btn btn-secondary btn-sm" #click="moveDown(index)">Down</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
title: 'title1'
},
{
title: 'title2'
},
{
title: 'title3'
},
{
title: 'title4'
},
{
title: 'title5'
}
]
}
},
methods: {
moveUp(index) {
if (index === 0) {
return;
}
let priorIndex = index - 1;
let itemCopy = {...this.items[index]};
let priorItemCopy = {...this.items[priorIndex]};
// Swap array position with prior element
this.$set(this.items, priorIndex, itemCopy);
this.$set(this.items, index, priorItemCopy);
},
moveDown(index) {
if (index === this.items.length - 1) {
return;
}
let subsequentIndex = index + 1;
let itemCopy = {...this.items[index]};
let subsequentItemCopy = {...this.items[subsequentIndex]};
// Swap array positions with subsequent element
this.$set(this.items, subsequentIndex, itemCopy);
this.$set(this.items, index, subsequentItemCopy);
}
}
}
</script>
This solution is similar to Tim's, but a bit simpler and easier to follow:
<template>
<v-app>
<ul>
<li v-for="(item, index) in items" :key="index">
{{item.order}} {{item.title}} {{index}}
<button #click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
<button #click="changeOrderUp(item, index)" v-if="index != 0">up</button>
</li>
</ul>
</v-app>
</template>
<script>
export default {
name: 'App',
data: () => ({
items: [
{
title: "test1",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
}),
methods: {
changeOrderDown(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index + 1]
// move clicked item to destination
this.items[index + 1] = temp
},
changeOrderUp(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index - 1]
// move clicked item to destination
this.items[index - 1] = temp
},
}
};
</script>
<template>
<v-app>
<ul>
<li v-for="(item, index) in items" :key="index">
{{item.order}} {{item.title}} {{index}}
<button #click="changeOrderDown(item, index)" v-if="index != items.length-1">down</button>
<button #click="changeOrderUp(item, index)" v-if="index != 0">up</button>
</li>
</ul>
</v-app>
</template>
<script>
export default {
name: 'App',
data: () => ({
items: [
{
title: "test1",
order: 1
},
{
title: "test2",
order: 2
},
{
title: "test3",
order: 3
}
]
}),
methods: {
changeOrderDown(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index + 1]
// move clicked item to destination
this.items[index + 1] = temp
},
changeOrderUp(item, index) {
// save clicked item in temporary variable
let temp = item
// move the following item to the clicked element
this.items[index] = this.items[index - 1]
// move clicked item to destination
this.items[index - 1] = temp
},
}
};
</script>

Change v-model value without changin the actual data

So i've this data
data: () => ({
products: [
{ id: 1, name: "Prod 1", price: 2, stock: 5 },
{ id: 2, name: "Prod 2", price: 3, stock: 6 }
]
})
Template
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#input="handleInputStock($event.target.value)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</tbody>
</table>
So what I want is that when the user hit delete/backspace from the stock input field the value cannot be empty (blank) or it must be greater than or equal to zero. but without changing the products.stock value. this is because I need the product.stock value to compare with the changed value (stock input field) before sending to the server. So if stock value is equal to product.stock don't send to server otherwise send and update stock value.
so here's what i've done so far.
prevent the stock value empty but not working
handleInputStock(value) {
return +value.replace(/[^0-9]/g, "");
},
update stock
updateStock(stock, productId) {
const productStock = this.products.find(product => product.id == productId).stock;
if (!(stock == productStock)) {
// do ajax
}
},
onlyNumber
onlyNumber(e) {
const charCode = e.which ? e.which : event.keyCode;
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
e.preventDefault();
}
},
Personally this feels like a higher level question to which your flow of product editing needs tweaking. Here is what I can think of:
User enters all the information.
User hits submit button.
Check whether of not the stock count is empty or 0.
Return an error message if it is.
Submit and update otherwise.
It might be worth looking into vuelidate that handles such validation in JavaScript. Meanwhile, we are also coming up with a tool called CRUDS DS (a WIP) that handles such situation with ease.
The best way is to create a ProductComponent and watch every product separately inside its own component, as shown below:
Product.vue
<ProductComponent
v-for="product in products"
:product="product"
:key="product.id" />
ProductComponent.vue
<template>
<tr>
<td>{{ product.name }}</td>
<td>
<input
type="text"
class="form-control"
v-model="product.price"
#paste.prevent
/>
</td>
<td>
<input
type="text"
class="form-control"
maxlength="999"
v-model="product.stock"
#paste.prevent
#keypress="onlyNumber($event)"
#blur="updateStock($event.target.value, product.id)"
/>
</td>
</tr>
</template>
<script>
export default {
props: {
product: {
type: Object,
default: {},
},
},
data: () => ({ actual_stock: "" })
// this is for handle stock cannot be empty or GTE:0
// also you dont need handleInputStock anymore
watch: {
product: {
handler(val) {
this.actual_stock = val.stock;
},
immediate: true,
},
"product.stock": function (newVal, oldVal) {
this.product.stock = +newVal;
},
},
methods: {
updateStock(stock, productId) {
if (!(stock == this.actual_stock)) {
// do ajax
}
}
}
}
</script>
If you want to handle it on parent side, you may use $emit to send an event upwards.
Can we have two versions of products? One for the server, one for v-models.
var server_products = [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
//...
data: () => ({
products = server_products
})
updateStock(stock, productId) {
server_products.forEach((product) => {
if(product.id === productId && stock !== product.stock){
product.stock = stock
// do ajax
}
})
},
//...
If not than you can use vue's watch property so vue finds changes to the array for you.
//...
data: () => ({
products: [
{ id: 1, name: "Prod 1", stock: 5 },
{ id: 2, name: "Prod 2", stock: 6 }
]
}),
watch: {
'products': {
handler: function(newValue) {
// do ajax
},
deep: true
}
}
//...

Datatables plugin for jQuery. How to hide rows? Export to Excel still showing hidden rows

I was able to figure out how to hide the rows I wanted using JQuery. I have a modal that pops out for the user deselects the checkbox on what rows they want to hide. It works perfectly but when I export it to excel it still displays the hidden rows. I am not using the API at all and I think that's my problem. I am hiding the rows by using .show() and .hide(). I am using the API to hide the columns and when I click my export to excel button it works just fine. Could anyone help me figure out how to hide rows and be able to export it to excel with the rows not showing on the spreadsheet?
HTML
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.2.4/js/dataTables.buttons.min.js">
</script>
<script type="text/javascript" language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js">
</script>
<script type="text/javascript" language="javascript" src="https://cdn.datatables.net/buttons/1.2.4/js/buttons.html5.min.js">
</script>
<table cellpadding="0" cellspacing="0" border="0" class="dataTable" class="example">
<thead>
<tr>
<th>Item</th>
<th>Store 1</th>
<th>Store 2</th>
<th>Store 3</th>
<th>Store 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sugar</td>
<td>21</td>
<td>95</td>
<td>4</td>
<td>5</td>
</tr>
<tr>
<td>Coffee</td>
<td>5</td>
<td>41</td>
<td>5</td>
<td>1</td>
</tr>
<tr>
<td>Banana</td>
<td>123</td>
<td>323</td>
<td>22</td>
<td>233</td>
</tr>
<tr>
<td>Gum</td>
<td>13</td>
<td>213</td>
<td>2</td>
<td>33</td>
</tr>
<tr>
<td>Milk</td>
<td>23</td>
<td>24</td>
<td>44</td>
<td>242</td>
</tr>
</tbody>
</table>
</div>
</body>
JavaScript
var table = $('.example').DataTable({
dom: 'Bfrtip',
buttons: [
{
extend: 'excelHtml5',
exportOptions: {
columns: ':visible'
},
text: 'Export to Excel'
},
],
});
// I get the attribute name from the checkbox and I match it with the id on the row and when the checkbox is unchecked I hide the rows.
$('input.rowsHideSeek').click(function() {
var name = $(this).attr('name');
if ($(this).is(':checked')) {
$('#' + name).show()
} else {
$('#' + name).hide()
}
});
This is what I came up with and it seems to work. It shows and hides rows and, on export, only shows rows not hidden.
here is it is on jsbin http://jsbin.com/qituvu/edit?output
The key part is that it uses the customizeData in the extended excelHtml5.
$(document).ready(function () {
// this just creates a critera to filter on
var faketype = 1;
for (var i = 0; i < dataStore.data1.length; ++i) {
dataStore.data1[i].type = faketype++;
if (faketype == 5) faketype = 1;
}
var aTable = $('#example').DataTable({
"data": dataStore.data1,
paging:false,
"columns": [
{ "data": "name" },
{ "data": "position" },
{ "data": "office", className: "editColumn" },
{ "data": "extn" },
{ "data": "start_date" },
{ "data": "salary" },
{ "data": "type"}
], dom: 'Bfrti',
"createdRow": function( row, data, dataIndex ) {
$(row).addClass('type_' + data.type);
},
buttons: [{text:"Excel",
extend: 'excelHtml5', customizeData: function (exData) {
var rowNodes = aTable.rows().nodes();
var newData = []
for (var i = (exData.body.length - 1) ; i >= 0; i--) {
if ($(rowNodes[i]).css("display") == "none") {
continue;
}
newData[newData.length] = exData.body[i];
}
// the loop reverses order so put it back
exData.body = newData.reverse();
}}]
});
// event handler for hiding and showing rows
$("input[name='hideType']").on("click", function () {
var c = $(this).val();
var isChecked = $(this).prop('checked');
$(aTable.rows('.type_' + c).nodes()).toggle(isChecked);
aTable.rows().invalidate().draw();
});
});
The problem with jQuery hide() / show() is that you will need to tweak any part of dataTables you are using, if you want to fake persistent "hidden" rows.
Here is a few plug-in methods that provide real hide() and restore() on rows in dataTables :
$.fn.dataTableExt.hiddenRows = {};
$.fn.dataTable.Api.register( 'row.hide()', function(selector) {
var row = this.row(selector);
if (row.length == 0) return -1;
var index = row.node()._DT_RowIndex;
$.fn.dataTableExt.hiddenRows[index] = row.node().outerHTML;
row.remove().draw(false);
return index;
});
$.fn.dataTable.Api.register( 'row.restore()', function(index) {
var row = $.fn.dataTableExt.hiddenRows[index];
if (row) {
this.row.add( $(row) ).draw(false);
delete $.fn.dataTableExt.hiddenRows[index];
return true;
}
return false;
});
Now you have table.row.hide() and table.row.restore(). The methods use the dataTables API, and does nothing but maintaining a list of removed rows by their unique indexes. A demo could look like this :
var table = $('#example').DataTable({}) ;
$('#example').on('click', 'tbody tr', function() {
var index = table.row.hide(this);
var $button = $('<button></button>')
.addClass('restore')
.attr('index', index)
.text('show #'+index);
$('body').prepend($button);
})
$('body').on('click', '.restore', function() {
var index = $(this).attr('index');
table.row.restore(index);
$(this).remove();
})
When you click on a row it is hidden (i.e removed); a restore button is generated for each hidden row. If you click on the button the corresponding row is restored.
Demo -> http://jsfiddle.net/s183ek2q/