Can't read property 'replace' of null in angular data table - datatables

I wrote an angular datatable which fetches and populates the table with data from server. Code is given below:
HTML:
<table
class="table display table-striped table-hover table-bordered row-border hover responsive nowrap"
datatable
[dtOptions]="dtOptions"
datatable=""
#dataTable
id="issueTable"
>
<thead class="headColor">
<!-- <tr>
<th>Action</th>
</tr> -->
</thead>
<tbody>
<!-- <tr>
<td><button class="btn btn-primary btnColor" (click)="buttonInRowClick($event)">View</button></td>
</tr> -->
</tbody>
</table>
angular code:
import { Component, OnInit, Renderer, AfterViewInit, ViewChild } from '#angular/core';
import { Router, RouterLink } from '#angular/router';
import { routerTransition } from '../../router.animations';
import { DataTableDirective } from 'angular-datatables';
import 'datatables.net';
import 'datatables.net-bs4';
window['jQuery'] = window['$'] = $;
#Component({
selector: 'app-charts',
templateUrl: './issues.component.html',
styleUrls: ['./issues.component.scss'],
animations: [routerTransition()]
})
export class IssuesComponent implements OnInit {
#ViewChild(DataTableDirective)
datatableElement: DataTableDirective;
message = '';
title = 'angulardatatables';
#ViewChild('dataTable') table;
dataTable: any;
/**
* gets settings of data tables
*
* #type {DataTables.Settings}
* #memberof IssuesComponent
*/
constructor(private router: Router) { }
dtOptions: DataTables.Settings = {};
// someClickhandler(info: any): void {
// this.message = info.number + '-' + info.assignedto + '-' + info.company;
// console.log(this.message);
// }
ngOnInit(): void {
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 15,
processing: true,
responsive: true,
autoWidth: false,
dom: 'Bfrtip',
'ajax': {
url: 'http://localhost:8080/incident-updated',
type: 'GET',
dataSrc: 'result',
},
columns: [
{
title: 'Incident',
data: 'number'
},
{
title: 'Product',
data: 'u_product'
},
{
title: 'Status',
data: 'state'
},
{
title: 'Created By',
data: 'sys_created_by'
},
{
title: 'Group',
data: 'assignment_group'
},
{
title: 'Category',
data: 'u_category'
},
{
title: 'Updated on',
data: 'sys_updated_on'
},
{
title: null,
data: null,
render: function(data, type, full) {
return '<a class="btn btn-info btn-sm" href=#/>' + 'View' + '</a>';
}
}
]
// ,
// columnDefs: [
// {
// targets: -1,
// data: null,
// defaultContent: '<button>Click!</button>'
// }
// ]
};
this.dataTable = $(this.table.nativeElement);
console.log('table is ==>', this.dataTable);
$('#issueTable tbody').on('click', 'button', function () {
const data = this.dataTable.row($(this).parents('tr').data);
console.log('Data is ==>', data);
});
}
buttonInRowClick(event: any): void {
event.stopPropagation();
console.log('Button in the row clicked.');
this.router.navigate(['/event-viewer']);
}
wholeRowClick(): void {
console.log('Whole row clicked.');
}
// ngAfterViewInit() {
// this.renderer.listenGlobal('document', 'click', (event) => {
// if (event.target.hasAttribute("view-person-id")) {
// this.router.navigate(["/person/" + event.target.getAttribute("view-person-id")]);
// }
// });
// }
}
When I run that code, I get this error:
ERROR TypeError: Cannot read property 'replace' of null
at _fnSortAria (VM1053 scripts.js:16659)
at jQuery.fn.init.<anonymous> (VM1053 scripts.js:11820)
at VM1053 scripts.js:17237
at Function.map (VM1053 scripts.js:456)
at _fnCallbackFire (VM1053 scripts.js:17236)
at _fnDraw (VM1053 scripts.js:14107)
at _fnReDraw (VM1053 scripts.js:14150)
at _fnInitialise (VM1053 scripts.js:15333)
at loadedInit (VM1053 scripts.js:11893)
at HTMLTableElement.<anonymous> (VM1053 scripts.js:11905)
How do I fix this error?

