如何写一个拖拽日历组件(附源码)

avatar
数据可视化 @蚂蚁集团

作者简介 Kid 蚂蚁金服·数据体验技术团队

本文会介绍如何写一个可拖拽日历组件,偏重点在于日历组件的功能挖掘以及对于开发过程的一些思考,编码部分会介绍核心部分的实现。代码在最后也会放出来给大家。

效果演示

先看下项目最后的实现效果:

running.gif | left | 663x480

前期调研

要做一个可拖拽日历组件,先得去看一圈市场上已有的日历组件做成什么样了。主要是为了收集功能以及避免重复造轮子。我调研了google日历,tower,fullcalendar,teambition,webmail等的日历控件。其中google日历和fullcalendar算是使用感受比较好的了,以下是他们的使用动图:

google日历

google.gif | left | 464x372

fullcalendar

QQ20180402-115350-HD.gif | left | 470x397

功能对比

以下是功能细节的对比列表:

拖拽已有事件
(拖拽默认起始位置)
拖拽已有事件拖拽中效果
(产生遮挡)
左右拖拽事件
(只实现了单方向,并且产生遮挡)
拖拽空白区域新建
事件智能布局
(跨周后事件布局乱了)

从表格的对比可以看到,这几家实现的还算不错的日历组件功能都没有实现全,而且在一些细节上都没做好。能找到的开源的只有fullcalendar,不过他是jquery版本的,而且功能实现的并不好,细节上也有不少错误,所以就打算自己写一个了(终于找到重复造轮子的理由了~)。

功能

根据之前的调研,一个较好的可拖拽日历控件需要实现的功能:

  • 拖拽已有事件
  • 双击添加事件
  • 左右拖拽事件
  • 拖拽空白区域新建事件

除了功能之外,还有用户可用性上的细节打磨:

  • 拖拽已有事件的阴影效果
  • 拖拽已有事件拖拽中隐藏
  • 左右拖拽的预览以及阴影效果
  • 事件智能布局,包括堆叠和换行后的冗余清理
  • 拖拽态的处理(偏移量计算以及dragLayer的重写)

编码

需求调研完了进入到编码过程,项目主要基于react和react-dnd。编码的整体步骤包括:

  • 绘制日历
  • 根据事件列表绘制事件
  • 事件布局整理,堆叠效果和换行清理
  • 让事件可拖动
  • 让事件可左右拖动
  • 让空白区域可拖动勾选
  • 拖拽阴影处理 细讲的话文章太长,大家看着累,我讲一下关键实现点。

可拖拽区域

image.png | left | 463x253

主要是用了react-dnd的拖拽能力,图里的4种颜色的圈分别对应了4种可拖拽组件的source。

  • 紫色是事件本身
  • 红色是向左拖拽区域
  • 黑色是向右拖拽区域
  • 绿色是拖拽空白的区域(这个设计最有意思,用拖拽的行为来模拟选中~)

为这4种source分别处理好对应的拖拽事件。为每个source定义好各自的hover事件可以处理不同的阴影效果。定义好layer可以处理拖拽时鼠标的预览效果。

事件智能布局

image.png | left | 334x193

事件分类

就是把一天的事件分为如图所示的5种:

  • 1是有前一天,有后一天
  • 2是无前一天,有后一天
  • 3是有前一天,无后一天
  • 4是无前一天,无后一天
  • 5是空白的填充事件

渲染某一天的时候,先渲染1和3这种有前一天的,因为他们有个稳定的index,然后按照优先级渲染事件2,然后是4,填不满空白的渲染事件5。然后将1和2的index传到下一天。这样就能较好的布局了。

新周事件清理

image.png | left | 503x180

如图,检测到新一周开始的时候,我会先清理中间的空白。然后再根据优先级排序事件。

整个开心的代码编码过程结束后,居然发现自己内心有些拒绝这份代码..为啥写完代码,调试完成后,自己不愿意再回头看了呢。哦,是因为真的写的不好看啊!不好看换句话说就是代码的内在质量差。

