Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Workshop application to learn Hotwire

NotificationsYou must be signed in to change notification settings

tomdonarski/hotwire-kanban

 
 

Repository files navigation

A simple application to experiment with Turbo.

Setup

This application uses:

  • ruby 3.3.0
  • sqlite 3
  • redis

Have them installed, clone repo and run:

$ bundle$ rails db:setup

You can runrails db:seed many times to have more data.

Userails s to run the server.

Testing

Run$ rspec for tests.

Run$ rubocop for linter check.

Knowledge base

Turbo Drive

Links:
https://hotwired.dev/
https://turbo.hotwired.dev/handbook/drive

Task 1

  • go to any web page
  • analyse content of Network tab in Inspector during navigating through sub-pages
  • run workshop app withrails s
  • analyse content of Network tab in Inspector during navigating through sub-pages
  • add at the bottom ofapp/javascript/application.js
Turbo.session.drive = false
  • analyse content of Network tab in Inspector during navigating through sub-pages

Turbo Frames

Links:
https://turbo.hotwired.dev/handbook/frames
https://rubydoc.info/github/hotwired/turbo-rails/Turbo%2FFramesHelper:turbo_frame_tag
https://apidock.com/rails/ActionView/RecordIdentifier/dom_id

Task 1: edit in place for cards

Add turbo frames for cards to enable edit in place

  1. Updateapp/views/cards/_card.html.erb -wrap all the code into turbo frame tag block:

    Updated file:
    <%= turbo_frame_tag dom_id(card) do%><divclass="card card-body"><divclass="d-flex justify-content-between align-items-center mb-2"><h5class="card-title mb-0"><%=card.title%></h5><divclass="d-flex gap-2"><%=link_toedit_card_path(card),class:'text-default'do%><iclass="fa-solid fa-pencil"></i><%end%><%=link_tocard_path(card),data:{turbo_method::delete,turbo_confirm:'Are you sure?'},class:'text-danger'do%><iclass="fa-solid fa-trash"></i><%end%></div></div><divclass="card-text text-primary-grey-600"><%=card.description%></div></div><%end%>
  1. Updateapp/views/cards/edit.html.erb - wrap ‘form’ into turbo frame tag block:

    Updated file:
    <h1class="text-primary-dark-500">Edit Card</h1><divclass="row w-100 justify-content-center mt-3"><%=turbo_frame_tagdom_id(@card)do%><divclass="border border-primary-grey-200 bg-light p-2"><%=render'form'%></div><%end%></div>

Task 2: edit in place for board columns

Add turbo frames to board column headers to edit column names in place

  1. Updateapp/views/board_columns/_column_header.html.erb -wrap all the code into turbo frame tag block:

    Updated file:
    <%= turbo_frame_tag dom_id(board_column, :edit) do%><divclass="d-flex flex-row"><h5class="d-flex flex-col"><%=board_column.name%></h5><divclass="d-flex flex-col ms-auto gap-2"><%=link_toedit_board_column_path(board_column)do%><divclass="fa-solid fa-pencil text-primary-dark-600"></div><%end%><%=link_toboard_column_path(board_column),data:{turbo_method::delete,turbo_confirm:'Are you sure?'}do%><divclass="fa-solid fa-trash text-primary-dark-600"></div><%end%></div></div><%end%>
  1. Updateapp/views/board_columns/edit.html.erb - wrap ‘form’ into turbo frame tag block:

    Updated file:
    <h1class="text-primary-dark-500">Edit Board Column</h1><divclass="row w-100 justify-content-center mt-3"><%=turbo_frame_tagdom_id(@board_column,:edit)do%><divclass="border border-primary-grey-200 bg-light p-2"><%=render'form'%></div><%end%></div>

Task 3: edit in place for boards

Add turbo frames to board headers to edit board name in place

  1. Updateapp/views/boards/index.html.erb -wrap .card-header into turbo frame tag block (line 17):

    Updated file:
    <divclass="row w-100"><divclass="d-flex justify-content-between"><%=link_to'New Board',new_board_path,class:'btn btn-outline-primary invisible'%><h1class="text-primary-dark-500">    Boards</h1><div><%=link_to'New Board',new_board_path,class:'btn btn-outline-primary'%></div></div></div><divclass="row w-100"><%@boards.eachdo |board|%><divclass="col-3 my-3"><divclass="card border border-primary-grey-200"><%=turbo_frame_tagdom_id(board,:edit)do%><divclass="card-header bg-primary-grey-200"><divclass="d-flex justify-content-between align-items-center"><h5class="card-title mb-0"><%=link_toboard.name,board,class:'link-underline link-underline-opacity-0'%></h5><divclass="d-flex gap-2"><%=link_toedit_board_path(board),class:'text-default'do%><iclass="fa-solid fa-pencil text-primary-dark-600"></i><%end%><%=link_toboard,data:{turbo_method::delete,turbo_confirm:'Are you sure?'},class:'text-danger'do%><iclass="fa-solid fa-trash text-primary-dark-600"></i><%end%></div></div></div><%end%><divclass="card-body bg-primary-grey-100"><divclass="card-text"><p><%="Columns:#{board.board_columns.size}"%></p><p><%="Cards:#{board.board_columns.joins(:cards).count}"%></p></div></div></div></div><%end%></div>
  1. Updateapp/views/boards/edit.html.erb - wrap ‘form’ into turbo frame tag block:

    Updated file:
    <h1class="text-primary-dark-500">Edit Board</h1><divclass="row w-100 justify-content-center mt-3"><%=turbo_frame_tagdom_id(@board,:edit)do%><divclass="border border-primary-grey-200 bg-light p-2"><%=render'form'%></div><%end%></div>

