Using typeahead.js with Json - javascript

I'm following this link which is a great clear blog post about typeahead.js with Json. However, I'm having problems with it and can't figure out where I'm going wrong.
Here is my js:
<script type="text/javascript">
$('#Search').typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
name: 'states',
displayKey: 'stateName',
source: function (query, process) {
states = [];
map = {};
var data = [
{ "stateCode": "CA", "stateName": "California" },
{ "stateCode": "AZ", "stateName": "Arizona" },
{ "stateCode": "NY", "stateName": "New York" },
{ "stateCode": "NV", "stateName": "Nevada" },
{ "stateCode": "OH", "stateName": "Ohio" }
];
$.each(data, function (i, state) {
map[state.stateName] = state;
states.push(state.stateName);
});
process(states);
},
updater: function (item) {
selectedState = map[item].stateCode;
return item;
}
});
</script>
When I type in the input control all the results come back as undefined. I think it's something to do with the displayKey and I've tried setting it to state.stateName but that results in the same problem. Maybe I'm looking in the wrong area?
I've setup a plnkr.co demo here.
Thanks for reading.
Paul

You need to change your states.push row, in your plunker it is on row 45.
Typeahead expects results in JSON format, now you are just pushing results.
Change row 45 to:
states.push({stateName : state.stateName});
Now your displayKey: "stateName" (which is the key of JSON formatted data) will exist and it will show the name of the state as a result.

Related

How to get unmodified json data for column of datatable

I am working on a project using Laravel and yajrabix/laravel-datatables. I am having an issue when trying to access a column using columndefs. The column is supposed to be JSON data. Nothing is working to work with the data in that column. Is there a way to send the data unmodified for that column?
Bottom line, I would like to be able to access the data from the json stored in the results column. No matter what I do, it doesnt work.
Here is the code in my blade view. Everything else works on the datatable.
<script type="text/javascript">
$(function () {
var table = $('.table').DataTable({
processing: true,
serverSide: false,
ajax: "/admin/logs/datatable/lookup-ip",
columns: [
{data: 'id', name: 'id'},
{data: 'ip_address', name: 'ip_address'},
{data: 'results', name: 'results'},
{data: 'created_by', name: 'created_by'},
{data: 'created_at', name: 'created_at'},
],
columnDefs: [
{
targets: "_all",
className: 'nk-tb-col tb-col-mb'
},
{
targets: 2,
render: function (data, type, row, meta) {
return 'ISP: ' + data.ip;
}
}
],
});
});
</script>
This is the function in the controller serving the datatable.
public function datatable($log)
{
switch($log)
{
case 'activity':
$table = config('activitylog.table_name');
break;
case 'lookup-ip':
$table = IPLookup::getModel()->getTable();
break;
case 'lookup-phone':
$table = PhoneLookup::getModel()->getTable();
break;
}
$query = DB::table($table);
return DataTables::of($query)->toJson();
}
This is the data that is stored in the database.
{"ip": "8.8.8.8", "asn": "AS15169", "isp": "Google LLC", "org": "Google LLC", "city": "Ashburn", "type": "IPv4", "region": "Virginia", "country": "United States", "success": true, "currency": "US Dollar", "latitude": "39.0437567", "timezone": "America/New_York", "continent": "North America", "longitude": "-77.4874416", "country_code": "US", "country_flag": "https://cdn.ipwhois.io/flags/us.svg", "timezone_gmt": "GMT -5:00", "country_phone": "+1", "currency_code": "USD", "timezone_name": "Eastern Standard Time", "continent_code": "NA", "currency_rates": "1", "country_capital": "Washington", "currency_plural": "US dollars", "currency_symbol": "$", "completed_requests": 29, "country_neighbours": "CA,MX,CU", "timezone_dstOffset": "0", "timezone_gmtOffset": "-18000"}
The model of the IP log has casts setup.
protected $casts = [
'results' => 'array',
];
Okay so it figures after posting the question... I figure it out.
According to yajrabox:
By default, Laravel DataTables protects us from XSS attack by escaping all our outputs. In cases where you want to render an html content, please use rawColumns api.
So I modified the code in the controller returning the datatable and now it works as expected and I can parse the JSON and use it as an object in javascript.
return DataTables::of($query)
->rawColumns(['results'])
->toJson();
One thing I've notice is that the data stored on your database, according to your example, isn't wrapped into the array brackets ([ ]) and you're using a cast on the Module. That could be the cause of the problem.
Try going to your database and manually wrapping your JSON on array brackets and give it a shot!

