Module: Hecks::Forms::CommandFormRenderer
- Defined in:
- lib/hecks/forms/command_form_renderer.rb
Overview
Command -> the page body for its HTML form — the working half of
what command_form.bluebook names (see docs/command-form-and-query-
form-bluebook.md). Every command in every loaded, exposed bluebook
renders through here — nothing here is per-command code; see that
doc for the 1:1 rule this deliberately holds to for now.
Class Method Summary collapse
- .error_banner(error) ⇒ Object
-
.field_errors(_error) ⇒ Object
No structured field attribution exists on a domain refusal today (it is a typed exception with a rendered message — see docs/command-form-and-query-form-bluebook.md's note on
RefusalWording), so this returns empty rather than guessing which field a message meant; the banner above carries the real text instead of a misattributed hint. -
.fields_for(aggregate, command) ⇒ Object
registryis only used to populate a:referencefield's<select>with real records (including the identity picker itself, for a non-creating command — seeidentity_fieldbelow). - .givens_callout(command) ⇒ Object
- .header(domain, aggregate, command) ⇒ Object
- .identity_field(aggregate) ⇒ Object
- .inspect_panel(domain, aggregate, command, action, fields) ⇒ Object
- .render(registry:, domain:, aggregate:, command:, action:, values: nil, error: nil, prefill: {}) ⇒ Object
Class Method Details
.error_banner(error) ⇒ Object
74 75 76 77 78 79 80 81 82 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 74 def self.(error) return "" unless error <<~HTML <div class="error-banner" role="alert"> <p><strong>#{Escape.html(error.class.name.split('::').last)}</strong> — #{Escape.html(error.)}</p> </div> HTML end |
.field_errors(_error) ⇒ Object
No structured field attribution exists on a domain refusal today
(it is a typed exception with a rendered message — see
docs/command-form-and-query-form-bluebook.md's note on
RefusalWording), so this returns empty rather than guessing which
field a message meant; the banner above carries the real text
instead of a misattributed hint.
90 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 90 def self.field_errors(_error) = {} |
.fields_for(aggregate, command) ⇒ Object
registry is only used to populate a :reference field's
<select> with real records (including the identity picker itself,
for a non-creating command — see identity_field below). values/
error carry a sticky re-render after a refused submission; leave
both nil/{} for a fresh form. prefill carries values a caller
arrived WITH (typically ?to=... off a record's own detail page) —
kept separate from values because a prefill is not an error retry
and should not be treated as one by a future reader of this code.
The SAME field list a POST handler needs to cast raw params against
(params.rb's Params.extract) — one derivation, so a page never
renders an input the submit handler doesn't also expect.
27 28 29 30 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 27 def self.fields_for(aggregate, command) addressing = command.creates? ? [] : [identity_field(aggregate)] addressing + command.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) } end |
.givens_callout(command) ⇒ Object
67 68 69 70 71 72 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 67 def self.givens_callout(command) return "" if command.givens.empty? items = command.givens.map { |given| "<li>#{Escape.html(given.description)}</li>" } %(<div class="callout"><strong>Preconditions</strong> — refused if any fail:<ul>#{items.join}</ul></div>) end |
.header(domain, aggregate, command) ⇒ Object
57 58 59 60 61 62 63 64 65 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 57 def self.header(domain, aggregate, command) <<~HTML <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}")}</h1> #{command.role ? %(<span class="badge role">role: #{Escape.html(command.role)}</span>) : ''} #{command.creates? ? %(<span class="badge">creates a new #{Escape.html(aggregate.hecks_name)}</span>) : ''} #{command.goal ? %(<p class="goal">#{Escape.html(command.goal)}</p>) : ''} #{givens_callout(command)} HTML end |
.identity_field(aggregate) ⇒ Object
51 52 53 54 55 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 51 def self.identity_field(aggregate) Field.new(path: "to", label: "#{aggregate.hecks_name} (#{aggregate.identity_paths.join(', ')})", kind: :reference, html_type: "text", target_aggregate: aggregate, help: "The record this command acts on.") end |
.inspect_panel(domain, aggregate, command, action, fields) ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 92 def self.inspect_panel(domain, aggregate, command, action, fields) verb = "#{domain}::#{aggregate.hecks_name}.#{command.hecks_name}" paths = Params.paths(fields) curl = <<~SH.strip curl -X POST '#{action}' \\ #{paths.map { |path| "-d '#{path}=...'" }.join(" \\\n ")} SH <<~HTML <details class="inspect"> <summary>Inspect — #{Escape.html(verb)}</summary> <p>Emits: #{command.emits.empty? ? '<em>nothing declared</em>' : command.emits.map { |e| "<code>#{Escape.html(e)}</code>" }.join(', ')}</p> <p>Equivalent request (as <code>curl</code>) — every field is <code>#{Escape.html('name.path')}</code>-encoded, form or JSON alike:</p> <div class="link-row"><code id="curl-snippet">#{Escape.html(curl)}</code><button type="button" class="copy" data-copy="#curl-snippet">copy</button></div> <p>Fields this command takes: #{paths.map { |p| "<code>#{Escape.html(p)}</code>" }.join(', ')}</p> <p>The command's own declaration, as the runtime holds it:</p> <pre>#{Escape.html(JSON.pretty_generate(command.to_h))}</pre> </details> HTML end |
.render(registry:, domain:, aggregate:, command:, action:, values: nil, error: nil, prefill: {}) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/hecks/forms/command_form_renderer.rb', line 32 def self.render(registry:, domain:, aggregate:, command:, action:, values: nil, error: nil, prefill: {}) all_fields = fields_for(aggregate, command) = ReferenceOptions.collect(registry, domain, all_fields) shown_values = values || prefill <<~HTML #{header(domain, aggregate, command)} #{(error)} <form method="post" action="#{Escape.attr(action)}" novalidate> #{all_fields.map { |f| FieldRenderer.render(f, values: shown_values, errors: field_errors(error), reference_options: ) }.join} <div class="actions"> <button type="submit">#{Escape.html(command.hecks_name)}</button> <a class="button secondary" href="#{Escape.attr("/#{domain}/#{aggregate.hecks_name}")}">Cancel</a> </div> </form> #{inspect_panel(domain, aggregate, command, action, all_fields)} HTML end |