Writing a Blog Engine in Phoenix and Elixir: Part 11, better UI

Brandon Richey
15 min readMar 28, 2016

--

Latest Update: 08/02/2016

Wow, monthlyhipstercrate.com really paid off this month!

Previous Post in this series

Current Versions

  • Elixir: v1.3.1
  • Phoenix: v1.2.0
  • Ecto: v2.0.2

Where We Left Off

When we left off last, we had just finished writing tests for everything we had related to channels. This tutorial again is going to be significantly less Elixir/Phoenix-heavy and just showcase how to integrate some external stuff into our Phoenix application. Generally, Phoenix starts you off with a very, very basic version of Twitter Bootstrap that has been tweaked to be very Phoenix-specific, so we’re going to go off the rails a little bit and swap out to a different framework (and utilize SASS).

Disclaimer: I am really, really not a designer. I may do things that are icky or otherwise ill-advised, and if you notice them, please feel free to point them out or change them for yourself, but consider yourself warned.

Why Foundation?

I won’t attempt to jump into the Bootstrap/Foundation discussion or argument since web design is really not my focus anymore. Instead, I’m choosing Foundation because it looks a little more interesting than your standard programmer-designed Bootstrap site, and I find it to have a little less boilerplate stuff that I have to do in Bootstrap 3. I’ve not used Bootstrap 4, so I can’t really speak to how good that one is yet. Besides, not a lot of people have used Foundation in comparison, so let’s have some fun with it!

Installing Zurb Foundation 6

$ npm install --save-dev foundation-sites motion-ui sass-brunch jquery@2.2.4
$ cp node_modules/foundation-sites/dist/foundation.min.js web/static/vendor/

NOTE: Please note that we’re installing a very specific version of jquery here (v2.2.4) to avoid issues between jquery and foundation that may appear. For more information. For more information, check out https://medium.com/@brettwise/as-of-the-posting-of-this-comment-foundation-chokes-on-jquery-version-3-the-default-installed-with-710ee277be00#.uwn3dmaz2 (Thanks a lot, Brett Wise!)

We’ll also need to edit brunch-config.js to accommodate the new changes that pull Foundation into Brunch (namely, including the sass load directories and sass module config and the inclusion of jquery as a global for NPM):

exports.config = {
// See http://brunch.io/#documentation for docs.
files: {
javascripts: {
joinTo: "js/app.js"
},
stylesheets: {
joinTo: "css/app.css",
order: {
after: ["web/static/css/app.css"] // concat app.css last
}
},
templates: {
joinTo: "js/app.js"
}
},

conventions: {
// This option sets where we should place non-css and non-js assets in.
// By default, we set this to "/web/static/assets". Files in this directory
// will be copied to `paths.public`, which is "priv/static" by default.
assets: /^(web\/static\/assets)/
},

// Phoenix paths configuration
paths: {
// Dependencies and current project directories to watch
watched: [
"web/static",
"test/static"
],

// Where to compile files to
public: "priv/static"
},

// Configure your plugins
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/]
},
sass: {
options: {
includePaths: [
'node_modules/foundation-sites/scss',
'node_modules/motion-ui/src',
]
}
}
},

modules: {
autoRequire: {
"js/app.js": ["web/static/js/app"]
}
},

npm: {
enabled: true,
globals: {
$: 'jquery',
jQuery: 'jquery',
}
}
};

Next, since we’re going to be using SASS for this project with Foundation, we’ll want to create a folder for our sass files. Create web/static/scss and then we’re going to add three files to the directory:

In web/static/scss/application.scss:

// Only put imports and things here
@import "settings";
@import "foundation";
@include foundation-everything;
@import "motion-ui";
@include motion-ui-transitions;

@import "custom";

In web/static/scss/_settings.scss:

Just download this file and place the contents in the file above (the file is huge so I don’t want to embed it here): Download from here

In web/static/scss/_custom.scss:

// Put custom styles here
$darker-gray: #2a2a2a;

html, body {
height: 100%;
background-image: url("/images/computer-bg.png");
background-size: cover;
background-repeat: no-repeat;
background-color: #000;
background-attachment: fixed;
}

.top-bar {
background-color: rgba(0, 0, 0, 0.6);
ul {
background-color: inherit;
padding-left: rem-calc(40);
padding-right: rem-calc(40);
}
a {
color: $white;
}
a:hover {
color: $alert-color;
}
a.button:hover {
color: $white;
}
.logo {
padding-right: rem-calc(50);
text-transform: uppercase;
color: $white;
font-weight: bolder;
}
}

