{"id":17974135,"url":"https://github.com/seanpdoyle/turbo_stream_button","last_synced_at":"2025-09-07T12:33:45.646Z","repository":{"id":41110752,"uuid":"508055855","full_name":"seanpdoyle/turbo_stream_button","owner":"seanpdoyle","description":"Harness the power of Turbo Streams to declare click event handlers as a series of HTML mutations.","archived":false,"fork":false,"pushed_at":"2024-10-24T18:42:17.000Z","size":69,"stargazers_count":42,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-01-01T00:11:28.330Z","etag":null,"topics":["hotwire","html","rails","stimulus","turbo"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seanpdoyle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"MIT-LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-06-27T20:41:32.000Z","updated_at":"2024-12-20T21:27:32.000Z","dependencies_parsed_at":"2023-02-09T13:45:59.136Z","dependency_job_id":null,"html_url":"https://github.com/seanpdoyle/turbo_stream_button","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seanpdoyle%2Fturbo_stream_button","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seanpdoyle%2Fturbo_stream_button/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seanpdoyle%2Fturbo_stream_button/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seanpdoyle%2Fturbo_stream_button/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seanpdoyle","download_url":"https://codeload.github.com/seanpdoyle/turbo_stream_button/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232213678,"owners_count":18489569,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hotwire","html","rails","stimulus","turbo"],"created_at":"2024-10-29T17:06:48.884Z","updated_at":"2025-01-02T15:18:24.023Z","avatar_url":"https://github.com/seanpdoyle.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `\u003cbutton\u003e` + `\u003cturbo-stream\u003e`\n\nHarness the power of [Turbo Streams][] to declare `click` event handlers as a\nseries of HTML mutations.\n\nCombine built-in `\u003cbutton\u003e` elements with `\u003cturbo-stream\u003e` elements to\ndeclaratively drive client-side interactions with server-generated HTML.\n\n```html\n\u003cbutton type=\"button\" id=\"call-to-action\"\n        data-controller=\"turbo-stream-button\"\n        data-action=\"click-\u003eturbo-stream-button#evaluate\"\u003e\n  \u003cspan\u003eClick me to insert the template's contents after this button!\u003c/span\u003e\n\n  \u003ctemplate data-turbo-stream-button-target=\"turboStreams\"\u003e\n    \u003cturbo-stream action=\"after\" target=\"call-to-action\"\u003e\n      \u003ctemplate\u003e\u003cp\u003eYou clicked the button!\u003c/p\u003e\u003c/template\u003e\n    \u003c/turbo-stream\u003e\n  \u003c/template\u003e\n\u003c/button\u003e\n```\n\n[Try it out.](https://jsfiddle.net/toybqx89/)\n\n[Turbo Streams]: https://turbo.hotwired.dev/reference/streams\n\n## Usage\n\nIn your JavaScript code, import and register the `turbo-stream-button`\ncontroller with your Stimulus application:\n\n```javascript\nimport \"@hotwired/turbo\"\nimport { Application } from \"stimulus\"\nimport { TurboStreamButtonController } from \"@seanpdoyle/turbo_stream_button\"\n\nconst application = Application.start()\napplication.register(\"turbo-stream-button\", TurboStreamButtonController)\n```\n\nIn your Rails templates, call the `turbo_stream_button_tag` helper or render the\n`turbo_stream_button` view partial to create the `\u003cbutton\u003e` element. The view\npartial renders:\n\n* the block content as the `\u003cbutton\u003e` element's content\n* other keyword arguments as the `\u003cbutton\u003e` element's attributes\n* any content captured by any call to the `#turbo_streams` method invoked on the\n  block's single argument\n\nWhen the button is clicked, the `turbo-stream-button` [Stimulus controller][]\ninvokes the `evaluate` [Action][] to insert the contents of the [`\u003ctemplate\u003e`\nelement][mdn-template], activating any `\u003cturbo-stream\u003e` elements nested inside.\n\n[Stimulus controller]: https://stimulus.hotwired.dev/handbook/hello-stimulus#controllers-bring-html-to-life\n[Action]: https://stimulus.hotwired.dev/handbook/building-something-real#connecting-the-action\n[mdn-template]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template\n\n### Introductory: Hello, world\n\n```html+erb\n\u003c%= turbo_stream_button_tag id: \"the-button\" do |button| %\u003e\n  \u003cspan\u003eClick me to say \"hello\"\u003c/span\u003e\n\n  \u003c% button.turbo_streams do %\u003e\n    \u003c%= turbo_stream.after \"the-button\", \"Hello, world!\" %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n\n\u003c%# =\u003e  \u003cbutton type=\"button\" id=\"the-button\"\n                data-controller=\"turbo-stream-button\"\n                data-action=\"click-\u003eturbo-stream-button#evaluate\"\u003e\n          \u003cspan\u003eClick me to say \"hello\"\u003c/span\u003e\n\n          \u003ctemplate data-turbo-stream-target=\"turboStreams\"\u003e\n            \u003cturbo-stream action=\"after\" target=\"the-button\"\u003e\n              \u003ctemplate\u003eHello, world!\u003c/template\u003e\n            \u003c/turbo-stream\u003e\n          \u003c/template\u003e\n        \u003c/button\u003e %\u003e\n```\n\n### Intermediate: Compose with other Stimulus controller actions\n\n```html+erb\n\u003cscript type=\"module\"\u003e\n  import \"@hotwired/turbo\"\n  import { Application, Controller } from \"stimulus\"\n  import { TurboStreamButtonController } from \"@seanpdoyle/turbo_stream_button\"\n\n  class ClipboardController extends Controller {\n    copy({ target: { value } }) {\n      navigator.clipboard.writeText(value)\n    }\n  }\n\n  const application = Application.start()\n  application.register(\"turbo-stream-button\", TurboStreamButtonController)\n  application.register(\"clipboard\", ClipboardController)\n\u003c/script\u003e\n\n\u003cdiv id=\"flash\" role=\"alert\"\u003e\u003c/div\u003e\n\n\u003c%= turbo_stream_button_tag value: \"invitation-code-abc123\",\n                            data: { controller: \"clipboard\", action: \"click-\u003eclipboard#copy\" } do |button| %\u003e\n  Copy to clipboard\n\n  \u003c% button.turbo_streams do %\u003e\n    \u003cturbo-stream action=\"append\" target=\"flash\"\u003e\n      \u003ctemplate\u003e\n        \u003cp\u003eCopied \"invitation-code-abc123\" to your clipboard!\u003c/p\u003e\n      \u003c/template\u003e\n    \u003c/turbo-stream\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n\n\u003c%# =\u003e  \u003cbutton type=\"button\" value=\"invitation-code-abc123\"\n                data-controller=\"turbo-stream-button clipboard\"\n                data-action=\"click-\u003eturbo-stream-button#evaluate click-\u003eclipboard#copy\"\u003e\n          Copy to clipboard\n\n          \u003ctemplate data-turbo-stream-target=\"turboStreams\"\u003e\n            \u003cturbo-stream action=\"append\" target=\"flash\"\u003e\n              \u003ctemplate\u003e\n                \u003cp\u003eCopied \"invitation-code-abc123\" to your clipboard!\u003c/p\u003e\n              \u003c/template\u003e\n            \u003c/turbo-stream\u003e\n          \u003c/template\u003e\n        \u003c/button\u003e %\u003e\n```\n\n### Advanced: Nest buttons\n\n```html+erb\n\u003cdiv id=\"flash\" role=\"alert\"\u003e\u003c/div\u003e\n\n\u003c%= turbo_stream_button_tag do |button| %\u003e\n  Append flash message\n\n  \u003c% button.turbo_streams do %\u003e\n    \u003cturbo-stream action=\"append\" target=\"flash\"\u003e\n      \u003ctemplate\u003e\n        \u003cdiv id=\"a_flash_message\" role=\"status\"\u003e\n          Hello, world!\n\n          \u003c%= turbo_stream_button_tag do |button| %\u003e\n            Dismiss\n\n            \u003c% button.turbo_streams do %\u003e\n              \u003c%= turbo_stream.remove \"a_flash_message\" %\u003e\n            \u003c% end %\u003e\n          \u003c% end %\u003e\n        \u003c/div\u003e\n      \u003c/template\u003e\n    \u003c/turbo-stream\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n\n\u003c%# =\u003e  \u003cbutton type=\"button\"\n                data-controller=\"turbo-stream-button\"\n                data-action=\"click-\u003eturbo-stream-button#evaluate\"\u003e\n          Append flash message\n\n          \u003ctemplate data-turbo-stream-target=\"turboStreams\"\u003e\n            \u003cturbo-stream action=\"append\" target=\"flash\"\u003e\n              \u003ctemplate\u003e\n                \u003cdiv id=\"a_flash_message\" role=\"status\"\u003e\n                  Hello, world!\n\n                  \u003cbutton type=\"button\"\n                          data-controller=\"turbo-stream-button\"\n                          data-action=\"click-\u003eturbo-stream-button#evaluate\"\u003e\n                    Dismiss\n\n                    \u003ctemplate data-turbo-stream-target=\"turboStreams\"\u003e\n                      \u003cturbo-stream action=\"remove\" target=\"a_flash_message\"\u003e\u003c/turbo-stream\u003e\n                    \u003c/template\u003e\n                  \u003c/button\u003e\n                \u003c/div\u003e\n              \u003c/template\u003e\n            \u003c/turbo-stream\u003e\n          \u003c/template\u003e\n        \u003c/button\u003e %\u003e\n```\n\n### Advanced: Append form controls\n\n```html+erb\n\u003cscript type=\"module\"\u003e\n  import \"@hotwired/turbo\"\n  import { Application } from \"stimulus\"\n  import { TurboStreamButtonController } from \"@seanpdoyle/turbo_stream_button\"\n  import { TemplateInstance } from \"https://cdn.skypack.dev/@github/template-parts\"\n\n  class CloneController extends Controller {\n    static targets = [ \"template\" ]\n    static values = { count: Number, counter: String }\n\n    templateTargetConnected(target) {\n      const templateInstance = new TemplateInstance(target, {\n        [this.counterValue]: this.countValue\n      })\n\n      target.content.replaceChildren(templateInstance)\n\n      this.countValue++\n    }\n  }\n\n  const application = Application.start()\n  application.register(\"turbo-stream-button\", TurboStreamButtonController)\n  application.register(\"clone\", CloneController)\n\u003c/script\u003e\n\n\u003c%= form_with scope: :applicant do |form| %\u003e\n  \u003cfieldset data-controller=\"clone\" data-clone-counter-value=\"counter\" data-clone-count-value=\"0\"\u003e\n    \u003clegend\u003eReferences\u003c/legend\u003e\n\n    \u003col id=\"references\"\u003e\u003c/ol\u003e\n\n    \u003c%= form.fields :reference_attributes, index: \"{{counter}}\" do |reference_form| %\u003e\n      \u003c%= turbo_stream_button_tag do |button| %\u003e\n        Add reference\n\n        \u003c% button.turbo_streams do %\u003e\n          \u003cturbo-stream action=\"append\" target=\"references\"\u003e\n            \u003ctemplate data-clone-target=\"template\"\u003e\n              \u003cli\u003e\n                \u003c%= reference_form.label :referrer %\u003e\n                \u003c%= reference_form.text_field :referrer %\u003e\n\n                \u003c%= reference_form.label :relationship %\u003e\n                \u003c%= reference_form.text_field :relationship %\u003e\n              \u003c/li\u003e\n            \u003c/template\u003e\n          \u003c/turbo-stream\u003e\n        \u003c% end %\u003e\n      \u003c% end %\u003e\n    \u003c% end %\u003e\n  \u003c/fieldset\u003e\n\u003c% end %\u003e\n\n\u003c%# =\u003e  \u003cbutton type=\"button\"\n                data-controller=\"turbo-stream-button\"\n                data-action=\"click-\u003eturbo-stream-button#evaluate\"\u003e\n          Add reference\n\n          \u003ctemplate data-turbo-stream-button-target=\"turboStreams\"\u003e\n            \u003cturbo-stream action=\"append\" target=\"references\"\u003e\n              \u003ctemplate data-clone-target=\"template\"\u003e\n                \u003cli\u003e\n                  \u003clabel for=\"applicant_reference_attributes_{{counter}}_referrer\"\u003eReferrer\u003c/label\u003e\n                  \u003cinput type=\"text\" name=\"applicant[reference_attributes][{{counter}}][referrer]\" id=\"applicant_reference_attributes_{{counter}}_referrer\"\u003e\n\n                  \u003clabel for=\"applicant_reference_attributes_{{counter}}_relationship\"\u003eRelationship\u003c/label\u003e\n                  \u003cinput type=\"text\" name=\"applicant[reference_attributes][{{counter}}][relationship]\" id=\"applicant_reference_attributes_{{counter}}_relationship\"\u003e\n                \u003c/li\u003e\n              \u003c/template\u003e\n            \u003c/turbo-stream\u003e\n          \u003c/template\u003e\n        \u003c/button\u003e %\u003e\n```\n\n## Helpers\n\nThere are two helpers declared by the engine:\n\n### `turbo_stream_button_tag`\n\nThe `turbo_stream_button_tag` helper renders a `\u003cbutton\u003e` element ready to\nevaluate a collection of `\u003cturbo-stream\u003e` elements:\n\n```erb\n\u003c%= turbo_stream_button_tag do |button| %\u003e\n  Click to append \"Hello!\"\n\n  \u003c% button.turbo_streams do %\u003e\n    \u003c%= turbo_stream.append_all \"body\" do %\u003e\n      Hello!\n    \u003c% end %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\n### `turbo_stream_button`\n\nThe `turbo_stream_button` helper returns an attributes-aware HTML tag builder.\nRender a `\u003cbutton\u003e` element with the appropriate `[data-controller]` and\n`[data-action]` attributes by calling `#tag`:\n\n```erb\n\u003c%= turbo_stream_button.tag do %\u003e\n  Click to append \"Hello!\"\n\u003c% end %\u003e\n```\n\nThe return a `Hash` of attributes containing the appropriate `[data-controller]`\nand `[data-action]` attributes, call `#merge`, `#to_h` or splat them into\nkeyword arguments (with `**`):\n\n```erb\n\u003c%= form_with model: Post.new do |form| %\u003e\n  \u003c%= form.button **turbo_stream_button, type: :submit do %\u003e\n    Click to append \"Hello!\"\n\n    \u003c%= tag.template turbo_stream_button.template.merge(data: { a_controller_target: \"template\" }) do %\u003e\n      \u003c%= turbo_stream.append_all \"body\" do %\u003e\n        Hello!\n      \u003c% end %\u003e\n    \u003c% end %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\nTo render the `\u003ctemplate\u003e` element nested within the `\u003cbutton\u003e`, call\n`#template_tag`:\n\n```erb\n\u003c%= turbo_stream_button.tag do %\u003e\n  Click to append \"Hello!\"\n\n  \u003c% turbo_stream_button.template_tag do %\u003e\n    \u003c%= turbo_stream.append_all \"body\" do %\u003e\n      Hello!\n    \u003c% end %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\nThe return a `Hash` of attributes containing the appropriate\n`[data-turbo-stream-button-target]` attribute, call `#merge`, `#to_h`, or splat\nthem into keyword arguments (with `**`):\n\n```erb\n\u003c%= turbo_stream_button.tag do %\u003e\n  Click to append \"Hello!\"\n\n  \u003c%= tag.template turbo_stream_button.template.merge(data: { a_controller_target: \"template\" }) do %\u003e\n    \u003c%= turbo_stream.append_all \"body\" do %\u003e\n      Hello!\n    \u003c% end %\u003e\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\n## Exploring examples\n\nTo poke around with some working examples, start the [dummy application][]\nlocally:\n\n```sh\ncd test/dummy\nbundle exec rails server --port 3000\n```\n\nThen, visit \u003chttp://localhost:3000/examples\u003e.\n\n[![Run on Repl.it](https://repl.it/badge/github/seanpdoyle/turbo_stream_button)](https://replit.com/@seanpdoyle/turbostreambutton)\n\nYou can also fork the [@seanpdoyle/turbo_stream_button][] sandbox project on\n[replit.com][].\n\n[dummy application]: ./test/dummy\n[replit.com]: https://replit.com/\n[@seanpdoyle/turbo_stream_button]: https://replit.com/@seanpdoyle/turbostreambutton\n\n## Installation\n\nAdd the `turbo_stream_button` dependency to your application's Gemfile:\n\n```ruby\ngem \"turbo_stream_button\", github: \"seanpdoyle/turbo_stream_button\", branch: \"main\"\n```\n\nAnd then execute:\n\n```bash\n$ bundle\n```\n\n### Installation through [importmap-rails][]\n\nOnce the gem is installed, add the client-side dependency mapping to your\nproject's `config/importmap.rb` declaration:\n\n```ruby\n# config/importmap.rb\n\npin \"stimulus\", to: \"stimulus.min.js\", preload: true\npin \"@seanpdoyle/turbo_stream_button\", to: \"turbo_stream_button.js\"\n```\n\n[importmap-rails]: https://github.com/rails/importmap-rails\n\n### Installation through `npm` or `yarn`\n\nOnce the gem is installed, add the client-side dependency to your project via\nnpm or Yarn:\n\n```bash\nyarn add https://github.com/seanpdoyle/turbo_stream_button.git\n```\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## License\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseanpdoyle%2Fturbo_stream_button","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseanpdoyle%2Fturbo_stream_button","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseanpdoyle%2Fturbo_stream_button/lists"}