Giter VIP home page Giter VIP logo

yii2-utility's People

Contributors

drodata avatar

Watchers

 avatar

Forkers

heartshare

yii2-utility's Issues

Enhance modal ajax viewing to avoid duplicate coding

以 Modal 形式查看详情体验更好。问题是每增加一个功能,都要做类似的工作:

  • 使用 jQuery 提前拦截拦截的跳转,通过 ajax 获取内容,再通过 Modal 显示出来;
  • 增加对应的 controller action 及 view files;

如何设计才能省掉这些重复工作?

增加 static routes() 存储常用动作对应路由 map

模型操作分两类:一类是跟具体模型有关,例如修改删除等,路由后都带有主键参数锁定具体模型;另一种跟具体模型无关,例如新建操作。

新建操作按钮.

Lookup::route('cost-create-prepay')

Html::a() 按钮禁用时显示 Tooltip

  • 需求:想在项目首页放置一系列入口按钮,按钮内用 badge 显示当前需处理的条目个数。当没有需要处理的内容时,直接禁用按钮。同时以 tooltip 形式 提示用户。
  • 事实:Bootstrap Tooltip 显示 disabled 元素时需要外加一个 wrapper 才能奏效。

抽象:在 Html::a() 内加入此特性,避免代码重复。当链接同时满足下列两个条件时,自动添加 wrapper:

  • 类名中含有 .disabled
  • title 值不为空;

CRUD 增加 _tabs 模板

用于将相近的 tabs 归类,最好能做成一个开关。另外一个开关是,是否为 index, view 两个 actions 生成专门适合手机的 views.

Html::cancelButton() 的必要性

其实每个表单都应该显性添加一个取消按钮,给用户一个反悔的选择。尽管有其它变相的返回办法(比如点击浏览器的返回前一页按钮),但是并不是所有用户都知道,一定要在醒目的地方给用户留一个出口。

所谓“取消”,就是返回前一页 (Yii::$app->request->referrer).

Problem in "after created model, insert activity record"

必须使用动态绑定的一种情况

假设有两个表:order 和 activity, 分别表示订单和日志。订单的每一次操作(创建、修改等)都写入日志。日志表有一个 reference_id 列用来标识 order id. 问题:新建操作比较麻烦,无法向其它操作一样,使用内置的 EVENT_AFTER_INSERT 或 EVENT_AFTER_UPDATE 来完成,因为 on() 第三个参数需要带上 order id, 而绑定时 order id 尚未产生(修改操作没有这个问题)。

额外的开销就是,需要特地新建一个类似 EVENT_AFTER_CREATED 的事件。我在想是否有必要像官方的 beforeSave() 那样,在 drodata\db\ActiveRecord 内预定义一个 afterCreated(), 方法内除了触发 EVENT_AFTER_CREATE 外,什么也不做。

这种情况适合所有设计多表存储的情形,其它的例子包括:新建订单时, orderitem 两个表,item.order_id 是外键。

actionLink() 移入 ActiveRecord

public function actionLink($action, $configs = [])
{
list($route, $options) = $this->getActionOptions($action);
return Html::actionLink($route, ArrayHelper::merge($options, $configs));
}

现在配置模型链接都在 getActionOptions() 内完成,actionLink() 仅负责调用,内容不会再变动,可以直接放进 drodata\db\ActiveRecord 内。

AssetBundle $appendMd5Hash 不支持 Source Asset

新增的 appendMd5Hash 属性有个不足:仅支持 web 可直接访问的 asset, 不支持类似 AdminLTE custom.css 这样的 source assets. custom.css 也属于经常变动的 asset, 但由于浏览器缓存的原因,即便 composer update 后,asset url 仍然没有变动,导致 asset 仍不是最新的。