.image-overlay {
margin-top: 10%;
margin-bottom: 10%;
}

section.about {
background: rgba(0, 0, 0, 0.6);
color: $white;
padding: rem-calc(50);
.icon {
text-align: center;
font-size: rem-calc(64);
margin-bottom: rem-calc(20);
}
h2 {
text-align: center;
}
}

section.more-info {
background: rgba(0, 0, 0, 0.9);
color: $white;
min-height: 200px;
padding: rem-calc(50);
.social-media {
font-size: rem-calc(48);
a {
color: white;
}
a:hover {
color: $secondary-color;
}
}
}

section.main-content {
margin-top: rem-calc(100);
}

.content {
background: rgba(255, 255, 255, 0.8);
padding: rem-calc(30);
}

footer {
background-color: #000;
color: $white;
text-align: center;
padding-bottom: rem-calc(20);
font-size: rem-calc(14);
}

.post {
background: rgba(255, 255, 255, 0.8);
padding: rem-calc(30);
margin-bottom: rem-calc(25);
.title {
text-align: center;
border-bottom: 2px solid rgba(0, 0, 0, 0.6);
margin-bottom: rem-calc(10);
}
}

You’ll also want to download https://raw.githubusercontent.com/Diamond/pxblog/new_ui/web/static/assets/images/computer-bg.png and throw the file into web/static/assets/images/computer-bg.png since our custom scss file uses that as the background.

Installing Foundation Icons/Fonts

Also, you’ll want to download the Icon Fonts for Foundation. To do this, you’ll need to follow these steps:

  1. Download http://zurb.com/playground/uploads/upload/upload/288/foundation-icons.zip
  2. Unzip the file contents
  3. Copy the following files to web/static/assets/fonts: foundation-icons.eot, foundation-icons.svg, foundation-icons.ttf, foundation-icons.woff
  4. Copy the following file to web/static/css: foundation-icons.css
  5. Finally, modify the foundation-icons.css file so that the urls for each of the fonts respects Phoenix’s asset paths:
/* Rest of the file */@font-face {
font-family: "foundation-icons";
src: url("/fonts/foundation-icons.eot");
src: url("/fonts/foundation-icons.eot?#iefix") format("embedded-opentype"),
url("/fonts/foundation-icons.woff") format("woff"),
url("/fonts/foundation-icons.ttf") format("truetype"),
url("/fonts/foundation-icons.svg#fontcustom") format("svg");
font-weight: normal;
font-style: normal;
}
/* Rest of the file */

Now Foundation Icons should be installed as well!

Removing The Phoenix Default Styles

Open up web/static/css/phoenix.css and delete the whole thing! We may not use this later at all, but it’s good to have if you need to do something in standard CSS instead of SASS for some reason.

Refactoring Our Current User Approach

One of the things we want to do before we continue is take a refactoring approach to some of the code we’ve been writing. For example, the way we handle the current user is a little wonky, requiring us to import some code over and over again. Let’s make everything related to current user more reusable for us. We’ll start by creating a new plug that we’ll insert into web/controllers/current_user_plug.ex:

defmodule Pxblog.CurrentUserPlug do
import Plug.Conn

def init(default), do: default
def call(conn, _opts) do
if current_user = get_session(conn, :current_user) do
assign(conn, :current_user, current_user)
else
conn
end
end
end

What is this doing? Well, we start off by importing Plug.Conn into our module (so that we easily access the assign/get_session functions). We then declare our init function that takes in a default variable and immediately returns it back out. The meat of our plug lives in the call function, however. Any plug we write needs to implement both an init and call function, and our call function needs to accept the conn and optionally some options and return out a conn as well (modified or not).

If the current_user is able to get fetched out of the current session, then we turn around and throw that in the assigns so that anything that has access to the conn will also have access to the current user! If we can’t fetch it out, then we’ll just return an unmodified conn! Next, to implement our current_user plug globally, we’ll open up web/router.ex and insert it into our standard browser pipeline:

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Pxblog.CurrentUserPlug
end

And that’s it! Now in our layouts and templates, when we need to access the current logged-in user, we can just access @conn.assigns[:current_user]. Nice!

Refactoring Our Application Layout

