TX Text Control .NET Server

JavaScript

Topic

Any web page can show the Document Editor and the Document Viewer, whether a web server delivers it as a static file or another server technology renders it. The page needs no framework and no npm package: it loads the scripts of both components from an ASP.NET Core backend and talks to them through their JavaScript APIs.

Document Editor: JavaScript and Document Viewer: JavaScript start with one component per page and describe the initialization settings of the editor and the settings of the viewer element. What follows connects such pages to a backend on another origin, exchanges documents with it, combines both components and creates and removes the editor and the viewer at run time.

Where the Scripts Come From

ComponentScriptServed by
Document Editorhttps://<backend>/api/TXWebSocket/GetResource?name=tx-document-editor.min.jsThe WebSocket middleware of the backend
Document Viewerhttps://<backend>/TextControl/GetResource?Resource=minimized.tx-viewer.min.js and https://<backend>/TextControl/GetResource?Resource=minimized.tx-viewer-component.min.jsThe TextControl controller of the viewer package

The editor script provides the global TXTextControl object with init, which creates the editor in a container element. The viewer scripts provide the tx-document-viewer web component and the TXDocumentViewer API.

Prepare the Backend

Set up the backend as described in Configure Program.cs for Separate Frontends and add the controller of Add a Document Controller. Place a template.docx in the Documents folder of the backend.

  • CORS. A page on another origin calls the viewer and your controllers with HTTP requests, so the CORS policy of the backend must allow the origin of the page, for example http://localhost:8080. The Document Editor needs no CORS policy.
  • Addresses. The examples assume that the backend runs on https://localhost:7118. Replace the port in every URL with the HTTPS port of your backend, and use ws instead of wss when the backend runs on HTTP.

Load and Save Documents From a Page

The following page loads the template from the backend when the editor is ready and saves the document back as output.docx:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Document Editor With a Separate Backend</title>
  <script src="https://localhost:7118/api/TXWebSocket/GetResource?name=tx-document-editor.min.js"></script>
  <style>
    #txDocumentEditor { width: 1000px; height: 600px; }
  </style>
</head>
<body>
  <div id="txDocumentEditor"></div>
  <input type="button" onclick="saveToServer()" value="Save Document" />
  <input type="button" onclick="loadFromServer('output.docx')" value="Load Saved Document" />

  <script>
    const backendUrl = "https://localhost:7118";

    TXTextControl.init({
      containerID: "txDocumentEditor",
      webSocketURL: "wss://localhost:7118/api/TXWebSocket"
    });

    TXTextControl.addEventListener("textControlLoaded", function () {
      loadFromServer("template.docx");
    });

    function loadFromServer(name) {
      fetch(backendUrl + "/Document/Load?name=" + encodeURIComponent(name))
        .then(response => response.json())
        .then(stored => {
          TXTextControl.loadDocument(TXTextControl.StreamType.WordprocessingML, stored.data);
        });
    }

    function saveToServer() {
      TXTextControl.saveDocument(TXTextControl.StreamType.WordprocessingML, function (result) {
        fetch(backendUrl + "/Document/Save", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: "output.docx", data: result.data })
        });
      });
    }
  </script>
</body>
</html>

The code works as follows:

  • The script element loads the editor from the backend, and init creates the editor in the div with the ID txDocumentEditor.
  • addEventListener waits for the textControlLoaded event before the page loads the template.
  • loadFromServer requests a document from the controller and passes the base64-encoded data to loadDocument.
  • saveToServer saves the document with saveDocument and posts SaveDocumentResult.data to the controller.

Open the page from a web server whose origin the CORS policy allows. Loading and Saving Documents describes the formats and callbacks.

Show the Editor and the Viewer on One Page

Both components can share a page, for example to preview a template in the viewer while it's edited:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Document Editor and Document Viewer</title>
  <script src="https://localhost:7118/api/TXWebSocket/GetResource?name=tx-document-editor.min.js"></script>
  <script src="https://localhost:7118/TextControl/GetResource?Resource=minimized.tx-viewer.min.js"></script>
  <script src="https://localhost:7118/TextControl/GetResource?Resource=minimized.tx-viewer-component.min.js"></script>
  <style>
    #txDocumentEditor, #viewerContainer { width: 800px; height: 600px; }
  </style>
