Giter VIP home page Giter VIP logo

turbo_power-rails's Introduction

TurboPower for Rails

GEM Version Gem Downloads

Getting Started

TurboPower Rails is a power-pack for Turbo Streams. This gem provides server-side Ruby helpers for the NPM package turbo_power.

Important Note

This is very much a work in progress right now. This requires the NPM package @hotwired/turbo >= 7.2.0 or @hotwired/turbo-rails >= 7.2.0 and the gem turbo-rails >= 1.3.0.

Also: I can't guarantee that the current API stays that way, there might be even more ways to improve it.

Installation

Install the gem and add to the application's Gemfile by executing:

bundle add turbo_power

Install the JavaScript package:

yarn add turbo_power

Initialize TurboPower in application.js:

// application.js
import * as Turbo from '@hotwired/turbo'

+import TurboPower from 'turbo_power'
+TurboPower.initialize(Turbo.StreamActions)

Installation on a stock Rails 7 install with importmaps

  1. ./bin/importmap pin turbo_power

  2. You'll then have to make sure the following files are modified as such:

config/importmaps.rb

pin "application", preload: true
- pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
+ pin "@hotwired/turbo", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
- pin "@hotwired/turbo", to: "https://ga.jspm.io/npm:@hotwired/[email protected]/dist/turbo.es2017-esm.js"
pin "turbo_power", to: "https://ga.jspm.io/npm:[email protected]/dist/index.js"

app/javascript/application.js

- import '@hotwired/turbo-rails'
+ import { Turbo } from '@hotwired/turbo'
import 'controllers'
+ import TurboPower from 'turbo_power'
+ TurboPower.initialize(Turbo.StreamActions)

Note: These modifications will continue to work when upgrading Turbo via the turbo-rails gem. You can read more for the reasoning behind these adjustments.

Custom Actions

DOM Actions

  • turbo_stream.graft(targets, parent, **attributes)
  • turbo_stream.morph(targets, html = nil, **attributes, &block)
  • turbo_stream.inner_html(targets, html = nil, **attributes, &block)
  • turbo_stream.insert_adjacent_html(targets, html = nil, position: 'beforeend', **attributes, &block)
  • turbo_stream.insert_adjacent_text(targets, text, position: 'beforebegin', **attributes)
  • turbo_stream.outer_html(targets, html = nil, **attributes, &block)
  • turbo_stream.text_content(targets, text, **attributes)
  • turbo_stream.set_meta(name, content, **attributes)

Attribute Actions

  • turbo_stream.add_css_class(targets, classes, **attributes)
  • turbo_stream.remove_attribute(targets, attribute, **attributes)
  • turbo_stream.remove_css_class(targets, classes, **attributes)
  • turbo_stream.set_attribute(targets, attribute, value, **attributes)
  • turbo_stream.set_dataset_attribute(targets, attribute, value, **attributes)
  • turbo_stream.set_property(targets, name, value, **attributes)
  • turbo_stream.set_style(targets, name, value, **attributes)
  • turbo_stream.set_styles(targets, styles, **attributes)
  • turbo_stream.set_value(targets, value, **attributes)
  • turbo_stream.toggle_css_class(targets, classes, **attributes)
  • turbo_stream.replace_css_class(targets, from, to, **attributes)

Event Actions

  • turbo_stream.dispatch_event(targets, name, detail: {}, **attributes)

Form Actions

  • turbo_stream.reset_form(targets, **attributes)

Storage Actions

  • turbo_stream.clear_storage(type, **attributes)
  • turbo_stream.clear_local_storage(**attributes)
  • turbo_stream.clear_session_storage(**attributes)
  • turbo_stream.remove_storage_item(key, type, **attributes)
  • turbo_stream.remove_local_storage_item(key, **attributes)
  • turbo_stream.remove_session_storage_item(key, **attributes)
  • turbo_stream.set_storage_item(key, value, type, **attributes)
  • turbo_stream.set_local_storage_item(key, value, **attributes)
  • turbo_stream.set_session_storage_item(key, value, **attributes)

Browser Actions

  • turbo_stream.reload(**attributes)
  • turbo_stream.scroll_into_view(**attributes)
    • turbo_stream.scroll_into_view(targets)
    • turbo_stream.scroll_into_view(targets, align_to_top)
    • turbo_stream.scroll_into_view(targets, behavior:, block:, inline:)
  • turbo_stream.set_focus(targets, **attributes)
  • turbo_stream.set_title(title, **attributes)

Document Actions

  • turbo_stream.set_cookie(cookie, **attributes)
  • turbo_stream.set_cookie_item(key, value, **attributes)

Browser History Actions

  • turbo_stream.history_back(**attributes)
  • turbo_stream.history_forward(**attributes)
  • turbo_stream.history_go(delta = 0, **attributes)
  • turbo_stream.push_state(url, title = "", state = {}, **attributes)
  • turbo_stream.replace_state(url, title = "", state = {}, **attributes)

