Ajax Reload on Vue.js DataTables - vue.js

ajax.reload() and/or ajax.url().load() is just not working for Vue3 DataTable integration. It works if you include the CDN and follow the jQuery approach to it.
If you follow the instructions on the DataTables website on how to integrate DataTables into Vue3, that feature just doesn't seem to work.
https://datatables.net/blog/2022-06-22-vue
For example:
<template>
<div class="p-6">
<button #click="changeIt">
Change
</button>
<DataTable
class="display"
id="datatable"
:columns="columns"
:ajax="{
url: 'api/users',
}"
ref="table"
:options="{
select: true,
serverSide: true,
}"
>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Action</th>
</tr>
</thead>
<tbody>
</tbody>
</DataTable>
</div>
</template>
<script>
import DataTable from 'datatables.net-vue3'
import DataTablesLib from 'datatables.net';
DataTable.use(DataTablesLib);
export default {
name: 'DataTableComponent',
components: {DataTable},
data(){
return {
columns: [
{"data": "id"},
{"data": "name"},
{"data": "email"},
{"data": "action", "sortable": false},
],
}
},
methods: {
changeIt() {
$('#datatable').DataTable().ajax.url('users/user').load();
}
},
}
</script>
<style>
#import 'datatables.net-dt';
</style>
For a simple example, if you click the button, there would be a new Ajax request to somewhere else. This does not currently work.
The error that I get is:
runtime-core.esm-bundler.js:218 Uncaught TypeError: $(...).DataTable is not a function
at Proxy.changeIt (DataTableComponent.vue:55:29)
at _createElementVNode.onClick._cache.<computed>._cache.<computed> (DataTableComponent.vue:3:25)
at callWithErrorHandling (runtime-core.esm-bundler.js:155:22)
at callWithAsyncErrorHandling (runtime-core.esm-bundler.js:164:21)
at HTMLButtonElement.invoker (runtime-dom.esm-bundler.js:369:13)
I've tried every combination, and it doesn't seem to work. For example:
$('#datatable').DataTable().ajax.url('users/user').load();
$('#datatable').DataTable.ajax.url('users/user').load();
$('#datatable').ajax.url('users/user').load();
DataTable().ajax.url('users/user').load();
DataTable.ajax.url('users/user').load();
Any ideas?
EDIT
I did find that DataTables provides a dt() function, but it's still not working. It recommends the following:
DataTables API
Update and delete are very similar in that we just manipulate the array of data, but we will need to access the DataTables API to know which rows have been selected by the end user. The DataTable component provides a dt() method that we can use to get this via a Vue reference:
let dt;
const table = ref(); // This variable is used in the `ref` attribute for the component
onMounted(function () {
dt = table.value.dt();
});
So the modified code looks like this, but still doesn't work.
<template>
<div>
<h1>Simple table</h1>
<button #click="changeIt">Change It</button>
<DataTable
class="display"
id="datatable"
:columns="columns"
ajax="api/users"
ref="table"
:options="{
select: true,
serverSide: true,
}"
>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Action</th>
</tr>
</thead>
<tbody>
</tbody>
</DataTable>
</div>
</template>
<script>
import DataTable from 'datatables.net-vue3';
import DataTablesLib from 'datatables.net';
import 'datatables.net-select';
import {onMounted, ref} from "vue";
DataTable.use(DataTablesLib);
export default {
name: "DataTableComponent",
components: {DataTable},
data(){
return {
columns: [
{"data": "id"},
{"data": "name"},
{"data": "email"},
{"data": "action", "sortable": false},
]
}
},
setup() {
let dt;
const table = ref();
onMounted(() => {
dt = table.value.dt();
});
return {
dt,
}
}
}
</script>
<style>
#import 'datatables.net-dt';
</style>
Code Sandbox: https://codesandbox.io/s/serverless-pond-4mu4zw?file=/src/App.vue
EDIT #2:
And I got it to work like this, if I use the <script setup> tag. Still can't get it to work in the export default.
https://codesandbox.io/s/festive-forest-1ojspm?file=/src/App.vue:514-719
EDIT #3 (SOLUTION):
Solved with the help of #Aleksandr Savkin. I had to use this.$refs in mounted() and not ref() as the docs suggested.
<template>
<div>
<h1>Simple table</h1>
<button #click="changeIt">Change It</button>
<DataTable
class="display"
id="datatable"
:columns="columns"
ajax="api/users"
ref="table"
:options="{
select: true,
serverSide: true,
}"
>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Action</th>
</tr>
</thead>
<tbody>
</tbody>
</DataTable>
</div>
</template>
<script>
import DataTable from 'datatables.net-vue3';
import DataTablesLib from 'datatables.net';
import 'datatables.net-select';
DataTable.use(DataTablesLib);
export default {
name: "DataTableComponent",
components: {DataTable},
data(){
return {
columns: [
{"data": "id"},
{"data": "name"},
{"data": "email"},
{"data": "action", "sortable": false},
],
dt: null,
}
},
mounted() {
this.dt = this.$refs.table.dt();
},
methods: {
changeIt() {
this.dt.ajax.url('api/users/user').load();
}
}
}
</script>
<style>
#import 'datatables.net-dt';
</style>