I'm not sure about this but I think here is the problem:
{
title: null,
data: null,
render: function(data, type, full) {
return '<a class="btn btn-info btn-sm" href=#/>' + 'View' + '</a>';
}
}
Even if you don't use any field inside return, you should put some column name, for example data: 'u_product':
{
title: null,
data: 'u_product',
render: function(data, type, full) {
return '<a class="btn btn-info btn-sm" href=#/>' + 'View' + '</a>';
}
}
I recommend you to use the primary key column of the table, maybe in the future it will be useful in the button.

Related

Vue.js: Child Component mutates state, parent displays property as undefined

I have a parent component that lists all the tasks:
<template>
<div class="tasks-wrapper">
<div class="tasks-header">
<h4>{{ $t('client.taskListingTitle') }}</h4>
<b-button variant="custom" #click="showAddTaskModal">{{ $t('client.addTask') }}</b-button>
</div>
<b-table
striped
hover
:items="tasks"
:fields="fields"
show-empty
:empty-text="$t('common.noResultsFound')">
</b-table>
<AddTaskModal />
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import AddTaskModal from '#/components/modals/AddTaskModal'
import moment from 'moment'
export default {
name: 'TaskListing',
components: {
AddTaskModal
},
data () {
return {
tasks: [],
fields: [
{ key: 'createdOn', label: this.$t('tasks.tableFields.date'), formatter: 'formatDate' },
{ key: 'domain', label: this.$t('tasks.tableFields.task') },
{ key: 'comment', label: this.$t('tasks.tableFields.comment') },
{ key: 'status', label: this.$t('tasks.tableFields.status') }
]
}
},
computed: {
...mapGetters('users', ['user'])
},
methods: {
...mapActions('tasks', ['fetchTasks']),
...mapActions('users', ['fetchUserById']),
formatDate: function (date) {
return moment.utc(date).local().format('DD.MM.YYYY HH:mm')
},
showAddTaskModal () {
this.$bvModal.show('addTaskModal')
}
},
async mounted () {
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
}
}
}
</script>
Inside this component there is a child which adds a task modal.
<template>
<b-modal
id="addTaskModal"
:title="$t('modals.addTask.title')"
hide-footer
#show="resetModal"
#hidden="resetModal"
>
<form ref="form" #submit.stop.prevent="handleSubmit">
<b-form-group
:invalid-feedback="$t('modals.requiredFields')">
<b-form-select
id="task-type-select"
:options="taskTypesOptions"
:state="taskTypeState"
v-model="taskType"
required
></b-form-select>
<b-form-textarea
id="add-task-input"
:placeholder="$t('modals.enterComment')"
rows="3"
max-rows="6"
v-model="comment"
:state="commentState"
required />
</b-form-group>
<b-button-group class="float-right">
<b-button variant="danger" #click="$bvModal.hide('addTaskModal')">{{ $t('common.cancel') }}</b-button>
<b-button #click="addTask">{{ $t('modals.addTask.sendMail') }}</b-button>
</b-button-group>
</form>
</b-modal>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'AddTaskModal',
data () {
return {
comment: '',
commentState: null,
taskTypesOptions: [
{ value: null, text: this.$t('modals.addTask.taskType') },
{ value: 'OnBoarding', text: 'Onboarding' },
{ value: 'Accounts', text: 'Accounts' },
{ value: 'TopUp', text: 'Topup' },
{ value: 'Overdraft', text: 'Overdraft' },
{ value: 'Aml', text: 'Aml' },
{ value: 'Transfers', text: 'Transfers' },
{ value: 'Consultation', text: 'Consultation' },
{ value: 'TechnicalSupport', text: 'TechnicalSupport' },
{ value: 'UnblockPin', text: 'UnblockPin' },
{ value: 'Other', text: 'Other' }
],
taskType: null,
taskTypeState: null
}
},
computed: {
...mapGetters('users', ['user']),
...mapGetters('tasks', ['tasks'])
},
methods: {
...mapActions('tasks', ['addNewTask', 'fetchTasks']),
...mapActions('users', ['fetchUserById']),
async addTask (bvModalEvt) {
bvModalEvt.preventDefault()
if (!this.checkFormValidity()) { return }
const currUserId = this.$router.history.current.params.id
if (this.user || this.user.userId !== currUserId) {
await this.fetchUserById(currUserId)
}
const data = {
clientPhone: this.user.phoneNumber,
comment: this.comment,
clientReferenceNumber: this.user.clientNumber,
domain: this.taskType
}
await this.addNewTask(data)
if (this.user.clientNumber !== null) {
const filters = { clientReferenceNumber: { value: this.user.clientNumber } }
this.tasks = await this.fetchTasks({ filters })
// this.tasks may be useless here
}
console.log(this.tasks)
this.$nextTick(() => { this.$bvModal.hide('addTaskModal') })
},
checkFormValidity () {
const valid = this.$refs.form.checkValidity()
this.commentState = valid
this.taskTypeState = valid
return valid
},
resetModal () {
this.comment = ''
this.commentState = null
this.taskTypeState = null
}
}
}
</script>
When I add a task I call getalltasks to mutate the store so all the tasks are added. Then I want to render them. They are rendered but the property createdOn on the last task is InvalidDate and when I console log it is undefined.
The reason I need to call gettasks again in the modal is that the response on adding a task does not return the property createdOn. I do not want to set it on the front-end, I want to get it from the database.
I logged the store and all the tasks are added to the store.
Why is my parent component not rendering this particular createdOn property?
If I refresh the page everything is rendering fine.
If you add anything into a list of items that are displayed by v-for, you have to set a unique key. Based on your explanation, I assume that your key is the index and when you add a new item, you mess with the current indexes. Keys must be unique and unmutateable. What you need to do is to create a unique id for each element.
{
id: Math.floor(Math.random() * 10000000)
}
When you create a new task, use the same code to generate a new id, and use id as key. If this doesn't help, share your d-table and related vuex code too.

