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.