Why are my row/cell changes in Tabulator applied to both old and new rows/cells? - tabulator

I am attempting to react to a cellEdited event in Tabulator by: (a) copying the modified row to a new row; and (b) restoring the cell value in the modified row.
My Tabulator instance is coded as follows:
function TabulatorInvitees(divId, companyName) {
try {
var table = new Tabulator(divId, {
columns: [
{
title: "<div style='width:20%; float:left; text-align:left; color:blue; font-size:14px;'>Vendor Invitees</div>",
columns: [
{ title: "DbId", field: "DbId", visible: true },
{ title: "Added", field: "Added", visible: true },
{ title: "Changed", field: "Changed", visible: true },
{ title: "Marked For Exclusion", field: "MarkedForExclusion", visible: true },
{
title: "Email Address",
field: "Email",
widthGrow: 1,
responsive: 0,
hozAlign: "center",
editor: "input",
visible: true,
cellEdited: function (cell) {
// When an email address has been changed the former record needs to be marked for exclusion and a new record needs to be added.
// Step #1: Copy the edited row to a new row and modify the [DbId] and [Added] columns.
let refTable = cell.getTable();
let row = cell.getRow();
refTable.addRow({}, true).then((newRow) => {
newRow.update(row.getData()); // Paste a copy of the data from the edited row.
newRow.update({ DbId: 0, Added: true });
})
// Step #2: Restore the original value to the edited row.
cell.restoreOldValue();
}
},
{
title: "First Name",
field: "FirstName",
widthGrow: 0.5,
responsive: 1,
hozAlign: "center",
editor: "input",
visible: true,
cellEdited: function (cell) {
cell.getRow().update({ Changed: true });
}
},
{
title: "Last Name",
field: "LastName",
widthGrow: 0.5,
responsive: 1,
hozAlign: "center",
editor: "input",
visible: true,
cellEdited: function (cell) {
cell.getRow().update({ Changed: true });
}
}
]
},
{
title: tabulatorAddUserIcon(companyName),
field: "ManageRows",
widthGrow: 0.25,
responsive: 2,
hozAlign: "center",
formatter: "tickCross",
headerClick: function (e, row) {
row.getTable().addRow({ DbId: 0, Added: true }, false);
},
cellClick: function (e, cell) {
tabulatorFreezeUnfreezeDelete(cell.getRow());
}
},
],
data: [],
height: "100%",
layout: "fitColumns", // required when using 'widthGrow'
placeholder: tabulatorPlaceholder(companyName), //display message to user on empty table
reactiveData: true, //enable reactive data
responsiveLayout: "collapse",
rowContextMenu: tabulatorContextMenu(),
});
table.on("rowTapHold", function (e, row) {
// from Tabulator documentation: "The rowTapHold event is triggered when a user taps on a row on a touch display and holds their finger down for over 1 second."
//e - the tap event object
tabulatorContextMenu();
});
table.on("tableBuilt", function () {
if (companyName.length > 0) {
table.setData(getDataSync({ caseSelector: "VendorMgmt_EmployeeList", companyCode: companyName, userEmail: graphMail }));
}
else {
table.setData([]);
}
});
}
catch (error) {
console.log(error);
}
}
Specifically, any time an [Email Address] is edited the original version must be preserved and marked as excluded. My remedy is to copy the edited version of the row into a new Tabulator row then reverse the edits to the original row (thus preserving its contents).
The following screenshot shows the initial state of the Tabulator instance after data has been retrieved from a local database using the setData() method.
The following screenshot shows the original [Email Address] (jane.doe#gmail.com) having been modified (jane.doe#yahoo.com):
The anticipated behavior is that a new record will be inserted into the table (jane.doe#yahoo.com) and the edited row will be restored using the restoreOldValue() method.
By removing the restoreOldValue() method, I can readily see the contents of the modified row have been copied to a new row. So far, so good....
The new row is added with the new [Email Address], as anticipated.
My problem arises when the restoreOldValue() method is invoked on the [Email] cell of the edited row. The restored/original value is applied to both the new row and the edited row, as follows:
Instead of having a new row for "jane.doe#yahoo.com" and an original row for "jane.doe#gmail.com" I now have two rows for "jane.doe#gmail.com".
I feel as though there should be a simple fix but I have yet to find it. Any assistance is greatly appreciated.

Related

Why is the Tabulator getRows() function not working?

This seems like the simplest of requests but I can't seem to retrieve a set of rows from a Tabulator object.
Here's the code which instantiates the Tabulator object.........
function TabulatorInvitees(divId, companyName, userEmail) {
try {
var table = new Tabulator(divId, {
columns: [
{
title: "<div style='width:20%; float:left; text-align:left; color:blue; font-size:14px;'>Vendor Invitees</div>",
columns: [
{ title: "Id", field: "Id", visible: false },
{ title: "Added", field: "Added", visible: false },
{ title: "Changed", field: "Changed", visible: false },
{ title: "MarkedForExclusion", field: "MarkedForExclusion", visible: false },
{ title: "Email Address", field: "Email", widthGrow: 1, responsive: 0, hozAlign: "center", editor: "input", visible: true },
{ title: "First Name", field: "FirstName", widthGrow: 0.5, responsive: 1, hozAlign: "center", editor: "input", visible: true },
{ title: "Last Name", field: "LastName", widthGrow: 0.5, responsive: 1, hozAlign: "center", editor: "input", visible: true }
]
},
{
title: tabulatorAddUser(companyName),
field: "ManageRows",
widthGrow: 0.25,
responsive: 2,
hozAlign: "center",
formatter: "tickCross",
headerClick: function (e, row) {
row.getTable().addRow({ Id: 0, Added: true }, false);
},
cellClick: function (e, cell) {
tabulatorFreezeUnfreezeDelete(cell.getRow());
}
},
],
data: [],
height: "100%",
layout: "fitColumns", // required when using 'widthGrow'
placeholder: tabulatorPlaceholder(companyName), //display message to user on empty table
reactiveData: true, //enable reactive data
responsiveLayout: "collapse",
rowContextMenu: tabulatorContextMenu(),
});
table.on("rowTapHold", function (e, row) {
// from Tabulator documentation: "The rowTapHold event is triggered when a user taps on a row on a touch display and holds their finger down for over 1 second."
//e - the tap event object
//row - row component
tabulatorContextMenu();
});
table.on("tableBuilt", function () {
if (companyName.length > 0) {
table.setData(getDataSync({ caseSelector: "VendorMgmt_EmployeeList", companyCode: companyName, userEmail: userEmail }));
}
else {
table.setData([]);
}
});
}
catch (error) {
console.log(error);
}
}
The setData() function makes a call to a database function which returns three rows, similar to the following:
The following JQuery function is called when a radio button is clicked....
$(".vendorStatus").click(function (e) {
const status = e.target.value;
const tbls = Tabulator.findTable("#divVendorEmployees");
const tbl = tbls[0];
const tblRows = tbl.getRows();
console.log("tbls.length", tbls.length);
console.log("tblRows", tblRows);
});
The browser console indicates a table has been found (tbls.length = 1) but the tblRows array is empty:
I see the three rows in my Tabulator but I am not able to recall them programmatically. It seems like a simple problem which should have a simple answer.
I am using the most recent version of Tabulator (v5.4).
Any assistance is greatly appreciated.
After much searching, I finally came to the realization the DOM element associated with the Tabulator instance must be managed when attempting to refresh or replace data. I've implemented a method which allows me to delete and rebuild the DOM element each time I need to save data to my database and subsequently refresh my Tabulator instance.
Here's the code...
function refreshTabulatorObject(obj) {
let parentId = obj.parentId;
let childId = obj.childId;
//Empty and remove the current version of the [Tabulator] object.
const tables = Tabulator.findTable(childId);
if (tables.length > 0) {
var table = Tabulator.findTable(childId)[0];
table.setData([]);
}
//Remove the existing <div> from the DOM.
$(childId).remove();
//Re-create the <div> element for the [Tabulator] object and append it to the DOM.
var parentDiv = document.getElementById(parentId);
parentDiv.innerHTML = "";
var childDiv = document.createElement("div");
childDiv.setAttribute("id", childId);
parentDiv.appendChild(childDiv);
//Re-create the [Tabulator] object.
TabulatorInvitees("#" + childId, obj.companyName);
}
I'm sure those of you with a more intimate knowledge of Tabulator would likely suggest a more elegant method, however, this one appears to work and I've already spent far more time on this issue that I had anticipated. Unfortunately, elegance is not a luxury I can afford at this point.
I hope this solution might be of help to some other struggling soul.

How do I programmatically change a cell value for a programmatically inserted row in Tabulator?

I have figured out how to programmatically copy and insert a new row in my Tabulator table utilizing a right-click and the rowContextMenu property; however I am having difficulty programmatically modifying data within the cells of the newly inserted row.
An abbreviated version of my Tabulator table setup code is as follows:
function TabulatorTimeSheet(divId) {
try {
var myActionContextMenu = [
{
label: "Copy & Paste Row",
action: function (e, row) {
var myTable = row.getTable();
var rowData = row.getData();
var idx = 0;
myTable.addData(rowData, false)
.then(function (newRows) {
//NOTE: The [Added] column of the new row needs to be set to "true".
newRows.forEach(newRow => {
idx = newRow.getPosition(true);
myTable.updateRow(idx, { Added: "true" });
});
myTable.redraw(true);
})
}
}
]
var table = new Tabulator(divId, {
height: "100%",
data: [],
layout: "fitDataFill",
selectable: 1,
cellEdited: function (cell) {
var myTable = cell.getTable();
var row = cell.getRow();
var rowData = row.getData();
rowData["Changed"] = "true";
},
rowContextMenu: myActionContextMenu,
columns: [
{ title: "Date Worked", field: "DateComp", responsive: 0, hozAlign: "center", sorter: "date", editor: dateEditor },
{ title: "Start Time", field: "TimeStart", responsive: 0, hozAlign: "center", sorter: "time", editor: timeEditor },
{ title: "Finish Time", field: "TimeFinish", responsive: 0, hozAlign: "center", sorter: "time", editor: timeEditor },
{ title: "Added", field: "Added", visible: true, responsive: 0, hozAlign: "center" },
{ title: "Changed", field: "Changed", visible: false, responsive: 0, hozAlign: "center" } ]
});
table.setData(myEndPointURL, { caseSelector: myCaseSelector, emplId: myEmployeeId });
}
catch (error) {
console.log(error);
}
The addData() function inserts a copy of the selected row, as expected; however, I am unable to modify the Added cell of the newly inserted row. Regardless of my efforts, I still see a blank cell.
Any assistance is greatly appreciated.
Most of your setup looks fine. Instead of doing myTable.updateRow, use newRows.update({field: 'new val'}). Because you already have the row reference, you don't need to get the id. I think that will solve your issue.
A few other things.
You are only inserting one row, so instead of myTable.addData, use myTable.addRow. Then, the return value will only be 1 row, so you don't need the forEach.
The table.redraw probably isn't necessary.
Here is a very simple example that should do all that you need it to. I omitted the context menu as it isn't relevant to the issue, but you just need to move the function into there and it should be good.
https://jsfiddle.net/nrayburn/65cngder/6/

how to Cancel Edit in tabulator

after editing data in any row how to allow the user to cancel the edited data to go back to original data ,
cannot reload the whole table as this will cancel all edited rows , i only need to cancel one row
the undo function "table.undo();" need to be called many times to undo the whole row ,is there some thing like "row.undo();"
i would like to code to look something like blow
var ivInvGRNDGrid = new Tabulator("#ivInvGRNDGrid", {
ajaxURL: "/abc/abcd/GetData",
layout: "fitColumns",
index: "id",
columns: [
{ title: "Delete", formatter: DeleteIcon, width: 40, align: "center", cellClick: function (e, cell) { ivInvGRNDDelete(cell.getRow().getData().id); }, headerSort: false },
{ title: "Cancel Edit", formatter: UndoIcon, width: 40, align: "center", cellClick: function (e, cell) { row.undo(); }, headerSort: false },
{ id: "ID", title: "#Localizer["ID"]", field: "iD", headerToolTip: "#Localizer["IDtip"]", validator: "required", editor: "number", visible: false, sorter: "number", editable: Editable },
{ id: "ItemID", title: "#Localizer["ItemID"]", field: "itemID", headerToolTip: "#Localizer["ItemIDtip"]", validator: "required", editor: "number", validator: "required", minWidth: 120, editable: Editable },
],
});
There is no row undo function i'm afraid, but you can restore the previous value of a cell by calling the restoreOldValue on the cell component. so in the cellClick function on your cancel edit row you could do something like this:
function(e, cell){
cell.getRow().getCells().forEach(function(cell){
var oldVal = cell.getOldValue();
if(oldVal !== null){
cell.restoreOldValue();
}
})
}

jTable dynamic custom toolbar item in child table

I have a jTable with a child table for each row. On the toolbar header of the child table I have added a custom toolbar item. I want to make that toolbar item dynamic in the sense that if there are already some rows I do not want it to show. I came across a very similar query for the main toolbar "add new" button which added a function to run on recordsLoaded:
Below is my first attempt - it is just the field entry for the main table that specifies the child table. However the ".find(....)" spec will not work in my case as mine is a custom toolbar item. What do I need to put as the .find criteria?
Thanks
Dance: {
title: '',
width: '4%',
sorting: false,
create: false,
listClass: 'centreCol',
display: function(book) {
var $img = $('<img src="Images/layers.png" title="Show associated dance entries" />');
//Open child table when user clicks the image
$img.click(function() {
var thisrow = $img.closest('tr'); //Parent row
if($('#BookTableContainer').jtable('isChildRowOpen',thisrow)) { // Clicking image a second time closes the child row
$('#BookTableContainer').jtable('closeChildRow',thisrow);
} else {
currentTitleID = book.record.DanceTitleID;
$('#BookTableContainer').jtable(
'openChildTable',
thisrow,
{
title: 'Related Dance',
toolbar: {
items: [
{
icon: 'Images/add.png',
text: 'New dance',
tooltip: 'Add dance details',
click: function() { CreateDanceDialog(); }
}
]
},
actions: {
listAction: 'BookPageData.php?action=listChildDances&DanceTitleID=' + currentTitleID,
// createAction: 'dancesData.php?action=createAssignment',
// deleteAction: 'dancesData.php?action=deleteAssignment'
},
recordsLoaded: function(event, data) {
var rowCount = data.records.length;
if (rowCount>0){
$('#BookTableContainer').find('.jtable-toolbar-item.jtable-toolbar-item-add-record').remove();
}
},
fields: {
DanceID: { key: true, create: false, edit: false, list: false, visibility: 'hidden' },
DanceTitleID: { type: 'hidden', defaultValue: currentTitleID },
ChoreographerID: { title: 'Choreographer', width: '40%', options: function() { return ChoreographerOptions; } },
FormationID: { title: 'Formation', width: '30%', options: function() { return FormationOptions; } },
GenreID: { title: 'Genre', width: '30%', options: function() { return GenreOptions; } }
}
},
function(data) { data.childTable.jtable('load'); }
);
}
});
//Return image to show on the person row
return $img;
}
},
Try this
$('#BookTableContainer').find('.jtable-toolbar').remove();

Adding a column to a dstore backed dgrid

I have a grid with five columns - username, email, enabled, locked and remove.
Username, email, enabled and locked are sourced from the server. Remove is a client-side element used to indicate that a row should be removed.
I would like to either inject the default value of remove in the store on the client side as the grid content is loading, or set it as the user interacts with the CheckBox widget.
How can I catch the code which is requesting the objects from the server and add another column?
Or, is there a better way to do this.
var TrackableRest = declare([Rest, SimpleQuery, Trackable]);
var store = new TrackableRest({target: '/api/users', useRangeHeaders: true, idProperty: 'username'});
aspect.after(store, "fetch", function (deferred) {
return deferred.then(function (response) {
response.remove = false;
return json(response);
})
});
var grid = new (declare([OnDemandGrid, Selection, Editor]))({
collection: store,
className: "dgrid-autoheight",
columns: {
username: {
label: core.username
},
email: {
label: core.email
},
enabled: {
label: core.enabled,
editor: CheckBox,
editOn: "click",
sortable: false,
renderCell: libGrid.renderGridCheckbox
},
locked: {
label: core.locked,
editor: CheckBox,
editOn: "click",
sortable: false,
renderCell: libGrid.renderGridCheckbox
},
remove: {
editor: CheckBox,
editorArgs: {"checked": false},
editOn: "click",
label: core.remove,
sortable: false,
className: "remove-cb",
renderHeaderCell: function (node) {
var inp = domConstruct.create("input", {id: "cb-all", type: "checkbox"});
return inp;
},
renderCell: libGrid.renderGridCheckbox
}
},
selectionMode: "none"
}, 'grid');
In addition, I don't want to send the remove column to the server.
My final implementation was to code the remove column like so:
remove: {
editor: CheckBox,
label: core.remove,
sortable: false,
className: "remove-cb",
renderHeaderCell: function (node) {
var inp = domConstruct.create("input", {id: "cb-all", type: "checkbox"});
return inp;
}
}
The code to perform the removes is as follows:
var removeBtn = new Button({
label: core.remove
}, 'user-remove-btn');
removeBtn.startup();
removeBtn.on("click", function (event) {
var markedForDeletion = query(".dgrid-row .remove-cb input:checked", "user-grid");
if( markedForDeletion.length > 0 ) {
lib.confirmAction(core.areyousure, function () {
markedForDeletion.forEach(function (node) {
var row = grid.row(node);
store.remove(row.data.username);
});
});
}
});
Thus the remove column became a client-side only control that was handled by the grid and the event handler.

Resources