/weber

Web framework for Elixir inspired by Rails

Primary LanguageElixirMIT LicenseMIT

Weber

Weber - is a MVC Web framework for Elixir.

weber-256

Join the Community

#WeberMVC on freenode IRC

Build Status

Features

  • MVC web framework;
  • Project generation;
  • Json generation with exjson;
  • Websocket support;
  • HTML helpers;
  • Web controller Helpers.
  • i18n support;
  • Sessions support;
  • weber-contrib

Quick start

  1. Get and install Elixir from master.
  2. Clone this repository.
  3. Execute make && make test in the weber directory.
  4. Create new project with: mix weber.new /home/user/testWebApp or mix weber.new /home/user/testWebApp --grunt for Grunt integration.

Now go to the /home/user/testWebApp and execute there: mix deps.get && mix compile --all --force. Then you can try to run your testWeberApplication with:

./start.sh

or run it in daemon mode:

./start.sh --no-shell

and go to the http://localhost:8080/

For more details see in examples directory and Weber's API.

Directory structure

Dir/File Description
./start.sh Startup script
./lib/controllers Directory with web controllers
./lib/helpers Helper functions
./lib/models Directory for models (ecto)
./lib/views Directory with EEx views
./lib/app.ex Application startup settings
./lib/config.ex Configuration file.
./lib/route.ex File with routes declaration
./public Directory for static files (css, js ....)

Routing

Routing declaration is in route.ex files:

    route on("GET", "/", :Simpletodo.Main, :action)
       |> on("POST", "/add/:note", :Simpletodo.Main, :add)
       |> redirect("GET", "/redirect", "/weber")
       |> on("ANY", %r{/hello/([\w]+)}, :Simpletodo.Main, :action)

Also on supports following syntax:

    route on("GET", "/", "Simpletodo.Main#action")
       |> on("POST", "/add/:note", "Simpletodo.Main#add")

It is route macro which value is chain of on functions with 3 parametes:

  • Http method
  • Route path, can be binding (starts with ':' symbol);
  • Module name of controller;
  • Function name from this controller.

Http method can be:

  • "GET"
  • "POST"
  • "PUT"
  • "DELETE"
  • "PATCH"
  • "ANY"

You can set up resource in routing:

    route resource(:Controller.Work)  

It will be the same as

