Algolia template if no hits are returned - javascript

I've implemented my own Algolia PoC based of https://www.algolia.com/doc/search/auto-complete and I'm now struggling with a specific use case: how can I handle a search which does not return any hits?
Here is my code:
I've been able to identify and detect when/where no hits are returned, but I can't do anything beside just using a console.log(). I tried to get a custom return_msg but I can't call the function.
I also tried to do some tweak under suggestion: function(suggestion) but this function is never called if no hits are returned.
I also did not found any documentation about this "Templates" section on https://github.com/algolia/autocomplete.js
$('#q').autocomplete({ hint: false }, [
{
source: function(q, cb) {
index.search(q,
{ hitsPerPage: 10 },
function(error, content) {
if (error) {
cb([]);
return;
}
if (content.nbHits == 0)
{ return_msg = '<h5> Sorry, no result </h5>';
// DO something here
console.log(return_msg);
// console.log return "Sorry, no result"
}
cb(content.hits, content);
});
},
displayKey: 'game',
templates: {
suggestion: function(suggestion) {
return_msg = '<h5> '+ suggestion.MY_ATTRIBUTE + '</h5>'
return return_msg;
}
}
}
]).on('autocomplete:selected', function(event, suggestion, dataset) {
window.location = (suggestion.url);
});
Any pointers would be greatly appreciated =)

Using the templates option of your dataset you can specify the template to use when there are no results:
source: autocomplete.sources.hits(indexObj, { hitsPerPage: 2 }),
templates: {
suggestion: // ...
header: // ...
footer: // ...
empty: function(options) {
return '<div>My empty message</div>';
}
}
Full documentation here.

Related

Multiple Remote Sources with Twitter Typeahead.js

