Search code examples
javascripthtmlweb-componentace-editor

Make a web componenet for an editor


I want to make a web component tag which consists of an editor with some added functionality. I am using ACE-JSON editor for now.

The main code is this, i do have some other js file with it.

<!DOCTYPE HTML>
<html>

<head>
  <title>JSONEditor</title>
  <meta charset="utf-8">
  <link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.32.5/jsoneditor.css" rel="stylesheet" type="text/css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.32.5/jsoneditor.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
  <script src="app.js"></script>
  <!-- <script src="worker.js"></script> -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">

  <style>
    html,
    body {
      font: 11pt sans-serif;
    }

    #jsoneditor {
      width: 700px;
      height: 600px;
    }

    #paste,
    #link,
    #file {
      margin-top: 25px;
      align-content: center;

    }
  </style>
</head>

<body>
  <div class="container-fluid">
    <h1 align="center">Load and save JSON documents</h1>
    <br><br><br>
    <div class="row">
      <div class="col">
        <div id="jsoneditor"></div>
      </div>
      <div class="col col-centered">
        <div class="row">
          <div class="col">
            <select id="test" name="form_select" onchange="showDiv(this)">
              <option value="paste">paste json</option>
              <option value="file">Upload file</option>
              <option value="link">use link</option>
            </select>
          </div>
          <div class="col">
            <p>
              Save a JSON document: <input type="button" id="saveDocument" value="Save" />
            </p>
          </div>
        </div>
        <div class="row">
          <div class="col">
            <div id="file" style="display: none;" class="col-sm col-centered">
              Load a JSON document: <input type="file" id="loadDocument" value="Load" />
              <button class="button" onclick="readFile()">LOAD JSON</button>
            </div>
            <div id="paste" style="display: none;" class="col-sm col-centered">
              <h3>Paste JSON data </h3>
              <textarea id="myText" rows="4" cols="50"></textarea>
              <button class="button" onclick="loadText()">LOAD JSON</button>
            </div>
            <div id="link" style="display: none;" class="col-sm col-centered">
              <h3>URL</h3>
              <input type="text" id="url">
              <button class="button" onclick="urlOnclick()">LOAD JSON</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

<script>
  var options = {
    mode: 'tree',
    modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
    onError: function (err) {
      alert(err.toString());
    },
    onModeChange: function (newMode, oldMode) {
      console.log('Mode switched from', oldMode, 'to', newMode);
    }
  };
  // create the editor
  var editor = new JSONEditor(document.getElementById('jsoneditor'), options);

  // Save a JSON document
  document.getElementById('saveDocument').onclick = function () {
    // Save Dialog
    fname = window.prompt("Save as...");

    // Check json extension in file name
    if (fname.indexOf(".") == -1) {
      fname = fname + ".json";
    } else {
      if (fname.split('.').pop().toLowerCase() == "json") {
        // Nothing to do
      } else {
        fname = fname.split('.')[0] + ".json";
      }
    }
    var blob = new Blob([editor.getText()], { type: 'application/json;charset=utf-8' });
    saveAs(blob, fname);
  };
</script>

</html>

How can I wrap it up in a web-component? I tried it with JS but most of the tutorials were giving a small example of a simple tag. Most of the site was pointing towards polymer, but I didn't find any proper reference of what I want to do. please can you guide me or provide m some reference for it.