AG-Grid data update after drag & drop

I'm new to AG-Grid and I'm trying to use it inside my Vue app.
I'm try to figure out why, after a drag&drop event, the data doesn't get update.
I created a little example here: https://plnkr.co/edit/vLnMXZ5y1VTDrhd5
import Vue from 'vue';
import { AgGridVue } from '#ag-grid-community/vue';
import { ClientSideRowModelModule } from '#ag-grid-community/client-side-row-model';
import '#ag-grid-community/core/dist/styles/ag-grid.css';
import '#ag-grid-community/core/dist/styles/ag-theme-alpine.css';
const VueExample = {
template: `
<div style="height: 100%">
<button #click="logData">Log data</button>
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-alpine"
id="myGrid"
:gridOptions="gridOptions"
#grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowDragManaged="true"
:animateRows="true"
:modules="modules"
:rowData="rowData"></ag-grid-vue>
</div>
`,
components: {
'ag-grid-vue': AgGridVue,
},
data: function () {
return {
gridOptions: null,
gridApi: null,
columnApi: null,
columnDefs: null,
defaultColDef: null,
modules: [ClientSideRowModelModule],
rowData: null,
};
},
beforeMount() {
this.gridOptions = {};
this.columnDefs = [
{
field: 'Month',
rowDrag: true,
},
{ field: 'Max temp (C)' },
{ field: 'Min temp (C)' },
{ field: 'Days of air frost (days)' },
{ field: 'Sunshine (hours)' },
{ field: 'Rainfall (mm)' },
{ field: 'Days of rainfall >= 1 mm (days)' },
];
this.defaultColDef = {
width: 100,
sortable: true,
filter: true,
};
},
mounted() {
this.gridApi = this.gridOptions.api;
this.gridColumnApi = this.gridOptions.columnApi;
},
methods: {
logData() {
this.rowData.forEach(function(item) {
console.log(item.Month);
});
},
onGridReady(params) {
const httpRequest = new XMLHttpRequest();
const updateData = (data) => {
this.rowData = data;
};
httpRequest.open(
'GET',
'https://raw.githubusercontent.com/ag-grid/ag-grid/master/grid-packages/ag-grid-docs/src/weather_se_england.json'
);
httpRequest.send();
httpRequest.onreadystatechange = () => {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
updateData(JSON.parse(httpRequest.responseText));
}
};
},
},
};
new Vue({
el: '#app',
components: {
'my-component': VueExample,
},
});
if you click on the "Log data" button you can see in console that data isn't updated in sync with the view.
How can I do that?
Thank you!
I found a solution adding a #row-drag-end="rowDragEnd" event; I updated the example here: https://plnkr.co/edit/vLnMXZ5y1VTDrhd5
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-alpine"
id="myGrid"
:gridOptions="gridOptions"
#grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowDragManaged="true"
#row-drag-end="rowDragEnd"
:animateRows="true"
:modules="modules"
:rowData="rowData"></ag-grid-vue>
rowDragEnd: function(event) {
var itemsToUpdate = [];
this.gridApi.forEachNodeAfterFilterAndSort(function (rowNode) {
itemsToUpdate.push(rowNode.data);
});
this.rowData = itemsToUpdate;
},
According to this page, changing the row order in the grid will not change the rowData order.
If you want to log the order of months as they are in the grid, you may use this.gridApi.forEachNode(node, index), as described in State 3 of the same page. You can write your logData() method like this:
logData() {
this.gridApi.forEachNode(node => console.log(node.data.Month));
}

