handy_location_inputs 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +56 -0
- data/Rakefile +32 -0
- data/app/assets/config/handy_location_inputs_manifest.js +2 -0
- data/app/assets/javascripts/handy_location_inputs/input_controllers.js +180 -0
- data/app/assets/stylesheets/handy_location_inputs/application.css +15 -0
- data/app/controllers/concerns/handy_location_inputs/locatable.rb +40 -0
- data/app/controllers/handy_location_inputs/locations_controller.rb +30 -0
- data/app/helpers/handy_location_inputs/application_helper.rb +4 -0
- data/app/jobs/handy_location_inputs/application_job.rb +4 -0
- data/app/mailers/handy_location_inputs/application_mailer.rb +6 -0
- data/app/models/handy_location_inputs/application_record.rb +5 -0
- data/app/views/layouts/handy_location_inputs/application.html.erb +16 -0
- data/config/routes.rb +4 -0
- data/lib/handy_location_inputs.rb +5 -0
- data/lib/handy_location_inputs/engine.rb +5 -0
- data/lib/handy_location_inputs/version.rb +3 -0
- data/lib/tasks/handy_location_inputs_tasks.rake +4 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 278e0cc94cffc6ccfa881af0d79356dbd4b898e84de67dfbab4d02a68c5d849b
|
4
|
+
data.tar.gz: ec2af4700802febe3495a11ce38f005cdb00a0ad3658b267ead66449293868b3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 32252ebe82b061b901ddd8570e1926b5c6789f376bd159434eee3c1d3354216ef9e8f35781550cb2f251a900eaeedac9cfd54d2b1e1fd80be06919cedb7e8617
|
7
|
+
data.tar.gz: 52cc17252396bb125ab63dcfe46bb70ebec013c58e7b9dd37f81c75dc937f7968beccd476156303ec2284f9d0dfa1c323dda1304bb6da738eeeb2a5bd4ba21e2
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2019 handofthecode
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# HandyLocationInputs
|
2
|
+
HandyLocationInputs is a rails gem/engine for adding powerful location inputs to your forms. It allows you to guarentee your Country, State, and City names will be uniform without the UX penalties of Select Boxes.
|
3
|
+
|
4
|
+
## Requirements
|
5
|
+
Rails with turbolinks and bulma css. (Future versions will remove the dependency on Bulma.)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'handy_location_inputs'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install handy_location_inputs
|
22
|
+
```
|
23
|
+
|
24
|
+
## Setup
|
25
|
+
|
26
|
+
First, add this line to your app/assets/javascripts/application.js manifest file.
|
27
|
+
|
28
|
+
```javascript
|
29
|
+
// = require handy_location_inputs/input_controllers
|
30
|
+
```
|
31
|
+
|
32
|
+
To use handy_location_inputs in a view, ```include HandyLocationInputs::Locatable``` to that view's controller.
|
33
|
+
|
34
|
+
If the associated model is new, or has no location data yet, call ```country_list_to_client``` to the controller action. This will pass the country list to the client.
|
35
|
+
|
36
|
+
If the model already has state and city data that you would like to pass to the client as well, call ```location_lists_to_client(your_model)```.
|
37
|
+
|
38
|
+
### In The View
|
39
|
+
|
40
|
+
To trigger functionality in a form, include the class ```handy-location-inputs``` somewhere on the page.
|
41
|
+
|
42
|
+
Each input needs an id of ```country-input```, ```state-input```, or ```city-input``` respectively.
|
43
|
+
|
44
|
+
The dropdown for each input should look like this.
|
45
|
+
|
46
|
+
```html
|
47
|
+
<div class="dropdown country-dropdown">
|
48
|
+
<div class="dropdown-menu" role="menu">
|
49
|
+
<div class="country-dropdown-content">
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
```
|
54
|
+
|
55
|
+
## License
|
56
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'HandyLocationInputs'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,180 @@
|
|
1
|
+
document.addEventListener("turbolinks:load", function(){
|
2
|
+
if(!document.querySelector('.handy-location-inputs')) return;
|
3
|
+
|
4
|
+
var countryInput = document.getElementById("country-input");
|
5
|
+
var countryDropdown = document.querySelector('.country-dropdown');
|
6
|
+
var countryDropdownContent = document.querySelector('.country-dropdown-content')
|
7
|
+
|
8
|
+
var stateInput = document.getElementById("state-input");
|
9
|
+
var stateDropdown = document.querySelector('.state-dropdown');
|
10
|
+
var stateDropdownContent = document.querySelector('.state-dropdown-content')
|
11
|
+
|
12
|
+
var cityInput = document.getElementById("city-input");
|
13
|
+
var cityDropdown = document.querySelector('.city-dropdown');
|
14
|
+
var cityDropdownContent = document.querySelector('.city-dropdown-content')
|
15
|
+
|
16
|
+
class InputController {
|
17
|
+
constructor(type, input, dropdown, dropdownContent) {
|
18
|
+
this.type = type;
|
19
|
+
this.input = input;
|
20
|
+
this.dropdown = dropdown;
|
21
|
+
this.dropdownContent = dropdownContent;
|
22
|
+
this.setPlaces();
|
23
|
+
this.addAllEventListeners();
|
24
|
+
}
|
25
|
+
|
26
|
+
addAllEventListeners() {
|
27
|
+
this.addInputListener();
|
28
|
+
this.addClickListener();
|
29
|
+
this.addBlurInputListener();
|
30
|
+
this.addFocusListener();
|
31
|
+
this.addClearSubordinateInputsListener();
|
32
|
+
}
|
33
|
+
|
34
|
+
addClearSubordinateInputsListener() {
|
35
|
+
this.input.addEventListener('change', () => this.clearSubordinateInputs());
|
36
|
+
}
|
37
|
+
|
38
|
+
addClickListener() {
|
39
|
+
this.dropdown.addEventListener('click', e => {
|
40
|
+
this.input.value = e.target.innerText;
|
41
|
+
var nextInput = this.subordinateInputs()[0];
|
42
|
+
if(nextInput) {
|
43
|
+
this.ajaxCallForSubordinateInput();
|
44
|
+
nextInput.focus();
|
45
|
+
}
|
46
|
+
});
|
47
|
+
}
|
48
|
+
|
49
|
+
subordinateInputs() {
|
50
|
+
var result;
|
51
|
+
if(this.type === 'countries') {
|
52
|
+
result = [stateInput, cityInput];
|
53
|
+
} else if(this.type === 'states') {
|
54
|
+
result = [cityInput];
|
55
|
+
} else {
|
56
|
+
result = [];
|
57
|
+
}
|
58
|
+
return result
|
59
|
+
}
|
60
|
+
|
61
|
+
subordinateInputType() {
|
62
|
+
if(this.type === 'countries') {
|
63
|
+
return 'states';
|
64
|
+
} else if(this.type === 'states') {
|
65
|
+
return 'cities';
|
66
|
+
} else return null;
|
67
|
+
}
|
68
|
+
|
69
|
+
superiorController() {
|
70
|
+
if(this.type === 'states') return countryInputController;
|
71
|
+
if(this.type === 'cities') return stateInputController;
|
72
|
+
}
|
73
|
+
|
74
|
+
clearSubordinateInputs() {
|
75
|
+
this.subordinateInputs().forEach(input => input.value = '');
|
76
|
+
}
|
77
|
+
|
78
|
+
inputIsValid() {
|
79
|
+
return this.input.value &&
|
80
|
+
gon[this.type] &&
|
81
|
+
this.inputWithProperCase();
|
82
|
+
}
|
83
|
+
|
84
|
+
inputWithProperCase() {
|
85
|
+
return this.places.find(place => place.toLowerCase() === this.input.value.toLowerCase())
|
86
|
+
}
|
87
|
+
|
88
|
+
fixCase() {
|
89
|
+
if(this.inputWithProperCase()) {
|
90
|
+
this.input.value = this.inputWithProperCase();
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
ajaxCallForSubordinateInput() {
|
95
|
+
if(!this.inputIsValid()) return;
|
96
|
+
if(this.type === 'cities') return;
|
97
|
+
var url;
|
98
|
+
var data;
|
99
|
+
if(this.type === 'countries') {
|
100
|
+
url = "/HandyLocationInputs/states.json";
|
101
|
+
data = `country=${this.inputWithProperCase()}`;
|
102
|
+
} else if(this.type === 'states') {
|
103
|
+
url = "/HandyLocationInputs/cities.json";
|
104
|
+
data = `country=${countryInputController.inputWithProperCase()}&state=${this.inputWithProperCase()}`
|
105
|
+
}
|
106
|
+
Rails.ajax({
|
107
|
+
dataType: "json",
|
108
|
+
url: url,
|
109
|
+
type: "POST",
|
110
|
+
data: data,
|
111
|
+
beforeSend: () => true,
|
112
|
+
success: (data) => {
|
113
|
+
gon[this.subordinateInputType()] = data[this.subordinateInputType()];
|
114
|
+
},
|
115
|
+
});
|
116
|
+
}
|
117
|
+
|
118
|
+
addBlurInputListener() {
|
119
|
+
this.input.addEventListener('blur', () => {
|
120
|
+
this.fixCase();
|
121
|
+
this.ajaxCallForSubordinateInput();
|
122
|
+
setTimeout(() => {
|
123
|
+
this.dropdown.classList.remove('is-active');
|
124
|
+
}, 300)
|
125
|
+
});
|
126
|
+
}
|
127
|
+
|
128
|
+
addFocusListener() {
|
129
|
+
this.input.addEventListener('focus', () => {
|
130
|
+
if(!this.superiorController()) {
|
131
|
+
this.input.dispatchEvent(new Event('keyup'));
|
132
|
+
} else if(this.superiorController().inputIsValid()) {
|
133
|
+
setTimeout(() => {
|
134
|
+
this.input.dispatchEvent(new Event('keyup'))
|
135
|
+
}, 1000)
|
136
|
+
} else {
|
137
|
+
this.superiorController().input.focus()
|
138
|
+
}
|
139
|
+
});
|
140
|
+
}
|
141
|
+
|
142
|
+
setPlaces() {
|
143
|
+
this.places;
|
144
|
+
if(gon) {
|
145
|
+
if(this.type === 'countries') {
|
146
|
+
this.places = gon.countries;
|
147
|
+
} else if(this.type === 'states') {
|
148
|
+
this.places = gon.states;
|
149
|
+
} else if(this.type === 'cities') {
|
150
|
+
this.places = gon.cities;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
if(!this.places || !this.places.length) return;
|
154
|
+
}
|
155
|
+
|
156
|
+
addInputListener() {
|
157
|
+
this.input.addEventListener("keyup", () => {
|
158
|
+
var inputValLower = this.input.value.toLowerCase();
|
159
|
+
this.dropdown.classList.add('is-active');
|
160
|
+
this.setPlaces();
|
161
|
+
this.matching = this.places.filter(c => c.toLowerCase().slice(0, inputValLower.length) == (inputValLower)).slice(0, 5);
|
162
|
+
if(this.dropdownContent.firstChild) {
|
163
|
+
while (this.dropdownContent.firstChild) {
|
164
|
+
this.dropdownContent.removeChild(this.dropdownContent.firstChild);
|
165
|
+
}
|
166
|
+
}
|
167
|
+
this.matching.forEach((c) => {
|
168
|
+
let item = document.createElement("div");
|
169
|
+
item.className = 'dropdown-item';
|
170
|
+
item.innerText = c;
|
171
|
+
this.dropdownContent.appendChild(item)
|
172
|
+
});
|
173
|
+
});
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
countryInputController = new InputController('countries', countryInput, countryDropdown, countryDropdownContent)
|
178
|
+
stateInputController = new InputController('states', stateInput, stateDropdown, stateDropdownContent)
|
179
|
+
cityInputController = new InputController('cities', cityInput, cityDropdown, cityDropdownContent)
|
180
|
+
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module HandyLocationInputs
|
2
|
+
module Locatable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def country_list_to_client
|
8
|
+
countries = CS.countries.map { |c| c[1] }.sort
|
9
|
+
countries.delete("country_name")
|
10
|
+
countries.delete("United States")
|
11
|
+
countries.unshift("United States")
|
12
|
+
gon.countries = countries
|
13
|
+
end
|
14
|
+
|
15
|
+
def location_lists_to_client(model)
|
16
|
+
country_list_to_client
|
17
|
+
gon.states = CS.states(country_key model).values if !model[:country].blank?
|
18
|
+
gon.cities = CS.cities(state_key(model), country_key(model)) if !model[:state].blank?
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_location?(model)
|
22
|
+
return false if state_key(model).blank?
|
23
|
+
return false if !CS.cities(state_key(model), country_key(model)).blank? &&
|
24
|
+
CS.cities(state_key(model), country_key(model)).map(&:downcase).exclude?(model[:city].downcase)
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def country_key(model)
|
29
|
+
@country_key ||= CS.countries.key(model[:country])
|
30
|
+
end
|
31
|
+
|
32
|
+
def state_key(model)
|
33
|
+
@state_key ||= return_key(CS.states(country_key(model)), model[:state])
|
34
|
+
end
|
35
|
+
|
36
|
+
def return_key(obj, match)
|
37
|
+
obj.each {|key, val| break key if key.to_s == match || val == match}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module HandyLocationInputs
|
2
|
+
class LocationsController < ApplicationController
|
3
|
+
protect_from_forgery with: :exception
|
4
|
+
|
5
|
+
include HandyLocationInputs::Locatable
|
6
|
+
|
7
|
+
skip_around_action :set_time_zone
|
8
|
+
skip_before_action :require_user, :require_village
|
9
|
+
|
10
|
+
def states
|
11
|
+
respond_to do |format|
|
12
|
+
format.json {render json: { states: CS.states(country_key).values }}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def cities
|
17
|
+
respond_to do |format|
|
18
|
+
format.json {render json: { cities: CS.cities(state_key, country_key)}}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def country_key
|
23
|
+
CS.countries.key(params[:country])
|
24
|
+
end
|
25
|
+
|
26
|
+
def state_key
|
27
|
+
return_key(CS.states(country_key), params[:state])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Handy location inputs</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "handy_location_inputs/application", media: "all" %>
|
9
|
+
<%= javascript_include_tag "handy_location_inputs/application" %>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
|
13
|
+
<%= yield %>
|
14
|
+
|
15
|
+
</body>
|
16
|
+
</html>
|
data/config/routes.rb
ADDED
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: handy_location_inputs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- handofthecode
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-07-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.3
|
27
|
+
description: HandyLocationInputs is an easy way to get uniform location input from
|
28
|
+
a user without the UX compromise of select boxes.
|
29
|
+
email:
|
30
|
+
- deartovi@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- MIT-LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- app/assets/config/handy_location_inputs_manifest.js
|
39
|
+
- app/assets/javascripts/handy_location_inputs/input_controllers.js
|
40
|
+
- app/assets/stylesheets/handy_location_inputs/application.css
|
41
|
+
- app/controllers/concerns/handy_location_inputs/locatable.rb
|
42
|
+
- app/controllers/handy_location_inputs/locations_controller.rb
|
43
|
+
- app/helpers/handy_location_inputs/application_helper.rb
|
44
|
+
- app/jobs/handy_location_inputs/application_job.rb
|
45
|
+
- app/mailers/handy_location_inputs/application_mailer.rb
|
46
|
+
- app/models/handy_location_inputs/application_record.rb
|
47
|
+
- app/views/layouts/handy_location_inputs/application.html.erb
|
48
|
+
- config/routes.rb
|
49
|
+
- lib/handy_location_inputs.rb
|
50
|
+
- lib/handy_location_inputs/engine.rb
|
51
|
+
- lib/handy_location_inputs/version.rb
|
52
|
+
- lib/tasks/handy_location_inputs_tasks.rake
|
53
|
+
homepage: https://github.com/handofthecode/handy_location_inputs
|
54
|
+
licenses:
|
55
|
+
- MIT
|
56
|
+
metadata:
|
57
|
+
allowed_push_host: https://rubygems.org
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.0.1
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: HandyLocationInputs auto suggests locations as the user types. It prevents
|
77
|
+
them from continuing until a valid location is selected. Subordinate input possibilities
|
78
|
+
are requested from the server via ajax as soon as approved locations are entered.
|
79
|
+
Cases are fixed.
|
80
|
+
test_files: []
|