Branch with all edits-in-place:git checkout turbo-frames-edits

Task 4: Fix show board link

  1. Updateapp/views/boards/index.html.erb - adddata: { turbo_frame: '_top' } to show link:

    Updated file:
    <h5class="card-title mb-0"><%=link_toboard.name,board,data:{turbo_frame:'_top'},class:'link-underline link-underline-opacity-0'%></h5>

Branch with fixed link:git checkout turbo-frames-top

Turbo Streams

Links:
https://turbo.hotwired.dev/handbook/streams

Task 1: Fix deleting cards

  1. Updateapp/controllers/cards_controller.rb#destroy -add turbo stream format response

    Updated file:
    respond_todo |format|format.html{redirect_toboard_url(board),notice:"Card was successfully destroyed."}format.turbo_streamend
  1. Createapp/views/cards/destroy.turbo_stream.erb

    Updated file:
    <%= turbo_stream.remove dom_id(@card)%>

Task 2: Fix deleting board columns

  1. Updateapp/controllers/board_columns_controller.rb#destroy -add turbo stream format response with inline turbo stream render

    Updated file:
    respond_todo |format|format.html{redirect_toboard_url(board),notice:"BoardColumn was successfully destroyed."}format.turbo_stream{renderturbo_stream:turbo_stream.remove(@board_column)}end
  1. Updateapp/views/board_columns/_board_column.html.erb - add unique ID forboard columns:

    Updated file:
    <divid=<%=dom_id(board_column)%>class="board-column" data-sortable-column-id-value="<%=board_column.id%>"><%=renderpartial:'board_columns/column_header',locals:{board_column:board_column}%><divid=<%=dom_id(board_column,:column_body)%>,class="board-column-body",data-sortable-target="cardsContainer"><%board_column.cards.order(:position).eachdo |card|%><divclass="draggable_card my-1"data-sortable-id="<%=card.id%>"><%=renderpartial:'cards/card',locals:{card:card}%></div><%end%></div><divclass="board-column-footer"><%=link_tonew_card_url(board_column_id:board_column.id),class:'text-success fs-2'do%><divclass="fa-solid fa-plus"></div><%end%></div></div>

Task 3: Fix deleting boards

  1. Updateapp/controllers/boards_controller.rb#destroy -add turbo stream format response with inline turbo stream render

    Updated file:
    defdestroy@board.destroy!respond_todo |format|format.html{redirect_toboards_url,notice:"Board was successfully destroyed."}format.turbo_stream{renderturbo_stream:turbo_stream.remove(@board)}endend
  1. Updateapp/views/boards/index.html.erb - add unique IDs for each board:

    Updated file:
    <divclass="row w-100"><divclass="d-flex justify-content-between"><%=link_to'New Board',new_board_path,class:'btn btn-outline-primary invisible'%><h1class="text-primary-dark-500">    Boards</h1><div><%=link_to'New Board',new_board_path,class:'btn btn-outline-primary'%></div></div></div><divclass="row w-100"><%@boards.eachdo |board|%><divid=<%=dom_id(board)%>class="col-3 my-3"><divclass="card border border-primary-grey-200"><%=turbo_frame_tagdom_id(board,:edit)do%><divclass="card-header bg-primary-grey-200"><divclass="d-flex justify-content-between align-items-center"><h5class="card-title mb-0"><%=link_toboard.name,board,data:{turbo_frame:'_top'},class:'link-underline link-underline-opacity-0'%></h5><divclass="d-flex gap-2"><%=link_toedit_board_path(board),class:'text-default'do%><iclass="fa-solid fa-pencil text-primary-dark-600"></i><%end%><%=link_toboard,data:{turbo_method::delete,turbo_confirm:'Are you sure?'},class:'text-danger'do%><iclass="fa-solid fa-trash text-primary-dark-600"></i><%end%></div></div></div><%end%><divclass="card-body bg-primary-grey-100"><divclass="card-text"><p><%="Columns:#{board.board_columns.size}"%></p><p><%="Cards:#{board.board_columns.joins(:cards).count}"%></p></div></div></div></div><%end%></div>

