Skip to content

Commit 2114992

Browse files
authored
Merge pull request #292 from plausible/select_all_toggle
GITHUB-170 Added: Select All toggles to toggle lists.
2 parents c86eb05 + a49e918 commit 2114992

3 files changed

Lines changed: 199 additions & 11 deletions

File tree

assets/src/js/admin/main.js

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ document.addEventListener('DOMContentLoaded', () => {
6666
* event.
6767
*/
6868
document.addEventListener('click', this.toggleOption);
69+
/**
70+
* Select All/None toggles.
71+
*/
72+
document.addEventListener('click', this.bulkToggle);
6973

7074
if (this.stepElems.length > 0) {
7175
for (let i = 0; i < this.stepElems.length; i++) {
@@ -86,7 +90,7 @@ document.addEventListener('DOMContentLoaded', () => {
8690
*/
8791
toggleOption: async function (e) {
8892
/**
89-
* Make sure event target is a toggle.
93+
* Make sure the event target is a toggle.
9094
*/
9195
if (e.target.classList === null || !e.target.classList.contains('plausible-analytics-toggle')) {
9296
return;
@@ -123,6 +127,9 @@ document.addEventListener('DOMContentLoaded', () => {
123127
plausible.toggleSection(button.value.replace('-', '_'));
124128
}
125129

130+
const container = button.closest('.plausible-analytics-section');
131+
plausible.syncBulkToggle(container);
132+
126133
const form = new FormData();
127134
form.append('action', 'plausible_analytics_toggle_option');
128135
form.append('option_name', button.name);
@@ -141,6 +148,120 @@ document.addEventListener('DOMContentLoaded', () => {
141148
plausible.maybeDisableOptions(data.capabilities);
142149
},
143150

151+
/**
152+
* Toggles all underlying toggles when the Select All/None toggle is clicked.
153+
*
154+
* @param e
155+
* @returns {Promise<void>}
156+
*/
157+
bulkToggle: async function (e) {
158+
/**
159+
* Make sure the event target is a bulk toggle.
160+
*/
161+
if (e.target.classList === null || !e.target.classList.contains('plausible-analytics-bulk-toggle')) {
162+
return;
163+
}
164+
165+
const button = e.target.closest('button');
166+
const checked = button.dataset.status !== 'on';
167+
const container = button.closest('.plausible-analytics-section');
168+
const toggles = container.querySelectorAll('button.plausible-analytics-toggle');
169+
const options = [];
170+
171+
/**
172+
* Trigger animations for each toggle.
173+
*/
174+
toggles.forEach(function (toggle) {
175+
const span = toggle.querySelector('span');
176+
177+
if (checked) {
178+
toggle.classList.replace('bg-gray-200', 'bg-indigo-600');
179+
span.classList.replace('translate-x-0', 'translate-x-5');
180+
toggle.dataset.status = 'on';
181+
} else {
182+
toggle.classList.replace('bg-indigo-600', 'bg-gray-200');
183+
span.classList.replace('translate-x-5', 'translate-x-0');
184+
toggle.dataset.status = 'off';
185+
}
186+
187+
options.push({
188+
name: toggle.name,
189+
value: toggle.value,
190+
status: checked ? 'on' : '',
191+
});
192+
});
193+
194+
/**
195+
* Toggle collapsable sections.
196+
*/
197+
toggles.forEach(function (toggle) {
198+
if (toggle.dataset.addtlOpts !== '1') {
199+
return;
200+
}
201+
202+
const sectionName = toggle.value.replace('-', '_');
203+
const section = document.getElementById(sectionName + '_content');
204+
205+
if (section === null) {
206+
return;
207+
}
208+
209+
const isHidden = section.classList.contains('hidden');
210+
211+
if (checked && isHidden) {
212+
plausible.toggleSection(sectionName);
213+
} else if (!checked && !isHidden) {
214+
plausible.toggleSection(sectionName);
215+
}
216+
});
217+
218+
button.dataset.status = checked ? 'on' : 'off';
219+
220+
const bulkSpan = button.querySelector('span');
221+
222+
if (checked) {
223+
button.classList.replace('bg-gray-200', 'bg-indigo-600');
224+
bulkSpan.classList.replace('translate-x-0', 'translate-x-5');
225+
} else {
226+
button.classList.replace('bg-indigo-600', 'bg-gray-200');
227+
bulkSpan.classList.replace('translate-x-5', 'translate-x-0');
228+
}
229+
230+
const form = new FormData();
231+
form.append('action', 'plausible_analytics_bulk_toggle');
232+
form.append('options', JSON.stringify(options));
233+
form.append('_nonce', plausible.nonce);
234+
235+
await plausible.ajax(form);
236+
},
237+
238+
/**
239+
* Sets the initial state of the Select All toggle.
240+
*
241+
* @param container
242+
*/
243+
syncBulkToggle: function (container) {
244+
const bulkToggle = container.querySelector('.plausible-analytics-bulk-toggle');
245+
246+
if (bulkToggle === null) {
247+
return;
248+
}
249+
250+
const toggles = container.querySelectorAll('button.plausible-analytics-toggle');
251+
const allOn = Array.from(toggles).every(t => t.dataset.status === 'on');
252+
const bulkSpan = bulkToggle.querySelector('span');
253+
254+
if (allOn) {
255+
bulkToggle.classList.replace('bg-gray-200', 'bg-indigo-600');
256+
bulkSpan.classList.replace('translate-x-0', 'translate-x-5');
257+
bulkToggle.dataset.status = 'on';
258+
} else {
259+
bulkToggle.classList.replace('bg-indigo-600', 'bg-gray-200');
260+
bulkSpan.classList.replace('translate-x-5', 'translate-x-0');
261+
bulkToggle.dataset.status = 'off';
262+
}
263+
},
264+
144265
/**
145266
* Adds an input node.
146267
*

src/Admin/Settings/API.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -597,32 +597,57 @@ public function render_group_field( array $group, $hide_header = false ) {
597597
/**
598598
* if $fields contains more than one checkbox field type, this is a list, which is treated different in @see Ajax::toggle_option()
599599
*/
600-
$is_list = array_filter(
600+
$toggles = array_filter(
601601
$fields,
602602
function ( $field ) {
603603
return $field['type'] === 'checkbox';
604604
}
605605
);
606+
$is_list = count( $toggles ) > 1;
606607
$count = count( $fields );
607608
$half = ceil( $count / 2 );
608609
?>
610+
<?php if ( $is_list ): ?>
611+
<?php
612+
$settings = Helpers::get_settings();
613+
$option_name = $fields[ array_key_first( $fields ) ]['slug'];
614+
$option_values = array_column(
615+
array_filter( $fields, function ( $f ) {
616+
return $f['type'] === 'checkbox';
617+
} ),
618+
'value'
619+
);
620+
$saved_values = $settings[ $option_name ] ?? [];
621+
$all_checked = ! empty( $option_values ) && empty( array_diff( $option_values, (array) $saved_values ) );
622+
?>
623+
<div class="toggle-container flex items-center mt-4 mb-2 space-x-3">
624+
<button class="plausible-analytics-bulk-toggle <?php echo $all_checked ? 'bg-indigo-600' : 'bg-gray-200'; ?> dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2
625+
border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring"
626+
type="checkbox" data-status="<?php echo $all_checked ? 'on' : 'off'; ?>">
627+
<span
628+
class="plausible-analytics-bulk-toggle <?php echo $all_checked ? 'translate-x-5' : 'translate-x-0'; ?> inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800
629+
shadow transform transition-translate ease-in-out duration-200"></span>
630+
</button>
631+
<span class="ml-2 dark:text-gray-100 text-lg"><?php esc_html_e( 'Select all', 'plausible-analytics' ); ?></span>
632+
</div>
633+
<?php endif; ?>
609634
<?php if ( $divide_in_columns && $count > 4 ) : ?>
610-
<div class="grid grid-cols-1 md:grid-cols-2">
635+
<div class="grid grid-cols-1 md:grid-cols-2 !mt-0">
611636
<div>
612637
<?php foreach ( array_slice( $fields, 0, $half ) as $field ) {
613-
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
638+
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
614639
} ?>
615640
</div>
616641
<div>
617642
<?php foreach ( array_slice( $fields, $half ) as $field ) {
618-
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
643+
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
619644
} ?>
620645
</div>
621646
</div>
622647
<?php else : ?>
623-
<div>
648+
<div class="!mt-0">
624649
<?php foreach ( $fields as $field ) {
625-
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, count( $is_list ) > 1 );
650+
echo call_user_func( [ $this, "render_{$field['type']}_field" ], $field, $is_list );
626651
} ?>
627652
</div>
628653
<?php endif; ?>

src/Ajax.php

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ private function init() {
3636
add_action( 'wp_ajax_plausible_analytics_show_wizard', [ $this, 'show_wizard' ] );
3737
add_action( 'wp_ajax_plausible_analytics_toggle_option', [ $this, 'toggle_option' ] );
3838
add_action( 'wp_ajax_plausible_analytics_save_options', [ $this, 'save_options' ] );
39+
add_action( 'wp_ajax_plausible_analytics_bulk_toggle', [ $this, 'bulk_toggle_options' ] );
3940
}
4041

4142
/**
@@ -93,12 +94,12 @@ public function quit_wizard() {
9394
* Clean variables using `sanitize_text_field`.
9495
* Arrays are cleaned recursively. Non-scalar values are ignored.
9596
*
96-
* @param string|array $var Sanitize the variable.
97-
*
98-
* @return string|array
9997
* @since 1.3.0
10098
* @access public
10199
*
100+
* @param string|array $var Sanitize the variable.
101+
*
102+
* @return string|array
102103
*/
103104
private function clean( $var, $key = '' ) {
104105
// If the variable is an array, recursively apply the function to each element of the array.
@@ -188,8 +189,8 @@ public function show_wizard() {
188189
/**
189190
* Save Admin Settings
190191
*
191-
* @return void
192192
* @since 1.0.0
193+
* @return void
193194
*/
194195
public function toggle_option() {
195196
// Sanitize all the post data before using.
@@ -275,6 +276,47 @@ private function maybe_render_additional_message( $option_name, $option_value )
275276
return $additional_message_html;
276277
}
277278

279+
public function bulk_toggle_options() {
280+
$post_data = $this->clean( $_POST );
281+
$settings = Helpers::get_settings();
282+
283+
if ( ! current_user_can( 'manage_options' ) || wp_verify_nonce( $post_data['_nonce'], 'plausible_analytics_toggle_option' ) < 1 ) {
284+
wp_send_json_error( __( 'Not allowed.', 'plausible-analytics' ), 403 );
285+
}
286+
287+
$options = json_decode( $post_data['options'], true );
288+
289+
if ( empty( $options ) ) {
290+
wp_send_json_error( __( 'No options found.', 'plausible-analytics' ), 400 );
291+
}
292+
293+
foreach ( $options as $option ) {
294+
$name = sanitize_text_field( $option['name'] );
295+
$value = sanitize_text_field( $option['value'] );
296+
$status = sanitize_text_field( $option['status'] );
297+
298+
if ( ! isset( $settings[ $name ] ) || ! is_array( $settings[ $name ] ) ) {
299+
continue;
300+
}
301+
302+
if ( $status === 'on' ) {
303+
if ( ! in_array( $value, $settings[ $name ] ) ) {
304+
$settings[ $name ][] = $value;
305+
}
306+
} else {
307+
if ( ( $key = array_search( $value, $settings[ $name ] ) ) !== false ) {
308+
unset( $settings[ $name ][ $key ] );
309+
}
310+
}
311+
}
312+
313+
update_option( 'plausible_analytics_settings', $settings );
314+
315+
Messages::set_success( __( 'Settings saved.', 'plausible-analytics' ) );
316+
317+
wp_send_json_success( null, 200 );
318+
}
319+
278320
/**
279321
* Save Options
280322
*

0 commit comments

Comments
 (0)