1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2024 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15/**
16 * Provides functions for managing the app user interface.
17 * @namespace RapidContext.UI
18 */
19(function (window) {
20
21 // The global error dialog
22 var errorDialog = null;
23
24 /**
25 * Displays an error message for the user. This operation may or may
26 * not block the user interface, while the message is being
27 * displayed (depending on implementation). All arguments will be
28 * concatenated and displayed.
29 *
30 * @param {...(String|Error)} [arg] the messages or errors to display
31 *
32 * @memberof RapidContext.UI
33 */
34 function showError() {
35 var msg = Array.from(arguments).map(function (arg) {
36 var isError = arg instanceof Error && arg.message;
37 return isError ? arg.message : arg;
38 }).join(", ");
39 console.warn(msg, ...arguments);
40 if (!errorDialog) {
41 var xml = [
42 "<Dialog title='Error' system='true' style='width: 25rem;'>",
43 " <i class='fa fa-exclamation-circle fa-3x color-danger mr-3'></i>",
44 " <div class='inline-block vertical-top' style='width: calc(100% - 4em);'>",
45 " <h4>Error message:</h4>",
46 " <div class='text-pre-wrap' data-message='error'></div>",
47 " </div>",
48 " <div class='text-right mt-1'>",
49 " <Button icon='fa fa-lg fa-times' data-dialog='close'>",
50 " Close",
51 " </Button>",
52 " </div>",
53 "</Dialog>"
54 ].join("");
55 errorDialog = RapidContext.UI.create(xml);
56 window.document.body.append(errorDialog);
57 }
58 if (errorDialog.isHidden()) {
59 errorDialog.querySelector("[data-message]").innerText = msg;
60 errorDialog.show();
61 } else {
62 var txt = errorDialog.querySelector("[data-message]").innerText;
63 if (!txt.includes(msg)) {
64 txt += "\n\n" + msg;
65 }
66 errorDialog.querySelector("[data-message]").innerText = txt;
67 }
68 }
69
70 /**
71 * Creates a tree of widgets from a parsed XML document. This
72 * function will call `createWidget()` for any XML element node found,
73 * performing some basic adjustments on the element attributes
74 * before sending them as attributes to the widget constructor. Text
75 * nodes with non-whitespace content will be mapped to HTML DOM text
76 * nodes.
77 *
78 * @param {Object} node the XML document or node
79 * @param {Object} [ids] the optional node id mappings
80 *
81 * @return {Array|Object} an array or an object with the root
82 * widget(s) created
83 *
84 * @deprecated Use RapidContext.UI.create() instead.
85 *
86 * @memberof RapidContext.UI
87 */
88 function buildUI(node, ids) {
89 console.warn("deprecated: call to RapidContext.UI.buildUI(), use create() instead");
90 if (node.documentElement) {
91 return buildUI(node.documentElement.childNodes, ids);
92 } else if (node && node.item && typeof(node.length) == "number") {
93 return Array.from(node).map((el) => buildUI(el, ids)).filter(Boolean);
94 } else {
95 try {
96 let el = RapidContext.UI.create(node);
97 if (el) {
98 [el.matches("[id]") && el, ...el.querySelectorAll("[id]")]
99 .filter(Boolean)
100 .forEach((el) => ids[el.attributes.id.value] = el);
101 }
102 return el;
103 } catch (e) {
104 console.error("Failed to build UI element", node, e);
105 return null;
106 }
107 }
108 }
109
110 /**
111 * Connects the default UI signals for a procedure. This includes a default
112 * error handler, a loading icon with cancellation handler and a reload icon
113 * with the appropriate click handler.
114 *
115 * @param {Procedure} proc the `RapidContext.Procedure` instance
116 * @param {Icon} [loadingIcon] the loading icon, or `null`
117 * @param {Icon} [reloadIcon] the reload icon, or `null`
118 *
119 * @see RapidContext.Procedure
120 *
121 * @memberof RapidContext.UI
122 */
123 function connectProc(proc, loadingIcon, reloadIcon) {
124 // TODO: error signal not automatically cleaned up on stop()...
125 MochiKit.Signal.connect(proc, "onerror", showError);
126 if (loadingIcon) {
127 MochiKit.Signal.connect(proc, "oncall", loadingIcon, "show");
128 MochiKit.Signal.connect(proc, "onresponse", loadingIcon, "hide");
129 MochiKit.Signal.connect(loadingIcon, "onclick", proc, "cancel");
130 }
131 if (reloadIcon) {
132 MochiKit.Signal.connect(proc, "oncall", reloadIcon, "hide");
133 MochiKit.Signal.connect(proc, "onresponse", reloadIcon, "show");
134 MochiKit.Signal.connect(reloadIcon, "onclick", proc, "recall");
135 }
136 }
137
138 // Export module API
139 let RapidContext = window.RapidContext || (window.RapidContext = {});
140 let module = RapidContext.UI || (RapidContext.UI = {});
141 Object.assign(module, { showError, buildUI, connectProc });
142
143})(this);
144
RapidContext
Access · Discovery · Insight
www.rapidcontext.com