How I Found A Job With Node + Angular

Miron Ophir
22 min readMay 1, 2023

--

This story first appeared a series of posts in my personal blog on 2014, almost a decade ago. I repeat it here for its nostalgic value…

Part 1: Let’s Do Some Node

After 4 years in AT&T as mobile R&D group manager and system architect (working on AT&T Connect and Unified Communications products) I was leaving and looking for a new position. If you ever looked for an appealing position in the past, you must have noticed a disturbing fact: some of the more attractive positions are offered far far away (geographically speaking) from where you live. At this time in my life I was not willing to move out for a new job (the family really likes our humble neighborhood and community) nor commute to death (in here that meant no more than 1 hour travel for each direction). So my career council shared with me a web site that offered all the relevant companies in my field, with a geographical distribution information, to help me choose the “right” company for me. While this web site was a helping factor in my job search, it was annoying me for its old user interface design, and poor usability. And since I had some free time now, I decided (you know me — if there is something I love it is to learn some new stuff, and fix some broken things) to try to make a better tool for my fellow job seekers.

I was playing with some node.js and angular.js tutorials to get the hang of it for a new application I was designing, so I decided this could be a good learning experience for me — to try and build a geographically oriented job seeking utility. As I progressed with it, I captured my findings in this post, so I could share it with you. It is mainly meant for people with some programming background, who want to experience these new JavaScript technologies. Others could just stick on for the final conclusions towards the end.

I started by following some tutorials and examples I found in node.js site and in angular.js site. I decided to develop on my Windows machine so I installed node.js for Windows. It will also install npm (node packaged modules) for you.

To test that node is working on your machine, try the inevitable “Hello World” test. Open node console in your sources directory and run node (simply type “node“). you should see node prompt, then type:

var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

You can type it line by line at the console or paste all of it to the console. Once you entered all the lines, open your browser to http://localhost:1337 and you will see “Hello World” staring at you from the page. Your Windows firewall may require you to approve node, and if it does — please approve it so it will not block your application.

Now that you’ve got node running. Let’s install some modules to help us in the making of our application. One very convenient module is express. It will help us rapidly create the web server code, taking most of the boiler plate off our hands.

follow the installation instructions here — in your sources directory create the file :

{
"name": "hello-world",
"description": "hello world test app",
"version": "0.0.1",
"private": true,
"dependencies": {
"express": "3.x"
}
}

Now run at the node console:

> npm install

If the installation succeeds, you should see a node_modules folder created in your source code folder, with express folder under it.

Now create a “hello.js” file to run our “Hello World” test with express:

var express = require('express');
var app = express();
app.get('/hello.txt', function(req, res){
res.send('Hello World');
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});

run this with:

> node hello.js

And point your browser to “http”//localhost:3000”. You should see “Cannot GET / “ on the screen. This is good, because it shows that express is listening on port 3000, but since we didn’t define the route to serve “/” requests, it fails. Now try “http://localhost:3000/hello.txt” and you should see the encouraging “Hello World” response.
In the next part we will add some database functionality to our application.

Part 2: Let’s Level Some Jobs

In the previous part, I explained my frustration about seeking possible close (geographically) and awesome future employers. I started my journey towards geo aware job finding application by discussing Node.JS basics.

In this post, I would like to serve some companies that offer positions in my field, from the node web server we just created. This means, we need to retrieve them from somewhere. Aha! A database? Well, this could be anything from hard coded JS array, a file and of course — a database. Following this short presentation about using LevelDB with Node.JS I decided to use LevelDB for this tutorial. This turned to be a bad decision. While my tests on Amazon EC2 cluster went very well, installing level on a Windows machine became a sisyphic task. Simply trying

> npm install level

Will fail. Looking at the logs you can see some rants about leveldown failed installation. LevelDB is composed from 2 main packages: levelup — which provide a high level API to the DB, and leveldown which provide the low level hard core DB functions. Trying to follow the instructions on Richard’s blog by installing only levelup:

> npm install levelup

will give a wrong first impression. While the installation goes by ok, the DB will fail on first try:

