Added fuzzy search using fuse.js

This commit is contained in:
Hillel Coren 2016-02-28 22:43:43 +02:00
parent d465c0d019
commit 6a9b2130c5
8 changed files with 227 additions and 178 deletions

View File

@ -95,13 +95,14 @@ module.exports = function(grunt) {
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.no.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.es.min.js',
'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js', 'public/vendor/bootstrap-datepicker/dist/locales/bootstrap-datepicker.sv.min.js',
'public/vendor/typeahead.js/dist/typeahead.min.js', 'public/vendor/typeahead.js/dist/typeahead.jquery.min.js',
'public/vendor/accounting/accounting.min.js', 'public/vendor/accounting/accounting.min.js',
'public/vendor/spectrum/spectrum.js', 'public/vendor/spectrum/spectrum.js',
'public/vendor/jspdf/dist/jspdf.min.js', 'public/vendor/jspdf/dist/jspdf.min.js',
'public/vendor/moment/min/moment.min.js', 'public/vendor/moment/min/moment.min.js',
'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js', 'public/vendor/moment-timezone/builds/moment-timezone-with-data.min.js',
'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js', 'public/vendor/stacktrace-js/dist/stacktrace-with-polyfills.min.js',
'public/vendor/fuse.js/src/fuse.min.js',
//'public/vendor/moment-duration-format/lib/moment-duration-format.js', //'public/vendor/moment-duration-format/lib/moment-duration-format.js',
//'public/vendor/handsontable/dist/jquery.handsontable.full.min.js', //'public/vendor/handsontable/dist/jquery.handsontable.full.min.js',
//'public/vendor/pdfmake/build/pdfmake.min.js', //'public/vendor/pdfmake/build/pdfmake.min.js',

View File

@ -74,8 +74,7 @@ class AccountRepository
{ {
$data = $this->getAccountSearchData(); $data = $this->getAccountSearchData();
$type = trans('texts.navigation'); $data['navigation'] = $this->getNavigationSearchData();
$data[$type] = $this->getNavigationSearchData();
return $data; return $data;
} }
@ -83,10 +82,10 @@ class AccountRepository
private function getAccountSearchData() private function getAccountSearchData()
{ {
$data = [ $data = [
trans('texts.clients') => [], 'clients' => [],
trans('texts.contacts') => [], 'contacts' => [],
trans('texts.invoices') => [], 'invoices' => [],
trans('texts.quotes') => [], 'quotes' => [],
]; ];
$clients = Client::scope() $clients = Client::scope()
@ -95,26 +94,31 @@ class AccountRepository
foreach ($clients as $client) { foreach ($clients as $client) {
if ($client->name) { if ($client->name) {
$data[trans('texts.clients')][] = [ $data['clients'][] = [
'value' => $client->name, 'value' => $client->name,
'tokens' => explode(' ', $client->name),
'url' => $client->present()->url, 'url' => $client->present()->url,
]; ];
} }
foreach ($client->contacts as $contact) { foreach ($client->contacts as $contact) {
$data[trans('texts.contacts')][] = [ if ($contact->getFullName()) {
'value' => $contact->getDisplayName(), $data['contacts'][] = [
'tokens' => explode(' ', $contact->getFullName() . ' ' . $contact->email), 'value' => $contact->getDisplayName(),
'url' => $client->present()->url, 'url' => $client->present()->url,
]; ];
}
if ($contact->email) {
$data[trans('texts.contacts')][] = [
'value' => $contact->email,
'url' => $client->present()->url,
];
}
} }
foreach ($client->invoices as $invoice) { foreach ($client->invoices as $invoice) {
$entityType = $invoice->getEntityType(); $entityType = $invoice->getEntityType();
$data[trans("texts.{$entityType}s")][] = [ $data["{$entityType}s"][] = [
'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(), 'value' => $invoice->getDisplayName() . ': ' . $client->getDisplayName(),
'tokens' => explode(' ', $invoice->invoice_number . ' ' . intval($invoice->invoice_number) . ' ' . $client->getDisplayName()),
'url' => $invoice->present()->url, 'url' => $invoice->present()->url,
]; ];
} }

View File

@ -14,7 +14,7 @@
"underscore": "1.7.0", "underscore": "1.7.0",
"jspdf": "1.0.272", "jspdf": "1.0.272",
"bootstrap-datepicker": "1.4.0", "bootstrap-datepicker": "1.4.0",
"typeahead.js": "0.9.3", "typeahead.js": "0.11.1",
"accounting": "0.3.2", "accounting": "0.3.2",
"spectrum": "1.3.4", "spectrum": "1.3.4",
"d3": "3.4.11", "d3": "3.4.11",
@ -25,7 +25,8 @@
"moment-timezone": "~0.4.0", "moment-timezone": "~0.4.0",
"quill": "~0.20.0", "quill": "~0.20.0",
"datetimepicker": "~2.4.5", "datetimepicker": "~2.4.5",
"stacktrace-js": "~1.0.1" "stacktrace-js": "~1.0.1",
"fuse.js": "~2.0.2"
}, },
"resolutions": { "resolutions": {
"jquery": "~1.11" "jquery": "~1.11"

File diff suppressed because one or more lines are too long

125
public/css/built.css vendored
View File

@ -2054,89 +2054,76 @@ See http://bgrins.github.io/spectrum/themes/ for instructions.
.combobox-container:not(.combobox-selected) .fa-times { .combobox-container:not(.combobox-selected) .fa-times {
display: none; display: none;
} }
.twitter-typeahead .tt-query, /**********************************************************
.twitter-typeahead .tt-hint { * typeahead.js v0.11.1 - twitter bootstrap v3.3.5 *
margin-bottom: 0; **********************************************************/
/*root typeahead class*/
.twitter-typeahead {
display: inherit !important;
width: 100%;
} }
.tt-dropdown-menu { .twitter-typeahead .tt-input[disabled] {
min-width: 160px; background-color : #eeeeee !important;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
} }
.tt-suggestion { /*Added to input that's initialized into a typeahead*/
display: block; .twitter-typeahead .tt-input {
}
/*Added to hint input.*/
.twitter-typeahead .hint {
}
/*Added to menu element*/
.twitter-typeahead .tt-menu {
width: 100%;
max-height: 500px;
overflow-y: none;
border: 1px solid #cccccc;
border-radius:4px;
-moz-box-shadow: 12px 14px 30px -7px #616161;
-webkit-box-shadow: 12px 14px 30px -7px #616161;
box-shadow: 12px 14px 30px -7px #616161;
}
/*Added to dataset elements*/
.twitter-typeahead .tt-dataset {
}
/*dded to suggestion elements*/
.twitter-typeahead .tt-suggestion {
padding: 3px 20px; padding: 3px 20px;
white-space: nowrap;
} }
.tt-suggestion.tt-is-under-cursor { /*Added to menu element when it contains no content*/
color: #fff; .twitter-typeahead .tt-empty {
background-color: #0081c2; background-color: white;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
} }
.tt-suggestion.tt-is-under-cursor a { /*Added to menu element when it is opened*/
color: #fff; .twitter-typeahead .tt-open {
background-color: white;
} }
.tt-suggestion p { /*Added to suggestion element when menu cursor moves to said suggestion*/
margin: 0; .twitter-typeahead .tt-suggestion:hover,
.twitter-typeahead .tt-suggestion:focus,
.twitter-typeahead .tt-cursor {
cursor: hand !important;
background-color: #337ab7;
color: white;
} }
/* /*Added to the element that wraps highlighted text*/
.tt-hint { .twitter-typeahead .tt-highlight {
padding: 6px 12px;
}
*/
.twitter-typeahead .tt-hint
{
display: block;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
border: 1px solid transparent;
border-radius:4px;
}
.twitter-typeahead .hint-small
{
height: 30px;
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
line-height: 1.5;
}
.twitter-typeahead .hint-large
{
height: 45px;
padding: 10px 16px;
font-size: 18px;
border-radius: 6px;
line-height: 1.33;
} }
body { background: #f8f8f8 !important; body { background: #f8f8f8 !important;
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;

View File

@ -1,84 +1,71 @@
.twitter-typeahead .tt-query, /**********************************************************
.twitter-typeahead .tt-hint { * typeahead.js v0.11.1 - twitter bootstrap v3.3.5 *
margin-bottom: 0; **********************************************************/
/*root typeahead class*/
.twitter-typeahead {
display: inherit !important;
width: 100%;
} }
.tt-dropdown-menu { .twitter-typeahead .tt-input[disabled] {
min-width: 160px; background-color : #eeeeee !important;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0,0,0,.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
} }
.tt-suggestion { /*Added to input that's initialized into a typeahead*/
display: block; .twitter-typeahead .tt-input {
}
/*Added to hint input.*/
.twitter-typeahead .hint {
}
/*Added to menu element*/
.twitter-typeahead .tt-menu {
width: 100%;
max-height: 500px;
overflow-y: none;
border: 1px solid #cccccc;
border-radius:4px;
-moz-box-shadow: 12px 14px 30px -7px #616161;
-webkit-box-shadow: 12px 14px 30px -7px #616161;
box-shadow: 12px 14px 30px -7px #616161;
}
/*Added to dataset elements*/
.twitter-typeahead .tt-dataset {
}
/*dded to suggestion elements*/
.twitter-typeahead .tt-suggestion {
padding: 3px 20px; padding: 3px 20px;
white-space: nowrap;
} }
.tt-suggestion.tt-is-under-cursor { /*Added to menu element when it contains no content*/
color: #fff; .twitter-typeahead .tt-empty {
background-color: #0081c2; background-color: white;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
} }
.tt-suggestion.tt-is-under-cursor a { /*Added to menu element when it is opened*/
color: #fff; .twitter-typeahead .tt-open {
background-color: white;
} }
.tt-suggestion p { /*Added to suggestion element when menu cursor moves to said suggestion*/
margin: 0; .twitter-typeahead .tt-suggestion:hover,
.twitter-typeahead .tt-suggestion:focus,
.twitter-typeahead .tt-cursor {
cursor: hand !important;
background-color: #337ab7;
color: white;
} }
/* /*Added to the element that wraps highlighted text*/
.tt-hint { .twitter-typeahead .tt-highlight {
padding: 6px 12px;
}
*/
.twitter-typeahead .tt-hint
{
display: block;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
border: 1px solid transparent;
border-radius:4px;
}
.twitter-typeahead .hint-small
{
height: 30px;
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
line-height: 1.5;
}
.twitter-typeahead .hint-large
{
height: 45px;
padding: 10px 16px;
font-size: 18px;
border-radius: 6px;
line-height: 1.33;
} }