Maybe it'll help you!
You tried to get a table element using Jquery, but it's not Vue's way to get a component.
I noticed you use the Vue's ref attribute for the DataTable component, so use that to get the component, like this-
methods: {
changeIt() {
const componentTable = this.$refs.table;
componentTable.ajax.url('users/user').load();
}
},
Also, see this- Template refs documentation

DataTables requires jQuery. Don't select either version if you already have it.
So, verify if you are-
Failing to contain the DataTables library.
Loading the DataTables library before the jQuery library.
Loading the jQuery library double.
Selecting the wrong route to the jQuery files.
For instance, in your HTML file at the head section, verify those-
<!-- Load CSS file for DataTables -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/css/jquery.dataTables.min.css"
integrity="sha512-1k7mWiTNoyx2XtmI96o+hdjP8nn0f3Z2N4oF/9ZZRgijyV4omsKOXEnqL1gKQNPy2MTSP9rIEWGcH/CInulptA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<!-- load jQuery -->
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"
></script>
<!-- load DataTables -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/js/jquery.dataTables.min.js"
integrity="sha512-BkpSL20WETFylMrcirBahHfSnY++H2O1W+UnEEO4yNIl+jI2+zowyoGJpbtk6bx97fBXf++WJHSSK2MV4ghPcg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
For more information, refer this documentation- https://bobbyhadz.com/blog/jquery-datatable-is-not-a-function

Related

Uncaught (in promise) File 'Roboto-Regular.ttf' not found in virtual file system DataTable - vuejs

I am working with vuejs - DataTable and when I want to generate my report of the tables in pdf I get an error.
while if I generate an excel report it works correctly
my form
<table id="example1" class="table table-bordered table-striped responsive">
<thead>
<tr>
<th>Id_categoria</th>
<th>Categoria</th>
<th>Fecha(s)</th>
<!--<th>Botones(s)</th>-->
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th>Id_categoria</th>
<th>Categoria</th>
<th>Fecha(s)</th>
</tr>
</tfoot>
</table>
my script
<script>
import axios from "axios";
import $ from 'jquery';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
export default {
mounted() {
this.getUsers();
},
methods: {
getUsers() {
axios
.get("https://sistema-control-inventario.herokuapp.com/categoria/")
.then((response) => {
var table = $("#example1").DataTable({
"responsive": true, "lengthChange": true, "autoWidth": false,
"buttons": ["copy", "csv", "excel", "pdf", "print", "colvis"],
"language": "",
data: response.data,
columns: [
{ data: "id_categoria" },
{ data: "categoria" },
{ data: "fecha" },
// { defaultContent : '<button type="button" class="editar btn btn-primary"><i class = "fa fa-pencil-alt"></i></button> <button type="button" class="eliminar btn btn-danger" data-toggle="modal" data-target="#modalEliminar"><i class="fas fa-dumpster-fire"></i></button>'}
],
}).buttons().container().appendTo('#example1_wrapper .col-md-6:eq(0)')
//obtener_data_editar('',table);
}).catch((error) => console.log(error.response));
},
},
};
var obtener_data_editar = function(tbody,table){
$(tbody).on("click", "button.editar", function(){
var data = table.row($(this).parent('tr')).data();
console.log(data)
})
}
</script>
the page looks something like this
I would be grateful if you could help me to generate the pdf with this button and with the data that are in this one.
Thank you very much.
pd: i am using vue 3 cli
Maybe you can try changing the pdfmake imports to be like this:
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.vfs = pdfFonts.pdfMake.vfs;
Note the third line which is (probably) the most important one.
This is straight from the library's documentation.
Aside from that, I strongly encourage you to reconsider using jQuery here. You'll do much better with Vue.js and vanilla JS alone, especially since Vue uses a virtual DOM so manipulating the actual DOM directly doesn't make any sense.

Vue 2 + TinyMCE Single Page Confusion