c:\Dev\node\geojob>
> var level = require('levelup');
undefined
> var db = level('./DatabaseDirectory');
LevelUPError: Could not locate LevelDOWN, try <code>npm install leveldown</code>
at getLevelDOWN (c:\Dev\node\geojob\node_modules\levelup\lib\util.js:109:11)
at LevelUP.open (c:\Dev\node\geojob\node_modules\levelup\lib\levelup.js:109:37)
at new LevelUP (c:\Dev\node\geojob\node_modules\levelup\lib\levelup.js:82:8)
at LevelUP (c:\Dev\node\geojob\node_modules\levelup\lib\levelup.js:44:12)
at repl:1:10
at REPLServer.self.eval (repl.js:110:21)
at repl.js:249:20
at REPLServer.self.eval (repl.js:122:7)
at Interface.&lt;anonymous&gt; (repl.js:239:12)
at Interface.emit (events.js:95:17)
>

Again, because of our friend leveldown. Trying to install leveldown directly will fail as well.
There are a lot of articles discussing how to compile leveldown for Windows, but I decided to let go of LevelDB for now. This is not what I would have decided, if I was going to continue this tutorial on a linux machine, but for Windows machine I needed a simpler solution.

I decided to go along with CouchDB. It has a Windows installer and the installation was a breeze. After you restart your computer, the CouchDB service will run, and you can test if your DB is working properly by pointing your browser to “http://localhost:5984/_utils/index.html”. You should see the Couch admin console.

The part title should be renamed more appropriately: “Part 2: Let’s Secure Some Jobs From Our Couch”, but I left it as is because I still think node + level is an awesome solution.

Now we need to install node support for couch. There are a lot of utility packages that help connect node to couch: couchdb (promise-based CouchDB client), node-couchdb, request (which basically wrap the couch http requests), but I think nano is the easiest minimalist couch client to date.

> npm install nano

To check couch with nano, let’s run this simple test:

var nano = require('nano')('http://localhost:5984');
// clean up the database we created previously
nano.db.destroy('alice', function() {
// create a new database
nano.db.create('alice', function() {
// specify the database we are going to use
var alice = nano.use('alice');
// and insert a document in it
alice.insert({ crazy: true }, 'rabbit', function(err, body, header) {
if (err) {
console.log('[alice.insert] ', err.message);
return;
}
console.log('you have inserted the rabbit.')
console.log(body);
});
});
});

You should see the response:

you have inserted the rabbit.
{ ok: true,
id: 'rabbit',
rev: '1-6e4cb465d49c0368ac3946506d26335d' }

You can also check this with Couch administration console (aka futon). You should see the “alice” database in the list of databases, and if you drill in you will see the “rabbit” document and its content.

Now we are ready to create out jobs database. Let’s call it “geojob” and insert some job offerings into it. The job document (object) will be structured like this: location id (Hebrew name), company name, sector, number of employees, area, location Name, location area, coordinates:

var nano = require('nano')('http://localhost:5984');
nano.db.create('geojob');
var geojob = nano.use('geojob');
geojob.insert({ companyname: "A company", sector: "A Sector", employees: 100, area: "North", location: "A Location", locationarea: "Area", lat: 0, lon: 0 }, 'somejob', function(err, body, header) {
if (err) {
console.log('[geojob.insert] ', err.message);
return;
}
console.log('you have inserted the job.')
console.log(body);
});
> ...
> geojob.insert({ companyname: "Another company", sector: "Another Sector", employees: 50, area: "South", location: "Another Location", locationarea: "Area 51", lat: 0, lon: 0 }, 'someotherjob', function(err, body, header) {
if (err) {
console.log('[geojob.insert] ', err.message);
return;
}
console.log('you have inserted the job.')
console.log(body);
});

Now our database should have 2 jobs:

> geojob.list(function(err, body) {
if (!err) {
body.rows.forEach(function(doc) {
console.log(doc);
});
}
});

> { id: 'somejob',
key: 'somejob',
value: { rev: '1-6fa32fe79461b0743ce9a0ef667b14c3' } }
{ id: 'someotherjob',
key: 'someotherjob',
value: { rev: '1-bbfa44377ce9ed50a69f7fdef8db7bc4' } }

So it seems everything works as planned. I’ve created a script that will read my jobs (maintained in a csv file) and populate the database (I leave you as exercise to use ‘fs’ and ‘nano’ and accomplish this).
In the next part we will start to use this information.

Part 3: The Angular Angle

In the first port, I explained my frustration about seeking possible close (geographically) and awesome future employers. I started my journey towards geo aware job finding application by discussing Node.JS basics. In the previous post we added some database functionality with CouchDB.

