Multistep Form (Wizard) — Rails Recipes

Feb 12 2023
Cấu tạo của biểu mẫu nhiều bước Biểu mẫu nhiều bước là một biến thể của trình hướng dẫn. Trình hướng dẫn là một công cụ khả dụng.

Giải phẫu của một hình thức nhiều bước

Biểu mẫu nhiều bước là một biến thể của trình hướng dẫn. Trình hướng dẫn là một công cụ khả dụng. Nó thể hiện một nhiệm vụ dưới dạng một tập hợp các bước được sắp xếp theo thứ tự quy định . Người dùng phải hoàn thành từng bước theo thứ tự nhất định để đạt được mục tiêu.

Biểu mẫu nhiều bước chia các biểu mẫu phức tạp thành các phần nhỏ hơn, nhóm các trường của chúng theo các tiêu chí khác nhau . Điều này dẫn đến trải nghiệm điền biểu mẫu ít khó khăn hơn cho người dùng cuối. Khi được kết hợp với các hướng dẫn, biểu mẫu nhiều bước làm tăng đáng kể mức độ dễ sử dụng của các ứng dụng của chúng tôi .

So sánh biểu mẫu chính quy với biểu mẫu nhiều bước

Hạn chế chính của họ là hiệu quả . Việc điền vào một biểu mẫu nhiều bước thường mất nhiều thời gian hơn so với việc điền vào một biểu mẫu duy nhất (nếu bạn đã quen thuộc với quy trình này). Điều này là do điều hướng giữa các bước không tồn tại ở dạng tiêu chuẩn.

Cách bạn triển khai điều hướng này có thể tác động đáng kể đến trải nghiệm người dùng : bạn sẽ cho phép người dùng điều hướng tới và lui tới bất kỳ bước nào trong trình hướng dẫn hay chỉ tới các bước tiếp theo và trước đó? thông tin bước trước đó có hiển thị trong bước hiện tại không? Các lựa chọn thiết kế không phù hợp có thể dẫn đến trải nghiệm điền biểu mẫu chậm và khó chịu khiến người dùng không muốn điền vào biểu mẫu và thậm chí sử dụng hệ thống của chúng tôi.

Công thức này sẽ hướng dẫn bạn cách tạo các biểu mẫu nhiều bước có thể tùy chỉnh và tái sử dụng trong Rails.

Các thách thức

Trong ứng dụng của chúng tôi, chúng tôi quản lý Projects. Một dự án được xác định bởi a namevà a description. A Projectcó thể có nhiều Booksliên kết với nó. A Bookđược xác định bởi a titlevà a description. Chúng tôi muốn triển khai biểu mẫu nhiều bước để tạo tệp Projects. Bước đầu tiên sẽ yêu cầu thông tin cơ bản của dự án và bước thứ hai, tùy chọn, thông tin về một Bookliên kết với Project.

Công thức

điều kiện tiên quyết

  • (Bắt buộc) Đã cài đặt Rails ViewComponentgem : ViewComponentbọc Rails viewsthành các đối tượng. Điều này giúp kiểm tra chế độ xem của chúng tôi dễ dàng hơn và cung cấp các phương pháp bổ sung để mang lại trải nghiệm tốt hơn cho nhà phát triển khi làm việc với chúng.
  • (Tùy chọn) đã cài đặt plugin : khung này loại bỏ nhu cầu viết mã của chúng tôi bằng cách cung cấp một tập hợp các lớp mạnh mẽ.TailwindCSS PostCSSCSSCSSCSS

