pico_phone-rails 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +42 -0
- data/config/routes.rb +5 -0
- data/lib/pico_phone/rails/engine.rb +26 -0
- data/lib/pico_phone/rails/form_helper.rb +24 -2
- data/lib/pico_phone/rails/javascript/pico_phone/rails/phone_controller.js +38 -0
- data/lib/pico_phone/rails/javascript.rb +3 -0
- data/lib/pico_phone/rails/validations_controller.rb +37 -0
- data/lib/pico_phone/rails/version.rb +1 -1
- data/lib/pico_phone/rails.rb +1 -0
- metadata +13 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e4c80e86756e81b40665de3fe0c6436ee462b8d5ea011992e3a8507eaedb323
|
|
4
|
+
data.tar.gz: 8ce58cb67fc8b73d92ba3b945b303a5c7360265a6ac217e5c0e1e1806c2157b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca51d0d37018f62fceba7ac87b2f0ebd42f4e537b55d9814e33488b48ea30aa822fd18eef92713d4a9b464e8849298e91ca41b430d1f99d471aba28b371b494b
|
|
7
|
+
data.tar.gz: dba580ca4a43fe7b83e776cf4e8430a8ca19cf9fc97f0f059c423fdd4e6ae6117f9c1dd702b4ccf878a13f3a848ac271041b62e3350d9143b230cc16523f1d33
|
data/README.md
CHANGED
|
@@ -206,6 +206,48 @@ a plain `<input type="tel">` with no formatting) -- redefining a core Rails
|
|
|
206
206
|
helper would silently change behavior for every `phone_field` call in an
|
|
207
207
|
app, not just ones backed by a PicoPhone-managed attribute.
|
|
208
208
|
|
|
209
|
+
### Live validation
|
|
210
|
+
|
|
211
|
+
Mount the engine:
|
|
212
|
+
|
|
213
|
+
```ruby
|
|
214
|
+
# config/routes.rb
|
|
215
|
+
mount PicoPhone::Rails::Engine, at: "/pico_phone"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The controller is auto-pinned into `importmap-rails` when present, but
|
|
219
|
+
still needs to be registered with your Stimulus application -- pinning
|
|
220
|
+
only makes it importable, the same as any other pinned module:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
// app/javascript/controllers/index.js
|
|
224
|
+
import { application } from "controllers/application"
|
|
225
|
+
import PhoneController from "pico_phone/rails/phone_controller"
|
|
226
|
+
application.register("phone", PhoneController)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Then opt in per field:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
<%= f.pico_phone_field :phone, region: "US", live: true %>
|
|
233
|
+
<span data-phone-target="error"></span>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Debounces input and POSTs to the mounted engine, showing the error message
|
|
237
|
+
in a sibling `data-phone-target="error"` element (anywhere under the same
|
|
238
|
+
parent as the field -- `data-controller` sits on the `<input>` itself,
|
|
239
|
+
which can't have descendants, so the target is found via the field's
|
|
240
|
+
parent rather than Stimulus's usual descendant-scoped target lookup)
|
|
241
|
+
while the user types. Never rewrites the field's value while it has
|
|
242
|
+
focus -- only on blur, reformatted to national format if what's there
|
|
243
|
+
parses validly, the same reformat-on-blur behavior as the plain
|
|
244
|
+
(non-`live`) helper.
|
|
245
|
+
|
|
246
|
+
`ValidationsController` uses your app's normal CSRF protection -- the
|
|
247
|
+
controller reads the token from the page's `<meta name="csrf-token">`
|
|
248
|
+
(rendered by Rails' own `csrf_meta_tags`, already in any standard layout)
|
|
249
|
+
and sends it with every request, no extra setup needed.
|
|
250
|
+
|
|
209
251
|
### ActiveJob serializer
|
|
210
252
|
|
|
211
253
|
```ruby
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/engine"
|
|
4
|
+
require "pico_phone/rails/validations_controller"
|
|
5
|
+
|
|
6
|
+
module PicoPhone
|
|
7
|
+
module Rails
|
|
8
|
+
class Engine < ::Rails::Engine
|
|
9
|
+
isolate_namespace PicoPhone::Rails
|
|
10
|
+
|
|
11
|
+
initializer "pico_phone_rails.assets" do |app|
|
|
12
|
+
app.config.assets.paths << Pathname.new(__dir__).join("javascript") if app.config.respond_to?(:assets)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# importmap-rails draws config.importmap.paths in its own "importmap"
|
|
16
|
+
# initializer -- appending after that is a no-op, so this must run before.
|
|
17
|
+
# A no-op itself if importmap-rails isn't installed.
|
|
18
|
+
initializer "pico_phone_rails.importmap", before: "importmap" do |app|
|
|
19
|
+
next unless app.config.respond_to?(:importmap)
|
|
20
|
+
|
|
21
|
+
app.config.importmap.paths << Pathname.new(__dir__).join("javascript.rb")
|
|
22
|
+
app.config.importmap.cache_sweepers << Pathname.new(__dir__).join("javascript")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -22,6 +22,18 @@ module PicoPhone
|
|
|
22
22
|
phone_number.valid? ? phone_number.national : string
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# @param region [String, nil]
|
|
26
|
+
# @param validate_path [String] the mounted engine's validate endpoint
|
|
27
|
+
# @return [Hash] Stimulus data attributes for +live:+ validation
|
|
28
|
+
def self.live_validation_data(region, validate_path)
|
|
29
|
+
{
|
|
30
|
+
controller: "phone",
|
|
31
|
+
action: "input->phone#validate blur->phone#reformat",
|
|
32
|
+
phone_region_value: region.to_s,
|
|
33
|
+
phone_url_value: validate_path
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
25
37
|
# Included into ActionView::Base by the railtie once ActionView loads.
|
|
26
38
|
# Adds +pico_phone_field_tag+, a +text_field_tag+-like helper that
|
|
27
39
|
# displays national format for a valid number while leaving the
|
|
@@ -39,9 +51,14 @@ module PicoPhone
|
|
|
39
51
|
# @param name [String, Symbol]
|
|
40
52
|
# @param value [String, PicoPhone::PhoneNumber, nil]
|
|
41
53
|
# @param region [String, nil] ISO 3166-1 alpha-2 region for interpreting +value+ when it's a raw string
|
|
54
|
+
# @param live [Boolean] wire up debounced validation/reformatting; requires the engine to be mounted
|
|
42
55
|
# @return [String] an HTML-safe +<input type="tel">+ tag
|
|
43
|
-
def pico_phone_field_tag(name, value = nil, region: nil, **options)
|
|
56
|
+
def pico_phone_field_tag(name, value = nil, region: nil, live: false, **options)
|
|
44
57
|
display_value = PicoPhone::Rails.phone_field_display_value(value, region)
|
|
58
|
+
if live
|
|
59
|
+
data = PicoPhone::Rails.live_validation_data(region, pico_phone_rails.validate_path)
|
|
60
|
+
options[:data] = data.merge(options[:data] || {})
|
|
61
|
+
end
|
|
45
62
|
text_field_tag(name, display_value, options.merge(type: "tel"))
|
|
46
63
|
end
|
|
47
64
|
end
|
|
@@ -59,10 +76,15 @@ module PicoPhone
|
|
|
59
76
|
# raw value when it isn't already a {PicoPhone::PhoneNumber} -- a String is used as-is, a Symbol is
|
|
60
77
|
# called as an instance method on the form's object, a Proc is called with the object, same resolution
|
|
61
78
|
# rules as {Extraction.extract_phone_numbers_from} and {PhoneSearchIndex.maintain_phone_search_index}
|
|
79
|
+
# @param live [Boolean] wire up debounced validation/reformatting; requires the engine to be mounted
|
|
62
80
|
# @return [String] an HTML-safe +<input type="tel">+ tag
|
|
63
|
-
def pico_phone_field(method, region: nil, **options)
|
|
81
|
+
def pico_phone_field(method, region: nil, live: false, **options)
|
|
64
82
|
resolved_region = PicoPhone::Rails.resolve_region(region, object)
|
|
65
83
|
display_value = PicoPhone::Rails.phone_field_display_value(object.public_send(method), resolved_region)
|
|
84
|
+
if live
|
|
85
|
+
data = PicoPhone::Rails.live_validation_data(resolved_region, @template.pico_phone_rails.validate_path)
|
|
86
|
+
options[:data] = data.merge(options[:data] || {})
|
|
87
|
+
end
|
|
66
88
|
text_field(method, options.merge(value: display_value, type: "tel"))
|
|
67
89
|
end
|
|
68
90
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// data-controller sits on the <input> itself, which can't have children --
|
|
4
|
+
// so the error element (a sibling, placed wherever the caller wants) is
|
|
5
|
+
// unreachable via Stimulus's normal descendant-scoped static targets.
|
|
6
|
+
// Found by hand instead, scoped to the input's parent.
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
static values = { url: String, region: String, debounce: { type: Number, default: 300 } }
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
this.errorElement = this.element.parentElement?.querySelector('[data-phone-target="error"]') ?? null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
validate() {
|
|
15
|
+
clearTimeout(this.timeout)
|
|
16
|
+
this.timeout = setTimeout(() => this.check(), this.debounceValue)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async reformat() {
|
|
20
|
+
const data = await this.check()
|
|
21
|
+
if (data.valid) this.element.value = data.national
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async check() {
|
|
25
|
+
const headers = { "Content-Type": "application/json", Accept: "application/json" }
|
|
26
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content
|
|
27
|
+
if (csrfToken) headers["X-CSRF-Token"] = csrfToken
|
|
28
|
+
|
|
29
|
+
const response = await fetch(this.urlValue, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers,
|
|
32
|
+
body: JSON.stringify({ phone: this.element.value, region: this.regionValue }),
|
|
33
|
+
})
|
|
34
|
+
const data = await response.json()
|
|
35
|
+
if (this.errorElement) this.errorElement.textContent = data.valid || data.blank ? "" : data.message
|
|
36
|
+
return data
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_controller"
|
|
4
|
+
|
|
5
|
+
module PicoPhone
|
|
6
|
+
module Rails
|
|
7
|
+
# `region:` always arrives as an already-resolved String -- no record lookup.
|
|
8
|
+
class ValidationsController < ActionController::Base
|
|
9
|
+
def validate
|
|
10
|
+
phone = params[:phone].to_s
|
|
11
|
+
return render json: { valid: false, blank: true } if phone.strip.empty?
|
|
12
|
+
|
|
13
|
+
phone_number = PicoPhone.parse(phone, params[:region].presence)
|
|
14
|
+
|
|
15
|
+
if phone_number.valid?
|
|
16
|
+
render json: {
|
|
17
|
+
valid: true,
|
|
18
|
+
blank: false,
|
|
19
|
+
e164: safe_phone_call(phone_number, :e164),
|
|
20
|
+
national: safe_phone_call(phone_number, :national)
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
render json: { valid: false, blank: false, message: I18n.t("errors.messages.invalid_phone") }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# @return [String, nil] nil if unparseable, matching PhoneSearchIndex's guard
|
|
30
|
+
def safe_phone_call(phone_number, method_name)
|
|
31
|
+
phone_number.public_send(method_name)
|
|
32
|
+
rescue TypeError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/pico_phone/rails.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pico_phone-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabi Jack
|
|
@@ -56,8 +56,11 @@ description: 'pico_phone-rails wires the pico_phone gem into Rails: a :phone_num
|
|
|
56
56
|
class macro that rewrites phone attributes to E.164 before validation, extract_phone_numbers_from
|
|
57
57
|
for pulling phone numbers out of free text (with an optional persisted backend for
|
|
58
58
|
cross-record search), maintain_phone_search_index for keeping search columns in
|
|
59
|
-
sync on an existing phone-number table,
|
|
60
|
-
|
|
59
|
+
sync on an existing phone-number table, pico_phone_field_tag/f.pico_phone_field
|
|
60
|
+
form helpers that display national format for a valid number without discarding
|
|
61
|
+
what the user typed, live: true on those same helpers for debounced Stimulus-driven
|
|
62
|
+
validation and reformatting via a mountable engine, and an ActiveJob serializer
|
|
63
|
+
so a PhoneNumber survives being passed as a job argument.'
|
|
61
64
|
email:
|
|
62
65
|
- gabi@gabijack.com
|
|
63
66
|
executables: []
|
|
@@ -66,6 +69,7 @@ extra_rdoc_files: []
|
|
|
66
69
|
files:
|
|
67
70
|
- LICENSE.txt
|
|
68
71
|
- README.md
|
|
72
|
+
- config/routes.rb
|
|
69
73
|
- lib/generators/pico_phone/rails/extracted_phone_numbers/extracted_phone_numbers_generator.rb
|
|
70
74
|
- lib/generators/pico_phone/rails/extracted_phone_numbers/templates/create_pico_phone_rails_extracted_phone_numbers.rb.tt
|
|
71
75
|
- lib/generators/pico_phone/rails/phone_number/phone_number_generator.rb
|
|
@@ -73,9 +77,12 @@ files:
|
|
|
73
77
|
- lib/generators/pico_phone/rails/phone_number/templates/model.rb.tt
|
|
74
78
|
- lib/phone_validator.rb
|
|
75
79
|
- lib/pico_phone/rails.rb
|
|
80
|
+
- lib/pico_phone/rails/engine.rb
|
|
76
81
|
- lib/pico_phone/rails/extracted_phone_number.rb
|
|
77
82
|
- lib/pico_phone/rails/extraction.rb
|
|
78
83
|
- lib/pico_phone/rails/form_helper.rb
|
|
84
|
+
- lib/pico_phone/rails/javascript.rb
|
|
85
|
+
- lib/pico_phone/rails/javascript/pico_phone/rails/phone_controller.js
|
|
79
86
|
- lib/pico_phone/rails/locale/en.yml
|
|
80
87
|
- lib/pico_phone/rails/normalizer.rb
|
|
81
88
|
- lib/pico_phone/rails/phone_search_index.rb
|
|
@@ -83,6 +90,7 @@ files:
|
|
|
83
90
|
- lib/pico_phone/rails/region_resolution.rb
|
|
84
91
|
- lib/pico_phone/rails/serializers/phone_number_serializer.rb
|
|
85
92
|
- lib/pico_phone/rails/type.rb
|
|
93
|
+
- lib/pico_phone/rails/validations_controller.rb
|
|
86
94
|
- lib/pico_phone/rails/version.rb
|
|
87
95
|
homepage: https://github.com/gjack/pico_phone-rails
|
|
88
96
|
licenses:
|
|
@@ -111,5 +119,6 @@ requirements: []
|
|
|
111
119
|
rubygems_version: 3.6.9
|
|
112
120
|
specification_version: 4
|
|
113
121
|
summary: 'Rails integration for pico_phone: attribute type, validator, normalizer,
|
|
114
|
-
free-text extraction, phone search index,
|
|
122
|
+
free-text extraction, phone search index, form field helpers, live validation, and
|
|
123
|
+
ActiveJob serializer'
|
|
115
124
|
test_files: []
|