Skip to content

Latest commit

 

History

History
304 lines (228 loc) · 12.1 KB

B_添加页面.md

File metadata and controls

304 lines (228 loc) · 12.1 KB

添加页面

我们的目标是给我们的 Phoenix 项目增加两个新的页面,一个是纯静态页面,另一个则从 url 里面截取一部分作为输入, 然后传递给模板显示。从这两个简单的例子中我们将会熟悉一个 Phoenix 项目的基本构成: 路由,控制器,视图以及模板。

当我们用命令产生一个 Phoenix 项目后,默认的目录结构如下:

├── _build
├── assets
├── config
├── deps
├── lib
│   └── hello
│   └── hello_web
│   └── hello.ex
│   └── hello_web.ex
├── priv
├── test

我们这里涉及的大部分内容都在 lib/hello_phoenix/web 目录下,也就是和 web 相关的 部分,它的结构展开如下图:

├── channels
│   └── user_socket.ex
├── controllers
│   └── page_controller.ex
├── templates
│   ├── layout
│   │   └── app.html.eex
│   └── page
│       └── index.html.eex
└── views
│   ├── error_helpers.ex
│   ├── error_view.ex
│   ├── layout_view.ex
│   └── page_view.ex
├── endpoint.ex
├── gettext.ex
├── router.ex

controllers, templatesviews 目录里的所有文件都是用于创建我们之前看到的那个 Welcome to Phenix 欢迎页面的,我们之后将重用这里的一些代码。在开发模式下,每一 次新的请求到达,web 目录都会自动重新编译。

我们应用所需的所有静态资源都在 assets 目录下(按照css,images或者js分类), 编译 过程(brunch或其他前端工具)会把这里对应的 js, css 文件分别编译到 priv/static , 我们现在不会深入,只是留个印象。

├── assets
│   ├── css
│   │   └── app.css
│   ├── js
│   │   └── app.js
│   └── static
│   └── node_modules
│   └── vendor

我还需要了解一下 "非 web 相关的部分"。我的应用文件(负责启动我们的 Elixir 应用和 监测树)位于 lib/hello/application.ex. 以及 Ecto Repo lib/hello_phoenix/repo.ex 负责和数据库交互. 我们会在后面的章节介绍 Ecto.

lib
├── hello
|   ├── application.ex
|   └── repo.ex
├── hello_web
|   ├── channels
|   ├── controllers
|   ├── templates
|   ├── views
|   ├── endpoint.ex
|   ├── gettext.ex
|   └── router.ex

我们的 web 相关的文件 -- 路由,控制器,模板,通道等都在 lib/hello_web 目录下,而其他的部分都在 lib/hello/ 下,你可以像其他任何的 Elixir 应用 一样来组织他们。

唠叨的差不多了,现在让我们编写一个新的 Phoenix 页面吧!

一个新路由

路由将给定并唯一的 HTTP 动词/路径(verb/path) 映射到处理他们的 controller/action , Phoenix的路由 配置信息在 lib/hello_web/router.ex 文件,让我们打开来看看。

欢迎页面对应的路由是这条语句:

get "/", PageController, :index

让我们看看这代表什么意思呢,浏览器访问 http://localhost:4000/ 会给我们网站的根 目录发出一个GET请求,这个请求会被 HelloPhoenix.pageController 中的 index 函数处理,后者定义在 lib/hello_web/controllers/page_controller.ex文件中.

我们要建立的页面功能是:当浏览器访问 http://localhost:4000/hello 时,简单 的在页面上显示 'Hello World from Phoenix'.

首先我们要为这个页面增加一个路由,现在lib/hello_phoenix/web/router.ex看起来如下:

defmodule HelloWeb.Router do
  use HelloWeb, :router

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

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", HelloWeb do
  #   pipe_through :api
  # end
end

现在,我们先不管 pipelinesscope 部分(如果你好奇的话,可以阅读 路由指北) ).

让我们为 /helloGET 请求创建一个路由吧, 在稍后创建的HelloWeb.HelloController 的index 部分。

get "/hello", HelloController, :index

添加后,router.ex 文件的 scope '/' 部分看起来应该是这个样子:

scope "/", HelloPhoenix do
  pipe_through :browser # Use the default browser stack

  get "/", PageController, :index
  get "/hello", HelloController, :index
end

一个新控制器

控制器 里面其实就是 Elixir 的模块、action 和定义在其中的Elixir 函数,action 的作用是 收集和处理渲染页面所需的数据和某些操作 (gather any data and perform any tasks needed for rendering )。 我们现在要建立一个模块 HelloPhoenix.HelloController, 里面包含 index/2 这个action ( /2,意思是需要两个参数的意思)。

具体的,我们建立文件 lib/hello_phoenix/web/controllers/hello_controller.ex, 然后加入以下内容:

defmodule HelloWeb.HelloController do
  use HelloWeb, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

我们会在 控制器指北 章节中详细讨论 HelloPhoenix.Web, :controller 语句,现在我们先来看看 index/2 函数。

所有的 controller actions 都接受两个参数,第一个是 conn--一个包含了大量请求(request)信息的结构体。 第二个是params--请求参数,我们的例子不需要params, 所以加一个前缀 _表示忽略该参数,否则编译过程会产生警告。

