Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forms using yaaf #3290

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ gem "sneakers", "~> 2.11" # rabbitMQ background processing
gem "state_machines-activerecord"
gem "strip_attributes"
gem "turbo-rails", "~> 1.0"
gem "view_component", "~> 2.56.2" # https://github.com/github/view_component/issues/1390
gem "view_component", "~> 3.5"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprise! We're using an old version of View Components, which causes all sorts of problems for form rendering.

gem "whenever", require: false # Work around https://github.com/javan/whenever/issues/831
gem "yaaf" # Form objects
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A tiny gem that helps with form objects.

gem "zipline", "~> 1.4"
11 changes: 8 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,9 @@ GEM
uber (0.1.0)
unicode-display_width (2.4.2)
version_gem (1.1.3)
view_component (2.56.2)
activesupport (>= 5.0.0, < 8.0)
view_component (3.5.0)
activesupport (>= 5.2.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
virtus (2.0.0)
axiom-types (~> 0.1)
Expand All @@ -646,6 +647,9 @@ GEM
chronic (>= 0.6.3)
xpath (3.2.0)
nokogiri (~> 1.8)
yaaf (2.2.0)
activemodel (>= 5.2)
activerecord (>= 5.2)
zeitwerk (2.6.11)
zip_tricks (5.6.0)
zipline (1.5.0)
Expand Down Expand Up @@ -727,10 +731,11 @@ DEPENDENCIES
super_diff
tty-progressbar
turbo-rails (~> 1.0)
view_component (~> 2.56.2)
view_component (~> 3.5)
web-console (>= 3.3.0)
webmock
whenever
yaaf
zipline (~> 1.4)

BUNDLED WITH
Expand Down
29 changes: 29 additions & 0 deletions app/components/nested_form_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div data-controller="<%= controller %>" data-<%= controller %>-wrapper-selector-value=".<%= controller %>-wrapper">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the strategy is to abstract nested forms. This provides a nested form component that handles creating the template, rendering the rows, providing the elements needed by the nested forms stimulus controller, and rendering the add another button.

<template data-<%= controller %>-target="template">
<%= form.fields_for field, clazz.new, child_index: "NEW_RECORD" do |nested_form| %>
<div class="<%= controller %>-wrapper" data-new-record="<%= nested_form.object.main_model.new_record? %>">
<%= nested_form.hidden_field :_destroy %>
<%= render row.new(form: nested_form, controller: controller, **other_row_params) %>
</div>
<% end %>
</template>

<%= form.fields_for field, nested_forms do |nested_form| %>
<div class="<%= controller %>-wrapper" data-new-record="<%= nested_form.object.main_model.new_record? %>">
<%= nested_form.hidden_field :id %>
<%# :_destroy must come before nested form given queryselector that controller uses for find it. %>
<%= nested_form.hidden_field :_destroy %>
<%# nested_form may have a remove button which has remove action for the controller %>
<%= render row.new(form: nested_form, controller: controller, **other_row_params) %>
</div>
<% end %>

<%# Inserted elements will be injected before that target. %>
<div data-<%= controller %>-target="target"></div>

<% if add_another_button? %>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow a customized add another button to be provided, but provide default.

<%= add_another_button %>
<% else %>
<%= form.add_another_button controller, "+ Add another", class: "btn btn-outline-primary" %>
<% end %>
</div>
28 changes: 28 additions & 0 deletions app/components/nested_form_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

# Support for nested forms.
class NestedFormComponent < ApplicationComponent
# Optional slot. If provided, the button must have add as its action for the controller.
# See H2FormBuilder#add_another_button
renders_one :add_another_button

def initialize(controller:, form:, field:, clazz:, row:, ordered: false)
@controller = controller
@form = form
@field = field
@clazz = clazz
@row = row
@ordered = ordered
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows specifying that the nested forms are ordered (e.g, authors).

end

attr_reader :controller, :form, :field, :clazz, :row, :ordered

def nested_forms
# Form object must have a method that returns nested forms for this field.
form.object.send("#{field}_forms".to_sym)
end

def other_row_params
ordered ? {ordered:} : {}
end
end
21 changes: 21 additions & 0 deletions app/components/wokes/affiliation_row_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<div class="mb-3 row">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nested form must have a row component that renders an individual form. This is for affiliations. It is a bit more complicated because it uses autocomplete.

<div class="col-sm-5">
<div data-controller="autocomplete" data-autocomplete-url-value="/ror" data-autocomplete-min-length-value="3" class="dropdown" role="combobox" aria-expanded="false" data-autocomplete-ready-value="true" aria-label="Autocomplete for affiliation">
<!-- This input is populated by autocomplete controller.-->
<!-- That triggers the autocomplete-edit controller, which populates the uri and cocina type inputs. -->
<%= form.label :label, "Affiliation *", class: "col-form-label" %>
<%= form.text_field :label, class: "form-control affiliation-input", data: {autocomplete_target: "input"} %>
<%= form.hidden_field :uri, {"data-autocomplete-target": "hidden", "aria-label": "URI for affiliation", class: "affiliation-input"} %>
<ul class="list-group" data-autocomplete-target="results"></ul>
</div>
</div>
<div class="col-sm-5">
<%= form.label :department, "Department/Institute/Center", class: "col-form-label" %>
<%= form.text_field :department, class: "form-control affiliation-input" %>
</div>
<div class="offset-sm-1 col-sm-1">
<%= form.remove_button controller, "Remove", class: "btn btn-sm", aria: {label: "Remove affiliation"} do %>
<span class="fa-regular fa-trash-alt"></span>
<% end %>
</div>
</div>
13 changes: 13 additions & 0 deletions app/components/wokes/affiliation_row_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Wokes
# Renders a row for an affiliation form.
class AffiliationRowComponent < ApplicationComponent
def initialize(form:, controller:)
@form = form
@controller = controller
end

attr_reader :form, :controller
end
end
10 changes: 10 additions & 0 deletions app/components/wokes/author_row_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div>
<%= form.label :first_name %>
<%= form.text_field :first_name %>
</div>
<div>
<%= form.label :last_name %>
<%= form.text_field :last_name %>
</div>
<%= render NestedFormComponent.new(form: form, controller: "affiliation-form", row: Wokes::AffiliationRowComponent, field: "affiliations", clazz: Forms::Affiliation) %>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is double-nesting (affiliations within authors) that was problematic with reform.

<%= form.remove_button controller, "Remove", class: "btn btn-outline-primary" %>
13 changes: 13 additions & 0 deletions app/components/wokes/author_row_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Wokes
# Renders a row for an author form.
class AuthorRowComponent < ApplicationComponent
def initialize(form:, controller:)
@form = form
@controller = controller
end

attr_reader :form, :controller
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<section data-controller="nested-form" id="author">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much just copied from works.

<header>List authors and contributors *</header>
<p>Enter the name(s) of people, organizations or events responsible for producing the deposit.</p>

<%= render Wokes::AuthorsComponent.new(form: form) %>

<%= render Wokes::ContributorsComponent.new(form: form) %>
</section>
14 changes: 14 additions & 0 deletions app/components/wokes/authors_and_contributors_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Wokes
# A widget for managing both the ordered authors and unordered contributors to the work.
# We make this distinction between different types of contributors, because only authors
# appear in the automatically generated citation.
class AuthorsAndContributorsComponent < ApplicationComponent
def initialize(form:)
@form = form
end

attr_reader :form
end
end
14 changes: 14 additions & 0 deletions app/components/wokes/authors_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<section>
<header aria-describedby="popover-work.author">
Authors to include in citation
<%= render PopoverComponent.new key: "work.author" %>
</header>
<p>When there are multiple authors, list them in the order they should appear in the citation. If you need to change the order of the authors, click the arrows to move individual authors up or down in the list.</p>

<%= render NestedFormComponent.new(form: form, controller: "author-form", row: Wokes::ContributorRowComponent,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authors are just like contributors except they use a different controller name, a different field ("authors" instead of "contributors"), a different form class, and are ordered. However, the row component is the same (which is where the bulk of the rendering is).

field: "authors", clazz: Forms::Author, ordered: true) do |component| %>
<% component.with_add_another_button do %>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of providing a custom add another button.

<%= form.add_another_button "author-form", "+ Add another author", class: "btn btn-outline-primary" %>
<% end %>
<% end %>
</section>
12 changes: 12 additions & 0 deletions app/components/wokes/authors_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Wokes
# A widget for managing the collection of contributors to the work.
class AuthorsComponent < ApplicationComponent
def initialize(form:)
@form = form
end

attr_reader :form
end
end
41 changes: 41 additions & 0 deletions app/components/wokes/buttons_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copied, with some code that relies on files commented out.

<div data-deposit-button-target="globusMessage" class="row justify-content-end">
<div class="col-8">
<p class="text-end my-4">
<strong>
<span class="fa-solid fa-exclamation-circle me-2" style="color: var(--stanford-warning);"></span>
Deposit is disabled until file transfer is complete.
</strong>
</p>
</div>
</div>
-->
<div class="row justify-content-between">
<div class="col-auto">
<% if show_first_draft_cancel? %>
<%= link_to model, method: :delete, aria: {label: "Delete #{title}"},
data: {
confirm: "Are you sure you want to delete this draft work? It cannot be undone.",
action: "unsaved-changes#allowFormSubmission"
} do %>
<span class="btn btn-primary"><span class="fa-regular fa-trash-alt"></span> Discard draft</span>
<% end %>
<% elsif show_version_draft_cancel? %>
<%= link_to work_version, method: :delete, aria: {label: "Delete #{title}"},
data: {
confirm: "Are you sure you want to revert to the previously published version? It cannot be undone.",
action: "unsaved-changes#allowFormSubmission"
} do %>
<span class="btn btn-primary"><span class="fa-regular fa-trash-alt"></span> Discard draft</span>
<% end %>
<% end %>
</div>
<div class="col-auto">
<%= link_to "Cancel", cancel_link_location, class: "btn btn-link" %>
<%= form.submit "Save as draft", class: "btn btn-primary", id: "save-draft-button",
data: {action: "unsaved-changes#allowFormSubmission"} %>
<%= form.submit submit_button_label, class: "btn btn-primary",
disabled: false,
data: {action: "unsaved-changes#allowFormSubmission", deposit_button_target: "depositButton"} %>
</div>
</div>
50 changes: 50 additions & 0 deletions app/components/wokes/buttons_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

module Wokes
# Displays the button for saving a draft or depositing for a work
class ButtonsComponent < ApplicationComponent
def initialize(form:)
@form = form
end

attr_reader :form

def submit_button_label
work_in_reviewed_coll? ? "Submit for approval" : "Deposit"
end

private

def collection
form.object.collection
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting the object from a form is much more straight-forward.

end

def work_version
form.object.work_version
end

def work
form.object.work
end

def work_in_reviewed_coll?
collection.review_enabled?
end

def show_version_draft_cancel?
work_version.version_draft? && work_version.persisted?
end

def show_first_draft_cancel?
work_version.deleteable?
end

def cancel_link_location
if work_version.persisted?
work_path(work)
else
collection_works_path(collection)
end
end
end
end
15 changes: 15 additions & 0 deletions app/components/wokes/contact_email_row_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="mb-3 row plain-container">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contact email is probably the most basic nested form.

<div class="col-sm-2">
<%= form.label :email, "Contact email *", class: "col-form-label" %> <%= render PopoverComponent.new key: "work.contact_email" %>
</div>
<div class="col-sm-9">
<%= form.email_field :email, class: "form-control#{" is-invalid" if error?}",
pattern: Devise.email_regexp.source, required: true %>
<div class="invalid-feedback">You must provide a valid email address</div>
</div>
<div class="col-sm-1">
<%= form.remove_button controller, class: "btn btn-sm float-end", aria: {label: "Remove"} do %>
<span class="fa-regular fa-trash-alt"></span>
<% end %>
</div>
</div>
17 changes: 17 additions & 0 deletions app/components/wokes/contact_email_row_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Wokes
# Renders a row for an affiliation form.
class ContactEmailRowComponent < ApplicationComponent
def initialize(form:, controller:)
@form = form
@controller = controller
end

attr_reader :form, :controller

def error?
form.object.errors.where(:email).present?
end
end
end
Loading