Web Core and Tabulator

Thanks Andrew

Keep the questions coming! There is a lot that Tabulator can do all on its own with a little nudge here and there. It is also quite feasible to edit data in the table via all kinds of mechanisms, so depending on what you're trying to do, there's almost certainly something available to help. And interacting with a Tabulator table from within TMS WEB Core is also super-easy once you get past the first few steps.

What is the best way to change the default colors?

Oh sure, start with the easy questions :grinning:

For starters, I'd direct you to the Tabulator theme documentation, where you can see a few links for themes that you can add by adding a CSS link, for Bootstrap 5 or something like that.
Tabulator Themes

eg: /dist/css/tabulator_midnight.min.css

Beyond that, or buried within that, is the idea that this is 100% HTML and CSS and not a <canvas> tag to be found anywhere at all (yay!) so you can actually customize anything you like by overriding CSS styles with new values. If you already have a theme for your project, it is likely you can add (admittedly probably at least a dozen) CSS overrides to get everything just the way you want. If you're familiar with SCSS, that's also a good option, but likely well beyond what you need if you're just making a few changes.

There are specific things you can change with some of the options when the table is created. Like if you wanted different arrows for sorting (you can use FontAwesome icons here), or different placeholder text (what appears when no data is loaded). Icons can also be included in the header titles, and drop-down menus (for selecting columns or filtering) can also be added, that use the same kinds of CSS.

There are also options that control row selection, whether you want row selection at all, one row at a time, multiple selections, and that sort of thing.

So I guess the question I should've asked at the outset was... What kinds of changes do you want to make, specifically?

They describe how to change the sort icons on this page:
Header Sort Icon

I've changed the sort icons to use the FontAwesome DuoTone icons. But I also link to a FontAwesome JS+SVG kit, so my CSS looks like this:

.tabulator-col .tabulator-col-sorter svg {
  display: none;
}
/* Display the fa-sort icon when the column is not sorted */
.tabulator-col[aria-sort="none"] .tabulator-col-sorter svg.fa-sort {
  display: inline-block;
  color: #aaa;
}
/* Display the fa-sort-up icon when the column is sorted in ascending order */
.tabulator-col[aria-sort="asc"] .tabulator-col-sorter svg.fa-sort-up {
  display: inline-block;
  color: #000;
}
/* Display the fa-sort-down icon when the column is sorted in descending order */
.tabulator-col[aria-sort="desc"] .tabulator-col-sorter svg.fa-sort-down {
  display: inline-block;
  color: #000;
}

sorting

There's also a list of CSS styles you might want to override that can be found here.
Tabulator Styling

That might be a bit much to take in all at once. The suggestion on the page is actually to look at the CSS that is generated using the developer console and then adjust accordingly, so you get the right specificity. Or take one of their CSS files and change the values contained within it. There are a lot of CSS rules though, so to be thorough would take a bit of effort.

Thanks Andrew. My requirements are quite simple. white background, darkorange header text, black text for the resr. No borders/grid lines other than one straight darkorange line under the headers.

Also having problems with Luxon. I am retrieaving some time stamps from OpenSea which seem pretty standard : "2022-05-15T07:35:54.405522" but I cannot manage to display these in the local date/time format.

DTFormat:=FormatSettings.ShortDateFormat.Replace('MM','LL')+' HH:mm:ss';
asm
    this.gTab = new Tabulator("#gridSample", {
      layout: "fitColumns",
 	    columns: [
        {field: "EVENT", title: "Event", minWidth: 100 },
        {field: "TIMESTAMP", title: "Date/Time", width: 200, resizable: false,
          formatter:"datetime", formatterParams:{inputFormat:"iso", 
          outputFormat: DTFormat,
          invalidPlaceholder:"(invalid date)" }},
        {field: "NAME", title: "Name", minWidth: 100 },
        {field: "QUANTITY", title: "Quantity", width: 100, resizable: false }
      ]
    });
end;

This works but seems a bit Heath Robinson!
1 Like

Have more-or-less got it to look like what I want:

image

CSS:

.tabulator {
  border: 0px;
  background-color: white !important;
}
.tabulator .tabulator-header .tabulator-col {
   border-right: 0px;
   border-bottom: 3px solid darkorange;
   background-color: white !important;
   background: white !important;
   color: darkorange;
}
.tabulator-row .tabulator-cell {
   border-right: 0px;
   background-color: white !important;
}
.tabulator-row.tabulator-selected {
   background-color: darkorange !important;
   background: darkorange !important;
}

Only problem is I can't manage to select a row with a mouse click. I have checked the documentation and have added selectable: 1 but still doesn't work!

1 Like

I think what's overly complicated there is that you're trying (successfully!) to convert a Delphi date format into a Luxon date format which is admittedly an unusual thing to want to do. I totally understand why you're doing it, but it's a strange thing to do, all the same.

If all of your dates are system-generated, you can probably leave off the bit about invalidPlaceholder as that shouldn't ever be needed. In which case, the formatter is just converting from ISO to your particular format. Hard to simplify that further.

