Skip to content

Commit 2fbc7f7

Browse files
committed
feature #632 Fix jQuery instantSearch plugin and search result (yceruto)
This PR was squashed before being merged into the master branch (closes #632). Discussion ---------- Fix jQuery instantSearch plugin and search result ![search](https://user-images.githubusercontent.com/2028198/29907671-c766eaa8-8dea-11e7-97fb-78ff51a7a33e.png) **instantSearch** * Refactored to some known JavaScript pattern (implemented Prototype Design Pattern) * Allow override options through input attributes `data-*` and added support to text translation ("No results found") * Add article template prototype and render method (improved visibility) * Sent configured limit of items to search controller (this allows to configure more than 10 items as results from plugin) * Simplify properties and methods (improved visibility) **Search result** * Removed old effect of hide/show results on focus out/in of the input * Show publishedDate and author for each article found * Sync style with blog index page and some margin adjustments **Minor unrelated changes** * Fix @return type-hints for `PostRepository::findBySearchQuery()` method (better autocomplete) * Show logout link as last link always Commits ------- ea96338 Fix jQuery instantSearch plugin and search result
2 parents 1123e9b + ea96338 commit 2fbc7f7

17 files changed

+147
-586
lines changed

assets/js/jquery.instantSearch.js

+75-62
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,105 @@
22
* jQuery plugin for an instant searching.
33
*
44
* @author Oleg Voronkovich <[email protected]>
5+
* @author Yonel Ceruto <[email protected]>
56
*/
6-
(function($) {
7-
$.fn.instantSearch = function(config) {
8-
return this.each(function() {
9-
initInstantSearch(this, $.extend(true, defaultConfig, config || {}));
10-
});
7+
(function ($) {
8+
'use strict';
9+
10+
String.prototype.render = function (parameters) {
11+
return this.replace(/({{ (\w+) }})/g, function (match, pattern, name) {
12+
return parameters[name];
13+
})
1114
};
1215

13-
var defaultConfig = {
16+
// INSTANTS SEARCH PUBLIC CLASS DEFINITION
17+
// =======================================
18+
19+
var InstantSearch = function (element, options) {
20+
this.$input = $(element);
21+
this.$form = this.$input.closest('form');
22+
this.$preview = $('<ul class="search-preview list-group">').appendTo(this.$form);
23+
this.options = $.extend({}, InstantSearch.DEFAULTS, this.$input.data(), options);
24+
25+
this.$input.keyup(this.debounce());
26+
};
27+
28+
InstantSearch.DEFAULTS = {
1429
minQueryLength: 2,
15-
maxPreviewItems: 10,
16-
previewDelay: 500,
17-
noItemsFoundMessage: 'No results found.'
30+
limit: 10,
31+
delay: 500,
32+
noResultsMessage: 'No results found',
33+
itemTemplate: '\
34+
<article class="post">\
35+
<h2><a href="{{ url }}">{{ title }}</a></h2>\
36+
<p class="post-metadata">\
37+
<span class="metadata"><i class="fa fa-calendar"></i> {{ date }}</span>\
38+
<span class="metadata"><i class="fa fa-user"></i> {{ author }}</span>\
39+
</p>\
40+
<p>{{ summary }}</p>\
41+
</article>'
1842
};
1943

20-
function debounce(fn, delay) {
44+
InstantSearch.prototype.debounce = function () {
45+
var delay = this.options.delay;
46+
var search = this.search;
2147
var timer = null;
48+
var self = this;
49+
2250
return function () {
23-
var context = this, args = arguments;
2451
clearTimeout(timer);
2552
timer = setTimeout(function () {
26-
fn.apply(context, args);
53+
search.apply(self);
2754
}, delay);
2855
};
29-
}
30-
31-
var initInstantSearch = function(el, config) {
32-
var $input = $(el);
33-
var $form = $input.closest('form');
34-
var $preview = $('<ul class="search-preview list-group">').appendTo($form);
35-
36-
var setPreviewItems = function(items) {
37-
$preview.empty();
38-
39-
$.each(items, function(index, item) {
40-
if (index > config.maxPreviewItems) {
41-
return;
42-
}
56+
};
4357

44-
addItemToPreview(item);
45-
});
58+
InstantSearch.prototype.search = function () {
59+
var query = $.trim(this.$input.val()).replace(/\s{2,}/g, ' ');
60+
if (query.length < this.options.minQueryLength) {
61+
this.$preview.empty();
62+
return;
4663
}
4764

48-
var addItemToPreview = function(item) {
49-
var $link = $('<a>').attr('href', item.url).text(item.title);
50-
var $title = $('<h3>').attr('class', 'm-b-0').append($link);
51-
var $summary = $('<p>').text(item.summary);
52-
var $result = $('<div>').append($title).append($summary);
65+
var self = this;
66+
var data = this.$form.serializeArray();
67+
data['l'] = this.limit;
5368

54-
$preview.append($result);
55-
}
69+
$.getJSON(this.$form.attr('action'), data, function (items) {
70+
self.show(items);
71+
});
72+
};
5673

57-
var noItemsFound = function() {
58-
var $result = $('<div>').text(config.noItemsFoundMessage);
74+
InstantSearch.prototype.show = function (items) {
75+
var $preview = this.$preview;
76+
var itemTemplate = this.options.itemTemplate;
5977

78+
if (0 === items.length) {
79+
$preview.html(this.options.noResultsMessage);
80+
} else {
6081
$preview.empty();
61-
$preview.append($result);
82+
$.each(items, function (index, item) {
83+
$preview.append(itemTemplate.render(item));
84+
});
6285
}
86+
};
6387

64-
var updatePreview = function() {
65-
var query = $.trim($input.val()).replace(/\s{2,}/g, ' ');
88+
// INSTANTS SEARCH PLUGIN DEFINITION
89+
// =================================
6690

67-
if (query.length < config.minQueryLength) {
68-
$preview.empty();
69-
return;
70-
}
91+
function Plugin(option) {
92+
return this.each(function () {
93+
var $this = $(this);
94+
var instance = $this.data('instantSearch');
95+
var options = typeof option === 'object' && option;
7196

72-
$.getJSON($form.attr('action') + '?' + $form.serialize(), function(items) {
73-
if (items.length === 0) {
74-
noItemsFound();
75-
return;
76-
}
97+
if (!instance) $this.data('instantSearch', (instance = new InstantSearch(this, options)));
7798

78-
setPreviewItems(items);
79-
});
80-
}
81-
82-
$input.focusout(function(e) {
83-
$preview.fadeOut();
84-
});
99+
if (option === 'search') instance.search();
100+
})
101+
}
85102

86-
$input.focusin(function(e) {
87-
$preview.fadeIn();
88-
updatePreview();
89-
});
103+
$.fn.instantSearch = Plugin;
104+
$.fn.instantSearch.Constructor = InstantSearch;
90105

91-
$input.keyup(debounce(updatePreview, config.previewDelay));
92-
}
93106
})(window.jQuery);

assets/js/search.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import './jquery.instantSearch.js';
22

33
$(function() {
44
$('.search-field').instantSearch({
5-
previewDelay: 100,
5+
delay: 100,
66
});
77
});

assets/scss/app.scss

+20
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,23 @@ body#comment_form_error h1.text-danger {
308308
width: 98%;
309309
}
310310
}
311+
312+
/* Page: 'Blog search'
313+
------------------------------------------------------------------------- */
314+
body#blog_search #main h1,
315+
body#blog_search #main p {
316+
margin-bottom: 0.5em
317+
}
318+
319+
body#blog_search article.post:first-child {
320+
margin-top: 2em;
321+
}
322+
323+
body#blog_search article.post {
324+
margin-bottom: 2em;
325+
}
326+
327+
body#blog_search .post-metadata {
328+
font-size: 16px;
329+
margin-bottom: 8px;
330+
}

public/build/css/app.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/js/admin.js

+13-469
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/js/app.js

+1-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/js/common.js

+2-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/js/login.js

+1-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/js/search.js

+1-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/manifest.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/build/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@
2525
"build/js/login.js": "/build/js/login.js",
2626
"build/js/search.js": "/build/js/search.js",
2727
"build/manifest.js": "/build/manifest.js"
28-
}
28+
}

