picky-client 0.2.4 → 0.3.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.
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,62 @@
1
+ var Localization = {
2
+ // TODO Remove.
3
+ location_delimiters: { de:'in', fr:'à', it:'a', en:'in', ch:'in' },
4
+
5
+ explanation_delimiters: { de:'und', fr:'et', it:'e', en:'and', ch:'und' }
6
+ };
7
+
8
+ // The client handles parameters and
9
+ // offers an insert method.
10
+ //
11
+ var PickyClient = function(config) {
12
+
13
+ // Params handling.
14
+ //
15
+
16
+ // This is used to generate the correct query strings, localized.
17
+ //
18
+ // e.g with locale it:
19
+ // ['title', 'ulysses', 'Ulysses'] => 'titolo:ulysses'
20
+ //
21
+ // This needs to correspond to the parsing in the search engine.
22
+ //
23
+ Localization.qualifiers = config.qualifiers || {};
24
+
25
+ // This is used to explain the preceding word in the suggestion text.
26
+ //
27
+ // e.g. with locale it:
28
+ // ['title', 'ulysses', 'Ulysses'] => 'Ulysses (titolo)'
29
+ //
30
+ Localization.explanations = config.explanations || {};
31
+
32
+ // TODO Explain.
33
+ //
34
+ Localization.explanation_delimiters = { de:'und', fr:'et', it:'e', en:'and', ch:'und' };
35
+
36
+ // Either you pass it a backends hash with full and live,
37
+ // or you pass it full and live (urls), which will then
38
+ // be wrapped in appropriate backends.
39
+ //
40
+ var backends = config.backends;
41
+ if (backends) {
42
+ backends.live || alert('Both a full and live backend must be provided.');
43
+ backends.full || alert('Both a full and live backend must be provided.');
44
+ } else {
45
+ config.backends = {
46
+ live: config.live && new LiveBackend(config.live) || alert('A live backend path must be provided.'),
47
+ full: config.full && new FullBackend(config.full) || alert('A live backend path must be provided.')
48
+ };
49
+ }
50
+
51
+ // The central Picky controller.
52
+ //
53
+ var controller = config.controller && new config.controller(config) || new PickyController(config);
54
+
55
+ // Insert a query into the client and run it.
56
+ // Default is a full query.
57
+ //
58
+ this.insert = function(query, full) {
59
+ controller.insert(query, full || true);
60
+ };
61
+
62
+ };
@@ -0,0 +1,107 @@
1
+ var PickyController = function(config) {
2
+
3
+ var view = new PickyView(this, config);
4
+
5
+ var backends = config.backends;
6
+ var beforeCallback = config.before || function(params, query, offset) { };
7
+ var successCallback = config.success || function(data, query) { };
8
+ var afterCallback = config.after || function(data, query) { };
9
+
10
+ // If the given backend cannot be found, ignore the search request.
11
+ //
12
+ var search = function(type, query, callback, offset, specificParams) {
13
+ var currentBackend = backends[type];
14
+ if (currentBackend) { currentBackend.search(query, callback, offset, specificParams); };
15
+ };
16
+
17
+ var fullSearchCallback = function(data, query) {
18
+ data = successCallback(data, query) || data;
19
+
20
+ view.fullResultsCallback(data);
21
+
22
+ afterCallback(data, query);
23
+ };
24
+ var fullSearch = function(query, possibleOffset, possibleParams) {
25
+ var params = possibleParams || {};
26
+ var offset = possibleOffset || 0;
27
+ liveSearchTimer.stop();
28
+
29
+ params = beforeCallback(params, query, offset) || params;
30
+
31
+ search('full', query, fullSearchCallback, offset, params);
32
+ };
33
+ var liveSearchCallback = function(data, query) {
34
+ data = successCallback(data, query) || data;
35
+
36
+ view.liveResultsCallback(data);
37
+
38
+ afterCallback(data, query);
39
+ };
40
+ var liveSearch = function(query, possibleParams) {
41
+ var params = possibleParams || {};
42
+
43
+ params = beforeCallback(params) || params;
44
+
45
+ search('live', query, liveSearchCallback, 0);
46
+ };
47
+
48
+ // The timer is initially instantly stopped.
49
+ //
50
+ var liveSearchTimer = $.timer(180, function(timer) {
51
+ liveSearch(view.text());
52
+ timer.stop();
53
+ });
54
+ liveSearchTimer.stop();
55
+
56
+ this.insert = function(query, full) {
57
+ view.insert(query);
58
+
59
+ if (full) { fullSearch(query); } // TODO
60
+ };
61
+
62
+ var clearButtonClicked = function() {
63
+ liveSearchTimer.stop();
64
+ };
65
+ this.clearButtonClicked = clearButtonClicked;
66
+
67
+ var searchTextCleared = function() {
68
+ liveSearchTimer.stop();
69
+ };
70
+ this.searchTextCleared = searchTextCleared;
71
+
72
+ var shouldTriggerSearch = function(event) {
73
+ var validTriggerKeys = [
74
+ 0, // special char (ä ö ü etc...)
75
+ 8, // backspace
76
+ 13, // enter
77
+ 32, // space
78
+ 46, // delete
79
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // numbers
80
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 // a-z
81
+ ];
82
+
83
+ return $.inArray(event.keyCode, validTriggerKeys) > -1;
84
+ };
85
+ var searchTextEntered = function(text, event) {
86
+ if (shouldTriggerSearch(event)) {
87
+ if (event.keyCode == 13) { fullSearch(text); } else { liveSearchTimer.reset(); }
88
+ }
89
+ };
90
+ this.searchTextEntered = searchTextEntered;
91
+
92
+ var searchButtonClicked = function(text) {
93
+ fullSearch(text);
94
+ };
95
+ this.searchButtonClicked = searchButtonClicked;
96
+
97
+ var allocationChosen = function(text) {
98
+ fullSearch(text);
99
+ };
100
+ this.allocationChosen = allocationChosen;
101
+
102
+ // Move to a view object.
103
+ var addinationClicked = function(text, event) {
104
+ fullSearch(text, event.data.offset);
105
+ };
106
+ this.addinationClicked = addinationClicked;
107
+ };
@@ -0,0 +1,78 @@
1
+ // The data is basically the model behind the search.
2
+ //
3
+
4
+ // Container for an allocation.
5
+ //
6
+ function Allocation(type, weight, count, combination, ids, rendered) {
7
+ var self = this;
8
+
9
+ this.type = type; // 'books'
10
+ this.weight = weight; // 5.14
11
+ this.count = count; // 14
12
+ this.combination = combination; // [['title', 'Old', 'old'], ['title', 'Man', 'man']]
13
+ this.ids = ids || [];
14
+ this.rendered = rendered || [];
15
+ this.entries = this.rendered;
16
+
17
+ this.isType = function(name) {
18
+ return name == self.type;
19
+ };
20
+ };
21
+
22
+ // Container for the allocations.
23
+ //
24
+ // allocs (should) come preordered by weight.
25
+ //
26
+ function Allocations(allocations) {
27
+ var self = this;
28
+
29
+ this.allocations = [];
30
+
31
+ // Wrap and save the allocations.
32
+ //
33
+ for (var i = 0, l = allocations.length; i < l; i++) {
34
+ var alloc = allocations[i];
35
+ var new_allocation = new Allocation(alloc[0], alloc[1], alloc[2], alloc[3], alloc[4], alloc[5]);
36
+ this.allocations.push(new_allocation);
37
+ }
38
+ this.length = this.allocations.length;
39
+
40
+ this.each = function(callback) {
41
+ return $.each(this.allocations, callback);
42
+ };
43
+ };
44
+
45
+ // Container for the types.
46
+ //
47
+ // data:
48
+ // offset: X
49
+ // duration: X
50
+ // total: X
51
+ // allocations:
52
+ // Allocation[] of [weight, count, combination, Entry[] of [id, content]]
53
+ //
54
+ function PickyData(data) {
55
+ var self = this;
56
+
57
+ // Attributes.
58
+ //
59
+ var total = data.total;
60
+ var duration = data.duration;
61
+ var offset = data.offset;
62
+ var allocations = new Allocations(data.allocations || []);
63
+
64
+ // Expose some attributes.
65
+ //
66
+ this.total = total;
67
+ this.duration = duration;
68
+ this.offset = offset;
69
+ this.allocations = allocations;
70
+
71
+ // Are there any results?
72
+ //
73
+ var isEmpty = function() {
74
+ return total == 0;
75
+ };
76
+ this.isEmpty = isEmpty;
77
+
78
+ };
@@ -0,0 +1,15 @@
1
+ Array.prototype.index = function(val) {
2
+ for(var i = 0, l = this.length; i < l; i++) {
3
+ if(this[i] == val) return i;
4
+ }
5
+ return null;
6
+ };
7
+
8
+ Array.prototype.include = function(val) {
9
+ return this.index(val) !== null;
10
+ };
11
+
12
+ Array.prototype.remove = function(index) {
13
+ this.splice(index, 1);
14
+ return this;
15
+ };
@@ -0,0 +1,17 @@
1
+ Array.prototype.index=function(b){for(var d=0,f=this.length;d<f;d++)if(this[d]==b)return d;return null};Array.prototype.include=function(b){return this.index(b)!==null};Array.prototype.remove=function(b){this.splice(b,1);return this};var PickyI18n={};$(function(){PickyI18n.locale=$("html").attr("lang")||"en"});
2
+ var dictionary={common:{join:{de:"und",fr:"et",it:"e",en:"and",ch:"und"},"with":{de:"mit",fr:"avec",it:"con",en:"with",ch:"mit"},of:{de:"von",fr:"de",it:"di",en:"of",ch:"vo"},to:{de:"bis",fr:"\u00e0",it:"alla",en:"to",ch:"bis"}},results:{addination:{more:{de:"Weitere Resultate",fr:"Autres r\u00e9sultats",it:"Altri risultati",en:"More results",ch:"Mee Resultaat"}},header:{de:"Ergebnisse",fr:"R\u00e9sultats",it:"Risultati",en:"Results",ch:"Erg\u00e4bnis"}}},t=function(b){for(var d=PickyI18n.locale||
3
+ "en",f=b.split(".").concat(d),c=dictionary,j=0,r=f.length;j<r;j++){c=c[f[j]];if(c==undefined){c="Translation missing: "+b+"."+d;break}}return c};function Allocation(b,d,f,c,j,r){var e=this;this.type=b;this.weight=d;this.count=f;this.combination=c;this.ids=j||[];this.entries=this.rendered=r||[];this.isType=function(k){return k==e.type}}function Allocations(b){this.allocations=[];for(var d=0,f=b.length;d<f;d++){var c=b[d];this.allocations.push(new Allocation(c[0],c[1],c[2],c[3],c[4],c[5]))}this.length=this.allocations.length;this.each=function(j){return $.each(this.allocations,j)}}
4
+ function PickyData(b){var d=b.total,f=b.duration,c=b.offset;b=new Allocations(b.allocations||[]);this.total=d;this.duration=f;this.offset=c;this.allocations=b;this.isEmpty=function(){return d==0}};var PickyView=function(b,d){var f=d.showResultsLimit||10,c=$("#picky input.query"),j=$("#picky div.reset"),r=$("#picky input.search_button"),e=$("#picky div.status"),k=$("#picky .dashboard"),s=$("#picky .results"),o=$("#picky .no_results"),g=new PickyAddination(this,s),n=new PickyAllocationsCloud(this),u=new PickyResultsRenderer(g),w=function(){j.fadeTo(166,1)},z=function(){n.hide();s.empty();o.hide()},D=function(m){c.val(m);j.fadeTo(166,0);B("empty");e.empty();z()},x=function(){return c.val()};this.text=
5
+ x;var A=function(m){e.text(m);m>0&&m<=5&&e.fadeTo("fast",0.5).fadeTo("fast",1)},E=function(m){if(m.isEmpty())return"none";if(m.total>f&&m.allocations.length>1)return"support";return"ok"},B=function(m){k.attr("class","dashboard "+m)};this.insert=function(m){c.val(m);c.select()};this.fullResultsCallback=function(m){B(E(m));if(m.isEmpty()){z();A(0);o.show();w()}else if(m.total>f&&m.allocations.length>1){z();w();n.show(m);A(m.total)}else if(m.offset==0){z();A(m.total);u.render(m);s.show();w()}else{g.remove();
6
+ u.render(m);$.scrollTo("#picky .results div.info:last",{duration:500,offset:-12})}c.focus()};this.liveResultsCallback=function(m){B(E(m));A(m.total)};this.allocationChosen=function(m){m=m.data.query;c.val(m);b.allocationChosen(m)};this.addinationClicked=function(m){b.addinationClicked(x(),m)};(function(){c.keyup(function(m){if(x()==""){D();b.searchTextCleared()}else{b.searchTextEntered(x(),m);w()}});r.click(function(){x()==""||b.searchButtonClicked(x())});j.click(function(){D("");b.clearButtonClicked();
7
+ c.focus()})})();c.focus()};var PickyBackend=function(b){var d=function(f,c,j,r){var e=r||{};e=$.extend({query:f,offset:j},r);$.ajax({type:"GET",url:b,data:e,success:function(k){c&&c(new PickyData(k))},dataType:"json"})};this.search=function(f,c,j,r,e){d(f,function(k){c&&c(e,k)},j,r)}},LiveBackend=function(b){var d=new PickyBackend(b);this.search=function(f,c,j,r,e){e=e||{};latestRequestTimestamp=new Date;e.live=latestRequestTimestamp;d.search(f,function(k,s){if(!k.live||k.live==latestRequestTimestamp)c&&c(s)},j,r,e)}},FullBackend=
8
+ function(b){var d=new PickyBackend(b);this.search=function(f,c,j,r,e){e=e||{};latestRequestTimestamp=new Date;e.full=latestRequestTimestamp;d.search(f,function(k,s){if(!k.full||k.full==latestRequestTimestamp)c&&c(s)},j,r,e)}};var PickyController=function(b){var d=new PickyView(this,b),f=b.backends,c=b.before||function(){},j=b.success||function(){},r=b.after||function(){},e=function(g,n){g=j(g,n)||g;d.fullResultsCallback(g);r(g,n)},k=function(g,n,u){u=u||{};n=n||0;o.stop();u=c(u,g,n)||u;var w=f.full;w&&w.search(g,e,n,u)},s=function(g,n){g=j(g,n)||g;d.liveResultsCallback(g);r(g,n)},o=$.timer(180,function(g){var n=d.text();c({});var u=f.live;u&&u.search(n,s,0,void 0);g.stop()});o.stop();this.insert=function(g,n){d.insert(g);
9
+ n&&k(g)};this.clearButtonClicked=function(){o.stop()};this.searchTextCleared=function(){o.stop()};this.searchTextEntered=function(g,n){if($.inArray(n.keyCode,[0,8,13,32,46,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90])>-1)n.keyCode==13?k(g):o.reset()};this.searchButtonClicked=function(g){k(g)};this.allocationChosen=function(g){k(g)};this.addinationClicked=function(g,n){k(g,n.data.offset)}};var Localization={location_delimiters:{de:"in",fr:"\u00e0",it:"a",en:"in",ch:"in"},explanation_delimiters:{de:"und",fr:"et",it:"e",en:"and",ch:"und"}},PickyClient=function(b){Localization.qualifiers=b.qualifiers||{};Localization.explanations=b.explanations||{};Localization.explanation_delimiters={de:"und",fr:"et",it:"e",en:"and",ch:"und"};var d=b.backends;if(d){d.live||alert("Both a full and live backend must be provided.");d.full||alert("Both a full and live backend must be provided.")}else b.backends=
10
+ {live:b.live&&new LiveBackend(b.live)||alert("A live backend path must be provided."),full:b.full&&new FullBackend(b.full)||alert("A live backend path must be provided.")};var f=b.controller&&new b.controller(b)||new PickyController(b);this.insert=function(c,j){f.insert(c,j||true)}};var PickyAddination=function(b,d){this.remove=function(){d.find(".addination").remove()};this.render=function(f){var c=f.total,j=void 0;j=j||0;j=f.offset+20+j;var r=j+20;f=f.total;if(f<r)r=f;f={offset:j,start:j+1,end:r};if(f.offset<c){c=$("<div class='addination current'>"+t("results.addination.more")+"<div class='tothetop'><a href='javascript:$.scrollTo(0,{ duration: 500});'>&uarr;</a></div></div>");c.bind("click",{offset:f.offset},b.addinationClicked);return c}else return""}};var PickyResultsRenderer=function(b){var d=["street_number","zipcode"],f=function(e){var k=e[e.length-1];e=e.slice(0,e.length-1);if(e==[])e=[e];if(!d.include(k[0]))if(k[1].match(/[^\*~]$/))k[1]+="*";e.push(k);return e},c=function(e){for(var k=Localization.explanations&&Localization.explanations[PickyI18n.locale]||{},s=[],o,g=0,n=e.length;g<n;g++){o=e[g];var u=o[0];u=k[u]||u;s.push([u,o[1]])}return s},j=function(e,k){var s=Localization.explanation_delimiters[PickyI18n.locale],o=c(f(k));o=$.map(o,function(g){return[g[0].replace(/([\w\s\u00c4\u00e4\u00d6\u00f6\u00dc\u00fc\u00e9\u00e8\u00e0]+)/,
11
+ "<strong>$1</strong>"),g[1]].join(" ")});o=o.join(" "+s+" ");return'<div class="explanation">'+e+" "+o+"</div>"},r=function(e,k){var s='<div class="info">';s+=j(k.type,k.combination);if(e.offset>0)s+='<div class="tothetop"><a href="javascript:$.scrollTo(0,{ duration: 500 });">&uarr;</a></div>';s+='<div class="clear"></div></div>';return s};this.render=function(e){var k=$("#picky .results");e.allocations.each(function(s,o){k.append(r(e,o)).append(o.entries.join("")).append(b.render(e))})}};function AllocationRenderer(b){function d(a){var h={},p={},i=[],q;q=0;for(l=a.length;q<l;q++){var v=a[q][0];if(v in h){h[v]=h[v]+" "+a[q][1];i.push(q)}else{h[v]=a[q][1];p[q]=v}}for(q in p)a[q][1]=h[p[q]];for(q=i.length-1;q>=0;q--)a.remove(i[q]);return a}function f(a,h){var p=h||false,i=a[0],q=a[1],v=D[i];if(v){if(v.method)q=q[v.method]();if(v.format)q=v.format.replace(/%1\$s/,q)}i=w[i]||i;if(p&&!(v&&v.ignoreSingle))return q+"&nbsp;("+i+")";return q}function c(a){if(a.length==0)return"";else if(a.length==
12
+ 1)return f(a[0],true);else{var h=[],p=[];a=d(a);for(var i=0,q=a.length;i<q;i++)if(a[i][0]=="first_name")h.unshift(f(a[i]));else a[i][0]=="maiden_name"?p.push(f(a[i])):h.push(f(a[i]));p.length>0&&h.push(p);return h.join(" ")}}function j(a){return function(h,p){for(var i=0,q=a.length;i<q;i++)if(a[i][0]==p)return a[i][1];return""}}function r(a){if(a.length==0)return"";var h=a=d(a);h.sort(function(v,y){return v[0]<y[0]?-1:1});for(var p=[],i=0,q=h.length;i<q;i++)p.push(h[i][0]);return x[p].replace(/:(zipcode|city)/g,
13
+ j(a))}function e(a){var h=a[0];return a[1]+"&nbsp;("+(w[h]||h)+")"}function k(a){if(a.length==0)return"";result=[];a=d(a);for(var h=0,p=a.length;h<p;h++)result.push(e(a[h]));return result.join(", ")}function s(a){for(var h=[],p=[],i=[],q=0,v=a.length;q<v;q++){var y=a[q];if(E.include(y[0]))i.push(y);else A.include(y[0])?h.push(y):p.push(y)}var C;if(i.length>0)C=i[i.length-1];else if(p.length>0)C=p[p.length-1];else if(h.length>0)C=h[h.length-1];z.include(C[0])||(C[1]+="...");a=c(h);p=k(p);i=r(i);return[a,
14
+ p,i]}function o(a){var h=a[0],p=a[1];a=a[2];var i="";i=h!=""?p!=""?[h,p].join(B):h:p;if(a=="")return i;return[i,a].join(m)}function g(a){var h=[],p,i;for(i in a){p=a[i][0];p=u[p]||p;h[i]=p+":"+a[i][1]}return h.join(" ")}var n=PickyI18n.locale,u=Localization.qualifiers&&Localization.qualifiers[n]||{},w=Localization.explanations&&Localization.explanations[n]||{};n=Localization.location_delimiters[n];this.explanation=this.query=this.text="";var z=["street_number","zipcode"];this.contract=d;var D={maiden_name:{format:"(-%1$s)",
15
+ method:"capitalize",ignoreSingle:true},name:{format:"<strong>%1$s</strong>",method:"toUpperCase",ignoreSingle:true},first_name:{format:"%1$s",method:"capitalize"}};this.who=c;var x={zipcode:":zipcode ["+w.city+"]",city:":city","city,zipcode":":zipcode :city"};this.where=r;this.what=k;var A=["first_name","name","maiden_name"],E=["zipcode","city"];this.trisect=s;var B=", ",m=" "+n+"&nbsp;";this.fuse=o;this.querify=g;this.render=function(a){var h=a.combination,p=a.count;a=g(h);h=o(s(h));h=$('<li><div class="text">'+
16
+ h+'</div><div class="count">'+p+"</div></li>");h.bind("click",{query:a},b);return h}};var PickyAllocationsCloud=function(b){var d=$("#picky .allocations"),f=d.find(".shown"),c=d.find(".more"),j=d.find(".hidden"),r=function(){d.hide()},e=new AllocationRenderer(function(o){r();b.allocationChosen(o)}),k=function(o){var g=[];o.each(function(n,u){g.push(e.render(u))});return g},s=function(o){if(o.length==0)return $("#search .allocations").hide();f.empty();c.hide();j.empty().hide();if(o.length>3){$.each(o.slice(0,2),function(g,n){f.append(n)});$.each(o.slice(2),function(g,n){j.append(n)});
17
+ c.show()}else $.each(o,function(g,n){f.append(n)});return $("#search .allocations").show()};c.click(function(){c.hide();j.show()});this.hide=r;this.show=function(o){s(k(o.allocations));d.show()}};
@@ -0,0 +1,103 @@
1
+ var PickyResultsRenderer = function(addination) {
2
+
3
+ // Adds asterisks to the last token.
4
+ //
5
+ var no_asterisks = ['street_number', 'zipcode']; // TODO Works. Parametrize!
6
+ var asteriskifyLastToken = function(combination) {
7
+ var last_part = combination[combination.length-1];
8
+ var parts = combination.slice(0, combination.length-1);
9
+ if (parts == []) { parts = [parts]; }
10
+ if (!no_asterisks.include(last_part[0])) {
11
+ // Replace with * unless there is already one or a tilde.
12
+ //
13
+ if (last_part[1].match(/[^\*~]$/)) { last_part[1] += '*'; }
14
+ }
15
+ parts.push(last_part);
16
+ return parts;
17
+ };
18
+
19
+ // Replaces the category with an explanation of the category.
20
+ //
21
+ var explainCategory = function(combination) {
22
+ var explanations = Localization.explanations && Localization.explanations[PickyI18n.locale] || {}; // TODO
23
+ var parts = [];
24
+ var combo;
25
+
26
+ for (var i = 0, l = combination.length; i < l; i++) {
27
+ combo = combination[i];
28
+ var explanation = combo[0];
29
+ explanation = explanations[explanation] || explanation;
30
+ parts.push([explanation, combo[1]]);
31
+ }
32
+ return parts;
33
+ };
34
+
35
+ //
36
+ //
37
+ var explain = function(type, combination) {
38
+ var explanation_delimiter = Localization.explanation_delimiters[PickyI18n.locale];
39
+
40
+ var parts = explainCategory(asteriskifyLastToken(combination));
41
+ var replaced = $.map(parts, function(part) {
42
+ var category = part[0].replace(/([\w\sÄäÖöÜüéèà]+)/, "<strong>$1</strong>");
43
+ var token = part[1];
44
+ return [category, token].join(' ');
45
+ });
46
+ replaced = replaced.join(' ' + explanation_delimiter + ' ');
47
+
48
+ return '<div class="explanation">' + type + ' ' + replaced + '</div>';
49
+ };
50
+
51
+ // TODO Make customizable.
52
+ //
53
+ var renderHeader = function(data, allocation) {
54
+ // TODO Make type definable. (Mapping, i18n)
55
+ //
56
+ var header_html = '<div class="info">';
57
+ header_html += explain(allocation.type, allocation.combination);
58
+ if (data.offset > 0) {
59
+ header_html += '<div class="tothetop"><a href="javascript:$.scrollTo(0,{ duration: 500 });">&uarr;</a></div>'; // searchEngine.focus();
60
+ }
61
+
62
+ // TODO Parametrize!
63
+ // var names = '';
64
+ // var firstEntryName = $(allocation.entries[0]).find('.name').html();
65
+ // var lastEntryName = $(allocation.entries[allocation.entries.length-1]).find('.name').html();
66
+ // if(firstEntryName == lastEntryName) {
67
+ // var firstEntryFirstName = $(allocation.entries[0]).find('.first_name').html();
68
+ // var lastEntryFirstName = $(allocation.entries[allocation.entries.length-1]).find('.first_name').html();
69
+ // names = '<div class="names">' + firstEntryName + ', ' + firstEntryFirstName + ' ' + t('common.to') + ' ' + lastEntryFirstName + '</div>';
70
+ // }
71
+ // else {
72
+ // names = '<div class="names">' + firstEntryName + ' ' + t('common.to') + ' ' + lastEntryName + '</div>';
73
+ // }
74
+
75
+ // var rangeStart = data.offset + 1;
76
+ // var rangeEnd = data.offset + allocation.entries.length;
77
+ // var rangeText = (rangeStart == rangeEnd) ? rangeStart : rangeStart + '-' + rangeEnd;
78
+ // var range = '<div class="range">' + rangeText + ' ' + t('common.of') + ' ' + data.total + '</div>';
79
+
80
+ // if (data.total > 20) { // TODO Make settable.
81
+ // // header_html += '<div class="clear"></div>'; // TODO
82
+ // // header_html += names; // TODO
83
+ // // header_html += range; // TODO
84
+ // }
85
+
86
+ // For smooth addination scrolling. Don't ask.
87
+ //
88
+ header_html += '<div class="clear"></div></div>';
89
+
90
+ return header_html;
91
+ };
92
+
93
+ // Render results with the data.
94
+ //
95
+ this.render = function(data) {
96
+ var results = $('#picky .results'); // TODO Extract, also from view.
97
+ data.allocations.each(function(i, allocation) {
98
+ results.append(renderHeader(data, allocation)) // TODO header.render(data);
99
+ .append(allocation.entries.join(''))
100
+ .append(addination.render(data));
101
+ });
102
+ };
103
+ };
@@ -0,0 +1,50 @@
1
+ // Translations
2
+ //
3
+ var PickyI18n = { };
4
+
5
+ // Set the correct locale for all js code.
6
+ //
7
+ $(function() {
8
+ PickyI18n.locale = $('html').attr('lang') || 'en';
9
+ });
10
+
11
+ var dictionary = {
12
+ common:{
13
+ join: {de:'und',fr:'et',it:'e',en:'and',ch:'und'},
14
+ 'with': {de:'mit',fr:'avec',it:'con',en:'with',ch:'mit'},
15
+ of: {de:'von',fr:'de',it:'di',en:'of',ch:'vo'},
16
+ to: {de:'bis',fr:'à',it:'alla',en:'to',ch:'bis'}
17
+ },
18
+ results:{
19
+ addination:{
20
+ more:{
21
+ de: 'Weitere Resultate',
22
+ fr: 'Autres résultats',
23
+ it: 'Altri risultati',
24
+ en: 'More results',
25
+ ch: 'Mee Resultaat'
26
+ }
27
+ },
28
+ header:{
29
+ de: 'Ergebnisse',
30
+ fr: 'Résultats',
31
+ it: 'Risultati',
32
+ en: 'Results',
33
+ ch: 'Ergäbnis'
34
+ }
35
+ }
36
+ };
37
+ var t = function(key) {
38
+ var locale = PickyI18n.locale || 'en';
39
+ var keys = key.split('.').concat(locale);
40
+ var current = dictionary;
41
+
42
+ for (var i = 0, l = keys.length; i < l; i++) {
43
+ current = current[keys[i]];
44
+ if (current == undefined) {
45
+ current = 'Translation missing: ' + key + '.' + locale;
46
+ break;
47
+ }
48
+ };
49
+ return current;
50
+ };
@@ -0,0 +1,214 @@
1
+ // Add PickyResults?
2
+ var PickyView = function(picky_controller, config) {
3
+
4
+ var controller = picky_controller;
5
+
6
+ var showResultsLimit = config.showResultsLimit || 10;
7
+
8
+ var searchField = $('#picky input.query');
9
+ var clearButton = $('#picky div.reset');
10
+ var searchButton = $('#picky input.search_button');
11
+ var resultCounter = $('#picky div.status');
12
+ var dashboard = $('#picky .dashboard');
13
+
14
+ var results = $('#picky .results'); // Push into results.
15
+ var noResults = $('#picky .no_results');
16
+
17
+ var addination = new PickyAddination(this, results); // Push into results.
18
+
19
+ var allocationsCloud = new PickyAllocationsCloud(this);
20
+ var resultsRenderer = new PickyResultsRenderer(addination); // TODO Rename results.
21
+
22
+ // Toggle the clear button visibility.
23
+ //
24
+ var showClearButton = function() {
25
+ clearButton.fadeTo(166, 1.0);
26
+ };
27
+ var hideClearButton = function() {
28
+ clearButton.fadeTo(166, 0.0);
29
+ };
30
+
31
+ // TODO Move to results
32
+ var clearResults = function() {
33
+ results.empty();
34
+ };
35
+ var hideEmptyResults = function() {
36
+ noResults.hide();
37
+ };
38
+
39
+ var focus = function() {
40
+ searchField.focus();
41
+ };
42
+ var select = function() {
43
+ searchField.select();
44
+ };
45
+
46
+ // Cleans the interface of any results or choices presented.
47
+ //
48
+ var clean = function() {
49
+ allocationsCloud.hide();
50
+ clearResults();
51
+ hideEmptyResults();
52
+ };
53
+
54
+ // Resets the whole view to the inital state.
55
+ //
56
+ var reset = function(to_text) {
57
+ searchField.val(to_text);
58
+ hideClearButton();
59
+ setSearchStatus('empty');
60
+ resultCounter.empty();
61
+ clean();
62
+ };
63
+
64
+ var bindEventHandlers = function() {
65
+ searchField.keyup(function(event) {
66
+ if (isTextEmpty()) {
67
+ reset();
68
+ controller.searchTextCleared();
69
+ } else {
70
+ controller.searchTextEntered(text(), event);
71
+ showClearButton();
72
+ }
73
+ });
74
+
75
+ searchButton.click(function(event) {
76
+ if (!isTextEmpty()) {
77
+ controller.searchButtonClicked(text());
78
+ }
79
+ });
80
+
81
+ clearButton.click(function() {
82
+ reset('');
83
+ controller.clearButtonClicked();
84
+ focus();
85
+ });
86
+ };
87
+
88
+ var text = function() {
89
+ return searchField.val();
90
+ };
91
+ this.text = text; // TODO Remove.
92
+ var isTextEmpty = function() {
93
+ return text() == '';
94
+ };
95
+
96
+ var showEmptyResults = function() {
97
+ clean();
98
+ updateResultCounter(0);
99
+ noResults.show();
100
+ showClearButton();
101
+ };
102
+ var showTooManyResults = function(data) {
103
+ clean();
104
+ showClearButton();
105
+ allocationsCloud.show(data);
106
+ updateResultCounter(data.total);
107
+ };
108
+ var showResults = function(data) {
109
+ clean();
110
+ updateResultCounter(data.total);
111
+ resultsRenderer.render(data);
112
+ results.show();
113
+ showClearButton();
114
+ };
115
+
116
+ var appendResults = function(data) {
117
+ addination.remove(); // TODO Where should this be?
118
+ resultsRenderer.render(data);
119
+ $.scrollTo('#picky .results div.info:last', { duration: 500, offset: -12 });
120
+ };
121
+
122
+ var updateResultCounter = function(total) {
123
+ resultCounter.text(total); // ((total > 999) ? '999+' : total); // TODO Decide on this.
124
+ flashResultCounter(total);
125
+ };
126
+
127
+ var alertThreshold = 5;
128
+ var flashResultCounter = function(total) {
129
+ if (total > 0 && total <= alertThreshold) {
130
+ resultCounter.fadeTo('fast', 0.5).fadeTo('fast', 1);
131
+ }
132
+ };
133
+
134
+ var tooManyResults = function(data) {
135
+ return data.total > showResultsLimit && data.allocations.length > 1;
136
+ };
137
+ var resultStatusFor = function(data) {
138
+ if (data.isEmpty()) { return 'none'; };
139
+ if (tooManyResults(data)) { return 'support'; }
140
+ return 'ok';
141
+ };
142
+ var setSearchStatus = function(statusClass) {
143
+ dashboard.attr('class', 'dashboard ' + statusClass);
144
+ };
145
+ var setSearchStatusFor = function(data) {
146
+ setSearchStatus(resultStatusFor(data));
147
+ };
148
+
149
+ // Insert a search text into the search field.
150
+ // Field is always selected when doing that.
151
+ //
152
+ var insert = function(text) {
153
+ searchField.val(text);
154
+ select();
155
+ };
156
+ this.insert = insert;
157
+
158
+ // Callbacks.
159
+ //
160
+
161
+ // Full results handling.
162
+ //
163
+ var fullResultsCallback = function(data) {
164
+ setSearchStatusFor(data);
165
+
166
+ if (data.isEmpty()) {
167
+ showEmptyResults();
168
+ } else if (tooManyResults(data)) {
169
+ showTooManyResults(data);
170
+ } else {
171
+ if (data.offset == 0) {
172
+ showResults(data);
173
+ } else {
174
+ appendResults(data);
175
+ }
176
+ };
177
+
178
+ focus();
179
+ };
180
+ this.fullResultsCallback = fullResultsCallback;
181
+
182
+ // Live results handling.
183
+ //
184
+ var liveResultsCallback = function(data) {
185
+ setSearchStatusFor(data);
186
+
187
+ updateResultCounter(data.total);
188
+ };
189
+ this.liveResultsCallback = liveResultsCallback;
190
+
191
+ // Callback for when an allocation has been chosen
192
+ // in the allocation cloud.
193
+ //
194
+ var allocationChosen = function(event) {
195
+ var text = event.data.query;
196
+
197
+ searchField.val(text);
198
+
199
+ controller.allocationChosen(text);
200
+ };
201
+ this.allocationChosen = allocationChosen;
202
+
203
+ // Callback for when the addination has been clicked.
204
+ //
205
+ var addinationClicked = function(event) {
206
+ controller.addinationClicked(text(), event);
207
+ };
208
+ this.addinationClicked = addinationClicked;
209
+
210
+ //
211
+ //
212
+ bindEventHandlers();
213
+ focus();
214
+ };