</head>
<body>
  <div id="txDocumentEditor"></div>
  <input type="button" onclick="showPreview()" value="Preview" />

  <div id="viewerContainer">
    <tx-document-viewer id="viewer1" settings='{"dock":1, "basePath":"https://localhost:7118"}'>
    </tx-document-viewer>
  </div>

  <script>
    TXTextControl.init({
      containerID: "txDocumentEditor",
      webSocketURL: "wss://localhost:7118/api/TXWebSocket"
    });

    function showPreview() {
      TXTextControl.saveDocument(TXTextControl.StreamType.InternalUnicodeFormat, function (result) {
        document.getElementById("viewer1").loadDocument(result.data, "preview.tx");
      });
    }
  </script>
</body>
</html>

The code works as follows:

  • The page loads the script of the editor and the two scripts of the viewer from the same backend.
  • showPreview saves the document of the editor in the internal format of TX Text Control and loads it into the viewer.
  • The page calls loadDocument on the tx-document-viewer element. Each viewer element has its own instance, so a page with several viewers addresses each one by its element; the global TXDocumentViewer refers to the viewer initialized last.

A server endpoint can merge the template with data before the preview, as Show the Editor and the Viewer in One View shows with a controller and MailMerge.

Create and Remove the Editor at Run Time

On pages that build their content dynamically, add the editor only when it's needed and remove it cleanly when it's no longer shown. The page loads the script at run time, creates the editor in its onload handler and removes both again:

<div id="editorHost" style="width: 800px; height: 600px;"></div>
<button onclick="createEditor()">Add Editor</button>
<button onclick="removeEditor()">Remove Editor</button>

<script>
  const scriptUrl = "https://localhost:7118/api/TXWebSocket/GetResource?name=tx-document-editor.min.js";

  function createEditor() {
    const script = document.createElement("script");
    script.src = scriptUrl;
    script.onload = function () {
      TXTextControl.init({
        containerID: "editorHost",
        webSocketURL: "wss://localhost:7118/api/TXWebSocket"
      });
    };
    document.head.appendChild(script);
  }

  function removeEditor() {
    const script = document.querySelector("script[src='" + scriptUrl + "']");
    if (script) script.remove();

    if (typeof TXTextControl !== "undefined") TXTextControl.removeFromDom();
  }
</script>

The code works as follows:

  • containerID names the container element and is required, also when containerElement passes the element itself. Of the two, containerElement takes precedence.
  • removeFromDom closes the WebSocket connection gracefully and removes the editor, its event handlers and its CSS references. Save the document before, because unsaved content is lost.
  • Removing the script element as well lets createEditor load the script and create the editor again.

Instead of creating the editor again, a page can also keep one editor and move its container between places, for example between tabs. It saves the document of one place, resets or loads the content for the next one and fires a resize event after the move. The blog article Reuse Document Editor Instances by Dynamically Moving them in the DOM implements this approach.

Remove and Recreate the Document Viewer

The Document Viewer has no method that closes it. Remove its tx-document-viewer element from the page to take the viewer away, and add a new element to show a viewer again:

function removeViewer() {
  document.getElementById("viewer1").remove();
}

function createViewer() {
  const viewer = document.createElement("tx-document-viewer");
  viewer.id = "viewer1";
  viewer.setAttribute("settings", JSON.stringify({ dock: 1, basePath: "https://localhost:7118" }));
  document.getElementById("viewerContainer").appendChild(viewer);
}

A new element initializes a new viewer as soon as it's added to the page and fires the documentViewerLoaded event again, after which loadDocument on the element loads a document. The element reads its settings only once, so set them before you add the element. Moving an existing element to another place in the page also initializes its viewer again.

Pass Access Tokens

When the backend protects its endpoints with access tokens, the page gets a token from the backend first. What it can do with it differs between the two components:

  • Document Editor. DocumentEditorSettings.webSocketProtocols sends the token as an additional WebSocket sub-protocol instead of in the URL, as Pass Tokens Without the URL shows.
  • Document Viewer. The settings of the tx-document-viewer element have no entry for a token, and the JavaScript API of the viewer documents none. Serve the page from the origin of the backend and protect the viewer with the sign-in cookie of the user instead, as Frontends on Another Origin describes. A token in the query string of BasePath is documented for the HTML helper of an ASP.NET Core view, which Protect the Endpoints shows.

Tokens in URLs can appear in the logs of web servers and proxies. Access Tokens covers the server side.

Node.js Applications

A Node.js application can render the editor on the server with the @txtextcontrol/tx-document-editor package. Its render method writes the same script element and init call that a static page contains, and the @txtextcontrol/tx-websocket-server package or an ASP.NET Core backend with TXTextControl.Web provides the WebSocket endpoint. npm Packages: Document Editor describes the packages and their backend.

Next Steps