|
3 | 3 | *
|
4 | 4 | * Two important concepts are used:
|
5 | 5 | * 1. The first XLST-added repeat view is cloned to serve as a template of that repeat.
|
6 |
| - * 2. Each repeat series has a sibling .or-repeat-info element that stores info that is relevant to that series. |
| 6 | + * 2. Each repeat series has a sibling .or-repeat-info element that stores info that is relevant to that series. (More details below) |
7 | 7 | *
|
8 | 8 | * Note that with nested repeats you may have many more series of repeats than templates, because a nested repeat
|
9 | 9 | * may have multiple series.
|
10 | 10 | *
|
11 | 11 | * @module repeat
|
12 | 12 | */
|
13 | 13 |
|
| 14 | +/** |
| 15 | + * "Repeat info" elements are used to convey and/or contain several sorts of |
| 16 | + * metadata, optimization-focused state, and interactive behavior. The following |
| 17 | + * should be regarded as non-exhaustive, but the intent is to update it as any |
| 18 | + * further usage becomes known. |
| 19 | + * |
| 20 | + * Metadata: |
| 21 | + * |
| 22 | + * - The "repeat info" element itself serves as a footer for a series of zero or |
| 23 | + * more repeat instances in the view, i.e. a marker designating where a given |
| 24 | + * series of repeat instances *does or may* precede. |
| 25 | + * |
| 26 | + * ```html |
| 27 | + * <section class="or-repeat" name="/root/repeat-name">...</section> |
| 28 | + * <div class="or-repeat" data-name="/root/repeat-name">...</div> |
| 29 | + * ``` |
| 30 | + * |
| 31 | + * This element is produced by Enketo Transformer. |
| 32 | + * |
| 33 | + * - Its `data-name` attribute is the nodeset referenced by its associated |
| 34 | + * repeat instances (if any). |
| 35 | + * |
| 36 | + * This attribute is produced by Enketo Transformer. |
| 37 | + * |
| 38 | + * - Its `data-repeat-count` attribute is the repeat's `jr:count` expression, if |
| 39 | + * defined in the corresponding XForm. |
| 40 | + * |
| 41 | + * ```html |
| 42 | + * <div class="or-repeat" data-repeat-count="/data/repeat-count" ...> |
| 43 | + * ``` |
| 44 | + * |
| 45 | + * This attribute is produced by Enketo Transformer. |
| 46 | + * |
| 47 | + * - Its `data-repeat-fixed` attribute, if defined in the corresponding XForm |
| 48 | + * with `jr:noAddRemove="true()"`. |
| 49 | + * |
| 50 | + * ```html |
| 51 | + * <div class="or-repeat" data-repeat-fixed ...> |
| 52 | + * ``` |
| 53 | + * |
| 54 | + * This attribute is produced by Enketo Transformer. |
| 55 | + * |
| 56 | + * Optimization-focused state: |
| 57 | + * |
| 58 | + * - "Shared", "static" itemsets—when rendered as `datalist`s—along with their |
| 59 | + * associated translation definitions, and the current state of their |
| 60 | + * translated label elements. A minimal (seriously!) example: |
| 61 | + * |
| 62 | + * ```html |
| 63 | + * <div class="repeat-shared-datalist-itemset-elements" style="display: none;"> |
| 64 | + * <datalist id="datarepitem0" data-name="/data/rep/item-0" class="repeat-shared-datalist-itemset"> |
| 65 | + * <option class="itemset-template" value="" data-items-path="instance('items-0')/item">...</option> |
| 66 | + * <option value="items-0-0" data-value="items 0 0"></option> |
| 67 | + * </datalist> |
| 68 | + * |
| 69 | + * <span class="or-option-translations" style="display:none;" data-name="/data/rep/item-0"> </span> |
| 70 | + * |
| 71 | + * <span class="itemset-labels" data-value-ref="name" data-label-type="itext" data-label-ref="itextId" data-name="/data/rep/item-0"> |
| 72 | + * <span lang="en" class="option-label active" data-itext-id="items-0-0">items-0-0</span> |
| 73 | + * </span> |
| 74 | + * </div> |
| 75 | + * ``` |
| 76 | + * |
| 77 | + * The child elements are first produced by Enketo Transformer. They are then |
| 78 | + * identified (itemset.js), augmented and reparented (repeat.js) by Enketo |
| 79 | + * Core to the outer element created during form initialization. |
| 80 | + * |
| 81 | + * Interactive behavior: |
| 82 | + * |
| 83 | + * - The button used to add new repeat user-controlled instances (i.e. when |
| 84 | + * instances are not controlled by `jr:count` or `jr:noAddRemove`): |
| 85 | + * |
| 86 | + * ```html |
| 87 | + * <button type="button" class="btn btn-default add-repeat-btn">...</button> |
| 88 | + * ``` |
| 89 | + * |
| 90 | + * This element is created and appended in Enketo Core, with requisite event |
| 91 | + * handler(s) for user interaction when adding repeat instances. |
| 92 | + * |
| 93 | + * Each user-controlled repeat instance's corresponding removal button is |
| 94 | + * contained by its respective repeat instance, under a `.repeat-buttons` |
| 95 | + * element (also added by Enketo Core; no other buttons are added besides the |
| 96 | + * removal button). |
| 97 | + * @typedef {HTMLDivElement} EnketoRepeatInfo |
| 98 | + * @property {`${string}or-repeat-info${string}`} className - This isn't the |
| 99 | + * best! It just ensures `EnketoRepeatInfo` is a distinct type (according to |
| 100 | + * TypeScript and its language server), rather than an indistinguishable alias |
| 101 | + * to `HTMLDivElement`. |
| 102 | + */ |
| 103 | + |
14 | 104 | import $ from 'jquery';
|
15 | 105 | import { t } from 'enketo/translator';
|
16 | 106 | import dialog from 'enketo/dialog';
|
@@ -576,29 +666,59 @@ export default {
|
576 | 666 | if (this.staticLists.includes(id)) {
|
577 | 667 | datalist.remove();
|
578 | 668 | } else {
|
579 |
| - // Let all identical input[list] questions amongst all repeat instances use the same |
580 |
| - // datalist by moving it under repeatInfo. |
581 |
| - // It will survive removal of all repeat instances. |
| 669 | + // Let all identical input[list] questions amongst all |
| 670 | + // repeat instances use the same datalist by moving it |
| 671 | + // under repeatInfo. It will survive removal of all |
| 672 | + // repeat instances. |
| 673 | + |
582 | 674 | const parent = datalist.parentElement;
|
583 | 675 | const { name } = input;
|
584 | 676 |
|
585 | 677 | const dl = parent.querySelector('datalist');
|
586 | 678 | const detachedList = parent.removeChild(dl);
|
587 | 679 | detachedList.setAttribute('data-name', name);
|
588 |
| - repeatInfo.appendChild(detachedList); |
589 | 680 |
|
590 | 681 | const translations = parent.querySelector(
|
591 | 682 | '.or-option-translations'
|
592 | 683 | );
|
593 | 684 | const detachedTranslations =
|
594 | 685 | parent.removeChild(translations);
|
595 | 686 | detachedTranslations.setAttribute('data-name', name);
|
596 |
| - repeatInfo.appendChild(detachedTranslations); |
597 | 687 |
|
598 | 688 | const labels = parent.querySelector('.itemset-labels');
|
599 | 689 | const detachedLabels = parent.removeChild(labels);
|
600 | 690 | detachedLabels.setAttribute('data-name', name);
|
601 |
| - repeatInfo.appendChild(detachedLabels); |
| 691 | + |
| 692 | + // Each of these supporting elements are nested in a |
| 693 | + // containing element, so any subsequent DOM queries for |
| 694 | + // their various sibling elements don't mistakenly match |
| 695 | + // those from a previous itemset in the same repeat. |
| 696 | + const sharedItemsetContainer = |
| 697 | + document.createElement('div'); |
| 698 | + |
| 699 | + sharedItemsetContainer.style.display = 'none'; |
| 700 | + sharedItemsetContainer.append( |
| 701 | + detachedList, |
| 702 | + detachedTranslations, |
| 703 | + detachedLabels |
| 704 | + ); |
| 705 | + repeatInfo.append(sharedItemsetContainer); |
| 706 | + |
| 707 | + // Add explicit class which can be used to determine |
| 708 | + // this condition elsewhere. See its usage and |
| 709 | + // commentary in `itemset.js` |
| 710 | + datalist.classList.add( |
| 711 | + 'repeat-shared-datalist-itemset' |
| 712 | + ); |
| 713 | + // This class currently serves no functional purpose |
| 714 | + // (please do not use it for new functional purposes |
| 715 | + // either). It's included specifically so that the |
| 716 | + // resulting DOM structure has some indication of why |
| 717 | + // it's the way it is, and some way to trace back to |
| 718 | + // this code producing that structure. |
| 719 | + sharedItemsetContainer.classList.add( |
| 720 | + 'repeat-shared-datalist-itemset-elements' |
| 721 | + ); |
602 | 722 |
|
603 | 723 | this.staticLists.push(id);
|
604 | 724 | // input.classList.add( 'shared' );
|
|
0 commit comments