Can technology help you make better food decisions?
My first few blog posts of the new year will focus on the high-tech tools that might assist in keeping some classic New Year's resolutions. If you missed the first one on vaping and smoking cessation please check it out! Also, this article serves as part 2 to my IoT Smart Pantry project from September.
-Nick
Last year I wrote about a project that I was working on for organizing foodstuffs and tracking their nutritional value. That post was mainly about the hardware build so this time around, I want to talk more about the software. You may remember I was planning to use the Nutritionix API to search product UPCs and return their nutrition facts. Let's hack together a little javascript to interact with the Nutritionix service and maybe we can start building a nice display in the process.
Looking into the Nutritionix docs, I found out that I could sign up for free and request up to 50 UPC searches per day. In order to do that, I had to sign up for an application ID which was pretty simple. After my first sign in, I was presented with this page:
This is all of the info that I need to start poking at the API. According to the UPC Scanning quick-start guide, UPC searches are performed using a GET request formatted as follows:
And when I do that, I should get a response that looks something like this:
{
"item_id": "51c3d78797c3e6d8d3b546cf",
"item_name": "Cola, Cherry",
"brand_id": "51db3801176fe9790a89ae0b",
"brand_name": "Coke",
"item_description": "Cherry",
"updated_at": "2013-07-09T00:00:46.000Z",
"nf_ingredient_statement": "Carbonated Water, High Fructose Corn Syrup and/or Sucrose, Caramel Color, Phosphoric Acid, Natural Flavors, Caffeine.",
"nf_calories": 100,
"nf_calories_from_fat": 0,
"nf_total_fat": 0,
"nf_saturated_fat": null,
"nf_cholesterol": null,
"nf_sodium": 25,
"nf_total_carbohydrate": 28,
"nf_dietary_fiber": null,
"nf_sugars": 28,
"nf_protein": 0,
"nf_vitamin_a_dv": 0,
"nf_vitamin_c_dv": 0,
"nf_calcium_dv": 0,
"nf_iron_dv": 0,
"nf_servings_per_container": 6,
"nf_serving_size_qty": 8,
"nf_serving_size_unit": "fl oz",
}
So, make a GET request and get a JSON response with all of the product information neatly organized – perfect! This is going to be easy to handle with Javascript! Let's start by putting a few basic inputs on a web page and use them to make the GET request to Nutritionix. Ideally, I'd like to be able to enter the UPC of a product into a text box and view the JSON report that comes back; that means I'll at least need a text input, a button and a text field. The HTML looks something like this:
<input name="upc"/>
<input type="button" name="search" value="Food Me"/><br/>
<textarea name="results" style="width: 222px; height: 350px;"></textarea>
I couldn't help but style things a little bit, but the basics are all there: a few elements with unique names so we can script them, and a clearly labeled button. Now that we have our page built, let's add script to do the work. At the top of my Javascript, I'm going to declare two constants: appKEY and appID. This way I can reference them throughout this post without giving away the actual keys. The next order of business is to actually construct our GET request.
In Javascript, it's best practice to write asynchronous code so that if a function takes longer than expected to execute, it doesn't disrupt the user experience or produce unexpected results. In our case, that means we want our script to send the request and then go on managing other things until the response comes back from Nutritionix. The way we achieve this is by using callbacks. Callbacks, or callback functions, are essentially just a way of telling one function to hand off to another function when the first is finished. You can set a callback in Javascript by passing the name of your callback function as the "callback" parameter of another function. I know it can sound confusing, but have a look at our code below:
function upcGet(upc, callback)
{
var getURL = "https://api.nutritionix.com/v1_1/item?upc=" + upc + "&appId=" + appID + "&appKey=" + appKEY;
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(xmlHttp.responseText);
}
xmlHttp.open("GET", getURL, true); // true for asynchronous
xmlHttp.send(null);
}
function postResults(response)
{
$("[name='results']").html(response);
}
In this case postResult() is our callback function, and all it does is find the element with the name "results" and change the html inside that element to match the variable called "response," which gets passed to it. The function called upcGet() is the function that we expect to take some time. You can see that we're passing two parameters to upcGet(): upc and callback. The parameter named upc is what we'll use to pass in the contents of the upc entry field; the "callback" parameter will be used to pass the callback function. In this context, whenever we call the function callback(), it actually calls whatever function name that we passed in as the "callback" parameter.
The first line of the code in upcGet() simply concatenates a bunch of strings together to format a valid GET request. Included are our variables for upc, appID and appKEY. Next, we create an object called "xmlHttp" to represent a new Http request. Then we set up an event handler, so that we can tell the script what to do when the ready state of the request changes. In this case, we check to see if we got a good response and we launch our callback function. Now that our event listener in ready to catch the response, we can finally launch our request! We do this using the .open method.
The last thing to do is to make the button kick off this process:
$(document).on("click", "[name='search']", function(){
upcGet($("[name='upc']").val(), postResults);
})
Here we're just using the JQuery .on() method to attach an event to our button. When the button is clicked, the upcGet() function will get called with the value of our text input named "upc" as the first argument, and the name of our callback function as the second argument. When we spin this all up and enter a UPC, it looks a little like this:
Not a bad start - but that JSON response looks like a real mess. What if I just want to pick a few values from this list and display them? Well, first we need to modify our form so that there are fields for each of the line items that we want to isolate:
<input name="upc"/>
<input type="button" name="search" value="Food Me"/><br/><br/>
<textarea name="calories" rows=1 style="width: 153px;"></textarea> Calories<br/>
<textarea name="fat" rows=1 style="width: 153px;"></textarea> Fat<br/>
<textarea name="sodium" rows=1 style="width: 153px;"></textarea> Sodium<br/>
<textarea name="sugars" rows=1 style="width: 153px;"></textarea> Sugars<br/>
As you can see, we now have a text area for each of four separate values: calories, total fat, sodium and sugars. That's easy enough, but the real challenge is teasing out each of those values and slotting them into those fields... or is it? Since our response is received in JSON format, it's actually trivial to pick particular values out of the list by using the JSON.parse() method. We just need to make a few minor adjustments to our postResults() function:
function postResults(response)
{
var calories;
var fat;
var sodium;
var sugars;
var foodItem = JSON.parse(response);
$("[name='calories']").html(foodItem.nf_calories);
$("[name='fat']").html(foodItem.nf_total_fat);
$("[name='sodium']").html(foodItem.nf_sodium);
$("[name='sugars']").html(foodItem.nf_sugars);
}
Notice how by creating a new object called "foodItem" to contain the parsed JSON data, I was able to reference each of the values by appending the key name to my foodItem object. The key-value pairs in our JSON document were turned into a Javascript object such that, in this case, foodItem.nf_calories is now equal to the number of calories in the food item. And now our page looks like this:
Alright, now our page really does something. But before we call it a day, why not make it look pretty? We can replace the boring text area fields with flashy dials using the jQuery-Knob library. All we need to do is replace our text area tags with input tags of the class "dial." But there's a little more styling we can do; here's what the markup ended up looking like:
<div style="text-align: center;">
Enter a UPC
</div>
<div style="text-align: center;">
<input name="upc"/>
</div>
<br/>
<div style="text-align: center;" name="productName">
</div><br/>
<div style="text-align: center;">
<div style="text-align: center; display: inline-block;">
Calories<br/>
<input type="text" name="calories" class="dial" data-min="0" data-max="500" data-fgColor="#66CC66" data-angleOffset="-125" data-angleArc="250" data-readOnly="true" data-width="100">
</div>
<div style="text-align: center; display: inline-block;">
Fat
<br/>
<input type="text" name="fat" class="dial" data-min="0" data-max="500" data-fgColor="#66CC66" data-angleOffset="-125" data-angleArc="250" data-readOnly="true" data-width="100">
</div>
<div style="text-align: center; display: inline-block;">
Sodium
<br/>
<input type="text" name="sodium" class="dial" data-min="0" data-max="500" data-fgColor="#66CC66" data-angleOffset="-125" data-angleArc="250" data-readOnly="true" data-width="100">
</div>
<div style="text-align: center; display: inline-block;">
Sugars
<br/>
<input type="text" name="sugars" class="dial" data-min="0" data-max="500" data-fgColor="#66CC66" data-angleOffset="-125" data-angleArc="250" data-readOnly="true" data-width="100">
</div>
</div>
Part of the reason that this seems like a lot more markup (even though it really isn't) is because the JQuery Knob library makes liberal use of data-* attributes in order to style the dial elements. I've also added the "productName" block, deleted the search button and wrapped everything into a number of other blocks to better organize it. Once the library itself has been included in the page, there isn't a lot of code modification that has to happen. First, removing the search button means I have to trigger the getUPC function some other way. Personally, I like when you can hit the 'Enter' key on a form:
$(document).on("keydown", "[name='upc']", function(e){
console.log(e);
if (e.keyCode === 13) {
upcGet($("[name='upc']").val(), postResults);
}
});
In this chunk of code, all I'm doing is checking every time a key is pressed in the upc field, whether the key that's pressed is the 'Enter' key. When it is, I launch my function. The next thing to do is to initialize the JQuery Knobs:
$(function() {
$(".dial").knob();
});
$('.dial')
.val(0)
.trigger('change');
This is just attaching the hooks from my .dial class elements to the Knob library. Then I set all of them to 0 so they have a consistent look. Finally, we make a minor change to the postResults() function:
function postResults(response)
{
var calories;
var fat;
var sodium;
var sugars;
var foodItem = JSON.parse(response);
$("[name='calories']").val(foodItem.nf_calories);
$("[name='calories']").trigger('change');
$("[name='fat']").val(foodItem.nf_total_fat);
$("[name='fat']").trigger('change');
$("[name='sodium']").val(foodItem.nf_sodium);
$("[name='sodium']").trigger('change');
$("[name='sugars']").val(foodItem.nf_sugars);
$("[name='sugars']").trigger('change');
$("[name='productName']").html(foodItem.item_name);
}
Now, instead of posting the results to a number of text fields, we're posting the results to our newly created dial elements. We're also extracting the product name from the JSON and displaying it in the productName block. We can further customize the look of our page by adding a stylesheet:
@font-face {
font-family: 'Varela Round';
font-style: normal;
font-weight: 400;
src: local('Varela Round Regular'), local('VarelaRound-Regular'), url(https://fonts.gstatic.com/s/varelaround/v8/APH4jr0uSos5wiut5cpjrugdm0LZdjqr5-oayXSOefg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
*{
font-family: 'Varela Round';
font-size: 110%;
color: #66CC66;
background-color: white;
}
input[name="upc"]:focus,
input[name="upc"]{
background-color: transparent;
border: solid;
border-width: 0 0 5px 0;
outline: 0;
-webkit-box-shadow: none;
box-shadow: none;
text-align: center;
}
This stylesheet adds a Google font, removes the borders from our upc field and changes the color of a few elements. Let's see what the page looks like now:
I'd say we know pretty well how to interact with the Nutritionix API now! My next step is going to be installing nodeJS on a Raspberry Pi and integrating our little page with input from the hardware. I'll dig into this in the next installment of this project but in the meantime, I'd be happy to talk Javascript in the comments section! We sometimes gloss over web programming here on the SparkFun blog, but with IoT rising in popularity, it's going to become more and more important even to makers who stick to embedded platforms. What are your feelings about Javascript/nodeJS? Is it the new hotness, or just another language/toolset?