Styling with CSS

I have a plain WebCore App with some WebEdits and WebGrids. I want to style them by providing my own CSS. I add the CSS via the library manager and can see the class definitions in the ElementClass property of the components.

I started with setting my WebGrid to ElementClass "grid" and added to my CSS
.grid {
color: red;
}

This however did not work. Looking in the console I saw, that the grids are constructed by cascading tables and I can´t figure out, how I have to build my CSS to style the grid.

What i wanted to do:

  • setting the background
  • setting an overall font
  • making the controls border rounded and colored upen receiving focus

Thanks for giving me some hints!

You can do a lot with CSS styling and TMS WEB Core :grin: Using Bootstrap with TMS WEB Core is one way to get started if you're going to be doing a lot of this kind of thing. Another is to use the approach in this blog post about TWebString Styling.

Please keep in mind there are likely dozens of ways to do all of this. Which method works best for you depends on where you want to spend your efforts. I'm a big fan of doing as much as possible direclty in CSS, for example. But it may be better (easier, cheaper, faster, or any combination of the three) to do all of the styling just by using IDE component properties. Or by writing Delphi code to set those component properties. Or using Delphi code to set the CSS properties of elements in the page directly rather than using a separate CSS at all. There are even more ways to do this, but that's one of the benefits of TMS WEB Core - lots of options and often it is a combination that gets you to where you want to be.

But to just get started with your list, you're off to a good start by setting the ElementClassName properties of your components to something that you can then locate with CSS.

For TWebEdit components, you can try setting the ElementClassName property to just TWebEdit. Note that CSS and JavaScript are both case-sensitive. The CSS would then look something like this:

.TWebEdit {
  color: white;
  background-color: darkred;
  border-width: 2px;
  border-color: black;
  border-radius: 5px;
  padding: 20px 10px;
}

If you want it to be styled differently when it has focus, you can have a second CSS stanza that looks something like this, where you just add attributes for whatever you want to be different from the non-focused version. There's also no limit to the styling options, including things like drop shadows.

.TWebEdit:focus {
  background-color: darkblue; 
  filter: drop-shadow(0px 0px 4px black);
}

The results should look something like this. The first is unstyled. The second is with the regular styling applied. And the third has focus.

image

Styling of grids is indeed much more complicated. If you add a TWebStringGrid to your form and set the ElementClassName property to TWebStringGrid, the CSS can then initially be set in the same way, but there's more to the story here. For example. try this.

.TWebStringGrid {
  background-color: darkred;
  border-width: 2px;
  border-color: black;
  border-radius: 5px;
  padding: 10px;
  height: auto !important;
  overflow: visible; !important;
}

.TWebStringGrid:focus {
  background-color: darkblue;
  filter: drop-shadow(0px 0px 4px black);
}

You should see something like this.

Note that the 'focus' only changes when you click on the border of the table, not on one of the cells of the table. And that the background of the non-fixed cells is transparent, so you see the background color through the table. So it's a start, but just barely.

To address the focus issue, you can adjust the CSS to use a class to indicate when the TWebString grid is active, and then use a bit of Delphi code to add or remove that class. So instead of just .TWebStringGrid:focus we can do the following.

.TWebStringGrid.Active,
.TWebStringGrid:focus {
  background-color: darkblue;
  filter: drop-shadow(0px 0px 4px black);
}

Then, back in Delphi, you have to figure out what is going to focus the grid and then set the class when that happens. Trouble ahead though, as it isn't an easy thing to determine when to take away the class. This is a bit of a problem with JavaScript, not really anything to do with TtMS WEB Core. Normally you might handle this in JavaScript with some kind of 'blur' event handling, but there isn't an easy choice here. One way around it is to just remove the Active class whenever something else gets the focus, by calling a function to do it. You would need to add DeactivateGrids to any other controls on the same page that get focus, so they too will remove the Active class from the grid. Kind of overkill but this does the trick no matter how many you have.