Now it is time to start showing some UI, to display the jobs stored in our database. There are many modern UI/Web frameworks that can do the job. My favorites are jQuery (mainly because of jQuery mobile and me being mobile oriented) and Angular.JS.

I’ve chosen Angular for this tutorial. Download and install the Angular files. I would also recommend the excellent Angular free tutorial to get you started with basic Angular concepts.

Let’s create our first application files. We start with the html:

geojob.html
<html ng-app="geojob">
<head>
<title>GeoJob Finder - Hello GeoJob</title>
</head>
<body>
<div ng-controller="JobsController">
<h1>GeoJob Finder - Hello GeoJob</h1>
{{hello}}
</div>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/geojob-app.js"></script>
</body>
</html>

You can see the important Anguler bits here:
line 1 — ng-app=”geojob” is the name of the application module.
line 6 — ng-controller=”JobsController” is our basic controller which will provide the data to be displayed in line 8, replacing the {{hello}} placeholder with the controller hello variable content. Also note that angular JavaScript and our application are placed in the “JS” sub folder.

And add the application:

geojob-app.js
(function() {

var app = angular.module('geojob', []);

app.controller('JobsController', function($scope) {
$scope.hello = "Hello GeoJob";
});

})();

Here we can see we named the application module “geojob” and created a simple “JobsControlloer” controller that has a single hello variable initialized to some predefined hello world message. And if we load geojob.html, we get:

Cool! Now we are ready to serve this page from our node server. Let’s define a simple node application that will do that:

server.js
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/'));
app.get('/', function(req, res){
res.sendfile('./geojob.html');
});
var server = app.listen(80, function() {
console.log('Listening on port %d', server.address().port);
});

Lines 4,5 will serve our geojob.html file. However, since the html include some assets (Java scripts, and maybe css and images in the future) we define in line 3 that all static content should be served from the “/” path (you can change this to ‘/html/’ or whatever suits you, but keep in mind that the relative folder structure — /js etc. should be maintained). Run “node server.js” and point your browser to “http://localhost” and you should see the page above!

Now it’s time to use our database and use Angular to render the jobs that the server will return. We can use Angular “$http” service to perform the jobs query. Let’s modify our app:

geojob-app.js
(function() {

var app = angular.module('geojob', []);

app.controller('JobsController', function($scope, $http) {
$scope.hello = "Hello GeoJob";
$scope.jobs = {};
$http.get("/jobs").success(function(data) {
$scope.jobs = data;
}).error(function() {
alert("Error");
});
});

})();