Lookup label() 改进:支持 code 是 0 的情况

    if (empty($code) || empty($color)) {

上面的 empty() 不好。设置的值还不能是 0, 实际中 code 设置为 0 很常见,且看上去很直接。


InventoryAction 有一个不再使用(Picking), 将 visible 设置为 0 后, lookup() 直接不显示内容,应该显示出来,是否可见。

Gii model 的 tabular tempalte 增加常用代码 getIsXxx() 等

Model template 增加一下内容:

public function getIsCreated()
{
    return $this->status == self::STATUS_CREATED;
}

...

public function getCreator()
{
    return $this->hasOne(User::className(), ['id' => 'created_by']);
}

crud 模板方便:

  • _grid.php 内创建时间列增加 width:170px;min-width:170px; 样式;
  • _field-tabular.php: 表头列增加 table-layout:fixed 属性,用最简单的办法确保 tabular row 的各个列的宽度;

引入 Html::popoverHelper() 方法

借助改进后的 ActiveField, 我们可以通过在 AR model 内声明 getAttributesHints() 的方式,快速在 Form element 新建 popover.

这种简易的 popover helper 在其它地方也有用到,每次都要重复写类似下面的内容有些费劲,

return Html::icon('question-circle-o', [
    'class' => 'text-info',
    'data' => [
        'toggle' => 'popover',
        'trigger' => 'hover',
        'placement' => 'auto top',
        'content' => '此价格尚未包含运费',
    ],
]);

不如在 Html 内声明一个简易的方法。

AJAX 调用 fail handler 提示信息不够

不利于排查错误。

code : 0
message : "服务器内部错误。"
name : "Internal Server Error"
status : 500

----

file : "/path/to/Model.php"
line : 300
message : "Call to a member function validate() on null"
name : "PHP Fatal Error"
stack-trace : Array[2]
type : "yii\base\ErrorException"

yii\web\Controller 增加 jumpBack() method

if ($invalid) {
    Yii::$app->session->setFlash('warning', 'invalid');
    $this->redirect(Yii::$app->request->referrer);
}

类似这样的常用操作,仿照 goHome() 添加一个 jumpBack() shortcut 以减少输入。如此命名是因为 goBack() 已经存在。

TodoTabs Widget 还需要视图参数选项

config 数组的参数顺序依次是:key, tab name, view file, active query 和 visible
todotabs-widget

有的是否,view file 还需要传入额外的参数。以上面的 tabs 为例,报销(前两个)和采购共用一个模型,“方向”列仅对采购有意义,我想在两个报销 tabs 内隐藏“方向”列,这时如果将 tab key 传到 view file 内,就能实现。这个值是数组类型,类似 render() 第二个参数,若不需要,直接显性传入 null.

Html::tooltipIcon() 的需求

在 GridView 的 action column 内,根据记录的状态的不同,操作按钮经常需要显示成禁用状态。例如,当订单状态为“已发出”时,禁止删除订单,按钮如下:

'buttons' => [
    'delete' => function ($url, $model, $key) {
        if ($model->status == SENT) {
            return Html::icon('trash', [
                'title' => '已发出的订单暂不支持删除',
                'class' => 'text-muted',
                'data' => [
                    'toggle' => 'tooltip',
                ],
            ]);
        } else {
            return Html::a(
                Html::icon('trash'),
                ['update', 'id' => $model->id],
                [
                    'title' => '删除'
                ]
            );
        }
    },
],

针对这个还算普遍的需求,上面生成禁用按钮的方法太过繁琐,有必要加一个 shorthand method tooltopIcon().

对应的 popoverHelper() 同时做如下变动:

  • 调整参数类型,将必填内容作为单独的参数,而不是放在数组内;
  • 名称调整为 popoverIcontooltipIcon 保持一致;

AR 通用方法 write($attrs)

Update: 这里设计的很不好,像这种通用的写入操作,更多的是通过事件触发来完成,此方法应该写成 event handler 的形式就好了!

解决:向 \drodata\db\ActiveRecord 内追加 handleWrite() handler, 专门用于事件触发

public static function handleWrite($event)
{
    static::write($event->data)
};

想通 #15 后觉得没必要。


在数据库设计中,通常会存在几个多个模型共用的表格,例如可以创建一个 map 表,专门存储各种多对多关系,结构大致如下:

id
type
from_id
to_id

假设有下面两个多对多关系,依靠上面的 map 表关联:

  • 一个产品有多个产品图片
  • 一个包裹有多个产品图片

那么在实现新建产品或包裹操作时,都要在代码中存储 map 记录,例如:

# code-example-1

$map = new Map();
$map->type = ITEM2IMG;
$map->from_id = 3; // item id
$map->to_id = 5; // image id
$map->save();

每次都要完整地写出上面 5 行代码,很麻烦。其实类似 map 表有两个特点:表格结构简单;大多出现在事务中,很少单独存储。拿上面的新建产品为例,事务中要分别向三个表格中写入记录:产品表、图片表和 map 表。根据这两个特点,我们可以考虑把上面的代码抽象成一个静态方法,如下:

// in Map.php
public static function write($attrs)
{   
    $map = new Map();
    $map->attributes = $attrs;
    if (!$map->save()) {
        throw new \yii\db\Exception("Failed to insert into Map");
    }
}

这样以来, code-example-1 处的代码就可简化为:

Map::write([
    'type' => ITEM2IMG,
    'from_id' => 3,
    'to_id' => 5,
]);

进一步抽象成 AR 的一个静态方法

像 map 表这样的表还有很多,难道在每个 model 内都声明一个 write 方法吗? 更好的办法是创建一个继承自 yii\db\ActiveRecord 的子类,把通用的 write() 放在这个子类中,然后使用 Gii 生成 Model 类文件时,自动让类继承自自定义的子类即可。更改后的方法如下:

public static function write($attrs)
{   
    $name = static::className();
    $model = new $name();
    $model->attributes = $attrs;
    if (!$model->save()) {
        throw new \yii\db\Exception(
            "Failed to insert into " . static::tableName()
        );
    }
}

引入 .table-nowrap 类避免表格内容换行

GridView columns 经常需要调整的一个地方是:每一列的宽度不能自适应,导致一些列的宽度由于过窄使得单元格内容出现换行,就像下图中的“重量”列:

image

之前的办法是为每一列都设置 contentOptions 属性来限定宽度:

'contentOptions' => [
    'style' => 'width: 180px',
],

这种方法问题在于过于繁琐,想要的是一种自适应的单元格。搜索后发现 white-space CSS 属性 值是 nowrap 时能达到类似效果。

TimestampBehavior 增加 getReadableTime()

In grid/detail view widget, one could display human-readable timestamp via custom <attribute>:date format, that feature doesn't work in custom detail view file, because of this fact, we should declare the follow method in nearly every model:

public functon getReadableCreatedAt()
{
    return Yii::$app->formatter->asDatetime($this->created_at);
}

To avoid of duplicate coding, declaring a common method in TimestampBehavior may be a good choice.


实践证明此法行不通,自己的设想是能向方法传递列名(如 'created_at'),但是在方法内部是不能使用 $this->created_at 的,模型具有 Timestamp behavior 不代表时间戳行为能直接使用模型中的属性。 没看代码,已经提供 $createdAtAttribute 属性。

关键:$this->owner 可以访问到 AR 对象。

声明 behaviors 的一个技巧——使用 named behavior 而不是 annoymous behavior

以 TimestampBehavior 为例,在某些场景下,当写入一条新纪录时,我们不想插入当前的时间戳,这时就需要使用 detachBehavior() 临时解绑,此方法的参数表示行为名称,这也意味着对应的行为必须是 named behavior 而不是 annoymous behavior. 因此修改 Gii 模板,在 behaviors() 内统一使用 named behavior 来声明。

Added icon() to LookupBehavior

类似 label(), icon() 用来生成一个带有 tooltip 的 icon, 主要是为了解决 label 过宽的问题,类似 GridView ActionColumn 中的 action link.

自定义 Generator 时意识到 'gii-templates' 命名存在问题

之前使用 Gii 一直没涉及自定义 generator. 现在需要做一个 api controler 的 genetator, api 下的控制器和 web 控制器区别很大,例如没有视图文件等。因此需要重新定义 generator. 现在的目录结构导致自定义的 generator 的命名空间只能是 drodata\gii-templates\api\controller, 这个横线 - 导致类无法被识别。例如,假设一个名为 foo\bar-a\MyWidget, 在视图中进行输入时:

<? = foo\bar-a\MyWidget::widget() ?>

提示 Undefined constant 'foo\bar', 换句话说,PHP 把命名空间中的 - 解析成了减号,foo\bar 被理解成了变量。

关于这个限制,暂时没有在官方手册中找到相关说明。以后禁止使用横线,目录尽量用一个单词表示,非要用多个单词了,也要使用 camelCase.

引入 disabledHint() 的必要性

Update

  • 由于 getDisabledHint() 中表示动作的参数必不可少,因此方法名称没必要加 get 前缀,容易和 Yii2 的 getter/setter 混淆。统一更改为 disabledHint() 更合适;

Html::actionLink() 中跟逻辑密切相关的选项有三个:visible, disabled 和 confirm. visible 相对简单。禁止执行某个动作的条件和执行某个动作前的确认信息比较复杂。之前在 Gii model 模板中已先后添加 actionLink()getConfirmText($action), 后者的目的就是将这个复杂的逻辑剥离出来而新建的方法。

今天在设置合同评审禁用状态时发现可能性较多,也适合剥离出来。getDisabledHint() 返回值类型有两个:string 或 null. 允许执行返回 null, 否则返回禁止操作提示信息,例如下面:

public function getDisabledHint($action)
{
    switch ($action) {
        case 'audit':
            if ($this->isAudited) {
                return '已评审';
            }
            // 已提交初次评审
            if ($this->isUnaudited && $this->hasContractAudit) {
                $auditor = $this->contractAudit->currentAuditor;
                return empty($auditor) ? false : "等待 $auditor 审核";
            }
            
            return null;
            break;
    }
}

代码内部使用了 guard conditions: 自上而下一次列出所有需要禁止操作的可能性并提前返回值,在所有可能性都列出后,自然就是可以操作了。

优化含有子条目的模型详情页在小平上的显示

主要对诸如 Trial 和 TrialItem 这样含有子条目的模型详情页面在下屏幕上进行了优化。

  • 父模型的 _detail-view 内明细页面合并成 _div-items, 在后者进行大小屏区分显示;
  • 明细页 _div-items 大屏幕上显示 _grid-parent; 小屏幕上显示 _list-ol. 这两个视图都进行了命名调整,更加直观;
  • 增加 _list-item-span 视图,用于在小屏上以 <li> 形式显示子条目内容;相对应的,默认的 _list-item 是 block 形式;

学会使用 $view->params 在视图间共享数据

视图中的变量应该仅来自以下两个地方:

  • 从控制器传递过来的变量;
  • 存储在 $view->params 内的数组变量;

除此之外,不应该在视图内声明任何变量,MVC 中视图只负责数据展示,不应该含有组装数据的代码。

Gii CRUD 自适应: PC 端使用 GridView, 移动端使用 ListView

借助 Bootstrap 的 .table-responsive 类,可以让 GridView table 在手机上实现自适应——当表格宽度大于手机屏幕宽度时,可以通过左右滑动,显示表格中不可见的内容。这种方式在虽能达到一定程度上的自适应,但实际体验并不理想,尤其是当用户需要频繁在手机上进行相关操作时。用户需要先向左滑动表格,使得表格最右侧的 action column 显示出来,然后点击对应的按钮。另外一个体验不好的地方是 grid view 中的 filter, 也需要借助滑动屏幕才能进行。

自适应如果能达到这样一种效果就好了:当用户在手机上访问时,GridView 自动变成 ListView, 页面顶部是一个搜索框,下方是满足条件的 list items, 每个 list item 使用 DetailView widget 显示,DetailView 默认用一个两列的表格显示,在手机上显示效果很好。经过简单搜索,Bootstrap 提供了相关的类, 借助下面两个类,就能实现想要的效果:

  • .visible-xs-block: 具有该类的 div 仅会在手机上显示,其它尺寸的设备上都会显示;
  • .hidden-xs: 具有该类的 div 仅会在手机上隐藏,其它尺寸的设备上都会显示;

我们在模型的管理页面(例如 /user/index)内同时放置 GridView 和 ListView 两个 widgets, 大致结构如下:

<?php
// in views/user/index.php
?>

<div class="row">
    <div class="col-xs-12 hidden-xs">
        <?= GridView::widget([]) ?>
    </div>
    <div class="col-xs-12 visible-xs-block">
        <?= $this->render('_search', ['model' => $searchModel]) ?>
        <?= ListView::widget([]) ?>
    </div>
</div>

在上面的结构下,根据个人的需要进行自定义设置即可。后期还可以把它单独作为一个 Gii 模板,提高开发速度。这里有我自己的 CRUD 模板,供大家参考。

HTML to PDF 下载、打印

从月结账单功能中发现使用 wkhtmltopdf 将打印内容输出成 PDF 有诸多好处,可以把里面很多公用的东西抽象出来,整合进来,满足打印下载这两个常见的需求。要点:

  • 需要一个专门的 layout;
  • 需要两个通用的 actions: print, download, 前者在 server 上完成 HTML 到 pdf 文件的转换;后者是供用户下载的入口;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.