I'm using the Twitter typeahead.js library for typeahead search with a single data source /api/domains.
I'd now like to add two more sources using other API endpoints:
/api/ips
/api/hostnames
How would I accomplish this? Do I need to use the BloodHound engine for this functionality?
Existing Code
<script>
$('#header').on('click', '.tt-selectable', function() {
window.location.href = "/explore/" + $(this).attr('data-domain');
});
var substringMatcher = function(strs) {
return function findMatches(q, cb) {
var matches, substringRegex;
// an array that will be populated with substring matches
matches = [];
// regex used to determine if a string contains the substring `q`
substrRegex = new RegExp(q, 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
$.each(strs, function(i, str) {
if (substrRegex.test(str)) {
matches.push(str);
}
});
cb(matches);
};
};
$.getJSON('/api/domains')
.done(function( json ) {
populateSuggestions(json.domains);
})
.fail(function( jqxhr, textStatus, error ) {
var err = textStatus + ", " + error;
console.log( "Request Failed: " + err );
});
const populateSuggestions = function(data) {
$('.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 1
}, {
displayKey: 'name',
limit: 10,
source: substringMatcher(data),
templates: {
suggestion: function (data) {
return "<div data-domain=" + data + ">" + data + "</div>"
}
}
});
}
</script>
Existing API response
{
domains: [
"a.com",
"b.com",
"c.com",
"d.com" ]
}
Do you want the suggestions differentiated by the source? I would expect so, since one source is domains, one is IP addresses (I presume?) and the other is hostnames. The first two I would expect someone to be typing in very different entries and getting back very different data.
Anyway, that's frequently how I use typeahead - similar searches but with multiple sources - and yes I use Bloodhound to set up each source. Then for each bloodhound source I have a separate JSON object in the typeahead setup. Like this:
$('#searchbox .typeahead').typeahead({
hint: true,
highlight: true,
autoselect: true,
minLength: 2
},
{
name: 'other',
display: 'addr',
source: otherBH,
templates: {
header: "<span class='typeahead-header'>Static</span>"
}
},
{
name: 'lookupSvc',
display: 'lookupSvc',
source: lookupServiceBH,
templates: {
header: "<span class='typeahead-header'>AJAX</span>"
}
}
...

Expanding a fancyTree via lazy load

I have set up my fancytree to open via lazy loading, and it works very nicely.
$("#tree").fancytree({
selectMode: 1, quicksearch:true, minExpandLevel:2,
autoScroll: true,
source: [{
title: "Root",
key: "1",
lazy: true,
folder: true
}],
lazyLoad: function(event, data) {
var node = data.node;
data.result = {
url: "getTreeData.jsp?parent=" + node.key,
data: {
mode: "children",
parent: node.key
},
cache: false
};
}
});
However, if a user has previously selected a point on the tree, I would like the tree to open to that point.
I have a variable called hierarchy which looks like "1/5/10/11/200" and holds the sequence of keys to that certain point.
The following will not work:
$("#tree").fancytree("getTree").getNodeByKey("1").setExpanded();
$("#tree").fancytree("getTree").getNodeByKey("5").setExpanded();
$("#tree").fancytree("getTree").getNodeByKey("10").setExpanded();
$("#tree").fancytree("getTree").getNodeByKey("11").setExpanded();
$("#tree").fancytree("getTree").getNodeByKey("200").setExpanded();
The reason why it will not work, apparently, is because there needs to be some delay between one statement and the next.
The following code works, however it is in my mind messy:
function openNode(item) {
$("#tree").fancytree("getTree").getNodeByKey(String(item)).setExpanded();
}
function expandTree(hierarchy) {
var i=0;
hierarchy.split("/").forEach(function (item) {
if (item!="") {
i++;
window.setTimeout(openNode, i*100,item);
}
});
Is there any neater way of opening to a specific point on the tree?
The following code seems to do the trick.
It was adapted from
http://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree.html#loadKeyPath
function expandTree(hierarchy) {
$('#tree').fancytree("getTree").loadKeyPath(hierarchy).progress(function(data) {
if (data.status === "loaded") {
console.log("loaded intermediate node " + data.node);
$('#tree').fancytree("getTree").activateKey(data.node.key);
} else if (data.status === "ok") {}
}).done(function() {});
}

How Do I Enable Filter Functions in CA Agile Central?

I need to employ a filter function to implement a heuristic for selecting records. Simple field/value checks, alone, are inadequate for our purpose.
I'm trying to follow the examples for function filters, but for some reason, the "allowFunctions" flag keeps getting set to false.
I attempt to set the allowFunctions property to true in the storeConfig:
storeConfig: {
models: ['userstory', 'defect'],
allowFunctions: true,
filters: [{
// This did not work ...
property: 'Iteration.Name',
value: 'Sprint 3',
// Trying dynamic Filter Function. Update: Never called.
filterFn: function (item) {
console.log("Entered Filter Function!");
var iter = item.get("Iteration");
console.log("Iteration field: ", iter);
if (iter !== null && iter !== undefined) {
return (iter.name === "Sprint 3");
} else {
return false;
}
}
}]
},
After the grid view renders, I inspect it the store configuration and its filters:
listeners: {
afterrender: {
fn: function (_myVar, eOpts) {
console.log("Arg to afterrender: ", _myVar, " and ", eOpts);
var _myStore = _myVar.getStore();
console.log("Store filters: ", _myStore.filters);
}
}
},
What I find is that the allowFunctions property has been set back to false and I see that the filter function I specified never fired.
Console Screen Shot
So either I am setting allowFunctions to true in the wrong place, or something built into the Rally Grid View and its data store prohibits filter functions and flips the flag back to false.
OR there's a third option betraying how badly off my theory of operation is.
Oh, wise veterans, please advise.
Here's the entire Apps.js file:
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function () {
//Write app code here
console.log("Overall App Launch function entered");
//API Docs: https://help.rallydev.com/apps/2.1/doc/
}
});
Rally.onReady(function () {
Ext.define('BOA.AdoptedWork.MultiArtifactGrid', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function () {
console.log("onReady Launch function entered");
this.theGrid = {
xtype: 'rallygrid',
showPagingToolbar: true,
showRowActionsColumn: false,
editable: false,
columnCfgs: [
'FormattedID',
'Name',
'ScheduleState',
'Iteration',
'Release',
'PlanEstimate',
'TaskEstimateTotal',
'TaskActualTotal', // For some reason this does not display ?? :o( ??
'TaskRemainingTotal'
],
listeners: {
afterrender: {
fn: function (_myVar, eOpts) {
console.log("Arg to afterrender: ", _myVar, " and ", eOpts);
var _myStore = _myVar.getStore();
console.log("Store filters: ", _myStore.filters);
}
}
},
storeConfig: {
models: ['userstory', 'defect'],
allowFunctions: true,
filters: [{
// This did not work ...
property: 'Iteration.Name',
value: 'Sprint 3',
// Trying dynamic Filter Function. Update: Never called.
filterFn: function (item) {
console.log("Entered Filter Function!");
var iter = item.get("Iteration");
console.log("Iteration field: ", iter);
if (iter !== null && iter !== undefined) {
return (iter.name === "Sprint 3");
} else {
return false;
}
}
}]
},
context: this.getContext(),
scope: this
};
this.add(this.theGrid);
console.log("The Grid Object: ", this.theGrid);
}
});
Rally.launchApp('BOA.AdoptedWork.MultiArtifactGrid', {
name: 'Multi-type Grid'
});
});
This is a tricky one since you still want your server filter to apply and then you want to further filter the data down on the client side.
Check out this example here:
https://github.com/RallyCommunity/CustomChart/blob/master/Settings.js#L98
I think you can basically add a load listener to your store and then within that handler you can do a filterBy to further filter your results on the client side.
listeners: {
load: function(store) {
store.filterBy(function(record) {
//return true to include record in store data
});
}
}
I'm not familiar with allowFunctions, but in general remoteFilter: true/false is what controls whether the filtering is occurring server side or client side. remoteFilter: true + the load handler above gives you the best of both worlds.

