Date range inside loop of multiple datatable in the same page - datatables

I came from the issue [https://datatables.net/forums/discussion/51949/looping-multiple-datatables-on-the-same-page#latest] and found an issue that comes from filtering of dates: if I filter and on change of this date range, it triggers table.draw() for the first one if it is inside of the loop of multiple datatable in the same page
My intention is to have the data range to work on each datatable individually
Here is a sample of what I already done
http://live.datatables.net/magokusa/4/edit
HTML
<div class="container">
<h3>Table 1</h3>
<div class="input-group input-group-sm">
<input type="date" id="dateFromupper" placeholder="Date from" value="2017-04-10">
<div>
<div>to</div>
</div>
<input type="date" id="dateToupper" placeholder="Date to" value="2018-09-10">
</div>
<table id="upper" data-action="https://demo.wp-api.org/wp-json/wp/v2/posts?per_page=5" class="display nowrap datatable" width="100%">
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
</tr>
</thead>
</table>
<hr>
<h3>Table 2</h3>
<div class="input-group input-group-sm">
<input type="date" id="dateFromlower" placeholder="Date from" value="2016-04-10">
<div>
<div>to</div>
</div>
<input type="date" id="dateTolower" placeholder="Date to" value="2018-09-12">
</div>
<table id="lower" data-action="https://css-tricks.com/wp-json/wp/v2/posts?per_page=5" class="display nowrap datatable" width="100%">
<thead>
<tr>
<th>col1</th>
<th>col2</th>
<th>col3</th>
</tr>
</thead>
</table>
</div>
JS
$(document).ready( function () {
$.each($('.datatable'), function () {
var dt_id = $(this).attr('id');
var dt_action = $(this).data('action');
$.fn.dataTable.ext.search.push(
function (settings, data, dataIndex) {
var min = $("#dateFrom" + dt_id).val();
var max = $("#dateTo" + dt_id).val();
if (min != null && max != null) {
min = new Date(min);
max = new Date(max);
var startDate = new Date(data[1]);
if (min == null && max == null) {
return true;
}
if (min == null && startDate <= max) {
return true;
}
if (max == null && startDate >= min) {
return true;
}
if (startDate <= max && startDate >= min) {
return true;
}
} else {
return true;
}
}
);
$("#dateFrom" + dt_id + ", #dateTo" + dt_id).change(function () {
table.draw();
});
if (dt_action != null) {
var dt_ajax = dt_action;
var table = $('#' + dt_id).DataTable({
ajax: {
"url": dt_ajax,
"dataSrc": ""
},
columns: [
{ "data": "status" },
{ "data": "date" },
{ "data": "date_gmt" },
]
});
} else {
var table = $('.datatable').DataTable({
retrieve: true,
responsive: true,
});
}
});
});

Since you already are declaring two different filters, you can just check if the current draw process is related to the filter itself :
$.fn.dataTable.ext.search.push(
function (settings, data, dataIndex) {
if (settings.sInstance != dt_id) return true
...
}
)

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;
}
}
}
}

Form collection validation with dates and string - Vuelidate

I am trying to validate series of dates with something like this.
const data = [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
... more values
];
// Elements inside data can be added or removed but will have at least one.
Here data[1][begin] should be more than or equal to data[0][begin] and data[1][place] should not equal to data[0][place]. Is there anyway to achieve this. Documentation talks about dynamic validation but I am not sure how I can achieve this with collection.
You can consider implementing a custom validation in the form submit event listener.
This can be achieved by looping through your array of objects and compare items in pairs.
HTML
<form
id="app"
#submit="checkForm"
action="/someurl"
method="post"
>
<table border="1">
<tr v-for="(item,index) in dates" :key="index">
<td>
{{index}}
</td>
<td>
{{formatDate(item.begin)}}
</td>
<td>
{{item.place}}
</td>
</tr>
</table>
<input type="date" v-model="dateEntry"/>
<input type="text" v-model="placeEntry"/>
<button type="button" #click="addEntry">Add</button>
<p>
<br>
<input
type="submit"
value="Submit"
>
</p>
<p v-for="error in errorList">
{{error}}
</p>
</form>
JS
new Vue({
el: "#app",
data: {
errorList: [],
dateEntry: null,
placeEntry: null,
dates: [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
]
},
methods: {
addEntry: function(){
if(this.dateEntry == null || this.dateEntry == "")
return false;
if(this.placeEntry == "")
return false;
this.dates.push({
begin: new Date(this.dateEntry),
place: this.placeEntry
});
this.dateEntry = null;
this.placeEntry= "";
},
checkForm: function(e){
var isValid = true;
var index = 0;
var nextIndex = 1;
this.errorList = [];
while(nextIndex < this.dates.length){
if(nextIndex < this.dates.length){
var isValidDate = this.validDate(this.dates[nextIndex].begin,this.dates[index].begin);
var isValidPlace = this.validPlace(this.dates[nextIndex].place,this.dates[index].place);
if(!isValidDate){
this.errorList.push("Invalid date on index " + nextIndex);
}
if(!isValidPlace){
this.errorList.push("Invalid place on index " + nextIndex);
}
}
index++;
nextIndex++;
}
if(!this.errorList.length){
this.errorList.push("All dates are valid");
return true;
}
e.preventDefault();
},
formatDate: function(date){
return date.toDateString();
},
validPlace: function(curPlace, prevPlace){
return curPlace != prevPlace;
},
validDate: function(curDate,prevDate){
try{
return curDate.getTime() >= prevDate.getTime();
}catch(e){
return false;
}
}
}
})
Check out this JS Fiddle that I created to illustrate my suggestion.
On the other hand, if you are building the array during runtime, then you can apply the validation before it gets added into the array.

