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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a72a26ab78d0c7a3b36da3079d9f646729e9068
4
- data.tar.gz: 7233ff6e62eaebea24cca44ff851cac14d96e0c2
3
+ metadata.gz: 5897d5b4ced03a593f32fb730321c58153d0f018
4
+ data.tar.gz: 38b9ecbf5fbe8bd6fa9c4533b6affdbe34a83516
5
5
  SHA512:
6
- metadata.gz: bac9cf71c1409cb82c8816cafe5c1c276651632a9ed8f72be09ce81c21685e088e6305ffad95832cf3f8a7040e663cac76852f08eb048b21971c732b0521ce1b
7
- data.tar.gz: b2df86801a2f190eafd7bf81920fadb2df1888d623d02105aab85335711bcae89eaa61349972ccb68c866b0223671d92992fcc1465a4e28f9ac8c28d661330b2
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
@@ -0,0 +1,9 @@
1
+ require 'json'
2
+
3
+ class Enumerator
4
+ def to_json
5
+ '[' << each.with_object('') do |e, s|
6
+ s << "#{s.empty? ? '' : ','}" << e.to_json
7
+ end << ']'
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ require 'zipcode-rack'
2
+
3
+ class ZipCode::Engine < ::Rails::Engine
4
+ end
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.0.0
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-05 00:00:00.000000000 Z
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