PageSourceSearch

https://customers.jam-software.de/js/jaxon_js/jaxon.core.js

js jam-software.de collected 2026-10-02 02:54:55 UTC 100,240 bytes, 2,872 lines download raw bytes

1/*
2    @package jaxon
3    @version $Id: jaxon.core.js 327 2007-02-28 16:55:26Z calltoconstruct $
4    @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson
5    @copyright Copyright (c) 2008-2010 by Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
6    @copyright Copyright (c) 2017 by Thierry Feuzeu, Joseph Woolley, Steffen Konerow, Jared White  & J. Max Wilson
7    @license https://opensource.org/license/bsd-3-clause/ BSD License
8*/
9
10/**
11 * Class: jaxon
12 */
13var jaxon = {
14    /**
15     * Version number
16     */
17    version: {
18        major: '4',
19        minor: '0',
20        patch: '2',
21    },
22
23    debug: {
24        /**
25         * Class: jaxon.debug.verbose
26         *
27         * Provide a high level of detail which can be used to debug hard to find problems.
28         */
29        verbose: {},
30    },
31
32    ajax: {
33        callback: {},
34        handler: {},
35        parameters: {},
36        request: {},
37        response: {},
38    },
39
40    cmd: {
41        head: {},
42        body: {},
43        script: {},
44        form: {},
45        event: {},
46    },
47
48    utils: {
49        dom: {},
50        form: {},
51        queue: {},
52        string: {},
53        upload: {},
54    },
55
56    dom: {},
57
58    /**
59     * This class contains all the default configuration settings.
60     * These are application level settings; however, they can be overridden by including
61     * a jaxon.config definition prior to including the <jaxon_core.js> file, or by
62     * specifying the appropriate configuration options on a per call basis.
63     */
64    config: {
65        /**
66         * An array of header entries where the array key is the header option name and
67         * the associated value is the value that will set when the request object is initialized.
68         *
69         * These headers will be set for both POST and GET requests.
70         */
71        commonHeaders: {
72            'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'
73        },
74
75        /**
76         * An array of header entries where the array key is the header option name and the
77         * associated value is the value that will set when the request object is initialized.
78         */
79        postHeaders: {},
80
81        /**
82         * An array of header entries where the array key is the header option name and the
83         * associated value is the value that will set when the request object is initialized.
84         */
85        getHeaders: {},
86
87        /**
88         * true if jaxon should display a wait cursor when making a request, false otherwise.
89         */
90        waitCursor: false,
91
92        /**
93         * true if jaxon should log the status to the console during a request, false otherwise.
94         */
95        statusMessages: false,
96
97        /**
98         * The base document that will be used throughout the code for locating elements by ID.
99         */
100        baseDocument: document,
101
102        /**
103         * The URI that requests will be sent to.
104         *
105         * @var {string}
106         */
107        requestURI: document.URL,
108
109        /**
110         * The request mode.
111         * - 'asynchronous' - The request will immediately return, the response will be processed
112         *   when (and if) it is received.
113         * - 'synchronous' - The request will block, waiting for the response.
114         *   This option allows the server to return a value directly to the caller.
115         */
116        defaultMode: 'asynchronous',
117
118        /**
119         * The Hyper Text Transport Protocol version designated in the header of the request.
120         */
121        defaultHttpVersion: 'HTTP/1.1',
122
123        /**
124         * The content type designated in the header of the request.
125         */
126        defaultContentType: 'application/x-www-form-urlencoded',
127
128        /**
129         * The delay time, in milliseconds, associated with the <jaxon.callback.onRequestDelay> event.
130         */
131        defaultResponseDelayTime: 1000,
132
133        /**
134         * Always convert the reponse content to json.
135         */
136        convertResponseToJson: true,
137
138        /**
139         * The amount of time to wait, in milliseconds, before a request is considered expired.
140         * This is used to trigger the <jaxon.callback.onExpiration event.
141         */
142        defaultExpirationTime: 10000,
143
144        /**
145         * The method used to send requests to the server.
146         * - 'POST': Generate a form POST request
147         * - 'GET': Generate a GET request; parameters are appended to <jaxon.config.requestURI> to form a URL.
148         */
149        defaultMethod: 'POST', // W3C: Method is case sensitive
150
151        /**
152         * The number of times a request should be retried if it expires.
153         */
154        defaultRetry: 5,
155
156        /**
157         * The value returned by <jaxon.request> when in asynchronous mode, or when a syncrhonous call
158         * does not specify the return value.
159         */
160        defaultReturnValue: false,
161
162        /**
163         * The maximum depth of recursion allowed when serializing objects to be sent to the server in a request.
164         */
165        maxObjectDepth: 20,
166
167        /**
168         * The maximum number of members allowed when serializing objects to be sent to the server in a request.
169         */
170        maxObjectSize: 2000,
171
172        /**
173         * The maximum number of commands allowed in a single response.
174         */
175        commandQueueSize: 1000,
176
177        /**
178         * The maximum number of requests that can be processed simultaneously.
179         */
180        requestQueueSize: 1000,
181
182        /**
183         * Common options for all HTTP requests to the server.
184         */
185        httpRequestOptions: {
186            mode: "cors", // no-cors, *cors, same-origin
187            cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
188            credentials: "same-origin", // include, *same-origin, omit
189            redirect: "manual", // manual, *follow, error
190        },
191    },
192};
193
194/**
195 * Register the command handlers provided by the library.
196 */
197(function(cfg) {
198    /**
199     * Set the options in the request object
200     *
201     * @param {object} oRequest The request context object.
202     *
203     * @returns {void}
204     */
205    cfg.setRequestOptions = (oRequest) => {
206        if (cfg.requestURI === undefined) {
207            throw { code: 10005 };
208        }
209
210        const aHeaders = ['commonHeaders', 'postHeaders', 'getHeaders'];
211        aHeaders.forEach(sHeader => oRequest[sHeader] = { ...cfg[sHeader], ...oRequest[sHeader] });
212
213        const oDefaultOptions = {
214            statusMessages: cfg.statusMessages,
215            waitCursor: cfg.waitCursor,
216            mode: cfg.defaultMode,
217            method: cfg.defaultMethod,
218            URI: cfg.requestURI,
219            httpVersion: cfg.defaultHttpVersion,
220            contentType: cfg.defaultContentType,
221            convertResponseToJson: cfg.convertResponseToJson,
222            retry: cfg.defaultRetry,
223            returnValue: cfg.defaultReturnValue,
224            maxObjectDepth: cfg.maxObjectDepth,
225            maxObjectSize: cfg.maxObjectSize,
226            context: window,
227            upload: false,
228            aborted: false,
229        };
230        Object.keys(oDefaultOptions).forEach(sOption =>
231            oRequest[sOption] = oRequest[sOption] ?? oDefaultOptions[sOption]);
232
233        oRequest.method = oRequest.method.toUpperCase();
234        if (oRequest.method !== 'GET') {
235            oRequest.method = 'POST'; // W3C: Method is case sensitive
236        }
237        oRequest.requestRetry = oRequest.retry;
238    };
239
240    /**
241     * Class: jaxon.config.status
242     *
243     * Provides support for updating the browser's status bar during the request process.
244     * By splitting the status bar functionality into an object, the jaxon developer has the opportunity
245     * to customize the status bar messages prior to sending jaxon requests.
246     */
247    cfg.status = {
248        /**
249         * A set of event handlers that will be called by the
250         * jaxon framework to set the status bar messages.
251         *
252         * @type {object}
253         */
254        update: {
255            onRequest: () => console.log('Sending Request...'),
256            onWaiting: () => console.log('Waiting for Response...'),
257            onProcessing: () => console.log('Processing...'),
258            onComplete: () => console.log('Done.'),
259        },
260
261        /**
262         * A set of event handlers that will be called by the
263         * jaxon framework where status bar updates would normally occur.
264         *
265         * @type {object}
266         */
267        dontUpdate: {
268            onRequest: () => {},
269            onWaiting: () => {},
270            onProcessing: () => {},
271            onComplete: () => {}
272        },
273    };
274
275    /**
276     * Class: jaxon.config.cursor
277     *
278     * Provides the base functionality for updating the browser's cursor during requests.
279     * By splitting this functionality into an object of it's own, jaxon developers can now
280     * customize the functionality prior to submitting requests.
281     */
282    cfg.cursor = {
283        /**
284         * Constructs and returns a set of event handlers that will be called by the
285         * jaxon framework to effect the status of the cursor during requests.
286         *
287         * @type {object}
288         */
289        update: {
290            onWaiting: () => {
291                if (jaxon.config.baseDocument.body) {
292                    jaxon.config.baseDocument.body.style.cursor = 'wait';
293                }
294            },
295            onComplete: () => {
296                if (jaxon.config.baseDocument.body) {
297                    jaxon.config.baseDocument.body.style.cursor = 'auto';
298                }
299            }
300        },
301
302        /**
303         * Constructs and returns a set of event handlers that will be called by the jaxon framework
304         * where cursor status changes would typically be made during the handling of requests.
305         *
306         * @type {object}
307         */
308        dontUpdate: {
309            onWaiting: () => {},
310            onComplete: () => {}
311        },
312    };
313})(jaxon.config);
314
315
316/**
317 * Class: jaxon.utils.dom
318 */
319
320(function(self, baseDocument) {
321    /**
322     * Shorthand for finding a uniquely named element within the document.
323     *
324     * @param {string} sId - The unique name of the element (specified by the ID attribute)
325     *
326     * @returns {object} The element found or null.
327     *
328     * @see <self.$>
329     */
330    self.$ = (sId) => !sId ? null :
331        (typeof sId === 'string' ? baseDocument.getElementById(sId) : sId);
332
333    /**
334     * Create a div as workspace for the getBrowserHTML() function.
335     *
336     * @returns {object} The workspace DOM element.
337     */
338    const _getWorkspace = () => {
339        const elWorkspace = self.$('jaxon_temp_workspace');
340        if (elWorkspace) {
341            return elWorkspace;
342        }
343        // Workspace not found. Must be created.
344        if (!baseDocument.body) {
345            return null;
346        }
347
348        const elNewWorkspace = baseDocument.createElement('div');
349        elNewWorkspace.setAttribute('id', 'jaxon_temp_workspace');
350        elNewWorkspace.style.display = 'none';
351        elNewWorkspace.style.visibility = 'hidden';
352        baseDocument.body.appendChild(elNewWorkspace);
353        return elNewWorkspace;
354    };
355
356    /**
357     * Insert the specified string of HTML into the document, then extract it.
358     * This gives the browser the ability to validate the code and to apply any transformations it deems appropriate.
359     *
360     * @param {string} sValue A block of html code or text to be inserted into the browser's document.
361     *
362     * @returns {string} The (potentially modified) html code or text.
363     */
364    self.getBrowserHTML = (sValue) => {
365        const elWorkspace = _getWorkspace();
366        elWorkspace.innerHTML = sValue;
367        const browserHTML = elWorkspace.innerHTML;
368        elWorkspace.innerHTML = '';
369        return browserHTML;
370    };
371
372    /**
373     * Tests to see if the specified data is the same as the current value of the element's attribute.
374     *
375     * @param {string|object} element The element or it's unique name (specified by the ID attribute)
376     * @param {string} attribute The name of the attribute.
377     * @param {string} newData The value to be compared with the current value of the specified element.
378     *
379     * @returns {true} The specified value differs from the current attribute value.
380     * @returns {false} The specified value is the same as the current value.
381     */
382    self.willChange = (element, attribute, newData) => {
383        element = self.$(element);
384        return !element ? false : (newData != element[attribute]);
385    };
386
387    /**
388     * Tests to see if the specified data is the same as the current value of the element's attribute.
389     *
390     * @param {string|object} element The element or it's unique name (specified by the ID attribute)
391     *
392     * @returns {void}
393     */
394    self.removeElement = (element) => {
395        element = self.$(element);
396        if (element && element.parentNode && element.parentNode.removeChild) {
397            element.parentNode.removeChild(element);
398        }
399    };
400
401    /**
402     * Get the value of an attribute of an object.
403     * Can also get the value of a var in an array.
404     *
405     * @param {object} xElement The object with the attribute.
406     * @param {string} sAttrName The attribute name.
407     *
408     * @returns {mixed}
409     */
410    self.getAttrValue = (xElement, sAttrName) => {
411        if((aMatches = sAttrName.match(/^(.+)\[(\d+)\]$/)) === null)
412        {
413            return xElement[sAttrName];
414        }
415
416        // The attribute is an array in the form "var[indice]".
417        sAttrName = aMatches[1];
418        const nAttrIndice = parseInt(aMatches[2]);
419        return xElement[sAttrName][nAttrIndice];
420    }
421
422    /**
423     * Find a function using its name as a string.
424     *
425     * @param {string} sFuncName The name of the function to find.
426     * @param {object} context
427     *
428     * @returns {object|null}
429     */
430    self.findFunction = (sFuncName, context = window) => {
431        const aNames = sFuncName.split(".");
432        const nLength = aNames.length;
433        for (let i = 0; i < nLength && (context); i++) {
434            context = self.getAttrValue(context, aNames[i]);
435        }
436        return context ?? null;
437    };
438
439    /**
440     * Given an element and an attribute with 0 or more dots,
441     * get the inner object and the corresponding attribute name.
442     *
443     * @param {string} sAttrName The attribute name.
444     * @param {object=} xElement The outer element.
445     *
446     * @returns {object|null} The inner object and the attribute name in an object.
447     */
448    self.getInnerObject = (sAttrName, xElement = window) => {
449        const aNames = sAttrName.split('.');
450        // Get the last element in the array.
451        sAttrName = aNames.pop();
452        // Move to the inner object.
453        const nLength = aNames.length;
454        for (let i = 0; i < nLength && (xElement); i++) {
455            // The real name for the "css" object is "style".
456            const sRealAttrName = aNames[i] === 'css' ? 'style' : aNames[i];
457            xElement = self.getAttrValue(xElement, sRealAttrName);
458        }
459        return !xElement ? null : { node: xElement, attr: sAttrName };
460    };
461
462    /**
463     * Create a function by inserting its code in the page using a <script> tag.
464     *
465     * @param {string} funcCode
466     * @param {string='jaxon.cmd.script.context.delegateCall'} funcName
467     * 
468     * @returns {boolean}
469     */
470    self.createFunction = (funcCode, funcName = 'jaxon.cmd.script.context.delegateCall') => {
471        if (!funcCode) {
472            return false;
473        }
474
475        try {
476            const scriptTagId = 'jaxon_cmd_script_' + (funcName === undefined ?
477                'delegate_call' : funcName.toLowerCase().replaceAll('.', '_'));
478
479            // Remove the tag if it already exists.
480            self.removeElement(scriptTagId);
481            // Create a new tag.
482            const scriptTag = baseDocument.createElement('script');
483            scriptTag.setAttribute('id', scriptTagId);
484            scriptTag.textContent = `
485    ${funcName} = ${funcCode}
486`;
487            baseDocument.body.appendChild(scriptTag);
488        } catch (e) {
489            return false;
490        }
491
492        return true;
493    };
494})(jaxon.utils.dom, jaxon.config.baseDocument);
495
496
497/**
498 * Class: jaxon.utils.form
499 */
500
501(function(self, dom) {
502    /**
503     * @param {object} xOptions
504     * @param {object} child
505     * @param {string} child.type
506     * @param {string} child.name
507     * @param {string} child.tagName
508     * @param {boolean} child.checked
509     * @param {boolean} child.disabled
510     * @param {mixed} child.value
511     * @param {array} child.options
512     *
513     * @returns {void}
514     */
515    const _getValue = (xOptions, { type, name, tagName, checked, disabled, value, options }) => {
516        if (!name || 'PARAM' === tagName)
517            return;
518        if (!xOptions.submitDisabled && disabled)
519            return;
520        const { prefix } = xOptions;
521        if (prefix.length > 0 && prefix !== name.substring(0, prefix.length))
522            return;
523        if ((type === 'radio' || type === 'checkbox') && !checked)
524            return;
525        if (type === 'file')
526            return;
527
528        const values = type !== 'select-multiple' ? value :
529            Array.from(options).filter(({ selected }) => selected).map(({ value: v }) => v);
530        const keyBegin = name.indexOf('[');
531
532        if (keyBegin < 0) {
533            xOptions.values[name] = values;
534            return;
535        }
536
537        // Parse names into brackets
538        let k = name.substring(0, keyBegin);
539        let a = name.substring(keyBegin);
540        if (xOptions.values[k] === undefined) {
541            xOptions.values[k] = {};
542        }
543        let p = xOptions.values; // pointer reset
544        while (a.length > 0) {
545            const sa = a.substring(0, a.indexOf(']') + 1);
546            const lastKey = k; //save last key
547            const lastRef = p; //save last pointer
548
549            a = a.substring(a.indexOf(']') + 1);
550            p = p[k];
551            k = sa.substring(1, sa.length - 1);
552            if (k === '') {
553                if ('select-multiple' === type) {
554                    k = lastKey; //restore last key
555                    p = lastRef;
556                } else {
557                    k = p.length;
558                }
559            }
560            if (k === undefined) {
561                /*check against the global xOptions.values Stack wich is the next(last) usable index */
562                k = Object.keys(lastRef[lastKey]).length;
563            }
564            p[k] = p[k] || {};
565        }
566        p[k] = values;
567    };
568
569    /**
570     * @param {object} xOptions
571     * @param {array} children
572     *
573     * @returns {void}
574     */
575    const _getValues = (xOptions, children) => {
576        children.forEach(child => {
577            const { childNodes, type } = child;
578            if (childNodes !== undefined && type !== 'select-one' && type !== 'select-multiple') {
579                _getValues(xOptions, childNodes);
580            }
581           _getValue(xOptions, child);
582        });
583    };
584
585    /**
586     * Build an associative array of form elements and their values from the specified form.
587     *
588     * @param {string} formId The unique name (id) of the form to be processed.
589     * @param {boolean=false} submitDisabled (optional): Include form elements which are currently disabled.
590     * @param {string=''} prefix (optional): A prefix used for selecting form elements.
591     *
592     * @returns {object} An associative array of form element id and value.
593     */
594    self.getValues = (formId, submitDisabled = false, prefix = '') => {
595        const xOptions = {
596            // Submit disabled fields
597            submitDisabled: (submitDisabled === true || submitDisabled === 1),
598            // Only submit fields with a prefix
599            prefix: prefix ?? '',
600            // Form values
601            values: {},
602        };
603
604        const form = dom.$(formId);
605        if (form && form.childNodes) {
606            _getValues(xOptions, form.childNodes);
607        }
608        return xOptions.values;
609    };
610})(jaxon.utils.form, jaxon.utils.dom);
611
612
613/**
614 * Class: jaxon.utils.queue
615 */
616
617(function(self) {
618    /**
619     * Construct and return a new queue object.
620     *
621     * @param {integer} size The number of entries the queue will be able to hold.
622     *
623     * @returns {object}
624     */
625    self.create = size => ({
626        start: 0,
627        count: 0,
628        size: size,
629        end: 0,
630        elements: [],
631        paused: false,
632    });
633
634    /**
635     * Check id a queue is empty.
636     *
637     * @param {object} oQueue The queue to check.
638     *
639     * @returns {boolean}
640     */
641    self.empty = oQueue => oQueue.count <= 0;
642
643    /**
644     * Check id a queue is empty.
645     *
646     * @param {object} oQueue The queue to check.
647     *
648     * @returns {boolean}
649     */
650    self.full = oQueue => oQueue.count >= oQueue.size;
651
652    /**
653     * Push a new object into the tail of the buffer maintained by the specified queue object.
654     *
655     * @param {object} oQueue The queue in which you would like the object stored.
656     * @param {object} obj    The object you would like stored in the queue.
657     *
658     * @returns {integer} The number of entries in the queue.
659     */
660    self.push = (oQueue, obj) => {
661        // No push if the queue is full.
662        if(self.full(oQueue)) {
663            throw { code: 10003 };
664        }
665
666        oQueue.elements[oQueue.end] = obj;
667        if(++oQueue.end >= oQueue.size) {
668            oQueue.end = 0;
669        }
670        return ++oQueue.count;
671    };
672
673    /**
674     * Push a new object into the head of the buffer maintained by the specified queue object.
675     *
676     * This effectively pushes an object to the front of the queue... it will be processed first.
677     *
678     * @param {object} oQueue The queue in which you would like the object stored.
679     * @param {object} obj    The object you would like stored in the queue.
680     *
681     * @returns {integer} The number of entries in the queue.
682     */
683    self.pushFront = (oQueue, obj) => {
684        // No push if the queue is full.
685        if(self.full(oQueue)) {
686            throw { code: 10003 };
687        }
688
689        // Simply push if the queue is empty
690        if(self.empty(oQueue)) {
691            return self.push(oQueue, obj);
692        }
693
694        // Put the object one position back.
695        if(--oQueue.start < 0) {
696            oQueue.start = oQueue.size - 1;
697        }
698        oQueue.elements[oQueue.start] = obj;
699        return ++oQueue.count;
700    };
701
702    /**
703     * Attempt to pop an object off the head of the queue.
704     *
705     * @param {object} oQueue The queue object you would like to modify.
706     *
707     * @returns {object|null}
708     */
709    self.pop = (oQueue) => {
710        if(self.empty(oQueue)) {
711            return null;
712        }
713
714        const obj = oQueue.elements[oQueue.start];
715        delete oQueue.elements[oQueue.start];
716        if(++oQueue.start >= oQueue.size) {
717            oQueue.start = 0;
718        }
719        oQueue.count--;
720        return obj;
721    };
722
723    /**
724     * Attempt to pop an object off the head of the queue.
725     *
726     * @param {object} oQueue The queue object you would like to modify.
727     *
728     * @returns {object|null}
729     */
730    self.peek = (oQueue) => {
731        if(self.empty(oQueue)) {
732            return null;
733        }
734        return oQueue.elements[oQueue.start];
735    };
736})(jaxon.utils.queue);
737
738
739/**
740 * Class: jaxon.dom
741 */
742
743/**
744 * Plain javascript replacement for jQuery's .ready() function.
745 * See https://github.com/jfriend00/docReady for a detailed description, copyright and license information.
746 */
747(function(self) {
748    "use strict";
749
750    let readyList = [];
751    let readyFired = false;
752    let readyEventHandlersInstalled = false;
753
754    /**
755     * Call this when the document is ready.
756     * This function protects itself against being called more than once
757     */
758    const ready = () => {
759        if (readyFired) {
760            return;
761        }
762        // this must be set to true before we start calling callbacks
763        readyFired = true;
764        // if a callback here happens to add new ready handlers,
765        // the jaxon.dom.ready() function will see that it already fired
766        // and will schedule the callback to run right after
767        // this event loop finishes so all handlers will still execute
768        // in order and no new ones will be added to the readyList
769        // while we are processing the list
770        readyList.forEach(cb => cb.fn.call(window, cb.ctx));
771        // allow any closures held by these functions to free
772        readyList = [];
773    }
774
775    const readyStateChange = () => document.readyState === "complete" && ready();
776
777    /**
778     * This is the one public interface
779     * jaxon.dom.ready(fn, context);
780     * The context argument is optional - if present, it will be passed as an argument to the callback
781     */
782    self.ready = function(callback, context) {
783        // if ready has already fired, then just schedule the callback
784        // to fire asynchronously, but right away
785        if (readyFired) {
786            setTimeout(function() { callback(context); }, 1);
787            return;
788        }
789        // add the function and context to the list
790        readyList.push({ fn: callback, ctx: context });
791        // if document already ready to go, schedule the ready function to run
792        if (document.readyState === "complete" || (!document.attachEvent && document.readyState === "interactive")) {
793            setTimeout(ready, 1);
794            return;
795        }
796        if (!readyEventHandlersInstalled) {
797            // first choice is DOMContentLoaded event
798            document.addEventListener("DOMContentLoaded", ready, false);
799            // backup is window load event
800            window.addEventListener("load", ready, false);
801
802            readyEventHandlersInstalled = true;
803        }
804    }
805})(jaxon.dom);
806
807
808/**
809 * Class: jaxon.utils.string
810 */
811
812(function(self) {
813    /**
814     * Replace all occurances of the single quote character with a double quote character.
815     *
816     * @param {string=} haystack The source string to be scanned
817     *
818     * @returns {string|false} A new string with the modifications applied. False on error.
819     */
820    self.doubleQuotes = haystack => haystack === undefined ?
821        false : haystack.replace(new RegExp("'", 'g'), '"');
822
823    /**
824     * Replace all occurances of the double quote character with a single quote character.
825     *
826     * @param {string=} haystack The source string to be scanned
827     *
828     * @returns {string|false} A new string with the modification applied
829     */
830    self.singleQuotes = haystack => haystack === undefined ?
831        false : haystack.replace(new RegExp('"', 'g'), "'");
832
833    /**
834     * Detect, and if found, remove the prefix 'on' from the specified string.
835     * This is used while working with event handlers.
836     *
837     * @param {string} sEventName The string to be modified
838     *
839     * @returns {string} The modified string
840     */
841    self.stripOnPrefix = (sEventName) => {
842        sEventName = sEventName.toLowerCase();
843        return sEventName.indexOf('on') === 0 ? sEventName.replace(/on/, '') : sEventName;
844    };
845
846    /**
847     * Detect, and add if not found, the prefix 'on' from the specified string.
848     * This is used while working with event handlers.
849     *
850     * @param {string} sEventName The string to be modified
851     *
852     * @returns {string} The modified string
853     */
854    self.addOnPrefix = (sEventName) => {
855        sEventName = sEventName.toLowerCase();
856        return sEventName.indexOf('on') !== 0 ? 'on' + sEventName : sEventName;
857    };
858
859    /**
860     * Get the type of an object. Unlike typeof, this function distinguishes
861     * objects from arrays, and the first letter is capitalized.
862     *
863     * @param {mixed} xObject The object to check
864     *
865     * @returns {string}
866     */
867    self.typeOf = (xObject) => Object.prototype.toString.call(xObject).slice(8, -1).toLowerCase();
868
869    /**
870     * String functions for Jaxon
871     * See http://javascript.crockford.com/remedial.html for more explanation
872     */
873    if (!String.prototype.supplant) {
874        /**
875         * Substitute variables in the string
876         *
877         * @param {object} values The substitution values
878         *
879         * @returns {string}
880         */
881        String.prototype.supplant = function(values) {
882            return this.replace(
883                /\{([^{}]*)\}/g,
884                (a, b) => {
885                    const r = values[b];
886                    const t = typeof r;
887                    return t === 'string' || t === 'number' ? r : a;
888                }
889            );
890        };
891    }
892})(jaxon.utils.string);
893
894
895/**
896 * Class: jaxon.utils.upload
897 */
898
899(function(self, dom, console) {
900    /**
901     * @param {object} oRequest A request object, created initially by a call to <jaxon.ajax.request.initialize>
902     * @param {string=} oRequest.upload The HTML file upload field id
903     *
904     * @returns {boolean}
905     */
906    const initRequest = (oRequest) => {
907        if (!oRequest.upload) {
908            return false;
909        }
910
911        oRequest.upload = {
912            id: oRequest.upload,
913            input: null,
914            form: null,
915        };
916        const input = dom.$(oRequest.upload.id);
917
918        if (!input) {
919            console.log('Unable to find input field for file upload with id ' + oRequest.upload.id);
920            return false;
921        }
922        if (input.type !== 'file') {
923            console.log('The upload input field with id ' + oRequest.upload.id + ' is not of type file');
924            return false;
925        }
926        if (input.files.length === 0) {
927            console.log('There is no file selected for upload in input field with id ' + oRequest.upload.id);
928            return false;
929        }
930        if (input.name === undefined) {
931            console.log('The upload input field with id ' + oRequest.upload.id + ' has no name attribute');
932            return false;
933        }
934        oRequest.upload.input = input;
935        oRequest.upload.form = input.form;
936        return true;
937    };
938
939    /**
940     * Check upload data and initialize the request.
941     *
942     * @param {object} oRequest A request object, created initially by a call to <jaxon.ajax.request.initialize>
943     *
944     * @returns {void}
945     */
946    self.initialize = (oRequest) => {
947        // The content type shall not be set when uploading a file with FormData.
948        // It will be set by the browser.
949        if (!initRequest(oRequest)) {
950            oRequest.postHeaders['content-type'] = oRequest.contentType;
951        }
952    }
953})(jaxon.utils.upload, jaxon.utils.dom, console);
954
955
956/**
957 * Class: jaxon.ajax.callback
958 */
959
960(function(self, str, config) {
961    /**
962     * Create a timer to fire an event in the future.
963     * This will be used fire the onRequestDelay and onExpiration events.
964     *
965     * @param {integer} iDelay The amount of time in milliseconds to delay.
966     *
967     * @returns {object}
967 A callback timer object.
968     */
969    const setupTimer = (iDelay) => ({ timer: null, delay: iDelay });
970
971    /**
972     * The names of the available callbacks.
973     *
974     * @var {array}
975     */
976    const aCallbackNames = ['onInitialize', 'onProcessParams', 'onPrepare',
977        'onRequest', 'onResponseDelay', 'onExpiration', 'beforeResponseProcessing',
978        'onFailure', 'onRedirect', 'onSuccess', 'onComplete'];
979
980    /**
981     * Create a blank callback object.
982     * Two optional arguments let you set the delay time for the onResponseDelay and onExpiration events.
983     *
984     * @param {integer=} responseDelayTime
985     * @param {integer=} expirationTime
986     *
987     * @returns {object} The callback object.
988     */
989    self.create = (responseDelayTime, expirationTime) => {
990        const oCallback = {
991            timers: {
992                onResponseDelay: setupTimer(responseDelayTime ?? config.defaultResponseDelayTime),
993                onExpiration: setupTimer(expirationTime ?? config.defaultExpirationTime),
994            },
995        };
996        aCallbackNames.forEach(sName => oCallback[sName] = null);
997        return oCallback;
998    };
999
1000    /**
1001     * The global callback object which is active for every request.
1002     *
1003     * @var {object}
1004     */
1005    self.callback = self.create();
1006
1007    /**
1008     * Move all the callbacks defined directly in the oRequest object to the
1009     * oRequest.callback property, which may then be converted to an array.
1010     *
1011     * @param {object} oRequest
1012     *
1013     * @return {void}
1014     */
1015    self.initCallbacks = (oRequest) => {
1016        if (str.typeOf(oRequest.callback) === 'object') {
1017            oRequest.callback = [oRequest.callback];
1018        }
1019        if (str.typeOf(oRequest.callback) === 'array') {
1020            oRequest.callback.forEach(oCallback => {
1021                // Add the timers attribute, if it is not defined.
1022                if (oCallback.timers === undefined) {
1023                    oCallback.timers = {};
1024                }
1025            });
1026            return;
1027        }
1028
1029        let callbackFound = false;
1030        // Check if any callback is defined in the request object by its own name.
1031        const callback = self.create();
1032        aCallbackNames.forEach(sName => {
1033            if (oRequest[sName] !== undefined) {
1034                callback[sName] = oRequest[sName];
1035                callbackFound = true;
1036                delete oRequest[sName];
1037            }
1038        });
1039        oRequest.callback = callbackFound ? [callback] : [];
1040    };
1041
1042    /**
1043     * Get a flatten array of callbacks
1044     *
1045     * @param {object} oRequest The request context object.
1046     * @param {array=} oRequest.callback The request callback(s).
1047     *
1048     * @returns {array}
1049     */
1050    const getCallbacks = ({ callback = [] }) => [self.callback, ...callback];
1051
1052    /**
1053     * Execute a callback event.
1054     *
1055     * @param {object} oCallback The callback object (or objects) which contain the event handlers to be executed.
1056     * @param {string} sFunction The name of the event to be triggered.
1057     * @param {object} xArgs The callback argument.
1058     *
1059     * @returns {void}
1060     */
1061    const execute = (oCallback, sFunction, xArgs) => {
1062        const func = oCallback[sFunction];
1063        if (!func || str.typeOf(func) !== 'function') {
1064            return;
1065        }
1066        const timer = oCallback.timers[sFunction];
1067        if (!timer) {
1068            func(xArgs); // Call the function directly.
1069            return;
1070        }
1071        // Call the function after the timeout.
1072        timer.timer = setTimeout(() => func(xArgs), timer.delay);
1073    };
1074
1075    /**
1076     * Execute a callback event.
1077     *
1078     * @param {object} oRequest The request context object.
1079     * @param {string} sFunction The name of the event to be triggered.
1080     *
1081     * @returns {void}
1082     */
1083    self.execute = (oRequest, sFunction) => getCallbacks(oRequest)
1084        .forEach(oCallback => execute(oCallback, sFunction, oRequest));
1085
1086    /**
1087     * Clear a callback timer for the specified function.
1088     *
1089     * @param {object} oCallback The callback object (or objects) that contain the specified function timer to be cleared.
1090     * @param {string} sFunction The name of the function associated with the timer to be cleared.
1091     *
1092     * @returns {void}
1093     */
1094    const clearTimer = (oCallback, sFunction) => {
1095        const timer = oCallback.timers[sFunction];
1096        timer !== undefined && timer.timer !== null && clearTimeout(timer.timer);
1097    };
1098
1099    /**
1100     * Clear a callback timer for the specified function.
1101     *
1102     * @param {object} oRequest The request context object.
1103     * @param {string} sFunction The name of the function associated with the timer to be cleared.
1104     *
1105     * @returns {void}
1106     */
1107    self.clearTimer = (oRequest, sFunction) => getCallbacks(oRequest)
1108        .forEach(oCallback => clearTimer(oCallback, sFunction));
1109})(jaxon.ajax.callback, jaxon.utils.string, jaxon.config);
1110
1111
1112/**
1113 * Class: jaxon.ajax.handler
1114 */
1115
1116(function(self, config, ajax, queue, dom) {
1117    /**
1118     * An array that is used internally in the jaxon.fn.handler object to keep track
1119     * of command handlers that have been registered.
1120     *
1121     * @var {object}
1122     */
1123    const handlers = {};
1124
1125    /**
1126     * The queues that hold synchronous requests as they are sent and processed.
1127     *
1128     * @var {object}
1129     */
1130    self.q = {
1131        send: queue.create(config.requestQueueSize),
1132        recv: queue.create(config.requestQueueSize * 2)
1133    };
1134
1135    /**
1136     * Registers a new command handler.
1137     *
1138     * @param {string} cmd The short name of the command handler.
1139     * @param {string} func The command handler function.
1140     * @param {string=''} name The full name of the command handler.
1141     *
1142     * @returns {void}
1143     */
1144    self.register = (cmd, func, name = '') => handlers[cmd] = { name, func };
1145
1146    /**
1147     * Unregisters and returns a command handler.
1148     *
1149     * @param {string} cmd The name of the command handler.
1150     *
1151     * @returns {callable} The unregistered function.
1152     */
1153    self.unregister = (cmd) => {
1154        const handler = handlers[cmd];
1155        delete handlers[cmd];
1156        return handler.func;
1157    };
1158
1159    /**
1160     * @param {object} command The response command to be executed.
1161     * @param {string} command.cmd The name of the function.
1162     *
1163     * @returns {boolean} (true or false): depending on whether a command handler has
1164     * been registered for the specified command (object).
1165     */
1166    self.isRegistered = ({ cmd }) => cmd !== undefined && handlers[cmd] !== undefined;
1167
1168    /**
1169     * Perform a lookup on the command specified by the response command object passed
1170     * in the first parameter.  If the command exists, the function checks to see if
1171     * the command references a DOM object by ID; if so, the object is located within
1172     * the DOM and added to the command data.  The command handler is then called.
1173     * 
1174     * If the command handler returns true, it is assumed that the command completed
1175     * successfully.  If the command handler returns false, then the command is considered
1176     * pending; jaxon enters a wait state.  It is up to the command handler to set an
1177     * interval, timeout or event handler which will restart the jaxon response processing.
1178     * 
1179     * @param {object} command The response command to be executed.
1180     *
1181     * @returns {true} The command completed successfully.
1182     * @returns {false} The command signalled that it needs to pause processing.
1183     */
1184    self.execute = (command) => {
1185        if (!self.isRegistered(command)) {
1186            return true;
1187        }
1188        // If the command has an "id" attr, find the corresponding dom element.
1189        if (command.id) {
1190            command.target = dom.$(command.id);
1191        }
1192        // Process the command
1193        return self.call(command);
1194    };
1195
1196    /**
1197     * Process a single command
1198     * 
1199     * @param {object} command The command to process
1200     *
1201     * @returns {boolean}
1202     */
1203    const processCommand = (command) => {
1204        try {
1205            self.execute(command);
1206            return true;
1207        } catch (e) {
1208            console.log(e);
1209        }
1210        return false;
1211    };
1212
1213    /**
1214     * While entries exist in the queue, pull and entry out and process it's command.
1215     * When commandQueue.paused is set to true, the processing is halted.
1216     *
1217     * Note:
1218     * - Set commandQueue.paused to false and call this function to cause the queue processing to continue.
1219     * - When an exception is caught, do nothing; if the debug module is installed, it will catch the exception and handle it.
1220     *
1221     * @param {object} commandQueue A queue containing the commands to execute.
1222     *
1223     * @returns {true} The queue was fully processed and is now empty.
1224     * @returns {false} The queue processing was halted before the queue was fully processed.
1225     */
1226    self.processCommands = (commandQueue) => {
1227        // Stop processing the commands if the queue is paused.
1228        let command = null;
1229        while (!commandQueue.paused && (command = queue.pop(commandQueue)) !== null) {
1230            if (!processCommand(command)) {
1231                return true;
1232            }
1233        }
1234        return true;
1235    };
1236
1237    /**
1238     * Calls the registered command handler for the specified command
1239     * (you should always check isRegistered before calling this function)
1240     *
1241     * @param {object} command The response command to be executed.
1242     * @param {string} command.cmd The name of the function.
1243     *
1244     * @returns {boolean}
1245     */
1246    self.call = (command) => {
1247        const handler = handlers[command.cmd];
1248        command.fullName = handler.name;
1249        return handler.func(command);
1250    }
1251
1252    /**
1253     * Attempt to pop the next asynchronous request.
1254     *
1255     * @param {object} oQueue The queue object you would like to modify.
1256     *
1257     * @returns {object|null}
1258     */
1259    self.popAsyncRequest = oQueue => {
1260        if (queue.empty(oQueue) || queue.peek(oQueue).mode === 'synchronous') {
1261            return null;
1262        }
1263        return queue.pop(oQueue);
1264    }
1265
1266    /**
1267     * Causes the processing of items in the queue to be delayed for the specified amount of time.
1268     * This is an asynchronous operation, therefore, other operations will be given an opportunity
1269     * to execute during this delay.
1270     *
1271     * @param {object} command The Response command object.
1272     * @param {integer} command.prop The number of 10ths of a second to sleep.
1273     * @param {object} command.response The command queue.
1274     *
1275     * @returns {true}
1276     */
1277    self.sleep = ({ prop: duration, response: commandQueue }) => {
1278        // The command queue is paused, and will be restarted after the specified delay.
1279        commandQueue.paused = true;
1280        setTimeout(() => {
1281            commandQueue.paused = false;
1282            self.processCommands(commandQueue);
1283        }, duration * 100);
1284        return true;
1285    };
1286
1287    /**
1288     * Set or reset a timeout that is used to restart processing of the queue.
1289     *
1290     * This allows the queue to asynchronously wait for an event to occur (giving the browser time
1291     * to process pending events, like loading files)
1292     *
1293     * @param {object} command The Response command object.
1294     * @param {integer} interval The number of milliseconds to wait before starting/restarting the processing of the queue.
1295     *
1296     * @returns {void}
1297     */
1298    self.setWakeup = (command, interval) => {
1299        const { prop: duration, response: commandQueue } = command;
1300        if (command.retries === undefined) {
1301            command.retries = duration;
1302        }
1303        commandQueue.paused = false;
1304        if (command.retries-- > 0) {
1305            // Requeue the command and sleep for the given interval.
1306            queue.pushFront(commandQueue, command);
1307            self.sleep({ prop: interval, response: commandQueue });
1308        }
1309    };
1310
1311    /**
1312     * Show the specified message.
1313     *
1314     * @param {string} message The message to display.
1315     *
1316     * @returns {void}
1317     */
1318    self.alert = (message) => ajax.message.info(message);
1319
1320    /**
1321     * The function to run after the confirm question, for the comfirmCommands.
1322     *
1323     * @param {object} commandQueue The queue to process.
1324     * @param {integer} count The number of commands to skip.
1325     *
1326     * @returns {void}
1327     */
1328    const confirmCallback = (commandQueue, count) => {
1329        // The last entry in the queue is not a user command, thus it cannot be skipped.
1330        while (count > 0 && commandQueue.count > 1 && queue.pop(commandQueue) !== null) {
1331            --count;
1332        }
1333        // After => the processing is executed.
1334        commandQueue.paused = false;
1335        self.processCommands(commandQueue);
1336    };
1337
1338    /**
1339     * Ask a confirm question and skip the specified number of commands if the answer is ok.
1340     *
1341     * The processing of the queue after the question is delayed so it occurs after this function returns.
1342     * before (when using the blocking confirm() function) or after this function returns.
1343     * @see confirmCallback
1344     *
1345     * @param {object} command The object to track the retry count for.
1346     * @param {integer} count The number of commands to skip.
1347     * @param {string} question The question to ask to the user.
1348     *
1349     * @returns {true}
1350     */
1351    self.confirm = (command, count, question) => {
1352        const { response: commandQueue } = command;
1353        // The command queue is paused, and will be restarted after the confirm question is answered.
1354        commandQueue.paused = true;
1355        ajax.message.confirm(question, '',
1356            () => confirmCallback(commandQueue, 0),
1357            () => confirmCallback(commandQueue, count));
1358        return true;
1359    };
1360})(jaxon.ajax.handler, jaxon.config, jaxon.ajax, jaxon.utils.queue, jaxon.utils.dom);
1361
1362
1363/**
1364 * Class: jaxon.ajax.parameters
1365 */
1366
1367(function(self, str, version) {
1368    /**
1369     * The array of data bags
1370     *
1371     * @type {object}
1372     */
1373    self.bags = {};
1374
1375    /**
1376     * Stringify a parameter of an ajax call.
1377     *
1378     * @param {mixed} oVal - The value to be stringified
1379     *
1380     * @returns {string}
1381     */
1382    const stringify = (oVal) => {
1383        if (oVal === undefined ||  oVal === null) {
1384            return '*';
1385        }
1386        const sType = str.typeOf(oVal);
1387        if (sType === 'object' || sType === 'array') {
1388            try {
1389                return encodeURIComponent(JSON.stringify(oVal));
1390            } catch (e) {
1391                oVal = '';
1392                // do nothing, if the debug module is installed
1393                // it will catch the exception and handle it
1394            }
1395        }
1396        oVal = encodeURIComponent(oVal);
1397        if (sType === 'string') {
1398            return 'S' + oVal;
1399        }
1400        if (sType === 'boolean') {
1401            return 'B' + oVal;
1402        }
1403        if (sType === 'number') {
1404            return 'N' + oVal;
1405        }
1406        return oVal;
1407    };
1408
1409    /**
1410     * Make the databag object to send in the HTTP request.
1411     *
1412     * @param {array} aKeys The keys of values to get from the data bag.
1413     *
1414     * @return {object}
1415     */
1416    const getBagsParam = (aKeys) => JSON.stringify(aKeys.reduce((oValues, sKey) => ({
1417        ...oValues,
1418        [sKey]: self.bags[sKey] ?? '*' }
1419    ), {}));
1420
1421    /**
1422     * Sets the request parameters in a container.
1423     *
1424     * @param {object} oRequest The request object
1425     * @param {object} oRequest.func The function to call on the server app.
1426     * @param {object} oRequest.parameters The parameters to pass to the function.
1427     * @param {array=} oRequest.bags The keys of values to get from the data bag.
1428     * @param {callable} fSetter A function that sets a single parameter
1429     *
1430     * @return {void}
1431     */
1432    const setParams = ({ func, parameters, bags = [] }, fSetter) => {
1433        const dNow = new Date();
1434        fSetter('jxnr', dNow.getTime());
1435        fSetter('jxnv', `${version.major}.${version.minor}.${version.patch}`);
1436
1437        Object.keys(func).forEach(sParam => fSetter(sParam, encodeURIComponent(func[sParam])));
1438
1439        // The parameters value was assigned from the js "arguments" var in a function. So it
1440        // is an array-like object, that we need to convert to a real array => [...parameters].
1441        // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
1442        [...parameters].forEach(xParam => fSetter('jxnargs[]', stringify(xParam)));
1443
1444        bags.length > 0 && fSetter('jxnbags', encodeURIComponent(getBagsParam(bags)));
1445    };
1446
1447    /**
1448     * Processes request specific parameters and store them in a FormData object.
1449     *
1450     * @param {object} oRequest
1451     *
1452     * @return {FormData}
1453     */
1454    const getFormDataParams = (oRequest) => {
1455        const rd = new FormData();
1456        setParams(oRequest, (sParam, sValue) => rd.append(sParam, sValue));
1457
1458        // Files to upload
1459        const input = oRequest.upload.input;
1460        input.files && input.files.forEach(file => rd.append(input.name, file));
1461        return rd;
1462    };
1463
1464    /**
1465     * Processes request specific parameters and store them in an URL encoded string.
1466     *
1467     * @param {object} oRequest
1468     *
1469     * @return {string}
1470     */
1471    const getUrlEncodedParams = (oRequest) => {
1472        const rd = [];
1473        setParams(oRequest, (sParam, sValue) => rd.push(sParam + '=' + sValue));
1474
1475        if (oRequest.method === 'POST') {
1476            return rd.join('&');
1477        }
1478        // Move the parameters to the URL for HTTP GET requests
1479        oRequest.requestURI += oRequest.requestURI.indexOf('?') === -1 ? '?' : '&';
1480        oRequest.requestURI += rd.join('&');
1481        return ''; // The request body is empty
1482    };
1483
1484    /**
1485     * Check if the request has files to upload.
1486     *
1487     * @param {object} oRequest The request object
1488     * @param {object} oRequest.upload The upload object
1489     *
1490     * @return {boolean}
1491     */
1492    const hasUpload = ({ upload }) => upload && upload.ajax && upload.input;
1493
1494    /**
1495     * Processes request specific parameters and generates the temporary
1496     * variables needed by jaxon to initiate and process the request.
1497     *
1498     * Note:
1499     * This is called once per request; upon a request failure, this will not be called for additional retries.
1500     *
1501     * @param {object} oRequest The request object
1502     *
1503     * @return {void}
1504     */
1505    self.process = (oRequest) => {
1506        // Make request parameters.
1507        oRequest.requestURI = oRequest.URI;
1508        oRequest.requestData = hasUpload(oRequest) ?
1509            getFormDataParams(oRequest) : getUrlEncodedParams(oRequest);
1510    };
1511})(jaxon.ajax.parameters, jaxon.utils.string, jaxon.version);
1512
1513
1514/**
1515 * Class: jaxon.ajax.request
1516 */
1517
1518(function(self, cfg, params, rsp, cbk, handler, upload, queue) {
1519    /**
1520     * Initialize a request object, populating default settings, where call specific
1521     * settings are not already provided.
1522     *
1523     * @param {object} oRequest An object that specifies call specific settings that will,
1524     *      in addition, be used to store all request related values.
1525     *      This includes temporary values used internally by jaxon.
1526     *
1527     * @returns {boolean}
1528     */
1529    self.initialize = (oRequest) => {
1530        cfg.setRequestOptions(oRequest);
1531
1532        cbk.initCallbacks(oRequest);
1533        cbk.execute(oRequest, 'onInitialize');
1534
1535        oRequest.status = (oRequest.statusMessages) ? cfg.status.update : cfg.status.dontUpdate;
1536        oRequest.cursor = (oRequest.waitCursor) ? cfg.cursor.update : cfg.cursor.dontUpdate;
1537
1538        // Look for upload parameter
1539        upload.initialize(oRequest);
1540    };
1541
1542    /**
1543     * Prepare a request, by setting the HTTP options, handlers and processor.
1544     *
1545     * @param {object} oRequest The request context object.
1546     *
1547     * @return {void}
1548     */
1549    self.prepare = (oRequest) => {
1550        --oRequest.requestRetry;
1551        cbk.execute(oRequest, 'onPrepare');
1552
1553        oRequest.httpRequestOptions = {
1554            ...cfg.httpRequestOptions,
1555            method: oRequest.method,
1556            headers: {
1557                ...oRequest.commonHeaders,
1558                ...(oRequest.method === 'POST' ? oRequest.postHeaders : oRequest.getHeaders),
1559            },
1560            body: oRequest.requestData,
1561        };
1562
1563        oRequest.responseConverter = (response) => {
1564            // Save the reponse object
1565            oRequest.response = response;
1566            // Get the response content
1567            return oRequest.convertResponseToJson ? response.json() : response.text();
1568        };
1569        oRequest.responseHandler = (responseContent) => {
1570            oRequest.responseContent = responseContent;
1571            // Synchronous request are processed immediately.
1572            // Asynchronous request are processed only if the queue is empty.
1573            if (queue.empty(handler.q.send) || oRequest.mode === 'synchronous') {
1574                rsp.received(oRequest);
1575            } else {
1576                queue.push(handler.q.recv, oRequest);
1577            }
1578        };
1579        oRequest.errorHandler = (error) => {
1580            cbk.execute(oRequest, 'onFailure');
1581            throw error;
1582        };
1583        if (!oRequest.responseProcessor) {
1584            oRequest.responseProcessor = rsp.jsonProcessor;
1585        }
1586
1587        // No request is submitted while there are pending requests in the outgoing queue.
1588        const submitRequest = queue.empty(handler.q.send);
1589        if (oRequest.mode === 'synchronous') {
1590            // Synchronous requests are always queued, in both send and recv queues.
1591            queue.push(handler.q.send, oRequest);
1592            queue.push(handler.q.recv, oRequest);
1593            return submitRequest;
1594        }
1595        // Asynchronous requests are queued in send queue only if they are not submitted.
1596        submitRequest || queue.push(handler.q.send, oRequest);
1597        return submitRequest;
1598    };
1599
1600    /**
1601     * Clean up the request object.
1602     *
1603     * @param {object} oRequest The request context object.
1604     *
1605     * @returns {void}
1606     */
1607    const cleanUp = (oRequest) => {
1608        // clean up -- these items are restored when the request is initiated
1609        delete oRequest.func;
1610        delete oRequest.URI;
1611        delete oRequest.requestURI;
1612        delete oRequest.requestData;
1613        delete oRequest.requestRetry;
1614        delete oRequest.httpRequestOptions;
1615        delete oRequest.responseHandler;
1616        delete oRequest.responseConverter;
1617        delete oRequest.responseContent;
1618        delete oRequest.response;
1619        delete oRequest.errorHandler;
1620    };
1621
1622    /**
1623     * Called by the response command queue processor when all commands have been processed.
1624     *
1625     * @param {object} oRequest The request context object.
1626     *
1627     * @return {void}
1628     */
1629    self.complete = (oRequest) => {
1630        cbk.execute(oRequest, 'onComplete');
1631        oRequest.cursor.onComplete();
1632        oRequest.status.onComplete();
1633
1634        cleanUp(oRequest);
1635
1636        // All the requests and responses queued while waiting must now be processed.
1637        if(oRequest.mode === 'synchronous') {
1638            // Remove the current request from the send and recv queues.
1639            queue.pop(handler.q.send);
1640            queue.pop(handler.q.recv);
1641            // Process the asynchronous responses received while waiting.
1642            while((recvRequest = handler.popAsyncRequest(handler.q.recv)) !== null) {
1643                rsp.received(recvRequest);
1644            }
1645            // Submit the asynchronous requests sent while waiting.
1646            while((nextRequest = handler.popAsyncRequest(handler.q.send)) !== null) {
1647                self.submit(nextRequest);
1648            }
1649            // Submit the next synchronous request, if there's any.
1650            if((nextRequest = queue.peek(handler.q.send)) !== null) {
1651                self.submit(nextRequest);
1652            }
1653        }
1654    };
1655
1656    /**
1657     * Send a request.
1658     *
1659     * @param {object} oRequest The request context object.
1660     *
1661     * @returns {void}
1662     */
1663    self._send = (oRequest) => {
1664        fetch(oRequest.requestURI, oRequest.httpRequestOptions)
1665            .then(oRequest.responseConverter)
1666            .then(oRequest.responseHandler)
1667            .catch(oRequest.errorHandler);
1668    };
1669
1670    /**
1671     * Create a request object and submit the request using the specified request type;
1672     * all request parameters should be finalized by this point.
1673     * Upon failure of a POST, this function will fall back to a GET request.
1674     *
1675     * @param {object} oRequest The request context object.
1676     *
1677     * @returns {mixed}
1678     */
1679    self.submit = (oRequest) => {
1680        oRequest.status.onRequest();
1681
1682        cbk.execute(oRequest, 'onResponseDelay');
1683        cbk.execute(oRequest, 'onExpiration');
1684        cbk.execute(oRequest, 'onRequest');
1685
1686        oRequest.cursor.onWaiting();
1687        oRequest.status.onWaiting();
1688
1689        self._send(oRequest);
1690
1691        return oRequest.returnValue;
1692    };
1693
1694    /**
1695     * Abort the request.
1696     *
1697     * @param {object} oRequest The request context object.
1698     *
1699     * @returns {void}
1700     */
1701    self.abort = (oRequest) => {
1702        oRequest.aborted = true;
1703        self.complete(oRequest);
1704    };
1705
1706    /**
1707     * Initiates a request to the server.
1708     *
1709     * @param {object}
1709 func An object containing the name of the function to
1710     *      execute on the server. The standard request is: {jxnfun:'function_name'}
1711     * @param {object=} funcArgs A request object which may contain call specific parameters.
1712     *      This object will be used by jaxon to store all the request parameters as well as
1713     *      temporary variables needed during the processing of the request.
1714     *
1715     * @returns {boolean}
1716     */
1717    self.execute = (func, funcArgs) => {
1718        if (func === undefined) {
1719            return false;
1720        }
1721
1722        const oRequest = funcArgs ?? {};
1723        oRequest.func = func;
1724
1725        self.initialize(oRequest);
1726
1727        // Process the request parameters
1728        cbk.execute(oRequest, 'onProcessParams');
1729        params.process(oRequest);
1730
1731        while (oRequest.requestRetry > 0) {
1732            try {
1733                return self.prepare(oRequest) ? self.submit(oRequest) : null;
1734            }
1735            catch (e) {
1736                cbk.execute(oRequest, 'onFailure');
1737                if (oRequest.requestRetry <= 0) {
1738                    throw e;
1739                }
1740            }
1741        }
1742        return true;
1743    };
1744})(jaxon.ajax.request, jaxon.config, jaxon.ajax.parameters, jaxon.ajax.response,
1745    jaxon.ajax.callback, jaxon.ajax.handler, jaxon.utils.upload, jaxon.utils.queue);
1746
1747
1748/**
1749 * Class: jaxon.ajax.response
1750 */
1751
1752(function(self, config, handler, req, cbk, queue) {
1753    /**
1754     * This array contains a list of codes which will be returned from the server upon
1755     * successful completion of the server portion of the request.
1756     *
1757     * These values should match those specified in the HTTP standard.
1758     *
1759     * @var {array}
1760     */
1761    const successCodes = [0, 200];
1762
1763    // 10.4.1 400 Bad Request
1764    // 10.4.2 401 Unauthorized
1765    // 10.4.3 402 Payment Required
1766    // 10.4.4 403 Forbidden
1767    // 10.4.5 404 Not Found
1768    // 10.4.6 405 Method Not Allowed
1769    // 10.4.7 406 Not Acceptable
1770    // 10.4.8 407 Proxy Authentication Required
1771    // 10.4.9 408 Request Timeout
1772    // 10.4.10 409 Conflict
1773    // 10.4.11 410 Gone
1774    // 10.4.12 411 Length Required
1775    // 10.4.13 412 Precondition Failed
1776    // 10.4.14 413 Request Entity Too Large
1777    // 10.4.15 414 Request-URI Too Long
1778    // 10.4.16 415 Unsupported Media Type
1779    // 10.4.17 416 Requested Range Not Satisfiable
1780    // 10.4.18 417 Expectation Failed
1781    // 10.5 Server Error 5xx
1782    // 10.5.1 500 Internal Server Error
1783    // 10.5.2 501 Not Implemented
1784    // 10.5.3 502 Bad Gateway
1785    // 10.5.4 503 Service Unavailable
1786    // 10.5.5 504 Gateway Timeout
1787    // 10.5.6 505 HTTP Version Not Supported
1788
1789    /**
1790     * This array contains a list of status codes returned by the server to indicate
1791     * that the request failed for some reason.
1792     *
1793     * @var {array}
1794     */
1795    const errorCodes = [400, 401, 402, 403, 404, 500, 501, 502, 503];
1796
1797    // 10.3.1 300 Multiple Choices
1798    // 10.3.2 301 Moved Permanently
1799    // 10.3.3 302 Found
1800    // 10.3.4 303 See Other
1801    // 10.3.5 304 Not Modified
1802    // 10.3.6 305 Use Proxy
1803    // 10.3.7 306 (Unused)
1804    // 10.3.8 307 Temporary Redirect
1805
1806    /**
1807     * An array of status codes returned from the server to indicate a request for redirect to another URL.
1808     *
1809     * Typically, this is used by the server to send the browser to another URL.
1810     * This does not typically indicate that the jaxon request should be sent to another URL.
1811     *
1812     * @var {array}
1813     */
1814    const redirectCodes = [301, 302, 307];
1815
1816    /**
1817     * Check if a status code indicates a success.
1818     *
1819     * @param {int} nStatusCode A status code.
1820     *
1821     * @return {bool}
1822     */
1823    self.isSuccessCode = nStatusCode => successCodes.indexOf(nStatusCode) >= 0;
1824
1825    /**
1826     * Check if a status code indicates a redirect.
1827     *
1828     * @param {int} nStatusCode A status code.
1829     *
1830     * @return {bool}
1831     */
1832    self.isRedirectCode = nStatusCode => redirectCodes.indexOf(nStatusCode) >= 0;
1833
1834    /**
1835     * Check if a status code indicates an error.
1836     *
1837     * @param {int} nStatusCode A status code.
1838     *
1839     * @return {bool}
1840     */
1841    self.isErrorCode = nStatusCode => errorCodes.indexOf(nStatusCode) >= 0;
1842
1843    /**
1844     * Parse the JSON response into a series of commands.
1845     *
1846     * @param {object} oRequest The request context object.
1847     *
1848     * @return {int}
1849     */
1850    const queueCommands = (oRequest) => {
1851        const responseContent = oRequest.responseContent;
1852        if (!responseContent || !responseContent.jxnobj) {
1853            return 0;
1854        }
1855
1856        oRequest.status.onProcessing();
1857
1858        if (responseContent.jxnrv) {
1859            oRequest.returnValue = responseContent.jxnrv;
1860        }
1861
1862        responseContent.debugmsg && console.log(responseContent.debugmsg);
1863
1864        let nSequence = 0;
1865        responseContent.jxnobj.forEach(command => queue.push(oRequest.commandQueue, {
1866            fullName: '*unknown*',
1867            ...command,
1868            sequence: nSequence++,
1869            response: oRequest.commandQueue,
1870            request: oRequest,
1871            context: oRequest.context,
1872        }));
1873
1874        return nSequence;
1875    };
1876
1877    /**
1878     * Queue the last command that is will end the response processing.
1879     *
1880     * @param {object} oRequest The request context object.
1881     * @param {int} nSequence The last command sequence
1882     *
1883     * @return {int}
1884     */
1885    const queueEndCommand = (oRequest, nSequence) => queue.push(oRequest.commandQueue, {
1886        fullName: 'Response Complete',
1887        sequence: nSequence,
1888        request: oRequest,
1889        context: oRequest.context,
1890        cmd: 'rcmplt',
1891    });
1892
1893    /**
1894     * This is the JSON response processor.
1895     *
1896     * @param {object} oRequest The request context object.
1897     *
1898     * @return {mixed}
1899     */
1900    self.jsonProcessor = (oRequest) => {
1901        const status = oRequest.response.status;
1902        if (self.isSuccessCode(status)) {
1903            cbk.execute(oRequest, 'onSuccess');
1904            // Queue and process the commands in the response.
1905            const nSequence = queueCommands(oRequest)
1906            queueEndCommand(oRequest, nSequence);
1907            handler.processCommands(oRequest.commandQueue);
1908        }
1909        else if (self.isRedirectCode(status)) {
1910            cbk.execute(oRequest, 'onRedirect');
1911            req.complete(oRequest);
1912            window.location = oRequest.response.headers.get('location');
1913        }
1914        else if (self.isErrorCode(status)) {
1915            cbk.execute(oRequest, 'onFailure');
1916            req.complete(oRequest);
1917        }
1918
1919        return oRequest.returnValue;
1920    };
1921
1922    /**
1923     * Process the response.
1924     *
1925     * @param {object} oRequest The request context object.
1926     *
1927     * @return {mixed}
1928     */
1929    self.received = (oRequest) => {
1930        // Sometimes the response.received gets called when the request is aborted
1931        if (oRequest.aborted) {
1932            return null;
1933        }
1934
1935        // Create a queue for the commands in the response.
1936        oRequest.commandQueue = queue.create(config.commandQueueSize);
1937
1938        // The response is successfully received, clear the timers for expiration and delay.
1939        cbk.clearTimer(oRequest, 'onExpiration');
1940        cbk.clearTimer(oRequest, 'onResponseDelay');
1941        cbk.execute(oRequest, 'beforeResponseProcessing');
1942
1943        return oRequest.responseProcessor(oRequest);
1944    };
1945})(jaxon.ajax.response, jaxon.config, jaxon.ajax.handler, jaxon.ajax.request,
1946    jaxon.ajax.callback, jaxon.utils.queue);
1947
1948
1949/**
1950 * Class: jaxon.cmd.body
1951 */
1952
1953(function(self, dom, baseDocument) {
1954    /**
1955     * Assign an element's attribute to the specified value.
1956     *
1957     * @param {object} command The Response command object.
1958     * @param {Element} command.target The HTML element to effect.
1959     * @param {string} command.prop The name of the attribute to set.
1960     * @param {string} command.data The new value to be applied.
1961     *
1962     * @returns {true} The operation completed successfully.
1963     */
1964    self.assign = ({ target: element, prop: property, data: value }) => {
1965        const xElt = dom.getInnerObject(property, element);
1966        if (xElt !== null) {
1967            xElt.node[xElt.attr] = value;
1968        }
1969        return true;
1970    };
1971
1972    /**
1973     * Append the specified value to an element's attribute.
1974     *
1975     * @param {object} command The Response command object.
1976     * @param {Element} command.target The HTML element to effect.
1977     * @param {string} command.prop The name of the attribute to append to.
1978     * @param {string} command.data The new value to be appended.
1979     *
1980     * @returns {true} The operation completed successfully.
1981     */
1982    self.append = ({ target: element, prop: property, data: value }) => {
1983        const xElt = dom.getInnerObject(property, element);
1984        if (xElt !== null) {
1985            xElt.node[xElt.attr] = xElt.node[xElt.attr] + value;
1986        }
1987        return true;
1988    };
1989
1990    /**
1991     * Prepend the specified value to an element's attribute.
1992     *
1993     * @param {object} command The Response command object.
1994     * @param {Element} command.target The HTML element to effect.
1995     * @param {string} command.prop The name of the attribute.
1996     * @param {string} command.data The new value to be prepended.
1997     *
1998     * @returns {true} The operation completed successfully.
1999     */
2000    self.prepend = ({ target: element, prop: property, data: value }) => {
2001        const xElt = dom.getInnerObject(property, element);
2002        if (xElt !== null) {
2003            xElt.node[xElt.attr] = value + xElt.node[xElt.attr];
2004        }
2005        return true;
2006    };
2007
2008    /**
2009     * Replace a text in the value of a given property in an element
2010     *
2011     * @param {object} xElt The value returned by the dom.getInnerObject() function
2012     * @param {string} sSearch The text to search
2013     * @param {string} sReplace The text to use as replacement
2014     *
2015     * @returns {void}
2016     */
2017    const replaceText = (xElt, sSearch, sReplace) => {
2018        const bFunction = (typeof xElt.node[xElt.attr] === 'function');
2019        const sCurText = bFunction ? xElt.node[xElt.attr].join('') : xElt.node[xElt.attr];
2020        const sNewText = sCurText.replaceAll(sSearch, sReplace);
2021        if (bFunction || dom.willChange(xElt.node, xElt.attr, sNewText)) {
2022            xElt.node[xElt.attr] = sNewText;
2023        }
2024    };
2025
2026    /**
2027     * Search and replace the specified text.
2028     *
2029     * @param {object} command The Response command object.
2030     * @param {Element} command.target The element which is to be modified.
2031     * @param {string} command.prop The name of the attribute to be set.
2032     * @param {object} command.data The search text and replacement text.
2033     * @param {object} command.data.s The search text.
2034     * @param {object} command.data.r The replacement text.
2035     *
2036     * @returns {true} The operation completed successfully.
2037     */
2038    self.replace = ({ target: element, prop, data: { s: search, r: replace } }) => {
2039        const xElt = dom.getInnerObject(prop, element);
2040        if (xElt !== null) {
2041            replaceText(xElt, prop === 'innerHTML' ? dom.getBrowserHTML(search) : search, replace);
2042        }
2043        return true;
2044    };
2045
2046    /**
2047     * Delete an element.
2048     *
2049     * @param {object} command The Response command object.
2050     * @param {Element} command.target The element which will be deleted.
2051     *
2052     * @returns {true} The operation completed successfully.
2053     */
2054    self.remove = ({ target: element }) => {
2055        dom.removeElement(element);
2056        return true;
2057    };
2058
2059    /**
2060     * Create a new element and append it to the specified parent element.
2061     *
2062     * @param {object} command The Response command object.
2063     * @param {Element} command.target The element which will contain the new element.
2064     * @param {string} command.data The tag name for the new element.
2065     * @param {string} command.prop The value to be assigned to the id attribute of the new element.
2066     *
2067     * @returns {true} The operation completed successfully.
2068     */
2069    self.create = ({ target: element, data: sTag, prop: sId }) => {
2070        if (element) {
2071            const target = baseDocument.createElement(sTag);
2072            target.setAttribute('id', sId);
2073            element.appendChild(target);
2074        }
2075        return true;
2076    };
2077
2078    /**
2079     * Insert a new element before the specified element.
2080     *
2081     * @param {object} command The Response command object.
2082     * @param {Element} command.target The element that will be used as the reference point for insertion.
2083     * @param {string} command.data The tag name for the new element.
2084     * @param {string} command.prop The value that will be assigned to the new element's id attribute.
2085     *
2086     * @returns {true} The operation completed successfully.
2087     */
2088    self.insert = ({ target: element, data: sTag, prop: sId }) => {
2089        if (element && element.parentNode) {
2090            const target = baseDocument.createElement(sTag);
2091            target.setAttribute('id', sId);
2092            element.parentNode.insertBefore(target, element);
2093        }
2094        return true;
2095    };
2096
2097    /**
2098     * Insert a new element after the specified element.
2099     *
2100     * @param {object} command The Response command object.
2101     * @param {Element} command.target The element that will be used as the reference point for insertion.
2102     * @param {string} command.data The tag name for the new element.
2103     * @param {string} command.prop The value that will be assigned to the new element's id attribute.
2104     *
2105     * @returns {true} The operation completed successfully.
2106     */
2107    self.insertAfter = ({ target: element, data: sTag, prop: sId }) => {
2108        if (element && element.parentNode) {
2109            const target = baseDocument.createElement(sTag);
2110            target.setAttribute('id', sId);
2111            element.parentNode.insertBefore(target, element.nextSibling);
2112        }
2113        return true;
2114    };
2115
2116    /**
2117     * Assign a value to a named member of the current script context object.
2118     *
2119     * @param {object} command The Response command object.
2120     * @param {string} command.prop The name of the member to assign.
2121     * @param {string|object} command.data The value to assign to the member.
2122     * @param {object} command.context The current script context object which is accessable via the 'this' keyword.
2123     *
2124     * @returns {true} The operation completed successfully.
2125     */
2126    self.contextAssign = ({ context, prop: sAttribute, data: value }) => {
2127        const xElt = dom.getInnerObject(sAttribute, context);
2128        if (xElt !== null) {
2129            xElt.node[xElt.attr] = value;
2130        }
2131        return true;
2132    };
2133
2134    /**
2135     * Appends a value to a named member of the current script context object.
2136     *
2137     * @param {object} command The Response command object.
2138     * @param {string} command.prop The name of the member to append to.
2139     * @param {string|object} command.data The value to append to the member.
2140     * @param {object} command.context The current script context object which is accessable via the 'this' keyword.
2141     *
2142     * @returns {true} The operation completed successfully.
2143     */
2144    self.contextAppend = ({ context, prop: sAttribute, data: value }) => {
2145        const xElt = dom.getInnerObject(sAttribute, context);
2146        if (xElt !== null) {
2147            xElt.node[xElt.attr] = xElt.node[xElt.attr] + value;
2148        }
2149        return true;
2150    };
2151
2152    /**
2153     * Prepend a value to a named member of the current script context object.
2154     *
2155     * @param {object} command The Response command object.
2156     * @param {string} command.prop The name of the member to prepend to.
2157     * @param {string|object} command.data The value to prepend to the member.
2158     * @param {object} command.context The current script context object which is accessable via the 'this' keyword.
2159     *
2160     * @returns {true} The operation completed successfully.
2161     */
2162    self.contextPrepend = ({ context, prop: sAttribute, data: value }) => {
2163        const xElt = dom.getInnerObject(sAttribute, context);
2164        if (xElt !== null) {
2165            xElt.node[xElt.attr] = value + xElt.node[xElt.attr];
2166        }
2167        return true;
2168    };
2169})(jaxon.cmd.body, jaxon.utils.dom, jaxon.config.baseDocument);
2170
2171
2172/**
2173 * Class: jaxon.cmd.event
2174 */
2175
2176(function(self, dom, str, script) {
2177    /**
2178     *  Set an event handler.
2179     *
2180     * @param {object} command The Response command object.
2181     * @param {string} command.id The target element id
2182     * @param {object} command.target The target element
2183     * @param {string} command.prop The event name
2184     * @param {string} command.data The callback code
2185     *
2186     * @returns {true} The operation completed successfully.
2187     */
2188    self.setEvent = ({ target: oTarget, prop: sEvent, data: sCode }) => {
2189        dom.createFunction(`(e) => { ${str.doubleQuotes(sCode)} }`);
2190        oTarget[str.addOnPrefix(sEvent)] = script.context.delegateCall;
2191        return true;
2192    };
2193
2194    /**
2195     * Add an event handler to the specified target.
2196     *
2197     * @param {object} command The Response command object.
2198     * @param {string} command.id The target element id
2199     * @param {object} command.target The target element
2200     * @param {string} command.prop The name of the event.
2201     * @param {string} command.data The name of the function to be called
2202     *
2203     * @returns {true} The operation completed successfully.
2204     */
2205    self.addHandler = ({ target, prop: sEvent, data: sFuncName }) => {
2206        target.addEventListener(str.stripOnPrefix(sEvent), dom.findFunction(sFuncName), false)
2207        return true;
2208    };
2209
2210    /**
2211     * Remove an event handler from an target.
2212     *
2213     * @param {object} command The Response command object.
2214     * @param {string} command.id The target element id
2215     * @param {object} command.target The target element
2216     * @param {string} command.prop The name of the event.
2217     * @param {string} command.data The name of the function to be removed
2218     *
2219     * @returns {true} The operation completed successfully.
2220     */
2221    self.removeHandler = ({ target, prop: sEvent, data: sFuncName }) => {
2222       target.removeEventListener(str.stripOnPrefix(sEvent), dom.findFunction(sFuncName), false);
2223       return true;
2224    };
2225})(jaxon.cmd.event, jaxon.utils.dom, jaxon.utils.string, jaxon.cmd.script);
2226
2227
2228/**
2229 * Class: jaxon.cmd.form
2230 */
2231
2232(function(self, baseDocument) {
2233    /**
2234     * Create and return a form input element with the specified parameters.
2235     *
2236     * @param {string} type The type of input element desired.
2237     * @param {string} name The value to be assigned to the name attribute.
2238     * @param {string} id The value to be assigned to the id attribute.
2239     *
2240     * @returns {true} The operation completed successfully.
2241     */
2242    const getInput = (type, name, id) => {
2243        const oInput = baseDocument.createElement('input');
2244        oInput.setAttribute('type', type);
2245        oInput.setAttribute('name', name);
2246        oInput.setAttribute('id', id);
2247        return oInput;
2248    };
2249
2250    /**
2251     * Create a new input element under the specified parent.
2252     *
2253     * @param {object} command The Response command object.
2254     * @param {string} command.id The target element id
2255     * @param {object} command.target The element that will be used as the reference for the insertion.
2256     * @param {string} command.type The value to be assigned to the type attribute.
2257     * @param {string} command.data The value to be assigned to the name attribute.
2258     * @param {string} command.prop The value to be assigned to the id attribute.
2259     *
2260     * @returns {true} The operation completed successfully.
2261     */
2262    self.createInput = ({ target: objParent, type: sType, data: sName, prop: sId }) => {
2263        const target = getInput(sType, sName, sId);
2264        if (objParent && target) {
2265            objParent.appendChild(target);
2266        }
2267        return true;
2268    };
2269
2270    /**
2271     * Insert a new input element before the specified element.
2272     *
2273     * @param {object} command The Response command object.
2274     * @param {string} command.id The target element id
2275     * @param {object} command.target The element that will be used as the reference for the insertion.
2276     * @param {string} command.type The value to be assigned to the type attribute.
2277     * @param {string} command.data The value to be assigned to the name attribute.
2278     * @param {string} command.prop The value to be assigned to the id attribute.
2279     *
2280     * @returns {true} The operation completed successfully.
2281     */
2282    self.insertInput = ({ target: objSibling, type: sType, data: sName, prop: sId }) => {
2283        const target = getInput(sType, sName, sId);
2284        if (target && objSibling && objSibling.parentNode) {
2285            objSibling.parentNode.insertBefore(target, objSibling);
2286        }
2287        return true;
2288    };
2289
2290    /**
2291     * Insert a new input element after the specified element.
2292     *
2293     * @param {object} command The Response command object.
2294     * @param {string} command.id The target element id
2295     * @param {object} command.target The element that will be used as the reference for the insertion.
2296     * @param {string} command.type The value to be assigned to the type attribute.
2297     * @param {string} command.data The value to be assigned to the name attribute.
2298     * @param {string} command.prop The value to be assigned to the id attribute.
2299     *
2300     * @returns {true} The operation completed successfully.
2301     */
2302    self.insertInputAfter = ({ target: objSibling, type: sType, data: sName, prop: sId }) => {
2303        const target = getInput(sType, sName, sId);
2304        if (target && objSibling && objSibling.parentNode) {
2305            objSibling.parentNode.insertBefore(target, objSibling.nextSibling);
2306        }
2307        return true;
2308    };
2309})(jaxon.cmd.form, jaxon.config.baseDocument);
2310
2311
2312/**
2313 * Class: jaxon.cmd.head
2314 */
2315
2316(function(self, handler, baseDocument) {
2317    /**
2318     * Add a reference to the specified script file if one does not already exist in the HEAD of the current document.
2319     *
2320     * @param {object} command The Response command object.
2321     * @param {string} command.data The URI of the file.
2322     *
2323     * @returns {true} The operation completed successfully.
2324     */
2325    self.includeScriptOnce = ({ data: src, type = 'text/javascript', elm_id }) => {
2326        // Check for existing script tag for this file.
2327        const loadedScripts = baseDocument.getElementsByTagName('script');
2328        // Find an existing script with the same file name
2329        const loadedScript = Array.from(loadedScripts)
2330            .find(script => script.src && script.src.indexOf(src) >= 0);
2331        return (loadedScript) ? true : self.includeScript({ data: src, type, elm_id });
2332    };
2333
2334    /**
2335     * Adds a SCRIPT tag referencing the specified file.
2336     * This effectively causes the script to be loaded in the browser.
2337     *
2338     * @param {object} command The Response command object.
2339     * @param {string} command.data The URI of the file.
2340     * @param {string='text/javascript'} command.type The type of the file.
2341     * @param {string=} command.elm_id The script tag id.
2342     *
2343     * @returns {true} The operation completed successfully.
2344     */
2345    self.includeScript = ({ data: src, type = 'text/javascript', elm_id }) => {
2346        const objHead = baseDocument.getElementsByTagName('head');
2347        const objScript = baseDocument.createElement('script');
2348        objScript.src = src;
2349        objScript.type = type;
2350        if (elm_id) {
2351            objScript.setAttribute('id', elm_id);
2352        }
2353        objHead[0].appendChild(objScript);
2354        return true;
2355    };
2356
2357    /**
2358     * Locates a SCRIPT tag in the HEAD of the document which references the specified file and removes it.
2359     *
2360     * @param {object} command The Response command object.
2361     * @param {string} command.data The URI of the file.
2362     * @param {string=} command.unld A function to execute.
2363     *
2364     * @returns {true} The operation completed successfully.
2365     */
2366    self.removeScript = ({ data: src, unld: unload }) => {
2367        const loadedScripts = baseDocument.getElementsByTagName('script');
2368        // Find an existing script with the same file name
2369        const loadedScript = Array.from(loadedScripts)
2370            .find(script => script.src && script.src.indexOf(src) >= 0);
2371        if (!loadedScript) {
2372            return true;
2373        }
2374        if (unload) {
2375            // Execute the provided unload function.
2376            self.execute({ data: unload, context: window });
2377        }
2378        loadedScript.parentNode.removeChild(loadedScript);
2379        return true;
2380    };
2381
2382    /**
2383     * Add a LINK reference to the specified .css file if it does not already exist in the HEAD of the current document.
2384     *
2385     * @param {object} command The Response command object.
2386     * @param {string} command.data The URI of the .css file to reference.
2387     * @param {string='screen'} command.media The media type of the css file (print/screen/handheld,..)
2388     *
2389     * @returns {true} The operation completed successfully.
2390     */
2391    self.includeCSS = ({ data: fileName, media = 'screen' }) => {
2392        const oHeads = baseDocument.getElementsByTagName('head');
2393        const oHead = oHeads[0];
2394        const found = Array.from(oHead.getElementsByTagName('link'))
2395            .find(link => link.href.indexOf(fileName) >= 0 && link.media == media);
2396        if (found) {
2397            return true;
2398        }
2399
2400        const oCSS = baseDocument.createElement('link');
2401        oCSS.rel = 'stylesheet';
2402        oCSS.type = 'text/css';
2403        oCSS.href = fileName;
2404        oCSS.media = media;
2405        oHead.appendChild(oCSS);
2406        return true;
2407    };
2408
2409    /**
2410     * Locate and remove a LINK reference from the current document's HEAD.
2411     *
2412     * @param {object} command The Response command object.
2413     * @param {string} command.data The URI of the .css file.
2414     * @param {string='screen'} command.media The media type of the css file (print/screen/handheld,..)
2415     *
2416     * @returns {true} The operation completed successfully.
2417     */
2418    self.removeCSS = ({ data: fileName, media = 'screen' }) => {
2419        const oHeads = baseDocument.getElementsByTagName('head');
2420        const oHead = oHeads[0];
2421        const oLinks = oHead.getElementsByTagName('link');
2422        oLinks.filter(link => link.href.indexOf(fileName) >= 0 && link.media === media)
2423            .forEach(link => oHead.removeChild(link));
2424        return true;
2425    },
2426
2427    /**
2428     * Attempt to detect when all .css files have been loaded once they are referenced by a LINK tag
2429     * in the HEAD of the current document.
2430     *
2431     * @param {object} command The Response command object.
2432     * @param {integer} command.prop The number of 1/10ths of a second to wait before giving up.
2433     * @param {object} command.response The Response object.
2434     *
2435     * @returns {true} The .css files appear to be loaded.
2436     * @returns {false} The .css files do not appear to be loaded and the timeout has not expired.
2437     */
2438    self.waitForCSS = (command) => {
2439        const oDocSS = baseDocument.styleSheets;
2440        const ssLoaded = Array.from(oDocSS).every(styleSheet => {
2441            const enabled = styleSheet.cssRules.length ?? styleSheet.rules.length ?? 0;
2442            return enabled !== 0;
2443        });
2444        if (ssLoaded) {
2445            return true;
2446        }
2447
2448        // Inject a delay in the queue processing and handle retry counter
2449        handler.setWakeup(command, 10);
2450        return true;
2451    };
2452})(jaxon.cmd.head, jaxon.ajax.handler, jaxon.config.baseDocument);
2453
2454
2455/**
2456 * Class: jaxon.cmd.script
2457 */
2458
2459(function(self, handler, dom, str, queue) {
2460    /**
2461     * Show the specified message.
2462     *
2463     * @param {object} command The Response command object.
2464     * @param {string} command.data The message to display.
2465     *
2466     * @returns {true} The operation completed successfully.
2467     */
2468    self.alert = ({ data: message }) => {
2469        handler.alert(message);
2470        return true;
2471    };
2472
2473    /**
2474     * Prompt the user with the specified question, if the user responds by clicking cancel,
2475     * then skip the specified number of commands in the response command queue.
2476     * If the user clicks Ok, the command processing resumes normal operation.
2477     *
2478     * @param {object} command The Response command object.
2479     * @param {string} command.data The question to ask.
2480     * @param {integer} command.count The number of commands to skip.
2481     *
2482     * @returns {false} Stop the processing of the command queue until the user answers the question.
2483     */
2484    self.confirm = (command) => {
2485        const { count, data: question } = command;
2486        handler.confirm(command, count, question);
2487        return false;
2488    };
2489
2490    /**
2491     * Call a javascript function with a series of parameters using the current script context.
2492     *
2493     * @param {object} command The Response command object.
2494     * @param {array} command.data  The parameters to pass to the function.
2495     * @param {string} command.func The name of the function to call.
2496     * @param {object} command.context The javascript object to be referenced as 'this' in the script.
2497     *
2498     * @returns {true} The operation completed successfully.
2499     */
2500    self.call = ({ func: sFuncName, data: aFuncParams, context = {} }) => {
2501        self.context = context;
2502        const func = dom.findFunction(sFuncName);
2503        func && func.apply(self.context, aFuncParams);
2504        return true;
2505    };
2506
2507    /**
2508     * Execute the specified string of javascript code, using the current script context.
2509     *
2510     * @param {object} command The Response command object.
2511     * @param {string} command.data The javascript to be evaluated.
2512     * @param {object} command.context The javascript object to be referenced as 'this' in the script.
2513     *
2514     * @returns {true} The operation completed successfully.
2515     */
2516    self.execute = ({ data: funcBody, context = {} }) => {
2517        self.context = context;
2518        const jsCode = `() => {
2519    ${funcBody}
2520}`;
2521
2522        dom.createFunction(jsCode) && self.context.delegateCall();
2523        return true;
2524    };
2525
2526    /**
2527     * Test for the specified condition, using the current script context;
2528     * if the result is false, sleep for 1/10th of a second and try again.
2529     *
2530     * @param {object} command The Response command object.
2531     * @param {string} command.data The javascript to evaluate.
2532     * @param {integer} command.prop The number of 1/10ths of a second to wait before giving up.
2533     * @param {object} command.context The javascript object to be referenced as 'this' in the script.
2534     *
2535     * @returns {true} The condition evaluates to true or the sleep time has expired.
2536     * @returns {false} The condition evaluates to false and the sleep time has not expired.
2537     */
2538    self.waitFor = (command) => {
2539        const { data: funcBody, context = {} } = command;
2540        self.context = context;
2541        const jsCode = `() => {
2542    return (${funcBody});
2543}`;
2544
2545        if (dom.createFunction(jsCode) && self.context.delegateCall()) {
2546            return true;
2547        }
2548
2549        // Inject a delay in the queue processing and handle retry counter
2550        handler.setWakeup(command, 100);
2551        return true;
2552    };
2553
2554    /**
2555     * Get function parameters as string
2556     *
2557     * @param {string} parameters 
2558     */
2559    const getParameters = (parameters) => {
2560        if (parameters === undefined) {
2561            return '';
2562        }
2563        const sType = str.typeOf(parameters);
2564        if (sType === 'array') {
2565            return parameters.join(', ');
2566        }
2567        if (sType === 'object') {
2568            return parameters.values().join(', ');
2569        }
2570        return parameters;
2571    };
2572
2573    /**
2574     * Constructs the specified function using the specified javascript as the body of the function.
2575     *
2576     * @param {object} command The Response command object.
2577     * @param {string} command.func The name of the function to construct.
2578     * @param {string} command.data The script that will be the function body.
2579     * @param {object} command.context The javascript object to be referenced as 'this' in the script.
2580     *
2581     * @returns {true} The operation completed successfully.
2582     */
2583    self.setFunction = ({ func: sFuncName, data: funcBody, prop: aFuncParams, context = {} }) => {
2584        self.context = context;
2585        const jsCode = `(${getParameters(aFuncParams)}) => {
2586    ${funcBody}
2587}`;
2588
2589        dom.createFunction(jsCode, sFuncName);
2590        return true;
2591    };
2592
2593    self.wrapped = {}; // Original wrapped functions will be saved here.
2594
2595    /**
2596     * Construct a javascript function which will call the original function with the same name,
2597     * potentially executing code before and after the call to the original function.
2598     *
2599     * @param {object} command The Response command object.
2600     * @param {string} command.func The name of the function to be wrapped.
2601     * @param {string} command.prop List of parameters used when calling the function.
2602     * @param {array} command.data The portions of code to be called before, after 
2603     *   or even between calls to the original function.
2604     * @param {object} command.context The javascript object to be referenced as 'this' in the script.
2605     *
2606     * @returns {true} The operation completed successfully.
2607     */
2608    self.wrapFunction = ({ func: sFuncName, type: returnType, prop: aFuncParams,
2609        data: [funcCodeBefore, funcCodeAfter = '// No call after'], context = {} }) => {
2610        self.context = context;
2611        const func = dom.findFunction(sFuncName);
2612        if (!func) {
2613            return true;
2614        }
2615
2616        // Save the existing function
2617        const wrappedFuncName = sFuncName.toLowerCase().replaceAll('.', '_');
2618        if (!self.wrapped[wrappedFuncName]) {
2619            self.wrapped[wrappedFuncName] = func;
2620        }
2621
2622        const varDefine = returnType ? `let ${returnType} = null;` : '// No return value';
2623        const varAssign = returnType ? `${returnType} = ` : '';
2624        const varReturn = returnType ? `return ${returnType};` : '// No return value';
2625
2626        const jsCode = `(${getParameters(aFuncParams)}) => {
2627    ${varDefine}
2628    ${funcCodeBefore}
2629
2630    const wrappedFuncName = "${sFuncName}".toLowerCase().replaceAll('.', '_');
2631    // Call the wrapped function (saved in jaxon.cmd.script.wrapped) with the same parameters.
2632    ${varAssign}jaxon.cmd.script.wrapped[wrappedFuncName](${aFuncParams});
2633    ${funcCodeAfter}
2634    ${varReturn}
2635}`;
2636
2637        dom.createFunction(jsCode) && self.context.delegateCall();
2638        return true;
2639    };
2640
2641    /**
2642     * Redirects the browser to the specified URL.
2643     *
2644     * @param {object} command The Response command object.
2645     * @param {string} command.data The new URL to redirect to
2646     * @param {integer} command.delay The time to wait before the redirect.
2647     *
2648     * @returns {true} The operation completed successfully.
2649     */
2650    self.redirect = ({ data: sUrl, delay: nDelay }) => {
2651        if (nDelay <= 0) {
2652            window.location = sUrl;
2653            return true;
2654        }
2655        window.setTimeout(() => window.location = sUrl, nDelay * 1000);
2656        return true;
2657    };
2658})(jaxon.cmd.script, jaxon.ajax.handler, jaxon.utils.dom, jaxon.utils.string, jaxon.utils.queue);
2659
2660
2661/*
2662    File: jaxon.js
2663
2664    This file contains the definition of the main jaxon javascript core.
2665
2666    This is the client side code which runs on the web browser or similar web enabled application.
2667    Include this in the HEAD of each page for which you wish to use jaxon.
2668*/
2669
2670/**
2671 * Initiates a request to the server.
2672 */
2673jaxon.request = jaxon.ajax.request.execute;
2674
2675/**
2676 * Registers a new command handler.
2677 * Shortcut to <jaxon.ajax.handler.register>
2678 */
2679jaxon.register = jaxon.ajax.handler.register;
2680
2681/**
2682 * Shortcut to <jaxon.utils.dom.$>.
2683 */
2684jaxon.$ = jaxon.utils.dom.$;
2685
2686/**
2687 * Shortcut to <jaxon.utils.form.getValues>.
2688 */
2689jaxon.getFormValues = jaxon.utils.form.getValues;
2690
2691/**
2692 * Prints various types of messages on the user screen.
2693 */
2694jaxon.msg = jaxon.ajax.message;
2695
2696/**
2697 * Shortcut to <jaxon.cmd.script>.
2698 */
2699jaxon.js = jaxon.cmd.script;
2700
2701/**
2702 * Indicates if jaxon module is loaded.
2703 */
2704jaxon.isLoaded = true;
2705
2706/**
2707 * Register the command handlers provided by the library, and initialize the message object.
2708 */
2709(function(register, cmd, ajax) {
2710    register('rcmplt', ({ request }) => {
2711        ajax.request.complete(request);
2712        return true;
2713    }, 'Response complete');
2714
2715    register('ino', cmd.head.includeScriptOnce, 'includeScriptOnce');
2716    register('in', cmd.head.includeScript, 'includeScript');
2717    register('rjs', cmd.head.removeScript, 'removeScript');
2718    register('css', cmd.head.includeCSS, 'includeCSS');
2719    register('rcss', cmd.head.removeCSS, 'removeCSS');
2720    register('wcss', cmd.head.waitForCSS, 'waitForCSS');
2721
2722    register('as', cmd.body.assign, 'assign/clear');
2723    register('ap', cmd.body.append, 'append');
2724    register('pp', cmd.body.prepend, 'prepend');
2725    register('rp', cmd.body.replace, 'replace');
2726    register('rm', cmd.body.remove, 'remove');
2727    register('ce', cmd.body.create, 'create');
2728    register('ie', cmd.body.insert, 'insert');
2729    register('ia', cmd.body.insertAfter, 'insertAfter');
2730    register('c:as', cmd.body.contextAssign, 'context assign');
2731    register('c:ap', cmd.body.contextAppend, 'context append');
2732    register('c:pp', cmd.body.contextPrepend, 'context prepend');
2733
2734    register('s', ajax.handler.sleep, 'sleep');
2735    register('wf', cmd.script.waitFor, 'waitFor');
2736    register('js', cmd.script.execute, 'execute Javascript');
2737    register('jc', cmd.script.call, 'call js function');
2738    register('sf', cmd.script.setFunction, 'setFunction');
2739    register('wpf', cmd.script.wrapFunction, 'wrapFunction');
2740    register('al', cmd.script.alert, 'alert');
2741    register('cc', cmd.script.confirm, 'confirm');
2742    register('rd', cmd.script.redirect, 'redirect');
2743
2744    register('ci', cmd.form.createInput, 'createInput');
2745    register('ii', cmd.form.insertInput, 'insertInput');
2746    register('iia', cmd.form.insertInputAfter, 'insertInputAfter');
2747
2748    register('ev', cmd.event.setEvent, 'setEvent');
2749    register('ah', cmd.event.addHandler, 'addHandler');
2750    register('rh', cmd.event.removeHandler, 'removeHandler');
2751
2752    register('dbg', ({ data: message }) => {
2753        console.log(message);
2754        return true;
2755    }, 'Debug message');
2756
2757    /**
2758     * Class: jaxon.ajax.message
2759     */
2760    ajax.message = {
2761        /**
2762         * Print a success message on the screen.
2763         *
2764         * @param {string} content The message content.
2765         * @param {string} title The message title.
2766         *
2767         * @returns {void}
2768         */
2769        success: (content, title) => alert(content),
2770
2771        /**
2772         * Print an info message on the screen.
2773         *
2774         * @param {string} content The message content.
2775         * @param {string} title The message title.
2776         *
2777         * @returns {void}
2778         */
2779        info: (content, title) => alert(content),
2780
2781        /**
2782         * Print a warning message on the screen.
2783         *
2784         * @param {string} content The message content.
2785         * @param {string} title The message title.
2786         *
2787         * @returns {void}
2788         */
2789        warning: (content, title) => alert(content),
2790
2791        /**
2792         * Print an error message on the screen.
2793         *
2794         * @param {string} content The message content.
2795         * @param {string} title The message title.
2796         *
2797         * @returns {void}
2798         */
2799        error: (content, title) => alert(content),
2800
2801        /**
2802         * Ask a confirm question to the user.
2803         *
2804         * @param {string} question The confirm question.
2805         * @param {string} title The confirm title.
2806         * @param {callable} yesCallback The function to call if the user answers yesn.
2807         * @param {callable} noCallback The function to call if the user answers no.
2808         *
2809         * @returns {void}
2810         */
2811        confirm: (question, title, yesCallback, noCallback) => {
2812            if(confirm(question)) {
2813                yesCallback();
2814                return;
2815            }
2816            noCallback && noCallback();
2817        },
2818    };
2819})(jaxon.register, jaxon.cmd, jaxon.ajax);
2820
2821
2822/**
2823 * Class: jaxon.command
2824 *
2825 * This class is defined for compatibility with previous versions, since its functions are used in other packages.
2826 */
2827jaxon.command = {
2828    handler: {},
2829
2830    handler: {
2831        register: jaxon.ajax.handler.register
2832    },
2833
2834    /**
2835     * Creates a new command (object) that will be populated with command parameters
2836     * and eventually passed to the command handler.
2837     */
2838    create: (sequence, request, context) => ({
2839        cmd: '*',
2840        fullName: '* unknown command name *',
2841        sequence: sequence,
2842        request: request,
2843        context: context
2844    }),
2845};
2846
2847/**
2848 * Class: jxn
2849 *
2850 * Contains shortcut's to frequently used functions.
2851 */
2852const jxn = {
2853    /**
2854     * Shortcut to <jaxon.utils.dom.$>.
2855     */
2856    $: jaxon.utils.dom.$,
2857
2858    /**
2859     * Shortcut to <jaxon.utils.form.getValues>.
2860     */
2861    getFormValues: jaxon.utils.form.getValues,
2862
2863    /**
2864     * Shortcut to <jaxon.request>.
2865     */
2866    request: jaxon.request,
2867
2868    /**
2869     * Shortcut to <jaxon.ajax.handler.call>.
2870     */
2871    call: jaxon.ajax.handler.call,
2872};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.