Adding HTML DOM addEventListener to custom button in datatables

I've added a custom button to datatables, I'm trying to add addEventListener to that button. Here is my code.
constructor(public router: Router) { }
#ViewChild(DataTableDirective)
datatableElement: DataTableDirective;
message = '';
title = 'angulardatatables';
#ViewChild('dataTable') table: { nativeElement: any; };
dataTable: any;
dtOptions: DataTables.Settings = {};
// someClickhandler(info: any): void {
// this.message = info.number + '-' + info.assignedto + '-' + info.company;
// console.log(this.message);
// }
ngOnInit(): void {
this.dtOptions = {
pagingType: 'full_numbers',
pageLength: 15,
processing: true,
responsive: true,
autoWidth: false,
dom: 'Bfrtip',
'ajax': {
url: 'http://localhost:8080/incident-updated',
type: 'GET',
dataSrc: 'result',
},
columns: [
{
title: 'Incident',
data: 'number'
},
{
title: 'Product',
data: 'u_product'
},
{
title: 'Status',
data: 'state'
},
{
title: 'Created By',
data: 'sys_created_by'
},
{
title: 'Group',
data: 'assignment_group'
},
{
title: 'Category',
data: 'u_category'
},
{
title: 'Updated on',
data: 'sys_updated_on'
},
{
title: 'Action',
data: null,
// render: function(data, type, full) {
// return '<a class="btn btn-primary btn-sm" style="color: #fff;" id="view" (click)="view($event)">View</a>';
// }
defaultContent: '<button class="btn btn-sm btn-primary" onClick="viewer();" (click)="viewer(event)"> View </button>'
}
]
};
this.dataTable = $(this.table.nativeElement);
console.log('table is ==>', this.dataTable);
$('#view tbody').on('click', 'button', function () {
const data = this.dataTable.row($(this).parents('tr').data);
alert('Data is ==>' + data);
});
}
ngAfterViewInit(): void {
// Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.
// Add 'implements AfterViewInit' to the class.
// this.viewer.addEventListener('click', function() {
// event.stopPropagation();
// // this.router.navigate(['/event-viewer']);
// alert('shdfiskludhzj');
// });
}
viewer() {
alert('sdjfhsklzdjh');
}
// viewer(event: any) {
// event.stopPropagation();
// this.router.navigate(['/event-viewer']);
// }
// buttonInRowClick(event: any): void {
// event.stopPropagation();
// console.log('Button in the row clicked.');
// this.router.navigate(['/event-viewer']);
// }
// wholeRowClick(): void {
// console.log('Whole row clicked.');
// }
It returns the error:
issues:1 Uncaught ReferenceError: viewer is not defined
at HTMLButtonElement.onclick (issues:1)
onclick
I've tried several other methods, none of them are working. How to fix this?
Add this function to your component code.
#HostListener('click')
viewer() {
// Perform your opration here
}
or
#HostListener('document:click')
viewer() {
// Perform your operation
}

Creating Vue Search Bar | How to hide/show data based on input?

