## 2. Sample web application integrating an XML editor

`XXE_INSTALL_DIR/web/doc/manual/apidemo/` contains `newsapp.html` , `newsapp.js` , `newapp.css` , a sample web application we'll use in this chapter to explain how to integrate `<xxe-client>` (defined by JavaScript class `XMLEditor` ) into any other web application.
The NewsApp web application mimics a Content Management System (CMS ) containing a number of news articles about XMLmind Software products. A news article is a short HTML file. Some news articles have an image attachment. NewsApp lets you browse or edit news articles and also “save”(1) the changes you made to an article.

#### Figure 6-1. `newsapp.html` opened in Google chrome; article "DITA Converter v3.12" opened in `<xxe-client>`

In order to mimics a CMS, NewsApp loads https://www.xmlmind.com/news/xmlmind.xml , an RSS file containing news items about XMLmind Software products. Each news item simulates a different, standalone HTML document contained in the CMS.

#### Figure 6-2. `Excerpts from https://www.xmlmind.com/news/xmlmind.xml`

    <rss version="2.0">
      <channel>
        <title>XMLmind News</title>
        <link>http://www.xmlmind.com/</link>
        ...
        <item>
          <title>Open Source XMLmind DITA Converter v3.12</title>
          <link>http://www.xmlmind.com/ditac/download.shtml</link>
          <description><![CDATA[Updated several
            software components. Official support of Java&trade;&nbsp;19.
            &ldquo;Plus distribution&rdquo; now bundled with <a
            href="https://xmlgraphics.apache.org/fop/2.8/" target="_blank">Apache
            FOP 2.8</a>.<br />More info <a
    href="http://www.xmlmind.com/ditac/changes.html#v3.12.0">here</a>.]]></description>
          <pubDate>Mon, 05 Dec 2022 18:00:00 +0100</pubDate>
          <guid isPermaLink="true">http://www.xmlmind.com/ditac/changes.html#v3.12.0</guid>
        </item>
        ...
      </channel>
    </rss>

### Running NewsApp

As explained in Section 1. Overview, `xxeserver` normally runs side by side with MyBackend on a server computer. Therefore the most “realistic” method for running NewsApp is:

1. Copy `XXE_INSTALL_DIR/web/doc/manual/apidemo/newsapp.html`, `newsapp.js`, `news.css` and also the whole `XXE_INSTALL_DIR/web/webapp/xxeclient/` to a directory published by your HTTP server.
   For example, on a Linux box having Apache `httpd` publishing the contents of `$HOME/public_html/` directory as `http://localhost/~USER/`, copy all these files to `$HOME/public_html/tmp/`.
2. Start `XXE_INSTALL_DIR/web/bin/xxeserver`.
   For example, on a Linux box:

       .../web/bin$ xxeserver

3. Open `newsapp.html` in a web browser.
   For example, on a Linux box, open `http://localhost/~USER/tmp/newsapp.html`.

Alternatively, if you don't have an HTTP server available for testing NewsApp, remember that `xxeserver` is not only a WebSocket server but also an HTTP server.

1. Copy `XXE_INSTALL_DIR/web/doc/manual/apidemo/newsapp.html`, `newsapp.js`, `news.css` to `XXE_INSTALL_DIR/web/webapp/`.
2. Start `XXE_INSTALL_DIR/web/bin/xxeserver`.
3. Open `http://localhost:18078/newsapp.html` in a web browser.

### NewsApp initialization

An HTML page containing `<xxe-client>` must include `xxeclient/xxeclient.css` and `xxeclient/xxeclient.js` as follows:

    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
        ...
        <link href="xxeclient/xxeclient.css" rel="stylesheet" type="text/css" />
        <script type="module" src="./xxeclient/xxeclient.js"></script>
        ...
      </head>
      <body>
        ...
        <xxe-client></xxe-client>
        ...
      </body>
    </html>

`apidemo/newsapp.js`, being a JavaScript module itself, imports everything it needs from JavaScript module `xxeclient/xxeclient.js`. Therefore `apidemo/newsapp.html` does not need to directly include `xxeclient/xxeclient.js`.

    <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      ...
      <link href="xxeclient/xxeclient.css" rel="stylesheet" type="text/css" />
      <link href="newsapp.css" rel="stylesheet" type="text/css" />

      <script type="module">//<![CDATA[

    import { NewsApp } from "./newsapp.js";

    window.onload = (event) => {
        new NewsApp();
    }

    //]]></script>
      </head>
      <body>
        ...
        <table id="paneLayout">
          <tr>
            <td rowspan="4">
              <select id="itemList" size="6">
                <option value="">Please choose a news item.</option>
              </select>
            </td>
            <td><button type="button" id="viewButton">View</button></td>
          </tr>
          <tr><td><button type="button" id="editButton">Edit</button></td></tr>
          <tr><td><button type="button" id="saveButton">Save</button></td></tr>
          <tr><td><button type="button" id="closeButton">Close</button></td></tr>
        </table>

        <xxe-client id="xmlEditor"
          serverurl="${protocol}://${hostname}:${defaultPort}/xxe/ws"></xxe-client>

      </body>
    </html>

