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.newsapp.html
opened in Google chrome;
article "DITA Converter v3.12" opened in
<xxe-client>
<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™ 19. “Plus distribution” 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>
xxeserver
normally runs side by side with MyBackend on a server computer.
Therefore the most “realistic” method for running NewsApp
is: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.
httpd
publishing the contents of
$HOME/public_html/
directory
as http://localhost/~USER/, copy all these files to
$HOME/public_html/tmp/
.XXE_INSTALL_DIR/web/bin/xxeserver
.
.../web/bin$ xxeserver
newsapp.html
in a web browser.
xxeserver
is not
only a WebSocket server but also an HTTP server.XXE_INSTALL_DIR/web/doc/manual/apidemo/newsapp.html
,
newsapp.js
, news.css
to
XXE_INSTALL_DIR/web/webapp/
.XXE_INSTALL_DIR/web/bin/xxeserver
.http://localhost:18078/newsapp.html
in a
web browser.<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>
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(); } }
<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
.
RememberThe
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 . |
XMLEditor
method openDocument
. The optional
readOnly
parameter, which is false
by
default, may be used to open an XML document in read-only
mode.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?`); }
ImportantAs 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. |
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; }
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. |