Using CodeMirror as alternative to TWebMemo or Ace Editor

This post is more of a discussion about how I've setup CodeMirror in my project rather than a specific support request, but there may be some questions as I go through this :slight_smile:

When displaying text, TWebMemo works pretty well, but not very fancy when it comes to things like syntax highlighting. Ace Editor also works pretty well. With a bit of effort, I was able to bend it to my will for the most part. Nothing here is intended as criticism of either component. Just that in the world of web development, if things aren't to your liking for any reason at all, there are likely alternatives.

For me there were three with Ace Editor. (1) The horizontal scrollbar would show up even when the text was barely half the width of the control. (2) The scrollbar would disappear entirely on iOS. (3) While many themes are available, adjusting the theme specifically to match my website themes seemed to be more difficult than changing my themes to match Ace Editor - not really how it should be. I don't particularly want to dwell on these as they may have solutions or may be non-issues for most people, just that I spent time trying to resolve them and it was easier to switch to something else.

So. CodeMirror is a project similar to Ace Editor in that it provides syntax highlighting for a number of languages, themes and the rest of it. I'm not affiliated with them in any way, but I've used it before on a project as the HTML editor behind a Drupal website. Seemed to work well in that environment.

To get this working in Web Core was pretty straight forward. Starting as usual with the CDN links to get it loaded up. In this case I've included bits for a custom scrollbar and the syntaxes that I'm interested in. Could probably pare this down a bit. There are themes that can also be added in this way, so check out the Code Mirror site if that is of interest.

    <!-- CodeMirror -->
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/lib/codemirror.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/addon/scroll/simplescrollbars.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/addon/scroll/annotatescrollbar.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/addon/scroll/scrollpastend.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/mode/htmlmixed/htmlmixed.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/mode/sql/sql.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/mode/xml/xml.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/codemirror@latest/mode/css/css.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/codemirror@latest/lib/codemirror.min.css" rel="stylesheet"/>
    <link href="https://cdn.jsdelivr.net/npm/codemirror@latest/addon/scroll/simplescrollbars.min.css" rel="stylesheet"/>

To create the control on the page, I added a TWebHTMLDiv component to a form and set its ElementID to cmMemo. In the interface for the form, I added a variable to reference the javascript object:

var
  cmMemoObject: JSValue;

And then in the form's WebFormCreate procedure I create the CodeMirror object. In this case, it starts out as an empty, read-only variant, with SQL-style syntax highlighting and my own theme and fancier scrollbars - all customizable to other things of course:

  asm
    this.cmMemoObject = CodeMirror(document.getElementById("cmMemo"), {
      value: "",
      mode:  "sql",
      readOnly: true,
      theme: "custom",
      scrollbarStyle: "overlay"
    });
  end;

In this example, I'm using the memo to display log entries, so adding text and scrolling to the bottom of that text is the objective. Seems to work well:

procedure TfrmWhatever.LogThis(LogData: String);
var
  LogString: String;
begin
  LogString := chr(10)+LogData;
  asm
    this.cmMemoObject.replaceRange(LogString, {line: Infinity});
    this.cmMemoObject.execCommand("goDocEnd");
  end;

  PreventCompilerHint(LogString);
end;

Some additional notes about this.

  1. When passing data to/from JS, it seems easier to create a new local variable rather than trying to mess about with JS syntax. Here it would probably be easy enough to add the chr(10) directly in JS but in other cases it may involve adding strings together or using variables from other forms, where the extra period or whatever is enough of an obstacle that this approach has become a habit.
  2. When declaring a variable in this way and only using it in the JS block, a compiler hint is generated. This happens often enough that I've added functions that do nothing (and hopefully the compiler optimizes them out completely) but they help suppress the unwanted hints. Not sure if there is a better way?

Now one of the benefits of CodeMirror is also that the entire control is drawn with HTML and CSS (and no sign of a canvas element anywhere :+1:t2:). So adjusting to fit into a theme becomes entirely possible. In my case I was interested in replacing the scollbars and having the theme mesh with the theme of my website. Rather than having to adjust my website to fit the themes available. CodeMirror has many themes as well (just like Ace) so this can be skipped.

But to give a bit of an idea of the end result, I've attached a video clip showing what this looks like. CodeMirror Example screencap.

Very interesting & very cool to learn.
Maybe, eventually this is something to add at: TMS Software | WEB Partners ?

Maybe. Or perhaps TMS will setup a showcase section where apps or projects developed with Web Core (and other TMS products), and developed by happy TMS customers rather than either TMS itself or product vendors, can be put on display? Maybe showing off how well and how easily Web Core integrates with other components or projects that are popular generally for web deveopment.

In the VCL world, most apps I imagine are hidden away in corporate silos. And would likely be limited to screenshots or screencap videos. But with WebCore I'm guessing the percentage of projects that are directly public-facing are waaaaaay higher so maybe this is of more value.

I'm planning on doing a similar writeup for Sun Editor and Tabulator as well, maybe even FlatPickr, all elements in my current project that work really well with a similar amount of non-effort. But that indirectly compete perhaps with other TMS offerings. My allegiance/fascination of late is with TMS Web Core and XData though. FNC perhaps not so much :grin:

A showcase would be nice as well, I was just thinking that a reusable encapsulation in an Object Pascal class would help others getting started faster.
We could also do a blog article describing & showing things.
There is such richness of existing web functionality that we welcome all of it, there is so much that the reality is that our team can never wrap everything that exists in the web world.

I think the hands-down best thing about Web Core is that you don't need a wrapper at all to do this sort of thing.

Adding classes via the Project menu seems to trip up people as much as help them, after all (see what happens when trying to incorporate a newer version of Ace for example), and perhaps they'd be better served being more aware of how easy it is to do that themselves. And then there are the pros/cons of using a CDN (and specifying "latest" vs. a specific version) vs. having the files hosted locally.

All of this can be good, very good in fact, but having a wrapper isn't really necessary any more.

It requires more effort to create but for novice users especially but also for the RAD experience, having access to functionality via a Object Pascal component is and will remain nice.

I do agree. Maybe as my work progresses, I'll find the time to figure out how to build these kinds of wrappers. Certainly would be more convenient than fiddling about with TWebHTMLDiv components and would be handy to have some of the extra properties available more directly as well.