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); 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 { [lang: string]: string }; 546 547 /** 548 * Resolve a set of translated strings into a single one 549 * depending upon the current language. 550 * @param vals All translations of a given word. 551 * @return The word in the current language (or the default) 552 * or an empty string on failure. 553 * @internal 554 */ 555 function _strlang(vals: langmap): string 556 { 557 const lang: string|null = 558 document.documentElement.lang; 559 560 if (lang !== null && lang in vals) 561 return vals[lang]; 562 else if ('_default' in vals) 563 return vals['_default']; 564 else 565 return ''; 566 } 567 568 /** 569 * Language replacement conditional upon the label (**jslabel** 570 * in the configuration). Like {@link _replcl} with inclusion 571 * set to false. 572 * @param e The root of the DOM tree in which we query for 573 * elements to fill into. 574 * @param name The class name we search for within the root (not 575 * inclusive). 576 * @param vals All possible translations. 577 * @internal 578 */ 579 function _replcllang(e: HTMLElement, name:string, 580 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, 615 name: string, text: string, inc: boolean): void 616 { 617 let i: number; 618 const list: HTMLElement[] = 619 _elemList(e, name, inc); 620 621 for (i = 0; i < list.length; i++) 622 _attr(list[i], attr, text); 623 } 624 625 /** 626 * Get all elements beneath (possibly including) a root matching 627 * the given class. 628 * @internal 629 */ 630 function _elemList(e: HTMLElement|null, 631 cls: string, inc: boolean): HTMLElement[] 632 { 633 let list: HTMLCollectionOf<Element>; 634 let i: number; 635 const a: HTMLElement[] = []; 636 637 if (e === null) 638 return a; 639 list = e.getElementsByClassName(cls); 640 for (i = 0; i < list.length; i++) 641 a.push(<HTMLElement>list[i]); 642 if (inc && e.classList.contains(cls)) 643 a.push(e); 644 return a; 645 } 646 647 /** 648 * Replace all children of an element with text. 649 * @internal 650 */ 651 function _repl(e: HTMLElement, text: string): void 652 { 653 while (e.firstChild) 654 e.removeChild(e.firstChild); 655 e.appendChild(document.createTextNode(text)); 656 } 657 658 /** 659 * Replace children of elements matching class with text. 660 * @internal 661 */ 662 function _replcl(e: HTMLElement, name: string, 663 text: string, inc: boolean): void 664 { 665 let i: number; 666 const list: HTMLElement[] = _elemList(e, name, inc); 667 668 for (i = 0; i < list.length; i++) 669 _repl(list[i], text); 670 } 671 672 /** 673 * Add class to class list if it doesn't exist. 674 * @internal 675 */ 676 function _classadd(e: HTMLElement, name: string): HTMLElement 677 { 678 if (!e.classList.contains(name)) 679 e.classList.add(name); 680 return(e); 681 } 682 683 /** 684 * Add class if doesn't exist to all elements with class name. 685 * @internal 686 */ 687 function _classaddcl(e: HTMLElement, name: string, 688 cls: string, inc: boolean): void 689 { 690 let i: number; 691 const list: HTMLElement[] = _elemList(e, name, inc); 692 693 for (i = 0; i < list.length; i++) 694 _classadd(list[i], cls); 695 } 696 697 /** 698 * "Hide" element by adding *hide* class. 699 * @internal 700 */ 701 function _hide(e: HTMLElement): HTMLElement 702 { 703 if (!e.classList.contains('hide')) 704 e.classList.add('hide'); 705 return e; 706 } 707 708 /** 709 * "Hide" all elements matching class by adding *hide* class. 710 * @internal 711 */ 712 function _hidecl(e: HTMLElement, name: string, inc: boolean): void 713 { 714 let i: number; 715 const list: HTMLElement[] = _elemList(e, name, inc); 716 717 for (i = 0; i < list.length; i++) 718 _hide(list[i]); 719 } 720 721 /** 722 * "Show" element by removing *hide* class. 723 * @internal 724 */ 725 function _show(e: HTMLElement): HTMLElement 726 { 727 if (e.classList.contains('hide')) 728 e.classList.remove('hide'); 729 return e; 730 } 731 732 /** 733 * "Show" all elements matching class by removing *hide* class. 734 * @internal 735 */ 736 function _showcl(e: HTMLElement, name: string, inc: boolean): void 737 { 738 let i: number; 739 const list: HTMLElement[] = _elemList(e, name, inc); 740 741 for (i = 0; i < list.length; i++) 742 _show(list[i]); 743 } 744 745 /** 746 * Check input elements (that is, set the attribute `checked` to 747 * the value `checked`) for all elements of class 748 * `fname-value-checked` whose value matches the given. The 749 * checked status is removed for each item scanned. 750 * A null value never matches. 751 * @param e Root of tree scanned for elements. 752 * @param fname Structure name, '-', field name. 753 * @param val The value to test for. 754 * @param inc Include root in scanning for elements. 755 * @internal 756 */ 757 function _fillValueChecked(e: HTMLElement, fname: string, 758 val: number|string|null, inc: boolean): void 759 { 760 let i: number; 761 const list: HTMLElement[] = _elemList 762 (e, fname + '-value-checked', inc); 763 764 for (i = 0; i < list.length; i++) { 765 const elem: HTMLInputElement = 766 <HTMLInputElement>list[i]; 767 const attrval: string|null = elem.value; 768 elem.removeAttribute('checked'); 769 if (val === null || attrval === null) 770 continue; 771 if (val.toString() === attrval) 772 elem.setAttribute('checked', 'checked'); 773 } 774 } 775 776 /** 777 * Take all `<option>` elements under the root (non-inclusive) 778 * and sets or unsets the `selected` attribute depending upon 779 * whether it matches the object's value. 780 * A null value never matches. 781 * @param e Root of tree scanned for elements. 782 * @param val The value to test for. 783 * @internal 784 */ 785 function _fillValueSelect(e: HTMLElement, 786 val: number|string|null): void 787 { 788 let i: number; 789 const list: HTMLCollectionOf<HTMLElement> = 790 e.getElementsByTagName('option'); 791 792 for (i = 0; i < list.length; i++) { 793 const elem: HTMLOptionElement = 794 <HTMLOptionElement>list[i]; 795 const attrval: string|null = elem.value; 796 elem.removeAttribute('selected'); 797 if (val === null || attrval === null) 798 continue; 799 if (val.toString() === attrval) 800 elem.setAttribute('selected', 'selected'); 801 } 802 } 803 804 /** 805 * Fill in ISO-8601 dates. 806 * Does nothing for null or unexported date. 807 * @param e Root of tree scanned for elements. 808 * @param fname Structure name, '-', field name. 809 * @param val Epoch date itself. 810 * @param inc Include root in scanning for elements. 811 * @internal 812 */ 813 function _fillDateValue(e: HTMLElement, fname: string, 814 val: string|number|null|undefined, inc: boolean): void 815 { 816 const v: Long|null = Long.fromValue(val); 817 const d: Date = new Date(); 818 819 if (v === null) 820 return; 821 822 d.setTime(v.toNumber() * 1000); 823 824 /* Make sure to zero-pad the digits. */ 825 826 const year: number = d.getFullYear(); 827 const mo: number = d.getMonth() + 1; 828 const day: number = d.getDate(); 829 const full: string = year + '-' + 830 (mo < 10 ? '0' : '') + mo + '-' + 831 (day < 10 ? '0' : '') + day; 832 833 _attrcl(e, 'value', fname + '-date-value', full, inc); 834 _replcl(e, fname + '-date-text', full, inc); 835 } 836 837 /** 838 * Check input elements (that is, set the attribute `checked` to 839 * the value `checked`) for elements with class 840 * `fname-value-checked` whose non-null, numeric value as a bit index 841 * is set in the bit-field given as input. 842 * A null value never matches. 843 * @param e Root of tree scanned for elements. 844 * @param fname Structure name, '-', field name. 845 * @param val Bit-field to test for. 846 * @param inc Include root in scanning for elements. 847 * @internal 848 */ 849 function _fillBitsChecked(e: HTMLElement, fname: string, 850 val: string|number|null|undefined, inc: boolean): void 851 { 852 let i: number; 853 let v: number; 854 const lval: Long|null|undefined = 855 Long.fromValueUndef(val); 856 const list: HTMLElement[] = _elemList 857 (e, fname + '-bits-checked', inc); 858 859 if (typeof lval === 'undefined') 860 return; 861 862 for (i = 0; i < list.length; i++) { 863 const elem: HTMLInputElement = 864 <HTMLInputElement>list[i]; 865 const attrval: string|null = elem.value; 866 elem.removeAttribute('checked'); 867 if (lval === null || attrval === null) 868 continue; 869 870 /* 871 * This would be better served with 872 * Number.isInteger(), but we don't want to 873 * assume ES6. 874 */ 875 876 v = Number(attrval); 877 if (isNaN(v)) 878 continue; 879 if (!(isFinite(v) && Math.floor(v) === v)) 880 continue; 881 if (v < 0 || v > 64) 882 continue; 883 884 if ((v === 0 && lval.isZero()) || 885 !Long.ONE.shl(v - 1).and(lval).isZero()) { 886 elem.setAttribute('checked', 'checked'); 887 } 888 } 889 } 890 891 /** 892 * Fill a structure field. This first does the has/no class 893 * setting for null values, then optionally returns if null 894 * (running the custom fields first), otherwise the generic 895 * text/value/etc fields, then finally the custom fields. 896 * @param e Root of the DOM tree filled into. 897 * @param strct Name of the structure filling in. 898 * @param name The name of the field. 899 * @param custom Custom callback functions. 900 * @param obj The data itself. 901 * @param inc Whether to include the root element in looking 902 * for elements to fill. Nested structures are always filled 903 * non-inclusively. 904 * @param cannull Whether the data may be null. 905 * @param sub If the data object is a nested structure 906 * interface, the allocated class of that interface. 907 * @internal 908 */ 909 function _fillField(e: HTMLElement, strct: string, name: string, 910 custom: DataCallbacks|null, obj: any, inc: boolean, 911 cannull: boolean, sub: any): void 912 { 913 let i: number; 914 const fname: string = strct + '-' + name; 915 916 /* Don't do anything if we're not defined. */ 917 918 if (typeof obj === 'undefined') 919 return; 920 921 /* First handle our has/no null situation. */ 922 923 if (cannull) { 924 if (obj === null) { 925 _hidecl(e, strct + '-has-' + name, inc); 926 _showcl(e, strct + '-no-' + name, inc); 927 } else { 928 _showcl(e, strct + '-has-' + name, inc); 929 _hidecl(e, strct + '-no-' + name, inc); 930 } 931 } 932 933 /* Don't process null values that can be null. */ 934 935 if (cannull && obj === null) { 936 if (custom !== null && fname in custom) { 937 if (custom[fname] instanceof Array) { 938 for (i = 0; i < custom[fname].length; i++) 939 custom[fname][i](e, fname, null); 940 } else 941 custom[fname](e, fname, null); 942 } 943 return; 944 } 945 946 /* 947 * Non-null non-structs. 948 * Note that "sub" is never undefined because that would 949 * otherwise be caught in the "typeof obj" guard above. 950 */ 951 952 if (sub !== null) { 953 const list: HTMLElement[] = 954 _elemList(e, fname + '-obj', inc); 955 for (i = 0; i < list.length; i++) 956 sub.fillInner(list[i], custom); 957 } else { 958 const list: HTMLElement[] = 959 _elemList(e, fname + '-enum-select', inc); 960 for (i = 0; i < list.length; i++) 961 _fillValueSelect(list[i], obj); 962 _replcl(e, fname + '-text', obj, inc); 963 _attrcl(e, 'value', fname + '-value', obj, inc); 964 _fillValueChecked(e, fname, obj, inc); 965 } 966 967 /* Lastly, handle the custom callback. */ 968 969 if (custom !== null && 970 typeof custom[fname] !== 'undefined') { 971 if (custom[fname] instanceof Array) { 972 for (i = 0; i < custom[fname].length; i++) 973 custom[fname][i](e, fname, obj); 974 } else 975 custom[fname](e, fname, obj); 976 } 977 } 978 979 export type DCbstring = (e: HTMLElement, 980 name: string, val: string) => void; 981 export type DCbstringNull = (e: HTMLElement, 982 name: string, val: string|null) => void; 983 export type DCbinteger = (e: HTMLElement, 984 name: string, val: string|number) => void; 985 export type DCbintegerNull = (e: HTMLElement, 986 name: string, val: string|number|null) => void; 987 export type DCbnumber = (e: HTMLElement, 988 name: string, val: number) => void; 989 export type DCbnumberNull = (e: HTMLElement, 990 name: string, val: number|null) => void; 991 export type DCbStructcompany = (e: HTMLElement, 992 name: string, val: companyData|null) => void; 993 export type DCbStructuser = (e: HTMLElement, 994 name: string, val: userData|null) => void; 995 export type DCbStructsession = (e: HTMLElement, 996 name: string, val: sessionData|null) => void; 997 998 /** 999 * All possible custom callbacks for this ort configuration. 1000 */ 1001 export interface DataCallbacks 1002 { 1003 [key: string]: any; 1004 'company'?: DCbStructcompany|DCbStructcompany[]; 1005 'company-name'?: DCbstring|DCbstring[]; 1006 'company-id'?: DCbinteger|DCbinteger[]; 1007 'company-somenum'?: DCbintegerNull|DCbintegerNull[]; 1008 'user'?: DCbStructuser|DCbStructuser[]; 1009 'user-company'?: DCbStructcompany|DCbStructcompany[]; 1010 'user-cid'?: DCbinteger|DCbinteger[]; 1011 'user-sex'?: DCbinteger|DCbinteger[]; 1012 'user-email'?: DCbstring|DCbstring[]; 1013 'user-image'?: DCbstringNull|DCbstringNull[]; 1014 'user-name'?: DCbstring|DCbstring[]; 1015 'user-uid'?: DCbinteger|DCbinteger[]; 1016 'session'?: DCbStructsession|DCbStructsession[]; 1017 'session-user'?: DCbStructuser|DCbStructuser[]; 1018 'session-userid'?: DCbinteger|DCbinteger[]; 1019 'session-token'?: DCbinteger|DCbinteger[]; 1020 'session-mtime'?: DCbinteger|DCbinteger[]; 1021 'session-id'?: DCbinteger|DCbinteger[]; 1022 } 1023 1024 /** 1025 * Controlling organisation. 1026 */ 1027 export interface companyData 1028 { 1029 /** 1030 * Name of the organisation. 1031 */ 1032 name: string; 1033 id: string|number; 1034 /** 1035 * Simply a check for null values. 1036 */ 1037 somenum: string|number; 1038 } 1039 1040 /** 1041 * A regular user. 1042 */ 1043 export interface userData 1044 { 1045 /** 1046 * This struct will be filled in from an inner join 1047 * on the "cid" variable. 1048 */ 1049 company: companyData; 1050 /** 1051 * A foreign key reference. 1052 */ 1053 cid: string|number; 1054 /** 1055 * User's birth sex. 1056 */ 1057 sex: string|number; 1058 /** 1059 * Unique e-mail address. 1060 */ 1061 email: string; 1062 /** 1063 * A PNG image or something. 1064 */ 1065 image: string; 1066 /** 1067 * User's full name. 1068 */ 1069 name: string; 1070 uid: string|number; 1071 } 1072 1073 /** 1074 * Authenticated session. 1075 */ 1076 export interface sessionData 1077 { 1078 user: userData; 1079 /** 1080 * Associated user. 1081 */ 1082 userid: string|number; 1083 /** 1084 * Random cookie. 1085 */ 1086 token: string|number; 1087 mtime: string|number; 1088 id: string|number; 1089 } 1090 1091 /** 1092 * Writes {@link companyData} into a DOM tree. 1093 */ 1094 export class company { 1095 readonly obj: companyData|companyData[]; 1096 /** 1097 * @param obj The object(s) to write. 1098 */ 1099 constructor(o: companyData|companyData[]) 1100 { 1101 this.obj = o; 1102 } 1103 1104 /** 1105 * Writes {@link companyData} into the given element. If 1106 * constructed with an array, the first element is used. 1107 * Elements within (and including) the element having the 1108 * following classes are manipulated as follows: 1109 * 1110 * - `company-name-enum-select`: sets or unsets the `selected` 1111 * attribute for non-inclusive descendent `<option>` elements 1112 * depending on whether the value matches 1113 * - `company-name-value-checked`: sets or unsets the `checked` 1114 * attribute depending on whether the value matches 1115 * - `company-name-text`: replace contents with **name** data 1116 * - `company-name-value`: replace `value` attribute with 1117 * **name** data 1118 * - `company-id-enum-select`: sets or unsets the `selected` 1119 * attribute for non-inclusive descendent `<option>` elements 1120 * depending on whether the value matches 1121 * - `company-id-value-checked`: sets or unsets the `checked` 1122 * attribute depending on whether the value matches 1123 * - `company-id-text`: replace contents with **id** data 1124 * - `company-id-value`: replace `value` attribute with **id** 1125 * data 1126 * - `company-has-somenum`: *hide* class removed if value is not 1127 * null, otherwise it is added 1128 * - `company-no-somenum`: *hide* class added if value is not 1129 * null, otherwise it is removed 1130 * - `company-somenum-enum-select`: sets or unsets the `selected` 1131 * attribute for non-inclusive descendent `<option>` elements 1132 * depending on whether the value matches (if non-null) 1133 * - `company-somenum-value-checked`: sets or unsets the 1134 * `checked` attribute depending on whether the value matches (if 1135 * non-null) 1136 * - `company-somenum-text`: replace contents with **somenum** 1137 * data (if non-null) 1138 * - `company-somenum-value`: replace `value` attribute with 1139 * **somenum** data (if non-null) 1140 * 1141 * @param e The DOM element. 1142 * @param custom The dictionary of functions keyed by structure 1143 * and field name (e.g., *foo** structure, **bar** field would be 1144 * `foo-bar`). The value is a function for custom handling that 1145 * accepts the 'e' value, the name of the structure-field, and 1146 * the value of the structure and field. You may also specify an 1147 * array of functions instead of a singleton. These callbacks are 1148 * invoked *after* the generic classes are filled. 1149 */ 1150 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1151 { 1152 if (e !== null) 1153 this._fill(e, this.obj, true, custom); 1154 } 1155 1156 /** 1157 * Like {@link fill} but not including the passed-in element. 1158 * @param e The DOM element. 1159 * @param custom Custom handler dictionary (see {@link fill} for 1160 * details). 1161 */ 1162 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1163 void 1164 { 1165 if (e !== null) 1166 this._fill(e, this.obj, false, custom); 1167 } 1168 1169 /** 1170 * Like {@link fill} but instead of accepting a single element to 1171 * fill, filling into all elements (inclusive) matching the given 1172 * class name beneath (inclusive) the element. 1173 * @param e The DOM element. 1174 * @param name Name of the class to fill. 1175 * @param custom Custom handler dictionary (see {@link fill} for 1176 * details). 1177 */ 1178 fillByClass(e: HTMLElement|null, name: string, 1179 custom?: DataCallbacks|null): void 1180 { 1181 if (e !== null) 1182 this._fillByClass(e, name, true, custom); 1183 } 1184 1185 /** 1186 * Like {@link fillByClass} but not inclusive the root element 1187 * and class matches. 1188 * @param e The DOM element. 1189 * @param name Name of the class to fill. 1190 * @param custom Custom handler dictionary (see {@link fill} for 1191 * details). 1192 */ 1193 fillInnerByClass(e: HTMLElement|null, name: string, 1194 custom?: DataCallbacks|null): void 1195 { 1196 if (e !== null) 1197 this._fillByClass(e, name, false, custom); 1198 } 1199 1200 private _fill(e: HTMLElement, obj: companyData|companyData[], 1201 inc: boolean, custom?: DataCallbacks|null): 1202 void 1203 { 1204 if (obj instanceof Array && obj.length === 0) 1205 return; 1206 const o: companyData = 1207 (obj instanceof Array) ? obj[0] : obj; 1208 if (typeof custom === 'undefined') 1209 custom = null; 1210 _fillField(e, 'company', 'name', custom, 1211 o.name, inc, false,null); 1212 _fillField(e, 'company', 'id', custom, o.id, inc, 1213 false,null); 1214 _fillField(e, 'company', 'somenum', custom, 1215 o.somenum, inc, true,null); 1216 if (custom !== null && 1217 typeof custom['company'] !== 'undefined') { 1218 if (custom['company'] instanceof Array) { 1219 let i: number; 1220 for (i = 0; i < custom['company'].length; i++) 1221 custom['company'][i](e, 'company', o); 1222 } else 1223 custom['company'](e, 'company', o); 1224 } 1225 } 1226 1227 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1228 custom?: DataCallbacks|null): void 1229 { 1230 let i: number; 1231 const list: HTMLElement[] = 1232 _elemList(e, name, inc); 1233 for (i = 0; i < list.length; i++) 1234 this._fill(list[i], this.obj, inc, custom); 1235 } 1236 1237 /** 1238 * Like {@link fillArray}, but hiding an element if the array is 1239 * empty or null. 1240 * @param e The DOM element. 1241 * @param tohide DOM element to hide. 1242 * @param o The array (or object) to fill. 1243 * @param custom Custom handler dictionary (see {@link fill}). 1244 */ 1245 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1246 custom?: DataCallbacks|null): void 1247 { 1248 let len: number; 1249 if (null === this.obj) 1250 len = 0; 1251 else if (this.obj instanceof Array) 1252 len = this.obj.length; 1253 else 1254 len = 1; 1255 if (null !== e) 1256 _hide(e); 1257 if (null !== tohide) 1258 _show(tohide); 1259 this.fillArray(e, custom); 1260 if (null !== tohide && 0 === len) 1261 _hide(tohide); 1262 } 1263 1264 /** 1265 * Like {@link fillArray}, but showing an element if the array is 1266 * empty or null. 1267 * @param e The DOM element. 1268 * @param toshow The DOM element to show. 1269 * @param o The array or object to fill. 1270 * @param custom Custom handler dictionary (see {@link fill}). 1271 */ 1272 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1273 custom?: DataCallbacks|null): void 1274 { 1275 let len: number; 1276 if (null === this.obj) 1277 len = 0; 1278 else if (this.obj instanceof Array) 1279 len = this.obj.length; 1280 else 1281 len = 1; 1282 if (null !== e) 1283 _hide(e); 1284 if (null !== toshow) 1285 _hide(toshow); 1286 this.fillArray(e, custom); 1287 if (null !== toshow && 0 === len) 1288 _show(toshow); 1289 } 1290 1291 /** 1292 * Like {@link fill} but for an array. If the data is not an 1293 * array, it is remapped as an array of one. This will save the 1294 * first element within 'e', remove all children of 'e', then 1295 * repeatedly clone the saved element and re-append it, filling 1296 * in the cloned subtree with the array (inclusive of the subtree 1297 * root). If the input array is empty or null, 'e' is hidden by 1298 * using the *hide* class. Otherwise, the *hide* class is 1299 * removed. 1300 * @param e The DOM element. 1301 * @param custom Custom handler dictionary (see {@link fill}). 1302 */ 1303 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1304 void 1305 { 1306 let i: number; 1307 const o: companyData[] = 1308 (this.obj instanceof Array) ? 1309 this.obj : [this.obj]; 1310 1311 if (e === null || e.children.length === 0) 1312 return; 1313 _hide(e); 1314 if (o.length === 0 || this.obj === null) 1315 return; 1316 _show(e); 1317 1318 const row: HTMLElement = 1319 <HTMLElement>e.children[0]; 1320 while (e.firstChild !== null) 1321 e.removeChild(e.firstChild) 1322 for (i = 0; i < o.length; i++) { 1323 const cln: HTMLElement = 1324 <HTMLElement>row.cloneNode(true); 1325 e.appendChild(cln); 1326 this._fill(cln, o[i], true, custom); 1327 } 1328 } 1329 1330 /** 1331 * Like {@link fillArray} but instead of accepting a single 1332 * element to fill, filling all elements by class name beneath 1333 * the given root (non-inclusive). 1334 * @param e The DOM element. 1335 * @param name Name of the class to fill. 1336 * @param custom Custom handler dictionary (see {@link fill} for 1337 * details). 1338 */ 1339 fillArrayByClass(e: HTMLElement|null, name: string, 1340 custom?: DataCallbacks|null): void 1341 { 1342 let i: number; 1343 const list: HTMLElement[] = 1344 _elemList(e, name, false); 1345 for (i = 0; i < list.length; i++) 1346 this.fillArray(list[i], custom); 1347 } 1348 1349 } 1350 1351 /** 1352 * Writes {@link userData} into a DOM tree. 1353 */ 1354 export class user { 1355 readonly obj: userData|userData[]; 1356 /** 1357 * @param obj The object(s) to write. 1358 */ 1359 constructor(o: userData|userData[]) 1360 { 1361 this.obj = o; 1362 } 1363 1364 /** 1365 * Writes {@link userData} into the given element. If constructed 1366 * with an array, the first element is used. Elements within 1367 * (and including) the element having the following classes are 1368 * manipulated as follows: 1369 * 1370 * - `user-company-obj`: invoke {@link company#fillInner} with 1371 * **company** data 1372 * - `user-cid-enum-select`: sets or unsets the `selected` 1373 * attribute for non-inclusive descendent `<option>` elements 1374 * depending on whether the value matches 1375 * - `user-cid-value-checked`: sets or unsets the `checked` 1376 * attribute depending on whether the value matches 1377 * - `user-cid-text`: replace contents with **cid** data 1378 * - `user-cid-value`: replace `value` attribute with **cid** 1379 * data 1380 * - `user-sex-enum-select`: sets or unsets the `selected` 1381 * attribute for non-inclusive descendent `<option>` elements 1382 * depending on whether the value matches 1383 * - `user-sex-value-checked`: sets or unsets the `checked` 1384 * attribute depending on whether the value matches 1385 * - `user-sex-text`: replace contents with **sex** data 1386 * - `user-sex-value`: replace `value` attribute with **sex** 1387 * data 1388 * - `user-email-enum-select`: sets or unsets the `selected` 1389 * attribute for non-inclusive descendent `<option>` elements 1390 * depending on whether the value matches 1391 * - `user-email-value-checked`: sets or unsets the `checked` 1392 * attribute depending on whether the value matches 1393 * - `user-email-text`: replace contents with **email** data 1394 * - `user-email-value`: replace `value` attribute with **email** 1395 * data 1396 * - `user-has-image`: *hide* class removed if value is not null, 1397 * otherwise it is added 1398 * - `user-no-image`: *hide* class added if value is not null, 1399 * otherwise it is removed 1400 * - `user-image-enum-select`: sets or unsets the `selected` 1401 * attribute for non-inclusive descendent `<option>` elements 1402 * depending on whether the value matches (if non-null) (the 1403 * base64 encoded value) 1404 * - `user-image-value-checked`: sets or unsets the `checked` 1405 * attribute depending on whether the value matches (if non-null) 1406 * (the base64 encoded value) 1407 * - `user-image-text`: replace contents with **image** data (if 1408 * non-null) (the base64 encoded value) 1409 * - `user-image-value`: replace `value` attribute with **image** 1410 * data (if non-null) (the base64 encoded value) 1411 * - `user-name-enum-select`: sets or unsets the `selected` 1412 * attribute for non-inclusive descendent `<option>` elements 1413 * depending on whether the value matches 1414 * - `user-name-value-checked`: sets or unsets the `checked` 1415 * attribute depending on whether the value matches 1416 * - `user-name-text`: replace contents with **name** data 1417 * - `user-name-value`: replace `value` attribute with **name** 1418 * data 1419 * - `user-uid-enum-select`: sets or unsets the `selected` 1420 * attribute for non-inclusive descendent `<option>` elements 1421 * depending on whether the value matches 1422 * - `user-uid-value-checked`: sets or unsets the `checked` 1423 * attribute depending on whether the value matches 1424 * - `user-uid-text`: replace contents with **uid** data 1425 * - `user-uid-value`: replace `value` attribute with **uid** 1426 * data 1427 * 1428 * @param e The DOM element. 1429 * @param custom The dictionary of functions keyed by structure 1430 * and field name (e.g., *foo** structure, **bar** field would be 1431 * `foo-bar`). The value is a function for custom handling that 1432 * accepts the 'e' value, the name of the structure-field, and 1433 * the value of the structure and field. You may also specify an 1434 * array of functions instead of a singleton. These callbacks are 1435 * invoked *after* the generic classes are filled. 1436 */ 1437 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1438 { 1439 if (e !== null) 1440 this._fill(e, this.obj, true, custom); 1441 } 1442 1443 /** 1444 * Like {@link fill} but not including the passed-in element. 1445 * @param e The DOM element. 1446 * @param custom Custom handler dictionary (see {@link fill} for 1447 * details). 1448 */ 1449 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1450 void 1451 { 1452 if (e !== null) 1453 this._fill(e, this.obj, false, custom); 1454 } 1455 1456 /** 1457 * Like {@link fill} but instead of accepting a single element to 1458 * fill, filling into all elements (inclusive) matching the given 1459 * class name beneath (inclusive) the element. 1460 * @param e The DOM element. 1461 * @param name Name of the class to fill. 1462 * @param custom Custom handler dictionary (see {@link fill} for 1463 * details). 1464 */ 1465 fillByClass(e: HTMLElement|null, name: string, 1466 custom?: DataCallbacks|null): void 1467 { 1468 if (e !== null) 1469 this._fillByClass(e, name, true, custom); 1470 } 1471 1472 /** 1473 * Like {@link fillByClass} but not inclusive the root element 1474 * and class matches. 1475 * @param e The DOM element. 1476 * @param name Name of the class to fill. 1477 * @param custom Custom handler dictionary (see {@link fill} for 1478 * details). 1479 */ 1480 fillInnerByClass(e: HTMLElement|null, name: string, 1481 custom?: DataCallbacks|null): void 1482 { 1483 if (e !== null) 1484 this._fillByClass(e, name, false, custom); 1485 } 1486 1487 private _fill(e: HTMLElement, obj: userData|userData[], 1488 inc: boolean, custom?: DataCallbacks|null): 1489 void 1490 { 1491 if (obj instanceof Array && obj.length === 0) 1492 return; 1493 const o: userData = 1494 (obj instanceof Array) ? obj[0] : obj; 1495 if (typeof custom === 'undefined') 1496 custom = null; 1497 _fillField(e, 'user', 'company', custom, 1498 o.company, inc, false, 1499 new company(o.company)); 1500 _fillField(e, 'user', 'cid', custom, o.cid, inc, 1501 false,null); 1502 _fillField(e, 'user', 'sex', custom, o.sex, inc, 1503 false,null); 1504 _fillField(e, 'user', 'email', custom, 1505 o.email, inc, false,null); 1506 _fillField(e, 'user', 'image', custom, 1507 o.image, inc, true,null); 1508 _fillField(e, 'user', 'name', custom, 1509 o.name, inc, false,null); 1510 _fillField(e, 'user', 'uid', custom, o.uid, inc, 1511 false,null); 1512 if (custom !== null && 1513 typeof custom['user'] !== 'undefined') { 1514 if (custom['user'] instanceof Array) { 1515 let i: number; 1516 for (i = 0; i < custom['user'].length; i++) 1517 custom['user'][i](e, 'user', o); 1518 } else 1519 custom['user'](e, 'user', o); 1520 } 1521 } 1522 1523 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1524 custom?: DataCallbacks|null): void 1525 { 1526 let i: number; 1527 const list: HTMLElement[] = 1528 _elemList(e, name, inc); 1529 for (i = 0; i < list.length; i++) 1530 this._fill(list[i], this.obj, inc, custom); 1531 } 1532 1533 /** 1534 * Like {@link fillArray}, but hiding an element if the array is 1535 * empty or null. 1536 * @param e The DOM element. 1537 * @param tohide DOM element to hide. 1538 * @param o The array (or object) to fill. 1539 * @param custom Custom handler dictionary (see {@link fill}). 1540 */ 1541 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1542 custom?: DataCallbacks|null): void 1543 { 1544 let len: number; 1545 if (null === this.obj) 1546 len = 0; 1547 else if (this.obj instanceof Array) 1548 len = this.obj.length; 1549 else 1550 len = 1; 1551 if (null !== e) 1552 _hide(e); 1553 if (null !== tohide) 1554 _show(tohide); 1555 this.fillArray(e, custom); 1556 if (null !== tohide && 0 === len) 1557 _hide(tohide); 1558 } 1559 1560 /** 1561 * Like {@link fillArray}, but showing an element if the array is 1562 * empty or null. 1563 * @param e The DOM element. 1564 * @param toshow The DOM element to show. 1565 * @param o The array or object to fill. 1566 * @param custom Custom handler dictionary (see {@link fill}). 1567 */ 1568 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1569 custom?: DataCallbacks|null): void 1570 { 1571 let len: number; 1572 if (null === this.obj) 1573 len = 0; 1574 else if (this.obj instanceof Array) 1575 len = this.obj.length; 1576 else 1577 len = 1; 1578 if (null !== e) 1579 _hide(e); 1580 if (null !== toshow) 1581 _hide(toshow); 1582 this.fillArray(e, custom); 1583 if (null !== toshow && 0 === len) 1584 _show(toshow); 1585 } 1586 1587 /** 1588 * Like {@link fill} but for an array. If the data is not an 1589 * array, it is remapped as an array of one. This will save the 1590 * first element within 'e', remove all children of 'e', then 1591 * repeatedly clone the saved element and re-append it, filling 1592 * in the cloned subtree with the array (inclusive of the subtree 1593 * root). If the input array is empty or null, 'e' is hidden by 1594 * using the *hide* class. Otherwise, the *hide* class is 1595 * removed. 1596 * @param e The DOM element. 1597 * @param custom Custom handler dictionary (see {@link fill}). 1598 */ 1599 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1600 void 1601 { 1602 let i: number; 1603 const o: userData[] = 1604 (this.obj instanceof Array) ? 1605 this.obj : [this.obj]; 1606 1607 if (e === null || e.children.length === 0) 1608 return; 1609 _hide(e); 1610 if (o.length === 0 || this.obj === null) 1611 return; 1612 _show(e); 1613 1614 const row: HTMLElement = 1615 <HTMLElement>e.children[0]; 1616 while (e.firstChild !== null) 1617 e.removeChild(e.firstChild) 1618 for (i = 0; i < o.length; i++) { 1619 const cln: HTMLElement = 1620 <HTMLElement>row.cloneNode(true); 1621 e.appendChild(cln); 1622 this._fill(cln, o[i], true, custom); 1623 } 1624 } 1625 1626 /** 1627 * Like {@link fillArray} but instead of accepting a single 1628 * element to fill, filling all elements by class name beneath 1629 * the given root (non-inclusive). 1630 * @param e The DOM element. 1631 * @param name Name of the class to fill. 1632 * @param custom Custom handler dictionary (see {@link fill} for 1633 * details). 1634 */ 1635 fillArrayByClass(e: HTMLElement|null, name: string, 1636 custom?: DataCallbacks|null): void 1637 { 1638 let i: number; 1639 const list: HTMLElement[] = 1640 _elemList(e, name, false); 1641 for (i = 0; i < list.length; i++) 1642 this.fillArray(list[i], custom); 1643 } 1644 1645 } 1646 1647 /** 1648 * Writes {@link sessionData} into a DOM tree. 1649 */ 1650 export class session { 1651 readonly obj: sessionData|sessionData[]; 1652 /** 1653 * @param obj The object(s) to write. 1654 */ 1655 constructor(o: sessionData|sessionData[]) 1656 { 1657 this.obj = o; 1658 } 1659 1660 /** 1661 * Writes {@link sessionData} into the given element. If 1662 * constructed with an array, the first element is used. 1663 * Elements within (and including) the element having the 1664 * following classes are manipulated as follows: 1665 * 1666 * - `session-user-obj`: invoke {@link user#fillInner} with 1667 * **user** data 1668 * - `session-userid-enum-select`: sets or unsets the `selected` 1669 * attribute for non-inclusive descendent `<option>` elements 1670 * depending on whether the value matches 1671 * - `session-userid-value-checked`: sets or unsets the `checked` 1672 * attribute depending on whether the value matches 1673 * - `session-userid-text`: replace contents with **userid** data 1674 * - `session-userid-value`: replace `value` attribute with 1675 * **userid** data 1676 * - `session-token-enum-select`: sets or unsets the `selected` 1677 * attribute for non-inclusive descendent `<option>` elements 1678 * depending on whether the value matches 1679 * - `session-token-value-checked`: sets or unsets the `checked` 1680 * attribute depending on whether the value matches 1681 * - `session-token-text`: replace contents with **token** data 1682 * - `session-token-value`: replace `value` attribute with 1683 * **token** data 1684 * - `session-mtime-enum-select`: sets or unsets the `selected` 1685 * attribute for non-inclusive descendent `<option>` elements 1686 * depending on whether the value matches 1687 * - `session-mtime-value-checked`: sets or unsets the `checked` 1688 * attribute depending on whether the value matches 1689 * - `session-mtime-text`: replace contents with **mtime** data 1690 * - `session-mtime-value`: replace `value` attribute with 1691 * **mtime** data 1692 * - `session-mtime-date-value`: set the element's `value` to the 1693 * ISO-8601 date format of the data 1694 * - `session-mtime-date-text`: like `session-mtime-date-value`, 1695 * but replacing textual content 1696 * - `session-id-enum-select`: sets or unsets the `selected` 1697 * attribute for non-inclusive descendent `<option>` elements 1698 * depending on whether the value matches 1699 * - `session-id-value-checked`: sets or unsets the `checked` 1700 * attribute depending on whether the value matches 1701 * - `session-id-text`: replace contents with **id** data 1702 * - `session-id-value`: replace `value` attribute with **id** 1703 * data 1704 * 1705 * @param e The DOM element. 1706 * @param custom The dictionary of functions keyed by structure 1707 * and field name (e.g., *foo** structure, **bar** field would be 1708 * `foo-bar`). The value is a function for custom handling that 1709 * accepts the 'e' value, the name of the structure-field, and 1710 * the value of the structure and field. You may also specify an 1711 * array of functions instead of a singleton. These callbacks are 1712 * invoked *after* the generic classes are filled. 1713 */ 1714 fill(e: HTMLElement|null, custom?: DataCallbacks|null): void 1715 { 1716 if (e !== null) 1717 this._fill(e, this.obj, true, custom); 1718 } 1719 1720 /** 1721 * Like {@link fill} but not including the passed-in element. 1722 * @param e The DOM element. 1723 * @param custom Custom handler dictionary (see {@link fill} for 1724 * details). 1725 */ 1726 fillInner(e: HTMLElement|null, custom?: DataCallbacks|null): 1727 void 1728 { 1729 if (e !== null) 1730 this._fill(e, this.obj, false, custom); 1731 } 1732 1733 /** 1734 * Like {@link fill} but instead of accepting a single element to 1735 * fill, filling into all elements (inclusive) matching the given 1736 * class name beneath (inclusive) the element. 1737 * @param e The DOM element. 1738 * @param name Name of the class to fill. 1739 * @param custom Custom handler dictionary (see {@link fill} for 1740 * details). 1741 */ 1742 fillByClass(e: HTMLElement|null, name: string, 1743 custom?: DataCallbacks|null): void 1744 { 1745 if (e !== null) 1746 this._fillByClass(e, name, true, custom); 1747 } 1748 1749 /** 1750 * Like {@link fillByClass} but not inclusive the root element 1751 * and class matches. 1752 * @param e The DOM element. 1753 * @param name Name of the class to fill. 1754 * @param custom Custom handler dictionary (see {@link fill} for 1755 * details). 1756 */ 1757 fillInnerByClass(e: HTMLElement|null, name: string, 1758 custom?: DataCallbacks|null): void 1759 { 1760 if (e !== null) 1761 this._fillByClass(e, name, false, custom); 1762 } 1763 1764 private _fill(e: HTMLElement, obj: sessionData|sessionData[], 1765 inc: boolean, custom?: DataCallbacks|null): 1766 void 1767 { 1768 if (obj instanceof Array && obj.length === 0) 1769 return; 1770 const o: sessionData = 1771 (obj instanceof Array) ? obj[0] : obj; 1772 if (typeof custom === 'undefined') 1773 custom = null; 1774 _fillField(e, 'session', 'user', custom, 1775 o.user, inc, false, 1776 new user(o.user)); 1777 _fillField(e, 'session', 'userid', custom, 1778 o.userid, inc, false,null); 1779 _fillField(e, 'session', 'token', custom, 1780 o.token, inc, false,null); 1781 _fillField(e, 'session', 'mtime', custom, 1782 o.mtime, inc, false,null); 1783 _fillDateValue(e, 'session-mtime', o.mtime, inc); 1784 _fillField(e, 'session', 'id', custom, o.id, inc, 1785 false,null); 1786 if (custom !== null && 1787 typeof custom['session'] !== 'undefined') { 1788 if (custom['session'] instanceof Array) { 1789 let i: number; 1790 for (i = 0; i < custom['session'].length; i++) 1791 custom['session'][i](e, 'session', o); 1792 } else 1793 custom['session'](e, 'session', o); 1794 } 1795 } 1796 1797 private _fillByClass(e: HTMLElement, name: string, inc: boolean, 1798 custom?: DataCallbacks|null): void 1799 { 1800 let i: number; 1801 const list: HTMLElement[] = 1802 _elemList(e, name, inc); 1803 for (i = 0; i < list.length; i++) 1804 this._fill(list[i], this.obj, inc, custom); 1805 } 1806 1807 /** 1808 * Like {@link fillArray}, but hiding an element if the array is 1809 * empty or null. 1810 * @param e The DOM element. 1811 * @param tohide DOM element to hide. 1812 * @param o The array (or object) to fill. 1813 * @param custom Custom handler dictionary (see {@link fill}). 1814 */ 1815 fillArrayOrHide(e: HTMLElement|null, tohide: HTMLElement|null, 1816 custom?: DataCallbacks|null): void 1817 { 1818 let len: number; 1819 if (null === this.obj) 1820 len = 0; 1821 else if (this.obj instanceof Array) 1822 len = this.obj.length; 1823 else 1824 len = 1; 1825 if (null !== e) 1826 _hide(e); 1827 if (null !== tohide) 1828 _show(tohide); 1829 this.fillArray(e, custom); 1830 if (null !== tohide && 0 === len) 1831 _hide(tohide); 1832 } 1833 1834 /** 1835 * Like {@link fillArray}, but showing an element if the array is 1836 * empty or null. 1837 * @param e The DOM element. 1838 * @param toshow The DOM element to show. 1839 * @param o The array or object to fill. 1840 * @param custom Custom handler dictionary (see {@link fill}). 1841 */ 1842 fillArrayOrShow(e: HTMLElement|null, toshow: HTMLElement|null, 1843 custom?: DataCallbacks|null): void 1844 { 1845 let len: number; 1846 if (null === this.obj) 1847 len = 0; 1848 else if (this.obj instanceof Array) 1849 len = this.obj.length; 1850 else 1851 len = 1; 1852 if (null !== e) 1853 _hide(e); 1854 if (null !== toshow) 1855 _hide(toshow); 1856 this.fillArray(e, custom); 1857 if (null !== toshow && 0 === len) 1858 _show(toshow); 1859 } 1860 1861 /** 1862 * Like {@link fill} but for an array. If the data is not an 1863 * array, it is remapped as an array of one. This will save the 1864 * first element within 'e', remove all children of 'e', then 1865 * repeatedly clone the saved element and re-append it, filling 1866 * in the cloned subtree with the array (inclusive of the subtree 1867 * root). If the input array is empty or null, 'e' is hidden by 1868 * using the *hide* class. Otherwise, the *hide* class is 1869 * removed. 1870 * @param e The DOM element. 1871 * @param custom Custom handler dictionary (see {@link fill}). 1872 */ 1873 fillArray(e: HTMLElement|null, custom?: DataCallbacks|null): 1874 void 1875 { 1876 let i: number; 1877 const o: sessionData[] = 1878 (this.obj instanceof Array) ? 1879 this.obj : [this.obj]; 1880 1881 if (e === null || e.children.length === 0) 1882 return; 1883 _hide(e); 1884 if (o.length === 0 || this.obj === null) 1885 return; 1886 _show(e); 1887 1888 const row: HTMLElement = 1889 <HTMLElement>e.children[0]; 1890 while (e.firstChild !== null) 1891 e.removeChild(e.firstChild) 1892 for (i = 0; i < o.length; i++) { 1893 const cln: HTMLElement = 1894 <HTMLElement>row.cloneNode(true); 1895 e.appendChild(cln); 1896 this._fill(cln, o[i], true, custom); 1897 } 1898 } 1899 1900 /** 1901 * Like {@link fillArray} but instead of accepting a single 1902 * element to fill, filling all elements by class name beneath 1903 * the given root (non-inclusive). 1904 * @param e The DOM element. 1905 * @param name Name of the class to fill. 1906 * @param custom Custom handler dictionary (see {@link fill} for 1907 * details). 1908 */ 1909 fillArrayByClass(e: HTMLElement|null, name: string, 1910 custom?: DataCallbacks|null): void 1911 { 1912 let i: number; 1913 const list: HTMLElement[] = 1914 _elemList(e, name, false); 1915 for (i = 0; i < list.length; i++) 1916 this.fillArray(list[i], custom); 1917 } 1918 1919 } 1920 1921 /** 1922 * Birthsex of individual 1923 */ 1924 export class sex { 1925 /** 1926 * Male 1927 */ 1928 static readonly male: string = '0'; 1929 /** 1930 * Female 1931 */ 1932 static readonly female: string = '1'; 1933 /** 1934 * Other 1935 */ 1936 static readonly other: string = '2'; 1937 /** 1938 * Uses the enumeration item's **jslabel** (or an empty string if 1939 * no **jslabel** is defined or there is no matching item for the 1940 * value) to format a custom label. This will act on 1941 * *xxx-yyy-label* classes, where *xxx* is the structure name and 1942 * *yyy* is the field name. 1943 * A null value is represented by the **isnull** labels (the 1944 * `ort-null` class is also appended in this case) 1945 * @param e The DOM element. 1946 * @param name If non-null, data is written to elements under the 1947 * root with the given class name. If null, data is written 1948 * directly into the DOM element. 1949 * @param v The enumeration value. 1950 */ 1951 static format(e: HTMLElement, name: string|null, 1952 v: string|number|null): void 1953 { 1954 let s: string; 1955 if (name !== null) 1956 name += '-label'; 1957 if (v === null && name !== null) { 1958 _classaddcl(e, name, 'ort-null', false); 1959 _replcllang(e, name, {_default: ''}); 1960 return; 1961 } else if (v === null) { 1962 _classadd(e, 'ort-null'); 1963 _repllang(e, {_default: ''}); 1964 return; 1965 } 1966 switch(v.toString()) { 1967 case sex.male: 1968 s = _strlang({_default: 'male'}); 1969 break; 1970 case sex.female: 1971 s = _strlang({_default: 'female'}); 1972 break; 1973 case sex.other: 1974 s = _strlang({_default: 'other'}); 1975 break; 1976 default: 1977 s = ''; 1978 break; 1979 } 1980 if (name !== null) 1981 _replcl(e, name, s, false); 1982 else 1983 _repl(e, s); 1984 } 1985 } 1986 1987 }