We've moved discussions to Discord

New Hotwire Scaffolds | Index Tables?

Dan Tappin
Chris Oliver the Hotwire updates are great - the one issue I see is that it looks like the index pages are no longer tables.  I get that for turbo to replace it needs to be a div and not a tr but the old styling seems to be gone.

I am also at a loss following the logic for the same model partial being rendered in the index collection and the show page.  I get the idea to reduce redundancy but the show and index layouts would tend to never be the same.
Christopher Stewart
What do the hotwire scaffolds do? I haven't checked it out yet. Does it set all the forms up for hotwire/turbo stream routing?
Ye Lin Aung
Chris Oliver   I think tables are better. +1 for this

Aska Konomi
For now I just replaced the render @model part in the show page with the content in the _model partial, then work from there. This is a simple task but I think yeah usually the show and the index partial are not the same.

For the table change though I am happy with it - I usually have to remove the table structure anyway, so this change actually saved me some time. I guess adding a flex and some space-x to the partial class so it looks more like a tabled content, can address this issue pretty easily?
Jeremy Noetzelman
Same thing here.   Chris Oliver  hate to tear all this out if you have a plan in mind, but I need something more indexy.  Can you comment on vision here?
Ramzi Laifa
same same
John Chambers
Just to give something to the other side of the tables vs divs debate for scaffolding index pages.
Initially I felt the same, when I saw the tables were gone. I was sceptical and wanted to add a table back into my index. 

However, the reality is all my Rails Apps needs to be mobile responsive and tables would never work long term.
It's been much easier and quicker to build mobile friendly UI's with the new system and tailwind.

I wonder if we could pass a flag with the scaffold generator to build an alternative index.html.erb with table when needed. 
Or maybe just build 2 index pages: index.html.erb and index_table.html.erb  
Then people can just rename the files if they want to use them (while also losing Hotwire in the index view).

Not sure my comments help. This idea might not be elegant enough!
Jeremy Noetzelman
tables vs divs is one thing.  You can implement tables with divs if you need to.

The current index view is just a list of all of the collection in full glory using the same partial as #show.

I don't understand this as a default, the vast majority of apps want table-y looking things (whether implemented with div or table) not a giant page full of detail.
Rob Jonson
Chris Oliver   any comment? 

I just fired up a new app with the new setup and it's just wrong as it stands!
Jon Sullivan
The current index view is just a list of all of the collection in full glory using the same partial as #show.

I don't understand this as a default, the vast majority of apps want table-y looking things (whether implemented with div or table) not a giant page full of detail.

Jeremy Noetzelman FWIW, I think that's a little tough to address from the standpoint of a generator whose resulting content will likely be overwritten anyway — the only difference between this generator's #show (which shows all the attribute fields in one column) and the sort of index view you're describing ("table-y looking things") is whether or not the attributes are in a single column vs. flex'd out to a 'row'.

Now granted, I agree with you. The generator spits out a #show that just renders the base partial for the record and I'd probably prefer the #show be separate from the base partial (probably just dupe the current base partial code) and the base-partial be flex'd... but it's not altogether a huge difference. Code-wise, at least. 
Rob Jonson
ok - I had a go at this, and I have a pretty good solution.
I'm showing meditations here, the goal is to have a single _meditation file which can be broadcast, and which can provide a full 'show' view, and a table-style 'index' view

CSS can do a _lot_ here! Specifically, it can make regular divs display like table rows/cells

Result first:

both of these update correctly with turbo

the key is in the css:

#/app/assets/stylesheets/custom/index.css


.index {
	display: table;
	width: 100%;
    border-collapse: separate;
    border-spacing: 0 1em;

	.index-row {
		display:  table-row;

	}
	.index-cell {
		display:  table-cell;
		vertical-align: middle;
	}

	.index-hide {
		display: none;
	}

}

.show {
	.show-hide {
		display: none;
	}
}

so the meditation can be styled to show/hide appropriately:

#/app/views/meditations/_meditation.html.haml

%div.index-row{:id => "#{dom_id meditation}"}
  .show-hide
    = background_image_tag url_for(meditation.image.representation(resize_to_fill: [100, 100])), class: "index-cell", size: "100x100"
  .mb-4.index-hide
    = background_image_tag url_for(meditation.image.representation(resize_to_fill: [500, 500])), size: "500x500"
  .mb-4.index-cell
    %p.text-sm.font-medium.text-gray-500.index-hide  Title
    = meditation.title
  .mb-4.index-cell
    %p.text-sm.font-medium.text-gray-500.index-hide  Author
    = meditation.author
  .mb-4.index-hide
    %p.text-sm.font-medium.text-gray-500 Description
    = meditation.description
  .mb-4.index-cell
    %p.text-sm.font-medium.text-gray-500.index-hide  Duration
    = meditation.duration
    minutes
  .mb-4.index-cell
    %p.text-sm.font-medium.text-gray-500.index-hide  Published
    = meditation.published
  .mb-4.index-hide
    %p.text-sm.font-medium.text-gray-500 Key
    = meditation.slug
  .mb-4.index-cell.show-hide
    = link_to "View", meditation, class: "btn btn-white"

note - I'm showing a large image in the show page, and I don't want that to be loaded in the index page, so I'm using a helper for background_image_tag and making sure that the parent is hidden (taken from here: https://timkadlec.com/2012/04/media-query-asset-downloading-results/ )

#/app/helpers/meditations_helper.rb	def background_image_tag(source,options = {})
        options = options.symbolize_keys
        check_for_image_tag_errors(options)
        skip_pipeline = options.delete(:skip_pipeline)

        options[:style] = "background-image:url(#{resolve_image_source(source, skip_pipeline).html_safe});"
        #1px transparrent png https://stackoverflow.com/questions/5775469/whats-the-valid-way-to-include-an-image-with-no-src
        options[:src] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII="

        options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
        tag("img", options)
	end