Debug Actions

  • turbo_stream.console_log(message, level = :log, **attributes)
  • turbo_stream.console_table(data, columns, **attributes)

Notification Actions

  • turbo_stream.notification(title, **options)

Turbo Actions

  • turbo_stream.redirect_to(url, turbo_action = nil, turbo_frame = nil, **attributes)
  • turbo_stream.turbo_clear_cache()

Turbo Progress Bar Actions

  • turbo_stream.turbo_progress_bar_show(**attributes)
  • turbo_stream.turbo_progress_bar_hide(**attributes)
  • turbo_stream.turbo_progress_bar_set_value(value, **attributes)

Turbo Frame Actions

  • turbo_stream.turbo_frame_reload(frame_id, **attributes)
  • turbo_stream.turbo_frame_set_src(frame_id, src, **attributes)

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/turbo_power-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the TurboPower project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

turbo_power-rails's People

Contributors

adrianthedev avatar cmer avatar leonvogt avatar marcoroth avatar minimul avatar valentinorusconi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

turbo_power-rails's Issues

Please clearify «targets»

Hi,

Many of your functions have the first attribute: targets.

I made a test on turbo_frame_set_src and noticed that its interpreting a simple string as id, because: #cars-box does not work while cars-box successfully targets the element with the id cars-box.

So under targets I would expect that a value like .cars-box would affect all matching elements as it would be the case with jquery.

Could you please clarify this?

On my gem, which includes yours in many cases, I kept the same naming because I thought target was a good naming for what it means, but clarified that target has a value like #my-target in logs, for example.

And why not allow css matchers like .customer-wrapper > #form? Yes, html-id should be unique, but this is hard to control and for complex pages child selectors would be a help.

Thanks,
Chris

turbo_frame_reload changes the src of the frame?

I tripped across this today, but not sure if this is by design / unavoidable in turbo, or a bug?

I have a turbo frame with a src like:

<turbo-frame id="blah" src="/blah/1">

in my rails controller, on another action (let's say I'm editing blah) I tell it to reload that frame:

#blah_controller

def edit
  #do stuff
  render turbo_stream: turbo_stream.turbo_frame_reload('blah')
end

Then the turbo frame src of the frame I'm reloading changes to:

<turbo-rame id="blah" src="/blah/1/edit">

which is incorrect, the src should stay the same.

Is this intentional, or due to some side effect of Turbo reloading? IMO, the src of a turbo frame should never change. And its causing unexpected issues in my app because of the url its putting there.

Installation hiccup on Rails 7 with turbo-rails and importmaps

The turbo-rails gem pins thus to the config/importmap.rb file:

pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true

When I ran bin/importmap pin turbo_power it did pin a version of @hotwired/turbo but since I didn't want two versions of Turbo, I removed it and received this error:

Uncaught TypeError: Failed to resolve module specifier "@hotwired/turbo". Relative references must start with either "/", "./", or "../".

As a workaround, I just made a "copy" in config/importmap.rb like so:

pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true
+pin @hotwired/turbo', to: 'turbo.min.js', preload: true

After that no problems and I've already implemented a .set_attribute() stream. 🚀

Thanks for this great gem and NPM package.

Action set_value throws an exception of ..

ArgumentError (You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option.):, while the same usage with set_attribute does not.

turbo_stream.set_value('#order_form_manual_time', '') => throws exception

turbo_stream.set_attribute('#order_form_manual_time', 'value', '') => works

I looked briefly at https://github.com/marcoroth/turbo_power-rails/blob/main/lib/turbo_power/stream_helper.rb and everything seems in order.

I can do more investigating and attempt to produce a failing test later but wanted to get this potential issue recorded.

Method Chaining

This for this gem, it's making my cable_ready migration possible without totally re-writing. Would you be interested in adding method chaining? Similar to cable_car from cable_ready?

I worked this out:

class ApplicationController < ActionController::Base
  def stream_car
    StreamCar.new(method(:turbo_stream))
  end

  class StreamCar
    def initialize(meth)
      @meth = meth
      @buffer = ActiveSupport::SafeBuffer.new
    end

    def to_s
      @buffer.to_s
    end

    def to_str
      @buffer.to_str
    end

    def method_missing(name, *args, **kwargs)
      @buffer << @meth.call.send(name, *args, **kwargs).html_safe

      self
    end
  end
end

Use by...

  def update
    render turbo_stream: stream_car.remove_css_class("#launch-card").set_value("#user_invite_form_name", value: "")
  end

Surprisingly, it works. Happy to make a PR and battle-test it more; just wanted to ask.

Gem doesn't provide a "bundled" turbo_power.js

