1 namespace ort { 2 /** 3 * Facilities to manipulate 64-bit numbers encoded as strings. 4 * The JavaScript implementation of openradtool uses strings for numbers 5 * because of the 64-bit problem: internally, openradtool uses 64-bit 6 * integer numbers, but JavaScript has only 53 bits of precision for 7 * integers, and 32 for bit-wise operations. 8 * This class is a modified version of long.js fixed to base 10 and 9 * converted to TypeScript. 10 * 11 * Original source: https://github.com/dcodeIO/long.js 12 * 13 * @license 14 * Copyright 2009 The Closure Library Authors. 15 * Copyright 2020 Daniel Wirtz / The long.js Contributors. 16 * Copyright 2021 Kristaps Dzonsons 17 * SPDX-License-Identifier: Apache-2.0 18 */ 19 export class Long { 20 private readonly __isLong__: boolean = true; 21 private readonly low: number; 22 private readonly high: number; 23 private readonly unsigned: boolean; 24 25 private constructor(low: number, 26 high?: number, unsigned?: boolean) 27 { 28 this.low = low | 0; 29 this.high = 30 (typeof high === 'undefined') ? 31 0 : (high | 0); 32 this.unsigned = 33 (typeof unsigned === 'undefined') ? 34 false: unsigned; 35 } 36 37 static readonly ZERO: Long = new Long(0, 0); 38 static readonly ONE: Long = new Long(1, 0); 39 static readonly UZERO: Long = new Long(0, 0, true); 40 static readonly TEN_TO_EIGHT: Long = new Long(100000000, 0); 41 42 static readonly MIN_VALUE: Long = 43 new Long(0, 0x80000000|0, false); 44 static readonly MAX_UNSIGNED_VALUE: Long = 45 new Long(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); 46 static readonly MAX_VALUE: Long = 47 new Long(0xFFFFFFFF|0, 0x7FFFFFFF|0, false); 48 49 /* Constant numbers used in the code. */ 50 51 private static readonly TWO_PWR_16_DBL: number = 52 (1 << 16); 53 private static readonly TWO_PWR_32_DBL: number = 54 Long.TWO_PWR_16_DBL * 55 Long.TWO_PWR_16_DBL; 56 private static readonly TWO_PWR_64_DBL: number = 57 Long.TWO_PWR_32_DBL * 58 Long.TWO_PWR_32_DBL; 59 private static readonly TWO_PWR_63_DBL: number = 60 Long.TWO_PWR_64_DBL / 2; 61 62 /** 63 * @return Whether this is a ortns.Long object. 64 */ 65 static isLong(obj: any): boolean 66 { 67 /* 68 * Complex to silence a typescript warning about 69 * __isLong__ being an unused local otherwise. 70 */ 71 72 return typeof obj === 'object' && 73 obj !== null && 74 '__isLong__' in obj && 75 (<Long>obj).__isLong__ === true; 76 } 77 78 /** 79 * Convert to a JavaScript number, losing precision 80 * (from 64 to 53 bits) and thus possibly truncating 81 * with larger or smaller numbers. 82 * @return The Long converted to a number. 83 */ 84 toNumber(): number 85 { 86 if (this.unsigned) 87 return ((this.high >>> 0) * 88 Long.TWO_PWR_32_DBL) + 89 (this.low >>> 0); 90 return this.high * Long.TWO_PWR_32_DBL + 91 (this.low >>> 0); 92 }; 93 94 /** 95 * Similar to toNumber() except further reducing the 96 * number to 32 bits of precision. 97 * @return The Long converted to a number. 98 */ 99 toInt(): number 100 { 101 return this.unsigned ? this.low >>> 0 : this.low; 102 } 103 104 /** 105 * Test whether the value is strictly <0. For unsigned 106 * values, this is always false. 107 * @return Whether the value is strictly <0. 108 */ 109 isNegative(): boolean 110 { 111 return !this.unsigned && this.high < 0; 112 } 113 114 /** 115 * Test whether the value is zero, regardless of sign. 116 * @return Whether the value is zero. 117 */ 118 isZero(): boolean 119 { 120 return this.high === 0 && this.low === 0; 121 } 122 123 /** 124 * Test whether the value is an odd number, regardless 125 * of sign. 126 * @return Whether the value is an odd number. 127 */ 128 isOdd(): boolean 129 { 130 return (this.low & 1) === 1; 131 } 132 133 /** 134 * Test whether the value equals the given value. This 135 * is according to the numerical value, not the bit 136 * pattern, so a negative signed value and a positive 137 * signed value will be the same even if having the same 138 * bit pattern. The exception here is zero, which is 139 * the same whether signed or not. 140 * @return Whether the values are equal. 141 */ 142 eq(other: Long|number): boolean 143 { 144 const v: Long = !Long.isLong(other) ? 145 Long.fromNumber(<number>other) : <Long>other; 146 147 if (this.unsigned !== v.unsigned && 148 (this.high >>> 31) === 1 && 149 (v.high >>> 31) === 1) 150 return false; 151 return this.high === v.high && 152 this.low === v.low; 153 } 154 155 /** 156 * Negate a number (make it negative if positive, 157 * positive if negative). If zero, this converts to 158 * zero of the other sign. 159 * @return The negated value. 160 */ 161 neg(): Long 162 { 163 if (!this.unsigned && this.eq(Long.MIN_VALUE)) 164 return Long.MIN_VALUE; 165 return this.not().add(Long.ONE); 166 } 167 168 /** 169 * The bit-wise NOT of the value. This retains the sign 170 * of the original. 171 * @return The bit-wise NOT of the value. 172 */ 173 not(): Long 174 { 175 return new Long(~this.low, 176 ~this.high, this.unsigned); 177 } 178 179 /** 180 * The bit-wise AND of the value with the argument. 181 * This retains the sign of the original. 182 * @return The bit-wise AND of the value and argument. 183 */ 184 and(other: Long|number): Long 185 { 186 const v: Long = !Long.isLong(other) ? 187 Long.fromNumber(<number>other) : <Long>other; 188 return new Long(this.low & v.low, 189 this.high & v.high, 190 this.unsigned); 191 } 192 193 /** 194 * The bit-wise OR of the value with the argument. 195 * Sign is inherited from the source value. 196 * @return The bit-wise OR of the value and argument. 197 */ 198 or(other: Long|number): Long 199 { 200 const v: Long = !Long.isLong(other) ? 201 Long.fromNumber(<number>other) : <Long>other; 202 return new Long(this.low | v.low, 203 this.high | v.high, 204 this.unsigned); 205 } 206 207 /** 208 * Compare the value with the argument and return their 209 * sign comparison: 1 if the value is greater, -1 if 210 * less, 0 if the same. Zero is always the same as 211 * zero, regardless the sign. 212 * @return 0 if they are the same, 1 if the value is 213 * greater, and -1 if the value is less. 214 */ 215 compare(other: Long|number): number 216 { 217 const v: Long = !Long.isLong(other) ? 218 Long.fromNumber(<number>other) : <Long>other; 219 220 if (this.eq(v)) 221 return 0; 222 223 const thisNeg: boolean = this.isNegative(); 224 const otherNeg: boolean = v.isNegative(); 225 226 if (thisNeg && !otherNeg) 227 return -1; 228 if (!thisNeg && otherNeg) 229 return 1; 230 231 // At this point the sign bits are the same 232 233 if (!this.unsigned) 234 return this.sub(v).isNegative() ? -1 : 1; 235 236 // Both are positive if at least one is unsigned 237 238 return (v.high >>> 0) > (this.high >>> 0) || 239 (v.high === this.high && 240 (v.low >>> 0) > (this.low >>> 0)) ? -1 : 1; 241 }; 242 243 /** 244 * Left-shift the value by the given number of bits 245 * (modulo 64). Sign is inherited from the source 246 * value. The module means that shifting by 64 bits is 247 * the same as zero, 65 the same as one, etc. Shifting 248 * by a negative number has undefined behaviour. 249 * @return The left-shifted value. 250 */ 251 shl(numBits: Long|number): Long 252 { 253 let v: number = Long.isLong(numBits) ? 254 (<Long>numBits).toInt() : <number>numBits; 255 256 if ((v &= 63) === 0) 257 return this; 258 else if (v < 32) 259 return new Long 260 (this.low << v, 261 (this.high << v) | 262 (this.low >>> (32 - v)), 263 this.unsigned); 264 else 265 return new Long 266 (0, this.low << (v - 32), 267 this.unsigned); 268 } 269 270 /** 271 * Arithmetic multiplication of the value to the argument. 272 * Wrapping behaviour, including the identity at MAX_VALUE or 273 * MIN_VALUE, is undefined. 274 * @return The multiplied value. 275 */ 276 mul(tomul: Long|number): Long 277 { 278 const v: Long = !Long.isLong(tomul) ? 279 Long.fromNumber(<number>tomul) : <Long>tomul; 280 281 if (this.isZero() || v.isZero()) 282 return Long.ZERO; 283 if (this.eq(Long.MIN_VALUE)) 284 return v.isOdd() ? Long.MIN_VALUE : Long.ZERO; 285 if (v.eq(Long.MIN_VALUE)) 286 return this.isOdd() ? Long.MIN_VALUE : Long.ZERO; 287 288 if (this.isNegative()) { 289 if (v.isNegative()) 290 return this.neg().mul(v.neg()); 291 else 292 return this.neg().mul(v).neg(); 293 } else if (v.isNegative()) 294 return this.mul(v.neg()).neg(); 295 296 // Divide each long into 4 chunks of 16 bits, 297 // and then add up 4x4 products. We can skip 298 // products that would overflow. 299 300 const a48: number = this.high >>> 16; 301 const a32: number = this.high & 0xFFFF; 302 const a16: number = this.low >>> 16; 303 const a00: number = this.low & 0xFFFF; 304 305 const b48: number = v.high >>> 16; 306 const b32: number = v.high & 0xFFFF; 307 const b16: number = v.low >>> 16; 308 const b00: number = v.low & 0xFFFF; 309 310 let c48: number = 0; 311 let c32: number = 0; 312 let c16: number = 0; 313 let c00: number = 0; 314 315 c00 += a00 * b00; 316 c16 += c00 >>> 16; 317 c00 &= 0xFFFF; 318 c16 += a16 * b00; 319 c32 += c16 >>> 16; 320 c16 &= 0xFFFF; 321 c16 += a00 * b16; 322 c32 += c16 >>> 16; 323 c16 &= 0xFFFF; 324 c32 += a32 * b00; 325 c48 += c32 >>> 16; 326 c32 &= 0xFFFF; 327 c32 += a16 * b16; 328 c48 += c32 >>> 16; 329 c32 &= 0xFFFF; 330 c32 += a00 * b32; 331 c48 += c32 >>> 16; 332 c32 &= 0xFFFF; 333 c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; 334 c48 &= 0xFFFF; 335 336 return new Long 337 ((c16 << 16) | c00, (c48 << 16) | c32, 338 this.unsigned); 339 } 340 341 /** 342 * Alias for adding the negative of the argument to the 343 * value. 344 * @return The difference of the values. 345 */ 346 sub(tosub: Long|number): Long 347 { 348 const v: Long = !Long.isLong(tosub) ? 349 Long.fromNumber(<number>tosub) : <Long>tosub; 350 return this.add(v.neg()); 351 }; 352 353 /** 354 * Add the argument to the value. 355 * @return The addition of the values. 356 */ 357 add(toadd: Long|number): Long 358 { 359 const v: Long = !Long.isLong(toadd) ? 360 Long.fromNumber(<number>toadd) : 361 <Long>toadd; 362 363 // Divide each number into 4 chunks of 16 bits, 364 // and then sum the chunks. 365 366 const a48: number = this.high >>> 16; 367 const a32: number = this.high & 0xFFFF; 368 const a16: number = this.low >>> 16; 369 const a00: number = this.low & 0xFFFF; 370 371 const b48: number = v.high >>> 16; 372 const b32: number = v.high & 0xFFFF; 373 const b16: number = v.low >>> 16; 374 const b00: number = v.low & 0xFFFF; 375 376 let c48: number = 0; 377 let c32: number = 0; 378 let c16: number = 0; 379 let c00: number = 0; 380 381 c00 += a00 + b00; 382 c16 += c00 >>> 16; 383 c00 &= 0xFFFF; 384 c16 += a16 + b16; 385 c32 += c16 >>> 16; 386 c16 &= 0xFFFF; 387 c32 += a32 + b32; 388 c48 += c32 >>> 16; 389 c32 &= 0xFFFF; 390 c48 += a48 + b48; 391 c48 &= 0xFFFF; 392 393 return new Long 394 ((c16 << 16) | c00, (c48 << 16) | c32, 395 this.unsigned); 396 } 397 398 /** 399 * Attempt to convert from a Long, number, or string. Failure 400 * to convert, whether by syntax, not being defined, or null, 401 * will return null. 402 * @return The value or null on failure. 403 */ 404 static fromValue(val: any, unsigned?: boolean): Long|null 405 { 406 if (typeof val === 'undefined') 407 return null; 408 if (typeof val === 'number') 409 return Long.fromNumber 410 (<number>val, unsigned); 411 if (typeof val === 'string') 412 return Long.fromString 413 (<string>val, unsigned); 414 if (Long.isLong(val)) 415 return <Long>val; 416 return null; 417 } 418 419 /** 420 * Like fromValue(), except passing through on undefined values. 421 * @return The value, null on failure, or undefined if not 422 * defined. 423 */ 424 static fromValueUndef(val: any, unsigned?: boolean): Long|null|undefined 425 { 426 if (typeof val === 'undefined') 427 return undefined; 428 return Long.fromValue(val, unsigned); 429 } 430 431 /** 432 * Convert from a number to the Long representation of the 433 * value. If NaN, returns zero or unsigned zero as applicable. 434 * If negative and wanting unsigned, bound at zero. Otherwise 435 * bound to a 64-bit integer size. 436 * @return The value. 437 */ 438 static fromNumber(value: number, unsigned?: boolean): Long 439 { 440 const usgn: boolean = 441 (typeof unsigned === 'undefined') ? 442 false : unsigned; 443 444 if (isNaN(value)) 445 return usgn ? Long.UZERO : Long.ZERO; 446 447 if (usgn) { 448 if (value < 0) 449 return Long.UZERO; 450 if (value >= Long.TWO_PWR_64_DBL) 451 return Long.MAX_UNSIGNED_VALUE; 452 } else { 453 if (value <= -Long.TWO_PWR_63_DBL) 454 return Long.MIN_VALUE; 455 if (value + 1 >= Long.TWO_PWR_63_DBL) 456 return Long.MAX_VALUE; 457 } 458 459 if (value < 0) 460 return Long.fromNumber 461 (-value, unsigned).neg(); 462 463 return new Long 464 ((value % Long.TWO_PWR_32_DBL) | 0, 465 (value / Long.TWO_PWR_32_DBL) | 0, 466 unsigned); 467 } 468 469 /** 470 * Like fromString(), except returning failed conversions as a 471 * zero value. 472 * @return The value. 473 */ 474 static fromStringZero(str: string, unsigned?: boolean): Long 475 { 476 const val: Long|null = Long.fromString(str, unsigned); 477 return val === null ? Long.ZERO : val; 478 } 479 480 /** 481 * Convert from a string representation of a number to a Long. 482 * The string may have an optional leading "-" followed by 483 * digits. This does not accept NaN, Infinity, or other 484 * symbols. 485 * @return The value or null on failure. 486 */ 487 static fromString(str: string, unsigned?: boolean): Long | null 488 { 489 const usgn: boolean = 490 (typeof unsigned === 'undefined') ? 491 false : unsigned; 492 const hyph: number = str.indexOf('-'); 493 const radixToPower: Long = Long.TEN_TO_EIGHT; 494 let result: Long = Long.ZERO; 495 496 if (str.length === 0 || hyph > 0 || 497 str === 'NaN' || str === 'Infinity' || 498 str === '+Infinity' || str === '-Infinity') 499 return null; 500 501 if (hyph === 0) { 502 const nresult: Long|null = 503 Long.fromString 504 (str.substring(1), usgn); 505 if (nresult === null) 506 return null; 507 return nresult.neg(); 508 } 509 510 if (!str.match(/^[0-9]+$/)) 511 return null; 512 513 // Do several (8) digits each time through the 514 // loop, so as to minimize the calls to the 515 // very expensive emulated div. 516 517 for (let i: number = 0; i < str.length; i += 8) { 518 const size: number = 519 Math.min(8, str.length - i); 520 const value: number = 521 parseInt(str.substring(i, i + size), 10); 522 if (isNaN(value)) 523 return null; 524 if (size < 8) { 525 const power: Long = 526 Long.fromNumber 527 (Math.pow(10, size)); 528 result = result.mul(power).add 529 (Long.fromNumber(value)); 530 } else { 531 result = result.mul 532 (radixToPower); 533 result = result.add 534 (Long.fromNumber(value)); 535 } 536 } 537 return new Long(result.low, result.high, usgn); 538 } 539 } 540 541 /** 542 * Labels ("jslabel" in ort(5)) may have multiple languages. 543 * This maps a language name to a translated string. 544 */ 545 interface langmap 546 { 547 [lang: string]: string 548 }; 549 550 /** 551 * Resolve a set of translated strings into a single one 552 * depending upon the current language. 553 * @param vals All translations of a given word. 554 * @return The word in the current language (or the default) 555 * or an empty string on failure. 556 * @internal 557 */ 558 function _strlang(vals: langmap): string 559 { 560 const lang = document.documentElement.lang; 561 if (lang !== null && lang in vals) 562 return vals[lang]; 563 else if ('_default' in vals) 564 return vals['_default']; 565 else 566 return ''; 567 } 568 569 /** 570 * Language replacement conditional upon the label (**jslabel** 571 * in the configuration). Like {@link _replcl} with inclusion 572 * set to false. 573 * @param e The root of the DOM tree in which we query for 574 * elements to fill into. 575 * @param name The class name we search for within the root (not 576 * inclusive). 577 * @param vals All possible translations. 578 * @internal 579 */ 580 function _replcllang(e: HTMLElement, name:string, vals: langmap): void 581 { 582 _replcl(e, name, _strlang(vals), false); 583 } 584 585 /** 586 * Language replacement conditional upon the label (**jslabel** 587 * in the configuration). Like {@link _repl}. 588 * @param e The root of the DOM tree in which we query for 589 * elements to fill into. 590 * @param vals All possible translations. 591 * @internal 592 */ 593 function _repllang(e: HTMLElement, vals: langmap): void 594 { 595 _repl(e, _strlang(vals)); 596 } 597 598 /** 599 * Set the attribute of an element. 600 * @param e The element whose attribute to set. 601 * @param attr The attribute name. 602 * @param text The attribute value. 603 * @internal 604 */ 605 function _attr(e: HTMLElement, attr: string, text: string): void 606 { 607 e.setAttribute(attr, text); 608 } 609 610 /** 611 * Set attributes for all elements matching a class. 612 * @internal 613 */ 614 function _attrcl(e: HTMLElement, attr: string, name: string, text: string, 615 inc: boolean): void 616 { 617 for (const elem of _elemList(e, name, inc)) 618 _attr(elem, attr, text); 619 } 620 621 /** 622 * Get all elements beneath (possibly including) a root matching 623 * the given class. 624 * @internal 625 */ 626 function _elemList(e: HTMLElement|null, cls: string, inc: boolean): HTMLElement[] 627 { 628 if (e === null) 629 return []; 630 const a = Array.prototype.filter.call( 631 e.getElementsByClassName(cls), 632 elem => elem instanceof HTMLElement, 633 ) as HTMLElement[]; 634 if (inc && e.classList.contains(cls)) 635 a.push(e); 636 return a; 637 } 638 639 /** 640 * Replace all children of an element with text. 641 * @internal 642 */ 643 function _repl(e: HTMLElement, text: string): void 644 { 645 e.replaceChildren(document.createTextNode(text)); 646 } 647 648 /** 649 * Replace children of elements matching class with text. 650 * @internal 651 */ 652 function _replcl(e: HTMLElement, name: string, text: string, inc: boolean): 653 void 654 { 655 for (const elem of _elemList(e, name, inc)) 656 _repl(elem, text); 657 } 658 659 /** 660 * Add class to class list if it doesn't exist. 661 * @internal 662 */ 663 function _classadd(e: HTMLElement, name: string): HTMLElement 664 { 665 if (!e.classList.contains(name)) 666 e.classList.add(name); 667 return(e); 668 } 669 670 /** 671 * Add class if doesn't exist to all elements with class name. 672 * @internal 673 */ 674 function _classaddcl(e: HTMLElement, name: string, cls: string, 675 inc: boolean): void 676 { 677 for (const elem of _elemList(e, name, inc)) 678 _classadd(elem, cls); 679 } 680 681 /** 682 * "Hide" element by adding *hide* class. 683 * @internal 684 */ 685 function _hide(e: HTMLElement): HTMLElement 686 { 687 if (!e.classList.contains('hide')) 688 e.classList.add('hide'); 689 return e; 690 } 691 692 /** 693 * "Hide" all elements matching class by adding *hide* class. 694 * @internal 695 */ 696 function _hidecl(e: HTMLElement, name: string, inc: boolean): void 697 { 698 for (const elem of _elemList(e, name, inc)) 699 _hide(elem); 700 } 701 702 /** 703 * "Show" element by removing *hide* class. 704 * @internal 705 */ 706 function _show(e: HTMLElement): HTMLElement 707 { 708 if (e.classList.contains('hide')) 709 e.classList.remove('hide'); 710 return e; 711 } 712 713 /** 714 * "Show" all elements matching class by removing *hide* class. 715 * @internal 716 */ 717 function _showcl(e: HTMLElement, name: string, inc: boolean): void 718 { 719 for (const elem of _elemList(e, name, inc)) 720 _show(elem); 721 } 722 723 /** 724 * Check input elements (that is, set the attribute `checked` to 725 * the value `checked`) for all elements of class 726 * `fname-value-checked` whose value matches the given. The 727 * checked status is removed for each item scanned. 728 * A null value never matches. 729 * @param e Root of tree scanned for elements. 730 * @param fname Structure name, '-', field name. 731 * @param val The value to test for. 732 * @param inc Include root in scanning for elements. 733 * @internal 734 */ 735 function _fillValueChecked(e: HTMLElement, fname: string, 736 val: number|string|null, inc: boolean): void 737 { 738 for (const elem of 739 _elemList(e, fname + '-value-checked', inc)) { 740 const inp = elem as HTMLInputElement; 741 const attrval = inp.value; 742 inp.removeAttribute('checked'); 743 if (val === null || attrval === null) 744 continue; 745 if (val.toString() === attrval) 746 inp.setAttribute('checked', 'checked'); 747 } 748 } 749 750 /** 751 * Take all `<option>` elements under the root (non-inclusive) 752 * and sets or unsets the `selected` attribute depending upon 753 * whether it matches the object's value. 754 * A null value never matches. 755 * @param e Root of tree scanned for elements. 756 * @param val The value to test for. 757 * @internal 758 */ 759 function _fillValueSelect(e: HTMLElement, 760 val: number|string|null): void 761 { 762 const list = Array.from(e.getElementsByTagName('option')); 763 for (const elem of list) { 764 const opt = elem as HTMLOptionElement; 765 const attrval = opt.value; 766 opt.removeAttribute('selected'); 767 if (val === null || attrval === null) 768 continue; 769 if (val.toString() === attrval) 770 opt.setAttribute('selected', 'selected'); 771 } 772 } 773 774 /** 775 * Fill in ISO-8601 dates. 776 * Does nothing for null or unexported date. 777 * @param e Root of tree scanned for elements. 778 * @param fname Structure name, '-', field name. 779 * @param val Epoch date itself. 780 * @param inc Include root in scanning for elements. 781 * @internal 782 */ 783 function _fillDateValue(e: HTMLElement, fname: string, 784 val: string|number|null|undefined, inc: boolean): void 785 { 786 const v = Long.fromValue(val); 787 if (v === null) 788 return; 789 790 const d = new Date(); 791 d.setTime(v.toNumber() * 1000); 792 793 /* Make sure to zero-pad the digits. */ 794 795 const year = d.getFullYear(); 796 const mo = d.getMonth() + 1; 797 const day = d.getDate(); 798 const full = year + '-' + 799 (mo < 10 ? '0' : '') + mo + '-' + 800 (day < 10 ? '0' : '') + day; 801 _attrcl(e, 'value', fname + '-date-value', full, inc); 802 _replcl(e, fname + '-date-text', full, inc); 803 } 804 805 /** 806 * Check input elements (that is, set the attribute `checked` to 807 * the value `checked`) for elements with class 808 * `fname-value-checked` whose non-null, numeric value as a bit index 809 * is set in the bit-field given as input. 810 * A null value never matches. 811 * @param e Root of tree scanned for elements. 812 * @param fname Structure name, '-', field name. 813 * @param val Bit-field to test for. 814 * @param inc Include root in scanning for elements. 815 * @internal 816 */ 817 function _fillBitsChecked(e: HTMLElement, fname: string, 818 val: string|number|null|undefined, inc: boolean): void 819 { 820 const lval = Long.fromValueUndef(val); 821 if (typeof lval === 'undefined') 822 return; 823 for (const elem of _elemList(e, fname + '-bits-checked', inc)) { 824 const inp = elem as HTMLInputElement; 825 const attrval: string|null = inp.value; 826 inp.removeAttribute('checked'); 827 if (lval === null || attrval === null) 828 continue; 829 830 /* 831 * This would be better served with 832 * Number.isInteger(), but we don't want to 833 * assume ES6. 834 */ 835 836 const v = Number(attrval); 837 if (isNaN(v)) 838 continue; 839 if (!(isFinite(v) && Math.floor(v) === v)) 840 continue; 841 if (v < 0 || v > 64) 842 continue; 843 844 if ((v === 0 && lval.isZero()) || 845 !Long.ONE.shl(v - 1).and(lval).isZero()) { 846 inp.setAttribute('checked', 'checked'); 847 } 848 } 849 } 850 851 /** 852 * Fill a structure field. This first does the has/no class 853 * setting for null values, then optionally returns if null 854 * (running the custom fields first), otherwise the generic 855 * text/value/etc fields, then finally the custom fields. 856 * @param e Root of the DOM tree filled into. 857 * @param strct Name of the structure filling in. 858 * @param name The name of the field. 859 * @param custom Custom callback functions. 860 * @param obj The data itself. 861 * @param inc Whether to include the root element in looking 862 * for elements to fill. Nested structures are always filled 863 * non-inclusively. 864 * @param cannull Whether the data may be null. 865 * @param sub If the data object is a nested structure 866 * interface, the allocated class of that interface. 867 * @internal 868 */ 869 function _fillField(e: HTMLElement, strct: string, name: string, 870 custom: DataCallbacks|null, obj: any, inc: boolean, 871 cannull: boolean, sub: any): void 872 { 873 const fname = strct + '-' + name; 874 875 /* Don't do anything if we're not defined. */ 876 877 if (typeof(obj) === 'undefined') 878 return; 879 880 /* First handle our has/no null situation. */ 881 882 if (cannull) { 883 if (obj === null) { 884 _hidecl(e, strct + '-has-' + name, inc); 885 _showcl(e, strct + '-no-' + name, inc); 886 } else { 887 _showcl(e, strct + '-has-' + name, inc); 888 _hidecl(e, strct + '-no-' + name, inc); 889 } 890 } 891 892 /* Don't process null values that can be null. */ 893 894 if (cannull && obj === null) { 895 if (custom !== null && fname in custom) { 896 if (Array.isArray(custom[fname])) { 897 for (const fn of custom[fname]) 898 fn(e, fname, null); 899 } else 900 custom[fname](e, fname, null); 901 } 902 return; 903 } 904 905 /* 906 * Non-null non-structs. 907 * Note that "sub" is never undefined because that would 908 * otherwise be caught in the "typeof obj" guard above. 909 */ 910 911 if (sub !== null) { 912 for (const elem of 913 _elemList(e, fname + '-obj', inc)) 914 sub.fillInner(elem as HTMLElement, custom); 915 } else { 916 for (const elem of 917 _elemList(e, fname + '-enum-select', inc)) 918 _fillValueSelect(elem, obj); 919 _replcl(e, fname + '-text', obj, inc); 920 _attrcl(e, 'value', fname + '-value', obj, inc); 921 _fillValueChecked(e, fname, obj, inc); 922 } 923 924 /* Lastly, handle the custom callback. */ 925 926 if (custom !== null && fname in custom) { 927 if (Array.isArray(custom[fname])) { 928 for (const fn of custom[fname]) 929 fn(e, fname, obj); 930 } else 931 custom[fname](e, fname, obj); 932 } 933 } 934 935 export type DCbstring = (e: HTMLElement, 936 name: string, val: string) => void; 937 export type DCbstringNull = (e: HTMLElement, 938 name: string, val: string|null) => void; 939 export type DCbinteger = (e: HTMLElement, 940 name: string, val: string|number) => void; 941 export type DCbintegerNull = (e: HTMLElement, 942 name: string, val: string|number|null) => void; 943 export type DCbnumber = (e: HTMLElement, 944 name: string, val: number) => void; 945 export type DCbnumberNull = (e: HTMLElement, 946 name: string, val: number|null) => void; 947 export type DCbStructcompany = (e: HTMLElement, 948 name: string, val: companyData|null) => void; 949 export type DCbStructuser = (e: HTMLElement, 950 name: string, val: userData|null) => void; 951 export type DCbStructsession = (e: HTMLElement, 952 name: string, val: sessionData|null) => void; 953 954 /** 955 * All possible custom callbacks for this ort configuration. 956 */ 957 export interface DataCallbacks 958 { 959 [key: string]: any; 960 'company'?: DCbStructcompany|DCbStructcompany[]; 961 'company-name'?: DCbstring|DCbstring[]; 962 'company-id'?: DCbinteger|DCbinteger[]; 963 'company-somenum'?: DCbintegerNull|DCbintegerNull[]; 964 'user'?: DCbStructuser|DCbStructuser[]; 965 'user-company'?: DCbStructcompany|DCbStructcompany[]; 966 'user-cid'?: DCbinteger|DCbinteger[]; 967 'user-sex'?: DCbinteger|DCbinteger[]; 968 'user-email'?: DCbstring|DCbstring[]; 969 'user-image'?: DCbstringNull|DCbstringNull[]; 970 'user-name'?: DCbstring|DCbstring[]; 971 'user-uid'?: DCbinteger|DCbinteger[]; 972 'session'?: DCbStructsession|DCbStructsession[]; 973 'session-user'?: DCbStructuser|DCbStructuser[]; 974 'session-userid'?: DCbinteger|DCbinteger[]; 975 'session-token'?: DCbinteger|DCbinteger[]; 976 'session-mtime'?: DCbinteger|DCbinteger[]; 977 'session-id'?: DCbinteger|DCbinteger[]; 978 } 979 980 /** 981 * Controlling organisation. 982 */ 983 export interface companyData 984 { 985 /** 986 * Name of the organisation. 987 */ 988 name: string; 989 id: string|number; 990 /** 991 * Simply a check for null values. 992 */ 993 somenum: string|number; 994 } 995 996 /** 997 * A regular user. 998 */ 999 export interface userData 1000 { 1001 /** 1002 * This struct will be filled in from an inner join 1003 * on the "cid" variable. 1004 */ 1005 company: companyData; 1006 /** 1007 * A foreign key reference. 1008 */ 1009 cid: string|number; 1010 /** 1011 * User's birth sex. 1012 */ 1013 sex: string|number; 1014 /** 1015 * Unique e-mail address. 1016 */ 1017 email: string; 1018 /** 1019 * A PNG image or something. 1020 */ 1021 image: string; 1022 /** 1023 * User's full name. 1024 */ 1025 name: string; 1026 uid: string|number; 1027 } 1028 1029 /** 1030 * Authenticated session. 1031 */ 1032 export interface sessionData 1033 { 1034 user: userData; 1035 /** 1036 * Associated user. 1037 */ 1038 userid: string|number; 1039 /** 1040 * Random cookie. 1041 */ 1042 token: string|number; 1043 mtime: string|number; 1044 id: string|number; 1045 } 1046 1047 /** 1048 * Writes {@link companyData} into a DOM tree. 1049 */ 1050 export class company { 1051 readonly obj: companyData|companyData[]; 1052 /** 1053 * @param obj The object(s) to write. 1054 */ 1055 constructor(o: companyData|companyData[]) 1056 { 1057 this.obj = o; 1058 } 1059 1060 /** 1061 * Writes {@link companyData} into the given element. If 1062 * constructed with an array, the first element is used. 1063 * Elements within (and including) the element having the 1064 * following classes are manipulated as follows: 1065 * 1066 * - `company-name-enum-select`: sets or unsets the `selected` 1067 * attribute for non-inclusive descendent `<option>` elements 1068 * depending on whether the value matches 1069 * - `company-name-value-checked`: sets or unsets the `checked` 1070 * attribute depending on whether the value matches 1071 * - `company-name-text`: replace contents with **name** data 1072 * - `company-name-value`: replace `value` attribute with 1073 * **name** data 1074 * - `company-id-enum-select`: sets or unsets the `selected` 1075 * attribute for non-inclusive descendent `<option>` elements 1076 * depending on whether the value matches 1077 * - `company-id-value-checked`: sets or unsets the `checked` 1078 * attribute depending on whether the value matches 1079 * - `company-id-text`: replace contents with **id** data 1080 * - `company-id-value`: replace `value` attribute with **id** 1081 * data 1082 * - `company-has-somenum`: *hide* class removed if value is not 1083 * null, otherwise it is added 1084 * - `company-no-somenum`: *hide* class added if value is not 1085 * null, otherwise it is removed 1086 * - `company-somenum-enum-select`: sets or unsets the `selected` 1087 * attribute for non-inclusive descendent `<option>` elements 1088 * depending on whether the value matches (if non-null) 1089 * - `company-somenum-value-checked`: sets or unsets the 1090 * `checked` attribute depending on whether the value matches (if 1091 * non-null) 1092 * - `company-somenum-text`: replace contents with **somenum** 1093 * data (if non-null) 1094 * - `company-somenum-value`: replace `value` attribute with 1095 * **somenum** data (if non-null) 1096 * 1097 * @param e The DOM element. 1098 * @param custom The dictionary of functions keyed by structure 1099 * and field name (e.g., *foo** structure, **bar** field would be 1100 * `foo-bar`). The value is a function for custom handling that 1101 * accepts the 'e' value, the name of the structure-field, and 1102 * the value of the structure and field. You may also specify an 1103 * array of functions instead of a singleton. These callbacks are 1104 * invoked *after* the generic classes are filled. 1105 */ 1106 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1107 { 1108 if (e !== null) 1109 this._fill(e, this.obj, true, custom); 1110 } 1111 1112 /** 1113 * Like {@link fill} but not including the passed-in element. 1114 * @param e The DOM element. 1115 * @param custom Custom handler dictionary (see {@link fill} for 1116 * details). 1117 */ 1118 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1119 void 1120 { 1121 if (e !== null) 1122 this._fill(e, this.obj, false, custom); 1123 } 1124 1125 /** 1126 * Like {@link fill} but instead of accepting a single element to 1127 * fill, filling into all elements (inclusive) matching the given 1128 * class name beneath (inclusive) the element. 1129 * @param e The DOM element. 1130 * @param name Name of the class to fill. 1131 * @param custom Custom handler dictionary (see {@link fill} for 1132 * details). 1133 */ 1134 fillByClass(e: HTMLElement|null, name: string, 1135 custom?: DataCallbacks|null): void 1136 { 1137 if (e !== null) 1138 this._fillByClass(e, name, true, custom); 1139 } 1140 1141 /** 1142 * Like {@link fillByClass} but not inclusive the root element 1143 * and class matches. 1144 * @param e The DOM element. 1145 * @param name Name of the class to fill. 1146 * @param custom Custom handler dictionary (see {@link fill} for 1147 * details). 1148 */ 1149 fillInnerByClass(e: HTMLElement|null, name: string, 1150 custom?: DataCallbacks|null): void 1151 { 1152 if (e !== null) 1153 this._fillByClass(e, name, false, custom); 1154 } 1155 1156 private _fill(e: HTMLElement, obj: companyData|companyData[], 1157 inc: boolean, custom?: DataCallbacks|null): 1158 void 1159 { 1160 if (Array.isArray(obj) && obj.length === 0) 1161 return; 1162 const o = Array.isArray(obj) ? obj[0] : obj; 1163 if (typeof(custom) === 'undefined') 1164 custom = null; 1165 _fillField(e, 'company', 'name', custom, 1166 o.name, inc, false,null); 1167 _fillField(e, 'company', 'id', custom, o.id, inc, 1168 false,null); 1169 _fillField(e, 'company', 'somenum', custom, 1170 o.somenum, inc, true,null); 1171 if (custom !== null && 1172 typeof(custom['company']) !== 'undefined') { 1173 if (Array.isArray(custom['company'])) { 1174 for (const fn of custom['company']) 1175 fn(e, 'company', o); 1176 } else 1177 custom['company'](e, 'company', o); 1178 } 1179 } 1180 1181 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1182 custom?: DataCallbacks|null): void 1183 { 1184 for (const elem of _elemList(e, name, inc)) 1185 this._fill(elem, this.obj, inc, custom); 1186 } 1187 1188 /** 1189 * Like {@link fillArray}, but hiding an element if the array is 1190 * empty or null. 1191 * @param e The DOM element. 1192 * @param tohide DOM element to hide. 1193 * @param o The array (or object) to fill. 1194 * @param custom Custom handler dictionary (see {@link fill}). 1195 */ 1196 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1197 custom?: DataCallbacks|null): void 1198 { 1199 const len = (this.obj === null) ? 0 : 1200 Array.isArray(this.obj) ? this.obj.length : 1201 1; 1202 if (e !== null) 1203 _hide(e); 1204 if (tohide !== null) 1205 _show(tohide); 1206 this.fillArray(e, custom); 1207 if (tohide !== null && len === 0) 1208 _hide(tohide); 1209 } 1210 1211 /** 1212 * Like {@link fillArray}, but showing an element if the array is 1213 * empty or null. 1214 * @param e The DOM element. 1215 * @param toshow The DOM element to show. 1216 * @param o The array or object to fill. 1217 * @param custom Custom handler dictionary (see {@link fill}). 1218 */ 1219 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1220 custom?: DataCallbacks|null): void 1221 { 1222 const len = (this.obj === null) ? 0 : 1223 Array.isArray(this.obj) ? this.obj.length : 1224 1; 1225 if (e !== null) 1226 _hide(e); 1227 if (toshow !== null) 1228 _hide(toshow); 1229 this.fillArray(e, custom); 1230 if (toshow !== null && len === 0) 1231 _show(toshow); 1232 } 1233 1234 /** 1235 * Like {@link fill} but for an array. If the data is not an 1236 * array, it is remapped as an array of one. This will save the 1237 * first element within 'e', remove all children of 'e', then 1238 * repeatedly clone the saved element and re-append it, filling 1239 * in the cloned subtree with the array (inclusive of the subtree 1240 * root). If the input array is empty or null, 'e' is hidden by 1241 * using the *hide* class. Otherwise, the *hide* class is 1242 * removed. 1243 * @param e The DOM element. 1244 * @param custom Custom handler dictionary (see {@link fill}). 1245 */ 1246 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1247 void 1248 { 1249 const o = Array.isArray(this.obj) ? 1250 this.obj : [this.obj]; 1251 if (e === null || e.children.length === 0) 1252 return; 1253 _hide(e); 1254 if (o.length === 0 || this.obj === null) 1255 return; 1256 _show(e); 1257 const row = e.children[0]; 1258 e.replaceChildren(); 1259 for (const obj of o) { 1260 const cln = <HTMLElement>row.cloneNode(true); 1261 e.append(cln); 1262 this._fill(cln, obj, true, custom); 1263 } 1264 } 1265 1266 /** 1267 * Like {@link fillArray} but instead of accepting a single 1268 * element to fill, filling all elements by class name beneath 1269 * the given root (non-inclusive). 1270 * @param e The DOM element. 1271 * @param name Name of the class to fill. 1272 * @param custom Custom handler dictionary (see {@link fill} for 1273 * details). 1274 */ 1275 fillArrayByClass(e: HTMLElement|null, name: string, 1276 custom?: DataCallbacks|null): void 1277 { 1278 for (const elem of _elemList(e, name, false)) 1279 this.fillArray(elem, custom); 1280 } 1281 1282 } 1283 1284 /** 1285 * Writes {@link userData} into a DOM tree. 1286 */ 1287 export class user { 1288 readonly obj: userData|userData[]; 1289 /** 1290 * @param obj The object(s) to write. 1291 */ 1292 constructor(o: userData|userData[]) 1293 { 1294 this.obj = o; 1295 } 1296 1297 /** 1298 * Writes {@link userData} into the given element. If constructed 1299 * with an array, the first element is used. Elements within 1300 * (and including) the element having the following classes are 1301 * manipulated as follows: 1302 * 1303 * - `user-company-obj`: invoke {@link company#fillInner} with 1304 * **company** data 1305 * - `user-cid-enum-select`: sets or unsets the `selected` 1306 * attribute for non-inclusive descendent `<option>` elements 1307 * depending on whether the value matches 1308 * - `user-cid-value-checked`: sets or unsets the `checked` 1309 * attribute depending on whether the value matches 1310 * - `user-cid-text`: replace contents with **cid** data 1311 * - `user-cid-value`: replace `value` attribute with **cid** 1312 * data 1313 * - `user-sex-enum-select`: sets or unsets the `selected` 1314 * attribute for non-inclusive descendent `<option>` elements 1315 * depending on whether the value matches 1316 * - `user-sex-value-checked`: sets or unsets the `checked` 1317 * attribute depending on whether the value matches 1318 * - `user-sex-text`: replace contents with **sex** data 1319 * - `user-sex-value`: replace `value` attribute with **sex** 1320 * data 1321 * - `user-email-enum-select`: sets or unsets the `selected` 1322 * attribute for non-inclusive descendent `<option>` elements 1323 * depending on whether the value matches 1324 * - `user-email-value-checked`: sets or unsets the `checked` 1325 * attribute depending on whether the value matches 1326 * - `user-email-text`: replace contents with **email** data 1327 * - `user-email-value`: replace `value` attribute with **email** 1328 * data 1329 * - `user-has-image`: *hide* class removed if value is not null, 1330 * otherwise it is added 1331 * - `user-no-image`: *hide* class added if value is not null, 1332 * otherwise it is removed 1333 * - `user-image-enum-select`: sets or unsets the `selected` 1334 * attribute for non-inclusive descendent `<option>` elements 1335 * depending on whether the value matches (if non-null) (the 1336 * base64 encoded value) 1337 * - `user-image-value-checked`: sets or unsets the `checked` 1338 * attribute depending on whether the value matches (if non-null) 1339 * (the base64 encoded value) 1340 * - `user-image-text`: replace contents with **image** data (if 1341 * non-null) (the base64 encoded value) 1342 * - `user-image-value`: replace `value` attribute with **image** 1343 * data (if non-null) (the base64 encoded value) 1344 * - `user-name-enum-select`: sets or unsets the `selected` 1345 * attribute for non-inclusive descendent `<option>` elements 1346 * depending on whether the value matches 1347 * - `user-name-value-checked`: sets or unsets the `checked` 1348 * attribute depending on whether the value matches 1349 * - `user-name-text`: replace contents with **name** data 1350 * - `user-name-value`: replace `value` attribute with **name** 1351 * data 1352 * - `user-uid-enum-select`: sets or unsets the `selected` 1353 * attribute for non-inclusive descendent `<option>` elements 1354 * depending on whether the value matches 1355 * - `user-uid-value-checked`: sets or unsets the `checked` 1356 * attribute depending on whether the value matches 1357 * - `user-uid-text`: replace contents with **uid** data 1358 * - `user-uid-value`: replace `value` attribute with **uid** 1359 * data 1360 * 1361 * @param e The DOM element. 1362 * @param custom The dictionary of functions keyed by structure 1363 * and field name (e.g., *foo** structure, **bar** field would be 1364 * `foo-bar`). The value is a function for custom handling that 1365 * accepts the 'e' value, the name of the structure-field, and 1366 * the value of the structure and field. You may also specify an 1367 * array of functions instead of a singleton. These callbacks are 1368 * invoked *after* the generic classes are filled. 1369 */ 1370 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1371 { 1372 if (e !== null) 1373 this._fill(e, this.obj, true, custom); 1374 } 1375 1376 /** 1377 * Like {@link fill} but not including the passed-in element. 1378 * @param e The DOM element. 1379 * @param custom Custom handler dictionary (see {@link fill} for 1380 * details). 1381 */ 1382 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1383 void 1384 { 1385 if (e !== null) 1386 this._fill(e, this.obj, false, custom); 1387 } 1388 1389 /** 1390 * Like {@link fill} but instead of accepting a single element to 1391 * fill, filling into all elements (inclusive) matching the given 1392 * class name beneath (inclusive) the element. 1393 * @param e The DOM element. 1394 * @param name Name of the class to fill. 1395 * @param custom Custom handler dictionary (see {@link fill} for 1396 * details). 1397 */ 1398 fillByClass(e: HTMLElement|null, name: string, 1399 custom?: DataCallbacks|null): void 1400 { 1401 if (e !== null) 1402 this._fillByClass(e, name, true, custom); 1403 } 1404 1405 /** 1406 * Like {@link fillByClass} but not inclusive the root element 1407 * and class matches. 1408 * @param e The DOM element. 1409 * @param name Name of the class to fill. 1410 * @param custom Custom handler dictionary (see {@link fill} for 1411 * details). 1412 */ 1413 fillInnerByClass(e: HTMLElement|null, name: string, 1414 custom?: DataCallbacks|null): void 1415 { 1416 if (e !== null) 1417 this._fillByClass(e, name, false, custom); 1418 } 1419 1420 private _fill(e: HTMLElement, obj: userData|userData[], 1421 inc: boolean, custom?: DataCallbacks|null): 1422 void 1423 { 1424 if (Array.isArray(obj) && obj.length === 0) 1425 return; 1426 const o = Array.isArray(obj) ? obj[0] : obj; 1427 if (typeof(custom) === 'undefined') 1428 custom = null; 1429 _fillField(e, 'user', 'company', custom, 1430 o.company, inc, false, 1431 new company(o.company)); 1432 _fillField(e, 'user', 'cid', custom, o.cid, inc, 1433 false,null); 1434 _fillField(e, 'user', 'sex', custom, o.sex, inc, 1435 false,null); 1436 _fillField(e, 'user', 'email', custom, 1437 o.email, inc, false,null); 1438 _fillField(e, 'user', 'image', custom, 1439 o.image, inc, true,null); 1440 _fillField(e, 'user', 'name', custom, 1441 o.name, inc, false,null); 1442 _fillField(e, 'user', 'uid', custom, o.uid, inc, 1443 false,null); 1444 if (custom !== null && 1445 typeof(custom['user']) !== 'undefined') { 1446 if (Array.isArray(custom['user'])) { 1447 for (const fn of custom['user']) 1448 fn(e, 'user', o); 1449 } else 1450 custom['user'](e, 'user', o); 1451 } 1452 } 1453 1454 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1455 custom?: DataCallbacks|null): void 1456 { 1457 for (const elem of _elemList(e, name, inc)) 1458 this._fill(elem, this.obj, inc, custom); 1459 } 1460 1461 /** 1462 * Like {@link fillArray}, but hiding an element if the array is 1463 * empty or null. 1464 * @param e The DOM element. 1465 * @param tohide DOM element to hide. 1466 * @param o The array (or object) to fill. 1467 * @param custom Custom handler dictionary (see {@link fill}). 1468 */ 1469 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1470 custom?: DataCallbacks|null): void 1471 { 1472 const len = (this.obj === null) ? 0 : 1473 Array.isArray(this.obj) ? this.obj.length : 1474 1; 1475 if (e !== null) 1476 _hide(e); 1477 if (tohide !== null) 1478 _show(tohide); 1479 this.fillArray(e, custom); 1480 if (tohide !== null && len === 0) 1481 _hide(tohide); 1482 } 1483 1484 /** 1485 * Like {@link fillArray}, but showing an element if the array is 1486 * empty or null. 1487 * @param e The DOM element. 1488 * @param toshow The DOM element to show. 1489 * @param o The array or object to fill. 1490 * @param custom Custom handler dictionary (see {@link fill}). 1491 */ 1492 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1493 custom?: DataCallbacks|null): void 1494 { 1495 const len = (this.obj === null) ? 0 : 1496 Array.isArray(this.obj) ? this.obj.length : 1497 1; 1498 if (e !== null) 1499 _hide(e); 1500 if (toshow !== null) 1501 _hide(toshow); 1502 this.fillArray(e, custom); 1503 if (toshow !== null && len === 0) 1504 _show(toshow); 1505 } 1506 1507 /** 1508 * Like {@link fill} but for an array. If the data is not an 1509 * array, it is remapped as an array of one. This will save the 1510 * first element within 'e', remove all children of 'e', then 1511 * repeatedly clone the saved element and re-append it, filling 1512 * in the cloned subtree with the array (inclusive of the subtree 1513 * root). If the input array is empty or null, 'e' is hidden by 1514 * using the *hide* class. Otherwise, the *hide* class is 1515 * removed. 1516 * @param e The DOM element. 1517 * @param custom Custom handler dictionary (see {@link fill}). 1518 */ 1519 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1520 void 1521 { 1522 const o = Array.isArray(this.obj) ? 1523 this.obj : [this.obj]; 1524 if (e === null || e.children.length === 0) 1525 return; 1526 _hide(e); 1527 if (o.length === 0 || this.obj === null) 1528 return; 1529 _show(e); 1530 const row = e.children[0]; 1531 e.replaceChildren(); 1532 for (const obj of o) { 1533 const cln = <HTMLElement>row.cloneNode(true); 1534 e.append(cln); 1535 this._fill(cln, obj, true, custom); 1536 } 1537 } 1538 1539 /** 1540 * Like {@link fillArray} but instead of accepting a single 1541 * element to fill, filling all elements by class name beneath 1542 * the given root (non-inclusive). 1543 * @param e The DOM element. 1544 * @param name Name of the class to fill. 1545 * @param custom Custom handler dictionary (see {@link fill} for 1546 * details). 1547 */ 1548 fillArrayByClass(e: HTMLElement|null, name: string, 1549 custom?: DataCallbacks|null): void 1550 { 1551 for (const elem of _elemList(e, name, false)) 1552 this.fillArray(elem, custom); 1553 } 1554 1555 } 1556 1557 /** 1558 * Writes {@link sessionData} into a DOM tree. 1559 */ 1560 export class session { 1561 readonly obj: sessionData|sessionData[]; 1562 /** 1563 * @param obj The object(s) to write. 1564 */ 1565 constructor(o: sessionData|sessionData[]) 1566 { 1567 this.obj = o; 1568 } 1569 1570 /** 1571 * Writes {@link sessionData} into the given element. If 1572 * constructed with an array, the first element is used. 1573 * Elements within (and including) the element having the 1574 * following classes are manipulated as follows: 1575 * 1576 * - `session-user-obj`: invoke {@link user#fillInner} with 1577 * **user** data 1578 * - `session-userid-enum-select`: sets or unsets the `selected` 1579 * attribute for non-inclusive descendent `<option>` elements 1580 * depending on whether the value matches 1581 * - `session-userid-value-checked`: sets or unsets the `checked` 1582 * attribute depending on whether the value matches 1583 * - `session-userid-text`: replace contents with **userid** data 1584 * - `session-userid-value`: replace `value` attribute with 1585 * **userid** data 1586 * - `session-token-enum-select`: sets or unsets the `selected` 1587 * attribute for non-inclusive descendent `<option>` elements 1588 * depending on whether the value matches 1589 * - `session-token-value-checked`: sets or unsets the `checked` 1590 * attribute depending on whether the value matches 1591 * - `session-token-text`: replace contents with **token** data 1592 * - `session-token-value`: replace `value` attribute with 1593 * **token** data 1594 * - `session-mtime-enum-select`: sets or unsets the `selected` 1595 * attribute for non-inclusive descendent `<option>` elements 1596 * depending on whether the value matches 1597 * - `session-mtime-value-checked`: sets or unsets the `checked` 1598 * attribute depending on whether the value matches 1599 * - `session-mtime-text`: replace contents with **mtime** data 1600 * - `session-mtime-value`: replace `value` attribute with 1601 * **mtime** data 1602 * - `session-mtime-date-value`: set the element's `value` to the 1603 * ISO-8601 date format of the data 1604 * - `session-mtime-date-text`: like `session-mtime-date-value`, 1605 * but replacing textual content 1606 * - `session-id-enum-select`: sets or unsets the `selected` 1607 * attribute for non-inclusive descendent `<option>` elements 1608 * depending on whether the value matches 1609 * - `session-id-value-checked`: sets or unsets the `checked` 1610 * attribute depending on whether the value matches 1611 * - `session-id-text`: replace contents with **id** data 1612 * - `session-id-value`: replace `value` attribute with **id** 1613 * data 1614 * 1615 * @param e The DOM element. 1616 * @param custom The dictionary of functions keyed by structure 1617 * and field name (e.g., *foo** structure, **bar** field would be 1618 * `foo-bar`). The value is a function for custom handling that 1619 * accepts the 'e' value, the name of the structure-field, and 1620 * the value of the structure and field. You may also specify an 1621 * array of functions instead of a singleton. These callbacks are 1622 * invoked *after* the generic classes are filled. 1623 */ 1624 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1625 { 1626 if (e !== null) 1627 this._fill(e, this.obj, true, custom); 1628 } 1629 1630 /** 1631 * Like {@link fill} but not including the passed-in element. 1632 * @param e The DOM element. 1633 * @param custom Custom handler dictionary (see {@link fill} for 1634 * details). 1635 */ 1636 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1637 void 1638 { 1639 if (e !== null) 1640 this._fill(e, this.obj, false, custom); 1641 } 1642 1643 /** 1644 * Like {@link fill} but instead of accepting a single element to 1645 * fill, filling into all elements (inclusive) matching the given 1646 * class name beneath (inclusive) the element. 1647 * @param e The DOM element. 1648 * @param name Name of the class to fill. 1649 * @param custom Custom handler dictionary (see {@link fill} for 1650 * details). 1651 */ 1652 fillByClass(e: HTMLElement|null, name: string, 1653 custom?: DataCallbacks|null): void 1654 { 1655 if (e !== null) 1656 this._fillByClass(e, name, true, custom); 1657 } 1658 1659 /** 1660 * Like {@link fillByClass} but not inclusive the root element 1661 * and class matches. 1662 * @param e The DOM element. 1663 * @param name Name of the class to fill. 1664 * @param custom Custom handler dictionary (see {@link fill} for 1665 * details). 1666 */ 1667 fillInnerByClass(e: HTMLElement|null, name: string, 1668 custom?: DataCallbacks|null): void 1669 { 1670 if (e !== null) 1671 this._fillByClass(e, name, false, custom); 1672 } 1673 1674 private _fill(e: HTMLElement, obj: sessionData|sessionData[], 1675 inc: boolean, custom?: DataCallbacks|null): 1676 void 1677 { 1678 if (Array.isArray(obj) && obj.length === 0) 1679 return; 1680 const o = Array.isArray(obj) ? obj[0] : obj; 1681 if (typeof(custom) === 'undefined') 1682 custom = null; 1683 _fillField(e, 'session', 'user', custom, 1684 o.user, inc, false, 1685 new user(o.user)); 1686 _fillField(e, 'session', 'userid', custom, 1687 o.userid, inc, false,null); 1688 _fillField(e, 'session', 'token', custom, 1689 o.token, inc, false,null); 1690 _fillField(e, 'session', 'mtime', custom, 1691 o.mtime, inc, false,null); 1692 _fillDateValue(e, 'session-mtime', o.mtime, inc); 1693 _fillField(e, 'session', 'id', custom, o.id, inc, 1694 false,null); 1695 if (custom !== null && 1696 typeof(custom['session']) !== 'undefined') { 1697 if (Array.isArray(custom['session'])) { 1698 for (const fn of custom['session']) 1699 fn(e, 'session', o); 1700 } else 1701 custom['session'](e, 'session', o); 1702 } 1703 } 1704 1705 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1706 custom?: DataCallbacks|null): void 1707 { 1708 for (const elem of _elemList(e, name, inc)) 1709 this._fill(elem, this.obj, inc, custom); 1710 } 1711 1712 /** 1713 * Like {@link fillArray}, but hiding an element if the array is 1714 * empty or null. 1715 * @param e The DOM element. 1716 * @param tohide DOM element to hide. 1717 * @param o The array (or object) to fill. 1718 * @param custom Custom handler dictionary (see {@link fill}). 1719 */ 1720 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1721 custom?: DataCallbacks|null): void 1722 { 1723 const len = (this.obj === null) ? 0 : 1724 Array.isArray(this.obj) ? this.obj.length : 1725 1; 1726 if (e !== null) 1727 _hide(e); 1728 if (tohide !== null) 1729 _show(tohide); 1730 this.fillArray(e, custom); 1731 if (tohide !== null && len === 0) 1732 _hide(tohide); 1733 } 1734 1735 /** 1736 * Like {@link fillArray}, but showing an element if the array is 1737 * empty or null. 1738 * @param e The DOM element. 1739 * @param toshow The DOM element to show. 1740 * @param o The array or object to fill. 1741 * @param custom Custom handler dictionary (see {@link fill}). 1742 */ 1743 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1744 custom?: DataCallbacks|null): void 1745 { 1746 const len = (this.obj === null) ? 0 : 1747 Array.isArray(this.obj) ? this.obj.length : 1748 1; 1749 if (e !== null) 1750 _hide(e); 1751 if (toshow !== null) 1752 _hide(toshow); 1753 this.fillArray(e, custom); 1754 if (toshow !== null && len === 0) 1755 _show(toshow); 1756 } 1757 1758 /** 1759 * Like {@link fill} but for an array. If the data is not an 1760 * array, it is remapped as an array of one. This will save the 1761 * first element within 'e', remove all children of 'e', then 1762 * repeatedly clone the saved element and re-append it, filling 1763 * in the cloned subtree with the array (inclusive of the subtree 1764 * root). If the input array is empty or null, 'e' is hidden by 1765 * using the *hide* class. Otherwise, the *hide* class is 1766 * removed. 1767 * @param e The DOM element. 1768 * @param custom Custom handler dictionary (see {@link fill}). 1769 */ 1770 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1771 void 1772 { 1773 const o = Array.isArray(this.obj) ? 1774 this.obj : [this.obj]; 1775 if (e === null || e.children.length === 0) 1776 return; 1777 _hide(e); 1778 if (o.length === 0 || this.obj === null) 1779 return; 1780 _show(e); 1781 const row = e.children[0]; 1782 e.replaceChildren(); 1783 for (const obj of o) { 1784 const cln = <HTMLElement>row.cloneNode(true); 1785 e.append(cln); 1786 this._fill(cln, obj, true, custom); 1787 } 1788 } 1789 1790 /** 1791 * Like {@link fillArray} but instead of accepting a single 1792 * element to fill, filling all elements by class name beneath 1793 * the given root (non-inclusive). 1794 * @param e The DOM element. 1795 * @param name Name of the class to fill. 1796 * @param custom Custom handler dictionary (see {@link fill} for 1797 * details). 1798 */ 1799 fillArrayByClass(e: HTMLElement|null, name: string, 1800 custom?: DataCallbacks|null): void 1801 { 1802 for (const elem of _elemList(e, name, false)) 1803 this.fillArray(elem, custom); 1804 } 1805 1806 } 1807 1808 /** 1809 * Birthsex of individual 1810 */ 1811 export class sex { 1812 /** 1813 * Male 1814 */ 1815 static readonly male = '0'; 1816 /** 1817 * Female 1818 */ 1819 static readonly female = '1'; 1820 /** 1821 * Other 1822 */ 1823 static readonly other = '2'; 1824 /** 1825 * Uses the enumeration item's **jslabel** (or an empty string if 1826 * no **jslabel** is defined or there is no matching item for the 1827 * value) to format a custom label. This will act on 1828 * *xxx-yyy-label* classes, where *xxx* is the structure name and 1829 * *yyy* is the field name. 1830 * A null value is represented by the **isnull** labels (the 1831 * `ort-null` class is also appended in this case) 1832 * @param e The DOM element. 1833 * @param name If non-null, data is written to elements under the 1834 * root with the given class name. If null, data is written 1835 * directly into the DOM element. 1836 * @param v The enumeration value. 1837 */ 1838 static format(e: HTMLElement, name: string|null, 1839 v: string|number|null): void 1840 { 1841 let s: string; 1842 if (name !== null) 1843 name += '-label'; 1844 if (v === null && name !== null) { 1845 _classaddcl(e, name, 'ort-null', false); 1846 _replcllang(e, name, {_default: ''}); 1847 return; 1848 } else if (v === null) { 1849 _classadd(e, 'ort-null'); 1850 _repllang(e, {_default: ''}); 1851 return; 1852 } 1853 switch(v.toString()) { 1854 case sex.male: 1855 s = _strlang({_default: 'male'}); 1856 break; 1857 case sex.female: 1858 s = _strlang({_default: 'female'}); 1859 break; 1860 case sex.other: 1861 s = _strlang({_default: 'other'}); 1862 break; 1863 default: 1864 s = ''; 1865 break; 1866 } 1867 if (name !== null) 1868 _replcl(e, name, s, false); 1869 else 1870 _repl(e, s); 1871 } 1872 } 1873 1874 }