1. Node.js应用:MVC 框架 – Adonis

本文由清尘发表于2019-04-03 16:04最后修改于2020-05-26属于Node.js分类

相关文章:

1. Node.js应用:MVC 框架 – Adonis
2. Node.js 应用:数据库
3. Node.js 应用:模型

=====================================

Tgit私有仓库:

HTTP: https://git.code.tencent.com/testpro/shine-adonis.git
SSH:git@git.code.tencent.com:testpro/shine-adonis.git

1. Node.js:MVC 框架 – Adonis

安装工具:@adonisjs/cli

执行一下 node -v ,可以查看当前正在使用的 node 版本 .. 至少要是 8 以上的 node .. 再执行一下 npm -v ,会返回 npm 工具的版本 .. 这个 npm 至少得是 3 以上的版本 ..

npm install @adonisjs/cli --global

完成以后执行

adonis --help

会给我们返回一些帮助的信息 .. 使用这个命令行工具,我们可以去创建项目 .. 启动项目的服务器 .. 它还可以创建新的命令行工具,创建数据库的 migration 等等 ..

创建并运行应用

进入要放置项目的目录下执行

adonis new shine-adonis
cd shine-adonis
adonis serve --dev

成功以后,这里会提示服务器的地址 … 默认是 http://127.0.0.1:3333

路由:Routes

打开项目下面的 start 里的 routes.js ,在这里我们可以定义应用需要的路由 .. 创建的这个默认的项目里面,已经定义好了一个路由,地址是 / ,表示应用的根 .. 这里用了 render 方法,意思是用户访问应用的根,我们用一个视图来响应用户的请求 .. 这个视图的名字就是 welcome ..

在 resources .. views 这里存储的就是应用的视图 .. 这里你会看到一个 welcome.edge,这就是应用的根这个路由使用的一个视图 ..

也就是我们打开这个 127.0.0.1:3333 看到的这个界面 .. 其实就是 welcome 这个视图里的东西 .. 视图文件的扩展名是 .edge,因为 adonis 用的模板引擎叫 edge ..

修改start/routes.js 增加一个自己的路由

......

Route.on('/').render('welcome')

Route.get('/hello',({request}) => {
  return `hello ~ ${ request.input('name')}`
})

访问http://localhost:3333/hello?name=shine 可以看到返回的信息

控制器:Controllers
在项目目录下执行

adonis make:controller Hello

选择HTTP 创建一个控制器,提示文件放在 app\Controllers\Http\HelloController.js
修改一下这个控制器

'use strict'

class HelloController {
  render ( { request}){
    return `hello ~ ${ request.input('name')}`
  }
}

module.exports = HelloController

现在可以用控制器来控制路由的显示
修改start/routes.js

const Route = use('Route')

Route.on('/').render('welcome')
Route.get('/hello','HelloController.render')

访问 http://localhost:3333/hello?name=shine 可以看到返回的信息

视图:Views

执行一下,创建一个视图

adonis make:view hello

提示文件放在 resources\views\hello.edge

修改app/Controllers/Http/HelloController.js 控制器,用视图渲染模板

'use strict'

class HelloController {
  render ( { request,view}){
    const name = request.input('name')
    return view.render('hello',{name})
  }
}

module.exports = HelloController

修改视图文件 resources/views/hello.edge

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>hello ~ {{name}}</title>
</head>
<body>
  <h1>hello ~ {{name}}</h1>
</body>
</html>

访问地址 http://localhost:3333/hello?name=shine 可以看到视图返回的HTML

数据库:Database

adonis 默认支持很多关系型数据库,PostgresQL,MySQL,MariaDB .. Oracle . SQLite,MSSQL ..
默认它会使用 sqlite 数据库 .. 相关的配置是在 config .. database.js 这里 .. 默认的设置是 sqlite ..
你可以把它换成你想用的数据库管理系统 .. 然后下面具体再配置一下 …
这里我们先用默认的 sqlite ..
下面还得再去安装一个 sqlite 驱动 .. 回到命令行 .. 在项目的下面 .. 执行一下

adonis install sqlite3

也可以直接使用 npm install 去安装

npm install sqlite3 --save