procedure TForm1.DeactivateGrids;
begin
  // find all the TWebStringGrids that are active and then deactivate them
  asm
    var grids = document.querySelectorAll('.TWebStringGrid.Active');
    for (var i = 0; i < grids.length; i++) {
       grids[i].classList.remove('Active');
    }
  end;
end;

procedure TForm1.WebEdit1Enter(Sender: TObject);
begin
  DeactivateGrids;
end;

procedure TForm1.WebStringGrid1Click(Sender: TObject);
begin
  DeactivateGrids;
  (Sender as TWebStringGrid).ElementHandle.classList.add('Active');
end;

Styling the rest of the grid is then just a matter of figuring out what you want to change and adding the CSS selector that goes with it. The real power of CSS is that, so long as you don't hit a <canvas> tag anywhere (ahem... FNC components....) , you can pretty much override anything with CSS. But just because you can doesn't mean you should. In this case, for example, it is more than a little bit of a pain to do and would likely need to be redone with even the slightest changes to a default TWebStringGrid. But if you wanted to, you could. Here's one way to do it.

/* Change the cells for the first row and first column to be red */
/* first cell */
/* rest of first row */
/* rest of first colum */
.TWebStringGrid.Active > table > tbody > tr:first-child > td:nth-child(1) > div > table > tbody > tr > td,
.TWebStringGrid.Active > table > tbody > tr:first-child > td:nth-child(2) > div > div > table > tbody > tr > td,
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td > div > table > tbody > tr > td {
  color: white !important;
  background-color: red !important;
}
/* Change the rest of the cells to be green */
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td:nth-child(2) > div> div > table > tbody > tr > td {
  color: white !important;
  background-color: green !important;
}
/* change the Highlighted cell to be purple */
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td:nth-child(2) > div> div > table > tbody > tr > td.selected {
  color: white !important;
  background-color: purple !important;
}
/* Aliens' Sgt. Apone: Check those corners. CHECK THOSE CORNERS! https://www.youtube.com/watch?v=khDcdiz6PwY */
.TWebStringGrid.Active > table > tbody > tr:first-child > td:first-child > div > table > tbody > tr:first-child > td:first-child,
.TWebStringGrid.Active > table > tbody > tr:first-child > td:first-child {
  border-top-left-radius: 5px;
}
.TWebStringGrid.Active > table > tbody > tr:first-child > td:last-child {
  border-top-right-radius: 5px;
}
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td > div > table > tbody > tr:last-child > td,
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td:first-child {
  border-bottom-left-radius: 5px;
}
.TWebStringGrid.Active > table > tbody > tr:nth-child(2) > td:last-child {
  border-bottom-right-radius: 5px;
}

So it would likely be best to just use the properties in the IDE in this case. This is partly because there are numerous tables embedded within this table in all kinds of ways that, honestly, I'm at a loss to explain. It all works, so no criticism intended, just that it isn't the most fun thing to try and figure out.

Also, the blog post linked at the outset shows how easy it is to use Delphi to adjust the individual cells if you wanted to highlight certain values, for example. Not so easy to do that with CSS. But you could if you add classes to cells to indicate whatever exception, and then use the CSS to color them according to those exceptions.

As a side note, scrollbars are a headache because browsers implement them all themselves, and often differently from one another. In most cases, it would be best to try not have scrollbars, particularly horizontal scrollbars, but this is a troublesome area for sure. Styling for custom scrollbars is not an easy thing to do, but is possible at least some of the time.

The last part of your question deals with fonts. This can be both easy and less easy, depending on what it is you're wanting to change. For example, you can define a default font size and weight for the page using something like this.

body{
  font-size:24px !important;
  font-weight: 900 !important;
}

Might be a good choice just to be certain that you've set it elsewhere. You'll notice if you haven't :slight_smile: But pretty much anything and everything that comes along will override it with something else. So this is likely not going to be a visible change unless you just have something like TWebLabel components in your form.