We’re going to refactor our current application layout to make it easier for us to modify some of the shared parts of our layout that we’ll be reusing. We’ll start by our entire head section (everything between the <head> tags). Create web/templates/layout/head.html.eex and copy/paste our current head tag into that section:

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A Tech Blog Written in Elixir and Phoenix">
<meta name="author" content="Brandon Richey">
<%= if current_user = @conn.assigns[:current_user] do %>
<%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, "user", current_user.id) %>
<% end %>

<title>Phoenix Tech Blog</title>
<link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
</head>

For the most part we just cut/pasted the head tag and contents into a new file but we’re also going to refactor the current user bit and add information for our description/author/title tags. Instead of our previous system of using a helper function inside of LayoutView, we now check if current_user is set in our assigns, and if it is, use the current user as our setting for the meta tag! Next, we’ll refactor our the navigation top bar into its own template. Create web/templates/layout/navigation.html.eex:

<div class="top-bar">
<div class="top-bar-left">
<ul class="menu">
<li class="logo">
Phoenix Tech Blog
</li>
<li>
<%= link "Home", to: page_path(@conn, :index) %>
</li>
<li>
<%= link "Posts", to: post_path(@conn, :index) %>
</li>
<%= if current_user = @conn.assigns[:current_user] do %>
<li>
<%= link "My Posts", to: user_post_path(@conn, :index, current_user.id) %>
</li>
<li>
<%= link "New Post", to: user_post_path(@conn, :new, current_user.id) %>
</li>
<% end %>
</ul>
</div>
<div class="top-bar-right">
<ul class="menu">
<%= if current_user = @conn.assigns[:current_user] do %>
<li>
<%= link current_user.username, to: user_path(@conn, :edit, current_user.id) %>
</li>
<li>
<%= link "Logout", to: session_path(@conn, :delete, current_user.id), method: :delete, class: "button alert" %>
</li>
<% else %>
<li>
<%= link "Login", to: session_path(@conn, :new), class: "button" %>
</li>
<% end %>
</li>
</ul>
</div>
</div>

This is the first big of code that’s really using Foundation in a major way. We start off using Foundation’s “top-bar” class and various menus and left/right pulls. We rip out the old Phoenix logo and navigation bar completely and just use some simple text for our new logo. We’ll just call this a “Phoenix Tech Blog” for now for sake of simplicity. Otherwise we’re just building out some simple HTML with a couple of Foundation’s built-in styles/components. Again, any place that we were referencing the old style of fetching the current user we replace with the new style that takes advantage of our new Plug.

A Quick Rundown Of Foundation Classes Introduced

In the last example, we started using a few Foundation classes that I’ll just briefly explain here:

  1. top-bar is just a top navigation bar, as you’d expect. (Docs)
  2. top-bar-left is a left-pulling subsection of our top-bar. Similarly, top-bar-right is a right-pulling subsection.
  3. menu tells Foundation that we’re expecting this to be a menu of links. (Docs)
  4. button tells Foundation to use default Button styling on the link or button tag. (Docs)
  5. alert tells Foundation which color to use for the button. (Docs)

Continuing Our Refactor Of The Application Layout

Next, we’ll tackle the alerts, since that is also pretty frequently reused code. Let’s create web/templates/layout/alerts.html.eex:

<div class="row alerts">
<%= if flash = get_flash(@conn, :info) do %>
<div class="success callout alert-info" role="alert">
<%= flash %>
</div>
<% end %>

<%= if flash = get_flash(@conn, :error) do %>
<div class="alert callout alert-error" role="alert">
<%= flash %>
</div>
<% end %>
</div>

We have to modify everything a little more heavily here, since Phoenix by default makes certain expectations about CSS classes being used and their intentions. These changes make the alert code and styling a little more platform-agnostic.

We see if we can fetch the flash out of the conn, and if we can, then we display the flash div appropriately. If not, nothing will get displayed. This WILL include a blank div, though (which won’t affect our layout, just the source output).

A Quick Rundown Of Foundation Classes Introduced

  1. row tells Foundation that it is a container for its grid system. (Docs)
  2. callout creates a standard Flash div container look. (Docs)
  3. success provides a nice light green style for a successful callout. (Docs)
  4. alert provides a nice light red style for error messages. (Docs)

The Final Refactoring Of Our Application Layout

We have two partials remaining that we want to refactor/redesign. The first is our main content rendering portion. We’ll wrap this inside of a div with the classes of “row” and “content”:

<main role="main row content">
<%= render @view_module, @view_template, assigns %>
</main>

We’ll also create a new template to store the standard JavaScript includes at the bottom. Create web/templates/layout/script.html.eex:

<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
<script type="text/javascript">
$(function() {
$(document).foundation();
});
</script>

The major new addition here is the closure that includes a call to the foundation() function on the document object. This is required for Foundation’s built-in javascript helpers!

Changing Our Application Layout To Use Our Partials

Finally, we’ll take a look at our application layout after we’ve refactored out all of the shared content. Let’s modify web/templates/layout/app.html.eex:

<!DOCTYPE html>
<html lang="en">
<%= render "head.html", conn: @conn %>

<body>
<%= render "navigation.html", conn: @conn %>

<section class="main-content row expanded">
<%= render "alerts.html", conn: @conn %>

<div class="row content">
<%= render @view_module, @view_template, assigns %>
</div>
</section>

<%= render "script.html", conn: @conn %>
</body>
</html>

As you can see, our application layout file is really small and really reusable now! This is going to be VERY handy as we start to dive into creating a new layout that will be used for the index action on our PageController (which will become our splash page).

Creating A New Layout

Next, we’re going to create a new layout that we’ll call splash. Create web/templates/layout/splash.html.eex:

<!DOCTYPE html>
<html lang="en">
<%= render "head.html", conn: @conn %>

<body>
<%= render "navigation.html", conn: @conn %>

<section class="main-content row expanded">
<%= render "alerts.html", conn: @conn %>

<section class="text-center image-overlay row content">
<%= render @view_module, @view_template, assigns %>
</section>

<section class="about expanded row">
<div class="columns medium-12 large-4">
<div class="icon">
<i class="fi-like"></i>
</div>
<h2>What Powers This Blog</h2>
<p>
This blog is powered by <a href="http://elixir-lang.org">Elixir</a> and <a href="http://phoenixframework.org">Phoenix Framework</a> to
give us a super fast, super clean, and super fun to modify blog engine!
</p>
</div>
<div class="columns medium-12 large-4">
<div class="icon">
<i class="fi-comments"></i>
</div>
<h2>We Support Live Comments</h2>
<p>
By using the latest and greatest in client and server technology, we're able to offer a live, realtime commenting system with nearly no impact
to performance!
</p>
</div>
<div class="columns medium-12 large-4">
<div class="icon">
<i class="fi-pencil"></i>
</div>
<h2>We Support Markdown</h2>
<p>
We allow you to use <a href="https://daringfireball.net/projects/markdown/">Markdown</a> in all of your posts, which will make it simple to write
up each post in the program of your choice and import it into this blog!
</p>
</div>
</section>

<section class="more-info expanded row">
This is an example design for the Phoenix Blog Engine project detailed in my series of
<a href="https://medium.com/@diamondgfx/introduction-fe138ac6079d">Medium</a> posts.
This design is merely a starting template; you may want to go out and find your own
as well to make it look truly original! You're welcome to use the source code for this
project as well as the design as you wish! If you're interested in more, please find me
on the internet via:
<br />
<div class="social-media text-center">
<a href="http://twitter.com/diamondgfx"><i class="fi-social-twitter"></i></a>
<a href="https://medium.com/@diamondgfx/"><i class="fi-social-medium"></i></a>
</div>
</section>

<footer class="expanded row">
I'm a standard footer for your site! Maybe you can throw a copyright &copy; down here or something like that, or licensing! You know, whatever!
</footer>
</section>

<%= render "script.html", conn: @conn %>
</body>
</html>

There’s a lot going on here, but nearly all of it is just content with a little bit of styling here and there! Let’s walk through some of the other Foundation classes we’re using here:

  1. expanded when attached to a row makes the row take up the whole width of the screen (Docs)
  2. text-center is exactly what it sounds like: centered text. Similarly, text-left and text-right align the text to the left and right, respectively (Docs)
  3. fi-comments, fi-pencil, fi-like are all Foundation icons (Docs)
  4. columns tells Foundation that we’ll be setting up separate columns using their grid system (Docs)
  5. medium-12, large-4 are examples of using the responsive sizing models for grid systems. On medium devices it will take up 12 columns, and on large devices it will take up 4 columns. (You will also see examples using small-N where N is some number between 1 and 12) (Docs)

Finally, let’s tell Phoenix that we’re going to use the new splash layout only for our PageController. Open up web/controllers/page_controller.ex and at the top, add the following line (after our use statement):

plug :put_layout, "splash.html"