创建一个数据表 .. 这里需要用的是 adonis 的 migration 功能 .. 先去创建一个 migration .. adonis make 一个 migration .. 名字是 Post

adonis make:migration Post

选择 Create table .. 创建表 .. 命令会给我们创建一个 migration 用的文件 .. 放在了 database/migrations 的下面 ..
打开 刚刚创建的文件,增加title 和 content两个字段

......
up () {
  this.create('posts', (table) => {
    table.increments()
    table.string('title')
    table.text('content','longtext')
    table.timestamps()
  })
}
......

再到命令行执行

adonis migration:run

这样会在数据库里给我们创建好需要的数据表

数据库:插入与查询数据
修改一下start/routes.js

......
const Database = use('Database')
const Route = use('Route')

Route.on('/').render('welcome')
Route.get('/hello','HelloController.render')

Route.get('/posts',async () => {
  return await Database.table('posts').select('*')
})
......

再访问一下 http://localhost:3333/posts 暂时数据库里还没有数据
下面我们直接在命令行下面去插入两条数据 .. 先执行一下

adonis repl 

.. 打开命令交互模式 .. 插入几条数据

await use('Database').table('posts').insert({title:'apple',content:'苹果'})
await use('Database').table('posts').insert({title:'lemon',content:'柠檬'})

再回到浏览器 访问一下 http://localhost:3333/posts
返回是 json 格式的数据 .. 现在这里会返回两条数据 .. 它们来自我们应用的数据库里的 posts 这个数据表 …

=============================

Node.js 应用:路由

为项目创建了一个新的git分支 routes

路由方法:post

修改start/routes.js 增加一个post路由

const Route = use('Route')

Route.on('/').render('welcome')

Route.get('/posts',() => 'List of posts.')
Route.post('/posts',() => 'Post has been created.')

POST访问一下http://localhost:3333/posts
这次服务端返回了一个 403 的状态,表示请求被禁用了 .. 会提示 Invalid CSRF token .. 这是因为在我们的应用里,启用了跨站身份欺诈的保护功能 .. 以后我们会介绍到这个 CSRF ..

打开start/kernel.js 这里的东西是应用使用的 Middleware,中间件
暂时先把这个 Shield 中间件注释掉 ..
然后再到客户端这里试一下 … 这回会返回服务器响应的文字 .. Post has been created ..

路由参数:Parameters
修改start/routes.js

......
const Route = use('Route')

Route.on('/').render('welcome')

Route.get('/posts',() => 'List of posts.')
Route.post('/posts',() => 'Post has been created.')
Route.get('/posts/:id',({params}) => {
  return `You're watching post ${params.id}.`
})
......

GET请求访问一下 http://localhost:3333/posts/33 可以看到返回的数据

路由方法:put/patch 与 delete

如果客户那里要提交修改内容的请求,它应该使用 PUT 或者 PATCH 方法发出请求 .. 在我们的后端应用里面,要定义支持使用这些方法发送的请求
修改start/routes.js 增加一个路由

...... 
Route.patch('/posts/:id',({params}) => {
  return `Post ${params.id} has been updated.`
})

使用REST客户端PATCH访问一下 http://localhost:3333/posts/33 可以看到返回的数据

在客户端那里如果想删除在我们的后端应用里的数据 .. 要发出 DELETE 类型的请求 .. 这里我们再用一下 Route 的 delete 方法 .. 定义一个支持 DELETE 类型的请求
修改start/routes.js

......
Route.delete('/posts/:id',({params}) => {
  return `Post ${params.id} has been removed.`
})

绑定控制器:Controllers
一般我们不会把处理请求的业务逻辑代码直接放在路由的处理器里面 .. 尽量让定义的路由保持简单 .. 复杂的业务逻辑代码,可以交给 Controller,也就是控制器去处理

创建一个控制器

adonis make:controller Post

修改这个控制器app/Controllers/Http/PostController.js 增加一个index方法

'use strict'

class PostController {
  index () {
    return `List of posts.`
  }
}

module.exports = PostController

修改start/routes.js 使用控制器返回数据

......
Route.get('/posts','PostController.index')
......