I write quite a few single page vue 2 files but have never tried using a "component" before. Can someone help spot the problem with my code? The error I'm getting is "Editor is not defined". Every example out there has you importing the vue module but I'm not using a builder so I thought just including the script(s) would work. I have removed a lot of extraneous code to make it simpler to read (I hope).
<script src="https://cdn.jsdelivr.net/npm/vue#2.X/dist/vue.js"></script>
...
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
<script src="https://cdn.jsdelivr.net/npm/tinymce-vue/dist/tinymce-vue.min.js"></script>
<div id="vue_app">
...
<div v-show="showtable">
...
<table class="tbdesign">
<tr>
<th>ID</th>
<th>Name</th>
...
<th>Func</th>
</tr>
<tr v-for='row in filteredRows' :key="row.property_id">
<td :id="row.property_id">{{row.property_id}}</td>
<td>{{ row.name }}</td>
...
<td><div v-on:click="editRow(row.property_id)" href="#">Edit</div>
</td>
</tr>
</table>
</div>
</section>
<section v-if="showeditor">
<div >
...
<form>
<div>
<div>
<label for="name">Name:</label> <input class="detail_update id="name" type="text" v-model="editrow.name" />
</div>
...
<div class="form-group col">
Description:<br>
<editor
apiKey="no-api-key"
v-model="editrow.description"
:init="{
height: 500,
menubar: true,
plugins: [
'advlist autolink lists link image charmap',
'searchreplace visualblocks code fullscreen',
'print preview anchor insertdatetime media',
'paste code help wordcount table'
],
toolbar:
'undo redo | formatselect | bold italic | \
alignleft aligncenter alignright | \
bullist numlist outdent indent | help'
}"
>
</editor>
</div>
<div class="form-group col">
<button v-on:click="submitData" type="button">Save</button>
</div>
</div>
</form>
</div>
...
</div>
<script type="module">
var app = new Vue({
el: '#vue_app',
data() {
return {
rows: [],
row: [],
...
editrow: [],
...
errors: []
}
},
components: {
'editor': Editor
},
mounted() {
this.init();
},
computed: {
...
},
methods: {
init() {
this.loading = true;
axios.get('/dap/api/?/functions/get_properties/')
.then(response => {
this.rows = response.data;
console.log(response.data);
this.showtable = true;
})
.catch(function(error) {
this.errored = true;
alert(error);
})
.finally(() => this.loading = false)
},
...
checkData() {
...
},
submitData() {
...
},
editRow(rowID) {
for (var i = 0; i < this.rows.length; i++) {
if (this.rows[i]['property_id'] == rowID) {
this.editrow = this.rows[i];
this.showeditor = true;
this.showtable = false;
break;
}
}
}
}
});
</script>
Editor is not actually defined anywhere in your code, and <script type="module"> uses strict mode, requiring all referenced variables to be declared upfront. Since the Editor variable doesn't exist, the script immediately fails with the error you observed. However, it doesn't look like you actually need <script type="module"> here, so you could just use a regular <script>.
Every example out there has you importing the vue module but I'm not using a builder so I thought just including the script(s) would work.
The examples that import .vue files use a build system to automatically compile the imports with vue-loader. In this case you're using a pre-compiled script from CDN, so no loader is needed, but you do need to reference the correct symbol that the tinymce-vue script defines.
The tinymce-vue script sets its exports on window.TinymceVue. The pre-built Editor.vue component happens to be exported as the same name as the root export: window.TinymceVue.TinymceVue.
So you should locally register tinymce-vue's Editor component as:
<script>
new Vue({
components: {
editor: window.TinymceVue.TinymceVue,
}
})
</script>
demo

Load More Data On Scroll With Vue And Vuex

