📜 ⬆️ ⬇️

Start learning Elixir right now! Translation of the entire series of articles is ready.


From the translator: “Elixir and Phoenix are a great example of where modern web development is heading. Already, these tools provide high-quality access to real-time technologies for web applications. Sites with increased interactivity, multiplayer browser games, microservices - those areas in which these technologies will serve a good service. The following is a translation of a series of 11 articles that describe in detail the aspects of development on the Phoenix framework that would seem such a trivial thing as a blog engine. But do not hurry to sulk, it will be really interesting, especially if the articles encourage you to pay attention to the Elixir or become its followers. ”


In this part we will talk more about layout than directly about Elixir, however, the article will be useful in that it will tell about the interaction with the Phoenix asset pipeline.


You can open the first part right now and create a working application on Phoenix in a few evenings and look at the Elixir in action. As well as get acquainted with web-sites, plug-ins, changesets and other scary words of functional web development :)


Where did we leave off


In the last part, we ended up writing tests for everything related to channels. This time there will be much less code on Elixir, instead of which we will learn how to use third-party libraries inside Phoenix applications. As a rule, Phoenix meets you with a very, very basic version of Bootstrap optimized for it. Let's move away a bit from this approach and replace it with another CSS framework (and will also use Sass).


A warning. The author of the article is definitely not a designer. The result may scare you, so try changing everything to your taste.


Why Foundation?


I would not like to start a debate on the topic of Bootstrap against the Foundation. Instead, let's just take Foundation, because the look of the site on it looks more interesting. Also for him you need to do a little less standard things, compared to Bootstrap. In addition, not many people tried to use the Foundation, so it will be so interesting!


Install the 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 in order to avoid jQuery and Foundation compatibility issues, the specific jQuery version (v2.2.4) is installed here. For more information, see this article .


You also need to correct brunch-config.js to accommodate the changes. Namely, connecting Foundation to Brunch, including loading the Sass-directories, configuring the Sass module, and installing jQuery as a global dependency 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', } } }; 

Then we need to add a directory for Sass files. Create a web/static/scss with 3 files inside:


  1. application.scss ;
  2. _settings.scss ;
  3. _custom.scss .

In the web/static/scss/application.scss file add:


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

In the file web/static/scss/_settings.scss add the contents of the file by reference .


In the web/static/scss/_custom.scss add:


 // 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 will also need to upload a picture and drop it into the web/static/assets/images/computer-bg.png so that the style file can use it as a background image.


Installing Icons and Fonts for the Foundation


If you want to connect a font with icons from the Foundation set, follow these steps:


  1. Download the archive .
  2. Unzip its contents.
  3. Copy the following files to the web/static/assets/fonts directory: foundation-icons.eot , foundation-icons.svg , foundation-icons.ttf , foundation-icons.woff .
  4. Copy the following file to the web/static/css directory: foundation-icons.css .
  5. Finally, modify the foundation-icons.css file foundation-icons.css paths for each font to the Phoenix foundation-icons.css 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 icons must be fully installed.


Remove standard styles


Open the file web/static/css/phoenix.css and delete everything that is in it! We do not need any of this, but it would be useful if we decided to use standard CSS.


We change work with the current user


Before continuing, I would like to do one more thing - to improve the code written earlier. For example, the way of working with the current user is a bit unreliable and requires you to write the same code over and over again. Let's add reusability to the code associated with the current user. First, create a new plug-in, which will be located in the web/controllers/current_user_plug.ex file:


 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's going on here? First, Plug.Conn imported into our module (so that you can easily access the assign and get_session ). The following describes the init function that accepts and then returns the default variable. The very essence of the plug is in the call function. Any plugin must implement two functions - init and call . The call function must accept conn and optional options, and return conn (modified or not).


If current_user is in the current session, then we get it and throw it inside the conn so that you can get access to the current user from everywhere. If you can't get it, then just return the unmodified conn . Then, to use the current_user plug-in globally, open the web/router.ex and paste it into the standard browser web/router.ex :


 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 

That's all! Now, when we need to refer to the current user inside the markup, we can simply call @conn.assigns[:current_user] . Wonderful!


Improving the main layout


We start refactoring of the main layout of the application to make it easier to make changes and easier to reuse common elements. Let's start with the head section (everything between the <head> tags). Create a web/templates/layout/head.html.eex and drag the head tag along with the contents into it:


 <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 simply copied the content of the head tag, but at the same time, we slightly simplified working with the current user and added some information to the description , author and title tags. Instead of the previous approach, when a helper from LayoutView , it is now checked whether the current_user inside assigns , and if it is, the current user is taken as a parameter for the channel meta tag.


Now we go to refactoring the layout of the top navigation bar. Create a web/templates/layout/navigation.html.eex file:


 <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 large piece of code in which Foundation is fully used. There is a class top-bar , as well as various variations of the menu and the right / left shifts. We completely cut out the standard Phoenix logo and navigation bar and instead use plain text as the logo. For simplicity, we will call our project Phoenix Tech Blog . Also build some simple HTML with a couple of styles and components built into the Foundation. Once again, in any place where the old way of getting the current user is used, you need to replace it with a new approach using the plug.