Accessing Key Value Pair in JSON data results

I haven't needed to access JSON data so I'm a bit stumped. I've validated the data using a JSON formatter and it is valid so I know that is good.
All I'm trying to do is retrieve the data and populate a select box (specifically x-editable).
In there example, they provide:
$('#id').editable({
source: [
{value: 1, text: 'Male'},
{value: 2, text: 'Female'}
]
});
Yes, I could throw the 50 states in there but I'd like to use this same approach for other select boxes I plan on implementing.
Here is the datafeed I'm currently getting back from my JSON call:
{
"data": [{
"stateID": 1,
"stateAbbr": "AL",
"state": "Alabama"
}, {
"stateID": 2,
"stateAbbr": "AK",
"state": "Alaska"
}, {
"stateID": 3,
"stateAbbr": "AZ",
"state": "Arizona"
}, {
"stateID": 51,
"stateAbbr": "WY",
"state": "Wyoming"
}]}
My function snippet that I'm working with is:
$.getJSON("test.html?format=json",function(data){
statesArr = statesArr.concat(data);
});
Ideally, I'd like to make the statesArr look like this:
source: [
{value: 1, text: 'Alabama'},
{value: 2, text: 'Alaska'},
...
{value:50, text: 'Wyoming'}
]
But I'm at a loss on what to do from here. The format of the data feed, {"data is throwing me off. I'm used to getting data pretty much like X-Editable is looking for.
I am assuming here that resp is the response. And resp has data property. If I have misunderstood you, please tell me so.
var statesArr;
$.getJSON('test.html?format=json', function(resp){
statesArr = resp.data.map(function(state){
return {
value: state.stateID,
text: state.state
}
});
});
/* somewhere else later; make sure that statesArr is set */
$('#id').editable({
source: statesArr
});

How can I filter results in typeahead.js using a second variable?