Trong Rails, chúng tôi sử dụng biểu mẫu để tạo và cập nhật bản ghi. Các hoạt động này được thực hiện thông qua POST, PUTvà PATCH HTTPcác yêu cầu được gửi từ một biểu mẫu duy nhất. Các yêu cầu được xử lý theo quy trình tương tự:

  1. Lớp Rails Controllerphân tích dữ liệu yêu cầu biểu mẫu và yêu cầu Modellớp thực hiện thao tác được yêu cầu với thông tin được trích xuất.
  2. Lớp Rails Modelxử lý dữ liệu yêu cầu, xác thực nó và thực hiện thao tác được yêu cầu nếu nó hợp lệ. Kết quả của hoạt động được gửi lại từ Modeltệp Controller.
  3. ControllerLớp Rails yêu cầu Viewlớp hiển thị phản hồi dựa trên kết quả hoạt động. Các hoạt động thành công hiển thị chế độ xem kết quả hoạt động. Các hoạt động không thành công hiển thị biểu mẫu đã gửi với dữ liệu yêu cầu biểu mẫu và thông báo lỗi.
Logic nghiệp vụ của một hình thức nhiều bước

Nhiệm vụ đầu tiên của chúng tôi là triển khai logic để xác thực xem tất cả các bước trong biểu mẫu nhiều bước đã được hoàn thành hay chưa.

Rails ActiveRecord::Validations API , được bao gồm trong ApplicationRecord Modelcác lớp, định nghĩa valid?phương thức. Phương pháp này được thực hiện trên mọi thao tác tạo và cập nhật . Nó đánh giá tất cả Modelcác điều kiện xác thực, trả lại truekhi tất cả các điều kiện xác thực vượt qua. Chỉ valid?các hoạt động có thể lưu giữ thông tin trong cơ sở dữ liệu.

Việc xác thực cho biểu mẫu nhiều bước của chúng tôi phải kiểm tra chỉ mục bước hiện tại trong yêu cầu biểu mẫu tương ứng với bước cuối cùng của nó . Chúng ta cần hai thuộc tính để theo dõi các biến này: current_stepđể lưu trữ chỉ số bước hiện tại và total_stepslưu trữ số bước ở dạng nhiều bước. Vì cả xác thực và thuộc tính đều giống nhau cho tất cả các mô hình, nên chúng ta có thể gói gọn chúng trong một cái concerncó thể được sử dụng lại trên Modelcác lớp của chúng ta:

# app/models/concerns/multistep_form_model.rb
​
module MultistepFormModel
  ERROR_ATTRIBUTE = :current_step
  ERROR_DETAIL = :incompleted_multistep_form
​
  extend ActiveSupport::Concern
​
  included do
    # Defines two new attributes in including Model class
    attr_accessor :current_step, :total_steps
​
    # Adds new validation rule in including Model class
    validate :all_multistep_form_steps_completed
  end
​
  private
​
  def all_multistep_form_steps_completed? = current_step == total_steps
​
  def all_multistep_form_steps_completed
    errors.add(ERROR_ATTRIBUTE, ERROR_DETAIL) unless all_multistep_form_steps_completed?
  end
end

# app/models/concerns/multistep_form_model.rb
​
module MultistepFormModel
  ...
  included do
    # Adds new validation rule in including Model class
    validate :all_multistep_form_steps_completed,
      if: -> { current_step.present? && total_steps.present? }
  end
  ...
end

Cuối cùng, chúng tôi có thể cập nhật các mô hình của mình bằng cách đưa vào biểu mẫu nhiều bước concernđể chúng tôi có thể sử dụng chúng với biểu mẫu nhiều bước của mình:

# app/models/project.rb
​
class Project < ApplicationRecord
  ...
  # Now our Project class has current_step and total_steps attributes, and the multistep validation rule
  include MultistepFormModel
  ...
end

Controllerschịu trách nhiệm phân tích dữ liệu yêu cầu biểu mẫu và chuyển nó xuống lớp Model. Để làm việc với các biểu mẫu nhiều bước, chúng ta cần cập nhật các tham số mạnh của bộ điều khiển để đảm bảo các tham số curret_stepvà total_stepsđược phân tích cú pháp và chuyển đến Modellớp.

# app/controllers/projects_controller.rb

