|
93 | 93 | }, 50 ); |
94 | 94 | } |
95 | 95 |
|
| 96 | + /** |
| 97 | + * A sibling control, found by its field key. |
| 98 | + * |
| 99 | + * Scoped to the repeater row the caller sits in, then the form, then the |
| 100 | + * document: a repeater has one of each key per row, and the first match |
| 101 | + * in the form would give every row the first row's value. The key is |
| 102 | + * matched as a bare name or as the last segment of a bracketed one, |
| 103 | + * which is how a row's inputs are named. |
| 104 | + * |
| 105 | + * Money reads its currency this way and region its country -- one lookup |
| 106 | + * rather than two copies of the same query, free to drift apart. |
| 107 | + * |
| 108 | + * @param {Element} wrap The control asking. |
| 109 | + * @param {string} key Field key of the sibling. |
| 110 | + * @return {Element|null} The control, if there is one. |
| 111 | + */ |
| 112 | + function sibling( wrap, key ) { |
| 113 | + if ( ! key ) { |
| 114 | + return null; |
| 115 | + } |
| 116 | + |
| 117 | + var scope = wrap.closest( '.field-kit__repeater-row' ) || wrap.closest( 'form' ) || document; |
| 118 | + |
| 119 | + return scope.querySelector( '[name$="[' + key + ']"], [name="' + key + '"]' ); |
| 120 | + } |
| 121 | + |
96 | 122 | /* ==================================================================== |
97 | 123 | * Conditional logic |
98 | 124 | * ================================================================= */ |
|
661 | 687 | } |
662 | 688 | }; |
663 | 689 |
|
664 | | - window.ArrayPressFieldKitModules = { Conditions: Conditions, Range: Range, Toggle: Toggle, Clipboard: Clipboard, CodeGenerator: CodeGenerator, Oembed: Oembed, announce: announce, bound: bound, t: t, config: config }; |
| 690 | + window.ArrayPressFieldKitModules = { Conditions: Conditions, Range: Range, Toggle: Toggle, Clipboard: Clipboard, CodeGenerator: CodeGenerator, Oembed: Oembed, announce: announce, bound: bound, sibling: sibling, t: t, config: config }; |
665 | 691 | } )(); |
666 | 692 |
|
667 | 693 | /** |
|
3694 | 3720 | /** |
3695 | 3721 | * The control holding this amount's currency code. |
3696 | 3722 | * |
3697 | | - * Scoped to the row rather than the document: a repeater has one per |
3698 | | - * row, and the first match in the form would give every row the |
3699 | | - * first row's currency. |
| 3723 | + * The kit's sibling lookup, which is scoped to the row: a repeater |
| 3724 | + * has one per row, and the first match in the form would give every |
| 3725 | + * row the first row's currency. |
3700 | 3726 | * |
3701 | 3727 | * @param {Element} wrap The money field. |
3702 | 3728 | * @return {Element|null} The control, if there is one. |
3703 | 3729 | */ |
3704 | 3730 | source: function ( wrap ) { |
3705 | | - var scope = wrap.closest( '.field-kit__repeater-row' ) || wrap.closest( 'form' ) || document; |
3706 | | - var key = wrap.dataset.currencyKey; |
3707 | | - |
3708 | | - return scope.querySelector( '[name$="[' + key + ']"], [name="' + key + '"]' ); |
| 3731 | + return kit.sibling( wrap, wrap.dataset.currencyKey ); |
3709 | 3732 | }, |
3710 | 3733 |
|
3711 | 3734 | /** |
|
3738 | 3761 |
|
3739 | 3762 | window.ArrayPressFieldKitModules.Money = Money; |
3740 | 3763 |
|
| 3764 | + /* ================================================================= |
| 3765 | + * Region |
| 3766 | + * ================================================================= */ |
| 3767 | + |
| 3768 | + var Region = { |
| 3769 | + |
| 3770 | + /** |
| 3771 | + * Fit each region control to the country of its row. |
| 3772 | + * |
| 3773 | + * PHP drew two controls -- a text input, live, and a select, empty |
| 3774 | + * and disabled -- because it could not know the country at render |
| 3775 | + * time. This asks the server for that country's regions and keeps |
| 3776 | + * whichever control fits: the select where there is a list, the text |
| 3777 | + * input where there is not. Every step is guarded so that a field |
| 3778 | + * with no country beside it, or a request that fails, is left as it |
| 3779 | + * was drawn, with the text input live and submitting. |
| 3780 | + * |
| 3781 | + * @param {Element} root Container. |
| 3782 | + */ |
| 3783 | + init: function ( root ) { |
| 3784 | + root.querySelectorAll( '.field-kit__region[data-country-key]' ).forEach( function ( wrap ) { |
| 3785 | + if ( wrap.dataset.fkBound ) { |
| 3786 | + return; |
| 3787 | + } |
| 3788 | + |
| 3789 | + wrap.dataset.fkBound = '1'; |
| 3790 | + |
| 3791 | + var source = kit.sibling( wrap, wrap.dataset.countryKey ); |
| 3792 | + |
| 3793 | + if ( ! source ) { |
| 3794 | + return; |
| 3795 | + } |
| 3796 | + |
| 3797 | + Region.load( wrap, source.value ); |
| 3798 | + |
| 3799 | + source.addEventListener( 'change', function () { |
| 3800 | + Region.load( wrap, source.value ); |
| 3801 | + } ); |
| 3802 | + } ); |
| 3803 | + }, |
| 3804 | + |
| 3805 | + /** |
| 3806 | + * Fetch a country's regions and fit the control to them. |
| 3807 | + * |
| 3808 | + * Numbered, as the combobox numbers its searches: a slow answer for |
| 3809 | + * the previous country must not land after the answer for this one |
| 3810 | + * and put the wrong list in the select. |
| 3811 | + * |
| 3812 | + * @param {Element} wrap The region field. |
| 3813 | + * @param {string} country The country code. |
| 3814 | + */ |
| 3815 | + load: function ( wrap, country ) { |
| 3816 | + var endpoint = wrap.dataset.searchEndpoint || ( config.restUrl ? config.restUrl + 'search' : '' ); |
| 3817 | + var ticket; |
| 3818 | + var url; |
| 3819 | + |
| 3820 | + wrap.fieldKitTicket = ( wrap.fieldKitTicket || 0 ) + 1; |
| 3821 | + ticket = wrap.fieldKitTicket; |
| 3822 | + |
| 3823 | + if ( ! country || ! endpoint || typeof fetch !== 'function' ) { |
| 3824 | + Region.apply( wrap, [] ); |
| 3825 | + |
| 3826 | + return; |
| 3827 | + } |
| 3828 | + |
| 3829 | + try { |
| 3830 | + url = new URL( endpoint, window.location.origin ); |
| 3831 | + } catch ( error ) { |
| 3832 | + Region.apply( wrap, [] ); |
| 3833 | + |
| 3834 | + return; |
| 3835 | + } |
| 3836 | + |
| 3837 | + url.searchParams.set( 'source', 'region' ); |
| 3838 | + url.searchParams.set( 'args[country]', country ); |
| 3839 | + |
| 3840 | + fetch( url, { |
| 3841 | + credentials: 'same-origin', |
| 3842 | + headers: { 'X-WP-Nonce': wrap.dataset.searchNonce || config.restNonce || '' } |
| 3843 | + } ).then( function ( response ) { |
| 3844 | + return response.ok ? response.json() : { results: [] }; |
| 3845 | + } ).then( function ( data ) { |
| 3846 | + if ( ticket === wrap.fieldKitTicket ) { |
| 3847 | + Region.apply( wrap, data.results || [] ); |
| 3848 | + } |
| 3849 | + } ).catch( function () { |
| 3850 | + if ( ticket === wrap.fieldKitTicket ) { |
| 3851 | + Region.apply( wrap, [] ); |
| 3852 | + } |
| 3853 | + } ); |
| 3854 | + }, |
| 3855 | + |
| 3856 | + /** |
| 3857 | + * Make one control live and the other inert. |
| 3858 | + * |
| 3859 | + * Only one may submit, so the one standing down is disabled as well |
| 3860 | + * as hidden: a hidden control still posts. Nothing is announced. A |
| 3861 | + * select appearing where a text box was is a change of control, not |
| 3862 | + * of state, and the label reads the same either way. |
| 3863 | + * |
| 3864 | + * @param {Element} wrap The region field. |
| 3865 | + * @param {Array} regions Rows of {id, text}; empty for a country without any. |
| 3866 | + */ |
| 3867 | + apply: function ( wrap, regions ) { |
| 3868 | + var select = wrap.querySelector( '.field-kit__region-select' ); |
| 3869 | + var text = wrap.querySelector( '.field-kit__region-text' ); |
| 3870 | + |
| 3871 | + if ( ! select || ! text ) { |
| 3872 | + return; |
| 3873 | + } |
| 3874 | + |
| 3875 | + // Whichever control is live holds the current value: the stored |
| 3876 | + // one from the text input before the first swap, the chosen one |
| 3877 | + // from the select after it. |
| 3878 | + var current = select.disabled ? text.value : select.value; |
| 3879 | + |
| 3880 | + select.textContent = ''; |
| 3881 | + |
| 3882 | + if ( ! regions.length ) { |
| 3883 | + select.disabled = true; |
| 3884 | + select.hidden = true; |
| 3885 | + text.disabled = false; |
| 3886 | + text.hidden = false; |
| 3887 | + |
| 3888 | + return; |
| 3889 | + } |
| 3890 | + |
| 3891 | + if ( ! select.hasAttribute( 'required' ) ) { |
| 3892 | + select.appendChild( Region.option( '', select.dataset.placeholder || t( 'selectRegion', 'Select a region' ) ) ); |
| 3893 | + } |
| 3894 | + |
| 3895 | + regions.forEach( function ( region ) { |
| 3896 | + var option = Region.option( String( region.id ), String( region.text ) ); |
| 3897 | + |
| 3898 | + if ( option.value === current ) { |
| 3899 | + option.selected = true; |
| 3900 | + } |
| 3901 | + |
| 3902 | + select.appendChild( option ); |
| 3903 | + } ); |
| 3904 | + |
| 3905 | + text.disabled = true; |
| 3906 | + text.hidden = true; |
| 3907 | + select.disabled = false; |
| 3908 | + select.hidden = false; |
| 3909 | + }, |
| 3910 | + |
| 3911 | + /** |
| 3912 | + * One option. |
| 3913 | + * |
| 3914 | + * @param {string} value Its value. |
| 3915 | + * @param {string} label Its text. |
| 3916 | + * @return {Element} The option. |
| 3917 | + */ |
| 3918 | + option: function ( value, label ) { |
| 3919 | + var option = document.createElement( 'option' ); |
| 3920 | + |
| 3921 | + option.value = value; |
| 3922 | + option.textContent = label; |
| 3923 | + |
| 3924 | + return option; |
| 3925 | + } |
| 3926 | + }; |
| 3927 | + |
| 3928 | + window.ArrayPressFieldKitModules.Region = Region; |
| 3929 | + |
3741 | 3930 | /* ==================================================================== |
3742 | 3931 | * Password reveal |
3743 | 3932 | * ================================================================= */ |
|
3901 | 4090 | function init( root ) { |
3902 | 4091 | root = root || document; |
3903 | 4092 |
|
3904 | | - [ 'Conditions', 'Range', 'Toggle', 'Clipboard', 'CodeGenerator', 'Oembed', 'Combobox', 'Reorder', 'Gallery', 'Repeater', 'Media', 'Tags', 'CodeEditor', 'ColorPicker', 'TagModal', 'PanelTabs', 'EmailPanel', 'ActionButton', 'Tooltip', 'IconPreview', 'Providers', 'Money', 'PasswordReveal', 'Counter' ].forEach( function ( name ) { |
| 4093 | + [ 'Conditions', 'Range', 'Toggle', 'Clipboard', 'CodeGenerator', 'Oembed', 'Combobox', 'Reorder', 'Gallery', 'Repeater', 'Media', 'Tags', 'CodeEditor', 'ColorPicker', 'TagModal', 'PanelTabs', 'EmailPanel', 'ActionButton', 'Tooltip', 'IconPreview', 'Providers', 'Money', 'PasswordReveal', 'Counter', 'Region' ].forEach( function ( name ) { |
3905 | 4094 | var module = window.ArrayPressFieldKitModules[ name ]; |
3906 | 4095 |
|
3907 | 4096 | if ( module && typeof module.init === 'function' ) { |
|
0 commit comments