XXE_INSTALL_DIR/web/doc/manual/apidemo/ contains
newsapp.html newsapp.js newapp.css <xxe-client>
(defined by JavaScript class XMLEditor 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 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 document.getElementById, NewsApp
configures this instance of XMLEditor by invoking method
addEventListener autoRecover false.
RememberThe
default value of property
autoRecover 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 readOnly parameter, which is false by
default, may be used to open an XML document in read-only
mode.XMLEditor properties documentIsOpened 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 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 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. |