Class: Forms::TagField

Inherits:
Phlex::HTML
  • Object
show all
Includes:
Phlex::Reactive::ClientBindings
Defined in:
lib/forms/tag_field.rb

Overview

A model-bound tag/chip input, composed from phlex-reactive's CLIENT-ONLY tag primitives (form state, no token, zero round trips). phlex-forms owns the polished chrome — daisyUI styling, model binding, the hidden comma-joined field — while phlex-reactive owns the behavior contract.

f.field :tags, as: :tags, suggestions: %w[Ruby Rails Hotwire Postgres]
f.field :tags, as: :tags, suggestions: { "Postgres" => "postgres database db sql" }

Submits ONE comma-joined param (user[tags] = "Ruby,Rails"), the primitive's wire contract — the model splits it (an attribute :tags + a tags= writer, or an ActiveModel array type).

This file is autoloaded ONLY when Phlex::Reactive is present (the Forms::Live gate in lib/phlex_forms.rb) because it includes ClientBindings at class level. The reactive_tags_* client helpers require phlex-reactive >= 0.11.4.

It uses the reactive_tags_add/option/remove helpers for the chip/query/option behavior, but emits the ROOT's data-reactive-tags-field raw rather than via reactive_tags(:tags): that helper compiles a SYMBOL through the class-level reactive_scope, but a form builder's wire name is per-instance ("user"). The data attribute IS the public contract; any CSS selector works (issue #6 Caveats 1 & 2). Likewise the query input targets by #id so it never submits.

Direct Known Subclasses

Plain::TagField, RootlessTagField

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*modifiers, name:, id:, value: nil, suggestions: [], error: false, placeholder: "Add a tag…", **attributes) ⇒ TagField

Returns a new instance of TagField.



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/forms/tag_field.rb', line 29

def initialize(*modifiers, name:, id:, value: nil, suggestions: [], error: false,
               placeholder: "Add a tag…", **attributes)
  @modifiers = modifiers
  @name = name # "user[tags]" — instance-dynamic
  @id = id
  @value = value.is_a?(Array) ? value.compact.join(",") : value.to_s
  @suggestions = normalize_suggestions(suggestions)
  @error = error
  @placeholder = placeholder
  @attributes = attributes
  super()
end

Class Method Details

.root_tag_attributes(name:, id:) ⇒ Object

The root's tag wire attrs. Raw, not reactive_tags(:tags)/reactive_filter(:q) (Caveats 1 & 2): target the hidden field by [name=…] and the query input by #id (an id selector means the query input never submits a stray param). Public so Forms::Live can hoist these onto the

root when the widget is lifted rootless.



56
57
58
59
60
61
# File 'lib/forms/tag_field.rb', line 56

def self.root_tag_attributes(name:, id:)
  { data: {
    reactive_tags_field: %([name="#{name}"]),
    reactive_filter_input: "##{id}_query"
  } }
end

Instance Method Details

#view_templateObject



42
43
44
45
46
47
48
49
# File 'lib/forms/tag_field.rb', line 42

def view_template
  # Standalone: THIS div is the reactive root. Inside a Forms::Live form the
  # widget renders rootless (Forms::RootlessTagField) — the outer <form> is
  # the root and carries these tag attrs — so the form owns the hidden field.
  div(**mix(reactive_root(id: "#{@id}_widget"), root_tag_attributes, class: root_classes)) do
    body
  end
end