Within TMS WEB Core, there is a component property called ElementFont that can be set to either efCSS or efProperty. If it is efProperty (likely the default), the font settings for the component in the IDE override pretty much everything else. If you want to adjust the fonts in CSS you'll first need to change that to efCSS on the component you're modifying. Note also that the form itself has this property and that will be used by everything. So if you're looking for the TMS WEB Core way to change everything at once, just change the font properties on the form itself and you might be done!

In CSS though, Individual elements sometimes have their own defaults depending on numerous other factors. Best to look through the elements with your browser developer tools to see what's overriding the body definition. Often these are things that are at a very low precedence, so anything else will override them. Adding font-size and font-weight to the .TWebEdit stanza that was shown earlier on would do this pretty well without anything more required.

But maybe you want to change the font family? Say, for example, you don't much care for Arial. Can't say I blame you! Thre is a CSS property that can be used just like font-size and font-width called font-family. But in order for that to work you first must have the fonts available on your page. Google Fonts are a popular choice, and they'll give you the links you'll need to add to your Project.html file in oder to get it to work.

Which reminds me. If nothing here is working for you at all, then it could be that the CSS file you're using isn't linked to your project. I've not tried using the Javascript Library Manager to do this, so not sure if that would work. But the usual approach (I think?) is to create a new CSS file and add it to your project, just like if you were adding a new form. And then you have to add this CSS file to your Project.hml file so that it gets loaded.

For example, in my version of Delphi, I'd go to File > New > Other... and then Other > Web > CSS Stylesheet. This will create an Untitled.css file in your project, which can be renamed to something like stylish.css. Then, in your Project.html file, you would add a line like this to the section.

    <link href="stylish.css" rel="stylesheet"/>

One final note is to be sure that you know how to do a hard-refresh in your browser as they tend to like to cache CSS files, so you could be making changes to it and not seeing them because the browser is using an older version. For Firefox, this is likely to be Ctrl+Shift-R or Ctrl-F5. I think Ctrl+Shift-R also works in Chrome, but if you hit F12 to open the developer tools and then long-click on the refresh button you'll see a "empty cache and refresh" option which is what I tend to use the most.

I think I covered everything. But like I mentioned at the outset, there are lots of ways to do all these things, so someone might come along and call me crazy and suggest completely different ways to do any of these things!

Andrew,
thanks a lot for this detailed answer. In fact, I find it difficult to close the gap between my (limited) knowledge of CSS and the way (well known) Delphi components are finally rendered. I have to change the way of looking at things, maybe.

Anyway - this is what we call a learning curve.

Thanks again + greetings from Vienna, Austria!
Bernd

Happy to help! What comes to mind when I think about Vienna is its famous waltz. Quite difficult to master! Way more difficult in fact than what we're dealing with here :laughing:

Rendering a Delphi control is often done by converting it to a standard HTML control and then making adjustments to make it more Delphi-like. TWebEdit translates to <input>. TWebMemo translates to <textarea>. TWebComboBox translates to <select>. That sort of thing. And apparently a single TWebStringGrid translates to numerous nested <table> elements. This is of course how the web works - HTML at its most basic is just a set of elements on a page, after all. If you just work within the IDE, this really doesn't really matter and you can carry along quite happily without having to think about it much. But when you want to use CSS to do the heavy lifting of styling your app, or extending your app with other JavaScript libraries, then it matters a great deal.

So potentially a pretty big learning curve, but hopefully when people have questions they'll find helpful answers here that will shorten that up nicely. Please have a look at the Tips for New TMS WEB Core Developers post and try and add something to it that you've learned already that might help others facing the same learning curve, no matter how trivial. :+1: :slight_smile:

But the biggest question is whether you were able to accomplish what you were after? I've been thoroughly surprised and impressed that I've been able to do virtually anything I've set out to do with the stock TMS WEB Core components and CSS styling and a bit of JavaScript here and there. Way more flexibility and control (and development speed) than even what is possible from the VCL environment in many ways. So if you're struggling to do something, please make a new post about it. As a community, we'll all learn more and have better apps if we see these kinds of challenges and find ways to overcome them.