# 开发 Pagekit 扩展
<p class="uk-article-lead">以下教程包含创建和开发一个在 Pagekit 管理界面管理待办事务(Todo)的扩展程序的所有完整步骤。你将学习扩展的基本概念、控制器、路由、视图渲染以及 Vue.js 框架等知识。</p>
[TOC=2]
如果你更愿意看视频教程,可以看这里: [如何创建 Pagekit 扩展](http://www.bilibili.com/video/av6824636/index_1.html) ,已经包含此教程内容了。
**Note** 本案例的 [最终成品](https://github.com/pagekit/example-todo) 就在 Github 上。
## Step 1: 使用 Modules 来扩展 Pagekit
作为开发者,你可以轻松扩展 Pagekit 已提供的功能。无论你是想自定义主题还是要制作包含附加功能的扩展,都是建立在相同的方法上的。第一步,我们会介绍 *包(package)* 和*模块(module)* 这两个 Pagekit 的核心概念。
<a href="http://www.bilibili.com/video/av6824636/index_2.html" class="uk-button">在影片中了解此步骤</a>
### 术语:Packages 和 modules
主题和扩展可能在外部看起来有所不同,但它们都我们称之为 *包/package*。Package 其实是包含多个文件和存储元数据的 `composer.json` 文件的一个文件夹。
要在 Pagekit 中注册任意事物,都需要包含一个 `index.php` 文件。它包含了我们对 *模块/module* 的定义。模块是 Pagekit 代码中最重要的构建单元。模块是由多个自包含的功能单元组成的,并形成了整个系统。模块在 Pagekit 核心和任意第三方扩展中都会用到。
### 把 package 文件放在哪里
每个包都有它独立的提供者名字,这样你就能在很清楚地查看全新安装的 Pagekit 目录结构。
核心包是 `pagekit` 命名空间的一部分,位于 `/packages` 目录中相应的子目录内。任意第三方包(包括你的)都有其自有的提供者名字,并放在单独的子目录里。
```
/packages
/pagekit
/blog
/theme-one
/your-vendor
/your-theme
/your-extension
```
在每个包中,你至少能发现两个文件: `composer.json` 和 `index.php`。
### 1. composer.json: 包的元数据
在 `composer.json` 中包含了 package 的元数据。这些数据会在你把自己的扩展上传到 Pagekit 商店时,显示在 Pagekit 后台。
`packages/pagekit/todo/composer.json`
```
{
"name": "pagekit/todo",
"version": "0.1",
"type": "pagekit-extension",
"title": "ToDo management"
}
```
### 2. index.php: 模块的定义文件
Pagekit 内部的模块捆绑了功能,并且是与 Pagekit 系统挂钩的方式。从代码的角度来说,模块的定义就是一个包含属性设置的 PHP 数组。`index.php` 必须 `return` 一个有效的数组。
```
<?php
use Pagekit\Application;
// packages/pagekit/todo/index.php
return [
'name' => 'todo',
'type' => 'extension',
// 在 Pagekit 初始化模块时调用
'main' => function (Application $app) {
echo "It's alive";
}
];
?>
```
要测试功能,需要确保已在后台启用了扩展。如果扩展已被启用,你会在屏幕上方看到输出的结果。
这个最小的例子显示了全功能模块可以有多小。它访问了 `Application` 实例。使用这个对象,可以访问所有服务,触发事件和监听由其他模块触发的事件。
## Step 2: 路由和控制器
对于基本的扩展程序结构,常用的任务是注册你自己的控制器并将菜单条目添加到管理界面中。为此,我们将查看一些附加的属性,这些属性可以添加到 `index.php` 中的模块定义里。
<a href="http://www.bilibili.com/video/av6824636/index_3.html" class="uk-button">在影片中了解此步骤</a>
### 添加控制器
Pagekit 中的控制器只是简单的 PHP 类。每个正确命名的公共方法(例如: `someAction()`)都由 Pagekit 的路由进行嵌入(例如: `/todo/some`)。
用下面的代码创建文件 `packages/pagekit/todo/src/Controller/TodoController.php` 。
```
<?php
namespace Pagekit\Todo\Controller;
/**
* @Access(admin=true)
*/
class TodoController
{
public function indexAction()
{
return "Yay.";
}
}
```
要限制对管理区域的访问,并将此控制器嵌入管理员页面 URL 中,我们使用 `@Access(admin=true)` 这句注释(Annotation)。 这里说到的注释是位于类或方法上方注释块中的关键字。了解更多关于注释的知识,查看[路由](224133)中“注释”这部分。
要使用这个控制器,我们需要自动加载命名空间并将控制器嵌入到路由中。添加以下属性到 `index.php` 中的配置数组。
```
// ...
// 从给定的目录中自动加载命名空间的数组
'autoload' => [
'Pagekit\\Example\\' => 'src'
],
// 路由的数组
'routes' => [
// 从你的代码中应用路由的标识符
'@todo' => [
// 此扩展要嵌入到的路径
'path' => '/todo',
// 要嵌入的控制器
'controller' => 'Pagekit\\Todo\\Controller\\TodoController'
]
],
// ...
```
你现在就能使用 `<pagekit_path>/admin/todo` 这个 URL 来访问新的控制器了。注意,这个 URL 由4个部分生成:
1. `<pagekit_path>` URL 以你的 URL 开头
1. `@Access(admin=true)` resulted in an admin url `/admin`
2. 此控制器以 `/todo` 的形式嵌入
3. The `indexAction` is the default route at that url.
如果不能访问此路由,需要[清除缓存](223152)。因为 Pagekit 为了更好的性能缓存了路由。
### 调试工具栏
要在开发过程中看到所有已注册的路由,可以在 Pagekit 系统设置中启用*调试工具栏* 。工具栏显示在屏幕的下方,显示所有已注册的路由一起其他有用的信息。
记得在上线的网站中关掉它。
### 添加菜单条目
在模块定义中使用 `menu` 属性来添加菜单条目。在 `index.php` 中添加下面这些代码:
```
// ...
'menu' => [
'example' => [
'label' => 'ToDo',
'icon' => 'app/system/assets/images/placeholder-icon.svg',
'url' => '@todo',
]
],
// ...
```
刷新 Pagekit 后台,你就能看到链接到 `@todo` 路由的新菜单条目了。
## Step 3: 视图渲染和模块配置
<p class="uk-article-lead">在上一步,我们讨论了模块和路由的基础。然而,我们的第一个控制器只能返回简单的字符串。在这一步,我们了解实际的视图渲染。</p>
<a href="http://www.bilibili.com/video/av6824636/index_4.html" class="uk-button">在影片中了解此步骤</a>
### 通过控制器操作来渲染视图
要传递参数给视图渲染器,控制器需要返回一个 PHP 数组。在 `$view` 键中有两个视图渲染器的参数,比如要渲染的视图文件的 `name`。
```
public function indexAction()
{
return [
'$view' => [
'title' => 'TODO management',
'name' => 'todo:views/admin/index.php'
],
'message' => 'Hello how\'s it going?'
];
}
```
渲染后的视图文件可以是纯 PHP 模板。简单的参数 (i.e. `message`) 可以用作 PHP 变量 (i.e. `$message`)。
`packages/pagekit/todo/views/admin/index.php`:
```
<h1><?php echo $message; ?></h1>
```
### 资源简写
我们可以使用简写语法,而不是引用文件的完整路径。`packages/pagekit/todo/views/admin/index.php` 可以简写为 `todo:views/admin/index.php`。这能更快地输入也更易于阅读。
在 `index.php` 中注册简写的目标路径。在这个例子中,我们想要将 `todo` 指向 `index.php` 的当前路径:
```
'resources' => [
'todo:' => ''
],
```
### 模块配置
模块可以由一个配置数组来保存各种设置。我们可以用它作为 TODO 条目的简单存储。
```
'config' => [
'entries' => [
['message' => 'Buy milk.', 'done' => false], // ...
]
],
```
在控制器中,我们可以将此配置作为模块实例的一个属性来访问。
`src/Controller/TodoController.php`:
```
use Pagekit\Application as App;
// ...
$module = App::module('todo');
$config = $module->config;
// ...
```
可以将模块配置的修改都存储到数据库中。模块的默认配置与已存储的修改进行合并,控制器就始终能拥有可用的配置了。
```
// 修改模块配置
App::config('todo')->set('entries', $entries);
```
## Step 4: 在 Pagekit 扩展中使用 Vue.js
<p class="uk-article-lead">在为管理区域创建自有的页面时,可以使用任意开发库。但由于 Pagekit 已经自带了 Vue.js,如果 Vue.js 合你口味就可以使用它了。在这一步,我们将介绍在 Pagekit 内部使用 Vue.js 的基础概念。</p>
<a href="http://www.bilibili.com/video/av6824636/index_5.html" class="uk-button">在影片中了解此步骤</a>
### 1. 传递数据给 JavaScript
在前文中,我们了解了如何在 PHP 控制器中访问数据。要在 JavaScript 中使用该数据,使用 `$data` 关键字将 PHP 数组传递给视图渲染器。Pagekit 会自动将它转换为 JSON 形式,并在生成的 HTML 的 head 部分渲染出来。
```
// packages/pagekit/todo/src/Controller/TodoController.php
// ...
public function indexAction()
{
$module = App::module('todo');
return [
'$view' => [
'name' => 'todo:views/admin/index.php'
],
'$data' => $module->config
];
}
```
下面的代码将被渲染到 `<head>` 部分。
```
<script>var $data = {"entries": [{"message":"Buy milk","done":false},{"message":"Drink coffee","done":true}] };</script>
```
### 2. 组合视图和模型
ViewModel 是一个 `Vue` 实例,用于在模型和视图界面尚同步数据。这就是所谓的 *实时(reactivity)* ,它是 Vue 的关键特性之一。正确地使用它,将帮助你保持 JavaScript 组件的小巧和可读。
可以将 `Vue` 实例附加到 DOM 元素(`el: '#todo'`)。模型即是你传递给 `data` 参数的任意数据。采用已渲染视图的全局 `window.$data` 对象来使用 Pagekit 的数据。
可以在模板中调用你创建的任意 `methods` 。我们将在下一步中创建模板标签。
```
// packages/pagekit/todo/js/todo.js
$(function(){
var vm = new Vue({
el: '#todo',
data: {
entries: window.$data.entries,
},
methods: {
add: function(e) {
// ...
},
toggle: function(entry) {
entry.done = !entry.done;
},
remove: function(entry) {
this.entries.$remove(entry);
},
save: function() {
// ...
}
}
});
});
```
### 2. Vue 的标签
对于 PHP,我们仍然渲染视图文件 `views/admin/index.php`。在这里,我们引入了 JavaScript 文件。要让 Vue 在脚本中可用,需要添加 `vue` 依然作为第三个参数。
```
<?php $view->script('todo', 'todo:js/todo.js', 'vue') ?>
```
在 HTML 中,包含 Vue 实例的 DOM 元素会查找 (`<div id="todo"></div>`)。在元素内部可以使用 Vue 指令。指令是指那些可以告诉 Vue 如何处理元素的关键字。
```
<p v-if="entry.done">This will be displayed if the item has been done.</p>
```
使用 `@click` 可以绑定点击事件,并调用视图模型的方法。
要从模型中输出值,可以使用花括号 (`{{ entry.message }}`)。Pagekit 提供了一个 `trans` 过滤器,如果所选区域有可用的语言包,它将自动将其替换为相应的语言。
```
{{ 'Save' | trans }}
```
这是一个简单的视图的样子:
```
<!-- packages/pagekit/todo/views/admin/index.php -->
<?php $view->script('todo', 'todo:js/todo.js', 'vue') ?>
<div id="todo">
<button @click="save()">{{ 'Save' | trans }}</button>
<ul>
<li v-for="entry in entries">
{{ entry.message }}
<button @click="toggle(entry)">{{ entry.done ? 'Undo' : 'Do' }}</button>
</li>
</ul>
</div>
```
### 了解更多 Vue.js
Vue.js 是一款强大的库,可以用来轻松构建灵活的 web 界面。了解更多,查看 [官方手册](http://vuejs.org/guide/)。强烈推荐 Laracasts 上的[videos on Vue.js](https://laracasts.com/series/learning-vue-step-by-step).。两者都是学习 Vue 的好去处。
<a id="complete"></a>
## 成品示例
在最终完成的示例中,我们实现了真实地保存到后台,并清理了源代码。它综合前面谈到的所有制作 Todo 扩展的步骤。成品就放在 [Github](https://github.com/pagekit/example-todo)上供你参考。