这个 action 的核心语句是 render conn, "index.html", Phoenix 会根据这条语句寻找一个叫做 index.html.eex 的模板并且渲染出来,查找规则是遵循 controller 的命名,即 lib/hello_web/templates/hello

注意 这里用原子(atom)的简写法也是可以的,比如render conn, :index,这是渲染机制就会根据请求头的规则自动寻找合适的模板,如 index.html 或者 index.json等等。

负责具体渲染的模块是视图(views),我们现在就来建立一个。

一个新的视图

视图(views) 负责几个重要的工作,它渲染模板,并且作为 controller裸数据 (raw data)展示层(presentation layer), 以及一些有用的函数将这些数据处理后供模板使用。

举个栗子,比如我们有一个包含了 first_name 字段和 last_name 的用户数据,然后在模板里,我们想显示这个 用户的全名。我们可以在模板里写函数把它们拼接起来,但更好的办法是在 视图(view) 里写一个函数 去做这件事,然后在模板里调用这个函数,这种解决方案会让模板变得干净且更加灵活。

为了能让我们的 HelloController 渲染模板,我们需要一个 HelloView。这里的命名同样具有特殊意义-- 'Hello' 要和 控制器的'Hello'相对应,让我们先创建一个 lib/hello_web/views/hello_view.ex 文件(稍后解释细节),内容如下:

defmodule HelloWeb.HelloView do
  use HelloWeb, :view
end

一个新模板

Phoenix 默认使用的模板引擎是eex,意思是 嵌入式 Elixir (Embedded Elixir), 所以我们的模板文件都会带有.eex后缀。

模板被视图所限定,视图又被控制器所限定,Phoenix 为我们创建了一个 lib/hello_phoenix/web/templates 目录来容纳这些文件,最佳实践就是使用命名空间来组织它们。所以在我们这个hello应用 中,这意味着我们需要在lib/hello_phoenix/web/templates目录下面建立一个hello文件夹,再在里面建立一 个index.html.eex文件,内容如下。

<div class="jumbotron">
  <h2>Hello World, from Phoenix!</h2>
</div>

现在我们有了路由控制器,视图模板,我们应该可以通过浏览器访问 http://localhost:4000/hello 看到新的欢迎页面了!(如果你之前停止了服务器可以通过 mix phoenix.server 重新启动)。

Gyazo

这里有些知识点需要注意:首先,改动代码以后,我们并不需要重启服务器,Phoenix 有代码热更新 ( hot code re-loading) 机制。另外, 尽管我们的 index.html.eex 文件只包含了一个div标签,我们依然得到了一个"完整的"页面,我们的模板被渲染进了应 用布局(application layout) -- lib/hello_web/templates/layout/app.html.eex。 如 果你打开它,就会发现有这样一行内容<%= render @view_module, @view_template, assigns %>, 意思在被送往浏览器之前,你自己的模板会被"插入"到这里。

另一个新页面

让我们为我们的应用增加一点复杂性。我们建立一个新的页面,她的功能是识别我们url的一部分,将她作为'信息'通过 给控制器传递给模板,然后显示出来。

就像我们之前做的那样,先添加一个路由。

新的路由

在这个练习中,我们复用之前创建的 HelloController 然后添加一个新的 show action, 增加的路由如下:

scope "/", HelloWeb do
  pipe_through :browser # Use the default browser stack.

  get "/", PageController, :index
  get "/hello", HelloController, :index
  get "/hello/:messenger", HelloController, :show
end

注意 这个原子写法::messenger, Phoenix 会把 messenger 作为键,并把用户输入在这个地址后的任何输入作为值,组成一个Map 传递给 controller。

比如,如果我们在浏览器输入 http://localhost:4000/hello/Frank, ":messenger" 的值就会是 "Frank" 。

新的 action

新的请求会被 HelloWeb.HelloControllershow action 处理,我们复用之前的 lib/hello_web/controllers/hello_controller.ex文件, 在其中添加 show action 如下(注意其中字典的写法):

def show(conn, %{"messenger" => messenger}) do
  render conn, "show.html", messenger: messenger
end

注意 如果你想要得到 params 的所有参数并绑定 messenger 变量,我们可以这样定义(类似js es6 的解构写法):

def show(conn, %{"messenger" => messenger} = params) do
  ...
end

注意所有的 keys 都是字符串,还有这里的 不是赋值符号,而是模式匹配(pattern match)

新的模板

终于到最后一道菜了,一个新的模板。根据命名规则,Phoenix会去 web/templates/hello 文件夹下寻找 show.html.eex文件。她和之前的 index.eex 模板长的很像,除了一个 Elixir 表达式:<%= %> , 注意标签里有个<%=符号。这表示这个便签里的 Elixir 代码会被执行并且结果会覆盖这个标签,如果我们不 加 = , 里面的 Elixir 代码依然会执行,但是结果却不会出现在页面上。

show.html.eex文件的内容如下:

<div class="jumbotron">
  <h2>Hello World, from <%= @messenger %>!</h2>
</div>

这里的@messenger并不是什么模块的属性,而是一个元编程的语法糖,表示 Map.get(assigns, :messenger)。只是这种写法对于模板来说更加友好。

一切就绪,现在让我们打开 http://localhost:4000/hello/Frank 来检验成果吧!

Gyazo

现在无论的在 /hello/ 后面输入什么,它都会出现在页面上了。