View File

@ -68,6 +68,8 @@ We're using the [Git-Flow](http://nvie.com/posts/a-successful-git-branching-mode
* [patricktalmadge/bootstrapper](https://github.com/patricktalmadge/bootstrapper) - Laravel Twitter Bootstrap Bundle * [patricktalmadge/bootstrapper](https://github.com/patricktalmadge/bootstrapper) - Laravel Twitter Bootstrap Bundle
* [danielfarrell/bootstrap-combobox](https://github.com/danielfarrell/bootstrap-combobox) - A combobox plugin * [danielfarrell/bootstrap-combobox](https://github.com/danielfarrell/bootstrap-combobox) - A combobox plugin
* [eternicode/bootstrap-datepicker](https://github.com/eternicode/bootstrap-datepicker) - A datepicker for @twitter bootstrap * [eternicode/bootstrap-datepicker](https://github.com/eternicode/bootstrap-datepicker) - A datepicker for @twitter bootstrap
* [twitter/typeahead.js](https://github.com/twitter/typeahead.js) - a fast and fully-featured autocomplete library
* [krisk/Fuse](https://github.com/krisk/Fuse) - Lightweight fuzzy-search, in JavaScript
* [knockout/knockout](https://github.com/knockout/knockout) - Knockout makes it easier to create rich, responsive UIs with JavaScript * [knockout/knockout](https://github.com/knockout/knockout) - Knockout makes it easier to create rich, responsive UIs with JavaScript
* [rniemeyer/knockout-sortable](https://github.com/rniemeyer/knockout-sortable) - A Knockout.js binding to connect observableArrays with jQuery UI sortable functionality * [rniemeyer/knockout-sortable](https://github.com/rniemeyer/knockout-sortable) - A Knockout.js binding to connect observableArrays with jQuery UI sortable functionality
* [bpampuch/pdfmake](https://github.com/bpampuch/pdfmake) - Client/server side PDF printing in pure JavaScript * [bpampuch/pdfmake](https://github.com/bpampuch/pdfmake) - Client/server side PDF printing in pure JavaScript

View File

@ -259,37 +259,84 @@
} }
function showSearch() { function showSearch() {
$('#search').typeahead('setQuery', ''); console.log('showSearch..');
//$('#search').typeahead('setQuery', '');
$('#navbar-options').hide(); $('#navbar-options').hide();
$('#search-form').show(); $('#search-form').show();
if (window.hasOwnProperty('searchData')) { if (window.hasOwnProperty('loadedSearchData')) {
console.log('has data');
$('#search').focus(); $('#search').focus();
} else { } else {
trackEvent('/activity', '/search'); trackEvent('/activity', '/search');
$.get('{{ URL::route('getSearchData') }}', function(data) { $.get('{{ URL::route('getSearchData') }}', function(data) {
console.log(data); window.loadedSearchData = true;
window.searchData = true;
var datasets = []; $('#search').typeahead({
for (var type in data) hint: true,
highlight: true,
},
{ {
if (!data.hasOwnProperty(type)) continue; name: 'data',
datasets.push({ display: 'value',
name: type, source: searchData(data['clients']),
header: '&nbsp;<b>' + type + '</b>', templates: {
local: data[type] header: '&nbsp;<b>{{ trans('texts.clients') }}</b>'
}); }
} },
if (datasets.length == 0) { {
return; name: 'data',
} display: 'value',
$('#search').typeahead(datasets).on('typeahead:selected', function(element, datum, name) { source: searchData(data['contacts']),
templates: {
header: '&nbsp;<b>{{ trans('texts.contacts') }}</b>'
}
},
{
name: 'data',
display: 'value',
source: searchData(data['invoices']),
templates: {
header: '&nbsp;<b>{{ trans('texts.contacts') }}</b>'
}
},
{
name: 'data',
display: 'value',
source: searchData(data['quotes']),
templates: {
header: '&nbsp;<b>{{ trans('texts.quotes') }}</b>'
}
},
{
name: 'data',
display: 'value',
source: searchData(data['navigation']),
templates: {
header: '&nbsp;<b>{{ trans('texts.navigation') }}</b>'
}
}).on('typeahead:selected', function(element, datum, name) {
window.location = datum.url; window.location = datum.url;
}).focus().typeahead('setQuery', $('#search').val()); }).focus();
//.typeahead('setQuery', $('#search').val());
}); });
} }
} }
function searchData(data) {
return function findMatches(q, cb) {
var options = {
keys: ['value'],
}
var fuse = new Fuse(data, options);
var matches = fuse.search(q);
cb(matches);
}
};
function hideSearch() { function hideSearch() {
$('#search-form').hide(); $('#search-form').hide();
$('#navbar-options').show(); $('#navbar-options').show();