Creating A Non-Logged-In Posts Display

We should also create a way for users to be able to view the last N number of posts if they’re not logged in, since there’s no point having a fancy blog if no one can read any of the posts! First, we’ll open up web/router.ex and add an index-only resource for posts:

resources "/posts", PostController, only: [:index]

And then we’ll open up web/controllers/post_controller.ex and modify our index function and add a new pattern-matched index function, but first we’ll start with a modification to our “assign_user” plug at the top of the file:

plug :assign_user when not action in [:index]

And then our index function modifications:

def index(conn, %{"user_id" => _user_id}) do
conn = assign_user(conn, nil)
if conn.assigns[:user] do
posts = Repo.all(assoc(conn.assigns[:user], :posts)) |> Repo.preload(:user)
render(conn, "index.html", posts: posts)
else
conn
end
end

def index(conn, _params) do
posts = Repo.all(from p in Post,
limit: 5,
order_by: [desc: :inserted_at],
preload: [:user])
render(conn, "index.html", posts: posts)
end

The first call looks for the existence of “user_id” in our params; that’s how we know we’re fetching the specific post for a specific user. If it is called, we then reuse our assign_user plug to assign the user into the conn. Also, we’ll need to make sure we don’t try to preload users into a potentially blank array, so we’ll check to make sure the user assigns exists in conn before doing the rest of the work. If it doesn’t, we’ll just return out conn since that will include the invalid user redirection, flash, and halt.

The second index function is a little different. We want to fetch the most recent 5 posts, so we limit it to the most recent 5 posts and preload the users into each post. We don’t need to worry about comments since we don’t display the comments in the Post index. The post index also needs to change a bit using our new current_user modifications and to make the design a little better. Open up web/templates/post/index.html.eex:

<div class="row">
<div class="small-10 columns">
<h1>Posts</h1>
</div>
<div class="small-2 columns text-right">
<%= if current_user = @conn.assigns[:current_user] do %>
<%= link "New post", to: user_post_path(@conn, :new, current_user.id), class: "button expanded large" %>
<% end %>
</div>
</div>

<%= for post <- @posts do %>
<div class="row post">
<div class="small-12 columns title">
<h3><%= post.title %></h3>
</div>
<div class="small-12 columns">
<small>
Posted on <%= post.inserted_at %> by <%= link post.user.username, to: user_post_path(@conn, :index, post.user) %>
</small>
</div>
<div class="small-12 columns">
<%= markdown(post.body) %>
</div>
<%= if current_user = @conn.assigns[:current_user] do %>
<div class="small-12 medium-4 columns">
<%= link "Show", to: user_post_path(@conn, :show, post.user, post), class: "button expanded" %>
</div>
<div class="small-12 medium-4 columns">
<%= link "Edit", to: user_post_path(@conn, :edit, post.user, post), class: "button warning expanded" %>
</div>
<div class="small-12 medium-4 columns">
<%= link "Delete", to: user_post_path(@conn, :delete, post.user, post), method: :delete, data: [confirm: "Are you sure?"], class: "button alert expanded" %>
</div>
<% end %>
</div>
<% end %>

Everything beyond this point is just cleaning up the styles here and there. We covered the more significant changes, so I’ll leave it up to the reader to make whatever further modifications they want to clean up the design. Just in case, the code for this will be at https://github.com/Diamond/pxblog so you can check out the other modifications that were made if you want to bring everything in line with my own changes and design.

Fixing Our Tests

We changed some of the layout and functionality, so we’ll have a few broken tests here and there. First, open up our page controller test (test/controllers/page_controller_test.exs) and modify the only assertion to look for “Phoenix Tech Blog”.

Conclusion

When everything is all done, we should see the following for our new splash page (I’m logged in as the admin in the example below):

Our final layout!

And this is what we should see when we grab the Posts index as a non-logged in user (depending on what test data you have loaded):

Our non-splash layout!

This is now a far, far more professional-looking tech blog, and we’re using Zurb Foundation 6 with SASS, built-in jquery, and lots of other nice little bonuses.

Check out my new book!

Hey everyone! If you liked what you read here and want to learn more with me, check out my new book on Elixir and Phoenix web development:

I’m really excited to finally be bringing this project to the world! It’s written in the same style as my other tutorials where we will be building the scaffold of a full project from start to finish, even covering some of the trickier topics like file uploads, Twitter/Google OAuth logins, and APIs!

--

--