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.
- data/bin/picky-client +14 -0
- data/lib/picky-client/generator.rb +180 -0
- data/sinatra_prototype/Gemfile +13 -0
- data/sinatra_prototype/app.rb +59 -0
- data/sinatra_prototype/book.rb +42 -0
- data/sinatra_prototype/config.ru +2 -0
- data/sinatra_prototype/images/picky.png +0 -0
- data/sinatra_prototype/javascripts/compiler.jar +0 -0
- data/sinatra_prototype/javascripts/generate_bundles +25 -0
- data/sinatra_prototype/javascripts/jquery-1.3.2.js +4376 -0
- data/sinatra_prototype/javascripts/jquery-1.4.3.min.js +166 -0
- data/sinatra_prototype/javascripts/jquery.scrollTo-1.4.2.js +215 -0
- data/sinatra_prototype/javascripts/jquery.timer.js +75 -0
- data/sinatra_prototype/javascripts/picky.addination.js +36 -0
- data/sinatra_prototype/javascripts/picky.allocation_renderer.js +291 -0
- data/sinatra_prototype/javascripts/picky.allocations_cloud.js +91 -0
- data/sinatra_prototype/javascripts/picky.backend.js +86 -0
- data/sinatra_prototype/javascripts/picky.client.js +62 -0
- data/sinatra_prototype/javascripts/picky.controller.js +107 -0
- data/sinatra_prototype/javascripts/picky.data.js +78 -0
- data/sinatra_prototype/javascripts/picky.extensions.js +15 -0
- data/sinatra_prototype/javascripts/picky.min.js +17 -0
- data/sinatra_prototype/javascripts/picky.results_renderer.js +103 -0
- data/sinatra_prototype/javascripts/picky.source.js.tar +0 -0
- data/sinatra_prototype/javascripts/picky.translations.js +50 -0
- data/sinatra_prototype/javascripts/picky.view.js +214 -0
- data/sinatra_prototype/library.csv +540 -0
- data/sinatra_prototype/stylesheets/stylesheet.css +175 -0
- data/sinatra_prototype/stylesheets/stylesheet.sass +216 -0
- data/sinatra_prototype/views/search.haml +107 -0
- data/spec/picky-client/generator_spec.rb +141 -0
- 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});'>↑</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 });">↑</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+" ("+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]+" ("+(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+" ";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 });">↑</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
|
+
};
|
Binary file
|
@@ -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
|
+
};
|