Hey @marcoroth

I just tried to do a:

pin "turbo_power", to: "turbo_power.js"

as in the README (instructions I put in with my PR 😀 btw) and the browser console throws an error that turbo_power.js cannot be found.

I looked in the Rails asset paths Rails.application.assets.paths.map { |path| puts system("ls #{path}/") }; nil and yeah, there is no turbo_power.js nor any paths provided from this gem.

I'm using the latest gem 'turbo_power', which at this time is 0.1.6.

Working with Turbo 8

Does it work correctly with Turbo 8 ?
Can it be used with Turbo 8? what problems?

Adding more examples

Hey @marcoroth

Really love the work you're doing with this project so far. Managed to use it for some of the modals at aitoolsfor

However, i think adoption of this project could improve if we added some examples for people to reference. Happy to work with you on this one if you could provide some guidance! 🙏

Some googling didn't show any tutorials or guides so it's a little tricky sometimes.

Let me know what you think!

make helpers available in controller

Thanks for this really helpful gem

currently my view looks anything like that:

= turbo_stream_flash
- if status_success
  = turbo_stream.redirect_to( admin_responsibilities_path, "advance")
- else
  = turbo_stream.replace 'responsibility-form' do
    = render 'form'

Would it be possible to make theese helpers available or some of that available in controller?
Or, at least some of them, like the redirect_to

this should, on rendering, adding the tag to the view, so, that turbo can handle the redirect.

this would simplyfiy CRUD actions like that:

  def create
    @responsibility = Responsibility.new(responsibility_params)
    if @responsibility.save
      turbo_stream.redirect_to(admin_responsibilities_path, "advance")
    else
      render status: :unprocessable_entity
    end
  end

Restore block rendering and partial rendering syntax for streams

Pull Request #12 removed the ability to use the block rendering and partial rendering syntax for Turbo Streams actions.

We should restore this functionality:

<%= turbo_stream.inner_html "body" do %>
  New Body
<% end %>

and

<%= turbo_stream.inner_html "#post_1", partial: "posts/post", locals: { post: @post } %>

Following Turbo action conventions

Hi, we're loving this gem. It's got a lot of really great abilities in here.

I spent a little while debugging an issue when I was using one of the actions today. I had a turbo stream template which was rendering several turbo stream actions

= turbo_stream.append dom_id(@model, "list"), ...
= turbo_stream.replace dom_id(@model, "map_pin"), ...
-# other turbo streams here

And we wanted to add a new stream for changing an attribute. Turbo power has the set_attribute action for this!

= turbo_stream.set_attribute dom_id(@model, "map"), ...

This failed however, and you might be able to guess why. Almost all of the Turbo Power actions use the custom_action_all helper, which sends builds turbo stream tags using the targets attribute. When you use the targets attribute then the Turbo JS switches to using querySelectorAll instead of findElementByID to locate your target. That took us a bit of time but we eventually figured out we needed to prefix our dom_id with a # in order to get everything working.

= turbo_stream.set_attribute "##{dom_id(@model, "map")}", ...

This is a little bit uglier, but it's workable.

Turbo has started to establish a convention with the stream helpers where calling an action sets the target attribute, and the *_all helpers sets the targets attributes (e.g. append vs append_all).

I think we could add the same thing in Turbo Power where it makes sense. That would involve making many additional helpers and having set_attribute_all in addition to set_attribute. I saw your rejected PR which would have made this quite a bit easier as we could have passed target/targets ourselves, as needed. In lieu of that, however, adding all versions of these actions might be good for consistency. Would you accept a PR which added these or do you have any other ideas of how to achieve this consistency a little better? The biggest downside I can see besides doubling the API surface is it would be a breaking change for the existing API. Let me know what your thoughts are, please!

TurboPower should work with `@hotwired/turbo` and `@hotwired/turbo-rails`

It shouldn't matter if you are using @hotwired/turbo or @hotwired/turbo-rails, TurboPower should work either way.

As pointed out in #2 and #3 (comment) it seems like there are some inconsistencies. The following snippets should work:

import * as Turbo from "@hotwired/turbo"
import TurboPower from "turbo_power"

TurboPower.initialize(Turbo.StreamActions)
import { StreamActions } from "@hotwired/turbo"
import TurboPower from "turbo_power"

TurboPower.initialize(StreamActions)
import { Turbo } from "@hotwired/turbo-rails"
import TurboPower from "turbo_power"

TurboPower.initialize(Turbo.StreamActions)

Don't generate `<template>` tag if it's not necessary

Currently the helper generates <turbo-stream> elements always with the <template> tag, we should just generate it when it's actually needed.

For example, this call:

turbo_stream.reset_form("#form")

Currently generates:

<turbo-stream action="reset_form" targets="#form"><template></template></turbo-stream>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.