class ProjectsController < ApplicationController
  ...
  private
  ...
  def project_params
    params
      .require(:project)
      .permit(:name, :description, :total_steps, :current_step, books_attributes: [:title, :description])
  end
  ...
end

Hãy phân tách một biểu mẫu nhiều bước trong ViewComponents. Biểu mẫu nhiều bước là biểu mẫu được chia thành nhiều phần (các bước). Về mặt thành phần, điều này có thể được mô hình hóa như một MultistepFormComponentnội dung bao gồm nhiều tệp StepComponents.

Mỗi bước chứa một tập hợp các trường biểu mẫu. Một bước được hoàn thành khi tất cả các trường bắt buộc của bước được điền dữ liệu hợp lệ. A StepComponenthiển thị các trường tương ứng với một bước biểu mẫu gồm nhiều bước và cung cấp một giao diện đơn giản để biết, với dữ liệu biểu mẫu, nếu bước mà biểu mẫu đại diện đã hoàn tất .

Trước đây chúng ta đã nói về valid?phương thức và cách nó xác định xem dữ liệu biểu mẫu có hợp lệ hay không. Ngoài hành vi này, còn cập nhật đối tượng bằng từ điển chứa lỗi xác thực . Nó có thể được truy cập thông qua , trả về một đối tượng. valid?Modelmodel.errorsActiveModel::Errors

Để xác định xem a StepComponentđã hoàn thành hay chưa, chúng ta có thể sử dụng form.object.errorsđối tượng. Đưa ra một bước với một tập hợp các trường đầu vào, chúng tôi có thể xác nhận bước đã hoàn thành nếu đối form.object.errorstượng không bao gồm bất kỳ lỗi nào liên quan đến các trường bước.

# How to check whether a step is completed.
def completed?(form_object)
  input_attributes.none? { |attr| form_object.errors.key?(attr) }
end

  1. Một ActionView::Helpers::FormBuilderđối tượng để hiển thị các trường bước và tìm nạp dữ liệu biểu mẫu thông qua .objectphương thức.
  2. Một danh sách các input_attributestrường mà nó hiển thị.
  3. Không được tiết lộ từ phương pháp nhưng rõ ràng, chúng ta cũng cần biết chỉ số của bước ở dạng nhiều bước.

# app/components/common/multistep_form_component/step_component.rb

module Common
  class MultistepFormComponent::StepComponent < ApplicationComponent
    class << self
      def title = raise "StepComponent #{name} does not define title method"

      def input_attributes = raise "StepComponent #{name} does not define list of input attributes"

      def completed?(form_object)
        input_attributes.none? { |attr| form_object.errors.key?(attr) }
      end
    end

    attr_reader :multistep_component, :index, :form

    def initialize(multistep_component:, index:, html_attributes: {})
      @index = index
      @form = multistep_component.form
      @multistep_component = multistep_component
      super(html_attributes:)
    end

    def call
      raise "
        #{self.class.name} is a StepComponent. StepComponents are abstract by default.
        Components inheriting from it must define its own template or call method.
      "
    end

    def current_step? = index == multistep_component.current_step

    def completed? = self.class.completed?(multistep_component.form.object)
  end
end

Đây StepComponentlà một thành phần trừu tượng . Nó không được thiết kế để hiển thị mà được mở rộng bởi StepComponentcác lớp khác. Đối với Projectcác bước biểu mẫu của chúng tôi, chúng tôi sẽ xác định hai thành phần mới, mỗi thành phần cho mỗi bước chúng tôi muốn kết xuất:

# app/components/common/projects/form_component/project_details_component.rb

module Common
  class Projects::FormComponent::ProjectDetailsComponent < Common::MultistepFormComponent::StepComponent
    class << self
      def title = "Project Details"

      def input_attributes = %w[name]
    end
    ...
  end
end
# app/components/common/projects/form_component/project_deliverables_component.rb

