# 开发 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)上供你参考。