GET请求访问一下http://localhost:3333/posts 现在数据就是在控制器里面返回的

创建资源的路由与控制器方法

修改控制器 app/Controllers/Http/PostController.js

'use strict'

class PostController {
  index () {
    return `List of posts.`
  }

  store(){
    return `Post has been created.`
  }

  show ({params}){
    return `You're watching post ${params.id}.`
  }

  update({params}){
    return `Post ${params.id} has been updated.`
  }

  destroy({params}){
    return `Post ${params.id} has been removed.`
  }

  create(){
    return `Create post.`
  }

  edit({params}){
    return `Editing post ${params.id} .`
  }

}

module.exports = PostController

修改start/routes.js

......
Route.on('/').render('welcome')

Route.get('/posts','PostController.index')
Route.post('/posts','PostController.store')
Route.get('/posts/create','PostController.create')
Route.get('/posts/:id','PostController.show')
Route.patch('/posts/:id','PostController.update')
Route.delete('/posts/:id','PostController.destroy')
Route.get('/posts/:id/edit','PostController.edit')

创建资源路由的简单方法:Route.resource
像之前我们为某个资源定义的这些路由,这种形式很常见 .. 所以有个更简单的方法为资源定义路由 … 用一下 Route 的 resource 方法去定义一个资源路由 ..
修改start/routes.js

......
Route.on('/').render('welcome')

Route.resource('posts','PostController')

样就可以把上面定义的所有这些跟 posts 资源相关的路由删除掉 .. 只用一个 resource 路由就行了 ..

有时候我们的资源只需要提供一些接口,就是请求资源的时候只返回相关的数据,数据的格式一般是 json .. 比如在创建前端应用,或者移动端应用的时候,可以使用资源的接口 ..

这种情况,我们可能就不再需要创建,还有编辑资源用的表单了 .. 这样在定义这种资源路由的时候,可以再用一下 apiOnly .. 它会去掉创建还有编辑资源用的路由 ..

start/routes.js

......
Route
  .resource('posts','PostController')
  .apiOnly()

或者,我们也可以用一个 only,明确地设置一下需要的路由 .. 一个数组 .. 比如需要一个 index .. 还有 show ..

这样就只能请求资源的列表,还有单个资源了 .. 对资源的其它类型的请求都会返回 404 …

也可以使用一下 except 方法 .. 设置一下不需要的路由 .. 同样是个数组 .. 里面可以设置一下不需要的路由 .. 比如添加一个 index .. 这样在客户端那里除了不能请求 post 资源的列表以外,其它的事情都可以做 ..

start/routes.js

Route.on('/').render('welcome')

Route
  .resource('posts','PostController')
  .except(['index'])
  // .only(['index','show'])
  // .apiOnly()

命名路由:Named routes

start/routes.js

...... 
Route.get('/users',() => 'List of users.').as('users.index')

resources/views/welcome.edg

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Hello Adonis</title>
  {{ style('style') }}
</head>
<body>
  <section>
      <a style="color:#fff" href="{{route('users.index')}}">List of users.</a>
  </section>
</body>
</html>

访问一下http://localhost:3333/ 点击链接跳转到路由, 返回信息

路由格式:Route formats

Route formats,是路由格式 .. 在定义路由的时候你可以设置可以使用的格式 .. 比如 json,html 等等 .. 然后在路由的处理器或者控制器的方法里面,我们可以判断用户请求的路由格式 .. 然后根据不同的格式,做出不同的响应 ..

start/routes.js

Route.get('/users',({request})=> {
  switch (request.format()){
    case 'json':
      return [
        {name:'shine1'},
        {name:'shine2'}
      ]
    default:
      return `
        - shine1
        - shine2
      `
  }
}).formats(['json'])

GET请求访问一下http://localhost:3333/users 没有设置格式默认返回字符串
再访问一下 http://localhost:3333/users.json 返回json数据

如果要强制请求的时候设置路由格式 .. 可以给这个 formats 方法的第二个参数设置成 true … 在格式这里,再添加一个 html ..

回到客户端 .. 请求一下 users .. 这次会返回 404 状态码 ..

再试一下 .. 地址里添加一个 .json .. 会返回一个 json 数据 .. 再把格式换成 html 试一下 … 这次返回的是普通的文字

...... 
Route.get('/users',({request})=> {
  switch (request.format()){
    case 'json':
      return [
        {name:'shine1'},
        {name:'shine2'}
      ]
    default:
      return `
        - shine1
        - shine2
      `
  }
}).formats(['json','html'],true)

路由群组:Route groups

如果有一些路由,需要共享一些特性,比如使用同样的地址前缀,中间件,域名,或者格式 .. 我们可以把这些路由放在一个群组里,然后统一去设置这组路由的特性 ..

用一下 Route 的 group .. 给它一个函数参数 .. 在这个函数里面可以定义一些路由 .. 比如我们用 Route 的 get 定义一条路由,地址是 users .. 路由直接返回一串文字 .. Manage users ..

Route.group( () => {
  Route.get('users',() => 'Manage users')
  Route.get('posts',() => 'Manage posts')
}).prefix('admin')

访问 http://localhost:3333/admin/users 和 http://localhost:3333/admin/posts 返回数据

单页面应用的路由

如果我们打算为应用再配一个前端应用, 那我们的后端应用可以只负责接口部分,就是只为前端应用提供数据或者前端发过来的数据 ..

这样应用的路由部分是在前端那里处理的 .. 所以后端这块只定义接口的路由就行了 .. 这样我们可以定义一个 wildcard route .. 通配类型的路由 ..

让它把所有的请求交给一个视图 .. 因为前端应用通过就是一个 Spa,single page application ,单页面应用

...... 
Route.any('*',({view}) => view.render('welcome'))

访问一个不存在的路由 http://localhost:3333/comments 返回welcome视图

Node.js 应用:请求与响应

创建一个新的分支 request-response

获取查询参数

得到请求里的查询参数 .. 可以使用 reqeust 的 get 方法 .. 先把方法返回的东西输出到控制台上检查一下 .. 用一个 console.log .. 输出 request.get() 方法返回的东西 ..

start/routes.js

...... 
Route.get('/posts',({request}) => {
  console.log(request.get())
})

可以在控制台看到输出的查询参数

获取请求中的数据

start/routes.js

...... 
Route.post('/posts',({request}) => request.post())

用REST客户端 POST请求http://localhost:3333/posts 发送一个json数据

{
	"title":"Lemon",
	"content":"柠檬",
	"status":"publish"
}

可以看到返回的数据

如果想要同时得到数据和查询参数,可以使用

Route.post('/posts',({request}) => request.all())

使用REST客户端请求http://localhost:3333/posts?redirect=home 附带json数据

{
	"title":"Lemon",
	"content":"柠檬",
	"status":"publish"
}

可以看到返回的数据里面有数据和查询参数

指定获取的数据

Route.post('/posts',({request}) => request.only(['title','content']))

不想得到的数据

Route.post('/posts',({request}) => request.except(['title','content']))

获取到某个具体的数据 .. 可以使用 input 方法 .. 再试一下这个方法 .. 它的第一个参数就是想要获取到的请求里的数据的名字 .. 比如 status .. 它的第二个参数可以设置一个默认的值,就是如果请求里面不包含这个 status 数据,就会使用这里提供的默认的值 .. 这里把它设置成 draft ..

Route.post('/posts',({request}) => request.input('status','draft'))

请求中的集合数据

Route.post('/posts',({request}) => request.collect(['title','content']))

头部信息:Headers

Headers,就是头部信息,它是客户端与服务端沟通交流的一种方式 . 客户端请求的时候可以带一些头部信息 .. 服务端响应的时候也可以带一些头部信息 ..

比如客户端发送请求的时候,里面可以包含一些头部信息 .. 告诉服务端,这个请求是谁发出的 .. 还可以跟服务端描述一下,客户端这里都授受什么类型的内容 .. 服务端做出响应的时候,也可以在头部信息里描述一下响应的内容是什么类型的 .. 这样在客户端那里就知道怎么去处理响应回来的数据 ..

有些头部信息是自动设置的 .. 比如浏览器在发送请求的时候,就会自动设置一些头部信息 ..

为了某些特殊的需求, 有时候我们也会在应用的前端那里额外再添加一些头部信息 .. 比如应用如果实施使用了 jwt 的身份验证方法 .. 用户登录成功以后,会得到服务端签发的 token,这样以后用户每次发送请求的时候,都需要把这个 token 的值放在一个特定的 header 里面 ..

服务端收到请求,得到了 header 里的 token 的值,验证一下,再决定怎么做出回应 ..

获取头部信息

Route.get('/posts',({ request }) => request.headers())

要得到头部信息里的具体的某条信息 .. 可以使用一个 header 方法 .. 然后把头部信息的名字交给这个方法 .. 比如我需要 user-agent ..

Route.get('/posts',({ request }) => request.header('user-agent'))

用浏览器访问 http://localhost:3333/posts 可以看到返回的信息

设置响应头部信息

Route.get('/posts',({request,response}) => {
  response.header('Content-type','text/plain')
  return '<h1>List of posts.</h1>'
})

用浏览器访问http://localhost:3333/posts 返回纯文本

Cookies

Cookies,指的就是服务端在客户端那里存储的一小块数据 .. 服务端在响应请求的时候,可以添加 Set-Cookie 这个头部信息,对应的值就是要设置的 cookie 数据的名字,还有对应的值 ..

浏览器在发送请求的时候,会带着 cookie 这个 header,里面会包含服务端设置的 cookie 数据 ..

比如用户登录成功以后,可以在客户端那里设置一条 cookie .. 这样下次用户再发送请求的时候我们应用的服务端就知道用户是谁了 ..

再比如用户在应用上做的一些个性化的设置,也可以在 cookie 里面存储起来 ..

应用在客户端那里存储数据,除了 cookie,我们还可以使用 Storage API .. 比如 localStorage,sessionStorage .. 还有 Indexed DB ..

设置与获取 Cookies

Route.get('/posts',({request,response}) => {
  response.cookie('theme','dark')
  return request.cookies()
})

删除cookies

Route.get('/posts',({request,response}) => {
  response.cookie('theme','dark')
  response.clearCookie('theme')
  return request.cookies('theme','light')
})

响应:Response

客户端对服务端发出各种请求,服务端可以根据请求响应回对应的数据 .. 做出响应的时候除了带着具体的请求需要的数据,还可以设置响应的头部信息,Cookies ,还有状态码这些东西。在客户端那里可以根据这些信息来判断到底应该怎么使用响应回来的数据 ..

异步响应

服务端在对请求做出响应的时候,有可能不会马上得到要响应的数据 .. 比如服务端可能会去查询数据库 .. 这是一个异步的动作,因为查询数据库需要花点时间 .. 我们需要等待查询的结果,然后再做出响应 ..

这种情况我们可以使用 async,await … 这就需要执行的操作必须返回的是 Promise .. 下面我们可以模拟一下这种异步的情况 ..

const delay = (data,time) => {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve(data)
    },time)
  })
}

Route.get('/posts',async ({response}) => {
  const data = await delay(
    'List of posts.',
    3000
  )

  return data

})

重定向:Redirects

Route.get('/list-of-posts',({response}) => {
  response.redirect('/posts')
})

Route.get('/posts',() => {
  return 'List of posts.'
})

用浏览器访问 http://localhost:3333/list-of-posts 被重定向

Node.js 应用:视图

新建一个分支 views

路由 – 控制器 – 视图

创建一个控制器

adonis make:controller Post

修改routes.js

Route.resource('posts','PostController')

修改控制器 app/Controllers/Http/PostController.js

'use strict'

class PostController {
  index ({view}) {
    return view.render('posts.index')
  }
}

module.exports = PostController

创建一个视图文件

adonis make:view posts.index

resources\views\posts\index.edge

<h1>Posts</h1>

浏览器访问 http://localhost:3333/posts 可以看到页面

传递与使用值

修改视图

<h1>{{ pageTitle || 'Untitled'}}</h1>

修改控制器app/Controllers/Http/PostController.js 传递值

'use strict'

class PostController {
  index ({view}) {
    const pageTitle = 'Posts'
    return view.render('posts.index',{pageTitle:pageTitle})
  }
}