module Common
  class Projects::FormComponent::ProjectDeliverablesComponent < Common::MultistepFormComponent::StepComponent
    class << self
      def title = "Project Deliverables"

      # Example of a step component with nested attributes / nested form fields
      def input_attributes = %w[books.title]
    end
    ...
  end
end

  • Nơi URLdữ liệu biểu mẫu sẽ được gửi.
  • Và chính dữ liệu biểu mẫu. Dữ liệu này đại diện cho bản ghi chúng tôi muốn tạo hoặc cập nhật. Trong mỗi lần lặp lại của chu kỳ biểu mẫu nhiều bước, dữ liệu sẽ đại diện cho thông tin do người dùng cung cấp trong lần gửi biểu mẫu trước đó.

# app/components/common/multistep_form_component.rb

module Common
  class MultistepFormComponent < ApplicationComponent
    class << self
      def steps = raise "#{name} MultistepFormComponent does not define steps class model"
    end

    delegate :steps, to: :class

    attr_reader :form_url, :back_url, :model, :form

    def initialize(form_url:, back_url:, model:, html_attributes: {})
      @form_url = form_url
      @back_url = back_url
      @model = model
      super(html_attributes:)
    end
    
    def call
      form_with(url:, model: model) do |form|
        @form = form

        concat(form.hidden_field(:total_steps, value: steps.count))
        concat(form.hidden_field(:current_step, value: current_step_index))

        steps.each_with_index do |step, index|
          concat(render step.new(multistep_form: self, index:))
        end
      end
    end
  end
end

Bây giờ chúng ta có thể định nghĩa dự án của mình FormComponentbằng cách mở rộng lớp và chỉ định bước của StepComponentcác lớp xác định biểu mẫu của nó:

# app/components/common/projects/form_component.rb

module Common
  class Projects::FormComponent < Common::MultistepFormComponent
    class << self
      def steps = [ProjectDetailsComponent, ProjectDeliverablesComponent]
    end
  end
end

Có nhiều cách để điều hướng giữa các bước: một số chỉ cho phép điều hướng về phía trước cho đến khi hoàn thành tất cả các bước; ở những người khác, có thể điều hướng qua lại từng bước một, và những người khác thậm chí còn cho phép hiển thị nhiều bước trước đó cùng một lúc. Biểu mẫu nhiều bước của chúng tôi phải đủ linh hoạt để thực hiện tất cả các chiến lược điều hướng khả thi . Tuy nhiên, nó không nên thực hiện bất kỳ chiến lược điều hướng nào. Chúng tôi sẽ ủy thác nó cho các thành phần mở rộng MultistepFormComponentlớp.

Chúng tôi có thể ẩn và hiển thị các bước bằng cách sử dụng đầu vào checkboxvà đơn giản radio HTML. A checkboxlà một hộp đơn giản có thể là checked. A radiolà một nhóm checkboxestrong đó chỉ có thể là một checked. CSS có thể nhắm mục tiêu trạng thái đã kiểm tra của các phần tử này thông qua :checkedbộ chọn. Điều này cho phép HTMLcác phần tử kiểu dáng dựa trên trạng thái của một checkboxhoặc radio.

Chúng ta có thể gán a radiovà checkboxđầu vào cho từng bước trong biểu mẫu của mình và tạo kiểu cho bước đó theo trạng thái của nó để nó chỉ hiển thị khi đầu vào tương ứng được chọn. Chúng tôi sẽ xác định một phương thức mới để bọc từng trường hợp bước trong một thẻ chứa radio và hộp kiểm để đạt được nó.

# app/components/common/multistep_form_component.rb

