Monday, June 24, 2013

Doing Bad Things with Web Components

‹prev | My Chain | next›

Tonight, I continue to explore web components in the web-ui packages for Dart. I was able to get a proof of concept web component up and running last night, using the ICE Code Editor Dart code that I have been so heavily involved with recently.

As web components go, it was fairly useless—there was no data binding, composition, extension. Heck, in the end, I did not even use the <template>, making my web component little more than a cheap, albeit pretty, way to instantiate a full-screen IDE-lite version of ICE. Tonight, I would like to explore supplying data to the web component constructor.

Based on what I know about web components, which admittedly is not much at this point, my sense is that what I want is not a valid web component use-case. But that never stopped me before!

(and really, abusing any kind of technology is the best way to understand it)

What I want is to supply content to be consumed, but not immediately inserted in the web page DOM. I want to supply code for the ICE Code Editor to edit, not to be displayed right away. In other words, something like the following should create an instance of the code editor with the following already loaded into the editor:
  <ice-code-editor>
    <body></body>
    <script>
      console.log("Don't want to see this on page load");
      document.addEventListener('keydown', function(e) {
        console.log(e.keyCode);
      });
    </script>
  </ice-code-editor>
This should prove a challenge because (a) depending on how the <body> tags are interpreted, problems could ensue and (b) I do not want any of that JavaScript actually executed.

So the question becomes, can I slurp that into my web component before it is interpreted by the DOM and/or executed? The answer to the first challenge is that I am not even going to get a chance—compiling my web component chokes on those extra <body> tags:
➜  public git:(web-ui) ✗ dart --package-root=packages/ packages/web_ui/dwc.dart --out web_ui/ web_ui.html
warning web_ui.html:9:5: Unexpected start tag (body).
    <body></body>
    ^^^^^^
warning web_ui.html:9:11: Unexpected end tag (ice-code-editor). Missing end tag (body).
    <body></body>
          ^^^^^^^
warning web_ui.html:10:5: Unexpected start tag token (script) in the after body phase.
    <script>
    ^^^^^^^^
I may still have options there, but I want to focus on answering the second challenge first. Can I prevent the JavaScript from being executed? To find out, I temporarily remove the <body> tags from the <ice-code-editor> tag, leaving just the JavaScript that I want to consume, but not evaluate:
  <ice-code-editor>
    <script>
      console.log("Don't want to see this on page load");
      document.addEventListener('keydown', function(e) {
        console.log(e.keyCode);
      });
    </script>
  </ice-code-editor>
Now, in the web component class, I print the contents of the element as soon as possible. In the case of Dart's WebComponent, that seems to be the create() callback method:
  <element name="ice-code-editor" extends="div">
    <template>
      <!-- Placeholder for template. Live elements added to BODY. -->
    </template>
    <script type="application/dart">
      import 'package:ice_code_editor/ice.dart' as ICE;
      import 'package:web_ui/web_ui.dart';

      class IceCodeEditor extends WebComponent {
        Full ide_lite;
        IceCodeEditor() {
          ide_lite = new ICE.Full();
        }
        created() {
          print(innerHtml);
        }
      }
    </script>
  </element>
If I have any hope of removing the <script> tags before they are evaluated the web component will have to print before the <script> code does. Unfortunately, the <script> tag code is evaluated before the web component code. When I recompile the web component and reload, I see the following output in the JavaScript console:
Don't want to see this on page load web_ui.html:10

    <script>
      console.log("Don't want to see this on page load");
      document.addEventListener('keydown', function(e) {
        console.log(e.keyCode);
      });
    </script>
Ah well, that is not too surprising. It would have been nice to be able to insert raw HTML (including <body> and <script> tags) inside my web component element, but there are other approaches. If there is a silver lining to this experiment, I have a way to read that content with the innerHtml property of WebComponent, which works as early as the create() callback (it does not work in the constructor).

Armed with that bit of knowledge, I believe that including the content in an HTML comment might work:
  <ice-code-editor>
<!--
<body></body>
<script>
  console.log("Don't want to see this on page load");
  document.addEventListener('keydown', function(e) {
    console.log(e.keyCode);
  });
</script>
-->
  </ice-code-editor>
To see this in action, I switch from the full-screen version of ICE to just the core ICE (the editor + preview). I do not need the constructor anymore—all of the action takes place in the created() and inserted() callbacks. Once the web component is created, I set the ID and some styles and grab the content of <ice-code-editor>. Then, when the web component is inserted into the DOM, I create an instance of the ICE editor:
<element name="ice-code-editor" extends="div">
    <template>
      <!-- Placeholder for template. Live elements added to BODY. -->
    </template>
    <script type="application/dart">
      import 'package:ice_code_editor/ice.dart' as ICE;
      import 'package:web_ui/web_ui.dart';

      class IceCodeEditor extends WebComponent {
        ICE.Editor ice;
        String content;

        created() {
          id = 'ice';
          style
           ..width = '600px'
           ..height = '400px';

          content = innerHtml.
            replaceFirst(new RegExp(r'^\s*<!--\s*', multiLine: true), '').
            replaceFirst(new RegExp(r'\s*-->\s*$', multiLine: true), '');
        }

        inserted() {
          ice = new ICE.Editor('#ice');
          ice.content= content;
        }
      }
    </script>
  </element>
And that actually seems to do the trick. When I first load up the page, the code from the comment is in the editor:



And I can edit the code and see the preview change:



So it all seems to works. For good measure, I even try loading a semi-sophisticated Three.js code example and that too works:



To be clear, I still believe that I am heartily abusing web components here. I am making no use whatsoever of data binding or real templates. Even so, this is pretty cool. I still might prefer to be able to get this working without the HTML comments, but even with them, this seems a fairly promising approach.


Day #792

1 comment: