⏳
8 mins
read time
The objective of this project is to make an simple application with some popular libraries such as nodejs and angularjs. I chose this project because I have never use these technologies before and I thought that it was a good opportunity to try them.
The application should present a list of ingredients that can be selected to make magic potions. Each magic potion is made with three different ingredients and they are shown in the application. The ingredients have a defined quantity and the application will do magic potions only when there are available ingredients.
AngularJS is great for declaring static documents. It has a data binding to update the view whenever the model changes, as well as updating the model whenever the view changes. This avoid the need to refresh the page in order to see the changes. I use it in the front end to access all the data and render the different views and changes of the app. NodeJS is used on the server side to execute JavaScript code.
I use the following files for this application:
core.js
contains the angularjs moduleindex.html
contains the external webpagepackage.json
contains the npm
node modules used for the application.server.js
include all javascript code executed using nodejs. I use express.js to create a small server which sends the data to the index.html
file in the front-end.├── public
│ ├── core.js
│ └── index.html
├── package.json
├── data.json
└── server.js
Inside server.js
I define a JSON
object with the data needed for my alchemy machine such as:
url
from an image of the ingredient."Ingredients": [
{"text": "Avocado", "emoji" : "🥑", "selected" :false, "quantity": 3, "image": "https://cdn.authoritynutrition.com/wp-content/uploads/2014/09/avocadoo-sliced-in-half.jpg"},
{"text": "Watermellon", "emoji" : "🍉", "selected" :false, "quantity": 2, "image": "http://www.pvfarms.com/assets/img/ourproduce_watermelon.png"},
{"text": "Jalapeno", "emoji" : "🌶", "selected" :false, "quantity": 3, "image": "http://images.realfoodtoronto.com/D.cache.large/Jalapeno-Pepper.jpg"},
{"text": "Pineapple", "emoji" : "🍍", "selected" :false, "quantity": 2, "image": "http://www.cuisine-de-bebe.com/wp-content/uploads/lananas.jpg"},
{"text": "Kiwi", "emoji" : "🥝", "selected" :false, "quantity": 5, "image": "http://media.mercola.com/assets/assets/img/foodfacts/kiwifruit-nutrition--facts.jpg"},
{"text": "Strawberry", "emoji" : "🍓", "selected" :false, "quantity": 2, "image": "http://maviedemamanlouve.com/wp-content/uploads/2015/10/fraise-1.jpg"},
{"text": "Lemon", "emoji" : "🍋", "selected" :false, "quantity": 4, "image": "https://realfood.tesco.com/media/assets/img/Lemon-easter-biscuits-hero-1d74c01d-8906-45fe-8135-322f0520c434-0-472x310.jpg"},
{"text": "Banana", "emoji" : "🍌", "selected" :false, "quantity": 2, "image": "http://www.granini.com/data/assets/img/fruit_images/full/banana.png"}
],
"Recipes":[
{"name" : "Power", "quantity" : 0, "ingredients" : ["Avocado", "Jalapeno", "Pineapple"]},
{"name" : "Invisibility", "quantity" : 0, "ingredients" : ["Pineapple", "Kiwi", "Lemon" ]},
{"name" : "Agility", "quantity" : 0, "ingredients" : ["Banana", "Watermellon", "Strawberry"]}
],
"Magic" : [{"value": false, "name": "empty"}]};
The file core.js
contains the module alchemyMachine
, the controller mainController
and the functions to handle the actions inside the application. There are two main functions:
get ('/api/donnes')
that hit the node API to obtain all the data from the route /api/donnes/
and bind it to $scope.todos
.post ('/api/donnes/mix
that sends that hit the /api/donnes/mix
to create a potion with the selected ingredients.var alchemyMachine = angular.module('alchemyMachine', []);
function mainController($scope, $http) {
// when landing on the page, get all info and show them
$http.get('/api/donnes')
.success(function(data) {
$scope.info = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
// hits the route to make a potion
$scope.createPotion = function() {
$http.post('/api/donnes/mix', $scope.info)
.success(function(data) {
$scope.formData = {};
$scope.info = data;
console.log(data);
})
.error(function(data) {
console.log('Error: ' + data);
});
};
}
The server.js
file exposes the application at http://localhost:8080
// listen (start app with node server.js) ======================================
app.listen(8080);
console.log("Please see: http://localhost:8080/");
The file server.js
also contains the routes to handle the API calls. The app.get
call send the data to the route /api/donnes
to be access then by the front-end. It means that JSON data can be viewed in the explorer at localhost:8080/api/donnes
. The app.post
call interacts with the server to calculate if the selected ingredients are part of a magic potion recipe. First it loops the ingredients list to obtain the selected ones and after that it loops the recipes list to see if the selected ingredients are contained in the recipes. If the ingredients are part of a magic potion:
At the end the app.post
call saves the modifications of the data inside file data.json
.
// get all info
app.get('/api/donnes', function(req, res) {
res.json(Data); // return all info in JSON format
});
// create a potion
app.post('/api/donnes/mix', function(req, res) {
var mix = [];
var mixposition = [];
req.body.Magic[0].value = false;
for(i=0;i<req.body.Ingredients.length;i++) {
if (req.body['Ingredients'][i]["selected"] && req.body['Ingredients'][i]["quantity"] > 0) {
mix.push(req.body['Ingredients'][i]["text"]);
mixposition.push(i);
}
}
for(i=0;i<req.body.Recipes.length;i++) {
if ( _.isEqual(req.body.Recipes[i]["ingredients"].sort(), mix.sort()) ) {
req.body.Magic[0].value = true;
req.body.Magic[0].name = req.body.Recipes[i].name;
req.body.Recipes[i].quantity++;
for(i=0;i<mix.length;i++) {
req.body['Ingredients'][mixposition[i]].quantity--;
}
}
}
fs.writeFile('data.json', JSON.stringify(req.body));
res.json(req.body);
});
The server.js
file exposes the application using the file index.html
.
// application -------------------------------------------------------------
app.get('*', function(req, res) {
res.sendfile('./public/index.html'); // load the single view file (angular will handle the page changes on the front-end)
});
The index.html
file interacts directly with angularjs using the module and the controller defined before. It shows:
<!-- ASSIGN OUR ANGULAR MODULE -->
<html ng-app="alchemyMachine">
<head>
<!-- META -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->
<title>The Alchemy Machine</title>
<!-- SCROLLS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"><!-- load bootstrap -->
<style>
#inline_images { display: inline-block;}
html { overflow-y:scroll; }
body { padding-top:50px; }
#ingredient-list { margin-bottom:30px; }
</style>
<!-- SPELLS -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->
<script src="core.js"></script>
</head>
<!-- SET THE CONTROLLER AND GET ALL INFO -->
<body ng-controller="mainController">
<div class="container">
<div class="text-center">
<h1> The alchemy Machine </h1>
</div>
<div id="inline_images" ng-repeat="todo in info.Ingredients">
<img ng-src="{{todo.image}}" width=100 height=100/>
</div>
<!-- INGREDIENT AND POTION LIST -->
<div id="ingredient-list" class="row">
<div class="col-md-4">
<div ng-show="info.Recipes">
<h3> 💊 Potions made </h3>
</div>
<li ng-repeat="w in info.Recipes">
{{w.name}}: {{w.quantity}}
</li>
</div>
<div class="col-md-4">
<h3> 🗃 Available ingredients </h3>
<!-- LOOP OVER THE DATA IN $scope.info -->
<div class="checkbox" ng-repeat="todo in info.Ingredients">
<label>
<input type="checkbox" ng-model="todo.selected" >{{ todo.text }} {{ todo.emoji.repeat(todo.quantity) }}
</label>
</div>
<button type="submit" class="btn btn-primary btn-lg" ng-click="createPotion()">Mix</button>
</div>
<div class="col-md-4">
<h3> 📖 Recipes </h3>
<li ng-repeat="w in info.Recipes">
{{w.name}}: <i ng-repeat="e in w.ingredients">{{e}},</i>
</li>
<div ng-show="info.Magic[0].value">
<h1>Potion: {{ info.Magic[0].name }} ! </h1>
</div>
</div>
</div>
</div>
</body>
</html>
In the following gif, I test the main functions of the application: selecting ingridients to make magic potions, see how the number of ingridients decreases while I am making potions and also that I not able to do magic potions when I run out of the ingridients.
The application can be found at my github. To get it all up and running:
Microcontrollers are adapted for simple task which don't need a lot of battery power. This post shows how to put in actions the Arduino MKR Fox for capturing temperature measurements, send them to Sigfox Cloud and expose the results in a serverless dashboard.
Imagine having interactive Christmas lights! This article will show you how to control lights with your Raspberry Pi and adding voice command superpowers.
An ESP8266 measures temperature and uses the Sigfox network to send the measurements into a web server to monitor a compost pile.
I'm tired to search for my phone to put/change/stop music. Instead I use snips technology to control music using voice (radio, podcast, files)