I am creating a dynamic search bar that will filter a sidebar full of names based on user input. However, I am having trouble temporarily hiding and showing data based on the search bar's value on keyup. What is the best way to achieve this the "Vue way"?
On keyup, I want to filter through all of the this.people data and only show names that contain the value of the search input.
Below is what my code looks like
Vue.component('sidebar',{
props: ['people', 'tables'],
data: () => {
return {
fullName: ''
}
},
computed: {
computed() {
return [this.people, this.tables].join()
}
},
template:
`
<div id="sidebarContain" v-if="this.people">
<input id="sidebar-search" type="text" placeholder="Search..." #keydown="searchQuery">
<select id="sidebar-select" #change="sidebarChanged">
<option value="AZ">A-Z</option>
<option value="ZA">Z-A</option>
<option value="notAtTable">No Table</option>
<option value="Dean's Guest">Dean's Guest</option>
<option value="BOO | VIP">BOO | VIP</option>
</select>
<div v-for="person in people" :class="[{'checked-in': isCheckedIn(person)}, 'person']" :id="person.id" :style="calcRegColor(person)">
<span v-if="person.table_name">{{person.first_name + ' ' + person.last_name + ' - ' + person.table_name}}</span>
<span v-else>{{person.first_name + ' ' + person.last_name}}</span>
</div>
</div>
`,
methods: {
isCheckedIn(person) {
return person.reg_scan == null ? true : false;
},
isHidden(person)
{
console.log("here");
},
calcRegColor(person)
{
switch(person.registration_type)
{
case "Dean's Guest" :
return {
color: 'purple'
}
break;
case "BOO | VIP" :
return {
color: 'brown'
}
break;
case "Student" :
return {
color: 'green'
}
break;
case "Faculty":
case "Staff":
return {
color: 'blue'
}
break;
case "Alumni Club Leader":
return {
color: 'gold'
}
break;
case "Table Guest" :
return {
color: 'pink'
}
break;
default:
return {
color: 'black'
}
}
}
},
watch: {
computed() {
console.log("People and Tables Available");
}
}
});
var app = new Vue({
el: '#main',
data: {
tables: {},
people: [],
currentAlerts: [],
lastDismissed: []
},
methods: {
loadTables() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'users/getTableAssignments/' + event_id
}).done(data => {
this.tables = data;
});
},
loadPeople() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'users/getParticipants2/' + event_id
}).done(data => {
this.people = data;
this.sortSidebar(this.people);
});
},
loadCurrentAlerts() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'alerts/getAlerts/' + event_id
}).done(data => {
this.currentAlerts = data;
});
},
loadLastDismissed(num = 15)
{
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'alerts/getLastDismissed/' + event_id + '/' + num
}).done(data => {
this.lastDismissed = data;
});
},
setRefresh() {
setInterval(() => {
console.log("Getting People and Tables");
this.loadPeople();
this.loadTables();
}, 100000);
},
makeTablesDraggable() {
$(document).on("mouseenter", '.table', function(e){
var item = $(this);
//check if the item is already draggable
if (!item.is('.ui-draggable')) {
//make the item draggable
item.draggable({
start: (event, ui) => {
console.log($(this));
}
});
}
});
},
makePeopleDraggable() {
$(document).on("mouseenter", '.person', function(e){
var item = $(this);
//check if the item is already draggable
if (!item.is('.ui-draggable')) {
//make the item draggable
item.draggable({
appendTo: 'body',
containment: 'window',
scroll: false,
helper: 'clone',
start: (event, ui) => {
console.log($(this));
}
});
}
});
}
makeDroppable() {
$(document).on("mouseenter", ".dropzone, .table", function(e) {
$(this).droppable({
drop: function(ev, ui) {
console.log("Dropped in dropzone");
}
});
});
}
},
mounted() {
this.loadTables();
this.loadPeople();
this.loadCurrentAlerts();
this.loadLastDismissed();
this.setRefresh();
this.makeTablesDraggable();
this.makePeopleDraggable();
this.makeDroppable();
}
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
</head>
<div id="app">
<sidebar :people="people" :tables="tables"></sidebar>
</div>
You can change the people property in sidebar into a computed property, which will be calculated based on user's input.
So change the sidebar code to
<div v-for="person in filteredPeople" :class="[{'checked-in': isCheckedIn(person)}, 'person']" :id="person.id" :style="calcRegColor(person)">
<span v-if="person.table_name">{{person.first_name + ' ' + person.last_name + ' - ' + person.table_name}}</span>
<span v-else>{{person.first_name + ' ' + person.last_name}}</span>
</div>
and add a computed property
computed: {
filteredPeople () {
// Logic to filter data
}
}
A foolish aproach that I use, without computed properties:
JS:
new Vue({
el: '#app',
data: {
list: [],
filteredList: []
},
mounted(){
this.filteredList = this.list
},
methods: {
filter(e){
if(e.target.value === '') this.filteredList = this.list
else {
this.filteredList = []
this.list.forEach(item=>{
if(list.name.includes(e.target.value)) this.filteredList.push(item)
})
}
}
}
})
The "name" property of list object can be changed to whatever property you want to look for.
HTML:
<input #keyup="filter" id="search" type="search" required>

