Source RapidContext_UI.js

1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2025 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    let 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        const msg = Array.from(arguments).map(function (arg) {
36            const isError = arg instanceof Error && arg.message;
37            return isError ? arg.message : arg;
38        }).join(", ");
39        console.warn(msg, ...arguments);
40        if (!errorDialog) {
41            const 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            let 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     * Connects the default UI signals for a procedure. This includes a default
72     * error handler, a loading icon with cancellation handler and a reload icon
73     * with the appropriate click handler.
74     *
75     * @param {Procedure} proc the `RapidContext.Procedure` instance
76     * @param {Icon} [loadingIcon] the loading icon, or `null`
77     * @param {Icon} [reloadIcon] the reload icon, or `null`
78     *
79     * @see RapidContext.Procedure
80     *
81     * @memberof RapidContext.UI
82     */
83    function connectProc(proc, loadingIcon, reloadIcon) {
84        // TODO: error signal not automatically cleaned up on stop()...
85        MochiKit.Signal.connect(proc, "onerror", showError);
86        if (loadingIcon) {
87            MochiKit.Signal.connect(proc, "oncall", loadingIcon, "show");
88            MochiKit.Signal.connect(proc, "onresponse", loadingIcon, "hide");
89            MochiKit.Signal.connect(loadingIcon, "onclick", proc, "cancel");
90        }
91        if (reloadIcon) {
92            MochiKit.Signal.connect(proc, "oncall", reloadIcon, "hide");
93            MochiKit.Signal.connect(proc, "onresponse", reloadIcon, "show");
94            MochiKit.Signal.connect(reloadIcon, "onclick", proc, "recall");
95        }
96    }
97
98    // Export module API
99    const RapidContext = window.RapidContext || (window.RapidContext = {});
100    const module = RapidContext.UI || (RapidContext.UI = {});
101    Object.assign(module, { showError, connectProc });
102
103})(globalThis);
104