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,为两者建立多对多关系,在录入书籍信息的时候添加一个字段可以输入其获得的奖项。

效果

October/Winter CMS创建自定义Widget

准备数据表

建立表julian_library_awards

建立表julian_library_awards

建立存放两表关系的数据表julian_library_books_awards

建立存放两表关系的数据表julian_library_books_awards

在后台为Awards表配置对应的ModelControllerMenuPermissions等功能,这里不过多阐述。

在各自的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 WidgetsReport Widgets这两种widget,了解更多可点击这里的官方说明

在本示例中对应插件的存放目录是 /Plugins/Julian/Library/formwidgets/awardbox,具体目录和文件结构看下图

Widget目录结构

下面开始准备我们需要的文件。

2.2 静态文件jquery select2插件

因为前端的交互效果需要借助jQueryselect2插件,我们需要在官方把cssjs文件下载下来并分别存放到下面这两个目录中

  • /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服务端无法运行。此问题已经收录至

所有逻辑和数据处理都依赖此文件

<?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产生冲突,我们使用select2contanerCssClass参数单独设置了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字段中查看是否能被正确调用,最后检查是否正确保存

Post Comment