-
-
Save chrise86/9428df53bef1d0c67d1f6ae06707ba7e to your computer and use it in GitHub Desktop.
Stimulus Page Refreshing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%# Create a partial for generating a trigger element to be appended to the container in a turbo_stream %> | |
<% selectors ||= '' %> | |
<% | |
# As a convenience, we can use Array.wrap to turn the selector value into an array if it is not one, then use join(' ') to | |
# create a list that works nicely with the stimulus controller. | |
%> | |
<div data-refresh-target='trigger' data-selctors='<%= Array.wrap(selectors).join(" ") %>' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%# In your layout file(s), add an empty tag that can be appended to by broadcasts, turbo_streams, etc...%> | |
<div id='stimulus-refresher' data-controller='refresher'></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Now you can use broadcasts from turbo_streams as long as the users page is subscribe to a known turbo channel | |
# | |
# | |
user.broadcast_append( | |
target: 'stimulus-refresher', | |
partial: 'stimulus_refresher/trigger', | |
locals: {selectors: ['your', 'ids', 'to', 'refresh']} | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
export default class extends Controller { | |
static get targets() { | |
return ['trigger'] | |
} | |
static get values() { | |
return { | |
selectors: { | |
type: String, | |
default: '' | |
} | |
} | |
} | |
triggerTargetConnected(ele) { | |
this.refresh(ele); | |
ele.remove(); // we don't need this element on the DOM once it has triggered the StimulusRefresh so we can remove it | |
} | |
/** | |
* Refreshes the users current page | |
*/ | |
refresh(element) { | |
const ids = element.dataset.selectors.split(' '); | |
if (ids.length === 0) return; | |
window.StimulusRefresh.refresh(this.selectorsValues.split(' ')); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* If you have the convenience of upgrading to the latest versions of turbo and stimulus, this isn't for you. With Turbo 8 | |
* there is available support for refreshing the page. However, for those of us that are maybe in a place where we can't simply | |
* upgrade to have the latest and greatest this is a simply implementation that allows for a turbo_stream broadcast from the backend | |
* to selectively updated a list of elements on the users page through means of fetching the users existing page. | |
* | |
* This sample code also makes the assumption that the code base does NOT support custom turbo stream actions. Using custom turbo_stream | |
* actions could simplify things significantly, but again, sometimes an upgrade isn't a viable option. If custom actions are | |
* available in the app, you can trim out the partials and the stimulus_refresher controller and from the backend then broadcast | |
* using that custom action. That said, I don't have much experience with custom turbo actions. I seem to remember that custom actions | |
* were not available in broadcasts - but that could be incorrect. | |
**/ | |
export class StimulusRefresh { | |
static start() { | |
// in application.js, import this file and run StimulusRefresh.start() | |
window.StimulusRefresh = new StimulusRefresh(); | |
} | |
constructor () { | |
this.selectors = []; | |
// debouncing the handler to avoid many broadcasts from various services/models forcing many updates to a page at once | |
this.handle = _.debounce(this._handle.bind(this), 500); | |
} | |
/** | |
* Adds the selectors to the current array of selectors to be refreshed and calls the handler | |
**/ | |
refresh(selectors) { | |
selectors = Array.isArray(selectors) ? selectors : [selectors] | |
this.selectors = this.selectors.concat(selectors) | |
this.handle() | |
} | |
/** | |
* Fetches the page content if there are elements on the current page that match the selectors that need to be refreshed | |
**/ | |
_handle() { | |
const elementsToHandle = this.elementsWithSelectors; | |
if (elementsToHandle.length === 0) return; | |
const joiner = window.location.search.length === 0 ? '?' : '&'; | |
// passing some extra params to the backend to let the server know it doesn't need to render the content in a layout | |
const fetchLocation = [window.location.href, 'layoutless=true'].join(joiner) | |
fetch(fetchLocation).then( | |
resp => resp.text() | |
).then(html => { | |
// now that we have the content, parse it and replace the elements that need to be replaced. | |
const page = new DOMParser().parseFromString(html, 'text/html'); | |
let newElement; | |
elementsToHandle.forEach( ({existingElement, selector}) => { | |
newElement = page.getElementById(selector); | |
if (newElement) existingElement.replaceWith(newElement); | |
}) | |
}) | |
} | |
/** | |
* Builds a nested array of selectors and their corresponding element - currently only supporting id selectors but this | |
* could easily be changed to utilize querySelector instead of getElementById. | |
**/ | |
get elementsWithSelectors() { | |
let ret = []; | |
let element; | |
const uniqSelectors = [...new Set(this.selectors)]; | |
this.selectors = []; | |
uniqSelectors.forEach(selector => { | |
element = document.getElementById(selector) | |
if (element) { | |
ret.push({existingElement: element, selector: selector}) | |
} | |
}) | |
return ret | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment