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. |