I would like to ask how can I display more data by using Vue and vuex. all data stored in vuex-store management already. From State management now I want to load more data on scrolling.
I found online solution by ajax. but I need to loading form state management (Vuex).
This is my Vue template:
<template>
<div>
<div class="panel panel-default">
<div class="panel-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<tr>
<th>Name - Number of Products: <span style="color: red"> {{products}} </span></th>
<th width="100"> </th>
</tr>
</tr>
</thead>
<tbody v-if="isLoaded">
<tr v-for="company, index in companies">
<td>{{ company.name }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return { }
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
products(){
return this.$store.getters['exa1Company/countProducts'];
}
},
mounted() {
this.$store.dispatch('exa1Company/indexResource');
}
}
</script>
My vuex store file is partial for simplicity
export const getters = {
countProducts(state) {
return state.list.data.length;
},
getProducts(state) {
return state.list.data;
},
getTodoById: (state) => (id) => {
return state.list.data.find(tod => tod.id === id)
}
};
export default {
namespaced: true,
state: customerState,
getters,
actions,
mutations,
};
something like this should work. use companiesLoaded in the template, and increase page when scrolled to bottom. I hope this helps.
data: function () {
return {
page: 1,
perPage: 20
}
},
computed: {
companies(){
return this.$store.getters['exa1Company/getProducts'];
},
companiesLoaded(){
return this.companies.slice(0, this.page * this.perPage)
},
...

Text only showing up in td element and not HTML

I have a table td element that only shows this and I don't need it to appear like that but only as a blank space.
new Vue({
el: '#app2',
beforeCreate: function() {
},
created: function() {
},
mounted: function() {},
data: {
itemPriceList: [],
orderItems: [{
globalGroupName: "-- ABC"
},
{
globalGroupName: "-- CDE"
},
{
globalGroupName: "--- FGH"
},
{
globalGroupName: "- IJK"
},
{
globalGroupName: "-- LMN"
}
],
priceList: null
},
methods: {
GetOrderItems: function() {
},
ReplaceDashesInGlobalGroups: function(globalGroupName) {
if (globalGroupName[0] === "-") {
// Remove leading dashes and replace with a blank space
console.log("ReplaceDashesInGlobalGroups");
return globalGroupName.replace(/-(?![a-zA-Z])|-(?=\s|-)/g, ' ');
}
return globalGroupName;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<div id="app2">
<table class="table table-striped table-bordered table-hover" style="width:350px;">
<thead class="thead-dark">
<tr>
<th width="235px"><label>Global Name</label></th>
</tr>
</thead>
<tbody>
<template v-for="global in orderItems">
<tr>
<td>{{ ReplaceDashesInGlobalGroups(global.globalGroupName) }}</td>
</tr>
</template>
</tbody>
</table>
</div>
Try this:
<td v-html="ReplaceDashesInGlobalGroups(global.globalGroupName)"></td>
This should be rendered as HTML now instead of just plain text. Note this can open you up to XSS attacks.
Source: Documentation
Updates the element’s innerHTML. Note that the contents are inserted
as plain HTML - they will not be compiled as Vue templates. If you
find yourself trying to compose templates using v-html, try to rethink
the solution by using components instead.
Dynamically rendering arbitrary HTML on your website can be very
dangerous because it can easily lead to XSS attacks. Only use v-html
on trusted content and never on user-provided content.
In single-file components, scoped styles will not apply to content
inside v-html, because that HTML is not processed by Vue’s template
compiler. If you want to target v-html content with scoped CSS, you
can instead use CSS modules or an additional, global <style> element
with a manual scoping strategy such as BEM.

Computed property on child component props

I'm having this setup where I have child component props that have datetime format inside it and I want to change it to more human readable format in my table, so i use moment js to change the format and to do those kinds of task it will be made more sense if I use computed property. Like this in my index.vue
<div class="page-container">
<div class="page-content">
<div class="content-wrapper">
<data-viewer :source="source" :thead="thead">
<template scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>{{props.item.creator}}</td>
<td>
<i class="icon-checkmark5" v-if="props.item.publish === '0'"></i>
<i class="icon-cancel-circle2" v-else></i>
{{props.item.publish}} //for testing purpose to see returned value
</td>
<td>{{publishDate}}</td> //this is where i put my computed to change created_at format
</tr>
</template>
</data-viewer>
</div>
</div>
</div>
<script type="text/javascript">
import DataViewer from '../../components/dataviewer.vue'
import moment from 'moment'
export default{
components:{
DataViewer
},
data(){
return{
source: '/api/article',
thead: [
{title: 'Name', key: 'name', sort: true},
{title: 'Creator', key: 'creator_id', sort: true},
{title: 'Publish', key: 'publish', sort: true},
{title: 'Created', key: 'created_at', sort: true}
],
}
},
computed: {
publishDate: function(){
return moment(props.item.created_at).format('YYYY-MM-DD')
}
}
}
</script>
and here is what inside my dataviewer file
<template>
<table class="table">
<thead class="bg-primary">
<tr>
<th v-for="item in thead">
<span>{{item.title}}</span>
</th>
</tr>
</thead>
<tbody>
<slot v-for="item in model.data" :item="item"></slot>
</tbody>
</table>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
export default {
props: ['source', 'thead'],
data() {
return {
model: {
data: []
},
}
},
beforeMount() {
this.fetchData()
},
methods: {
fetchData() {
var vm = this
axios.get(this.source)
.then(function(response) {
Vue.set(vm.$data, 'model', response.data.model)
})
.catch(function(error) {
console.log(error)
})
}
}
}
</script>
but it just won't work, it can't find props.item.created_at, so how I can change created_at or any other property item to change from my index.vue?
Seems like using a filter will work here:
Instead of using computed props:
filters: {
publishDate: function(value){
return moment(value).format('YYYY-MM-DD')
}
}
In place of {{publishDate}}
<td>{{props.item.created_at | publishedDate }}</td>