Using latest Tabulator version 4.4.
I need to trigger an event so I can capture the info about the row moved from one table to the next.
The move works fine in the browser but there is never anything triggered, at least that I can see, that tells me what was moved.
I've tried
rowMoved:function(row){
//row - row component
}
but it never happens. I tried on both sender and receiver.
I have a table with all possible questions, and another which will hold only specific questions dragged to it.
var allquestions = new Tabulator("#allquestions", {
ajaxURL : "includes/retrievequestions.php?id=All",
rowMoved:function(row){
//row - row component
alert("row moved ");
},
height:"300px",
layout:"fitColumns",
pagination:"local",
movableRows:true,
movableRowsConnectedTables:"#thistemplatequestions",
placeholder:"No Data Set available",
paginationSize:40,
paginationSizeSelector:[100, 200, 500, 1000],
movableColumns:true,
columns:[
{title:"Question", field:"questiontext", sorter:"string", width:100, headerFilter:"input"},
{title:"Description", field:"questiondescription"},
{title:"Value", field:"questionvalue", align:"center", width:100},
],
});
var thistemplatequestions = new Tabulator("#thistemplatequestions", {
ajaxURL : "includes/retrievequestions.php?id="+$("#thisid").text(),
movableRowsConnectedTables:"#allquestions",
rowMoved:function(row){
//row - row component
alert("row moved ");
},
movableRowsReceiver: customReceiver,
height:"300px",
layout:"fitColumns",
pagination:"local",
placeholder:"No Data Set available",
paginationSize:40,
paginationSizeSelector:[100, 200, 500, 1000],
movableRowsReceiver: "insert", //add rows when dropped on the table.
columns:[
{title:"Question", field:"questiontext", sorter:"string"},
{title:"Description", field:"questiondescription" , headerFilter:"input"},
{title:"Value", field:"questionvalue", align:"center", width:100},
],
});
var customReceiver = function(fromRow, toRow, fromTable){
alert("customer receiver ");
//fromRow - the row component from the sending table
//toRow - the row component from the receiving table (if available)
//fromTable - the Tabulator object for the sending table
if(toRow){
toRow.update({"questiontext":fromRow.getData().questiontext});
return true;
}
return false;
}
Also tried customReciever with no luck either. it never triggered.
I'm sure it can be done, I just cant work out how.
Any help greatly appreciated.
Looks like your tabulator definition for thistemplatequestions has duplicates of movableRowsReceiver. Remove the second one (movableRowsReceiver: "insert", //add rows when dropped on the table.) and your customReceiver will then be executed.
EDIT:
According to Tabulator documentation:
Note: The movableRows option must be enabled on both tables to move
rows from one to the other.
It appears that you are missing movableRows: true in the thistemplatequestions Tabulator defintion.
Here is the snippet of code, based on yours, with the modifications I made to get things working:
var thistemplatequestions = new Tabulator("#thistemplatequestions", {
...
movableRows:true,
movableRowsReceiver: customReceiver,
...
});
function customReceiver(fromRow, toRow, fromTable){
//fromRow - the row component from the sending table
//toRow - the row component from the receiving table (if available)
//fromTable - the Tabulator object for the sending table
console.log("New Row Received!");
this.table.addRow(fromRow.getData(), undefined, toRow);
return true;
}
Related
I have both rowclick and rowdblclick handlers for a tabulator table, I'd like to debounce the rowclick handler so I don't get two rowclick's then a rowdblclick firing off whenever I dblclick on a row, is there a built-in method to do this? I'm aware that I can use rxjs and create a subject and debounce, but I would like to use a built in debounce if it exists.
I have a very similar issue - global row/cellClick also fire when column based cellClick fire.
My work around is to place e.stopImmediatePropagation() into the column based cellClick function. This also still allows the rowDblClick event to pass upwards/downwards etc (bubbling?). However, this is probably the reverse of what you need, unless you remove the need for a double click by putting in an event column?
var editIcon = function(cell, formatterParams, onRendered){ //plain text value
return "<i class='fa fa-edit'></i>";
};
var table = new Tabulator("#table", {
columns:[
{title:"Name", field:"name"}, //etc
{formatter:editIcon, headerSort:false, width:40, align:"center", cellClick:function(e, cell){
// do whatever
e.stopImmediatePropagation(); //prevent table wide rowClick() from also triggering
},
],
rowClick:function(e, row){
//all rows/cells will inherit this function, however the cell level cellClick function will take the first bite of the event before bubbling up to rowClick
},
});
Don't know if this helps, probably some more elegant way, but sort of works for me.
This is a standard JavaScript click event behaviour rather than anything specific to Tabulator
Any time you bind a click and double click handler the click handler will be triggered first.
I would suggest that you use a set timeout to detect if the double click has happened, you then make the double click event clear the timeout preventing the click action from happening:
var debounce = null; //hold debounce timer
var table = new Tabulator("#table", {
rowClick:function(e, row){
debounce = setTimeout(function(){
//do something
}, 100)
},
rowDblClick:function(e, row){
clearTimeout(debounce);
},
});
What I ended up doing in the end is using an EventEmitter and doing a .emit and passing the id from the row that was clicked on. Then in my pipe for my subscription to the EventEmitter I did a .distinct, eliminating the second click on the same row when double clicking.
export class XYZComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.tabX = new Tabulator('#xyz', {
columns: [
// 1
{
title: 'clickable column',
field: 'X'
headerSort: false,
// visible: false,
width: '5rem',
cellDblClick: this.itemDblClick.bind(this),
cellClick: this.itemClick.bind(this),
},
//...
]
}
);
}
private itemClick(e, item) {
// both cells and rows have a getData function
this.onItemSelect(item.getData());
}
private itemDblClick(e, item) {
// both cells and rows have a getData function
this.onItemEdit(item.getData());
}
}
export class ABCComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.selectItemSubject
.pipe(
takeWhile(() => this.active)
, distinctUntilChanged() // this will eliminate the second click
)
.subscribe(item => {
// load additional data for item
});
this.editItemSubject.pipe(
takeWhile(() => this.active)
)
.subscribe((item) => {
// do whatever to edit the item
});
}
}
I have a Google spreadsheet template with two sheets, Data Entry and Data Validation. The Data Validation sheet has a number of columns with valid values for matching columns on the Data Entry sheet. Everything works as expected. I need to copy these two sheets to a Sheet Under Test (SUT). I am using the sheets API to copy both sheets. I copy the Data Validation sheet first and then the Data Entry sheet. Here's the code and this appears to work.
const request = {
spreadsheetId :fromSpreadsheetId,
sheetId : fromSheetId,
resource:{
destinationSpreadsheetId: toSpreadsheetId,
},
}
const result = await _sheetService.spreadsheets.sheets.copyTo(request)
On my SUT, both sheets appear and the Data entry sheet has all the expected dropdowns and they all have the proper values. Seems perfect. The problem is when you select an item from any drop down in any column it selects and enters the proper value and then adds a red triangle and the message that an invalid value has been entered. If the column has the rejection setting then the value is removed and the error dialog appears.
The image shows two cells where I have already selected Video Course from the drop down.
If I go in and reselect column where I want validation, use Data→DataValidation… and just hit the Save button that column starts working, so it would appear everything came across correctly but the sheet doesn't think so. Is there any programmatic way to force the above process that I did manually? Is there something else I need to do in the sheets.copyTo method to make this work properly?
EDIT
This project is written in Node.js with a combination of TypeScript and JavaScript. The lower level code for talking to the Sheets API and copying the sheet between spreadsheets can be found in this github file. The method is copySheetFromTo and it's at the bottom of the file.
A sample source sheet with public view permissions
A sample destination sheet with public edit permissions
The integration test that used the above two files to copy the sheets is at the bottom of the file and has 'DEBUGGING TEST' at the beginning of the name (starts at line 209)
You want to copy the sheet including Data Validation.
When the copied sheet is used, an error occurs at the drop down menu of Data Validation.
You want to remove this error.
If my understanding is correct, how about this answer? In this answer, in order to remove the error, I overwrite the Data Validation of the copied sheet as a workaround. Please think of this as just one of several workarounds.
The flow for your situation is as follows.
Flow:
Retrieve all data validations from the sheet of Data Entry in the source Spreadsheet ("DataValidationTest") using the spreadsheet.get method.
Copy the sheets of Data Entry in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
After the sheets of Data Entry was copied, rename the sheet name from Copy of Data Entry to Data Entry.
Then, overwrite the retrieved data validations to the sheet of Data Entry using the spreadsheet.batchUpdate method.
In this case, the structure of data validations retrieved by the spreadsheet.get method is almost the same with the structure for the spreadsheet.batchUpdate method. This workaround used this.
Copy the sheets of Data Validation in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
Rename the sheet name of Copy of Data Validation to Data Validation.
Sample script:
When you test this script, please set the variables. And I think that sheet of sheet.spreadsheets.get(), sheet.spreadsheets.batchUpdate() and sheet.spreadsheets.sheets.copyTo() is the same with sheetOps of your script.
const srcSpreadsheet = "###"; // Please set this.
const tempDestSheetId = "###"; // Please set this.
const srcDataEntrySheetId = 0; // Please set this.
const srcDataValidationSheetId = 123456789; // Please set this.
let dataValidation = await sheet.spreadsheets.get({
spreadsheetId: srcSpreadsheet,
ranges: ["Data Entry"],
fields: "sheets/data/rowData/values/dataValidation"
});
let data = dataValidation.data.sheets[0].data;
let rows = [];
for (let i = 0; i < data.length; i++) {
if (data[i].rowData) {
rows = data[i].rowData;
break;
}
}
sheet.spreadsheets.sheets.copyTo(
{
spreadsheetId: srcSpreadsheet,
sheetId: srcDataEntrySheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
},
(err, res) => {
sheet.spreadsheets.batchUpdate(
{
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: { sheetId: res.data.sheetId, title: "Data Entry" }
}
},
{
updateCells: {
rows: rows,
range: { sheetId: res.data.sheetId },
fields: "dataValidation"
}
}
]
}
},
(er, re) => {
if (err) {
console.error(er);
return;
}
console.log(re.data);
}
);
}
);
let result1 = await sheet.spreadsheets.sheets.copyTo({
spreadsheetId: srcSpreadsheet,
sheetId: srcDataValidationSheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
});
let result2 = await sheet.spreadsheets.batchUpdate({
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: {
sheetId: result1.data.sheetId,
title: "Data Validation"
}
}
}
]
}
});
console.log(result2.data);
Note:
This script supposes that Sheets API has already been able to be used.
This is the simple modified script for showing the flow of workaround. So please modify this for your situation.
As an important point, in order to overwrite the data validations to the copied sheet of Data Entry, it is required to execute it after the copy of the sheet of Data Entry was completely finished. So I modified the script like above.
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.get
Method: spreadsheets.sheets.copyTo
I was wondering if it's possible to have a nested data tree where the child row has different headers that the parent row.
E.g.
Parent row has the following headers: Make, Model, SellDate, Warranty
The data type for the headers above are: String, String, Date, Boolean
The child row will have the following headers: Warranty Start Date, Warranty End Date, Type of Warranty
The data type for the headers above are: Date, Date, String
So I would like to create something like this:
Make, Model, Selldate, Warranty
BMW, S3, 13-DEC-2017, Yes
13-DEC-2017, 13-DEC-2010, Labour & Parts
I just got finished writing this same thing. You can accomplish this with a combination of Nested Data and Row Formatters.
On your outer table configuration, specify:
dataTree: true,
rowFormatter: childRowFormatter,
Then, for the rowFormatter function, you can specify something like the below (following the lead from Tabulator's own Nested Tables example):
function childRowFormatter(row) {
// skip if not a child/nested data row - let main table rows render as usual
if (!row.getTreeParent())
return;
var rowEl = row.getElement();
var parentChildData = row.getTreeParent().getData()._children;
// only generate a new table for the first row of child data
if (
row.getPrevRow().getIndex() !== row.getTreeParent().getIndex() ||
row.getPrevRow().getPosition() < 0
) {
rowEl.remove();
// set row element to empty element without size
row._row.element = document.createElement('span');
return;
}
// remove generated cell elements
row._row.deleteCells();
// create and style holder elements
var holderEl = document.createElement("div");
var tableEl = document.createElement("div");
holderEl.style.boxSizing = "border-box";
holderEl.style.padding = "10px 30px 10px 10px";
holderEl.style.background = "#ddd";
holderEl.appendChild(tableEl);
// replace with holder
rowEl.appendChild(holderEl);
// create the new table
var subTable = new Tabulator(tableEl, {
data: parentChildData,
columns: [
{ ... },
...
],
dataTree: true,
dataTreeStartExpanded: true,
rowFormatter: childRowFormatter,
});
}
EDIT: I updated my code after realizing it was a bit more intricate than first revealed. Be cautious, as this code uses some undocumented properties and methods that weren't meant to be exposed to the developer.
I understand SharePoint lists are like excel, so I was wondering if it was possible to conditionally highlight whole rows/ cells based on the text value of a field.
I have a column in a list called "Status" with 4 options (initial, in progress, completed, awaiting developer resource). I would like to highlight these rows (or even just the status field) a different colour, depending on the value of the status.
Is this possible? Cant find anything relating to this for SP 2016
Cheers
Please use JavaScript to highlight the row based on the Status field:
<script type="text/javascript">
SP.SOD.executeFunc("clienttemplates.js", "SPClientTemplates", function() {
SPClientTemplates.TemplateManager.RegisterTemplateOverrides({
OnPostRender: function(ctx) {
var statusColors = {
'initial' : '#FFF1AD',
'in progress' : '#FFD800',
'completed' : '#01DF3A',
'awaiting developer resource':'#ff0000'
};
var rows = ctx.ListData.Row;
for (var i=0;i<rows.length;i++)
{
var status = rows[i]["Status"];
var rowId = GenerateIIDForListItem(ctx, rows[i]);
var row = document.getElementById(rowId);
row.style.backgroundColor = statusColors[status];
}
}
});
});
</script>
And place the code above in a Content Editor Web Part in the list view page, so the list row will render different color based on status:
I have a table pre-populated with the company LAN IP addresses with fields for associated data, status, etc. The (jquery-)jtable fields collection is configured like this.
fields: {
id: { title: 'ID'},
ip: { title: 'IP address, edit: false }
more: { ... }
}
This works but the problem is that when the edit dialog pops up the user can't see the ip address of the record being edited as jtable's edit form doesn't show the field.
I've read through the documentation but can't see any way to display a field as read-only in the edit form. Any ideas?
You don't need to hack the jTable library asset, this just leads to pains when you want to update to a later version. All you need to do is create a custom input via the jTable field option "input", see an example field setup to accomplish what you need here:
JobId: {
title: 'JobId',
create: true,
edit: true,
list: true,
input: function (data) {
if (data.value) {
return '<input type="text" readonly class="jtable-input-readonly" name="JobId" value="' + data.value + '"/>';
} else {
//nothing to worry about here for your situation, data.value is undefined so the else is for the create/add new record user interaction, create is false for your usage so this else is not needed but shown just so you know when it would be entered
}
},
width: '5%',
visibility: 'hidden'
},
And simple style class:
.jtable-input-readonly{
background-color:lightgray;
}
I have simple solution:
formCreated: function (event, data)
{
if(data.formType=='edit') {
$('#Edit-ip').prop('readonly', true);
$('#Edit-ip').addClass('jtable-input-readonly');
}
},
For dropdown make other options disabled except the current one:
$('#Edit-country option:not(:selected)').attr('disabled', true);
And simple style class:
.jtable-input-readonly{
background-color:lightgray;
}
I had to hack jtable.js. Start around line 2427. Changed lines are marked with '*'.
//Do not create element for non-editable fields
if (field.edit == false) {
//Label hack part 1: Unless 'hidden' we want to show fields even though they can't be edited. Disable the 'continue'.
* //continue;
}
//Hidden field
if (field.type == 'hidden') {
$editForm.append(self._createInputForHidden(fieldName, fieldValue));
continue;
}
//Create a container div for this input field and add to form
var $fieldContainer = $('<div class="jtable-input-field-container"></div>').appendTo($editForm);
//Create a label for input
$fieldContainer.append(self._createInputLabelForRecordField(fieldName));
//Label hack part 2: Create a label containing the field value.
* if (field.edit == false) {
* $fieldContainer.append(self._myCreateLabelWithText(fieldValue));
* continue; //Label hack: Unless 'hidden' we want to show fields even though they can't be edited.
* }
//Create input element with it's current value
After _createInputLabelForRecordField add in this function (around line 1430):
/* Hack part 3: Creates label containing non-editable field value.
*************************************************************************/
_myCreateLabelWithText: function (txt) {
return $('<div />')
.addClass('jtable-input-label')
.html(txt);
},
With the Metro theme both the field name and value will be grey colour.
Be careful with your update script that you're passing back to. No value will be passed back for the //edit: false// fields so don't include them in your update query.
A more simple version for dropdowns
$('#Edit-country').prop('disabled',true);
No need to disable all the options :)