the helper is pretty much just tweaked from Rails' image_tag, so I may have missed some subtleties, but it works for me

then in the scaffold, I just have to make sure that the index page has the index class

/app/views/meditations/index.html.haml
...
  = tag.div id: ("meditations" if first_page?), class: "bg-white rounded-md shadow p-6 space-y-8 index" do
    = render partial: "meditations/index", object: 
/app/views/meditations/_index.html.haml
-if meditations.count > 0
  .table-header-group
    .table-row
      .table-cell.text-sm.font-medium.text-gray-500 Image
      .table-cell.text-sm.font-medium.text-gray-500 Title
      .table-cell.text-sm.font-medium.text-gray-500 Author
      .table-cell.text-sm.font-medium.text-gray-500 Duration
      .table-cell.text-sm.font-medium.text-gray-500 Published
      .table-cell.text-sm.font-medium.text-gray-500

= render partial: "meditations/index_row", collection: meditations, as: :meditation, cached: true
and 

/app/views/meditations/_index_row.html.haml
= turbo_stream_from meditation
= render meditation

the show template is unchanged except for adding a 'show' class

...
  .p-8.bg-white.rounded.shadow.show
    = render partial: "meditations/meditation", object: @meditation, as: :meditation
and finally, tweak the broadcasts:

#/app/models/meditation.rb

  # Broadcast changes in realtime with Hotwire
  after_create_commit -> { broadcast_prepend_later_to :meditations, partial: "meditations/index_row", locals: {meditation: self} }
  after_update_commit -> { broadcast_replace_later_to self }
  after_destroy_commit -> { broadcast_remove_to :meditations, target: self }


this all works pretty well. Couple of imperfections:

1) if you have an empty table, then the broadcast of the first element won't trigger the header to show (you have to reload)
2) if you delete, then you leave an orphaned turbo_stream_from to the (now deleted) item

neither of those are a big deal for me.
Rob Jonson
I did a bit more thinking, and decided I didn't like the approach above. 
Making the same template work for row & show felt too forced.

So - new approach. This time, two completely separate templates for the row and show views. They're pretty standard in _meditation_row and _meditation_show

the main change was to use a turbo-stream template for my update broadcast

from meditation.rb

  # Broadcast changes in realtime with Hotwire
  after_create_commit -> { broadcast_prepend_later_to :meditations, partial: "meditations/meditation_row", locals: {meditation: self} }
  after_update_commit -> { broadcast_render_later_to self, locals: {meditation: self} }
  after_destroy_commit -> { broadcast_remove_to :meditations, target: dom_id(self, :row) }
where app/views/meditations/_meditation.turbo_stream.haml
= turbo_stream.replace dom_id(meditation, :row) do
  = render partial: "meditations/meditation_row", object: meditation, as: :meditation
= turbo_stream.replace dom_id(meditation, :show) do
  = render partial: "meditations/meditation_show", object: meditation, as: :meditation

index has

...
  = tag.div id: ("meditations" if first_page?), class: "bg-white rounded-md shadow p-6 space-y-8 table w-full" do
    -if @meditations.count > 0
      .table-header-group
        .table-row
          .table-cell.text-sm.font-medium.text-gray-500 Image
          .table-cell.text-sm.font-medium.text-gray-500 Title
          .table-cell.text-sm.font-medium.text-gray-500 Author
          .table-cell.text-sm.font-medium.text-gray-500 Duration
          .table-cell.text-sm.font-medium.text-gray-500 Published
          .table-cell.text-sm.font-medium.text-gray-500
    = render partial: "meditations/meditation_row", collection: @meditations, as: :meditation, cached: true
...
(again - the table header won't get built if you broadcast the first item as an append)

and /app/views/meditations/_meditation_row.html.haml

%div{:id => dom_id(meditation, :row), class: "table-row-group mb-4"}
  = turbo_stream_from meditation
  %div.table-row{:id => dom_id(meditation, :row)}
    = image_tag url_for(meditation.image.representation(resize_to_fill: [100, 100])), class: "table-cell", size: "100x100"
    .table-cell.align-middle
      = meditation.title
    .table-cell.align-middle
      = meditation.author
    .table-cell.align-middle
      = meditation.duration
      minutes
    .table-cell.align-middle
      = meditation.published
    .table-cell.align-middle
      = link_to "View", meditation, class: "btn btn-white"

for show:

...
  .p-8.bg-white.rounded.shadow
    = render partial: "meditations/meditation_show", object: @meditation, as: :meditation

with /app/views/meditations/_meditation_show.html.haml

%div{:id => dom_id(meditation, :show)}
  = image_tag url_for(meditation.image.representation(resize_to_fill: [500, 500])), size: "500x500", class: "mb-4"
  .mb-4
    %p.text-sm.font-medium.text-gray-500.index-hide  Title
    = meditation.title
  .mb-4
    %p.text-sm.font-medium.text-gray-500.index-hide  Author
    = meditation.author
  .mb-4
    %p.text-sm.font-medium.text-gray-500 Description
    = meditation.description
  .mb-4
    %p.text-sm.font-medium.text-gray-500.index-hide  Duration
    = meditation.duration
    minutes
  .mb-4
    %p.text-sm.font-medium.text-gray-500.index-hide  Published
    = meditation.published
  .mb-4
    %p.text-sm.font-medium.text-gray-500 Key
    = meditation.slug


I'm not using Chris Oliver  's technique of using _index to wrap _meditation.
This means that the turbo_stream for the row is replaced every time. I don't know if that is a problem, but it seems to work ok...
Notifications
You’re not receiving notifications from this thread.