Solution

  • Third party libraries are usually not designed to be used with Web Components.

    The problem you are going to run into is the use of jQuery and the jQuery plugins. When you create a Web Component you are creating a #document-fragment in a #shadowRoot component in the shadowDOM. What all that means is the standard DOM statements we use to select elements (document.getElementById(...)) won't work to select elements in the shadowDOM. That's because the shadowDOM is isolated from the root document. That's why you only find examples of simple tags.

    JSONEditor, for example, uses document.createElement() extensively.

    I've converted your HTML/JS into a Web Component that won't work for the reasons stated above. This will at least show you how to proceed with a more complex example.

    As you can see in the sample below, external scripts and stylesheets are easily loaded into the component. Module import syntax can be used rather than creating script tags, but these libraries don't support that syntax.

    Here are the key moving parts:

    1. An ES6 Class representing the logic of the custom element
    2. Your HTML has been placed within a <template> tag which is used to hold the DOM elements of the custom element
    3. This statement that makes the custom element available for use:
      • customElements.define('my-editor', MyEditor);
    4. Place <my-editor></my-editor> into the HTML file

    Refer to the source code comments for details.

    class MyEditor extends HTMLElement {
      constructor () {
        super();
        this.requiredScripts = [
          'https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js',
          'https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js',
          'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js',
          'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.32.5/jsoneditor.js',
          'https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js',
        ];
        // Grab a reference to the custom element
        let template = document.getElementById('my-custom-editor');
        // Get the #document-fragment
        let templateContent = template.content;
        // Place the template from the Web Component in to the active DOM
        this.attachShadow({ mode: 'open' }).appendChild(templateContent.cloneNode(true));
      }
      // lifecycle hook that fires when custom element is placed into DOM
      connectedCallback () {
        // load required external resources
        this.loadScripts(0);
      }
      
      loadScripts (idx) {
        const tag = document.createElement('script');
        tag.src = this.requiredScripts[idx];
        tag.onload = () => {
          if (idx < this.requiredScripts.length - 1) {
            // when a script has finished, go get the next one
            this.loadScripts(++idx);
          } else {
            // Once all the script files are loaded, initialize the working parts of the component
            this.initEditor();
          }
        }
        tag.onload.bind(this);
        document.head.appendChild(tag);
      }
      
      initEditor () {
        // Need a reference to the #document-fragment
        // Every instance of "document" in this method has been replaced with "component"
        const component = document.getElementById('my-custom-editor').content;
        const options = {
          mode: 'tree',
          modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
          onError: function (err) {
            alert(err.toString());
          },
          onModeChange: function (newMode, oldMode) {
            console.log('Mode switched from', oldMode, 'to', newMode);
          }
        };
        // create the editor IN THE compoent (rathar than the DOM)
        var editor = new JSONEditor(component.getElementById('jsoneditor'), options);
        component.getElementById('saveDocument').onclick = function () {
          fname = window.prompt("Save as...");
          if (fname.indexOf(".") == -1) {
            fname = fname + ".json";
          } else {
            if (fname.split('.').pop().toLowerCase() == "json") {
            } else {
              fname = fname.split('.')[0] + ".json";
            }
          }
          var blob = new Blob([editor.getText()], {
            type: 'application/json;charset=utf-8'
          });
          saveAs(blob, fname);
        };
        
      }
    }
    
    customElements.define('my-editor', MyEditor);
    html,
    body {
        font: 11pt sans-serif;
    }
    <!DOCTYPE HTML>
    <html>
    
    <head>
        <title>JSONEditor</title>
        <meta charset="utf-8">
        <link href="styles.css" rel="stylesheet">
        <script defer src="lib/MyEditor.js"></script>
        <!-- <script src="app.js"></script> -->
        <!-- <script src="worker.js"></script> -->
    
    </head>
    
    <body>
    
    <my-editor></my-editor>
    
    
    <template id="my-custom-editor">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/5.32.5/jsoneditor.css" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    
        <style>
            #jsoneditor {
                width: 700px;
                height: 600px;
            }
    
            #paste,
            #link,
            #file {
                margin-top: 25px;
                align-content: center;
            }
        </style>
        <div class="container-fluid">
            <h1 align="center">Load and save JSON documents</h1>
            <br><br><br>
            <div class="row">
                <div class="col">
                    <div id="jsoneditor"></div>
                </div>
                <div class="col col-centered">
                    <div class="row">
                        <div class="col">
                            <select id="test" name="form_select" onchange="showDiv(this)">
                                <option value="paste">paste json</option>
                                <option value="file">Upload file</option>
                                <option value="link">use link</option>
                            </select>
                        </div>
                        <div class="col">
                            <p>
                                Save a JSON document: <input type="button" id="saveDocument" value="Save"/>
                            </p>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col">
                            <div id="file" style="display: none;" class="col-sm col-centered">
                                Load a JSON document: <input type="file" id="loadDocument" value="Load"/>
                                <button class="button" onclick="readFile()">LOAD JSON</button>
                            </div>
                            <div id="paste" style="display: none;" class="col-sm col-centered">
                                <h3>Paste JSON data </h3>
                                <textarea id="myText" rows="4" cols="50"></textarea>
                                <button class="button" onclick="loadText()">LOAD JSON</button>
                            </div>
                            <div id="link" style="display: none;" class="col-sm col-centered">
                                <h3>URL</h3>
                                <input type="text" id="url">
                                <button class="button" onclick="urlOnclick()">LOAD JSON</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </template>
    </body>
    </html>