picky-client 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/bin/picky-client +14 -0
  2. data/lib/picky-client/generator.rb +180 -0
  3. data/sinatra_prototype/Gemfile +13 -0
  4. data/sinatra_prototype/app.rb +59 -0
  5. data/sinatra_prototype/book.rb +42 -0
  6. data/sinatra_prototype/config.ru +2 -0
  7. data/sinatra_prototype/images/picky.png +0 -0
  8. data/sinatra_prototype/javascripts/compiler.jar +0 -0
  9. data/sinatra_prototype/javascripts/generate_bundles +25 -0
  10. data/sinatra_prototype/javascripts/jquery-1.3.2.js +4376 -0
  11. data/sinatra_prototype/javascripts/jquery-1.4.3.min.js +166 -0
  12. data/sinatra_prototype/javascripts/jquery.scrollTo-1.4.2.js +215 -0
  13. data/sinatra_prototype/javascripts/jquery.timer.js +75 -0
  14. data/sinatra_prototype/javascripts/picky.addination.js +36 -0
  15. data/sinatra_prototype/javascripts/picky.allocation_renderer.js +291 -0
  16. data/sinatra_prototype/javascripts/picky.allocations_cloud.js +91 -0
  17. data/sinatra_prototype/javascripts/picky.backend.js +86 -0
  18. data/sinatra_prototype/javascripts/picky.client.js +62 -0
  19. data/sinatra_prototype/javascripts/picky.controller.js +107 -0
  20. data/sinatra_prototype/javascripts/picky.data.js +78 -0
  21. data/sinatra_prototype/javascripts/picky.extensions.js +15 -0
  22. data/sinatra_prototype/javascripts/picky.min.js +17 -0
  23. data/sinatra_prototype/javascripts/picky.results_renderer.js +103 -0
  24. data/sinatra_prototype/javascripts/picky.source.js.tar +0 -0
  25. data/sinatra_prototype/javascripts/picky.translations.js +50 -0
  26. data/sinatra_prototype/javascripts/picky.view.js +214 -0
  27. data/sinatra_prototype/library.csv +540 -0
  28. data/sinatra_prototype/stylesheets/stylesheet.css +175 -0
  29. data/sinatra_prototype/stylesheets/stylesheet.sass +216 -0
  30. data/sinatra_prototype/views/search.haml +107 -0
  31. data/spec/picky-client/generator_spec.rb +141 -0
  32. metadata +38 -6