route on("GET",    "/controller/work/:id",        :Controller.Work, :show)
   |> on("POST",   "/controller/work/:id/create", :Controller.Work, :create)
   |> on("GET",    "/controller/work/:id/edit,    :Controller.Work, :edit)
   |> on("PUT",    "/controller/work/:id/update,  :Controller.Work, :update)
   |> on("DELETE", "/controller/work/:id/delete,  :Controller.Work, :delete)

Build url from code

You can build url from your elixir code with:

import Weber.Route

route on("GET", "/", "Simpletodo.Main#action")
   |> on("POST", "/add/:note", "Simpletodo.Main#add")

# generates: /add/1 
link(:Elixir.Simpletodo.Main, :add, [note: 1])

Controllers

Every Weber's controller is just an elixir module, like:

defmodule Simpletodo.Main do

  import Simplemodel

  use Weber.Controller

  layout false

  def action(_, conn) do
    {:render, [project: "simpleTodo"], []}
  end

  def add([body: body], conn) do
    new(body)
    {:json, [response: "ok"], [{"Content-Type", "application/json"}]}
  end

end

Every controller's action passes 2 parameters:

Controller can return:

  • {:render, [project: "simpleTodo"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]} - Renders views from views/controller/action.html and sends it to response. Or without headers. {:render, [project: "simpleTodo"]}
  • {:render_inline, "foo <%= bar %>", [bar: "baz"]}} - Renders inline template.
  • {:render_other, Elixir.Views.Index, [foo: "foo"], []} - Renders any view in controller. Or without headers.
  • {:file, path, headers} - Sends file in response. Or without headers {:file, path}
  • {:json, [response: "ok"], [{"HttpHeaderName", "HttpHeaderValheaderVal"}]} - Weber converts keyword to json and sends it to response. Or without headers: {:json, [response: "ok"]}
  • {:redirect, "/main"} - Redirects to other resource.
  • {:text, data, headers} - Sends plain text. Or without headers: {:text, data}
  • {:nothing, ["Cache-Control", "no-cache"]} - Sends empty response with status 200 and headers.

Request params

Sometimes it is necessary for the request parameters in the controller. For this point can be used Weber.Http.Params API.

defmodule Simplechat.Main.Login do

  import Weber.Http.Params

  use Weber.Controller

  layout false

  def render_login([], conn) do
    # get body request
    body = get_body(conn)
    #
    # Do something with param
    #
    {:render, [project: "SimpleChat"]}
  end

end

If you need to get parameters from query string, it is easy to do with param/1 API. For example you got request for: /user?name=0xAX, you can get name parameter's value with:

defmodule Simplechat.Main.Login do

  import Weber.Http.Params

  use Weber.Controller

  def render_login([], conn) do
    name = param(:name, conn)
    #
    # Do something with param
    #
    {:render, [project: "SimpleChat", name: name]}
  end

end

You can find the full API at the wiki.

Before/After request hooks

You can define __before__ or after __after__ hooks in your controller. It will pass two parameters:

  • :action - action name
  • conn - connection parameter
defmodule Simplechat.Main.Login do

  def render_login([], conn) do
    {:render, [project: "SimpleChat", name: "WeberChat"]}
  end

  #
  # Executes before request
  #
  def __before__(:render_login, conn) do
    conn
  end
  
  #
  # Execute after response
  #
  def __after__(:render_login, conn) do
    conn
  end

end

Helper

Html Helper

Html helpers helps to generate html templates from elixir:

defmodule Simpletodo.Helper.MyHelper
  import Weber.Helper.Html

  # Generates <p>test</p>
  def do_something do
    tag(:p, "test")
  end

  # Generates <p class="class_test">test</p>
  def do_something do
    tag(:p, "test", [class: "class_test"])
  end

  # Generates <img src="path/to/file">
  def do_something do
    tag(:img, [src: "path/to/file"])
  end
end

Tags with blocks

defmodule Simpletodo.Helper.MyHelper
  import Weber.Helper.Html

  # Generates <div id="test"><p>test</p></div>
  def do_something do
    tag(:div, [id: "test"]) do
      tag(:p, "test")
    end
  end
end

Include view in your html

Include view helper helps to include other views inside another.

Import in your controller.

import Weber.Helper

Your view.

<p>Test</p>
<%= include_view "test.html", [value: "value"]%>

Resource Helpers

You can include your static resources like javascript, css, favicon or image files with resource helpers:

#
# Generates: <script type="text/javascript" src="/static/test.js"></script>
script("/static/test.js")
# If no value is passed for src it defaults to "/public/js/application.js"
script()

#
# Generates: <link href="/static/test.css" rel="stylesheet" media="screen">
#
style("/static/test.css")
# If no value is passed for href it defaults to "/public/css/application.css"
style()

#
# Generates: <link href="/public/img/favicon.ico" rel="shortcut icon" type="image/png">
favicon("/public/img/favicon.ico")
# If no value is passed for href it defaults to "/public/img/favicon.ico"
favicon()

#
# Generates: <img src="/public/img/example.jpg" alt="Image" class="some-class" height="100" width="100">"
image("/public/img/example.jpg", [alt: "Image", class: "some-class", height: 100, width: 100])

#
# Generates: <audio src="/public/audio/sound">
audio("/public/audio/sound")

#
# Generates: 
#  <audio autoplay="autoplay">
#    <souce src="/public/audio/sound1"></souce>
#    <souce src="/public/audio/sound2"></souce>
#  </audio>
#
audio(["/public/audio/sound1", "/public/audio/sound2"], [autoplay: true])

#
# Generates: <video src="public/videos/trailer">
video("public/videos/trailer")

#
# Generates:
#  <video height="48" width="48">
#    <souce src="/public/videos/video1"></souce>
#    <souce src="/public/videos/video2"></souce>
#  </video>
video(["/public/videos/video1", "/public/videos/video2"], [height: 48, width: 48])

Controller Helpers

content_for_layout and layout

NOTE: Now all views and layout files must start with capital letter.

All controllers got main.html by default for views, but you'd might change it.

You can create custom layout for you controller:

Create Layout.html in the lib/views/layouts directory and put there:

<!DOCTYPE html>
<html>
  <head>
    <title>
      My Project
    </title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <div id="container">
    <%= @content_for_layout %>      
    </div>
  </body>
</html> 

Than declare layout helper in your controller:

defmodule TestController.Main do

  use Weber.Controller

  layout "Layout.html"
  
  #
  # Here are some actions
  #

end

And you have lib/views/Main.html with:

Hello World!

Weber puts lib/views/Main.html content inside <%= content_for_layout %> and renders it in the response.

Logging

Weber uses exlager for the logging. For using it just set up:

log: true

in your config and use it:

defmodule LogTest.Main do
  
  require Lager
 
  def action([], _conn) do
    Lager.info "New request"
    {:render, []}
  end
 
end

Internationalization

Important Experemental now

See - Weber Internationalization

{
  "HELLO_STR" : "Hello, It is weber framework!", 
  "FRAMEWORK_DESCRIPTION" : "Weber - is a MVC Web framework for Elixir."
}

and you can use it like:

<span><%= t(@conn, "HELLO_STR") %></span>

in your html template.

Websocket

You can handle websocket connection and incoming/outcoming websocket message in your controllers.

First of all you need to designate websocket controller in your config.ex file in webserver: section, like:

ws:
  [
   ws_mod: :Handler
  ]

After it you must implement 3 callbacks in your controller like this:

defmodule Simplechat.Main.Chat do

  def websocket_init(pid, conn) do
    #
    # new websocket connection init
    #
  end

  def websocket_message(pid, message, conn) do
    #
    # handle incoming message here
    #
  end

  def websocket_terminate(pid, conn) do
    #
    # connection terminated
    #
  end

end

All websocket connections are must start with prefix /_ws/.

Session

Session API

Dependencies

Contributing

See Contributing.md

Additional info

Author

@0xAX.