Branch with all deletes fixed:git checkout turbo-frames-deletes

Task 4: Create new Cards with Turbo Streams

  1. Extract 'New Card link' into partial - createapp/views/cards/_new_card.html.erb:

    Created file:
    <%= link_to new_card_url(board_column_id: board_column.id), class: 'text-success fs-2' do%><divclass="fa-solid fa-plus"></div><%end%>
  1. Use new partial inapp/views/board_columns/_board_column.html.erb:

    Updated file:
    <divclass="board-column-footer"><%=renderpartial:'cards/new_card',locals:{board_column:board_column}%></div>
  1. Render new card form in place: wrap link to New Card into turbo frame inapp/views/cards/_new_card.html.erb:

    Updated file:
    <%= turbo_frame_tag dom_id(board_column, :new_card) do%><%= link_to new_card_url(board_column_id: board_column.id), class: 'text-success fs-2' do%><divclass="fa-solid fa-plus"></div><%end%><%end%>
  1. Wrap 'form' into turbo frame inapp/views/cards/new.html.erb:

    Updated file:
    <h1class="text-primary-dark-500">New Card</h1><divclass="row w-100 justify-content-center mt-3"><%=turbo_frame_tagdom_id(@card.board_column,:new_card)do%><divclass="border border-primary-grey-200 bg-light p-2"><%=render'form'%></div><%end%></div>
  1. Create turbo_stream response - updateapp/controllers/cards_controller.rb#create -

    Updated file:
    respond_todo |format|ifservice.call@card=service.cardformat.html{redirect_toboard_url(@card.board),notice:"Card was successfully created."}format.turbo_streamelse@card=service.cardformat.html{render:new,status::unprocessable_entity}endend
  1. Createapp/views/cards/create.turbo_stream.erb:

    Created file:
    <%= turbo_stream.append dom_id(@card.board_column, :column_body) do%><%= render 'cards/card', card: @card%><% end%><%= turbo_stream.replace dom_id(@card.board_column, :new_card) do%><%= render 'cards/new_card', board_column: @card.board_column%><% end%>

Task 5: Create new Boards with Turbo Streams

Add create-in-place for boards.

Updated files

No solution here.Try to implement it on your own. You can do it! 💪Or, checkout to branch with solution.

Task 6: Create new Board Columns with Turbo Streams

Add create-in-place for board columns. Ideally, new columns should be addedright to existing ones.

Updated files

No solution here.Try to implement it on your own. You can do it! 💪Or, checkout to branch with solution.

Branch with all records creation:git checkout turbo-frames-creates

Turbo Broadcasts

Links:
https://www.rubydoc.info/gems/turbo-rails/Turbo/Broadcastable
https://www.hotrails.dev/turbo-rails/turbo-streams

Task 1: Adding broadcasts to columns

  1. Updateapp/views/boards/show.html.erb -Add turbo stream tag to connect user to websocket channel at the top of file
New line:
<%= turbo_stream_from dom_id(@board)%>

also within the same file add turbo stream tag that we will use to append broadcasted columns

New line in file placement:
<% @board_columns.each do |board_column|%><%= render partial: 'board_columns/board_column', locals: { board_column: board_column }%><% end%><%= turbo_frame_tag dom_id(@board, 'columns') # newly added line%><%= turbo_frame_tag dom_id(BoardColumn.new)%>
  1. Updateapp/models/board_column.rb -include ActionView::RecordIdentifier library to usedom_id in model,add broadcast callback to model
Updated file:
classBoardColumn <ApplicationRecordincludeActionView::RecordIdentifier# ... leave old codebroadcasts_to->(board_column){"board_#{board_column.board_id}"},target:->(board_column){"columns_board_#{board_column.board.id}"},inserts_by::append

Task 2: Triggering columns broadcasts on card changes

  1. Updateapp/models/card.rb -add callback that will touch and update associated columns while modifying cards
Updated file:
classCard <ApplicationRecordincludeActionView::RecordIdentifier# ... leave old codeafter_commit:touch_affected_board_columnsprivatedeftouch_affected_board_columnsifprevious_changes[:board_column_id].present?board.board_columns.find_by(id:previous_changes[:board_column_id]&.first)&.touchboard.board_columns.find_by(id:previous_changes[:board_column_id]&.last)&.touchelseboard_column.touchendendend

Branch with broadcasts:git checkout turbo-broadcasts

About

Workshop application to learn Hotwire

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby69.6%
  • HTML18.0%
  • SCSS5.5%
  • JavaScript4.0%
  • Dockerfile2.6%
  • Shell0.3%

[8]ページ先頭

©2009-2025 Movatter.jp