Brief description of the classes presented


The last example uses the following classes:


  1. top-bar - top navigation bar, as expected. ( Documentation )
  2. top-bar-left - the sub-section of the top panel shifted to the left. top-bar-right - subsection shifted to the right.
  3. menu - menu of links. ( Documentation )
  4. button - style for the standard button. ( Documentation )
  5. alert - the color of the button. ( Documentation )

Continuing refactoring


Now let's take a block of alerts, which are also quite often used code. Let's create a web/templates/layout/alerts.html.eex file:


 <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> 

Here we need to make a bit more edits, since Phoenix has specific expectations about the CSS classes used by default. These changes will make the warning block code and styles a bit more platform-independent.


If we can get a message from conn , then accordingly display it inside a div . If not, we do not display anything. An empty div will still appear in the HTML source code, but it will not affect the display.


Brief description of the classes presented


  1. row - the container for the grid system. ( Documentation )
  2. callout - a standard container with a flash message. ( Documentation )
  3. success - a nice light green style for a successful message. ( Documentation )
  4. alert is a nice light red style for error messages. ( Documentation )

Finish with refactoring layout


We have 2 parls left that need to be improved. The first is the main content. Let's wrap the insides of the diva into the row and content classes:


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

The second is the template for the inclusion of standard Javascript code below. Create a file web/templates/layout/script.html.eex :


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

The main refinement here is the closure that is enabled to call the foundation() function on the document object. This is required for Foundation JS helpers built into Foundation.


We add parshly to the model


Finally, let's take a look at the layout of the application after our refactoring and the general content. Let's change the 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, the layout of the application now looks much more accurate. It will be EXTREMELY convenient if we add a new layout for the index action from the PageController , which we call the "splash page", which means the welcome page.


Create a new layout


Create a new web / templates / layout / splash.html.eex layout file:


 <!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> 

A lot of things are being done here, but in essence it’s just content with a couple of styles here and there. Let's look at a few more classes from the Foundation:


  1. expanded - expands the row to the full width of the page. ( Documentation )
  2. text-center - speaks for itself: aligns the text in the center. Similarly, text-left and text-right align the text left and right, respectively. ( Documentation )
  3. fi-comments , fi-pencil , fi-like - icons from the Foundation set. ( Documentation )
  4. columns - reports that we want to use the block as a separate column. ( Documentation )
  5. medium-12 , large-4 are examples of using an adaptive mesh model. On screens of size medium layout will take 12 columns, and on screens of size large only 4 columns. (You will also see an example of using small-N , where N is a number between 1 and 12). ( Documentation )

Finally, let Phoenix know that we will use the new layout for the welcome screen in PageController . Open the file web/controllers/page_controller.ex and add the following line:


 plug :put_layout, "splash.html" 

Create a screen posts for guests


We also need to give guests the opportunity to view the latest N posts, as it would be foolish to make a cool blog that no one can read. First, open the web/router.ex and add a resource for posts with a single index action.


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

And then open the web/controllers/post_controller.ex , change the index function and add another index function using pattern matching. But first, change the assign_user at the top of the file:


 plug :assign_user when not action in [:index] 

Now change the index function:


 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 checks for the presence of user_id in the parameters. So we know that we get a certain post for a specific user. If we fall into this function, then the assign_user must be reused to assign the user to conn . In order not to load users for an empty array, we check in advance the presence of the user in assigns . Otherwise, we simply return conn , which will contain the redirection for a nonexistent user, a flash message and stop the request.


The second index function is slightly different. We need to get the 5 most recent posts, so that we limit their number and preload users into each post. Do not worry about comments, because they do not need to be displayed on the page with a list of posts. Here you also need to slightly optimize the use of current_user . To do this, open the file 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 %> 

All that we did before - this is where they cleaned up the styles. Just in case, the code for these lessons is here , so you can look at all the changes you have made, if you want to have everything one-to-one.


We fix the tests


We changed part of the markup and functionality, so the tests dropped. To fix them, open the file test/controllers/page_controller_test.exs and change the check to “Phoenix Tech Blog”.


Conclusion


When everything is ready, you should see a similar welcome page:


This is what the guest will see when he decides to take a look at the list of posts:


Now the blog looks much more professional. In addition, we used Zurb Foundation 6 via Sass, as well as jQuery and a bunch of other small bonuses.


Other series articles


  1. Introduction
  2. Authorization
  3. Adding Roles
  4. Process roles in controllers
  5. We connect ExMachina
  6. Markdown support
  7. Add comments
  8. We finish with comments
  9. Channels
  10. Channel testing
  11. Conclusion

Conclusion from the translator


Hooray! Great work has been done, which has finally come to an end. Now everyone interested in Elixir and Phoenix has a pretty good introductory course that will help to get acquainted with the technology.


A few dozen articles on the Elixir in Russian you can find on the site of our project . - , - , . !


, , , . , , , , , , , , .


!


')

Source: https://habr.com/ru/post/335048/


All Articles