Skip to content

Commit 529b9bc

Browse files
authored
Merge pull request #6125 from WoltLab/6.2-option-handler
Migrate `WCF.Option.Handler` to typescript
2 parents 4eaa3d7 + c19381c commit 529b9bc

24 files changed

+306
-54
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{foreach name=radioButtons from=$selectOptions key=key item=selectOption}
2-
<label><input {if $tpl.foreach.radioButtons.first}id="{$option->optionName}" {/if}type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {@$disableOptions[$key]}]" data-enable-options="[ {@$enableOptions[$key]}]"{/if}> {lang}{@$selectOption}{/lang}</label>
2+
<label><input {if $tpl.foreach.radioButtons.first}id="{$option->optionName}" {/if}type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {$disableOptions[$key]}]" data-enable-options="[ {$enableOptions[$key]}]"{/if}> {lang}{@$selectOption}{/lang}</label>
33
{/foreach}

com.woltlab.wcf/templates/selectOptionType.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<select id="{$option->optionName}" name="values[{$option->optionName}]"{if $option->required} required{/if}{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}>
1+
<select id="{$option->optionName}" name="values[{$option->optionName}]"{if $option->required} required{/if}{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {$disableOptions}]" data-enable-options="[ {$enableOptions}]"{/if}>
22
{if !$allowEmptyValue|empty}<option value="">{lang}wcf.global.noSelection{/lang}</option>{/if}
33
{foreach from=$selectOptions key=key item=selectOption}
44
<option value="{$key}"{if $value == $key} selected{/if}>{lang}{@$selectOption}{/lang}</option>

com.woltlab.wcf/templates/settings.tpl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,4 @@
145145
</div>
146146
</form>
147147

148-
<script data-relocate="true">
149-
$(function() {
150-
new WCF.Option.Handler();
151-
});
152-
</script>
153-
154148
{include file='footer' __disableAds=true}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
<ol class="flexibleButtonGroup optionTypeBoolean">
22
<li>
3-
<input type="radio" id="{$option->optionName}"{if $value == 1} checked{/if} name="values[{$option->optionName}]" value="1"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}>
3+
<input type="radio" id="{$option->optionName}"{if $value == 1} checked{/if} name="values[{$option->optionName}]" value="1"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {$disableOptions}]" data-enable-options="[ {$enableOptions}]"{/if}>
44
<label for="{$option->optionName}" class="green">{icon name='check'} {lang}wcf.acp.option.type.boolean.yes{/lang}</label>
55
</li>
66
<li>
7-
<input type="radio" id="{$option->optionName}_no"{if $value == 0} checked{/if} name="values[{$option->optionName}]" value="0"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}>
7+
<input type="radio" id="{$option->optionName}_no"{if $value == 0} checked{/if} name="values[{$option->optionName}]" value="0"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {$disableOptions}]" data-enable-options="[ {$enableOptions}]"{/if}>
88
<label for="{$option->optionName}_no" class="red">{icon name='xmark'} {lang}wcf.acp.option.type.boolean.no{/lang}</label>
99
</li>
1010
</ol>