src/Controller/BlogController.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,15 @@ public function searchAction(Request $request)
165165
}
166166

167167
$query = $request->query->get('q', '');
168-
$posts = $this->getDoctrine()->getRepository(Post::class)->findBySearchQuery($query);
168+
$limit = $request->query->get('l', 10);
169+
$posts = $this->getDoctrine()->getRepository(Post::class)->findBySearchQuery($query, $limit);
169170

170171
$results = [];
171172
foreach ($posts as $post) {
172173
$results[] = [
173174
'title' => htmlspecialchars($post->getTitle()),
175+
'date' => $post->getPublishedAt()->format('M d, Y'),
176+
'author' => htmlspecialchars($post->getAuthor()->getFullName()),
174177
'summary' => htmlspecialchars($post->getSummary()),
175178
'url' => $this->generateUrl('blog_post', ['slug' => $post->getSlug()]),
176179
];

src/Repository/PostRepository.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ private function createPaginator(Query $query, $page)
6464
* @param string $rawQuery The search query as input by the user
6565
* @param int $limit The maximum number of results returned
6666
*
67-
* @return array
67+
* @return Post[]
6868
*/
6969
public function findBySearchQuery($rawQuery, $limit = Post::NUM_ITEMS)
7070
{

templates/base.html.twig

+4-4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
{% endif %}
5757
{% endblock %}
5858

59+
<li>
60+
<a href="{{ path('blog_search') }}"> <i class="fa fa-search"></i> {{ 'menu.search'|trans }}</a>
61+
</li>
62+
5963
{% if app.user %}
6064
<li>
6165
<a href="{{ path('security_logout') }}">
@@ -64,10 +68,6 @@
6468
</li>
6569
{% endif %}
6670

67-
<li>
68-
<a href="{{ path('blog_search') }}"> <i class="fa fa-search"></i> {{ 'menu.search'|trans }}</a>
69-
</li>
70-
7171
<li class="dropdown">
7272
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" id="locales">
7373
<i class="fa fa-globe" aria-hidden="true"></i>

templates/blog/search.html.twig

+9-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@
55
<script src="{{ asset('build/js/search.js') }}"></script>
66
{% endblock %}
77

8+
{% block body_id 'blog_search' %}
9+
810
{% block main %}
911
<form action="{{ path('blog_search') }}" method="get">
1012
<div class="form-group">
11-
<input name="q" type="text" class="form-control search-field" placeholder="{{ 'post.search_for'|trans }}" autocomplete="off" autofocus>
13+
<input name="q"
14+
class="form-control search-field"
15+
placeholder="{{ 'post.search_for'|trans }}"
16+
autocomplete="off"
17+
autofocus
18+
data-no-results-message="{{ 'post.search_no_results'|trans }}"
19+
>
1220
</div>
1321
</form>
1422

translations/messages.en.xlf

+4
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@
296296
<source>post.search_for</source>
297297
<target>Search for...</target>
298298
</trans-unit>
299+
<trans-unit id="post.search_no_results">
300+
<source>post.search_no_results</source>
301+
<target>No results found</target>
302+
</trans-unit>
299303

300304
<trans-unit id="notification.comment_created">
301305
<source>notification.comment_created</source>

translations/messages.es.xlf

+8
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@
329329
<source>post.deleted_successfully</source>
330330
<target>¡Artículo eliminado con éxito!</target>
331331
</trans-unit>
332+
<trans-unit id="post.search_for">
333+
<source>post.search_for</source>
334+
<target>Buscar...</target>
335+
</trans-unit>
336+
<trans-unit id="post.search_no_results">
337+
<source>post.search_no_results</source>
338+
<target>No se encontraron resultados</target>
339+
</trans-unit>
332340
<trans-unit id="notification.comment_created">
333341
<source>notification.comment_created</source>
334342
<target>¡Su artículo recibió un comentario!</target>

0 commit comments

Comments
 (0)