-
Notifications
You must be signed in to change notification settings - Fork 2
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
base: main
Are you sure you want to change the base?
Forms using yaaf #3290
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
gem "whenever", require: false # Work around https://github.com/javan/whenever/issues/831 | ||
gem "yaaf" # Form objects | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A tiny gem that helps with form objects. |
||
gem "zipline", "~> 1.4" |
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"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? %> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<div class="mb-3 row"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 |
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) %> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" %> |
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"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 |
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 %> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<!-- | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<div class="mb-3 row plain-container"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
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 |
There was a problem hiding this comment.
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.