com.woltlab.wcf/templates/shared_booleanSearchableOptionType.tpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked{/if}> {lang}wcf.user.option.searchBooleanOption{/lang}</label>
22
<ol class="flexibleButtonGroup optionTypeBoolean">
33
<li>
4-
<input type="radio" id="{$option->optionName}"{if $value == 1} checked{/if} name="values[{$option->optionName}]" value="1"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}{if !$searchOption} disabled{/if}>
4+
<input type="radio" id="{$option->optionName}"{if $value == 1} checked{/if} name="values[{$option->optionName}]" value="1"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {$disableOptions}]" data-enable-options="[ {$enableOptions}]"{/if}{if !$searchOption} disabled{/if}>
55
<label for="{$option->optionName}" class="green">{icon name='check'} {lang}wcf.acp.option.type.boolean.yes{/lang}</label>
66
</li>
77
<li>
8-
<input type="radio" id="{$option->optionName}_no"{if $value == 0} checked{/if} name="values[{$option->optionName}]" value="0"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {@$disableOptions}]" data-enable-options="[ {@$enableOptions}]"{/if}{if !$searchOption} disabled{/if}>
8+
<input type="radio" id="{$option->optionName}_no"{if $value == 0} checked{/if} name="values[{$option->optionName}]" value="0"{if $disableOptions || $enableOptions} class="jsEnablesOptions" data-is-boolean="true" data-disable-options="[ {$disableOptions}]" data-enable-options="[ {$enableOptions}]"{/if}{if !$searchOption} disabled{/if}>
99
<label for="{$option->optionName}_no" class="red">{icon name='xmark'} {lang}wcf.acp.option.type.boolean.no{/lang}</label>
1010
</li>
1111
</ol>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{foreach from=$selectOptions key=key item=selectOption}
2-
<label><input type="checkbox" name="values[{$option->optionName}][]" value="{$key}" {if $key|in_array:$value} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {@$disableOptions[$key]}]" data-enable-options="[ {@$enableOptions[$key]}]"{/if}> {lang}{@$selectOption}{/lang}</label>
2+
<label><input type="checkbox" name="values[{$option->optionName}][]" value="{$key}" {if $key|in_array:$value} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {$disableOptions[$key]}]" data-enable-options="[ {$enableOptions[$key]}]"{/if}> {lang}{@$selectOption}{/lang}</label>
33
{/foreach}

com.woltlab.wcf/templates/shared_radioButtonSearchableOptionType.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked{/if}> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
22
{foreach from=$selectOptions key=key item=selectOption}
3-
<label><input type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {@$disableOptions[$key]}]" data-enable-options="[ {@$enableOptions[$key]}]"{/if}{if !$searchOption} disabled{/if}> {lang}{@$selectOption}{/lang}</label>
3+
<label><input type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {$disableOptions[$key]}]" data-enable-options="[ {$enableOptions[$key]}]"{/if}{if !$searchOption} disabled{/if}> {lang}{@$selectOption}{/lang}</label>
44
{/foreach}
55

66
<script data-relocate="true">

com.woltlab.wcf/templates/userProfileAboutEditable.tpl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,3 @@
2727
<button type="button" class="button buttonPrimary" accesskey="s" data-type="save">{lang}wcf.global.button.save{/lang}</button>
2828
<button type="button" class="button" data-type="restore">{lang}wcf.global.button.cancel{/lang}</button>
2929
</div>
30-
31-
<script data-relocate="true">
32-
$(function() {
33-
new WCF.Option.Handler();
34-
});
35-
</script>

ts/WoltLabSuite/Core/Bootstrap.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ export function setup(options: BoostrapOptions): void {
188188
console.warn("The class `jsImageViewer` is deprecated. Use the attribute `data-fancybox` instead.");
189189
void import("./Component/Image/Viewer").then(({ setupLegacy }) => setupLegacy());
190190
});
191+
whenFirstSeen(".jsEnablesOptions", () => {
192+
void import("./Component/Option/Enable").then(({ setup }) => setup());
193+
});
191194