As you can see we initialize a variable for the jobs (line 7), and retrieve it with the $http service (lines 8–12). Refreshing the browser (“http://localhost”) will pop the “Error” alert, obviously. We need to modify our server as well:

server.js
var express = require('express');
var nano = require('nano')('http://localhost:5984');
var geojob = nano.use('geojob');
var app = express();
app.use(express.static(__dirname + '/'));
app.get('/', function(req, res){
res.sendfile('./geojob.html');
});
app.get('/jobs', function(req, res){
geojob.view('jobs', 'jobs', function(err, body) {
if (!err) {
res.json(body);
}
});
});
var server = app.listen(80, function() {
console.log('Listening on port %d', server.address().port);
});

Nothing new here. We are using nano for the database access, as in the previous post. We added a server route for “/jobs” (lines 9–15) so when the Angular controller will run the initialization code and perform the ajax http request (using the $http service), the server will query all the jobs in the database and return them in the response. Now if we refresh the browser, the pesky “Error” alert will not show up.

Line 10 require some explanation. We are usin nano (and CouchDB) view to retrieve the jobs information from the database.Explaining CouchDB views is beyonf the scope of this tutorial as it requires some Map/Reduce background, but fortunataly, we can simply create the view design (which includes a mapping function and a reducing function) in the futon console. For the jobs description in this post I choose to call both the design and the view “jobs”:

As you can see the view design map function simply returns the company name as a key and the area and number of employees as the values (and we left the reduce section empty):

function(doc) {
emit(doc.companyname, [doc.area, doc.employees]);
}

You can define the views in futon by selecting “temporary view…” and defining your design and view name after pressing “save as” to save your view:

You can also add view to the database programmatically with nano:

...
geojob.insert(
{ "views":
{ "jobs":
{ "map": function(doc) { emit(doc.companyname, [doc.area, doc.employees]); } }
}
}, '_design/jobs', function (error, response) {
console.log("yay");
});

So it’s time for some Angular magic — lets display the jobs:

We changed the header to print the number of jobs that were returned from the “/jobs” request — jobs.total_rows, and used angular ng-repeat (lines 9–13) to repeat for each job and print the company name, area and number of employees.

Now we have the core functionality of retrieving and displaying our jobs (to add additional fields to the screen, simply modify the CouchDB view and add additional doc.XXXXX fields to the value array).
In the next part we will ditch this ugly UI presentation for something far more appealing.

Part 4: Strap The Boot

In the previous parts we learned some Node.JS (and express) basics, incorporated CouchDB as our database and used some Angular.JS code to present our list of jobs.

But the final result we got is not a pretty sight, and not very usable or friendly. Those who know me, agree that I’m not that strong in UI design or implementation, so to make our application prettier and more usable, let’s use some framework for that. There are a lots of frameworks out there, but I had my eye on Bootstrap. Maybe it’s my strong mobile orientation, maybe the ease of use or maybe it’s just one of the frameworks I encountered first. In any case, I like the thinking of Bootstrap makers, I like that it is a remarkable responsive UI framework, and that it is fairly easy to us. So you can download Bootstrap from here, and lets make some changes to our application, using Bootstrap styles:

geojob.html

<html ng-app="geojob">
<head>
<title>GeoJob Finder - Bootstrap GeoJob</title>
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="css/geojob.css" />
</head>
<body>
<section class="container" ng-controller="JobsController">
<div class="header"><h1>GeoJob Finder <small>Bootstrap GeoJob</small></h1></div>
We have a total of: {{jobs.total_rows}} jobs
<div class="list-group">
<a href="#" class="list-group-item" ng-repeat="job in jobs.rows">{{job.key}}</a>
</div>
</section>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/geojob-app.js"></script>
</body>
</html>

In lines 4–5 we added the Bootstrap css files (which reside under the ‘bootstrap-2.1.1-dist‘ sub folder). We used them to style our header (line 10) and replace the ugly header lines with a styled list (lines 12–14). The result was not perfect since it was stretched from the edges of the screen, so we added a css file to our application, to constrain the display width with the “container” class (line 6):

geojob.css

.container {
width: 940px;
}

Now we have an appealing list of companies (lines 12–14):

Now let’s add the company details:

geojob.html

<html ng-app="geojob">
<head>
<title>GeoJob Finder - Bootstrap GeoJob</title>
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="css/geojob.css" />
</head>
<body>
<section class="container" ng-controller="JobsController">
<div class="header"><h1>GeoJob Finder <small>Bootstrap GeoJob</small></h1></div>
<div class="well well-sm">We have a total of: {{jobs.total_rows}} jobs</div>
<div class="list-group companylist">
<a href="#" class="list-group-item" ng-repeat="job in jobs.rows">{{job.key}}</a>
</div>
<div class="panel panel-default companydetails">
<div class="panel-body">
Name: <span style="float: right">{{company.key}}</span><br>
Sector: <span style="float: right">{{company.sector}}</span><br>
Employees: <span style="float: right">{{company.employees}}</span><br>
Area: <span style="float: right">{{company.area}}</span><br>
Location Name: <span style="float: right">{{company.locationName}}</span><br>
Location Area: <span style="float: right">{{company.locationArea}}</span><br>
</div>
</div>
</section>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/geojob-app.js"></script>
</body>
</html>

And we need to modify our controller (Angular) so that when you select a company from the list, it will display the details on the right panel (via the {{company.xxxx}} predicates). We also need to create the selection code when clicking a list item. In the previous post we left something annoying — the value array returned by CouchDB ‘view’ API. Let’s fix this by making it more intuitive to use the values (e.g. key-value!).

Map Function

function(doc) {
emit(doc.companyname, {"area": doc.area, "employees": doc.employees, "sector": doc.sector, "locationName": doc.locationname, "locationArea": doc.locationarea});
}

Map function: the changes are self explanatory: we return an object with all the doc fields mapped to the object keys.

geojob-app.js

...
app.controller('JobsController', function($scope, $http) {
$scope.hello = "Hello GeoJob";
$scope.jobs = {};
$scope.company = {};

$http.get("/jobs").success(function(data) {
$scope.jobs = data;
}).error(function() {
alert("Error");
});

$scope.setCompany = function(company) {
$scope.company = company;
};

$scope.isSelected = function(company) {
return $scope.company == company;
};
});
...

We changed the controller by adding a selection function (lines 13–15, setCompany). It gets the job from the angular repeater and uses it as argument to the selection function (and save it to the controller “company” variable). We also added isSelected function (lines 17–19) so we can use it to highlight the selected company in the list.

geojob.html

...
<div class="list-group companylist">
<a href="#" class="list-group-item" ng-class="{active : isSelected(job)}" ng-repeat="job in jobs.rows" ng-click="setCompany(job)">{{job.key}}</a>
</div>
<div class="panel panel-default companydetails">
<div class="panel-body">
Name: <span style="float: right">{{company.key}}</span><br>
Sector: <span style="float: right">{{company.value.sector}}</span><br>
Employees: <span style="float: right">{{company.value.employees}}</span><br>
Area: <span style="float: right">{{company.value.area}}</span><br>
Location Name: <span style="float: right">{{company.value.locationName}}</span><br>
Location Area: <span style="float: right">{{company.value.locationArea}}</span><br>
</div>
</div>
...

We changed the company list row so that it will be highlighted if this row is selected (line 3) by using the “ng-class” directive. We also added a click handler (by using the “ng-click” directive).
Now we can see that selecting a company display the company details in the right pane, and also the company is selected in the company list.

Ignore the weird characters that you see in the screen images, some of the text is in Hebrew (it is no secret that I live in Israel, and obviously look for a new job in Israel), and we’ll fix this in the next parts of the tutorial.
In the next part we will add some mapping functionality (why do you think we left this blank area in the middle of the screen?).

Part 5: Mind The Map

In this part I would like to start adding some mapping functionality to our application. As always, there are a lot of different ways to achieve this goal, but I have chosen to go with Google Maps as the mapping solution provider, because of their clean and simple API, the visualization layers, and frankly — because I have used it in the past and know it a little bit.

Even with that decided — there still a lot of ways to incorporate the Google Map in our application. Let’s try an Angular add-on that will add Google Maps directives to Angular. It is called Google Maps for AngularJS, and before we can use it, let’s install another package and dependency manager that will ease the add-on installation — Bower. You can install it yourself by placing the add-on JS in your js sub folder, but you will need to install some dependencies as well (lodash in this case). Bower packages will be placed under the “bower_components” sub folder.

> npm install -g bower
...
> bower install angular-google-maps

After the packages are installed, you can reference them directly from the install folders:

In my tutorial I copied the relevant files to the JS/ sub folder, and added the following to the html file (lines 3–5):

geojob.html

...
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src='//maps.googleapis.com/maps/api/js?sensor=false'></script>
<script type="text/javascript" src='js/lodash.underscore.min.js'></script>
<script type="text/javascript" src='js/angular-google-maps.min.js'></script>
<script type="text/javascript" src="js/geojob-app.js"></script>
</body>
</html>

Now we are ready to use the Google Maps API in our application.

First, let’s add the map to our controller:

geojob-app.js

app.controller('JobsController', function($scope, $http) {
$scope.hello = "Hello GeoJob";
$scope.jobs = {};
$scope.company = {};
$scope.map = {
draggable: true,
center: {
latitude: 32.1092255,
longitude: 34.838992
},
zoom: 17
};
...

lines 5–12 define the map variable (object) with some predefined initial values. We will make the map initialization dynamically in the next posts, but for now let’s try to use this map. I decided to draw a circle around each company location which is proportional to the number of employees that work at that company. Let’s add the circle variable with some predefined initial values:

geojob-app.js

app.controller('JobsController', function($scope, $http) {
...
$scope.circle = {
center: $scope.map.center,
radius: 50,
stroke: {
color: '#B2081F',
weight: 1,
opacity: 1
},
fill: {
color: '#B2081F',
opacity: 0.5
},
geodesic: true, // optional: defaults to false
draggable: false, // optional: defaults to false
clickable: false, // optional: defaults to true
editable: false, // optional: defaults to false
visible: true // optional: defaults to true
};
...

Here we can see how the circle border (stroke) and fill colors are defined, along with some other circle properties. The circle is centered (by default) to the map center and has a default radius of 50 meters.

I would also like to mark each company location by a map marker, which will give some details about the company. Let’s start simple by using the default marker, and showing the company name when the marker is clicked. For this let’s define the marker events:

geojob-app.js

app.controller('JobsController', function($scope, $http) {
...
$scope.markersEvents = {
click: function (marker, eventName, model) {
marker.showWindow = true;
}
};
...

The markerEvents variable defines the array of events and their callback functions. For now we defined only the click event and the function it calls will open the marker info window (the info window will inherit the default window behavior which include an “X” close button and will display the info window html fragment — we will see how it is initialized later in the html file). Finally, we need to calculate the radius of the circle from the company employees number. I decided to divide it by 10 for simplicity (so 500 employees company should draw a 50 meter radius circle on the map). Here is the controller, with lines 56–58 showing the radius calculation:

geojob-app.js

(function() {

var app = angular.module('geojob', ['google-maps']);

app.controller('JobsController', function($scope, $http) {
$scope.hello = "Hello GeoJob";
$scope.jobs = {};
$scope.company = {};
$scope.map = {
draggable: true,
center: {
latitude: 32.1092255,
longitude: 34.838992
},
zoom: 17
};
$scope.circle = {
center: $scope.map.center,
radius: 50,
stroke: {
color: '#B2081F',
weight: 1,
opacity: 1
},
fill: {
color: '#B2081F',
opacity: 0.5
},
geodesic: true, // optional: defaults to false
draggable: false, // optional: defaults to false
clickable: false, // optional: defaults to true
editable: false, // optional: defaults to false
visible: true // optional: defaults to true
};
$scope.markersEvents = {
click: function (marker, eventName, model) {
marker.showWindow = true;
}
};

$http.get("/jobs").success(function(data) {
$scope.jobs = data;
}).error(function() {
alert("Error");
});

$scope.setCompany = function(company) {
$scope.company = company;
};

$scope.isSelected = function(company) {
return $scope.company == company;
};

$scope.getRadius = function(job) {
return job.value.employees / 10;
};

});

})();

line 3 also show how we added the ‘google-map’ module dependency to our ‘geojob’ module.

Now we can use the modified controller in our html file:

geojob.html

<html ng-app="geojob">
<head>
<title>GeoJob Finder - Bootstrap GeoJob</title>
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="bootstrap-3.1.1-dist/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="css/geojob.css" />
</head>
<body>
<section class="container" ng-controller="JobsController">
<div class="header"><h1>GeoJob Finder <small>Bootstrap GeoJob</small></h1></div>
<div class="well well-sm">We have a total of: {{jobs.total_rows}} jobs</div>
<div class="list-group companylist">
<a href="#" class="list-group-item" ng-class="{active : isSelected(job)}" ng-repeat="job in jobs.rows" ng-click="setCompany(job)">{{job.key}}</a>
</div>
<google-map center="map.center" zoom="map.zoom" draggable="{{map.draggable}}">
<div ng-repeat="job in filtered = (jobs.rows)">
<marker coords="{latitude: job.value.lat, longitude: job.value.lon}" events="markersEvents" showWindow="false">
<window>
<p>{{job.key}}</p>
</window>
</marker>
<circle center="{latitude: job.value.lat, longitude: job.value.lon}" stroke="circle.stroke" fill="circle.fill" radius="getRadius(job)"
visible="circle.visible" geodesic="circle.geodesic" editable="circle.editable" draggable="circle.draggable" clickable="circle.clickable"></circle>
</div>
</google-map>
<div class="panel panel-default companydetails">
<div class="panel-body">
Name: <span style="float: right">{{company.key}}</span><br>
<span style="float: right">{{company.value.name}}</span><br>
Sector: <span style="float: right">{{company.value.sector}}</span><br>
Employees: <span style="float: right">{{company.value.employees}}</span><br>
Area: <span style="float: right">{{company.value.area}}</span><br>
Location Name: <span style="float: right">{{company.value.locationName}}</span><br>
Location Area: <span style="float: right">{{company.value.locationArea}}</span><br>
</div>
</div>
</section>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src='//maps.googleapis.com/maps/api/js?sensor=false'></script>
<script type="text/javascript" src='js/lodash.underscore.min.js'></script>
<script type="text/javascript" src='js/angular-google-maps.min.js'></script>
<script type="text/javascript" src="js/geojob-app.js"></script>
</body>
</html>

Lines 15–25 show the changes we made to the html file. It is the Angular Google Map directive that wraps the markers and circles directives.
Line 15 — the directive initializes the map with values from the $map variable in the controller.
Line 16 is Angular repeater for each job in the filtered job list. For now we will iterate all the jobs in the list, but we will add some filters later on to filter specific jobs from this list.
Lines 17–21 add the marker. Note how the info window is initialized with the company name ({{job.key}}) and the marker is placed in the company location (coords=”{latitude: job.value.lat, longitude: job.value.lon}”) with the info window closed by default (showWindow=”false”). The marker events are set to the controller markerEvents we’ve seen above.
Lines 22–23 add the circle. The circle center and radius are set to the company location (center=”{latitude: job.value.lat, longitude: job.value.lon}”) and calculated from the company employees number (radius=”getRadius(job)”), with other values set to the controller circle variable values.

The result? Here is a sample screen from our jobs db:

You can see the map, markers and circles. One marker was clicked to open it’s info window. The circles are proportional to the company sizes (employees), and the selected company circle radius is 50 meters according to it’s number of employees.

In the next post we will enhance the mapping functionality and add some cool filtering features.

Note: As I write this post, a rocket from Gaza destroyed a house 500 meters from my home. Please support Israel in it’s battle against the Hamas terror organization.

In the previous part I promised to tell you how to fix the mingled Hebrew characters display. As it turned out, this was a bug in the way I exported the company information from my excel. My bad! Couch DB support UTF-8 by default, so once the information was exported correctly — the problem disappeared.

If the phrase “Mind The Map” sound familiar to you, this is probably because it is a paraphrase on the famous “Mind The Gap” phrase.

Part 6: Going Production

The previous part in this series was almost a year and a half ago. A lot has happened in this time. The tension took some 2 months to resolve, and the new job that I got demanded some attention. I know I promised to enhance the mapping functionality and show some filtering tricks, but I decided to write this post about deploying the app to a production server, and share my findings with you.

For the production server, I choose to go with DigitalOcean. You can read here about some of the reasons behind this decision. To get started, you should create you’re account with DigitalOcean, and when you are ready to start your droplet, you are presented with a lot of pre-built stacks to choose from:

I choose the node stack and the weakest server possible (5$/month). With a sign in coupon of 10$ you get 2 months of server usage for free.

Once you droplet is created, open ssh connection with it (there is a lot of help document describing how to use the DigitalOcean droplets. You credentials should arrive by mail after the droplet is created). Check that node is running by: node -v

root@ubuntu-512mb-nyc3-01:~# node -v
v4.2.3

Great! Node is running. Let’s install the Couch DB. Follow the instructions here. If everything is ok, you should see this:

root@ubuntu-512mb-nyc3-01:~# curl localhost:5984
{
"couchdb":"Welcome",
"uuid":"a260bce11435fb728d7e7c31f1e0a340",
"version":"1.6.1",
"vendor":{
"name":"Ubuntu",
"version":"14.04"
}
}

Follow the additional steps to install Futon. If you followed the instructions carefully, you should see the futon control panel in your browser (after you created the tunnel by:

The next step will be to upload all your application files from your development machine to this newly created server. Once uploaded, let’s try to run the app (don’t forget to install all the required dependencies with npm and bower — see the previous post, and remember to run node server.js on the server), and point the browser to our server ip:

Hmmm. It seems that there is no data. Why? Because we didn’t create the table and imported the data into it yet…

If we run the data importer node app (remember the job-importer.js we discussed in the previous posts?), we encounter the first production world issue. the database and all it’s documents are not created. debugging the js script with the help of some curl commands reveal that couch is unhappy:

{"error":"unauthorized","reason":"You are not a server admin."}

And this is because one of the steps in the couch db installation was to create an admin user. To resolve this problem, the creation urls should be corrected:

instead of: var nano = require('nano')('http://localhost:5984');

we should use: var nano = require('nano')('http://admin_user_name:admin_password@localhost:5984');

Now the importer should run smoothly, and we get the map shown at the end of part 5 (Part 5: Mind The Map)

In the next post we’ll see some NoSQL magic and Couch tricks for data lookup without views.

--

--

Miron Ophir
Miron Ophir

Written by Miron Ophir

Interested in Technology. Developer. Manager. Nerd.

No responses yet