October/Winter CMS创建自定义Widget
Widget的概念最早在Wordpress里面,中文翻译叫小部件,在OctoberCMS中后台的每个表单模型都可理解为一个Widget,这其中包含前端静态文件以及逻辑处理的部分,如果对OctoberCMS系统自带的Widget模型不满意我们完全可以自己创建一个,而且也非常简单,只需要把Widget注册后在对应Plugin下的fields.yaml文件中的相关字段将type配置成我们的widget即可。同样的,官方的应用市场也有很多Widget提供有免费也有收费的,比如list switch这个插件,它通过一个switch开关的UI组件来改善blog的published
发布状态,在交互上就显得非常友好了。
业务背景:现有存放书籍信息的数据表
books
,需要增加一个存放奖项关联表awards
,为两者建立多对多关系,在录入书籍信息的时候添加一个字段可以输入其获得的奖项。
效果
准备数据表
建立表julian_library_awards
建立存放两表关系的数据表julian_library_books_awards
在后台为Awards
表配置对应的Model
,Controller
,Menu
,Permissions
等功能,这里不过多阐述。
在各自的Model中设置表关系
修改/Plugins/Julian/Library/Models/Book.php
,在$belongsToMany
中添加adwords
的配置,如下
public $belongsToMany = [ 'awards' => [ 'julian\library\models\Award', 'table' => 'julian_library_books_awards', 'order' => 'award_title' ] ];
修改/Plugins/Julian/Library/Models/Award.php
,在$belongsToMany
中添加books的配置,如下
public $belongsToMany = [ 'books' => [ 'julian\library\models\Book', 'table' => 'julian_library_books_awards', 'order' => 'title' ] ];
需要注意的是$belongsToMany
结构体只能一个,需要建立多个关系都要放在一个public $belongsToMany[]
中,否则会报错。
由于OctoberCMS是基于Laravel开发的,所以它还支持laravel的相关特性,在Award.php
中如果自定义一个function,注意命名规则
//Custom functions public function getUcaseTitleAttribute(){ return strtoupper($this->award_title); }// ucase_title
而在awardbox.php
中如果使用该方法返回的数据则可以使用下面这样的方法来完成,注意ucase_title
与上面方法名称getUcaseTitleAttribute
的对应规则
$this->vars['awards'] = Award::all()->lists('ucase_title', 'id');
创建Widget
2.1 文件目录结构
widget创建在Plugin
插件目录下,存放在/Plugins/userName/pluginName/
下,在本文我要创建的widget
名为awardbox
,而且是一个Form Widgets
,除了Form Widgets
,官方还提供了Generic Widgets
,Report Widgets
这两种widget,了解更多可点击这里的官方说明。
在本示例中对应插件的存放目录是 /Plugins/Julian/Library/formwidgets/awardbox
,具体目录和文件结构看下图
下面开始准备我们需要的文件。
2.2 静态文件jquery select2插件
因为前端的交互效果需要借助jQueryselect2插件,我们需要在官方把css
和js
文件下载下来并分别存放到下面这两个目录中
/Plugins/Julian/Library/formwidgets/awardbox/assets/css/select2.min.css
/Plugins/Julian/Library/formwidgets/awardbox/assets/js/select2.min.js
注意:后面的文件目录直接以formwidgets
开头,省略掉前面的固定格式/Plugins/Julian/Library
。
2.3 核心文件 /formwidgets/AwardBox.php
20220107更新: 此文件名注意大小写需要与class后面的类名一致,否则会导致在linux服务端无法运行。此问题已经收录至October/WinterCMS避坑指南
所有逻辑和数据处理都依赖此文件
<?php namespace Julian\Library\FormWidgets; use Backend\Classes\FormWidgetBase; use Julian\Library\Models\Award; use Config; class AwardBox extends FormWidgetBase{ /** * @var string A unique alias to identify this widget. */ protected $defaultAlias = 'awardbox'; public function widgetDetails(){ return [ 'name' => 'AwardBox', 'description' => 'Field for adding a Award' ]; } public function render(){ $this->prepareVars(); //dump($this->vars['awards']); //dump($this->vars['selectedValues']); return $this->makePartial('widget'); } public function prepareVars(){ //实现数据字典的读取 $this->vars['id'] = $this->model->id; $this->vars['awards'] = Award::all()->lists('award_title', 'id'); //ucase_title => models/Award.php => getUcaseTitleAttribute //实现数据保存 $this->vars['name'] = $this->formField->getName() . '[]'; //实现已存数据的读取 if(!empty($this->getLoadValue())){ $this->vars['selectedValues'] = $this->getLoadValue(); }else{ $this->vars['selectedValues'] = []; } } //save new award item public function getSaveValue($awards){ $newArray = []; //dd($awards); foreach($awards as $awardID){ if(!is_numeric($awardID)){ $newAward = new Award; $newAward->award_title = $awardID; $newAward->slug = str_slug($awardID); $newAward->save(); $newArray[] = $newAward->id; }else{ $newArray[] = $awardID; } } //dd($newArray); //die dump return $newArray; } public function loadAssets(){ /* $this->addCss('css/select2.min.css'); $this->addJs('js/select2.min.js'); */ } }
在该文件中因为loadAssets()
方法会加载widget运行所需要的前端css和js文件,这里需要说明的是因为我后台使用了系统的$belongsTo的relation字段,它同样使用的是select2
的插件,所以我这里注释掉了,无需重复引入。
2.4 前端文件 /formwidgets/awardbox/partials/_widget.htm
该文件用于展示前端界面
<style type="text/css"> /* Write styles in here*/ </style> <select name="<?php echo $name; ?>" class="s2" multiple> <?php foreach($awards as $key=>$value){ ?> <option value="<?php echo $key; ?>" <?php echo in_array($key, $selectedValues) ? 'selected="selected"' : ''; ?>><?php echo $value; ?></option> <?php } ?> </select> <script type="text/javascript"> $(document).on('render', function(){ $('.s2').select2({ placeholder: 'Add award', tags: true, containerCssClass: "mySelect2Container" //为了防止后台系统内置的select2冲突,这里需要定义container的class }); // $('.mySelect2Container').closest(".select2-container").css({"width": "100%"}); $('.mySelect2Container').find(".select2-search__field").css({"width": "100%"}); }); </script>
需要说明的是为了和系统创建的select2产生冲突,我们使用select2
的contanerCssClass
参数单独设置了select2的container class
,以便于更好的区分和控制自己的widget element。
2.5 注册Widget
找到/Plugins/Julian/Library/
下的Plugin.php
,添加以下代码来实现widget的注册
public function registerFormWidgets(){ return [ 'Julian\Library\FormWidgets\Awardbox' => 'awardbox' ]; }
2.6 启用Widget
找到/Plugins/Julian/Library/Models/Book/fields.yaml
文件,添加
awards: label: Awards span: left type: awardbox
测试功能
先添加一些Adward在后台
创建或修改一个book
数据,在adwards
字段中查看是否能被正确调用,最后检查是否正确保存