质量

软件质量可以分为外在的和内在的。外在质量包括:

  • 可用性
  • 正确性
  • 健壮性
  • ....

外在质量是用户关心的唯一软件特性,我上面一直追求和打磨的其实都属于软件的外在质量。用户是爽的~

可我内心有些拒绝这份刚写的代码的原因是因为软件的内在质量出了问题,内在质量包括:

  • 可读性(可能是最重要的了)
  • 可维护性
  • 可接入性(现在支持自定义form,感觉还是不错的~)
  • ....

写着写着发现代码不好理解了,耐下心细看一下发现:

  • 代码有冗余
  • 子程序命名没有起好
  • 代码结构混乱
  • 瞎定义css名
  • ....

接着干嘛,先重构咯~

  • 整理代码结构,抽抽抽,让整体流程清晰
  • 重新思考子程序命名,写注释
  • 思考对外提供的接口,加强可接入性
  • .....

重构完成一波后思考为什么内在质量会出现问题呢?

质量上的追求是无穷尽的,无论是内在的还是外在的。很多技术都可以提升质量:补充测试用例,良好的编程风格,合理的注释,分层抽象,合理的接口...让所有的特性都表现的尽善尽美绝无可能。根据一组互相竞争的目标找出一套解决方案,正是这种情况让软件成为一个真正的工程学科

设计

为什么质量会差呢?当然是因为设计的有问题,才会在开发中发现设计不符合而不断调整设计,导致破坏了软件中非常重要的“系统完整性”(人月神话相当推崇的重点)。最终就导致了质量差。

那么为什么没有好好设计呢?我估计是因为没有抵挡住“赶紧开始编码”的诱惑! 在思考了如何拖拽已有事件之后我就迫不及待的开始编码。没有进行更为详细的设计。其实这个阶段我在大脑里已经隔绝了后续的需求了(为了智力上可管理采取的自我保护吧~)。而在实现完拖拽已有事件之后,我开始思考如何拖拽空白区域新建,发现已有的设计需要调整。然后在想到了一个还不错的点子之后就又开始了编码。设计不完备的循环导致了质量的下降。

其实软件的产生与现实中的食物链一样,需求->设计->编码。一环套一环,越靠近上游出了问题影响面越大。归纳就是我每次的设计都是基于并不完整的需求来做的。

应该怎么做呢,应该在最开始的时候尽可能的设计全才行。应该加大在初期投入的设计时间。

迭代式开发

可能有朋友看到这里感觉,不对啊,设计本来就是“险恶的问题”(也就是必须被解决或者部分解决才能定义的问题)。所以开发过程采用迭代式开发没问题啊。迭代式的好处在于有利于规避风险。不断小成本的尝试来降低失败的风险。所以本来迭代式的开发就必须配备不断的重构来保证质量的。

对于这个问题。我觉得应该看项目的规模。看我们脑海中可控的复杂度。可能更好的方式是进行原型的开发进行试验,试验发现可能之后立刻重新整体设计,然后再编码,这样的成本会更小些。

就是在初次迭代进行了一些难点的验证之后,进行整体设计,这样可以减少迭代式开发后必不可少的重构环节,减少整体的开发成本,也提高了项目的质量。

总结

本文分享了可拖拽日历组件的实现过程,编码细节以及对于整体开发的思考。之所以会探讨对于开发的思考是因为各种方法论都被用于大小不同的项目,对于小项目,方法论的应用显得很不经意且趋于本能。如果我们有选择的使用一些方法论的话是能够减少开发时间以及提升产品质量的。最后附上代码的传送门,具体的接入使用可以看下项目的README,还是有相当多缺陷的,欢迎提PR~

对我们团队感兴趣的可以关注专栏,关注github或者发送简历至'tao.qit####alibaba-inc.com'.replace('####', '@'),欢迎有志之士加入~

原文地址:github.com/ProtoTeam/b…