AutoComplete with Key-Value in grid, showing key when focus

I am using paramquery grid component in which I am trying to use autocomplete.
Column Model for branch:
{ title: "Branch", dataIndx: "branchId", width: 150,
filter: { type: "select",
condition: 'equal',
prepend: { '': '--All--' },
listeners: ['change'],
valueIndx: "branchId",
labelIndx: "branchName",
options: branchList,
},
editor: {
type: "textbox",
init: autoCompleteEditor
//type: function (ui) { return dropdowneditor(this, ui); }
},
render: function (ui) {
for (var i = 0; i < branchList.length; i++) {
var option = branchList[i];
if (option.branchId == ui.rowData.branchId) {
return option.branchName;
}
}
}
}
autoCompleteEditorMethod:
var autoCompleteEditor = function (ui) {
var $inp = ui.$cell.find("input");
//initialize the editor
$inp.autocomplete({
source: function(request, response) {
var rows = imAutocompleteJSONParse(branchList);// this method converting my JSON object into Value and label format.
return response(rows);
},
selectItem: { on: true }, //custom option
highlightText: { on: true }, //custom option
minLength: 0,
select: function(event, ui) {
event.preventDefault();
$(this).val(ui.item.label);
},
focus: function(event, ui) {
event.preventDefault();
$("#search").val(ui.item.label);
}
}).focus(function () {
//open the autocomplete upon focus
$(this).autocomplete("search", "");
});
}
I get branch id into my grid and I have branchList JSON which have branch id & branch Name. Inside grid my render function showing branchName on UI.
But when I click on searchable dropdown I'm getting branch id.
Below snapshot may explain my issue properly.
Summary of issue: I am getting branch id in Grid. With help of render method I am able to show branch name on grid. but when I click on textbox I getting branch id.
http://jsfiddle.net/v4zx8tjc/4/
Like blackmiaool suggests in his comment, this question would be easier to answer with a live demo using something like JSFiddle.
Based on what I can see in your question, which isn't that much, there are a few areas I would take a second look at.
The Source function in JQuery.autoComplete. Where is branchList coming from? I don't see it declared anywhere and why are you not using the 'request' param?
Not sure what your custom properties are doing but it might be a good idea to verify those are not interfering with the results.
Edit 1: Looking back at the code you posted I think I see where your branchList variable is coming from. It would be very helpful to see your imAutocompleteJSONParse() method because I believe that may be where things are breaking down.

