1. Live Demo on Compute Engine: click here
2. Download complete source code here
Chapter II of our tutorial - Intro
On the last chapter, we got our Erlang based web server up & running on GCE. We've focused on making things work to the point of having a ChicagoBoss erlang controller that can return JSON from a REST API.On this chapter, we're starting to get serious: we'll turn that same site into a web site that can render views that get their appearance and features set by a DB configuration. We'll also make our site shiny and more real world by using some css libraries.
On the next chapter, we'll hook up Riak DB to our app , setup MemCache to improve performance and add a chat service to the page using Web Sockets, that will allow our users to chat with a sales representative in real-time.
Goals
So, let's pretend that we're a travel company that organises trips to New Zealand. Our design goals are:1. We want to have a single page web application that's will be rendering different views for each section of the site.
2. We want the site to look nice and have responsive design that will also work well on mobile.
3. We want be able to easily add new sections/views to our site, and we want
the view to get it's data from the server (In other words, we want an admin section).
Technology We'll Be Using
To accomplish this, we'll be using:1. We'll introduce the concept of a database in ChicagoBoss for the first time - BossDB. We'll be using this DB Adapter for storing and fetching data about our views. 2. Angular JS (learn more) - this will be the client-side framework for managing the calls to the server, the routing of the views, data binding - and more or less everything that we do client-side.3. Twitter Bootstrap - for some CSS goodness that will make our site all shiny and beautiful.Getting The DB Ready for Work
Mock DB
So, first things first. Let's setup our CB server to support Database operations. We'll be adding a database adapter of type "Mock" (this is an easy to work with DB that stores values in-memory, useful for debugging). CB supports many "real" DB adapters and in the next chapter we'll add a kickass DB - Riak - which is in itself Erlang based.This is the topmost part of our boss.config file:
Two things that we should focus on are changed in this file since our last chapter:
1. we've now using db adapter settings:
{db_host, "localhost"},
{db_port, 3306},
{db_adapter, mock},
{db_username, "root"},
{db_password, "password"},
{db_database, "templates_db"}
Chicago Boss Admin
2. we're also using CB_ADMIN (not mandatory, but nice to have - it's included in the source code above so I'd recommend not removing it), a nice user interface that can show us the memory state (so we can edit the in memory table) and other useful information. This is done by telling our CB application that we not only run our erlang_gc project here, we also want cb_admin:
{applications, [cb_admin, erlang_gc]}
To use cb_admin, you'll also have to add this to the end of the boss.config file:
{cb_admin, [
{path, "./deps/cb_admin"},
{allow_ip_blocks, ["127.0.0.1"]},
{base_url, "/admin"}
]}
and also resolve any dependencies required by cb_admin. let's add this to our rebar.config file:
{deps, [
% ...
{cb_admin, ".*", {git, "git://github.com/evanmiller/cb_admin.git", "HEAD"}}
]}.
and then run this:
./rebar get-deps compile
./init-dev.sh
If all is well, you should be able to browse over here:
http://localhost:8001/admin/
Data Model (M is for MVC!)
We will now be adding a Model for our pages. To keep it simple, we'll define our page data model to have only: Id (unique identifier for the DB record), View ID (the name of the view corresponding to this record of data) and PageDataJSON - this is the data we're going to bind to the view. Create the page.erl file under "Model" directory:
This will allow you to do a couple of interesting things:
1. You can start testing the model by creating new instances in the shell. You can experiment with the model's auto generated properties:
1. You can start testing the model by creating new instances in the shell. You can experiment with the model's auto generated properties:
2. That's right, ChicagoBoss automatically adds some useful properties to your model (like set, get and save to DB). You can even check them out at the auto generated documentation here:
http://localhost:8001/doc/page
3. If you are using CB_ADMIN, you can play with the in-memory table to add, edit or remove instances:
Writing Our Erlang Controllers Logic
ok, so we've got our DB and model all setup, it's time to write the code that uses them. On this chapter we'll write two controllers in erlang:
1. a home controller, that will only serve us to redirect the user to our Angular JS application. It will have a single default action called index.
2. a nice & simpler API controller that will have two actions:
create - add a new page data record to the DB
create - add a new page data record to the DB
templates - get a data for a certain view
The Home Controller
as you can see, this is not doing much. It basically makes the server perform a redirect to our static index page (Angular JS based app).
http://localhost:8001/home -> http://localhost:8001/static/app/index.html
and in turn, our angualr JS app will add:
http://localhost:8001/static/app/index.html -> http://localhost:8001/static/app/index.html#/home
http://localhost:8001/home -> http://localhost:8001/static/app/index.html
and in turn, our angualr JS app will add:
http://localhost:8001/static/app/index.html -> http://localhost:8001/static/app/index.html#/home
The API Controller
This guy is a bit more interesting. Let's take a look at the code:
-module(cb_poc_api_controller, [Req]).
-compile(export_all).
create('POST', []) ->
PageData = Req:post_param("pageData"),
ViewId = Req:post_param("viewId"),
Page = page:new(id, ViewId, PageData),
case Page:save() of
{ok, SavedNewPage} -> {json, [{id, SavedNewPage:id()}]};
{error, ErrorList} -> {json, [{errors, ErrorList}]}
end.
The create action corresponds to this URL:
The create action corresponds to this URL:
And it is set to receive a POST message containing data about the Page. This data is saved in the DB. We will serving this data in the second action:
templates('GET', [Id]) ->
case boss_db:find(page, [{view_id, equals, Id}]) of
{error, Reason} -> {json, [{error, "error"}]};
undefined -> {json, [{error, "not found"}]};
FoundIt -> {json, [{result, FoundIt}]}
end.
This action corresponds to this URL:
And we will be using this to perform GET requests from our client application. The result JSON would serve us to data bind into the view dynamically.
It is worth mentionting that every real world application will have to use a certain caching mechanism so that DB calls won't be made for every single request. On this and more on the next chapeter.
The Client Side Front End - Angular JS
Serving Static Files
With this, we are ready to code our application's front end. We'll be using Angular JS framework to achieve a SPA architecture with Angular's brilliant routing mechanism, and we will also take advantage of the frameworks' great modularity to write JS code that can be used to GET and POST requests to our erlang server.
We have setup our static files directory on the erlang server to be /priv/static. Under this, we will create our angular app directory, with subfolders for our js,css, eg.
Our index.html file is the main view, and under partials directory, we have several sub views, each corresponding to a section of our web site. The admin and manage view will be used to allow us to add data about views to our database.
Under the lib folder, you will find angular js and twitter bootstrap libraries, as well as holder.js and other stuff we're going to use.
The Index.html Page (The Master View)
Our index.html file is the main view, and under partials directory, we have several sub views, each corresponding to a section of our web site. The admin and manage view will be used to allow us to add data about views to our database.
Under the lib folder, you will find angular js and twitter bootstrap libraries, as well as holder.js and other stuff we're going to use.
The Index.html Page (The Master View)
The index is the main page (or "master page" for you .NET guys), which sets the common layout of the app. I've highlighted some of the Angular JS bits, the important ones are:
1. <html lang="en" ng-app="erlFacLiteTLV" ng-controller="MainCtrl"> - this defines the scope of our angular app to the entire HTML, and sets a "Main" angular controller to provide all the needs of the page.
2. as you've probably notices, the page is rather clean and tidy, using bootstrap css classes.
3. you can spot important angular directives like:
<li ng-repeat="nav_item in page.nav.nav_items">
which suggests that our main controller shares the "page" object on the scope (an object mutual to the controller and the view), and you can guess that this page is populated from server data.
<li ng-repeat="nav_item in page.nav.nav_items">
which suggests that our main controller shares the "page" object on the scope (an object mutual to the controller and the view), and you can guess that this page is populated from server data.
Highlighted is the common UI part:
The Main Controller and the App.js File
Our app.js file has all the basic application initialization code. In our case it's the routing logic. Every view of the app corresponds to a controller according to it's type (sections get the Page Ctrl and the special admin/manage views get the AdminCtrl).
When the controller gets loaded, we proceed to get the template data using the templatesDataFactory. We use the result to build the page object, and we bind that to the $scope, so that we could use it on the index.html view.
The Templates Data Factory
This is the interesting part, as far as communication with our Erlang server is concerned.
Highlights:
1. The getTemplatesData method is pretty straightforward. It performs a GET request to the REST URL of the the form: http://localhost:8001/api/templates/<view_id>
2. The POST request has an interesting part: we use the header
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
to make sure it is passed to the server "forms style" - this is mandatory for us to use the Erlang method Req:post_param("param_name")
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
to make sure it is passed to the server "forms style" - this is mandatory for us to use the Erlang method Req:post_param("param_name")
The Partial Views (Sections on the Site)
each section on our app, except for the admin section, has a similar look & feel: a top nav bar and a slider (handled by the main controller like depicted above) and a scroll-down layout based on circles and information aside them.
Highlights:
1. <div class="col-lg-4" ng-repeat="col in page.circle_cols"> - generates the columns with the pill-shaped images (the "marketing" container).
1. <div class="col-lg-4" ng-repeat="col in page.circle_cols"> - generates the columns with the pill-shaped images (the "marketing" container).
2. <div class="featurette" ng-repeat="featurette in page.featurettes"> - generates the rows of the "featurette" - large images with test aside them.
The controller logic for the partials are pretty straightforward and are left for you to read by yourself in the source code attached.
The controller logic for the partials are pretty straightforward and are left for you to read by yourself in the source code attached.
This is the UI part of the sections partial views:
The Admin and Manage Sections
The admin and manage sections are the place where we can edit the features of our views. What images will be in the views, what text besides each image, etc. The admin section features an authentication sign-in page - which is left unimplemented for now. Clicking Sign-In will automatically take us to the manage view, where we can add views data to our database.
The manage section makes use of Angualr JS's two way data binding feature to transfer data from the UI to the model - in this case, the data we'll POST to the server in order to create a new page in our DB.
UI:
View:
notice the ng-model directive (highlighted) - this is passed to the controller scope
notice the ng-model directive (highlighted) - this is passed to the controller scope
Controller:
No comments:
Post a Comment