...
  def call
    ...
      steps.each_with_index do |step, index|
        concat(step_wrapper(step, index) { render step.new(multistep_form: self, index:) })
      end
    ...
  end

  def radio_id(step_index) = "#{object_id}_radio_#{step_index}"

  def checkbox_id(step_index) = "#{object_id}_radio_#{step_index}"

  def step_wrapper(step, index, html_attributes: {}, &content)
    tag.div(**html_attributes) do
      concat(
        radio_button_tag(
          radio_id(nil),
          1,
          current_step_index == index,
          id: radio_id(index),
          class: "hidden peer/radio"
        )
      )
      concat(
        check_box_tag(
          checkbox_id(index),
          1,
          current_step_index == index,
          id: checkbox_id(index),
          class: "hidden peer/checkbox"
        )
      )
      concat(capture(&content))
    end
  end
...

Thành phần mở rộng MultistepFormComponentcó thể cập nhật phương pháp này xác định các thành phần chung có trong tất cả các bước và sử dụng peersđể hiển thị và ẩn các bước dựa trên checkedtrạng thái.

# app/components/common/projects/form_component.rb

...
def step_wrapper(step, index, html_attributes: {}, &content)
  selected = index == current_step_index

  options = (selected ? { "aria-current": "step" } : {})

  super(step, index, html_attributes: options) do
    concat(
      tag.label(
        for: index <= current_step_index ? radio_id(index) : "",
        class: "
          w-full inline-flex items-center px-4 py-2 gap-4 border-2 text-zinc-500 border-zinc-500
          peer-checked/radio:bg-zinc-500 peer-checked/radio:text-white
        "
      ) do
        concat(tag.p(index + 1, class: "h-6 w-6 text-center rounded-full text-white bg-zinc-500"))
        concat(tag.p(step.title))
      end
    )
    concat(
      tag.div(
        class: "
          hidden px-4 py-6
          peer-checked/radio:block
        "
      ) do
        concat(capture(&content))
        concat(
          tag.div(class: "mt-5 w-full inline-flex gap-2 items-center justify-end") do
            concat(link_to("cancel", back_url, class: "px-4 py-2 bg-zinc-500 text-white")) if index == 0
            concat(tag.label("previous", for: radio_id(index - 1), class: "px-4 py-2 bg-zinc-500 text-white")) if index > 0
            concat(form.submit("next", class: "cursor-pointer px-4 py-2 bg-zinc-500 text-white")) if index < (steps.count - 1)
            concat(form.submit("create project", class: "cursor-pointer px-4 py-2 bg-zinc-500 text-white")) if index == (steps.count - 1)
          end
        )
      end
    )
  end
end
...

Trong dòng 31 có logic để điều hướng lùi lại một bước trong biểu mẫu. Chúng tôi sử dụng MultistepFormComponent radio_idphương pháp để ánh xạ nhãn tới radiođầu vào từ bước trước. Khi chúng tôi nhấp vào nhãn, radionút sẽ được kiểm tra. Thao tác này sẽ ẩn bước hiện tại và hiển thị bước trước đó.

Để điều hướng về phía trước, chúng tôi luôn cần gửi biểu mẫu. Nếu sau khi gửi biểu mẫu, tất cả các bước trước đó đã được hoàn thành, thì nhiều bước sẽ hiển thị bước tiếp theo. Nếu không, biểu mẫu sẽ quay lại bước chưa hoàn thành.

# app/components/common/multistep_form_component.rb
...
def first_incompleted_step_index
  steps.find_index { |s| !s.completed?(model) }
end

def current_step_index
  previous_step = model.current_step&.to_i

  @current_step_index =
    if model.current_step.nil?
      0
    else
      next_step = previous_step + (steps[previous_step].completed?(model) ? 1 : 0)
      [next_step, first_incompleted_step_index].compact.min
    end
end
...

# app/components/common/multistep_form_component.rb
...
def step_wrapper(step, index, html_attributes: {}, &content)
  tag.div(**html_attributes) do
    concat(
      radio_button_tag(
        ...
        # If true radio is checked
        current_step_index == index,
        ...
      )
    )
    concat(
      check_box_tag(
        ...
        # If true checkbox is checked
        current_step_index == index,
        ...
      )
    )
    concat(capture(&content))
  end
