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,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
+ };