Search highlight in Elasticsearch (javascript)

I am having a problem with result highlighting in Elasticsearch. My query works, it does return results, but they are NOT highlighted... so I've been searching but I can't find what I'm doing wrong!
This is my code:
function search(searchInput){
emptyTable();
client.search({
index: 'movies',
size: 5,
body: {
query: {
//match: {_all: searchInput}
"term": {
"_all" : searchInput
}
},
"highlight": {
"require_field_match": true,
"fields": {
"_all": {
"pre_tags": [
"<b>"
],
"post_tags": [
"</b>"
]
}
}
}
}
}).then(function (resp) {
var hits = resp.hits.hits;
var hitcount = resp.hits.total;
if(!jQuery.isEmptyObject(hits)){
console.log(hits);
$.each(hits, function(key,obj) {
if(key%2==0){
$('#table').append('<tr><td>'+obj._source.imdbid+'</td><td>'+obj._source.name+'</td><td>'+obj._source.desc+'</td></tr>');
}else{
$('#table').append('<tr class="even"><td>'+obj._source.imdbid+'</td><td>'+obj._source.name+'</td><td>'+obj._source.desc+'</td></tr>');
}
});
}
$('#count').html("Aantal resultaten: "+hitcount);
});
}
I am searching data then putting it in a table, works fine. But the highlighting is not working at all. Please help me out!
I was having this same problem, and it turns out that when you specify the highlight parameter, elasticsearch returns not only the '_source' fields, but also 'highlight' fields. Upon further inspection, the ES docs seem to confirm this:
there will be another element in each search hit, called highlight, which includes the highlighted fields and the highlighted fragments
So, to get this working, you'd need to swap '_source' for 'highlight' in your code:
<td>'+obj.highlight.name+'</td>
I also found that ES also puts the highlight response in square brackets, so in my case, (using AngularJS) I accessed the value as follows:
// ...ng-repeat=result in results...
<p ng-bind-html="result.highlight.body[0]">{{result.highlight.body[0]}}</p>
that simplier version works for me too, elasticsearch 5, node 8.7, elasticsearch.js node module:
response = await client.search({
index: indexName,
type: indexType,
q: query,
highlight: {
fields: {
"*": {
"pre_tags": ["<b>"],
"post_tags": ["</b>"]
}
}
}
}
Working version for ES 2.2.
In the highlight section of the query use
require_field_match: false,
function search(searchInput){
emptyTable();
client.search({
index: 'movies',
size: 5,
body: {
query: {
//match: {_all: searchInput}
term: {
_all: searchText
}
},
highlight: {
require_field_match: false,
fields: {
"*": {
"pre_tags": [
"<b>"
],
"post_tags": [
"</b>"
]
}
}
}
}
}).then(function (resp) {
var hits = resp.hits.hits;
var hitcount = resp.hits.total;
if(!jQuery.isEmptyObject(hits)){
console.log(hits);
$.each(hits, function(key,obj) {
if(key%2==0){
// All highlight fields here...
$('#table').append('<tr><td>'+obj.highlight.imdbid+'</td><td>'+obj.highlight.name+'</td><td>'+obj.highlight.desc+'</td></tr>');
}else{
$('#table').append('<tr class="even"><td>'+obj._source.imdbid+'</td><td>'+obj._source.name+'</td><td>'+obj._source.desc+'</td></tr>');
}
});
}
$('#count').html("Aantal resultaten: "+hitcount);
});
}

Categories