I am trying to filter results using Typeahead.js. I can currently filter the results using a field called activity_title. This works fine.
How can I filter my results by a second value? In this case, I would like to select only the results that have a certain value for activity_level. I need to set this when the typeahead is initialised rather than hard coding it into the Bloodhound initialisation (e.g. I don't want to use url: 'api/activity/&range=1,3')
I have the following valid JSON that I access remotely:
{
"meta": [
{
"name": "activity_id",
"table": "table",
"max_length": 4
},
{
"name": "activity_title",
"table": "table",
"max_length": 91
},
{
"name": "activity_level",
"table": "table",
"max_length": 2
}
],
"detail": [
{
"activity_id": "57",
"activity_title": "Help old ladies to cross the road.",
"activity_level": "2"
},
{
"activity_id": "58",
"activity_title": "Help mum with the washing up.",
"activity_level": "3"
},
{
"activity_id": "59",
"activity_title": "Shine my shoes",
"activity_level": "1"
},
{
"activity_id": "60",
"activity_title": "Put the bins out",
"activity_level": "1"
}
]
}
I set up a Bloodhound instance like this:
var activities = new Bloodhound({
datumTokenizer: function (datum) {
return Bloodhound.tokenizers.whitespace(datum.activity_title);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: {
url: '/api/activity/',
filter: function(data) {
return $.map(data['detail'], function(detail) {
return {
activity_id: detail.activity_id,
activity_title: detail.activity_title,
objective_level: detail.objective_level
};
});
}
}
});
I use Typeahead.js to do a lookup on the data as I type.
$( document ).on( "focus", ".typeahead-init", function() {
// + '&range=' + minimum + ',' + maximum
var minimum = $('#group-level-min-1').val();
var maximum = $('#group-level-max-1').val();
$(this).typeahead({
highlight: true
},
{
name: 'activity_title',
displayKey: 'activity',
source: activities.ttAdapter(),
templates: {
header: '<div class="header-name">Activities</div>',
empty: [
'<div class="empty-message">',
'No activities match your search',
'</div>'
].join('\n'),
suggestion: Handlebars.compile('<div class="typeahead-activity" id="typeahead-activity-{{activity_id}}"><strong>{{objective_level}}</strong> - {{activity_title}}</div>')
}
})
//info on binding selection at https://github.com/twitter/typeahead.js/issues/300
.bind('typeahead:selected', function(obj, datum, name) {
var target = $(this).closest('.activity-container');
var activityId = datum['activity_id'];
var url = '/api/activity/id/'+activityId;
$(target).children('.activity-id').val(activityId);
//http://runnable.com/UllA9u8MD5wiAACj/how-to-combine-json-with-handlebars-js-for-javascript-ajax-and-jquery
var raw_template = $('#activity-output').html();
// Compile that into an handlebars template
var template = Handlebars.compile(raw_template);
// Fetch all data from server in JSON
$.get(url,function(data,status,xhr){
$.each(data,function(index,element){
// Generate the HTML for each post
var html = template(element);
// Render the posts into the page
target.append(html);
});
});
});
$(this).removeClass("typeahead-init");
$(this).focus();
});
This has been cobbled together from several answers on Stackoverflow and others. Any help greatly appreciated.

Use different value from JSON data instead of displayKey using Typeahead

I have started using Typeahead.js and am struggling to figure out a way of allowing a user to type and search for a company name, once selected input the associated company code.
.json file:
[{
"company_name": "Facebook",
"code": "fb",
}, {
"company_name": "Google",
"code": "goog",
}, {
"company_name": "Yahoo",
"code": "yhoo",
}, {
"company_name": "Apple",
"code": "aapl",
}, {
"company_name": "Royal Mail",
"code": "rmg.l",
}]
.js Script:
var stocks = new Bloodhound({
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.code);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
prefetch: {
url: 'javascripts/stockCodes.json',
filter: function(list) {
return $.map(list, function(stock) {
return {
code: stock
};
});
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'code',
source: stocks.ttAdapter()
});
Currently, this just displays the list of codes when the user types in the input field. However, I would like to know if there is a way to allow them to search on code but once selected, the value in the textbox to be company_name? Is this even possible using this plugin. Any help will be greatly appreciated.
Thanks!
displayKey is used to indicate which key should be shown in the dropdown list and in the input field after the user has selected an entry.
If you want the entries shown in the dropdown list to be different from what ends up in the input field you need to use a custom template.
From the documentation:
suggestion – Used to render a single suggestion. If set, this has to be a
precompiled template. The associated suggestion object will serve as the
context. Defaults to the value of displayKey wrapped in a p tag i.e.
<p>{{value}}</p>.
Something like this should work:
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: 'company_name',
source: stocks.ttAdapter(),
templates: {
suggestion: function (stock) {
return '<p>' + stock.code + '</p>';
}
}
});
The submitted template will be used to show stock.code in the dropdown list, while stock.company_name will be shown in the input field after the user selects an entry.
I am no jQuery / JavaScript-Guru. So I prefer it the easy way. Just to understand what I did when I look at my code later...
Thanks to Eric Saupe I found a smart solution:
//JSON FILE
[
{
"company_name": "Facebook",
"code": "fb",
},
{
"company_name": "Google",
"code": "goog",
},
{
"company_name": "Yahoo",
"code": "yhoo",
},
{
"company_name": "Apple",
"code": "aapl",
},
{
"company_name": "Royal Mail",
"code": "rmg.l",
},
]
// JS SCRIPT
var stocks = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('company_name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: 'javascripts/stockCodes.json'
});
stocks.initialize();
$('.typeahead').typeahead(
null, {
name: 'stocks',
displayKey: 'company_name',
source: stocks.ttAdapter()
}).on('typeahead:selected', function(event, data){
$('.typeahead').val(data.code);
});
Just use the typeahead custom events as described in the docs on github.
Hope, this helps (and when it's just for my own later reference).
JSFIDDLE
You need to use the ´displayKey´ variable. See below copy-paste from the typeahead docs:
displayKey – For a given suggestion object, determines the string representation of it. This will be used when setting the value of the input control after a suggestion is selected. Can be either a key string or a function that transforms a suggestion object into a string. Defaults to value.
https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md
Your specific code would then be something like this:
var stocks = new Bloodhound({
datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.code); },
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
prefetch: {
url: 'javascripts/stockCodes.json',
filter: function(list) {
// This should not be required, but I have left it incase you still need some sort of filtering on your server response
return $.map(list, function(stock) { return { code: stock.code, company_name: stock.company_name }; });
}
}
});
stocks.initialize();
$('.typeahead').typeahead(null, {
name: 'stocks',
displayKey: function(stock) {
return stock.company_name;
},
source: stocks.ttAdapter()
});
I had a very similar requirement on my own site, and I had this working with no issue. I have not tried this particular example though, so let me know if it works.
Remember to call stocks.clearPrefetchCache(); to clear your cache to easier track bugs.
If I read correctly, I believe this is what you want:
var stocksData = [{
"Facebook": "fb",
}, {
"Google": "goog",
}, {
"Yahoo": "yhoo",
}, {
"Apple": "aapl",
}, {
"Royal Mail": "rmg.l",
}, ];
var stocks = new Bloodhound({
datumTokenizer: function (d) {
for (var prop in d) {
return Bloodhound.tokenizers.whitespace(d[prop]);
}
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
limit: 3,
local: stocksData,
});
stocks.initialize();
$('input').typeahead(null, {
name: 'stocks',
displayKey: function(stocks) {
for (var prop in stocks) {
return prop;
}
},
source: stocks.ttAdapter()
});
You will of course want to change the local to prefetch/remote like you had for the json file.
UPDATE
FIDDLE
Had the same problem, could not get it working with the display or displayKey property. Worked around it by adding the name property to the source objects:
$.each(stocks, function (i, item) {
item.name = item.company_name;
});
Add the value that need to be shown in the display parameter:
$(".comp-search").typeahead({
hint: true,
highlight: true,
minLength: 1,
displayKey: 'company_name',
}, {
source: engine.ttAdapter(),
templates: {
empty: ['<div class="list-group search-results-dropdown"><div class="list-group-item">Nothing found.</div></div>'],
header: ['<div class="list-group search-results-dropdown">'],
suggestion: function (data) {
return '' + data.company_name + '';
}
},
display: function(data) { return data.company_name; }
});
Hope this is helpful

Complex JSON nesting of objects and arrays

I am having difficultly with syntax and structure of JSON objects/arrays.
{
"accounting" : [
{ "firstName" : "John",
"lastName" : "Doe",
"age" : 23 },
{ "firstName" : "Mary",
"lastName" : "Smith",
"age" : 32 }
],
"sales" : [
{ "firstName" : "Sally",
"lastName" : "Green",
"age" : 27 },
{ "firstName" : "Jim",
"lastName" : "Galley",
"age" : 41 }
]
}
I want to make a nested structure of objects and arrays that would house the following info:
{
"problems": [{
"Diabetes":[{
"medications":[{
"medicationsClasses":[{
"className":[{
"associatedDrug":[{
"name":"asprin",
"dose":"",
"strength":"500 mg"
}],
"associatedDrug#2":[{
"name":"somethingElse",
"dose":"",
"strength":"500 mg"
}]
}],
"className2":[{
"associatedDrug":[{
"name":"asprin",
"dose":"",
"strength":"500 mg"
}],
"associatedDrug#2":[{
"name":"somethingElse",
"dose":"",
"strength":"500 mg"
}]
}]
}]
}],
"labs":[{
"missing_field": "missing_value"
}]
}],
"Asthma":[{}]
}]}
But I have no idea what the right way to do this should be. Should I just be making JavaScript objects? Does JSON make sense for this project?
What is the correct syntax to set something like this up?
Here is my code so far:
$(document).ready(function() {
$.getJSON('js/orders.json', function(json) {
$.each(json.problems, function(index, order) {
$('.loadMeds').append('<p>' + order.name + '</p>')
});
});
});
The first code is an example of Javascript code, which is similar, however not JSON. JSON would not have 1) comments and 2) the var keyword
You don't have any comments in your JSON, but you should remove the var and start like this:
orders: {
The [{}] notation means "object in an array" and is not what you need everywhere. It is not an error, but it's too complicated for some purposes. AssociatedDrug should work well as an object:
"associatedDrug": {
"name":"asprin",
"dose":"",
"strength":"500 mg"
}
Also, the empty object labs should be filled with something.
Other than that, your code is okay. You can either paste it into javascript, or use the JSON.parse() method, or any other parsing method (please don't use eval)
Update 2 answered:
obj.problems[0].Diabetes[0].medications[0].medicationsClasses[0].className[0].associatedDrug[0].name
returns 'aspirin'. It is however better suited for foreaches everywhere
I successfully solved my problem. Here is my code:
The complex JSON object:
{
"medications":[{
"aceInhibitors":[{
"name":"lisinopril",
"strength":"10 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}],
"antianginal":[{
"name":"nitroglycerin",
"strength":"0.4 mg Sublingual Tab",
"dose":"1 tab",
"route":"SL",
"sig":"q15min PRN",
"pillCount":"#30",
"refills":"Refill 1"
}],
"anticoagulants":[{
"name":"warfarin sodium",
"strength":"3 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}],
"betaBlocker":[{
"name":"metoprolol tartrate",
"strength":"25 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}],
"diuretic":[{
"name":"furosemide",
"strength":"40 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}],
"mineral":[{
"name":"potassium chloride ER",
"strength":"10 mEq Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}]
}
],
"labs":[{
"name":"Arterial Blood Gas",
"time":"Today",
"location":"Main Hospital Lab"
},
{
"name":"BMP",
"time":"Today",
"location":"Primary Care Clinic"
},
{
"name":"BNP",
"time":"3 Weeks",
"location":"Primary Care Clinic"
},
{
"name":"BUN",
"time":"1 Year",
"location":"Primary Care Clinic"
},
{
"name":"Cardiac Enzymes",
"time":"Today",
"location":"Primary Care Clinic"
},
{
"name":"CBC",
"time":"1 Year",
"location":"Primary Care Clinic"
},
{
"name":"Creatinine",
"time":"1 Year",
"location":"Main Hospital Lab"
},
{
"name":"Electrolyte Panel",
"time":"1 Year",
"location":"Primary Care Clinic"
},
{
"name":"Glucose",
"time":"1 Year",
"location":"Main Hospital Lab"
},
{
"name":"PT/INR",
"time":"3 Weeks",
"location":"Primary Care Clinic"
},
{
"name":"PTT",
"time":"3 Weeks",
"location":"Coumadin Clinic"
},
{
"name":"TSH",
"time":"1 Year",
"location":"Primary Care Clinic"
}
],
"imaging":[{
"name":"Chest X-Ray",
"time":"Today",
"location":"Main Hospital Radiology"
},
{
"name":"Chest X-Ray",
"time":"Today",
"location":"Main Hospital Radiology"
},
{
"name":"Chest X-Ray",
"time":"Today",
"location":"Main Hospital Radiology"
}
]
}
The jQuery code to grab the data and display it on my webpage:
$(document).ready(function() {
var items = [];
$.getJSON('labOrders.json', function(json) {
$.each(json.medications, function(index, orders) {
$.each(this, function() {
$.each(this, function() {
items.push('<div class="row">'+this.name+"\t"+this.strength+"\t"+this.dose+"\t"+this.route+"\t"+this.sig+"\t"+this.pillCount+"\t"+this.refills+'</div>'+"\n");
});
});
});
$('<div>', {
"class":'loaded',
html:items.join('')
}).appendTo("body");
});
});
Make sure you follow the language definition for JSON. In your second example, the section:
"labs":[{
""
}]
Is invalid since an object must be composed of zero or more key-value pairs "a" : "b", where "b" may be any valid value. Some parsers may automatically interpret { "" } to be { "" : null }, but this is not a clearly defined case.
Also, you are using a nested array of objects [{}] quite a bit. I would only do this if:
There is no good "identifier" string for each object in the array.
There is some clear reason for having an array over a key-value for that entry.
First, choosing a data structure(xml,json,yaml) usually includes only a readability/size problem. For example
Json is very compact, but no human being can read it easily, very hard do debug,
Xml is very large, but everyone can easily read/debug it,
Yaml is in between Xml and json.
But if you want to work with Javascript heavily and/or your software makes a lot of data transfer between browser-server, you should use Json, because it is pure javascript and very compact. But don't try to write it in a string, use libraries to generate the code you needed from an object.
Hope this helps.
You can try use this function to find any object in nested nested array of arrays of kings.
Example
function findTByKeyValue (element, target){
var found = true;
for(var key in target) {
if (!element.hasOwnProperty(key) || element[key] !== target[key]) {
found = false;
break;
}
}
if(found) {
return element;
}
if(typeof(element) !== "object") {
return false;
}
for(var index in element) {
var result = findTByKeyValue(element[index],target);
if(result) {
return result;
}
}
};
findTByKeyValue(problems,{"name":"somethingElse","strength":"500 mg"}) =====> result equal to object associatedDrug#2

Categories