Jonathan Bennett

Turbo-Boost Your Toggle Updates

Currently, adding a source element to a page refreshes all scopes indiscriminately. While functional, this approach can be optimized to update only the necessary scopes:

// app/javascript/controllers/user_toggle_controller.js
export default class extends Controller {
	// …
	
	sourceTargetConnected(target) {
		// values should always be lists so wrap them if not
		const scope = target.dataset.userToggleScope
		this.toggleValues[scope] = [].concat(
		  JSON.parse(target.dataset.userToggleValues)
		)
		
		if (this.autoRemoveValue) target.remove
		
		// this.updateToggles()
		this.updateScope(scope)
	}
	
	// updateToggles() {
	// 	Object.keys(this.toggleValues).forEach((scope) => {
	// 	this.toggleTargets
	// 		.filter((toggle) => toggle.dataset.userToggleScope === scope)
	// 		.forEach((toggle) => {
	// 		const value = JSON.parse(toggle.dataset.userToggleShowIf)
	// 		const show = this.toggleValues[scope].indexOf(value) !== -1
	// 		toggle.classList.toggle("hidden", !show)
	// 		})
	// 	})
	// }
	
	updateScopes = () => {
		Object.keys(this.toggleValues).forEach(this.updateScope)
	}
	
	updateScope = (scope) => {
		this.toggleTargets
			.filter((toggle) => toggle.dataset.userToggleScope === scope)
			.forEach(this.updateToggle)
	}
	
	updateToggle = (toggle) => {
		const scope = toggle.dataset.userToggleScope
		const value = JSON.parse(toggle.dataset.userToggleShowIf)
		const show = this.toggleValues[scope].indexOf(value) !== -1
		toggle.classList.toggle("hidden", !show)
	}
}

One key adjustment here is the use of “fat arrow syntax (() => {})” for the update* functions. This ensures this is correctly bound to the controller, whether the functions are executed in loops or called directly.

We also need to handle dynamically added toggle targets, ensuring they update appropriately:

// app/javascript/controllers/user_toggle_controller.js
export default class extends Controller {
	// …
	
	sourceTargetConnected(target) {
		// –
	}

	toggleTargetConnected(target) {
		this.updateToggle(target)
	}
}

Finally, with this setup, dynamically added sources and toggles are fully supported, even when introduced via Turbo Stream responses:

<%# app/views/chats/update.turbo_stream.erb %>
<%= turbo_stream_append "my_dom_id" do %>
	<%= user_toggle_source(:chat_ids, Current.user.chat_ids) %>
	<%= user_toggle_toggle(:chat_ids, @chat.id) do %>
		<%# render stuff %>
	<% end %>
<% end %>

This implementation ensures updates occur efficiently and correctly, streamlining the user experience without requiring unnecessary page refreshes or manual interactions.