I don't know that it would simplify anything, but you could also use what Tabulator refers to as a "mutator" function, to convert the data as it is coming into Tabulator. So you could convert the datetime fields at that point, or any other data that is in a less-than-desirable format. I say it probably won't help as you still want to have a date format that Tabulator can use to sort properly, which ISO is already well-suited to. You might want to use that if you had dates that were not already in an ISO format to convert them into an ISO format, for example.

In the upcoming Luxon blog post I kinda went off on a tangent about date formats and all the silliness that is involved, and whether any of it is really of benefit to anyone. Be sure to check it out. Intended to be light-hearted as this is all stuff that everybody struggles with at some level (and necessarily so), but at the same time, I'm sure there are strongly held opinions by many about all of these kinds of things, myself included.

1 Like

Let's call this "fun with CSS". In this case, your .tabulator-cell is overriding your row. You can try changing that last stanza to look like this:

.tabulator-row.tabulator-selected .tabulator-cell {
   background-color: darkorange !important;
   background: darkorange !important;
}

And I think you'll find it works better.

There are also things within the column definitions that you can do instead of CSS. For example, add hozAlign: "right" to your Quantity column to right-align the data, if you like.

1 Like

One more little tip for you. I actually just figured this out last week. Hard to believe I didn't figure it out sooner in retrospect, but maybe I'll add it to the TIps & Tricks list.

When you're using JavaScript code within asm... ...end blocks, you can access other Delphi parts of your form using a "this." prefix. Works pretty well. Use it all the time. Great for form variables like we're using here, but also for calling Delphi functions.

However, when you have a JavaScript object, like a Tabulator table, that has callback functions or event handlers or things of that nature, these get called in a different context. And the general convention seems to be that "this." refers to the object that is generating the event or callback or whatever, thus leaving out the possibility of referencing Delphi objects using that prefix.

The trick then is to define a local JavaScript variable that references the Delphi thing you're interested in using and then use that to perform the call. So for our current example, it might work like this.

procedure TForm1.WebFormCreate(Sender: TObject);
begin

  // Define an empty table, attach it to the TWebHTMLDiv component that has an ElementID of 'tabExample'
  asm
    this.tabExample = new Tabulator("#tabExample", {
      layout: "fitColumns",
      selectable: 1,
 	    columns: [
        {field: "NAME",   title: "Name",     minWidth: 100                   },
        {field: "DOB",    title: "Birthday", width:    145, resizable: false,
          formatter:"datetime", formatterParams:{inputFormat:"yyyy-MM-dd", outputFormat:"yyyy-MMM-dd (ccc)", invalidPlaceholder:"(invalid date)" }},
        {field: "ROLE",   title: "Role",     minWidth: 100                   },
        {field: "POINTS", title: "Points",   width:     75, resizable: false, hozAlign: "right" }
      ]
    });

    var DelphiRowClick = this.RowClick;
    this.tabExample.on("rowClick", function(e,row){
      DelphiRowClick(row.getCell(this.getColumn('POINTS')).getValue());
    });

  end;

end;

procedure TForm1.RowClick(Points: Integer);
begin
  showmessage('Points of selected row: '+IntToStr(Points));
end;

There are many Tabulator "on" events that can be put to use, and you can also just lookup stuff whenever you want without using those, so plenty of options for further integration here.

1 Like

Thanks Andrew. That does the trick. CSS is not my strong point!

1 Like

Thanks Andrew. All of the dates are coming from OpenSea and are UTC so I should probably convert them to local date/time anyway.

I have another simple one for you :grinning:

I want to use remote pagination but provide the data myself rather than pass the url as it has to be manipulated before being loaded into tabulator.

Hi Andrew,

Don't bother with this as from the OpenSea call I am making I can only get the next and previous cursors so I will just have to implement my own Next and Previous buttons.

Regards,

Ken

I think Tabulator can likely handle all of this kind of thing for you directly. It has support for remote pagination and can deal with different page sizes, multiple requests and so on. I guess the question is about what kind of manipulation you're doing to the data, and whether you can do that as it is streaming into the table? Mutators can be used to create the (roughly) equivalent of calculated fields, which can in turn even call Delphi functions to do the work if necessary. This is more complicated if you're doing some kind of data join, but not at all out of the question.

If you can do that, then Tabulator should be able to handle the rest. I don't have a data source that restricts data in this way to use for a demo but I could toss together something to show how the mutator system works?

Tons of info in this page: Tabulator Pagination

As a side note, you have to be careful when using Google to search for Tabulator documentation as it almost always links to an older version of the docs.

Thanks Andrew. The main problem with trying to use tabulator pagination is that the data I am retriieving does not provider the total number of events that are available. The only way get this is to keep retrieving the data with a max of 50 events each time until it fails.This is also throttled if requested again within a short period of time. What I have done instead is just have a more button and then add the events to tabulator each time. I will be looking at alternate ways of retrieving the data.

Sounds like a plan. Tabulator can also manage the URL being generated to request the data, so it may save you some work there, depending on how the request is actually being made.

A perhaps more important question is whether you need to do anything to the data before presenting it to Tabulator. There is a lot you can do with a mutator function or two, and there's the possibility to filter out records as well, but if you have to create records from the source data, that is not so easy.

I only need about half a dozen fields from the download. Unfortunately I can't send you a link as it requires headers. The only change I am making is to change a date/time from UTC to local.