Module: Clickwrap::ViewHelpers
- Defined in:
- lib/clickwrap/view_helpers.rb
Overview
View helpers for CUSTOM presentation surfaces — the middle ground between
form.clickwrap (the one-line happy path) and hand-writing every input
against Clickwrap.present primitives.
A custom surface has to render three things correctly or its captures silently fail at submit: the signed token under the exact envelope name, each control under its statement's declared name and id, and a call to action whose words match the ones the manifest recorded. These helpers own those three contracts; the host owns every class, wrapper, and data attribute around them.
<% preparation = Clickwrap.present(:withdrawal_preparation, actor: current_user,
submit_button_text: "He leído todo") %>
<%= clickwrap_presentation_token_field(preparation) %>
<% preparation.statements.each do |statement| %>
<%= clickwrap_statement_check_box(statement, class: "my-checkbox") %>
<%= label_tag statement.control_id, statement.assertion %>
<% end %>
<%= clickwrap_submit_button(preparation, class: "my-button") %>
Instance Method Summary collapse
-
#clickwrap_combined_sentence(combined) ⇒ Object
The composed one-line offer, as markup: the sentence the presentation signed, with each document rendered as a real link where the words for it go.
-
#clickwrap_document_link(document) ⇒ Object
One document, as the link a person presses to read it.
-
#clickwrap_document_link_html_options(document = nil) ⇒ Object
Host-specific navigation attributes for immutable document links.
-
#clickwrap_hotwire_native_link_options ⇒ Object
The declared native answer, or nil when this render is not one of the cases
config.hotwire_native_document_linksdecides. - #clickwrap_native_link_context ⇒ Object
-
#clickwrap_presentation_token_field(presentation) ⇒ Object
The signed presentation token, under the envelope name the capture reads.
-
#clickwrap_sentence_fragment(fragment) ⇒ Object
One statement's share of that sentence.
-
#clickwrap_statement_check_box(statement, checked: false, **options) ⇒ Object
One statement's checkbox: declared name and id, initially unchecked (always — a pre-ticked box records the page's default, not the person's action),
requiredmirroring the server's own rule as progressive enhancement. -
#clickwrap_statement_label(control) ⇒ Object
The words that go in one control's label: a statement's own first-person assertion, or — when the control is the composed line — the whole sentence with its document links rendered inside it.
-
#clickwrap_statement_radio_button(statement, value, checked: false, **options) ⇒ Object
One option of a statement rendered as a radio group — the pattern for an answer with more than one presented choice (or an affirmative/negative pair, where the negative submits "0").
-
#clickwrap_submit_button(presentation) ⇒ Object
The call to action, worded by the presentation itself.
Instance Method Details
#clickwrap_combined_sentence(combined) ⇒ Object
The composed one-line offer, as markup: the sentence the presentation signed, with each document rendered as a real link where the words for it go. This is what the single checkbox's label contains, and it is a helper rather than template soup because an ejected view should be able to restyle the line without reassembling a sentence out of translated parts.
<%= label_tag presentation.combined.control_id,
clickwrap_combined_sentence(presentation.combined) %>
Nothing here interpolates markup into translated text: the presenter already split every fragment around the place its documents go, so this only joins pieces that are individually safe.
117 118 119 120 121 |
# File 'lib/clickwrap/view_helpers.rb', line 117 def clickwrap_combined_sentence(combined) fragments = combined.fragments.map { |fragment| clickwrap_sentence_fragment(fragment) } safe_join([safe_join(fragments, combined.joiner), combined.terminator]) end |
#clickwrap_document_link(document) ⇒ Object
One document, as the link a person presses to read it. The href is the exact path signed into the manifest and cannot be overridden here; the host's navigation hook chooses only how its client opens it. There is no "unavailable document" branch, because there is no such document: a presentation whose path could not be resolved refuses to be built at all.
The "(opens in a new tab)" truth is kept and the clutter is not: it is rendered for screen readers only, and only when the link really does open a new tab — a same-window link announcing otherwise would be the page lying about itself.
140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/clickwrap/view_helpers.rb', line 140 def clickwrap_document_link(document) = (document) link = link_to(document.label, document.path, class: "clickwrap-documents__link", **) return link unless [:target].to_s == "_blank" # The separating space lives INSIDE the sr-only span, which is out of # flow: a space between the elements would be laid out, and the sentence # would read "…Privacy Policy ." with a gap before its own full stop. hint = tag.span(" #{t("clickwrap.ui.opens_in_new_tab")}", class: "clickwrap-sr-only") safe_join([link, hint]) end |
#clickwrap_document_link_html_options(document = nil) ⇒ Object
Host-specific navigation attributes for immutable document links. This
helper deliberately lives with the helpers installed into every host
view, rather than only in EngineHelper: form.clickwrap renders the
engine's statement partial inside the host form's view context.
The immutable href is evidence-critical and therefore cannot be overridden here. Hosts may only choose how their client opens it (for example, target: "_blank" or data: { turbo: false }).
The hook is evaluated IN the rendering view, so a host can answer
per-request questions the way it always does — hotwire_native_app?
being the canonical one: a native WebView usually wants a same-window
link its screen rules can route, while the web wants a new tab.
config.hotwire_native_document_links, when set, answers native renders
entirely (the attributes here, and the absolutized href in
ControllerHelpers#clickwrap_document_version_path_for_presentation);
the hook keeps answering everything else.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/clickwrap/view_helpers.rb', line 42 def (document = nil) = return if = instance_exec(document, &Clickwrap.config.) unless .is_a?(Hash) || .is_a?(ActiveSupport::HashWithIndifferentAccess) raise ConfigurationError, "document_link_html_options_with must return a Hash of HTML attributes." end = .to_h.symbolize_keys # Case-insensitive on purpose: HTML lowercases attribute names and keeps # the FIRST duplicate, so a smuggled "HREF" would win over the signed # immutable path in the rendered link. if .keys.any? { |key| key.to_s.casecmp?("href") } raise ConfigurationError, "document_link_html_options_with cannot set href. Clickwrap signs the exact " \ "immutable document path into the presentation manifest; this hook may only " \ "choose how the client opens that path." end end |
#clickwrap_hotwire_native_link_options ⇒ Object
The declared native answer, or nil when this render is not one of the
cases config.hotwire_native_document_links decides.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/clickwrap/view_helpers.rb', line 68 def native_links = Clickwrap.config.hotwire_native_document_links return nil unless native_links return nil unless respond_to?(:hotwire_native_app?) && hotwire_native_app? # Asked with the CONTROLLER, not the view: the href was resolved from the # controller when the presentation was signed, and the two halves of one # document link must not be able to answer differently. case Clickwrap.config.hotwire_native_document_link_mode(clickwrap_native_link_context) when :external_browser # `data-turbo-false` keeps the tap out of the Turbo/Hotwire Native # navigation stack so the absolutized href reaches the system browser. { target: "_blank", rel: "noopener", data: { turbo: false } } when :same_screen # A plain link, on purpose: the app's own native path configuration # decides how the document presents (typically a modal sheet). {} end end |
#clickwrap_native_link_context ⇒ Object
88 89 90 |
# File 'lib/clickwrap/view_helpers.rb', line 88 def clickwrap_native_link_context (controller if respond_to?(:controller)) || self end |
#clickwrap_presentation_token_field(presentation) ⇒ Object
The signed presentation token, under the envelope name the capture reads. This is the one hidden field a clickwrap form carries — and the one whose name must never be hand-typed, because a typo here is a form that looks complete and refuses every submission.
156 157 158 |
# File 'lib/clickwrap/view_helpers.rb', line 156 def clickwrap_presentation_token_field(presentation) hidden_field_tag "clickwrap_submission[presentation_token]", presentation.token, id: nil end |
#clickwrap_sentence_fragment(fragment) ⇒ Object
One statement's share of that sentence.
124 125 126 127 128 |
# File 'lib/clickwrap/view_helpers.rb', line 124 def clickwrap_sentence_fragment(fragment) links = fragment.documents.map { |document| clickwrap_document_link(document) } safe_join([fragment.prefix, safe_join(links, fragment.documents_joiner), fragment.suffix]) end |
#clickwrap_statement_check_box(statement, checked: false, **options) ⇒ Object
One statement's checkbox: declared name and id, initially unchecked
(always — a pre-ticked box records the page's default, not the person's
action), required mirroring the server's own rule as progressive
enhancement. Everything in **options is yours; pass checked: true
only when re-rendering a submission the person already made.
165 166 167 168 169 170 |
# File 'lib/clickwrap/view_helpers.rb', line 165 def clickwrap_statement_check_box(statement, checked: false, **) = { id: statement.control_id }.merge() [:required] = true if statement.required? && !.key?(:required) check_box_tag statement.control_name, "1", checked, end |
#clickwrap_statement_label(control) ⇒ Object
The words that go in one control's label: a statement's own first-person assertion, or — when the control is the composed line — the whole sentence with its document links rendered inside it.
One helper for both is what lets the reference statement partial render either without knowing which it has, and what makes a host's ejected partial style the composed line for free.
99 100 101 102 103 |
# File 'lib/clickwrap/view_helpers.rb', line 99 def clickwrap_statement_label(control) return clickwrap_combined_sentence(control) if control.is_a?(Clickwrap::Presenter::Combined) control.assertion end |
#clickwrap_statement_radio_button(statement, value, checked: false, **options) ⇒ Object
One option of a statement rendered as a radio group — the pattern for an answer with more than one presented choice (or an affirmative/negative pair, where the negative submits "0"). All options share the statement's control name; each gets a value-suffixed id for its label.
176 177 178 179 180 |
# File 'lib/clickwrap/view_helpers.rb', line 176 def (statement, value, checked: false, **) = { id: "#{statement.control_id}_#{value}" }.merge() statement.control_name, value, checked, end |
#clickwrap_submit_button(presentation) ⇒ Object
The call to action, worded by the presentation itself. The manifest
recorded submit_button_text when the offer was signed; rendering the
button from the same object is what makes drift between the recorded
words and the pressed words impossible on a custom surface.
186 187 188 |
# File 'lib/clickwrap/view_helpers.rb', line 186 def (presentation, **) submit_tag(presentation., **) end |