Vuejs2- How to call a filter function from a method

I am using "moneyFormat" filter for formatting the currency value. It's formatting the values which is defined already. I want to format the dynamic values. Hence I have called the filter function through a method called "displayValue", but I am getting error
and the given input field also not updated.
Here is my code :
<template>
<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 name="" id="" class="form-control" v-model="row.billChgDesc">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
</td>
<td>
<input #input="displayValue" class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index">
<span v-show="vErrors.has('charges' + index)" class="is-danger">{{ vErrors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges | moneyFormat" 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">DELIVERY</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>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
filters:{
moneyFormat: function (val){
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
},
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
}
},
computed: {
total: function () {
var t = 0;
$.each(this.rows, function (i, e) {
t += accounting.unformat(e.total, ",");
});
return t;
},
taxtotal: function () {
var tt = 0;
$.each(this.rows, function (i, e) {
tt += accounting.unformat(e.tax_amount, ",");
});
return tt;
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
displayValue:function (e) {
var value = e.target.value
var a = this.filters.moneyFormat(value);
return a;
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
You could use:
this.$options.filters.moneyFormat(value)
Check: https://v2.vuejs.org/v2/api/#vm-options
For global filters, first set:
Vue.prototype.$filters = Vue.options.filters
And then:
this.$filters.foo
Edit:
Looking closer your code, you are not using the filter as a Vue filter and only calling from one point (a method) instead of calling inline from HTML, maybe it's better that the method itself returns the value of the input, like:
displayValue: function (e) {
var val = e.target.value
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
Did it work? Or the same error is shown? If yes, can you paste the error?
Hope it helps!
As it's been said, if you want to use the filter, you need to do this.$options.filters.moneyFormat(value)
What you're trying to achieve it's rendered the moneyFormat inside an input and the value displayed is the v-model. It's this one you have to format.
So you can initialize a new data property filled with each row.charges formatted on mounted:
data: function () {
return {
rows: [
//...
],
currentCharges: []
}
},
mounted() {
this.rows.forEach(row => {
let formattedCharges = this.$options.filters.moneyFormat(row.charges)
this.currentCharges.push(formattedCharges)
})
},
and use this data to fulfill your inputs:
<tr v-for="(row, index) in rows">
<td>
<input v-model="currentCharges[index]" #input="displayValue($event, index)">
<td>
To keep the current row.charges updated, reassign it when the v-model updates:
methods: {
displayValue:function (e, index) {
// the target value is a string like this " ₹ 55.20"
// split it and convert the last element to Float type
let arrValue = e.target.value.split(" ")
let parseValue = parseFloat(arrValue[arrValue.length -1])
// reassign the row.charges with the new float value
this.rows[index].charges = parseValue
}
},

cant get multiple methods in vuejs instance to work

I have two v-on:click events attached to html elements. the one calling method1 works but the other one doesnt work. i cant imagine what the issue is. i have no errors in the console
heres the the entire html page.
<div class="col-md-10" id="deckBuilder">
<button class="ClassTabs" id="classCardsTab">"#ViewData["ClassChoice"]"</button>
<button class="ClassTabs" id="neutralCardsTab">Neutral</button>
<div class="well col-md-9" id="classCards">
#foreach (var card in Model.ClassCards)
{
<img v-on:click="addCard" class="card" id="#card.CardID;#card.Name" style="width:200px;height:260px;" src="#Url.Content(card.Image)" alt="#card.Name" />
}
</div>
<div class="well col-md-3" id="tableWrapper">
<table id="deckTable">
<tr>
<th colspan="3" style="font-size:24px;"><input style="text-align:center;" placeholder="My #ViewData["ClassChoice"] Deck" v-model="deckName" /></th>
</tr>
<tr>
<th style="text-align:center;font-size:20px;">Name</th>
<th style="text-align:center;font-size:20px;">Count</th>
<th></th>
</tr>
</table>
</div>
<div class="well col-md-9" id="neutralCards">
#foreach (var item in Model.NeutralCards)
{
<img v-on:click="addCard" class="card" id="#item.CardID;#item.Name" style="width:200px;height:260px;" src="#Url.Content(item.Image)" alt="#item.Name" />
}
</div>
</div>
#section Scripts {
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script>
var deckBuilder = new Vue({
el: '#deckBuilder',
data: {
deckList: [],
deckCards: 0,
deckName: ''
},
methods: {
addCard: function(event) {
var count = 0;
var foundCard = false;
var cardInfo = event.path[0].id.split(';');
var cardId = cardInfo[0];
var cardName = cardInfo[1];
var deckTable = document.getElementById('deckTable');
var row;
for (var i = 0; i < this.deckList.length; i++) {
if (this.deckList[i].id === cardId && this.deckList[i].count < 3 && this.deckCards < 30) {
this.deckList[i].count++;
foundCard = true;
this.deckCards++;
for (var x = 0; x < deckTable.rows.length; x++) {
if (deckTable.rows[x].id === cardId) {
deckTable.rows[x].cells[1].innerHTML = this.deckList[i].count;
break;
}
}
break;
} else if (this.deckList[i].id === cardId && this.deckList[i].count === 3 && this.deckCards < 30) {
alert('Deck limit reached for this card.');
foundCard = true;
break;
}
}
if ((this.deckList.length === 0 || !foundCard) && this.deckCards < 30) {
this.deckList.push({ id: cardId, count: 1 });
this.deckCards++;
row = deckTable.insertRow(-1);
row.insertCell(0).innerHTML = '<a class="cardLink" href="#Url.Action("Details", "Cards")/' + cardId + '" >' + cardName + '</a>';
row.insertCell(1).innerHTML = 1;
row.insertCell(2).innerHTML = '<button v-on:click="removeCard">X</button>';
row.id = cardId;
}
console.log(this.deckCards);
},
removeCard: function (event) {
console.log("remove card");
}
}
})
</script>
}
You could try writing it like this:
var vueInstanct = new Vue({
el: "#myVueInstance",
methods: {
method1() {
console.log('method1 hit');
},
method2() {
console.log('method2 hit');
}
}
})
But there doesn't seem to be anything wrong with your code... maybe post the html elements these methods are attached to? Could be something there.

paging using knockout js

At first I have displlay data using knockout js successful,here is my code:
Js
var viewMode = {
lookupCollection: ko.observableArray()
};
$(document).ready(function () {
$.ajax({
type: "GET",
url: "/Home/GetIndex",
}).done(function (data) {
$(data).each(function (index, element) {
viewModel.lookupCollection.push(element);
});
ko.applyBindings(viewMode);
}).error(function (ex) {
alert("Error");
});
});
View:
<table class="paginated">
<tr>
<th>
Name
</th>
<th>
Price
</th>
<th>
Category
</th>
<th></th>
</tr>
<tbody data-bind="foreach: lookupCollection">
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: price"></td>
<td data-bind="text: Category"></td>
<td>
<button class="btn btn-success">Edit</button>
<button class="btn btn-danger">Delete</button>
</td>
</tr>
</tbody>
</table>
However, I need more code to paging the list items, I follow this site http://knockoutjs.com/examples/grid.html and replay my code but It has not display my list items:
JS:
var initialData = {
lookupCollection: ko.observableArray()
};
var PagedGridModel = function (items) {
this.items = ko.observableArray(items);
this.sortByName = function () {
this.items.sort(function (a, b) {
return a.name < b.name ? -1 : 1;
});
};
this.jumpToFirstPage = function () {
this.gridViewModel.currentPageIndex(0);
};
this.gridViewModel = new ko.simpleGrid.viewModel({
data: this.items,
columns: [
{ headerText: "Name", rowText: "Name" },
{ headerText: "Category", rowText: "Category" },
{ headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
],
pageSize: 4
});
};
$(document).ready(function () {
$.ajax({
type: "GET",
url: "/Home/GetIndex",
}).done(function (data) {
$(data).each(function (index, element) {
viewModel.lookupCollection.push(element);
});
ko.applyBindings(new PagedGridModel(initialData));
}).error(function (ex) {
alert("Error");
});
});
View:
<div data-bind='simpleGrid: gridViewModel'> </div>
<button data-bind='click: sortByName'>
Sort by name
</button>
<button data-bind='click: jumpToFirstPage, enable: gridViewModel.currentPageIndex'>
Jump to first page
</button>
thankyou very much your answer:
The "simpleGrid" binding u try to use is a custom one, not available by default in knockout.
Here's a simple example for pagination using a computed observable :
Fiddle : http://jsfiddle.net/RapTorS/qLHwx/
var viewModel = function () {
var self = this;
self.pageSize = 4;
self.currentPage = ko.observable(1);
self.lookupCollection = ko.observableArray([]);
self.currentCollection = ko.computed(function () {
var startIndex = self.pageSize * (self.currentPage() - 1);
var endIndex = startIndex + self.pageSize;
return self.lookupCollection().slice(startIndex, endIndex);
});
};