3
* http://github.com/jlukic/semantic-ui/
6
* Copyright 2014 Contributors
7
* Released under the MIT license
8
* http://opensource.org/licenses/MIT
12
;(function ($, window, document, undefined) {
14
$.fn.search = function(source, parameters) {
16
$allModules = $(this),
17
moduleSelector = $allModules.selector || '',
19
time = new Date().getTime(),
23
methodInvoked = (typeof query == 'string'),
24
queryArguments = [].slice.call(arguments, 1),
30
settings = $.extend(true, {}, $.fn.search.settings, parameters),
32
className = settings.className,
33
selector = settings.selector,
34
error = settings.error,
35
namespace = settings.namespace,
37
eventNamespace = '.' + namespace,
38
moduleNamespace = namespace + '-module',
41
$prompt = $module.find(selector.prompt),
42
$searchButton = $module.find(selector.searchButton),
43
$results = $module.find(selector.results),
44
$result = $module.find(selector.result),
45
$category = $module.find(selector.category),
48
instance = $module.data(moduleNamespace),
54
initialize: function() {
55
module.verbose('Initializing module');
58
inputEvent = (prompt.oninput !== undefined)
60
: (prompt.onpropertychange !== undefined)
66
.on('focus' + eventNamespace, module.event.focus)
67
.on('blur' + eventNamespace, module.event.blur)
68
.on('keydown' + eventNamespace, module.handleKeyboard)
70
if(settings.automatic) {
72
.on(inputEvent + eventNamespace, module.search.throttle)
76
.on('click' + eventNamespace, module.search.query)
79
.on('click' + eventNamespace, selector.result, module.results.select)
83
instantiate: function() {
84
module.verbose('Storing instance of module', module);
87
.data(moduleNamespace, module)
91
module.verbose('Destroying instance');
93
.removeData(moduleNamespace)
99
.addClass(className.focus)
101
module.results.show();
104
module.search.cancel();
106
.removeClass(className.focus)
108
module.results.hide();
111
handleKeyboard: function(event) {
113
// force latest jq dom
114
$result = $module.find(selector.result),
115
$category = $module.find(selector.category),
116
keyCode = event.which,
124
activeClass = className.active,
125
currentIndex = $result.index( $result.filter('.' + activeClass) ),
126
resultSize = $result.size(),
130
if(keyCode == keys.escape) {
131
module.verbose('Escape key pressed, blurring search field');
137
if($results.filter(':visible').size() > 0) {
138
if(keyCode == keys.enter) {
139
module.verbose('Enter key pressed, selecting active result');
140
if( $result.filter('.' + activeClass).size() > 0 ) {
141
$.proxy(module.results.select, $result.filter('.' + activeClass) )();
142
event.preventDefault();
146
else if(keyCode == keys.upArrow) {
147
module.verbose('Up key pressed, changing active result');
148
newIndex = (currentIndex - 1 < 0)
153
.removeClass(activeClass)
156
.removeClass(activeClass)
158
.addClass(activeClass)
160
.addClass(activeClass)
162
event.preventDefault();
164
else if(keyCode == keys.downArrow) {
165
module.verbose('Down key pressed, changing active result');
166
newIndex = (currentIndex + 1 >= resultSize)
171
.removeClass(activeClass)
174
.removeClass(activeClass)
176
.addClass(activeClass)
178
.addClass(activeClass)
180
event.preventDefault();
185
if(keyCode == keys.enter) {
186
module.verbose('Enter key pressed, executing query');
187
module.search.query();
189
.addClass(className.down)
192
.one('keyup', function(){
194
.removeClass(className.down)
204
xhr = $module.data('xhr') || false
206
if( xhr && xhr.state() != 'resolved') {
207
module.debug('Cancelling last search');
211
throttle: function() {
213
searchTerm = $prompt.val(),
214
numCharacters = searchTerm.length
216
clearTimeout(module.timer);
217
if(numCharacters >= settings.minCharacters) {
218
module.timer = setTimeout(module.search.query, settings.searchThrottle);
221
module.results.hide();
226
searchTerm = $prompt.val(),
227
cachedHTML = module.search.cache.read(searchTerm)
230
module.debug("Reading result for '" + searchTerm + "' from cache");
231
module.results.add(cachedHTML);
234
module.debug("Querying for '" + searchTerm + "'");
235
if(typeof source == 'object') {
236
module.search.local(searchTerm);
239
module.search.remote(searchTerm);
241
$.proxy(settings.onSearchQuery, $module)(searchTerm);
244
local: function(searchTerm) {
247
fullTextResults = [],
248
searchFields = $.isArray(settings.searchFields)
249
? settings.searchFields
250
: [settings.searchFields],
252
searchRegExp = new RegExp('(?:\s|^)' + searchTerm, 'i'),
253
fullTextRegExp = new RegExp(searchTerm, 'i'),
257
.addClass(className.loading)
259
// iterate through search fields in array order
260
$.each(searchFields, function(index, field) {
261
$.each(source, function(label, thing) {
262
if(typeof thing[field] == 'string' && ($.inArray(thing, results) == -1) && ($.inArray(thing, fullTextResults) == -1) ) {
263
if( searchRegExp.test( thing[field] ) ) {
266
else if( fullTextRegExp.test( thing[field] ) ) {
267
fullTextResults.push(thing);
272
searchHTML = module.results.generate({
273
results: $.merge(results, fullTextResults)
276
.removeClass(className.loading)
278
module.search.cache.write(searchTerm, searchHTML);
279
module.results.add(searchHTML);
281
remote: function(searchTerm) {
284
stateContext : $module,
286
urlData: { query: searchTerm },
287
success : function(response) {
288
searchHTML = module.results.generate(response);
289
module.search.cache.write(searchTerm, searchHTML);
290
module.results.add(searchHTML);
292
failure : module.error
296
module.search.cancel();
297
module.debug('Executing search');
298
$.extend(true, apiSettings, settings.apiSettings);
303
read: function(name) {
305
cache = $module.data('cache')
307
return (settings.cache && (typeof cache == 'object') && (cache[name] !== undefined) )
312
write: function(name, value) {
314
cache = ($module.data('cache') !== undefined)
315
? $module.data('cache')
320
.data('cache', cache)
327
generate: function(response) {
328
module.debug('Generating html from response', response);
330
template = settings.templates[settings.type],
333
if(($.isPlainObject(response.results) && !$.isEmptyObject(response.results)) || ($.isArray(response.results) && response.results.length > 0) ) {
334
if(settings.maxResults > 0) {
335
response.results = $.makeArray(response.results).slice(0, settings.maxResults);
337
if(response.results.length > 0) {
338
if($.isFunction(template)) {
339
html = template(response);
342
module.error(error.noTemplate, false);
347
html = module.message(error.noResults, 'empty');
349
$.proxy(settings.onResults, $module)(response);
352
add: function(html) {
353
if(settings.onResultsAdd == 'default' || $.proxy(settings.onResultsAdd, $results)(html) == 'default') {
358
module.results.show();
361
if( ($results.filter(':visible').size() === 0) && ($prompt.filter(':focus').size() > 0) && $results.html() !== '') {
366
$.proxy(settings.onResultsOpen, $results)();
370
if($results.filter(':visible').size() > 0) {
375
$.proxy(settings.onResultsClose, $results)();
378
select: function(event) {
379
module.debug('Search result selected');
382
$title = $result.find('.title'),
383
title = $title.html()
385
if(settings.onSelect == 'default' || $.proxy(settings.onSelect, this)(event) == 'default') {
387
$link = $result.find('a[href]').eq(0),
388
href = $link.attr('href') || false,
389
target = $link.attr('target') || false
391
module.results.hide();
396
if(target == '_blank' || event.ctrlKey) {
400
window.location.href = (href);
407
setting: function(name, value) {
408
if( $.isPlainObject(name) ) {
409
$.extend(true, settings, name);
411
else if(value !== undefined) {
412
settings[name] = value;
415
return settings[name];
418
internal: function(name, value) {
419
if( $.isPlainObject(name) ) {
420
$.extend(true, module, name);
422
else if(value !== undefined) {
423
module[name] = value;
431
if(settings.performance) {
432
module.performance.log(arguments);
435
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
436
module.debug.apply(console, arguments);
440
verbose: function() {
441
if(settings.verbose && settings.debug) {
442
if(settings.performance) {
443
module.performance.log(arguments);
446
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
447
module.verbose.apply(console, arguments);
452
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
453
module.error.apply(console, arguments);
456
log: function(message) {
462
if(settings.performance) {
463
currentTime = new Date().getTime();
464
previousTime = time || currentTime;
465
executionTime = currentTime - previousTime;
470
'Arguments' : [].slice.call(message, 1) || '',
471
'Execution Time' : executionTime
474
clearTimeout(module.performance.timer);
475
module.performance.timer = setTimeout(module.performance.display, 100);
477
display: function() {
479
title = settings.name + ':',
483
clearTimeout(module.performance.timer);
484
$.each(performance, function(index, data) {
485
totalTime += data['Execution Time'];
487
title += ' ' + totalTime + 'ms';
489
title += ' \'' + moduleSelector + '\'';
491
if($allModules.size() > 1) {
492
title += ' ' + '(' + $allModules.size() + ')';
494
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
495
console.groupCollapsed(title);
497
console.table(performance);
500
$.each(performance, function(index, data) {
501
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
509
invoke: function(query, passedArguments, context) {
516
passedArguments = passedArguments || queryArguments;
517
context = element || context;
518
if(typeof query == 'string' && object !== undefined) {
519
query = query.split(/[\. ]/);
520
maxDepth = query.length - 1;
521
$.each(query, function(depth, value) {
522
var camelCaseValue = (depth != maxDepth)
523
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
526
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
527
object = object[camelCaseValue];
529
else if( object[camelCaseValue] !== undefined ) {
530
found = object[camelCaseValue];
533
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
534
object = object[value];
536
else if( object[value] !== undefined ) {
537
found = object[value];
545
if ( $.isFunction( found ) ) {
546
response = found.apply(context, passedArguments);
548
else if(found !== undefined) {
551
if($.isArray(returnedValue)) {
552
returnedValue.push(response);
554
else if(returnedValue !== undefined) {
555
returnedValue = [returnedValue, response];
557
else if(response !== undefined) {
558
returnedValue = response;
564
if(instance === undefined) {
567
module.invoke(query);
570
if(instance !== undefined) {
579
return (returnedValue !== undefined)
585
$.fn.search.settings = {
587
name : 'Search Module',
588
namespace : 'search',
594
// onSelect default action is defined in module
595
onSelect : 'default',
596
onResultsAdd : 'default',
598
onSearchQuery : function(){},
599
onResults : function(response){},
601
onResultsOpen : function(){},
602
onResultsClose : function(){},
607
searchThrottle : 300,
630
noResults : 'Your search returned no results',
631
logging : 'Error in debug logging, exiting.',
632
noTemplate : 'A valid template name was not specified.',
633
serverError : 'There was an issue with querying the server.',
634
method : 'The method you called is not defined.'
639
searchButton : '.search.button',
640
results : '.results',
641
category : '.category',
646
message: function(message, type) {
650
if(message !== undefined && type !== undefined) {
652
+ '<div class="message ' + type +'">'
655
if(type == 'empty') {
657
+ '<div class="header">No Results</div class="header">'
658
+ '<div class="description">' + message + '</div class="description">'
662
html += ' <div class="description">' + message + '</div>';
668
categories: function(response) {
672
if(response.results !== undefined) {
674
$.each(response.results, function(index, category) {
675
if(category.results !== undefined && category.results.length > 0) {
677
+ '<div class="category">'
678
+ '<div class="name">' + category.name + '</div>'
680
// each item inside category
681
$.each(category.results, function(index, result) {
682
html += '<div class="result">';
683
html += '<a href="' + result.url + '"></a>';
684
if(result.image !== undefined) {
686
+ '<div class="image">'
687
+ ' <img src="' + result.image + '">'
691
html += '<div class="info">';
692
if(result.price !== undefined) {
693
html+= '<div class="price">' + result.price + '</div>';
695
if(result.title !== undefined) {
696
html+= '<div class="title">' + result.title + '</div>';
698
if(result.description !== undefined) {
699
html+= '<div class="description">' + result.description + '</div>';
711
if(response.resultPage) {
713
+ '<a href="' + response.resultPage.url + '" class="all">'
714
+ response.resultPage.text
721
simple: function(response) {
725
if(response.results !== undefined) {
728
$.each(response.results, function(index, result) {
729
html += '<a class="result" href="' + result.url + '">';
730
if(result.image !== undefined) {
732
+ '<div class="image">'
733
+ ' <img src="' + result.image + '">'
737
html += '<div class="info">';
738
if(result.price !== undefined) {
739
html+= '<div class="price">' + result.price + '</div>';
741
if(result.title !== undefined) {
742
html+= '<div class="title">' + result.title + '</div>';
744
if(result.description !== undefined) {
745
html+= '<div class="description">' + result.description + '</div>';
753
if(response.resultPage) {
755
+ '<a href="' + response.resultPage.url + '" class="all">'
756
+ response.resultPage.text
766
})( jQuery, window , document );
b'\\ No newline at end of file'