caoruiy‘s blog

Wisdom outweighs any wealth

[ui-bootstrap源码解析系列02]-collapse模块

概述

collapse模块主要用来显示和隐藏元素。

其源码可以在:geihub仓库中bootstrap/src/collapse/collapse.js 查看。该项目是本人fock原项目的,只添加了相关的注释。方便大家阅读和理解。

整个模块只实现了一个指令: uib-collapse

uib-collapse

该指令提供了一个简单的方法来显示和隐藏具有css transition属性的便捷方法。

为了很清楚的说明该模块的实现,首先需要了解他的用法。

用法

该模块的用法可以参见官方文档:https://angular-ui.github.io/bootstrap/

uib-collapse settings

  • collapsed() $ : 元素完成折叠后调用的可选表达式。
  • collapsing() $ : 在元素开始折叠之前调用的可选表达式,如果表达式返回一个promise对象,动画不会启动,直到reslove被执行;如果promise返回 reject,折叠动作将会被取消。
  • expanded() $ : 元素完成展开后调用的可选表达式。
  • expanding() $ : 在元素开始展开之前调用的可选表达式,如果表达式返回一个promise对象,动画不会启动,直到reslove被执行;如果promise返回 reject,展开动作将会被取消。
  • uib-collapse $ Wathch (Default: false) : 元素默认情况下是否被折叠,false为默认展开。
  • horizontal $ : 允许水平折叠的可选属性。

已知问题

当使用此指令的 horizontal 属性时,CSS可以回滚,因为折叠元素从0px到其所需的最终宽度,这可能导致高度更改。这可能导致动画无法运行。最好的方法是通过CSS在水平折叠元素上设置一个固定的高度,以便不会发生这种情况,因此动画可以按预期运行。

demo

虽然上面的链接给了官网的地址,这里还是提供一个简单的小示例:
该示例可以点击按钮来折叠和展开元素

<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">展开或折叠元素</button>
<hr>
<div uib-collapse="isCollapsed">
    <div class="well well-lg">Some content</div>
</div>

源码解析

原理概述

指令初始化时,会根据设置的属性对元素进行一些设置,比如添加一些class 类,添加一些css属性,同时会监听 uib-collapse 属性,如果为真则折叠,否则展开。在折叠展开之前和完成之后,会执行回调。

在展开时(以垂直折叠展开为例),会调用$animateCss服务,为元素添加 collaspein class 类,并获取元素的宽高,在展开之后为其设置原始的高度。效果折叠和其相反。

详细说明

在查看本文时,建议打开源码 geihub仓库中bootstrap/src/collapse/collapse.js 对照阅读,如果事先没有阅读过源码,可能有些问题无法理解。在源码中,我添加了一些注释,方便大家阅读。

uib-collapse 指令依赖 $animate, $q, $parse, $injector 服务,如果你对这几个服务不甚了解,可以参考下列文章。

$parse 比较简单,作用:将AngularJS表达式转换为函数。

  1. 首先,指令调用 $$injector.get('$animateCss') 获取 $animateCss 服务;
  2. 之后直接返回指令的link方法,在link方法中,定义了折叠展开的执行方法,其折叠和展开主要通过监听元素属性 uib-collapse 的变化来完成。

在link函数中,主要定义了以下几个方法:

  • init() : 根据属性初始化
  • getScrollFromElement() :获取元素原始的宽高信息
  • expand() :展开元素执行的函数
  • expandDone() :展开完成之后执行的函数
  • collapse() :折叠时执行的函数
  • collapseDone() : 折叠完成之后执行的内容

其中需要注意的是:

这里有一个小技巧,以展开为例:

假设我们是这样定义展开之前执行的函数的

<a uib-collapse expanding="expanding()"> 示例 </a>
$scope.expanding = function(){
    return $q.reject();
}
// 在指令开始地方定义的,意思是拿到执行展开之前的回调
var expandingExpr = $parse(attrs.expanding),

// 展开元素时执行的函数
function expand() {
  if (element.hasClass('collapse') && element.hasClass('in')) {
    return;
  }
  // 根据上面的$scope.expanding 返回的是一个 $q.reject()
  // 所以expandingExpr(scope)方法返回一个 reject
  // 此时不会执行到展开动画,而是执行 angular.noop 将上面读不做
  $q.resolve(expandingExpr(scope))
    .then(function() {
        // 执行细节
    }, angular.noop);

在调用动画执行完成的动作时,调用 $animateCss().start()['finally'](expandDone) 来执行完成时的回调。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注