JavaScript class `NewsApp`, part of JavaScript module `apidemo/newsapp.js`, does all its initializations in its constructor.

    import * as XUI from './xxeclient/xui.js';
    import * as XXE from './xxeclient/xxeclient.js';

    ...

    export class NewsApp {
        constructor() {
            this._itemList = document.getElementById("itemList");
            this._itemList.disabled = true;
            this._itemList.onchange = this.itemSelected.bind(this);

            this._viewButton = document.getElementById("viewButton");
            this._viewButton.disabled = true;
            this._viewButton.onclick = this.viewItem.bind(this);

            ...INITIALIZE 3 MORE BUTTONS...

            this._xmlEditor = document.getElementById("xmlEditor");
            this._xmlEditor.addEventListener("saveStateChanged",
                                             this.itemSaved.bind(this));

            this._xmlEditor.autoRecover = false;
            window.addEventListener("beforeunload", (event) => {
                if (this._xmlEditor.saveNeeded) {
                    event.preventDefault();
                    return (event.returnValue = true);
                }
            });

            this._items = [];
            this.loadNews(NewsStorage.baseURI + "xmlmind.xml");
        }

        async loadNews(rssURL) {...}
        ...
        itemSaved(event) {
            this.enableButtons();
        }
    }

After obtaining a “handle” to `<xxe-client>` (defined by JavaScript class `XMLEditor` ) using `document.getElementById`, `NewsApp` configures this instance of `XMLEditor` by invoking method `addEventListener` and by setting property `autoRecover` to `false`.

| ##### Remember The default value of property `autoRecover` is `true`. This means, that by default, the full state of `<xxe-client>` is automatically recovered when the user goes away from the page containing `<xxe-client>`, either intentionally (e.g. the user clicks the "Reload current page" button of the browser) or by mistake (e.g. the user closes the web browser tab without saving the changes made to the document). Having this automatic recovery feature enabled is very reassuring for the user but implies that your web application as whole either have a similar automatic recovery feature or is stateless. The sample XML Editor application, `<xxe-app>`, included in the XXEW distribution is stateless and works fine with `xmlEditor.autoRecover=true`. `NewApp` is also stateless and would work fine with `xmlEditor.autoRecover=true`. However in this `apidemo/newsapp.html` demo, we have chosen to set `autoRecover` to `false` to explain what to do in the general case. The answer is the "`beforeunload`" event listener found in the above excerpts of `apidemo/newsapp.js`.

### Opening a news article

Opening the news article selected in the list is done by invoking `XMLEditor` method `openDocument` . The optional `readOnly` parameter, which is `false` by default, may be used to open an XML document in read-only mode.
Of course before doing that, you must make sure that the user does not unintentionally loose changes made to the news article. This verification/confirmation step is implemented using `XMLEditor` properties `documentIsOpened` and `saveNeeded` .

    async openItem(readOnly) {
        let sel = this._itemList.selectedIndex;
        if (sel < 0) {
            return;
        }
        const selItem = this._items[sel];

        let confirmed = await NewsApp.confirmDiscardChanges(this._xmlEditor);
        if (!confirmed) {
            return;
        }

        let closed = await NewsApp.closeDocument(this._xmlEditor);
        if (!closed) {
            return;
        }

        let opened = await this._xmlEditor.openDocument(selItem.htmlSource,
                                                        selItem.uri, readOnly);
        if (!opened) {
            return;
        }

        this.enableButtons();
    }

    static confirmDiscardChanges(xmlEditor) {
        if (!xmlEditor.documentIsOpened || !xmlEditor.saveNeeded) {
            // No changes.
            return Promise.resolve(true);
        }

        return XUI.Confirm.showConfirm(
            `"${xmlEditor.documentURI}" has been modified\nDiscard changes?`);
    }

| ##### Important As you can see it in the above and following excerpts of `apidemo/newsapp.js`, almost all the methods of `XMLEditor` are asynchronous and return a `Promise`. This is why `async` and `await` are used in these excerpts.

### Saving a news article after modifying it

A modified news article is not really saved. Clicking the Save button just let the user preview the modified news article in a new browser tab. This action is implemented using `XMLEditor` methods `getDocument` and `saveDocument` .

    async saveItem(event) {
        if (!this._xmlEditor.documentIsOpened || !this._xmlEditor.saveNeeded) {
            return;
        }

        let savedItem = this.findItem(this._xmlEditor.documentURI);
        if (savedItem === null) {
            // Should not happen.
            return;
        }

        const htmlSource = await this._xmlEditor.getDocument();
        if (htmlSource === null) {
            return;
        }
        savedItem.htmlSource = htmlSource;

        let saved = await this._xmlEditor.saveDocument();
        if (!saved) {
            return;
        }
        // No need to enableButtons, there is itemSaved.

        let newWin = window.open("", "_blank");
        newWin.document.write(htmlSource);
        newWin.document.close();
    }

    findItem(docURI) {
        for (let item of this._items) {
            if (item.uri === docURI) {
                return item;
            }
        }
        return null;
    }

### Closing the news article being viewed or edited

Closing the news article being viewed or edited is done by invoking `XMLEditor` method `closeDocument` . Unless its optional `discardChanges` parameter, `false` by default, is set to `true`, `closeDocument` will not close a document having unsaved changes.

    static closeDocument(xmlEditor) {
        if (!xmlEditor.documentIsOpened) {
            return Promise.resolve(true);
        }

        return xmlEditor.closeDocument(/*discardChanges*/ true);
    }

    ...

    async closeItem(event) {
        let confirmed = await NewsApp.confirmDiscardChanges(this._xmlEditor);
        if (!confirmed) {
            return;
        }

        let closed = await NewsApp.closeDocument(this._xmlEditor);
        if (!closed) {
            return;
        }

        this._itemList.selectedIndex = -1;
        this.enableButtons();
    }

(1) | Previewing the modified news article in a new browser tab is used to simulate saving the document.