module.exports = PostController

有时候你可能希望不让 edge 处理这个两组大括号里的东西 .. 比如应用里如果使用了 vue.js 这个前端框架 .. 两组大括号这种形式,在 Vue.js 里面也有特别的意义 ..

如果你不想让 edge 去处理它里面的东西,可以在这两组大括号的前面,加上一个 @ 符号 .. 这样 edge 就不会动大括号里面的东西了 ..

<h1>@{{ pageTitle || 'Untitled'}}</h1>

如果想要输出渲染的html代码的时候可以用三个大括号
控制器

'use strict'

class PostController {
  index ({view}) {
    const pageTitle = 'List of <i>posts</i>'
    return view.render('posts.index',{pageTitle:pageTitle})
  }
}

module.exports = PostController

视图

<h1>{{{ pageTitle || 'Untitled'}}}</h1>

条件:Conditionals

控制器传user name

'use strict'

class PostController {
  index ({view}) {
    const pageTitle = 'List of <i>posts</i>'
    const user = {
      name:'shine'
    }
    return view.render('posts.index',{pageTitle,user})
  }
}

module.exports = PostController

视图判断显示

<h1>{{{ pageTitle || 'Untitled'}}}</h1>

@if(user.name)
{{user.name}}
@else
Login / Signup
@endif

迭代:Iteration

修改控制器,传递 entities

'use strict'

class PostController {
  index ({view}) {
    const pageTitle = 'List of <i>posts</i>'
    const user = {
      name:'shine'
    }
    const entities = [
      {id:1,title:'Lemon',content:'柠檬'},
      {id:2,title:'Watermelon',content:'西瓜'}
    ]
    return view.render('posts.index',{pageTitle,user,entities})
  }
}

module.exports = PostController

视图

<h1>{{{ pageTitle || 'Untitled'}}}</h1>

@each(entity in entities)
  <div>
    {{entity.title}}:{{entity.content}}
  </div>
@endeach

当前的索引值可以用 $loop.index 表示

视图

<h1>{{{ pageTitle || 'Untitled'}}}</h1>

@each(entity in entities)
  <div>
    <small>
      {{$loop.index}}.
      first : <i>{{ $loop.first }}</i>
      last : <i>{{ $loop.last }}</i>
      total : <i>{{ $loop.total }}</i>
      odd : <i>{{ $loop.isOdd }}</i>
      even : <i>{{ $loop.isEven }}</i>
    </small>
    {{entity.title}}:{{entity.content}}
  </div>
@endeach

布局:Layouts

Layouts,布局。就是在视图里面可以重复使用的页面的模板 .. 布局也是普通的视图文件,只不过你在布局里面可以定义 section,就是区域 .. 这些区域里的具体内容,你可以在使用这个布局的视图里面去定义 ..
新建一个布局 resources/views/layouts/main.edge

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>{{pageTitle || 'shine'}}</title>
</head>
<body>
  
</body>
</html>

使用这个布局 修改 resources/views/posts/index.edge

@layout('layouts.main')

访问 http://localhost:3333/posts 可以看到布局

布局区域:@section

在布局里,我们可以去定义一些 section,然后在使用它的时候,可以具体设置一下这些不同的 section 里面包含的内容 ..

修改resources/views/layouts/main.edge

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>{{pageTitle || 'shine'}}</title>
</head>
<body>
  <div class="container">
    <div class="header">
      @section('header')
      <small>__headerSection__</small>
      @endsection
    </div>
    <div class="content">
      @section('content')
        <p>There is no content yet.</p>
      @endsection
    </div>
  </div>
</body>
</html>

在resources/views/posts/index.edge 使用布局

@layout('layouts.main')

@section('header')
<h1>{{ pageTitle || 'Untitled'}}</h1>
@endsection

@section('content')
@each(entity in entities)
  <div>
    <small>
      {{$loop.index + 1}}.
    </small>
    {{entity.title}}:{{entity.content}}
  </div>
@endeach
@endsection

访问 http://localhost:3333/posts 可以看到页面

@super