end
...

Chúng tôi sẽ thêm một phương thức mới chịu trách nhiệm xóa tất cả các thông báo lỗi liên quan đến các bước mà người dùng không truy cập và một phương thức khác để xóa lỗi current_step. Để đạt được điều này, chúng tôi sẽ thêm thuộc tính thứ ba chịu trách nhiệm theo dõi bước mới nhất trong biểu mẫu mà người dùng đã điền để chúng tôi có thể xác định bước nào chưa được truy cập.

# app/components/common/multistep_form_component.rb
...
def call
    ...
    concat(form.hidden_field(:total_steps, value: steps.count))
    concat(form.hidden_field(:latest_step, value: latest_step_index))
    concat(form.hidden_field(:current_step, value: current_step_index))
    ...
  end
end
...
def latest_step_index
  @latest_step_index =
    if model.latest_step.nil?
      0
    else
      [current_step_index, model.latest_step].max
    end
end
...

Có hai yếu tố chính để kiểm tra biểu mẫu nhiều bước của chúng tôi: Modelmối quan tâm của chúng tôi và MultistepFormComponent. Các thử nghiệm hệ thống và đơn vị bổ sung cho bộ điều khiển, thành phần dự án và toàn bộ luồng người dùng tạo dự án cũng rất cần thiết. Tuy nhiên, chúng không liên quan đến bất kỳ cấu hình đặc biệt nào như những cấu hình khác.

Để kiểm tra MultistepFormModelmối quan tâm, chúng tôi có thể thử kiểm tra current_stepcác xác nhận thông qua Projectmô hình. Đây không phải là một phương pháp hay vì nó có thể dẫn đến các bài kiểm tra không ổn định: chúng ta không được kiểm tra các mối quan tâm thông qua Modelsviệc có thể không bao gồm hoặc mở rộng chúng trong tương lai.

Thay vào đó, chúng ta có thể định nghĩa một Modellớp phụ trợ với mục đích duy nhất là kiểm tra mối quan tâm, sau đó chạy thử nghiệm với nó. Cách tiếp cận này có hai lợi ích: kiểm tra mối quan tâm mà không cần dựa vào Modelcác lớp từ logic nghiệp vụ của chúng tôi và ghi lại cách mối quan tâm có thể được sử dụng trong Modelcác lớp của chúng tôi.

class MultistepFormModelTest < ActiveSupport::TestCase
  class ValidMultistepFormMode
    include ActiveModel::Model
    include MultistepFormModel
  end

  def setup
    @model = ValidMultistepFormMode.new
  end

  # Validations
  # -----------
  test "MultistepFormModel does not apply step validation if total and current attibutes are not specified" do
    assert_nil @model.current_step
    assert_nil @model.total_steps
    assert @model.valid?
  end

  test "MultistepFormModel is valid only when total and current steps are the same" do
    @model.current_step = 1
    @model.total_steps = 2
    refute @model.valid?

    @model.current_step = 3
    @model.total_steps = 3
    assert @model.valid?
  end

  test "MultistepFormModel sets custom error attribute and detail when total and current step attributes are invalid" do
    @model.current_step = 1
    @model.total_steps = 2
    refute @model.valid?

    assert_equal @model.errors.first.attribute, MultistepFormModel::ERROR_ATTRIBUTE
    assert_equal @model.errors.first.detail, MultistepFormModel::ERROR_DETAIL
  end
end

