Class: Dommy::HTMLFormElement
- Inherits:
-
HTMLElement
- Object
- Element
- HTMLElement
- Dommy::HTMLFormElement
- Defined in:
- lib/dommy/html_elements.rb
Overview
<form> — element collection, submit/reset, and a stubbed
validation surface.
Constant Summary collapse
- LISTED_CONTROL_SELECTOR =
form.elements— listed elements inside the form (excludes nested forms per spec; we approximate by walking input/select/textarea/button/output/fieldset). Returned as a live HTMLCollection so listening tosubmit/resetand adding fields between accesses works as expected.form.elements— the listed controls owned by this form, in document tree order, excluding input type=image. Membership follows the WHATWG form-owner algorithm (aformcontent attribute overrides DOM nesting), NOT plain descendant containment, so controls associated viaform=idare included and a control in a nested inner form is excluded. Memoized so the live collection is the [SameObject] each access returns. "input, select, textarea, button, output, fieldset, object"
Constants inherited from Element
Element::ADJACENT_POSITIONS, Element::APPROX_CHAR_PX, Element::APPROX_LINE_PX, Element::ATTRIBUTE_NODE, Element::CDATA_SECTION_NODE, Element::COMMENT_NODE, Element::DOCUMENT_FRAGMENT_NODE, Element::DOCUMENT_NODE, Element::DOCUMENT_POSITION_CONTAINED_BY, Element::DOCUMENT_POSITION_CONTAINS, Element::DOCUMENT_POSITION_DISCONNECTED, Element::DOCUMENT_POSITION_FOLLOWING, Element::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, Element::DOCUMENT_POSITION_PRECEDING, Element::DOCUMENT_TYPE_NODE, Element::ELEMENT_NODE, Element::HTML_NAMESPACE, Element::INLINE_TAGS, Element::PROCESSING_INSTRUCTION_NODE, Element::REFLECTED_TOKEN_LIST_HOSTS, Element::SHADOW_HOST_TAGS, Element::SVG_NAMESPACE, Element::TEXT_NODE
Constants included from Node
Node::ATTRIBUTE_NODE, Node::CDATA_SECTION_NODE, Node::COMMENT_NODE, Node::DOCUMENT_FRAGMENT_NODE, Node::DOCUMENT_NODE, Node::DOCUMENT_POSITION_CONTAINED_BY, Node::DOCUMENT_POSITION_CONTAINS, Node::DOCUMENT_POSITION_DISCONNECTED, Node::DOCUMENT_POSITION_FOLLOWING, Node::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, Node::DOCUMENT_POSITION_PRECEDING, Node::DOCUMENT_TYPE_NODE, Node::ELEMENT_NODE, Node::HTML_NAMESPACE, Node::PROCESSING_INSTRUCTION_NODE, Node::TEXT_NODE, Node::XMLNS_NAMESPACE, Node::XML_NAMESPACE
Instance Attribute Summary
Attributes inherited from Element
Instance Method Summary collapse
-
#__internal_navigate_for_submit__(submitter) ⇒ Object
Build the form data set and hand the resulting navigation to the delegate.
- #__js_call__(method, args) ⇒ Object
- #__js_get__(key) ⇒ Object
-
#__js_named_props__ ⇒ Object
WebIDL named getter: the form's supported property names are the name/id of each of its listed controls.
-
#__named_getter_result__(name, matches) ⇒ Object
A single matching control is returned directly; multiple matches yield a RadioNodeList.
-
#__owns_control__(control) ⇒ Object
The form owner of a listed control, per WHATWG: when the control carries a
formcontent attribute, its owner is the form element with that id (or nothing, if the id resolves to a non-form / nothing); otherwise it is the nearest ancestor form element. -
#__run_form_submission__(submitter = nil) ⇒ Object
The form submission algorithm's observable core, shared by
requestSubmit(), a submit button's activation (driver click), and Enter's implicit submission: fire a cancelableSubmitEvent(carrying the submitter), and — when nothing canceled it — hand the resulting navigation to the delegate. -
#check_validity ⇒ Object
Walk all listed elements; the form is "valid" iff every candidate control passes its own checkValidity.
- #elements ⇒ Object
- #length ⇒ Object
-
#named_controls ⇒ Object
name/id -> [controls], for the named getter (a name matching more than one control yields a RadioNodeList-like NodeList).
- #report_validity ⇒ Object
-
#request_submit(submitter = nil) ⇒ Object
Spec:
requestSubmit(submitter?)MIRRORS user-initiated submission — it fires asubmitevent (with the submitter), and on a non-canceled event hands the form navigation to the delegate. -
#reset ⇒ Object
Spec:
reset()does fire aresetevent; if the event is default-prevented, no reset happens. -
#submit ⇒ Object
Spec:
submit()performs form submission directly WITHOUT firing asubmitevent and without constraint validation.
Methods inherited from HTMLElement
#case_sensitive_attribute_names?, #disabled_by_ancestor_fieldset?, #labels_node_list, #parse_non_negative_reflected, #set_non_negative_reflected
Methods included from Internal::ReflectedAttributes
Methods inherited from Element
#[], #[]=, #__dommy_backend_node__, #__internal_approx_box, #__internal_case_sensitive_attribute_names__?, #__internal_created_namespace__, #__internal_normalize_attr_key__, #__internal_set_namespace__, #__internal_shadow_root__, #__js_attribute_snapshot__, #__js_set__, #__run_click_activation_behavior__, #__test_scroll_log__, #access_key_label, #accessibility_tree, #activation_behavior, #activation_target, #activation_target?, #after, #anchor_href, #animate, #approximate_layout?, #aria_content_attr, #aria_element_attr, #aria_element_get, #aria_element_set, #aria_elements_attr, #aria_elements_current, #aria_elements_get, #aria_elements_set, #aria_find_in_root, #aria_get, #aria_ref_in_valid_scope?, #aria_set, #aria_snapshot, #assigned_slot, #at_xpath, #attach_shadow, #attributes, #base_uri, #before, #blur, #child_element_count, #child_nodes, #children, #class_list, #class_name, #class_name=, #clear_aria_element_ref_for, #click, #clone_node, #closest, #computed_description, #computed_label, #computed_role, #contains?, #dataset, #element_prefix, #equal_node?, #first_child, #first_element_child, #focus, #get_animations, #get_attribute, #get_attribute_names, #get_attribute_node, #get_attribute_node_ns, #get_attribute_ns, #get_bounding_client_rect, #get_client_rects, #get_elements_by_class_name, #get_elements_by_tag_name, #get_elements_by_tag_name_ns, #get_html, #get_inner_html, #has_attribute?, #has_attribute_ns?, #has_attributes?, #has_child_nodes?, #hide_popover, #id, #id=, #initialize, #inner_html, #inner_html=, #insert_adjacent_element, #insert_adjacent_html, #insert_adjacent_text, #insert_before, #insertion_parent!, #is_connected?, #last_child, #last_element_child, #live_child_nodes, #local_name, #mark_fragment_scripts_started, #matches?, #namespace_uri, #next_element_sibling, #next_sibling, #on, #outer_html, #outer_html=, #owner_document, #parent_element, #parent_node, #path, #pre_click_activation_state, #previous_element_sibling, #previous_sibling, #query_selector, #query_selector_all, #reflected_attr_name, #reflected_token_list, #remove, #remove_attribute, #remove_attribute_node, #remove_attribute_ns, #remove_child, #replace_child, #replace_with_nodes, #request_fullscreen, #restore_pre_click_activation, #role, #role=, #root_node, #run_post_click_activation, #same_node?, #set_attribute, #set_attribute_node, #set_attribute_ns, #shadow_root, #show_popover, #slot, #slot=, #style, #tag_name, #text_content, #text_content=, #to_s, #toggle_attribute, #toggle_popover, #translate_mode?, #validate_adjacent_document_insert!, #with_selector_errors, #xpath
Methods included from Bridge::Methods
Methods included from Internal::ParentNode
#append, #append_child, #normalize, #prepend, #replace_children
Methods included from Internal::ChildNode
#child_node_after, #child_node_before, #child_node_replace_with, #nullable_dom_string, #validate_insert_before_ref!
Methods included from Node
#compare_document_position, #get_root_node, #is_default_namespace, #is_equal_node, #is_same_node, #lookup_namespace_uri, #lookup_prefix
Methods included from EventTarget
#__dommy_dump_event_failure__, #__internal_deliver_event__, #__internal_event_parent__, #__internal_process_event_handler_return__, #add_event_listener, capture_flag, #deliver_at, #dispatch_event, #event_name_from_on, #invoke_listener_isolated, js_truthy?, #on_handler, #remove_event_listener, #set_on_handler
Constructor Details
This class inherits a constructor from Dommy::Element
Instance Method Details
#__internal_navigate_for_submit__(submitter) ⇒ Object
Build the form data set and hand the resulting navigation to the delegate. Reuses the core FormSubmission serializer (submitter, method, action, enctype, GET query-stripping) — method-override is a host concern, so it's left off here (the delegate applies its own policy).
386 387 388 389 390 391 392 393 394 395 |
# File 'lib/dommy/html_elements.rb', line 386 def __internal_navigate_for_submit__(submitter) win = @document&.default_view return if win.nil? result = Dommy::Interaction::FormSubmission.new(self, submitter).submit! win.__internal_navigate__( url: result[:url], method: result[:method], params: result[:params], enctype: result[:enctype], source: :form ) end |
#__js_call__(method, args) ⇒ Object
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'lib/dommy/html_elements.rb', line 470 def __js_call__(method, args) case method when "submit" submit when "reset" reset when "requestSubmit" request_submit(args[0]) when "checkValidity" check_validity when "reportValidity" report_validity else super end end |
#__js_get__(key) ⇒ Object
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/dommy/html_elements.rb', line 419 def __js_get__(key) # HTMLFormElement is [LegacyOverrideBuiltIns]: a control whose name/id # matches a builtin (`elements`, `length`, `submit`, `action`, …) shadows # that builtin. So the named getter is consulted BEFORE the builtins. named = named_controls[key.to_s] return __named_getter_result__(key.to_s, named) if named && !named.empty? case key when "elements" elements when "length" length else super end end |
#__js_named_props__ ⇒ Object
WebIDL named getter: the form's supported property names are the name/id of each of its listed controls.
449 450 451 |
# File 'lib/dommy/html_elements.rb', line 449 def __js_named_props__ named_controls.keys end |
#__named_getter_result__(name, matches) ⇒ Object
A single matching control is returned directly; multiple matches yield a
RadioNodeList. The list is memoized per name and refreshed in place so
repeated named-getter reads return the [SameObject] (WebIDL requires
form.d === form.d), while still reflecting live membership.
440 441 442 443 444 445 |
# File 'lib/dommy/html_elements.rb', line 440 def __named_getter_result__(name, matches) return matches.first if matches.length == 1 form = self (@__radio_lists ||= {})[name] ||= RadioNodeList.new { form.named_controls[name] || [] } end |
#__owns_control__(control) ⇒ Object
The form owner of a listed control, per WHATWG: when the control carries a
form content attribute, its owner is the form element with that id (or
nothing, if the id resolves to a non-form / nothing); otherwise it is the
nearest ancestor form element.
315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/dommy/html_elements.rb', line 315 def __owns_control__(control) form_id = control.__dommy_backend_node__["form"].to_s owner = if form_id.empty? control.closest("form") else target = control.document.get_element_by_id(form_id) (target && target.tag_name.to_s.casecmp?("form")) ? target : nil end !owner.nil? && owner.__dommy_backend_node__.equal?(__dommy_backend_node__) end |
#__run_form_submission__(submitter = nil) ⇒ Object
The form submission algorithm's observable core, shared by
requestSubmit(), a submit button's activation (driver click), and Enter's
implicit submission: fire a cancelable SubmitEvent (carrying the
submitter), and — when nothing canceled it — hand the resulting navigation
to the delegate. Returns true if not default-prevented. This is the single
home for "a form was submitted"; callers that previously dispatched a bare
submit event route here so the event is a real SubmitEvent (with
submitter) and the navigation reaches the delegate.
374 375 376 377 378 379 380 |
# File 'lib/dommy/html_elements.rb', line 374 def __run_form_submission__(submitter = nil) not_canceled = dispatch_event( SubmitEvent.new("submit", "bubbles" => true, "cancelable" => true, "submitter" => submitter) ) __internal_navigate_for_submit__(submitter) if not_canceled not_canceled end |
#check_validity ⇒ Object
Walk all listed elements; the form is "valid" iff every
candidate control passes its own checkValidity. Dispatches a
non-bubbling invalid event on each failing control.
400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/dommy/html_elements.rb', line 400 def check_validity ok = true elements.each do |el| next unless el.respond_to?(:will_validate) next unless el.will_validate next if el.validity.valid && (el.instance_variable_get(:@custom_validity_message) || "").empty? # Fire invalid event on this control (matches spec). el.dispatch_event(Event.new("invalid", "bubbles" => false, "cancelable" => true)) ok = false end ok end |
#elements ⇒ Object
300 301 302 303 304 305 306 307 308 309 |
# File 'lib/dommy/html_elements.rb', line 300 def elements el = self @elements ||= HTMLFormControlsCollection.new do el.document.query_selector_all(LISTED_CONTROL_SELECTOR).select do |c| next false if c.tag_name.to_s.casecmp?("input") && c.respond_to?(:type) && c.type.to_s.casecmp?("image") el.__owns_control__(c) end end end |
#length ⇒ Object
327 328 329 |
# File 'lib/dommy/html_elements.rb', line 327 def length elements.size end |
#named_controls ⇒ Object
name/id -> [controls], for the named getter (a name matching more than one control yields a RadioNodeList-like NodeList).
455 456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'lib/dommy/html_elements.rb', line 455 def named_controls map = ::Hash.new { |h, k| h[k] = [] } elements.each do |el| next unless el.respond_to?(:__dommy_backend_node__) node = el.__dommy_backend_node__ name = node["name"].to_s map[name] << el unless name.empty? id = node["id"].to_s map[id] << el unless id.empty? || id == name end map end |
#report_validity ⇒ Object
415 416 417 |
# File 'lib/dommy/html_elements.rb', line 415 def report_validity check_validity end |
#request_submit(submitter = nil) ⇒ Object
Spec: requestSubmit(submitter?) MIRRORS user-initiated submission — it
fires a submit event (with the submitter), and on a non-canceled event
hands the form navigation to the delegate. Returns true if not
default-prevented. submitter (if given) must be a submit button inside
this form.
351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/dommy/html_elements.rb', line 351 def request_submit(submitter = nil) if submitter unless submitter.respond_to?(:__dommy_backend_node__) && submitter.__dommy_backend_node__.ancestors.include?(@__node__) raise DOMException::NotFoundError, "submitter is not a descendant of this form" end type = submitter.respond_to?(:type) ? submitter.type.to_s.downcase : "" unless %w[submit image].include?(type) raise TypeError, "submitter must be a submit button" end end __run_form_submission__(submitter) end |
#reset ⇒ Object
Spec: reset() does fire a reset event; if the event is
default-prevented, no reset happens. Dommy has no built-in
control re-init logic, so we just dispatch the event.
342 343 344 |
# File 'lib/dommy/html_elements.rb', line 342 def reset dispatch_event(Event.new("reset", "bubbles" => true, "cancelable" => true)) end |
#submit ⇒ Object
Spec: submit() performs form submission directly WITHOUT firing a
submit event and without constraint validation. Navigation is handed to
the delegate (a no-op recording by default).
334 335 336 337 |
# File 'lib/dommy/html_elements.rb', line 334 def submit __internal_navigate_for_submit__(nil) nil end |