Class: Dommy::Location

Inherits:
Object
  • Object
show all
Includes:
Bridge::Methods
Defined in:
lib/dommy/location.rb

Overview

window.location polyfill. The Window owns one Location and one History instance, and they share the same underlying state. Hash / pushState / replaceState all flow through __internal_set_url__.

Instance Method Summary collapse

Methods included from Bridge::Methods

included

Constructor Details

#initialize(window, origin: "http://localhost", pathname: "/", search: "", hash: "") ⇒ Location

Returns a new instance of Location.



10
11
12
13
14
15
16
# File 'lib/dommy/location.rb', line 10

def initialize(window, origin: "http://localhost", pathname: "/", search: "", hash: "")
  @window = window
  @origin = origin
  @pathname = pathname
  @search = search
  @hash = hash
end

Instance Method Details

#__internal_navigate_to__(raw, source:, replace: false, sync_cross_doc: true) ⇒ Object

location.href = X / assign / replace, and the shared entry point for a hyperlink's follow-the-hyperlink. A navigation that changes only the fragment is same-document (always updates the hash + fires hashchange); any other change is cross-document — the intent is handed to the delegate.

sync_cross_doc controls whether a cross-document target also mutates the URL parts synchronously: true for location.href=/assign/replace (a backward-compatible behavior existing code relies on), false for a link click (which leaves the location untouched until the delegate actually navigates — so "nothing happened" is observable with the default NullDelegate). A real delegate rebinds Location on document replacement regardless, so this only affects the no-op default.



134
135
136
137
138
139
140
141
142
# File 'lib/dommy/location.rb', line 134

def __internal_navigate_to__(raw, source:, replace: false, sync_cross_doc: true)
  target = resolve(raw)
  if same_document?(href, target)
    __internal_set_url__(raw)
  else
    __internal_set_url__(raw, fire_hash: false) if sync_cross_doc
    @window.__internal_navigate__(url: target, method: "GET", replace: replace, source: source)
  end
end

#__internal_set_url__(raw, fire_hash: true) ⇒ Object

Internal — accepts an absolute or relative URL string and updates pathname / search / hash. Called by History pushState / replaceState (with fire_hash: false, since a pushState never fires hashchange) and by the same-document navigation path. fire_hash gates the hashchange event so callers that handle the fragment-change signal themselves can suppress it.



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/dommy/location.rb', line 103

def __internal_set_url__(raw, fire_hash: true)
  previous_hash = @hash
  previous_href = href
  if raw.start_with?("#")
    @hash = raw
  else
    uri = URI.join(@origin + @pathname + @search + @hash, raw) rescue URI(raw)
    # An absolute URL (carrying scheme + host) navigates to a new
    # origin; a relative URL inherits the current origin from the
    # join base, so rebuilding with the same parts is a no-op.
    rebuild_origin(scheme: uri.scheme, host: uri.host, port: uri.port) if uri.scheme && uri.host
    @pathname = uri.path.to_s == "" ? "/" : uri.path
    @search = uri.query ? "?#{uri.query}" : ""
    @hash = uri.fragment ? "##{uri.fragment}" : ""
  end

  @window.fire_hashchange(previous_href, href) if fire_hash && previous_hash != @hash
end

#__js_call__(method, args) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/dommy/location.rb', line 80

def __js_call__(method, args)
  case method
  when "assign"
    __internal_navigate_to__(args[0].to_s, replace: false, source: :location)
  when "replace"
    __internal_navigate_to__(args[0].to_s, replace: true, source: :location)
  when "reload"
    # A reload re-requests the current URL (never same-document).
    @window.__internal_navigate__(url: href, method: "GET", replace: true, source: :reload)
  when "toString"
    href
  end
end

#__js_get__(key) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/dommy/location.rb', line 18

def __js_get__(key)
  case key
  when "origin"
    @origin
  when "pathname"
    @pathname
  when "search"
    @search
  when "hash"
    @hash
  when "href"
    href
  when "host"
    # WHATWG host = hostname, plus ":port" only when the port is non-default.
    uri = URI(@origin)
    hostname = uri.host || ""
    port = origin_port_string(uri)
    port.empty? ? hostname : "#{hostname}:#{port}"
  when "hostname"
    URI(@origin).host || ""
  when "protocol"
    URI(@origin).scheme ? "#{URI(@origin).scheme}:" : ""
  when "port"
    origin_port_string(URI(@origin))
  else
    Bridge::ABSENT
  end
end

#__js_set__(key, value) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/dommy/location.rb', line 47

def __js_set__(key, value)
  case key
  when "href"
    __internal_navigate_to__(value.to_s, replace: false, source: :location)
  when "hash"
    new_hash = value.to_s
    new_hash = "##{new_hash}" unless new_hash.empty? || new_hash.start_with?("#")
    return if new_hash == @hash

    previous_href = href
    @hash = new_hash
    # Setting the fragment is always same-document — fire hashchange with the
    # full URLs before/after (no delegate navigation).
    @window.fire_hashchange(previous_href, href)
  when "pathname"
    @pathname = value.to_s
  when "search"
    s = value.to_s
    @search = s.empty? || s.start_with?("?") ? s : "?#{s}"
  when "host"
    # `host` is "hostname[:port]" — split and update origin.
    update_origin_host(value.to_s)
  when "hostname"
    update_origin_hostname(value.to_s)
  when "port"
    update_origin_port(value.to_s)
  when "protocol"
    update_origin_protocol(value.to_s)
  end
end

#hrefObject



94
95
96
# File 'lib/dommy/location.rb', line 94

def href
  "#{@origin}#{@pathname}#{@search}#{@hash}"
end