StimulusReflex 양식 제출의 두 가지 모드
35960 단어 cablereadystimulusreflexrails
1.Vanilla CableReady
StimulusReflex discord에서, 우리는 반사에서 표 제출을 어떻게 가장 잘 처리하는지에 대해 자주 질문을 받는다.나의 답은 왕왕 "아니오"이다.대신 표준 Rails 원격 양식과 CableReady 의 강력한 기능에 의존하는 더 강력한 기술을 먼저 보여 드리겠습니다.
이번 훈련의 목적을 위해 저는 Stimulus Reflex 엑스포 사이트의 우수한 것들Calendar Demo을 빌려 썼습니다.(계속하려면 이 RailsByte에 StimulusReflex 및 TailwindCSS가 설치됩니다. 버전 레이블과 함께 demo repo 을 사용할 수도 있습니다.
우선, 우리의 소형 달력 응용 프로그램은 하나의 모델
CalendarEvent
만 필요로 하고, 우리는 scaffold 명령을 사용하여 이 모델을 생성합니다.rails new calendula --template "https://www.railsbytes.com/script/z0gsd8" --skip-spring --skip-action-text --skip-active-storage --skip-action-mailer --skip-action-mailbox --skip-sprockets --skip-webpack-install
bin/rails g scaffold CalendarEvent occurs_at:datetime description:text color:string
index
템플릿에서 우리는 두 부분만 보여 줍니다. 하나는 새로운 이벤트를 추가하는 폼을 포함하고, 다른 하나는 달력 격자 자체를 포함합니다.우리는 @dates
를 주입했다. 이것은 우리가 표시하고자 하는 Date
개의 대상의 집합과 @calendar_events
, 이 날짜 범위 내의 모든 것CalendarEvent
일 뿐이다.<!-- app/views/calendar_events/index.html.erb -->
<div id="calendar-form">
<%= render "form", calendar_event: @calendar_event %>
</div>
<div class="w-screen p-8">
<div class="grid grid-cols-7 w-full" id="calendar-grid">
<%= render partial: "calendar_events/calendar_grid",
locals: {dates: @dates,
calendar_events: @calendar_events} %>
</div>
</div>
_calendar_grid.html.erb
부분적 표현_date.html.erb
부분적 집합:<!-- app/views/calendar_events/_calendar_grid.html.erb -->
<% Date::DAYNAMES.each do |name| %>
<div class="bg-gray-500 text-white p-3"><%= name %></div>
<% end %>
<%= render partial: "date", collection: dates,
locals: { calendar_events: calendar_events } %>
<!-- app/views/calendar_events/_date.html.erb -->
<div class="flex flex-col justify-between border h-32 p-3">
<span class="text-small font-bold"><%= date %></span>
<% (calendar_events[date] || []).each do |calendar_event| %>
<span class="bg-<%= calendar_event.color %>-700 rounded text-white px-4 py-2">
<%= calendar_event.description %>
</span>
<% end %>
</div>
_form.html.erb
부분은 비계에서 생성된 표준 부분일 뿐 특별한 점은 없다.그러나 calendar_events_controller.rb
에서 우리는 몇 가지 일을 바꿔야 한다.(모든 실례 변수를 설정하는 부분은 생략합니다. 관심이 있으시면 demo repo 에서 찾을 수 있습니다.)우선, 우리는 성공적으로 삽입할 때 표준 리디렉션을 삭제합니다.그 다음으로 우리는 이 기능을 주입하기 위해
CableReady::Broadcaster
모듈을 포함해야 한다.그런 다음 @calendar_events
브로드캐스트를 사용하여 morph
새로 생성된 이벤트를 포함하는 #calendar-grid
및 CableReady
을 다시 가져옵니다.# app/controllers/calendar_events_controller.rb
class CalendarEventsController < ApplicationController
include CableReady::Broadcaster
# ...
def create
@calendar_event = CalendarEvent.new(calendar_event_params)
if @calendar_event.save
@calendar_events = CalendarEvent.where(occurs_at: @date_range)
.order(:occurs_at)
.group_by(&:occurs_at_date)
cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
locals: {dates: @dates,
calendar_events: @calendar_events}),
children_only: true})
cable_ready.broadcast
else
render :index
end
end
# ...end
물론 채널을 만들고 해당하는 JavaScript 채널을 구성하는 데 필요한 CableReady
설정이 필요합니다.bin/rails g channel CalendarEvents
# app/channels/calendar_events_channel.rb
class CalendarEventsChannel < ApplicationCable::Channel
def subscribed
stream_from "CalendarEvents"
end
end
// app/javascript/channels/calendar_events_channel.js
import CableReady from "cable_ready";
import consumer from "./consumer";
consumer.subscriptions.create("CalendarEventsChannel", {
received(data) {
if (data.cableReady) CableReady.perform(data.operations);
}
});
결과적으로 볼 때, 우리는 insert가 예상대로 작동하는 것을 관찰할 수 있으며, 폼만 리셋되지 않았다.그러나 이것은 우리가 정상적인 요청/응답 주기를 처리하지 않았기 때문에 우리는 반드시 스스로 이 일을 해야 한다.두 번째
CableReady
동작을 추가하여 #calendar-form
부분을 교체합시다.class CalendarEventsController < ApplicationControllerinclude CableReady::Broadcaster
# ...
def create
@calendar_event = CalendarEvent.new(calendar_event_params)
if @calendar_event.save
@calendar_events = CalendarEvent.where(occurs_at: @date_range)
.order(:occurs_at)
.group_by(&:occurs_at_date)
cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
locals: {dates: @dates,
calendar_events: @calendar_events}),
children_only: true}
)
# -----> we also have to reset the form <-----
cable_ready["CalendarEvents"].inner_html({selector: "#calendar-form",
html: CalendarEventsController.render(partial: "form",
locals: {calendar_event: CalendarEvent.new})
})
cable_ready.broadcast
else
render :index
end
end
# ...end
주의, 나는 여기에서 일부러 inner_html
조작을 사용한다. 왜냐하면 morph
입력의 현재 상태가 DOM에 반영되지 않았기 때문이다.순전히 Stimulus Reflex를 기반으로 하는 방법에 비해 이런 방법의 추가적인 장점은 사이트의 다른 부분에서 우리의 모델을 업데이트할 수 있다는 것이다.만약 우리가 이 논리를
ActiveJob
(또는 서비스 대상, 이렇게 하는 경향이 있는 사람들에게) 격리한다면, 우리는 그것을 더욱 쉽게 테스트하고 응용 프로그램의 다른 부분에서 다시 사용할 수 있다.bin/rails g job StreamCalendarEvents
# app/controllers/calendar_events_controller.rb
def create
@calendar_event = CalendarEvent.new(calendar_event_params)
if @calendar_event.save
@calendar_events = CalendarEvent.where(occurs_at: @date_range)
.order(:occurs_at)
.group_by(&:occurs_at_date)
StreamCalendarEventsJob.perform_now(dates: @dates,
calendar_events: @calendar_events,
date_range: @date_range)
else
render :index
end
end
# app/jobs/stream_workloads_job.rb
class StreamCalendarEventsJob < ApplicationJob
include CableReady::Broadcaster
queue_as :default
def perform(dates:, calendar_events:, date_range:)
cable_ready["CalendarEvents"].morph({selector: "#calendar-grid",
html: CalendarEventsController.render(partial: 'calendar_events/calendar_grid',
locals: {dates: dates,
calendar_events: calendar_events,
date_range: date_range}),
children_only: true})
# we also have to reset the form
cable_ready["CalendarEvents"].inner_html({selector: "#calendar-form",
html: CalendarEventsController.render(partial: "form",
locals: {calendar_event: CalendarEvent.new})
})
cable_ready.broadcast
end
end
우리는 두 번째 부분에서 이 문제를 다시 토론할 것이다 ActiveJob
.2. 문제를 사용하여 StimulusReflex 양식을 건조하여 제출
만약 당신이 주의하지 않는다면, 순수한 반사에 기반한 폼 제출은 산탄총 수술 냄새의 원천이 될 수 있다. 왜냐하면 당신은 결국 중복된 논리가 많기 때문이다.물론 표준 Rails 컨트롤러도 자원을 바탕으로 하지만
resource.rb
, resources_controller.rb
, resources_reflex.rb
구조를 만들면 유지보수할 수 없는 길을 빨리 안내할 수 있습니다.위에서 이 예시를 복습하고 폼 처리 논리를 반사에 넣읍시다.패턴 재사용
만약 내가 이 모델을 어떻게 다시 사용하는지 시범을 보이지 않았다면, 본 연습의 의미는 무엇입니까?이를 위해 우리는 달력에 국정공휴일 (나는 여기에서 사용) 과 holidaysgem) 을 포함하기를 희망한다고 가정한다.우선, 우리는 이 데이터를 저장하기 위한 모델이 필요하다.우리는 표준 ActiveRecord를 사용하고 "단례"방식으로 사용할 것입니다. 즉, 하나의 실례만 있다고 가정합니다.
bin/rails g model Settings holiday_region:string
우리는 간단한 하단 목록을 추가하여 한 나라를 선택하고 싶기 때문에, 우리는 단지 추가하기만 하면 된다<div class="grid grid-cols-2 w-full">
<div id="calendar-form"
data-controller="reflex-form"
data-reflex-form-reflex-name="CalendarEventsReflex">
<%= render "form", calendar_event: @calendar_event %>
</div>
<div id="settings-form"
data-controller="reflex-form"
data-reflex-form-reflex-name="SettingsReflex">
<%= form_with(model: Setting.first, method: :put, url: "#", class: "m-8",
data: {signed_id: Setting.first.to_sgid.to_s, target: "reflex-form.form"}) do |form| %>
<div class="max-w-lg rounded-md shadow-sm sm:max-w-xs">
<%= form.label :holiday_region %>
<%= form.country_select :holiday_region,
{only: Holidays.available_regions.map(&:to_s).map(&:upcase)},
class: "form-select block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5",
data: {action: "change->reflex-form#submit", reflex_dataset: "combined"} %>
</div>
<% end %>
</div>
</div>
우리에게calendar_events/index.html.erb
.우리가 사용하는 자극 컨트롤러는 위에서 만든 것과 같다는 것을 주의하세요!또한 Settings
인스턴스에 대한 서명 글로벌 ID(SGID)를 전달합니다.요소의 데이터 세트에 서명 ID가 있는지 확인하기 위해 Submittable
관심사를 확장해야 합니다.만약 그렇다면, 우리는 기록을 검색하고 변경된 속성을 분배합니다.만약 그렇지 않다면, 우리의 창작 논리는 변하지 않을 것이다.# app/reflexes/concerns/submittable.rb
module Submittable
# ...
included do
before_reflex do
if element.dataset.signed_id.present?
@resource = GlobalID::Locator.locate_signed(element.dataset.signed_id)
@resource.assign_attributes(submit_params)
else
resource_class = Rails.application.message_verifier("calendar_events")
.verify(element.dataset.resource)
.safe_constantize
@resource = resource_class.new(submit_params)
end
end
end
# ...
end
마지막으로 중요한 것은 SettingsReflex
관심사를 포함하여 Submittable
를 만들어야 한다는 것이다.bin/rails g stimulus_reflex SettingsReflex
# app/reflexes/settings_reflex.rb
class SettingsReflex < ApplicationReflex
include Submittable
before_reflex :fetch_dates
after_reflex do
@calendar_events = CalendarEvent.where(occurs_at: @date_range)
.order(:occurs_at)
.group_by(&:occurs_at_date)
StreamCalendarEventsJob.perform_now(dates: @dates,
calendar_events: @calendar_events,
date_range: @date_range)
end
private
def submit_params
params.require(:setting).permit(:holiday_region)
end
def fetch_dates
# ...
end
end
이 모델의 장점은 실제 반사 논리가 DOM 수리를 처리하는 것으로 간소화되고 심지어 문제를 추출할 수 있다는 데 있다. 왜냐하면 이것은 위와 완전히 같기 때문이다.휴일 이름을 표시하려면 _date.html.erb
섹션만 수정해야 합니다.<div class="flex flex-col justify-between border h-32 p-3 <%= "bg-gray-200" unless date_range.include? date %>">
<span class="text-small font-bold"><%= date %></span>
<% holidays = Holidays.on(date, Setting.first.holiday_region.downcase.to_sym) %>
<% unless holidays.empty? %>
<span class="bg-blue-600 rounded text-white px-4 py-2">
<%= holidays.map { |h| h[:name] }.join(" ") %>
</span>
<% end %>
<% (calendar_events[date] || []).each do |calendar_event| %>
<span class="bg-<%= calendar_event.color %>-700 rounded text-white px-4 py-2"><%= calendar_event.description %> </span>
<% end %>
</div>
최종 결과는 다음과 같습니다.country_select
결론
나는 두 가지 폼 제출 모델을 소개했는데 사용 과CableReady, 응용 프로그램에서 실현할 때 이 두 가지 모델을 고려해야 한다.WebSocket을 전송하기 위해서는 이미 만들어진 모델을 다시 고려해야 하지만, 잘하면 더욱 내재된 코드 라이브러리가 생길 수 있습니다.
Reference
이 문제에 관하여(StimulusReflex 양식 제출의 두 가지 모드), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/julianrubisch/two-patterns-for-stimulusreflex-form-submissions-5h2c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)