[![Firefox Console][1]][1]In my Vue app I am trying to use mdb-datatable, the table reads data() and sets the rows accordingly. I am setting the row data programmatically after my data is loaded with Ajax. In one column I need to add a button and it needs to call a function. I am trying to add onclick function to all buttons with "status-button" class but something weird happens.
When I print HtmlCollection it has a button inside, which is expected but I can't reach proceedButtons[0], it is undefined. proceedButtons.length also prints 0 length but I see the button in console.
I also tried to add onclick function but probably "this" reference changes and I get errors like "proceedStatus is not a function" it does not see anything from outer scope.
<mdb-datatable
:data="tableData"
:searching="false"
:pagination="false"
:responsive="true"
striped
bordered/>
export default {
name: "Applications",
mixins: [ServicesMixin, CommonsMixin],
components: {
Navbar,
Multiselect,
mdbDatatable
},
data () {
return {
statusFilter: null,
searchedWord: '',
jobRequirements: [],
applications: [],
options: ['Awaiting', 'Under review', 'Interview', 'Job Offer', 'Accepted'],
tableData: {
columns: [
{
label: 'Name',
field: 'name',
sort: 'asc',
},
{
label: 'Date',
field: 'date',
sort: 'asc'
},
{
label: 'Compatibility',
field: 'compatibility',
sort: 'asc'
},
{
label: 'Status',
field: 'status',
sort: 'asc'
},
{
label: 'Proceed Application Status',
field: 'changeStatus',
}
],
rows: []
}
}
}
fillTable(applications) {
let statusButtonId = 0;
applications.forEach(application => {
this.tableData.rows.push({
name: application.candidateLinkedIn.fullName,
date: this.parseDateFromDateObject(application.applicationDate),
compatibility: this.calculateJobCompatibility(application.candidateLinkedIn.linkedInSkillSet),
status: application.applicationStatus,
changeStatus: '<button type="button" class="btn-indigo btn-sm m-0 status-button"' +
' style="margin-left: 1rem">' +
'Proceed Status</button>',
candidateSkillSet: application.candidateLinkedIn.linkedInSkillSet
});
statusButtonId++;
});
},
addEventListenersToButtons() {
let proceedButtons = document.getElementsByClassName("status-button")
console.log(proceedButtons);
console.log(proceedButtons[0])
console.log(proceedButtons.item(0))
/*
proceedButtons.forEach(button => {
button.addEventListener("click",this.proceedStatus);
});
*/
},
[1]: https://i.stack.imgur.com/zUplv.png
From MDN:
Get the first element with a class of 'test', or undefined if there is no matching element:
document.getElementsByClassName('test')[0]
So undefined means no match, even if length is 0...
Since this is not an array, you do not get out-of-bounds exceptions.
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
Regarding Arrays
You can't index the list returned from getElementsByClassName.
You can turn it into an array though, and then index it.
ES6
let proceedButtons = document.getElementsByClassName("status-button")
const arr = Array.from(proceedButtons);
console.log(arr[0]);
Old School
const arr = []
Array.prototype.forEach.call(proceedButtons, function(el) {
arr.push(el);
});
console.log(arr[0]);
Related
I have a fairly simple table, and am currently using a bottom calculator formatter:
export let myTable = new Tabulator("#my-table", {
columns:[
{title:"ID", field:"id", headerSort:false, visible:false, responsive:2},
{formatter:"rowSelection", titleFormatter:"rowSelection", align:"center", bottomCalc:"sum", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
cell.getRow().toggleSelect();
}},
{title:"Name", field:"address", width:300, bottomCalc:"count"},
{title:"My Data", field:"mydata", bottomCalc:avNoOutsiders},
],
});
export let avNoOutsiders = function(values, data, calcParams){
// filter outliers
let myArray = filterOutliers(values);
// filter any null or falsy values
let av = average(myArray);
return av
}
The code isn't super important, but what I'd like to be able to do is allow the user to de-select a row to exclude the value from this calculation.
The problem is, I don't understand how to access the isSelected() function here, I think it's just the row() I can access it. I can access the values (all the column values) but there's no selection data there, I can access the data - the whole table, but there's no way of determining which row it is, or whether it is selected or not.
My current direction of thinking is either
using bottomCalcParams. I don't understand how I would do this. This function returns a getRow() is not a function error:
function cellIsSelected(cell){
selected = cell.getRow().isSelected()
return {isSelected:selected};
}
or
Writing individual functions for each bottom calc. This doesn't work as I can't call the table inside the table calcs - var selectedRows = table.getSelectedRows() causes a circular error if I try to put that into a column calc function. I can reference the table inside the table.
Any ideas how I can access the row selection data to make a column calculation?
There might be an easier way, but one way to achive that would be to create a hidden placeholder column which can be updated to true/false upon row selection/deselection. Then you can use the values of this hidden column in your bottomCalc function to exclude rows that are not selected. Here is an example where selecting a row will re-trigger bottom calculation and average the age of all selections:
const dataSet1 = [
{ id: 1, name: 'Billy Bob', age: '21', gender: 'male' },
{ id: 2, name: 'Mary May', age: '5', gender: 'female' },
{ id: 3, name: 'Christine Lobowski', age: '42', gender: 'female' },
{ id: 4, name: 'Brendon Philips', age: '80', gender: 'male' },
]
const calcAvg = (values, data, calcParams) => {
let selected = data.filter((row) => row.isSelected)
values = selected.map((i) => i.age)
let avg = values.reduce((a, b) => Number(a) + Number(b), 0) / values.length
return avg ? avg : ''
}
const table = new Tabulator('#table', {
data: dataSet1,
columns: [
{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
cellClick: (e, cell) => {
cell.getRow().toggleSelect()
}
},
{ title: 'Name', field: 'name' },
{ title: 'Age', field: 'age', bottomCalc: calcAvg },
{ title: 'Gender', field: 'gender' },
{ title: '', field: 'isSelected', visible: false } // Selection placeholder column
]
})
const selection = (row) => {
row.update({ isSelected: row.isSelected() })
table.recalc()
}
table.on('rowSelected', selection)
table.on('rowDeselected', selection)
<html>
<head>
<link href="https://unpkg.com/tabulator-tables#5.2.2/dist/css/tabulator.min.css" rel="stylesheet">
<script type="text/javascript" src="https://unpkg.com/tabulator-tables#5.2.2/dist/js/tabulator.min.js"></script>
</head>
<body>
<div id="table"></div>
</body>
<html>
#Tim's answer got me 95% of the way there.
Note that some of his responses don't work on Tabulator v4.9:
const selection = (row) => {
row.update({ isSelected: row.isSelected() })
table.recalc()
}
table.on('rowSelected', selection)
table.on('rowDeselected', selection)
I thought that figuring out a way round this might take longer than updating, so I plunge and updated to 5.2.2, which was less painful than expected. I had multiple columns to calculate, so rather than running a different function for each one, I passed the field name into the function via the bottomCalcParams, thus:
const calcAvg = (values, data, calcParams) => {
let selected = data.filter((row) => row.isSelected)
// Add your calcParams here:
values = selected.map((i) => i[calcParams.field])
let avg = values.reduce((a, b) => Number(a) + Number(b), 0) / values.length
return avg ? avg : ''
}
const table = new Tabulator('#table', {
data: dataSet1,
columns: [
{
formatter: 'rowSelection',
titleFormatter: 'rowSelection',
cellClick: (e, cell) => {
cell.getRow().toggleSelect()
}
},
{ title: 'Name', field: 'name' },
{ title: 'Age', field: 'age', bottomCalc: calcAvg, bottomCalcParams:{field:"age"} },
{ title: 'Remaining Teeth', field: 'teeth', bottomCalc: calcAvg, bottomCalcParams:{field:"teeth"} },
{ title: 'Personality Quirks', field: 'quirks', bottomCalc: calcAvg, bottomCalcParams:{field:"quirks"} },
{ title: 'Gender', field: 'gender' },
{ title: '', field: 'isSelected', visible: false } // Selection placeholder column
]
})
var ColDef = [{
headerName: "colA",
field: 'colA',
rowGroup: true
},
{
headerName: "colB",
field: 'colB',
pivot: true,
enablePivot: true
},
{
headerName: "colC",
field: 'colC',
rowGroup: true
},
{
field: 'colD',
aggFunc: 'first',
valueFormatter: currencyFormatter,
tooltip: function(params) {
return (params.valueFormatted);
},
},
{
field: 'comment'
},
{
field: 'colF'
}
];
function currencyFormatter(params) {
return params.value;
}
above code is from different question. it works but i want to use different 'comment' field as tool tip to current 'colD' . also this is a group and pivot agGrid,if it is normal grid this is not a problem. I would appreciate any ideas for group and pivot agGrid?
Not sure if there is good way for the grid to get the data in your scenario then, as your rows and columns are different than original model after pivot.
Maybe you can consider retrieve this information outside of grid. Assume you also want some aggregated information displays in the tooltip, the tooltip function may eventually look like this:
tooltip: params => {
const country = params.node.key;
const year = params.colDef.pivotKeys[0];
const athletesWithNumbers = this.state.rowData
.filter(d => d.year == year)
.filter(d => d.country === country)
.filter(d => d.gold > 0)
.map(d => d.athlete + ': ' + d.gold);
return athletesWithNumbers.join(', ');
}
See this plunker for what I am talking about - again, not sure if this is what you want but just an FYI.
just use tooltipValueGetter
{
field: 'message',
headerName: 'Message',
headerTooltip: 'Message',
width: 110,
filter: 'agSetColumnFilter',
tooltipValueGetter: (params) => `${params.value} some text`
}
or just use the same method for tooltipValueGetter
UPDATE:
Okay, I understood
but it also easy
Ag-grid has property tooltipField - where you can choose any field from grid
For example here - in the column of 'sport' I am showing tooltip of the previous column
Example: https://plnkr.co/edit/zNbMPT5HOB9yqI08
OR
You can easily manipulate with data for each field by tooltipValueGetter
with next construction:
tooltipValueGetter: function(params) {
return `Country: ${params.data.country}, Athlete: ${params.data.athlete}, Sport: ${params.data.sport}`;
},
Example: https://plnkr.co/edit/zNbMPT5HOB9yqI08
Result:
UPDATE 2
Hey Man! I do not understand was is wrong
I just used your code snippet and my solution
And it works as you want
Example: https://plnkr.co/edit/zNbMPT5HOB9yqI08
UPDATE 3
A little bit of manipulation and I can get the data
{ field: 'gold', aggFunc: 'sum',
tooltipValueGetter: function(params) {
var model = params.api.getDisplayedRowAtIndex(params.rowIndex);
return model.allLeafChildren[0].data.silver;
},
},
The Last:
https://plnkr.co/edit/9qtYjkngKJg6Ihwb?preview
var ColDef = [{
headerName: "colA",
field: 'colA',
rowGroup: true
},
{
headerName: "colB",
field: 'colB',
pivot: true,
enablePivot: true
},
{
headerName: "colC",
field: 'colC',
rowGroup: true
},
{
field: 'colD',
aggFunc: 'last',
tooltipValueGetter: commentTooltipValueGetter
},
{
field: 'comment'
},
{
field: 'colF'
}
];
function commentTooltipValueGetter(params) {
const colB = params.colDef.pivotKeys[0];
var model = params.api.getDisplayedRowAtIndex(params.rowIndex);
for (var i = 0; i < model.allLeafChildren.length ; i++) {
if (model.allLeafChildren[i].data.colB=== colB) {
return model.allLeafChildren[i].data.comments;
}
}
}
This is what i had to do for my question. It is combination of answers from #wctiger and #shuts below. So please also refer them for more context
I'm using vuejs#2.3.3, selectize#0.12.4, vue2-selectize.
I have a pretty big form with a few select inputs.
All options are loaded by ajax into a one property, which is initialized with a demo data before being replaced by ajax data:
addTrackData : {
styles : [
{ id: 1, title: 'style 1' },
{ id: 2, title: 'style 3' },
{ id: 3, title: 'style 2' },
],
authors: [
{inn: '111', name: 'demo 1'},
{inn: '222', name: 'demo 2'},
{inn: '333', name: 'demo 3'}
]
....
},
And I've got 2 problems:
1) If I use settings in this way, options doesn't loads at all:
<selectize v-model="form.data.authors[i]['id']" :settings="selectize.authors"></selectize>
selectize: {
authors: {
valueField: 'inn',
labelField: 'name',
searchField: ['name', 'inn'],
options: this.addTrackData.authors // that doesn't works, but hard coded array works
}
}
Because of error Error in data(): "TypeError: Cannot read property 'authors' of undefined".
Both this.addTrackData.authors and addTrackData.authors makes this error.
But this way works:
<selectize v-model="form.data.authors[i]['id']"
:settings=" {
valueField: 'inn',
labelField: 'name',
searchField: ['name', 'inn'],
options: addTrackData.authors, // It works, but looks too ugly!
}" >
</selectize>
2) Options are not reactive - when ajax data comes, all selects elements still shows a demo data. And I have no idea how to update them all...
UPDATE
Second problem could be fixed with If Conditional and empty initial array:
<selectize v-if="addTrackData.authors.length" v-model="form.data.authors[i]['id']"
:settings=" {
valueField: 'inn',
labelField: 'name',
searchField: ['name', 'inn'],
options: addTrackData.authors, // It works, but looks too ugly!
}" >
</selectize>
addTrackData : {
styles : [],
authors: []
....
}
But the first problem still makes me cry
I just read the source code of vue2-selectize and noticed that it's watch code for options key is incorrect.
his code is this way:
watch: {
value() {
this.setValue()
},
options (value, old) {
if (this.$el.selectize && !equal(value, old)) {
this.$el.selectize.clearOptions()
this.$el.selectize.addOption(this.current)
this.$el.selectize.refreshOptions(false)
this.setValue()
}
}
},
while it should be this way to work:
watch: {
value() {
this.setValue()
},
options (value, old) {
if (this.$el.selectize && !equal(value, old)) {
this.$el.selectize.clear();
this.$el.selectize.clearOptions();
var vm = this;
this.$el.selectize.load(function(callback) {
callback(vm.current);
});
this.$el.selectize.refreshOptions(false);
this.setValue();
}
}
},
I just prepared a hacky way to make it working but I dont encourage you using it in production.
Here is the fiddle's link: https://jsfiddle.net/ahmadm/h8p97hm7/
I'll try to send a pull request to his creator as soon as possible but until that time, your solution is already the only possible solution.
I am trying to implement the "RowSelectors" feature on the iggrid, but it will not change to a the next row.
On further inspection, i have got the selected row method and each row shows the same id. I am returning my data via ajax, if i use static json in a variable and use that as a datasource, it works as expected so not sure what the issue is..
$("#selector").igGrid("selectedRow");
I can only acheive a row change when holding control and clicking..
Object {element: n.fn.init(1), index: 0, id: 3407751001}
then next row is
Object {element: n.fn.init(1), index: 1, id: 3407751001}
Settings..
features: [
{
name: "Sorting",
columnSettings: [
{
columnIndex: 4,
allowSorting: true,
firstSortDirection: "ascending",
currentSortDirection: "descending"
}
]
},
{
name: 'RowSelectors',
enableCheckBoxes: true,
checkBoxStateChanging: function (ui, args) {
return false;
},
multipleSelection: true,
rowSelectorClicked: function (evt, ui) {
// Handle event
},
},
{
name: 'Selection'
}
]
The issue is already resolved, but I'm adding the answer so it doesn't stay unanswered.
The primaryKey column the igGrid uses needs to be a column with unique identifiers for each record.
I am using jquery's DataTables which is really working great. Then only problem I got is, that I am facing (in non-edit-view) the value of the select-field (which is an id). The user of course doesn't want to see the id of course.
Therefore I am looking for a possibility to configure that column in a way to show always the value of label property.
Here a some snippets:
$(document).ready(function() {
var table = $('#overviewTable').DataTable({
dom: "Tfrtip",
ajax: "/Conroller/GetTableData",
columns: [
{ data: "Id", className: "readOnly", visible: false },
{
data: "LoanTransactionId",
className: "readOnly readData clickable",
"fnCreatedCell": function(nTd, sData, oData, iRow, iCol) {
$(nTd).html("<a href='#'>" + oData.LoanTransactionId + "</a>");
}
},
{ data: "Id", className: "readOnly" },
{ data: "property_1", className: "readOnly" },
{ data: "Priority" },
{ data: null, className: "action readOnly", defaultContent: 'Info' }
],
order: [1, 'asc'],
tableTools: {
sRowSelect: "os",
sRowSelector: 'td:first-child',
aButtons: []
}
});
// data reload every 30 seconds
setInterval(function() {
table.ajax.reload();
}, 30000);
editor = new $.fn.dataTable.Editor({
ajax: "PostTable",
table: "#overviewTable",
fields: [
{
label: "Id",
name: "Id"
},
{
label: "Column 1",
name: "property_1"
},
{
label: "Priority",
name: "Priority",
type: "select",
options: [
{ label: "low", value: 0 },
{ label: "mid", id: 1 },
{ text: "high", id: 2 }
]
}
]
});
// Inline Edit - only those who are not readOnly
$('#overviewTable').on('click', 'tbody td:not(:first-child .readOnly)', function(e) {
editor.inline(this, {
submitOnBlur: true
});
});
How it looks in the display mode
How it looks in the edit mode
See the documentation on columns.render
You want to modify your column options for priority
Preferred Option: Your data source has a field with the priority as a string
This is the best option, as you don't want to have two places with this business logic. Keep it out of the client code.
Also, you will want to modify the editor as well so that the options used have been retrieved dynamically from the server to keep this business logic out of the client too. This is left as an exercise for the reader.
Since you don't provide details on what your data structure looks lik, I'm assuming it is an object, and it has an attribute priorityAsString so use the string option type for render.
columns: [
...
{
data: "Priority" ,
render: "priorityAsString",
},
Option 2) You write a function to map priority to string
Do this if you can't get the data from the server. But remember you will need to update many places when the priority list changes.
columns: [
...
{
data: "Priority" ,
render: renderPriorityAsString,
},
...
function renderPriorityAsString(priority) {
const priorityToString = {
0: 'low',
1: 'med',
2: 'high',
};
return priorityToString[priority] || `${priority} does not have a lookup value`;
}
"render": function ( data, type, full ) { return label;}