class Common::MultistepFormComponentTest < ViewComponent::TestCase
  class SampleModel
    include ActiveModel::Model
    include MultistepFormModel

    attr_accessor :attribute_1, :attribute_2, :attribute_3

    validates_presence_of :attribute_1
    validates_presence_of :attribute_2
    validates_presence_of :attribute_3
  end

  class TestMultistepFormComponent < Common::MultistepFormComponent
    class << self
      def steps = [FirstTestStepComponent, SecondTestStepComponent, ThirdTestStepComponent]
    end
  end

  class FirstTestStepComponent < Common::MultistepFormComponent::StepComponent
    class << self
      def title = "First Step"
      def input_attributes = %i[attribute_1]
    end

    def call
      tag.div(**wrapper_attributes)
    end
  end

  class SecondTestStepComponent < Common::MultistepFormComponent::StepComponent
    class << self
      def title = "Second Step"
      def input_attributes = %i[attribute_2]
    end

    def call
      tag.div(**wrapper_attributes)
    end
  end

  class ThirdTestStepComponent < Common::MultistepFormComponent::StepComponent
    class << self
      def title = "Third Step"
      def input_attributes = %i[attribute_3]
    end

    def call
      tag.div(**wrapper_attributes)
    end
  end

  def setup
    @component = TestMultistepFormComponent
  end

  test "Renders multistep form" do
    render_inline(@component.new(form_url: "test", back_url: "test", model: SampleModel.new))
    assert_selector(".#{@component.component_class}")
  end

  test "Renders multistep form steps" do
    render_inline(@component.new(form_url: "test", back_url: "test", model: SampleModel.new))
    assert_selector(".#{FirstTestStepComponent.component_class}", visible: :all)
    assert_selector(".#{SecondTestStepComponent.component_class}", visible: :all)
    assert_selector(".#{ThirdTestStepComponent.component_class}", visible: :all)
  end

  test "Selects first step as current step if current_step value is nil" do
    model = SampleModel.new
    model.valid?
    result = @component.new(form_url: "test", back_url: "test", model:).current_step_index
    assert_equal 0, result
  end
  ...
end

Multisteps trong Rails được liên kết chặt chẽ với các lớp mô hình và bộ điều khiển. Để giữ cho logic kinh doanh của chúng tôi tách biệt nhất có thể với thành phần này, chúng tôi dựa vào concernsvà các thành phần trừu tượng. Các công cụ này hoạt động như một logic plug & play giúp sử dụng nhiều bước cực kỳ dễ dàng trong bất kỳ phần nào của ứng dụng của chúng tôi.

Bản chất trừu tượng của MultistepFormComponentđơn giản hóa nhiệm vụ triển khai các biểu mẫu với các logic điều hướng và thiết kế khác nhau, bằng cách ủy thác việc triển khai logic này cho các thành phần mở rộng nó.

Cuối cùng, bằng cách viết một số thử nghiệm chung dựa trên thử nghiệm bất khả tri Modelvà Componentcác lớp, chúng tôi đảm bảo hoạt động chính xác của nhiều bước trên tất cả các ứng dụng của chúng tôi và cung cấp một ví dụ chi tiết và được ghi chép đầy đủ về cách các nhà phát triển có thể sử dụng logic nhiều bước trong tương lai.

Mã trong Github

  • 140 biểu mẫu nhiều bước của AlbertoHdezCerezo · Yêu cầu kéo #150 · Albert-Markdown-Editor/Albert
  • Cách thực hiện: Tạo biểu mẫu có thể truy cập — Dự án A11Y
  • Trong các biểu mẫu có nhiều bước, hãy xác định bước hiện tại bằng aria-current

Nếu bạn thấy mình bị lạc ở bất kỳ điểm nào trong bài viết hoặc cố gắng sao chép giải pháp của tôi mà không có kết quả, bạn có thể liên hệ với tôi trong phần bình luận. Tôi sẽ vui lòng cung cấp thêm thông tin và cố gắng làm rõ các câu hỏi mở. Tôi sẽ sớm triển khai các kênh bổ sung để chia sẻ phản hồi và câu hỏi.

Nếu bạn đã đạt đến điểm này, cảm ơn bạn đã dành thời gian và sự kiên nhẫn. Tôi hy vọng bạn tìm thấy hướng dẫn của tôi hữu ích!