Implement Datatables responsive details display with Ajax data

I want to implement Datatable's responsive details with Bootstrap modal but it doesn't work for me. The first thing I noticed is that the "plus-sign" does not appear on the first column of my table. I'm not sure if this is because I'm using ajax data and need additional parameters that the example doesn't show or because I've added an auto number as my first column.
Here is the table HTML:
<table id="users" class="table table-striped table-bordered nowrap" data-conf="#Model.ExtraVM.DialogMsg" data-title="#Model.ExtraVM.DialogTitle" data-btnok="#Model.ExtraVM.Button1" data-btncancel="#Model.ExtraVM.Button2">
<thead>
<tr>
<th>#Model.HeadingVM.Col1</th>
<th>#Model.HeadingVM.Col2</th>
<th>#Model.HeadingVM.Col3</th>
<th>#Model.HeadingVM.Col4</th>
<th>#Model.HeadingVM.Col5</th>
</tr>
</thead>
<tbody></tbody>
</table>
Here is the jquery code:
<script>
$(document).ready(function () {
var t = $("#users").DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details for ' + data[0] + ' ' + data[1];
}
}),
renderer: $.fn.dataTable.Responsive.renderer.tableAll({
tableClass: 'table'
})
}
},
columnDefs: [{
"searchable": false,
"orderable": false,
"targets": 0
}],
order: [[1, 'asc']],
ajax: {
url: "/api/users",
dataSrc: ""
},
columns: [
{
data: "id"
},
{
data: "firstName"
},
{
data: "lastName"
},
{
data: "userName"
},
{
data: "id",
render: function (data) {
return "<a href='#'><i class='fa fa-eye' data-id='" + data + "' data-toggle='modal' data-target='#dataPopup'></i></a> | <a href='#'><i class='fa fa-pencil js-edit' data-id='" + data + "'></i></a> | <a href='#'><i class='fa fa-trash js-delete' data-id='" + data + "'></i></a>";
}
}
]
});
t.on('order.dt search.dt', function () {
t.column(0, { search: 'applied', order: 'applied' }).nodes().each(function (cell, i) {
cell.innerHTML = i + 1;
});
}).draw();
$("#users").on("click", ".js-delete", function () {
var btn = $(this);
var confirm = btn.parents("table").attr("data-conf");
var dialogTitle = btn.parents("table").attr("data-title");
var btnOK = btn.parents("table").attr("data-btnOk");
var btnCancel = btn.parents("table").attr("data-btnCancel");
bootbox.dialog({
message: confirm,
title: dialogTitle,
buttons: {
main: {
label: btnCancel,
className: "btn-default",
callback: function () {
var result = "false";
}
},
success: {
label: btnOK,
className: "btn-primary",
callback: function () {
$.ajax({
url: "/api/users/" + btn.attr("data-id"),
method: "DELETE",
success: function () {
btn.parents("tr").remove();
}
});
}
}
}
});
});
});
</script>
}
This is how my tables looks (as you can see, the plus-sign is missing):
Your code seems to be fine. Plus sign only appears when viewing area is small enough and one of the columns become hidden.
There is no setting to force (+) sign to appear but you can use a trick with extra empty column and class none on it which will force column to always be hidden. See Class logic for more details.
See this jsFiddle for code and demonstration.