还有一个事,就是,就是在视图里设置布局 section 的时候,如果你不想覆盖原本在 laytout 里的 section 里定义的内容,只是想去扩展一下它里面的东西 ..

在设置 section 内容的时候,可以使用一个 @super ..

这样原本在布局里定义的这个 section 里的内容会被保留下来 ..

@layout('layouts.main')

@section('header')
@super
<h1>{{ pageTitle || 'Untitled'}}</h1>
@endsection

@section('content')
@each(entity in entities)
  <div>
    <small>
      {{$loop.index + 1}}.
    </small>
    {{entity.title}}:{{entity.content}}
  </div>
@endeach
@endsection

局部:Partials

Partials,局部 .. 我们可以视图上的一些东西放在一些局部里面 .. 然后在视图里可以使用 @include ,把局部里的东西包含到视图里面 ..
创建一个局部 resources/views/partials/footer.edge

<div class="footer">
    <hr style="border:none; border-bottom: 1px solid #eee">
    <small>&copy; shine. </small>
  </div>

在resources/views/layouts/main.edge使用局部

...... 
<div class="content">
    @section('content')
      <p>There is no content yet.</p>
    @endsection
  </div>
</div>
@include('partials.footer')
</body>
</html>

访问 http://localhost:3333/posts 可以看到页面

组件:Components

Components,组件。组件是一种独立的可以定制的能重复使用的视图 .. 你可以把一些常用的界面定义成组件,使用它的时候可以通过给组件传递值的方式,或者通过 slot 去定制组件的显示 ..

新建一个组件 resources/views/components/list.edge

@each(entity in entities)
  <div>
    <small>
      {{$loop.index + 1}}.
    </small>
    {{entity.title}}:{{entity.content}}
  </div>
@endeach

在resources/views/posts/index.edge使用组件
这里可以使用一下刚才定义的组件 .. 用的是 @component .. 如果不使用 endcomponent ,可以在 tag 名字的前面加上一个 ! 号,表示这是一个自关闭的 tag ..

@layout('layouts.main')

@section('header')
<!-- @super -->
<h1>{{ pageTitle || 'Untitled'}}</h1>
@endsection

@section('content')
  @!component('components.list',{entities:entities})
@endsection

@slot,@yield

在定义组件的时候,可以在组件里面预留一些显示内容的地方,这些地方都有个名字。在使用组件的时候,可以去设置这些地方具体要显示什么东西 .. 预留的这些地方,就是 slot ..

resources/views/components/list.edge

...... 
@yield($slot.notes)
  <small>Put some notes here.</small>
@endyield

使用 resources/views/posts/index.edge

@layout('layouts.main')

@section('header')
<!-- @super -->
<h1>{{ pageTitle || 'Untitled'}}</h1>
@endsection

@section('content')
  @component('components.list',{
    entities,
    border:true,
    borderColor:'#ddeaf2'
  })
    @slot('notes')
      <small>Eat some fruites everyday is good for your health.</small>
    @endslot
  @endcomponent
@endsection

请求相关信息
在视图里,我们可以得到表示当前请求的这个对象 .. 就是 request 对象 .. 它上面有一些东西你可能会在视图里用到 ..

resources/views/layouts/main.edge

...... 
<div>
    <small>URL:</small> {{ request.url() }} <br>
    <small>OriginalUrl:</small> {{ request.originalUrl() }} <br>
    <small>Method:</small> {{ request.method() }} <br>
    <small>Protocol:</small> {{ request.protocol() }} <br>
  </div>
...... 

现在 Url 跟 OriginalUrl 显示的是同样的地址 .. 在请求的地址这里,可以再添加一个查询参数 ..
OriginalUrl 返回的地址里面会包含地址里的查询参数部分 ..

链接资源

应用里的一些公开的资源会放在 public 这个目录的下面 .. 就是在这个目录下面的东西,任何人都可以访问到 .. 在视图里可以使用一些特别的方法去链接这里面的资源 ..
新建 public/css/main.css 和 public/js/main.js

在视图 resources/views/layouts/main.edge 引入css

{{ style('css/main') }}

引入JS

{{ script('js/main')}}

插入图片资源

{{ assetsUrl('splash.png') }}