zipcode-rack 1.0.0 → 1.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 +4 -4
- data/app/assets/javascript/zipcode-input.js +357 -0
- data/lib/json_enum.rb +9 -0
- data/lib/zipcode/rails.rb +4 -0
- data/lib/zipcode-rack.rb +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5897d5b4ced03a593f32fb730321c58153d0f018
|
4
|
+
data.tar.gz: 38b9ecbf5fbe8bd6fa9c4533b6affdbe34a83516
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 717bda60af4d256a9d4f1f4511522b491f4e1b6d6a3464e4936ad725acea36edd710d02bd28d103593cd348e3af896b1302025c125b0cc7ca6bcde22dfd7b8f8
|
7
|
+
data.tar.gz: d5b564297165788bd246d6a17bb05d1a9ec85fac5f1188108e95f8b5bc3d37c0e9d122b5c0ba1d9185de0088f5b9bebb8e5d13972b853dc192331cba5a6fa71e
|
@@ -0,0 +1,357 @@
|
|
1
|
+
(function () {
|
2
|
+
function parse(text) {
|
3
|
+
var parsed = {};
|
4
|
+
var re = /^\s*(\d+)?\s*(\D.*)?$/;
|
5
|
+
var m = re.exec(text);
|
6
|
+
if (m !== null) {
|
7
|
+
parsed.zip = m[1];
|
8
|
+
parsed.name = m[2];
|
9
|
+
}
|
10
|
+
return parsed;
|
11
|
+
}
|
12
|
+
|
13
|
+
function queryString(obj) {
|
14
|
+
var kv = [];
|
15
|
+
for (var key in obj) {
|
16
|
+
if (obj.hasOwnProperty(key)) {
|
17
|
+
kv.push([key, obj[key]].join('='));
|
18
|
+
}
|
19
|
+
}
|
20
|
+
return kv.join('&');
|
21
|
+
}
|
22
|
+
|
23
|
+
function request(method, url, params, done) {
|
24
|
+
var http = new XMLHttpRequest();
|
25
|
+
http.open(method, url + '?' + queryString(params), true);
|
26
|
+
http.onreadystatechange = function() {
|
27
|
+
if (http.readyState === 4) {
|
28
|
+
if (http.status === 200) {
|
29
|
+
var data = JSON.parse(http.responseText);
|
30
|
+
done(data);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
};
|
34
|
+
http.send(null);
|
35
|
+
}
|
36
|
+
|
37
|
+
var settings = {
|
38
|
+
endpoint: null,
|
39
|
+
dropdownClass: 'zip-city-dropdown',
|
40
|
+
};
|
41
|
+
|
42
|
+
function endpoint(url) {
|
43
|
+
if (url) { settings.endpoint = url; }
|
44
|
+
return settings.endpoint;
|
45
|
+
}
|
46
|
+
|
47
|
+
function dropdownClass(name) {
|
48
|
+
if (name) { settings.dropdownClass = name; }
|
49
|
+
return settings.dropdownClass;
|
50
|
+
}
|
51
|
+
|
52
|
+
function search(text, limit, done) {
|
53
|
+
var parsed = parse(text);
|
54
|
+
if (parsed.zip) {
|
55
|
+
request('GET', endpoint(), { zip: parsed.zip, limit: limit }, done);
|
56
|
+
} else if (parsed.name) {
|
57
|
+
request('GET', endpoint(), { name: parsed.name, limit: limit }, done);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
function handleSearch(e) {
|
62
|
+
var input = e.target.value;
|
63
|
+
if (input === '' && e.target.dropdown) {
|
64
|
+
e.target.dropdown.remove();
|
65
|
+
e.target.dropdown = null;
|
66
|
+
}
|
67
|
+
|
68
|
+
search(input, 10, function (data) {
|
69
|
+
updatePicker(e.target, data);
|
70
|
+
});
|
71
|
+
}
|
72
|
+
|
73
|
+
function dropdownFor(input) {
|
74
|
+
if (input.dropdown) {
|
75
|
+
return input.dropdown;
|
76
|
+
}
|
77
|
+
|
78
|
+
var dropdown = createDropdown(input);
|
79
|
+
assignDropdown(input, dropdown);
|
80
|
+
return dropdown;
|
81
|
+
}
|
82
|
+
|
83
|
+
function createDropdown(input) {
|
84
|
+
var dropdown = document.createElement('div');
|
85
|
+
|
86
|
+
// TODO: auto-align right if needed
|
87
|
+
// TODO: max-width + hide overflow
|
88
|
+
var style = '';
|
89
|
+
style += 'position: absolute;';
|
90
|
+
style += 'top: ' + (input.offsetTop + input.offsetHeight) + 'px;';
|
91
|
+
style += 'left: ' + input.offsetLeft + 'px;';
|
92
|
+
style += 'min-width: ' + input.offsetWidth + 'px;';
|
93
|
+
dropdown.setAttribute('style', style);
|
94
|
+
dropdown.classList.add(dropdownClass());
|
95
|
+
|
96
|
+
return dropdown;
|
97
|
+
}
|
98
|
+
|
99
|
+
function isHovered(dropdown) {
|
100
|
+
var items = document.querySelectorAll(':hover');
|
101
|
+
for (var i = 0; i < items.length; i++) {
|
102
|
+
if (items[i] === dropdown) {
|
103
|
+
return true;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
return false;
|
107
|
+
}
|
108
|
+
|
109
|
+
function isFocused(input) {
|
110
|
+
var items = document.querySelectorAll('input:focus');
|
111
|
+
for (var i = 0; i < items.length; i++) {
|
112
|
+
if (items[i] === input) {
|
113
|
+
return true;
|
114
|
+
}
|
115
|
+
}
|
116
|
+
return false;
|
117
|
+
}
|
118
|
+
|
119
|
+
function assignDropdown(input, dropdown) {
|
120
|
+
document.querySelector('body').appendChild(dropdown);
|
121
|
+
input.dropdown = dropdown;
|
122
|
+
input.addEventListener('blur', function() {
|
123
|
+
if (!isHovered(input.dropdown)) { removeDropdown(input); }
|
124
|
+
});
|
125
|
+
dropdown.addEventListener('mouseleave', function() {
|
126
|
+
if (!isFocused(input)) { removeDropdown(input); }
|
127
|
+
});
|
128
|
+
}
|
129
|
+
|
130
|
+
function removeDropdown(input) {
|
131
|
+
if (input.dropdown) {
|
132
|
+
var dropdown = input.dropdown;
|
133
|
+
input.dropdown = null;
|
134
|
+
dropdown.remove();
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
function renderResults(data) {
|
139
|
+
var list = '';
|
140
|
+
list += '<ul>';
|
141
|
+
for (var i = 0; i < data.length; i++) {
|
142
|
+
list += '<li>' + data[i].zip + ' ' + data[i].name + '</li>';
|
143
|
+
}
|
144
|
+
list += '</ul>';
|
145
|
+
|
146
|
+
return list;
|
147
|
+
}
|
148
|
+
|
149
|
+
function renderNoResult() {
|
150
|
+
return '<span>No results</span>';
|
151
|
+
}
|
152
|
+
|
153
|
+
function renderResultValue(item) {
|
154
|
+
return '' + item.zip + ' ' + item.name;
|
155
|
+
}
|
156
|
+
|
157
|
+
function setSelection(dropdown, item) {
|
158
|
+
var items = dropdown.querySelectorAll('li');
|
159
|
+
for (var i = 0; i < items.length; i++) {
|
160
|
+
if (items[i] === item) { dropdown.selection = i; }
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
function redrawSelection(dropdown) {
|
165
|
+
var items = dropdown.querySelectorAll('li');
|
166
|
+
for (var i = 0; i < items.length; i++) {
|
167
|
+
items[i].classList.remove('selected');
|
168
|
+
}
|
169
|
+
items[dropdown.selection].classList.add('selected');
|
170
|
+
}
|
171
|
+
|
172
|
+
function selectedValue(dropdown) {
|
173
|
+
var items = dropdown.querySelectorAll('li');
|
174
|
+
return items[dropdown.selection].data;
|
175
|
+
}
|
176
|
+
|
177
|
+
function applySelection(input) {
|
178
|
+
input.value = selectedValue(input.dropdown);
|
179
|
+
removeDropdown(input);
|
180
|
+
}
|
181
|
+
|
182
|
+
function handleResultMouseEnter(item, input) {
|
183
|
+
setSelection(input.dropdown, item);
|
184
|
+
redrawSelection(input.dropdown);
|
185
|
+
}
|
186
|
+
|
187
|
+
function handleResultClick(item, input) {
|
188
|
+
setSelection(input.dropdown, item);
|
189
|
+
redrawSelection(input.dropdown);
|
190
|
+
applySelection(input);
|
191
|
+
}
|
192
|
+
|
193
|
+
function resultMouseEnterHandler(input) {
|
194
|
+
return (function (e) { handleResultMouseEnter(e.target, input); });
|
195
|
+
}
|
196
|
+
|
197
|
+
function resultClickHandler(input) {
|
198
|
+
return (function (e) { handleResultClick(e.target, input); });
|
199
|
+
}
|
200
|
+
|
201
|
+
function setResultValues(dropdown, data) {
|
202
|
+
var items = dropdown.querySelectorAll('li');
|
203
|
+
for (var i = 0; i < items.length; i++) {
|
204
|
+
items[i].data = renderResultValue(data[i]);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
function setResultEvents(input) {
|
209
|
+
var dropdown = input.dropdown;
|
210
|
+
var items = dropdown.querySelectorAll('li');
|
211
|
+
for (var i = 0; i < items.length; i++) {
|
212
|
+
items[i].addEventListener('click', resultClickHandler(input));
|
213
|
+
items[i].addEventListener('mouseenter', resultMouseEnterHandler(input));
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
function initializeSelection(dropdown) {
|
218
|
+
dropdown.selection = -1;
|
219
|
+
}
|
220
|
+
|
221
|
+
function updatePicker(input, data) {
|
222
|
+
var dropdown = dropdownFor(input);
|
223
|
+
initializeSelection(dropdown);
|
224
|
+
if (data.length > 0) {
|
225
|
+
dropdown.innerHTML = renderResults(data);
|
226
|
+
setResultValues(dropdown, data);
|
227
|
+
setResultEvents(input);
|
228
|
+
} else {
|
229
|
+
dropdown.innerHTML = renderNoResult();
|
230
|
+
}
|
231
|
+
}
|
232
|
+
|
233
|
+
function hasDropdown(input) {
|
234
|
+
return !!input.dropdown;
|
235
|
+
}
|
236
|
+
|
237
|
+
function wrapSelection(dropdown, length) {
|
238
|
+
if (dropdown.selection == length) {
|
239
|
+
dropdown.selection = 0;
|
240
|
+
}
|
241
|
+
if (dropdown.selection < 0) {
|
242
|
+
dropdown.selection = length - 1;
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
function moveSelection(dropdown, direction) {
|
247
|
+
var items = dropdown.querySelectorAll('li');
|
248
|
+
if (items.length > 0) {
|
249
|
+
dropdown.selection += direction;
|
250
|
+
wrapSelection(dropdown, items.length);
|
251
|
+
redrawSelection(dropdown);
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
var UP = -1;
|
256
|
+
var DOWN = 1;
|
257
|
+
|
258
|
+
function handleDown(e) {
|
259
|
+
e.preventDefault();
|
260
|
+
if (hasDropdown(e.target)) {
|
261
|
+
moveSelection(e.target.dropdown, DOWN);
|
262
|
+
} else {
|
263
|
+
handleSearch(e);
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
function handleUp(e) {
|
268
|
+
e.preventDefault();
|
269
|
+
if (hasDropdown(e.target)) {
|
270
|
+
moveSelection(e.target.dropdown, UP);
|
271
|
+
} else {
|
272
|
+
handleSearch(e);
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
function handleEnter(e) {
|
277
|
+
e.preventDefault();
|
278
|
+
applySelection(e.target);
|
279
|
+
removeDropdown(e.target);
|
280
|
+
}
|
281
|
+
|
282
|
+
function handleEscape(e) {
|
283
|
+
e.preventDefault();
|
284
|
+
removeDropdown(e.target);
|
285
|
+
}
|
286
|
+
|
287
|
+
function handleKeydown(e) {
|
288
|
+
switch (e.keyCode) {
|
289
|
+
case 40: // ArrowDown
|
290
|
+
handleDown(e);
|
291
|
+
break;
|
292
|
+
case 38: // ArrowUp
|
293
|
+
handleUp(e);
|
294
|
+
break;
|
295
|
+
case 13: // Enter
|
296
|
+
handleEnter(e);
|
297
|
+
break;
|
298
|
+
case 27: // Escape
|
299
|
+
handleEscape(e);
|
300
|
+
break;
|
301
|
+
default:
|
302
|
+
return;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
function polyfill(input) {
|
307
|
+
if (!input.dropdown_listener) {
|
308
|
+
input.dropdown_listener = true;
|
309
|
+
input.addEventListener('keydown', handleKeydown);
|
310
|
+
input.addEventListener('input', handleSearch);
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
function polyfillDocument() {
|
315
|
+
var elements = document.querySelectorAll('input[type=zip-city]');
|
316
|
+
for (var i = 0; i < elements.length; i++) {
|
317
|
+
polyfill(elements[i]);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
|
321
|
+
function polyfillInsertedNodes() {
|
322
|
+
var observer = new MutationObserver(function (mutations) {
|
323
|
+
mutations.forEach(function (mutation) {
|
324
|
+
for (var i = 0; i < mutation.addedNodes.length; i++) {
|
325
|
+
var node = mutation.addedNodes[i];
|
326
|
+
if (node.nodeName === 'INPUT' && node.getAttribute('type') === 'zip-city') {
|
327
|
+
polyfill(node);
|
328
|
+
}
|
329
|
+
}
|
330
|
+
});
|
331
|
+
});
|
332
|
+
observer.observe(document.body, { childList: true });
|
333
|
+
}
|
334
|
+
|
335
|
+
var loaded = false;
|
336
|
+
function handleLoaded() {
|
337
|
+
if (loaded) { return; }
|
338
|
+
loaded = true;
|
339
|
+
|
340
|
+
polyfillDocument();
|
341
|
+
polyfillInsertedNodes();
|
342
|
+
|
343
|
+
}
|
344
|
+
|
345
|
+
// export
|
346
|
+
this.ZipCode = {
|
347
|
+
endpoint: endpoint,
|
348
|
+
dropdownClass: dropdownClass,
|
349
|
+
};
|
350
|
+
|
351
|
+
// handle async
|
352
|
+
if (document.readyState == 'complete' || document.readyState == 'interactive') {
|
353
|
+
setTimeout(handleLoaded, 1);
|
354
|
+
} else {
|
355
|
+
document.addEventListener('DOMContentLoaded', handleLoaded);
|
356
|
+
}
|
357
|
+
}).call(this);
|
data/lib/json_enum.rb
ADDED
data/lib/zipcode-rack.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'grape'
|
2
|
+
require 'json_enum'
|
2
3
|
|
3
4
|
module ZipCode
|
4
5
|
class API < Grape::API
|
@@ -29,10 +30,11 @@ module ZipCode
|
|
29
30
|
optional :name, type: String
|
30
31
|
optional :zip, type: String
|
31
32
|
optional :country, type: Symbol, regexp: /^[a-z]{2}$/
|
33
|
+
optional :limit, type: Integer, default: 20
|
32
34
|
exactly_one_of :name, :zip
|
33
35
|
end
|
34
36
|
get '/search' do
|
35
|
-
db.for(country).search(*criteria)
|
37
|
+
db.for(country).search(*criteria).take(params[:limit])
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipcode-rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Loic Nageleisen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: grape
|
@@ -115,7 +115,10 @@ executables: []
|
|
115
115
|
extensions: []
|
116
116
|
extra_rdoc_files: []
|
117
117
|
files:
|
118
|
+
- app/assets/javascript/zipcode-input.js
|
119
|
+
- lib/json_enum.rb
|
118
120
|
- lib/zipcode-rack.rb
|
121
|
+
- lib/zipcode/rails.rb
|
119
122
|
homepage: https://github.com/lloeki/zipcode-rack
|
120
123
|
licenses:
|
121
124
|
- MIT
|