@@ -0,0 +1,36 @@
1
+ var PickyAddination = function(view, results) {
2
+
3
+ // Calculate the addination range.
4
+ //
5
+ var calculateRange = function(data, correction) {
6
+ var correction = correction || 0;
7
+ var numberOfResults = 20; // Make parametrizable.
8
+ var offset = data.offset + numberOfResults + correction;
9
+ var end = offset + numberOfResults;
10
+ var total = data.total;
11
+ if (total < end) { end = total; }
12
+ return { offset:offset, start:(offset+1), end:end };
13
+ };
14
+
15
+ // Remove the addination.
16
+ //
17
+ var remove = function() {
18
+ results.find('.addination').remove();
19
+ };
20
+ this.remove = remove;
21
+
22
+ // Renders the addination;
23
+ //
24
+ var render = function(data) {
25
+ var total = data.total;
26
+ var range = calculateRange(data);
27
+ if (range.offset < total) {
28
+ var result = $("<div class='addination current'>" + t('results.addination.more') + "<div class='tothetop'><a href='javascript:$.scrollTo(0,{ duration: 500});'>&uarr;</a></div></div>");
29
+ result.bind('click', { offset: range.offset }, view.addinationClicked);
30
+ return result;
31
+ } else {
32
+ return '';
33
+ }
34
+ };
35
+ this.render = render;
36
+ };
@@ -0,0 +1,291 @@
1
+ function AllocationRenderer(allocationChosenCallback) {
2
+ var self = this;
3
+
4
+ var locale = PickyI18n.locale;
5
+
6
+ var qualifiers = Localization.qualifiers && Localization.qualifiers[locale] || {};
7
+ var explanations = Localization.explanations && Localization.explanations[locale] || {};
8
+ var location_delimiter = Localization.location_delimiters[locale];
9
+ var explanation_delimiter = Localization.explanation_delimiters[locale];
10
+
11
+ // Those are of interest to the public.
12
+ //
13
+ this.text = '';
14
+ this.query = '';
15
+ this.explanation = '';
16
+
17
+ // TODO parametrize.
18
+ //
19
+ var no_ellipses = ['street_number', 'zipcode'];
20
+
21
+ // Contracts the originals of the zipped.
22
+ //
23
+ function contract(zipped) {
24
+ var hash = {}; // remembers the values
25
+ var insert = {}; // remembers the insertion locations
26
+ var remove = []; // remembers the remove indexes
27
+ var i;
28
+ for (i = 0, l = zipped.length; i < l; i++) {
29
+ var key = zipped[i][0];
30
+ if (key in hash) {
31
+ hash[key] = hash[key] + ' ' + zipped[i][1];
32
+ remove.push(i);
33
+ } else {
34
+ hash[key] = zipped[i][1];
35
+ insert[i] = key;
36
+ }
37
+ }
38
+ // Insert the ones from the hash.
39
+ for (i in insert) {
40
+ zipped[i][1] = hash[insert[i]];
41
+ }
42
+ // Remove the ones from zipped we don't like. From the end.
43
+ for (i = remove.length-1; i >= 0; i--) {
44
+ zipped.remove(remove[i]);
45
+ }
46
+ return zipped;
47
+ };
48
+ this.contract = contract;
49
+
50
+ // TODO Parametrize!
51
+ var specialWhoCases = {
52
+ // Use the actual methods, not strings.
53
+ "maiden_name" : { format:"(-%1$s)", method:'capitalize', ignoreSingle:true },
54
+ "name" : { format:"<strong>%1$s</strong>", method:'toUpperCase', ignoreSingle:true },
55
+ "first_name" : { format:"%1$s", method:"capitalize" }
56
+ };
57
+ function handleWho(both, singleParam) {
58
+ var single = singleParam || false;
59
+ var allocation = both[0];
60
+ var word = both[1];
61
+
62
+ var formatting = specialWhoCases[allocation];
63
+ if (formatting) {
64
+ if (formatting.method) { word = word[formatting.method](); }
65
+ if (formatting.format) { word = formatting.format.replace(/%1\$s/, word); }
66
+ }
67
+ var explanation = explanations[allocation] || allocation;
68
+ if (single && !(formatting && formatting.ignoreSingle)) { return word + '&nbsp;(' + explanation + ')'; }
69
+
70
+ return word;
71
+ }
72
+ // Handles the first (who) part.
73
+ //
74
+ // Rules:
75
+ // * If there is no thing, do return an empty string.
76
+ // * If there is only one thing, add an explanation.
77
+ // * If there are multiple things, handle special cases.
78
+ // Without special cases, the name is always in front.
79
+ // The things are separated by commas, and explained.
80
+ // If there are multiple instances of the same category, they are contracted.
81
+ //
82
+ // Note: &nbsp; to not disconnect the explanation from the query text.
83
+ //
84
+ function who(zipped) {
85
+ if (zipped.length == 0) {
86
+ return '';
87
+ } else if (zipped.length == 1) {
88
+ return handleWho(zipped[0], true);
89
+ } else {
90
+ // else, sort, special cases etc.
91
+ var result = [];
92
+ var append = [];
93
+ zipped = contract(zipped);
94
+ for (var i = 0, l = zipped.length; i < l; i++) {
95
+ if (zipped[i][0] == 'first_name') {
96
+ result.unshift(handleWho(zipped[i])); // prepend the first name
97
+ } else {
98
+ if (zipped[i][0] == 'maiden_name') {
99
+ append.push(handleWho(zipped[i]));
100
+ } else {
101
+ result.push(handleWho(zipped[i]));
102
+ }
103
+ }
104
+ };
105
+ if (append.length > 0) { result.push(append); };
106
+ return result.join(' ');
107
+ }
108
+ }
109
+ this.who = who;
110
+
111
+ function replacerFor(zipped) {
112
+ return function(_, category) {
113
+ for (var i = 0, l = zipped.length; i < l; i++) {
114
+ if (zipped[i][0] == category) { return zipped[i][1]; };
115
+ };
116
+ return '';
117
+ };
118
+ };
119
+
120
+ // Handles the second (where) part.
121
+ //
122
+ // Rules:
123
+ // * If there is no location, do return an empty string.
124
+ // * If there is only a zipcode, add a [<city explanation>].
125
+ // * If there is only a city, add nothing.
126
+ // * If there are both, zipcode needs to be first.
127
+ // TODO Contraction of multiple "cities" and/or zipcode.
128
+ //
129
+ var locations = {
130
+ 'zipcode':(':zipcode [' + explanations.city + ']'),
131
+ 'city':':city',
132
+ 'city,zipcode':':zipcode :city'
133
+ };
134
+ function where(zipped) {
135
+ if (zipped.length == 0) { return ''; };
136
+ zipped = contract(zipped);
137
+ var key_ary = zipped;
138
+ key_ary.sort(function(zipped1, zipped2) {
139
+ return zipped1[0] < zipped2[0] ? -1 : 1;
140
+ });
141
+ // Now that it's sorted, get the right string.
142
+ var key = [];
143
+ for (var i = 0, l = key_ary.length; i < l; i++) {
144
+ key.push(key_ary[i][0]);
145
+ };
146
+ var loc = locations[key];
147
+ // Replace inside string.
148
+ var result = loc.replace(/:(zipcode|city)/g, replacerFor(zipped));
149
+ return result;
150
+ };
151
+ this.where = where;
152
+
153
+ function handleSingleWhat(both) {
154
+ var allocation = both[0];
155
+ var word = both[1];
156
+
157
+ var explanation = explanations[allocation] || allocation;
158
+
159
+ return word + '&nbsp;(' + explanation + ')';
160
+ }
161
+ function what(zipped) {
162
+ if (zipped.length == 0) { return ''; };
163
+
164
+ result = [];
165
+ zipped = contract(zipped);
166
+ for (var i = 0, l = zipped.length; i < l; i++) {
167
+ result.push(handleSingleWhat(zipped[i]));
168
+ }
169
+
170
+ return result.join(', ');
171
+ };
172
+ this.what = what;
173
+
174
+ // Orders the allocation identifiers according to
175
+ // [<who>, <what>, <where>]
176
+ // Returns a reordered array.
177
+ //
178
+ // TODO Rename "group".
179
+ //
180
+ var who_qualifiers = ['first_name', 'name', 'maiden_name'];
181
+ var where_qualifiers = ['zipcode', 'city'];
182
+ function trisect(zipped) {
183
+ var who_parts = [];
184
+ var what_parts = [];
185
+ var where_parts = [];
186
+
187
+ for (var i = 0, l = zipped.length; i < l; i++) {
188
+ var combination = zipped[i];
189
+ if (where_qualifiers.include(combination[0])) {
190
+ where_parts.push(combination);
191
+ } else if (who_qualifiers.include(combination[0])) {
192
+ who_parts.push(combination);
193
+ } else {
194
+ what_parts.push(combination);
195
+ }
196
+ }
197
+
198
+ // Ellipsisize the last part
199
+ var alloc_part;
200
+ if (where_parts.length > 0) {
201
+ alloc_part = where_parts[where_parts.length-1];
202
+ } else if (what_parts.length > 0) {
203
+ alloc_part = what_parts[what_parts.length-1];
204
+ } else if (who_parts.length > 0) {
205
+ alloc_part = who_parts[who_parts.length-1];
206
+ } // always results in a part
207
+ if (!no_ellipses.include(alloc_part[0])) { alloc_part[1] += '...'; } // TODO *
208
+
209
+ var rendered_who = who(who_parts);
210
+ var rendered_what = what(what_parts);
211
+ var rendered_where = where(where_parts);
212
+ return [rendered_who, rendered_what, rendered_where];
213
+ };
214
+ this.trisect = trisect;
215
+
216
+ // Fuses a possible who part to a possible what part to a possible where part.
217
+ //
218
+ // e.g. <who>, <what> in <where>
219
+ //
220
+ // Note: &nbsp; to not disconnect the location delimiter (e.g. "in") from the location.
221
+ //
222
+ // TODO Parametrize!
223
+ //
224
+ var who_what_join = ', ';
225
+ var whowhat_where_join = ' ' + location_delimiter + '&nbsp;';
226
+ function fuse(parts) {
227
+ var who = parts[0], what = parts[1], where = parts[2];
228
+ var who_what = '';
229
+ if (who != '') {
230
+ if (what != '') { who_what = [who, what].join(who_what_join); } else { who_what = who; }
231
+ } else {
232
+ who_what = what;
233
+ }
234
+ if (where == '') { return who_what; };
235
+ return [who_what, where].join(whowhat_where_join);
236
+ };
237
+ this.fuse = fuse;
238
+
239
+ // Creates a query string from combination and originals.
240
+ //
241
+ function querify(zipped) {
242
+ var query_parts = [];
243
+ var qualifier;
244
+ for (var i in zipped) {
245
+ qualifier = zipped[i][0];
246
+ qualifier = qualifiers[qualifier] || qualifier; // Use the returned qualifier if none is given.
247
+ query_parts[i] = qualifier + ':' + zipped[i][1];
248
+ };
249
+ return query_parts.join(' ');
250
+ };
251
+ this.querify = querify;
252
+
253
+ //
254
+ //
255
+ function suggestify(zipped) {
256
+ return fuse(trisect(zipped));
257
+ };
258
+
259
+
260
+ // Generates the text and the link.
261
+ //
262
+ var generate = function() {
263
+ this.query = querify(combination);
264
+ this.text = suggestify(combination);
265
+
266
+ return self;
267
+ };
268
+
269
+ //
270
+ //
271
+ var listItem = function(text, count) {
272
+ return $('<li><div class="text">' + text + '</div><div class="count">' + count + '</div></li>');
273
+ };
274
+
275
+ var render = function(allocation) {
276
+ var combination = allocation.combination;
277
+ var type = allocation.type;
278
+ var count = allocation.count;
279
+
280
+ var query = querify(combination);
281
+
282
+ var item = listItem(suggestify(combination), count);
283
+
284
+ // TODO Move this outwards?
285
+ //
286
+ item.bind('click', { query: query }, allocationChosenCallback);
287
+ return item;
288
+ };
289
+ this.render = render;
290
+
291
+ };
@@ -0,0 +1,91 @@
1
+ var PickyAllocationsCloud = function(view) {
2
+
3
+ var allocations = $('#picky .allocations');
4
+ var shownAllocations = allocations.find('.shown');
5
+ var showMoreAllocations = allocations.find('.more');
6
+ var hiddenAllocations = allocations.find('.hidden');
7
+
8
+ // Show the cloud.
9
+ //
10
+ var show = function(data) {
11
+ render(data.allocations);
12
+ allocations.show();
13
+ };
14
+ // Hide the cloud.
15
+ //
16
+ var hide = function() {
17
+ allocations.hide();
18
+ };
19
+
20
+ var clearAllocationCloud = function() {
21
+ shownAllocations.empty();
22
+ showMoreAllocations.hide();
23
+ hiddenAllocations.empty().hide();
24
+ };
25
+
26
+ //
27
+ //
28
+ var allocationChosen = function(event) {
29
+ hide();
30
+ view.allocationChosen(event);
31
+ };
32
+
33
+ var allocationRenderer = new AllocationRenderer(allocationChosen);
34
+
35
+ var createAllocationList = function(allocations) {
36
+ var shown = [];
37
+ allocations.each(function(i, allocation) {
38
+ shown.push(allocationRenderer.render(allocation));
39
+
40
+ // // TODO Combine.
41
+ // allocationRenderer.generate();
42
+ // var listItem = renderListItem(allocationRenderer);
43
+
44
+ // shown.push(listItem);
45
+ });
46
+ return shown;
47
+ };
48
+
49
+ var renderList = function(list) {
50
+ if (list.length == 0) {
51
+ return $('#search .allocations').hide();
52
+ }
53
+ var maxSuggestions = 3;
54
+ clearAllocationCloud();
55
+
56
+ if (list.length > maxSuggestions) {
57
+ $.each(list.slice(0,maxSuggestions-1), function(i, item) {
58
+ shownAllocations.append(item);
59
+ });
60
+ $.each(list.slice(maxSuggestions-1), function(i, item) {
61
+ hiddenAllocations.append(item);
62
+ });
63
+ showMoreAllocations.show();
64
+ }
65
+ else {
66
+ $.each(list, function(i, item) {
67
+ shownAllocations.append(item);
68
+ });
69
+ }
70
+ return $('#search .allocations').show();
71
+ };
72
+
73
+ // Render the allocation list.
74
+ //
75
+ var render = function(allocations) {
76
+ renderList(createAllocationList(allocations));
77
+ };
78
+
79
+ // Install handlers.
80
+ //
81
+ showMoreAllocations.click(function() {
82
+ showMoreAllocations.hide();
83
+ hiddenAllocations.show();
84
+ });
85
+
86
+ // Expose hide and show.
87
+ //
88
+ this.hide = hide;
89
+ this.show = show;
90
+
91
+ };
@@ -0,0 +1,86 @@
1
+ // Core search backend.
2
+ //
3
+ var PickyBackend = function(url) {
4
+
5
+ // Get returns the data without handling timestamps and whatnot.
6
+ //
7
+ var get = function(query, controllerCallback, offset, specificParams) {
8
+ var params = specificParams || {};
9
+ params = $.extend({ query: query, offset: offset }, specificParams);
10
+
11
+ // Wrap any data returned in a PickyData object.
12
+ //
13
+ var callback = function(data_hash) {
14
+ if (controllerCallback) { controllerCallback(new PickyData(data_hash)); }
15
+ };
16
+
17
+ $.ajax({ type: 'GET', url: url, data: params, success: callback, dataType: 'json'});
18
+ };
19
+
20
+ var search = function(query, controllerCallback, offset, specificParams, specificTimestamps) {
21
+ // Wrap the given callback.
22
+ //
23
+ var callback = function(data) {
24
+ if (controllerCallback) { controllerCallback(specificTimestamps, data); }
25
+ };
26
+
27
+ get(query, callback, offset, specificParams);
28
+ };
29
+ this.search = search;
30
+ };
31
+
32
+ // Live search backend.
33
+ //
34
+ var LiveBackend = function(url, callback) {
35
+ var backend = new PickyBackend(url);
36
+
37
+ var search = function(query, controllerCallback, offset, specificParams, fullTimestamps) {
38
+ var specificTimestamps = fullTimestamps || {};
39
+
40
+ latestRequestTimestamp = new Date();
41
+ specificTimestamps.live = latestRequestTimestamp;
42
+
43
+ // Wrap the given callback.
44
+ //
45
+ // Note: Binds the latest request timestamp for later comparison.
46
+ //
47
+ var callback = function(timestamps, data) {
48
+ if (!timestamps.live || timestamps.live == latestRequestTimestamp) {
49
+ if (controllerCallback) { controllerCallback(data); }
50
+ };
51
+ };
52
+
53
+ // Pass in the timestamp for later comparison.
54
+ //
55
+ backend.search(query, callback, offset, specificParams, specificTimestamps);
56
+ };
57
+ this.search = search;
58
+ };
59
+
60
+ // Full search backend.
61
+ //
62
+ var FullBackend = function(url) {
63
+ var backend = new PickyBackend(url);
64
+
65
+ var search = function(query, controllerCallback, offset, specificParams, givenTimestamps) {
66
+ var specificTimestamps = givenTimestamps || {};
67
+
68
+ latestRequestTimestamp = new Date();
69
+ specificTimestamps.full = latestRequestTimestamp;
70
+
71
+ // Wrap the given callback.
72
+ //
73
+ // Note: Binds the latest request timestamp for later comparison.
74
+ //
75
+ var callback = function(timestamps, data) {
76
+ if (!timestamps.full || timestamps.full == latestRequestTimestamp) {
77
+ if (controllerCallback) { controllerCallback(data); }
78
+ };
79
+ };
80
+
81
+ // Pass in the timestamp for later comparison.
82
+ //
83
+ backend.search(query, callback, offset, specificParams, specificTimestamps);
84
+ };
85
+ this.search = search;
86
+ };