192195
// Move the reCAPTCHA widget overlay to the `pageOverlayContainer`
193196
// when widget form elements are placed in a dialog.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Enables or disables options based on the value of the element.
3+
*
4+
* @author Olaf Braun
5+
* @copyright 2001-2024 WoltLab GmbH
6+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
7+
* @since 6.2
8+
*/
9+
10+
import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
11+
12+
type SelectOption = {
13+
value: string;
14+
option: string;
15+
};
16+
17+
function change(option: HTMLInputElement | HTMLSelectElement) {
18+
const disableOptions = JSON.parse(option.dataset.disableOptions!);
19+
const enableOptions = JSON.parse(option.dataset.enableOptions!);
20+
21+
if (option instanceof HTMLInputElement) {
22+
switch (option.type) {
23+
case "checkbox":
24+
showOptions(option.checked, disableOptions, enableOptions);
25+
break;
26+
27+
case "radio":
28+
if (option.checked) {
29+
let isActive = true;
30+
if (option.dataset.isBoolean && option.value !== "1") {
31+
isActive = false;
32+
}
33+
showOptions(isActive, disableOptions, enableOptions);
34+
}
35+
break;
36+
}
37+
} else if (option instanceof HTMLSelectElement) {
38+
const value = option.value;
39+
const relevantDisableOptions: string[] = [];
40+
const relevantEnableOptions: string[] = [];
41+
42+
for (const item of disableOptions as SelectOption[]) {
43+
if (item.value === value) {
44+
relevantDisableOptions.push(item.option);
45+
} else {
46+
relevantEnableOptions.push(item.option);
47+
}
48+
}
49+
50+
for (const item of enableOptions as SelectOption[]) {
51+
if (item.value === value) {
52+
relevantEnableOptions.push(item.option);
53+
} else {
54+
relevantDisableOptions.push(item.option);
55+
}
56+
}
57+
58+
showOptions(true, relevantDisableOptions, relevantEnableOptions);
59+
}
60+
}
61+
62+
function showOptions(isActive: boolean, disableOptions: string[], enableOptions: string[]) {
63+
disableOptions.forEach((optionName) => {
64+
getOptionElements(optionName).forEach((element) => {
65+
enableOption(element, !isActive);
66+
});
67+
});
68+
69+
enableOptions.forEach((optionName) => {
70+
getOptionElements(optionName).forEach((element) => {
71+
enableOption(element, isActive);
72+
});
73+
});
74+
}
75+
76+
function enableOption(element: HTMLElement, enable: boolean) {
77+
if (
78+
element instanceof HTMLSelectElement ||
79+
(element instanceof HTMLInputElement &&
80+
(element.type === "checkbox" || element.type === "file" || element.type === "radio"))
81+
) {
82+
if (element instanceof HTMLInputElement && element.type === "radio") {
83+
if (!element.checked) {
84+
element.disabled = !enable;
85+
} else {
86+
// Skip active radio buttons, this preserves the value on submit,
87+
// while the user is still unable to move the selection to the other,
88+
// now disabled options.
89+
}
90+
} else {
91+
element.disabled = !enable;
92+
}
93+
94+
const parentOptionTypeBoolean = element.closest(".optionTypeBoolean");
95+
if (parentOptionTypeBoolean) {
96+
// escape dots so that they are not recognized as class selectors
97+
const elementId = element.id.replace(/\./g, "\\.");
98+
99+
const noElement = document.getElementById(elementId + "_no") as HTMLInputElement;
100+
noElement.disabled = !enable;
101+
102+
const neverElement = document.getElementById(elementId + "_never") as HTMLInputElement;
103+
if (neverElement) {
104+
neverElement.disabled = !enable;
105+
}
106+
}
107+
} else {
108+
if (enable) {
109+
element.removeAttribute("readonly");
110+
} else {
111+
element.setAttribute("readonly", "true");
112+
}
113+
}
114+
115+
if (enable) {
116+
element.closest("dl")?.classList.remove("disabled");
117+
} else {
118+
element.closest("dl")?.classList.add("disabled");
119+
}
120+
}
121+
122+
function getOptionElements(optionName: string): HTMLElement[] {
123+
const optionElement = document.getElementById(optionName);
124+
if (optionElement) {
125+
return [optionElement];
126+
}
127+
128+
const container = document.querySelectorAll<HTMLElement>(`.${optionName}Input > dd`);
129+
130+
return Array.from(container)
131+
.map((element) => {
132+
return Array.from(element.querySelectorAll<HTMLElement>("input, select, textarea"));
133+
})
134+
.flat();
135+
}
136+
137+
export function setup() {
138+
wheneverFirstSeen(".jsEnablesOptions", (element: HTMLInputElement | HTMLSelectElement) => {
139+
change(element);
140+
141+
element.addEventListener("change", () => {
142+
change(element);
143+
});
144+
});
145+
}

0 commit comments

Comments
 (0)