├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── LICENSE
├── README.md
├── component
├── base.js
├── calendar
│ ├── calendar.css
│ ├── calendar.js
│ ├── demo
│ │ └── calendar.html
│ ├── images
│ │ ├── btn-next.png
│ │ ├── btn-prev.png
│ │ └── info.png
│ ├── readme.md
│ └── themes
│ │ └── grace
│ │ └── skins
│ │ └── default
│ │ └── skin.css
└── weekcalendar
│ ├── demo
│ ├── test.js
│ ├── test.json
│ └── week.html
│ ├── images
│ ├── btn-next.png
│ └── btn-prev.png
│ ├── readme.md
│ ├── weekcalendar.css
│ ├── weekcalendar.js
│ └── weekcalendar.scss
└── lib
├── jquery.min.js
├── mock-min.js
└── moment.min.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.min.js
2 | **/lib/*
3 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": false,
6 | "node": false
7 | },
8 | // 使用默认的配置
9 | "extends": "eslint:recommended",
10 | "parserOptions": {
11 | "ecmaFeatures": {
12 | "jsx": true
13 | },
14 | "sourceType": "script"
15 | },
16 | "globals": {
17 | // 是否允许被重写
18 | "jQuery": false,
19 | "$": false,
20 | "mini": false,
21 | "Util": false,
22 | "epoint": false
23 | },
24 | // [中文文档: http://eslint.cn/docs/rules/ ](http://eslint.cn/docs/rules/)
25 | "rules": {
26 | "no-const-assign": "warn",
27 | "no-this-before-super": "warn",
28 | "no-undef": "warn",
29 | "no-unreachable": "warn",
30 | "no-unused-vars": "warn",
31 | "constructor-super": "warn",
32 | "valid-typeof": "warn",
33 | "no-delete-var": "warn",
34 | // 分号警告
35 | "semi": "warn",
36 | // 单引号警告
37 | "quotes": ["warn", "single"],
38 | // 强制所有控制语句使用一致的括号风格
39 | "curly": "warn",
40 | // 要求 switch 语句中有 default 分支
41 | "default-case": "warn",
42 |
43 | "no-multiple-empty-lines": "warn",
44 | "no-console": "off",
45 | "no-empty": "off"
46 | }
47 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Douglas Chen [github](https://github.com/cdswyda)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # component-frame
2 |
3 | 一套组件机制,支持自动加载依赖资源、事件、继承、重写等
4 |
5 | 目前已经在里面新增了两个日历组件:
6 |
7 | - 一个月视图日历,支持在日历中插入任意内容。 [在线演示](https://cdswyda.github.io/component-frame/component/calendar/demo/calendar.html) [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/calendar)
8 | - 二维周视图日历。 [在线演示](https://cdswyda.github.io/component-frame/component/weekcalendar/demo/week.html) [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/weekcalendar)
9 |
10 | ## 基本功能
11 |
12 | - 提供了一套完整的事件机制
13 | - 提供控件的继承方法、重写方法
14 | - 自动加载控件依赖资源
15 | - 在资源加载完成后自动调用控件的初始化方法
16 |
17 | ## 基本使用
18 |
19 | 1. 引入控件机制的js文件
20 | 1. 引入实际要使用控件的js文件
21 | 1. 调用统一的初始化方法 `epctrl.init(name/* 控件名称 */, cfg /* 控件初始化配置*/)` 即可
22 |
23 | ## 控件机制api
24 |
25 | ### 继承方法
26 |
27 | ```js
28 | /**
29 | * 继承方法
30 | *
31 | * @param {String} subName 新Class的名称
32 | * @param {String} spName 父类的名称
33 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合
34 | * @returns 继承后的Class
35 | */
36 | epctrl.extend(subName, spName, overrides);
37 | ```
38 |
39 | 继承控件时,如果指定的的父类控件还未加载,会自动加载父类资源。
40 |
41 | ### 重写方法
42 |
43 | ```js
44 | /**
45 | * 重写方法
46 | *
47 | * @param {String} destName 要重写的控件的名称
48 | * @param {Object} proto 要重写的属性、方法集合
49 | * @returns 重写后的控件
50 | */
51 | epctrl.overwrite(destName, proto);
52 | ```
53 |
54 | 重写控件时,如果指定的的父类控件还未加载,会自动加载父类资源。
55 |
56 | ### 初始化方法
57 |
58 | 基于此机制的控件,统一使用此方法进行初始化。
59 |
60 | ```js
61 | /**
62 | * 初始化方法
63 | * @param {String} name 控件名称
64 | * @param {Object} cfg 控件需要的配置
65 | * @returns 实例控件
66 | */
67 | epctrl.init(name, cfg);
68 | ```
69 |
70 | 此方法中会自动实例化出一个指定的控件,并在此控件依赖资源加载完成后自动调用创建方法,返回实例控件。
71 |
72 | ### 控件基类
73 |
74 | `epctrl.Control` 为控件机制中所有控件的基类,其中提供了事件机制,以及自动引入相关资源,并在所有依赖资源加载完成后,自动调用控件的初始化方法。
75 |
76 | 基类构造函数在执行时将读取配置中的events属性,如果存在,则自动将指定的响应事件绑定到实例对象上。同时,如果此控件需要皮肤,则会自动绑定一个仅指向一次的加载皮肤事件,以便在加载资源时自动引入皮肤资源。
77 |
78 | **方法**
79 |
80 | 方法名称 | 参数 | 说明
81 | -- | -- | --
82 | `on(type, fn, scope)` | `type` :字符串类型,表示事件名称
`fn` :事件处理函数
`scope` : 额外指定的事件处理函数执行时的上下文,不指定时为控件实例对象 | 绑定一个事件
83 | `off(type, fn)` | `type` :字符串类型,表示事件名称
`fn` :事件处理函数 | 取消绑定一个事件,第二个参数可省略,省略时取消绑定当前类型下的所有处理事件
84 | `fire(type, data)` | `type` :字符串类型,表示事件名称
`data` 一个对象,触发此事件时的数据 | 触发一个事件,返回事件对象
事件处理函数的实参中除了fire时传递的data中的所有属性之外,还包括以下通用属性:
`type` : 事件类型
`origin` : 事件所在实例对象,是指为当前控件的实例对象
`scope` : 用户指定的事件处理函数的执行上下文,未指定时为undefined。
`cancel` : 是否取消,用于一些情况下取消事件的后续操作。
85 | `one(type, fn, scope)` | 和 `on` 方法相同 | 绑定一个仅执行一次的事件
86 | `addCss(url)` | `url` :字符串类型,表示css资源路径 | 为此控件添加一个css资源
87 | `addJs(url)` | `url` :字符串类型,表示js资源路径 | 为此控件添加一个js资源
88 | `setCss(url)` | `url` :字符串类型或数组类型,表示css资源路径,数组表示有多个资源按顺序加载 | 为此控件设置css资源
有相应的get方法,将set换为get即可,表示获取。
89 | `setJs(url)` | `url` :字符串类型或数组类型,表示js资源路径,数组表示有多个资源按顺序加载 | 为此控件设置js资源
有相应的get方法,将set换为get即可,表示获取。
90 | `setHtml(url)` | `url` :字符串类型,表示html资源路径 | 为此控件设置html资源
有相应的get方法,将set换为get即可,表示获取。
91 | `setHtmlContainer(selector)` | `selector` :字符串类型,一个jQuery选择器,表示html要插入的容器 | 为此控件设置html资源插入的容器
有相应的get方法,将set换为get即可,表示获取。
92 | `throwError(msg)` | `msg` 一个字符串,表示错误信息 | 抛出一个错误
93 | `print(msg, type)` | `msg` 一个字符串,表示要在控制台输出的信息
`type` 可选值为 `"info"` 、 `"warn"` 、 `"error"` 之一,表示输出信息类型 | 在控制台输出信息
94 | `loadSource()` | | 自动加载控件资源,此方法中将读取控件所依赖的css、html和js资源,并在html和js资源加载完成后自动调用控件的 `create` 方法。
**此方法无需手动调用**,会自动在 `epctrl.init` 方法中调用。
95 | `create()` | | 控件所依赖资源加载完成后的真实创建方法。
**控件应该实现此方法**
不同控件应该有不同的初始化方法,不应该直接调用到此基类的创建方法,此方法被调用时将抛出一个错误。
96 | `_cacheFn(fn [,fnArgs])` | fn 一个函数为迟延执行的方法
其他后续参数将被传递给指定的fn |缓冲一个方法,直到控件初始化完成后再真实调用。 |
97 |
98 | > 以上方法中,最终用户会使用的仅为 `on`、`one` 方法,其余均是提供给开发者在新控件中使用的。
99 |
100 | **提供属性**
101 |
102 | 属性名称 | 说明
103 | -- | --
104 | `type` | 类型为一个字符串 ,值和控件的构造函数名相同,不区分大小写,用于标识此控件的类型。
每个控件在继承时必须重写此属性。
105 | `useSkin` | 值为一个布尔值,表示此控件是否需要皮肤,默认在基类上提供,值为false,如果某控件需要换肤,需要设置为true,并提供相应的皮肤资源。
106 | `cssUrl` | 值为一个数组,表示此控件需要加载的所有的css资源路径
107 | `jsUrl` | 值为一个数组,表示此控件需要加载的所有的js资源路径
108 | `htmlUrl` | 值为一个字符串,表示此控件需要加载的html资源路径
109 | `htmlContainer` | 控件html资源加载成功后要插入的容器,值为一个jq选择器或jq对象或HTMLElement
110 |
111 | > 控件基类提供的以上属性中,均为控件开发者使用的,最终用户无需关注。
112 |
113 | **事件**
114 |
115 | 触发事件时,除了触发时传递的其他属性之外,固定有以下属性:
116 |
117 | ```js
118 | {
119 | type: String, // 事件类型
120 | origin: Object, // 事件绑定到的对象,即当前控件
121 | cancel: Boolean, // 是否取消
122 | scope: Object // 事件处理函数执行的上下文对象,绑定事件时未指定则同origin
123 | }
124 | ```
125 |
126 | 此外控件基类中自动处理流程中会按顺序触发以下事件:
127 |
128 | 事件名称 | 额外参数 | 说明
129 | -- | -- | --
130 | `beforeSourceLoad` | `cssUrl` : 将要加载的css资源
`jsUrl` :将要加载的js资源 | 控件开始加载所需要的资源之前触发。
131 | `beforeHTMLLoad` | `htmlUrl` : 将要加载的HTML资源 | 控件HTML资源加载前触发
132 | `beforeHtmlInsert` | `htmlUrl` : 将要加载的HTML资源
`content` :获取到的HTML内容
`htmlContainer` : HTML将要插入的容器 | 控件HTML资源加载成功,插入页面前触发。
133 | `afterHTMLLoad` | 同 beforeHtmlInsert | 控件HTML资源加载成功,并插入页面后触发。
134 | `beforeJsload` | `jsUrl` :将要加载的js资源 | js资源开始加载前触发。
135 | `afterJsLoad` | `jsUrl` :加载的js资源 | js资源加载完成后触发。
136 | `afterSourceLoad` | `cssUrl` : 加载的css资源
`jsUrl` :加载的js资源 | 控件加载资源完成后触发。
137 |
138 | ## 控件机制约定和规范
139 |
140 | ### 命名约定
141 |
142 | - 控件的构造函数名称遵循帕斯卡命名规范
143 | - 控件继承时,必须为此控件指定 `type` 属性,类型为字符,值和构造函数名称相同
144 | - 控件存放的js、css文件名称和控件的构造函数名称一致
145 | - 控件存放的js、css文件名称全部小写
146 |
147 | ### 控件资源组织
148 |
149 | 一个控件形成一个文件夹,以自己的控件名称(即控件的构造函数名称)命名,全部小写,放在控件基类文件(`base.js`)所在的目录下。
150 |
151 | 控件的资源目录组织结构应如下所示:
152 |
153 | ```
154 | component
155 | |- controlname
156 | |- controlname.js // 控件js
157 | |- controlname.css // 控件css
158 | |- readme // 关于此控件的说明
159 | |- images // 控件需要的图片资源
160 | |- 图片资源1
161 | |- 图片资源2
162 | |- ...
163 | |- 图片资源n
164 | |- themes // 皮肤资源 如果控件需要换肤则必须提供
165 | |- grace // 主题名称 如dream、classic、imac等
166 | |- skins
167 | |- default // 皮肤的名称
168 | |- skin.css // 此皮肤的css文件
169 | |- chinared
170 | |- skin.css
171 | |- dream
172 | |- ..
173 | |- classic
174 | |- ..
175 |
176 | ```
177 |
178 | 以下为一个控件的结构示例
179 |
180 | ```
181 | component
182 | |- calendar
183 | |- calendar.js // 控件js
184 | |- calendar.css // 控件css
185 | |- readme.md // 关于此控件的说明
186 | |- images // 控件需要的图片资源
187 | |- btn-prev.png
188 | |- btn-next.png
189 | |- themes // 皮肤资源 如果控件需要换肤则必须提供
190 | |- grace // 主题名称 如dream、classic、imac等
191 | |- skins
192 | |- default // 皮肤的名称
193 | |- skin.css // 此皮肤的css文件
194 | |- chinared
195 | |- skin.css
196 | ```
197 |
198 | ### 配置
199 |
200 | **开发时的约定**
201 |
202 | 除以上命名和资源组织之外,控件开发时还需要注意:
203 |
204 | - 每个控件需要实现自己的 `create` 方法,并在控件真实创建完成后触发 `afterCreate` 事件。
205 | - `useSkin` 属性:表示此控件是否需要切换皮肤
206 | - 通常一种控件是否需要换肤在开发时就可以确定,因此开发者应该在开发时为此控件指定好此属性,需要皮肤的情况下,在资源加载前,会自动获取当前皮肤并加载。
207 | - 如果需要切换皮肤,必须提供相应的皮肤资源,皮肤资源组织结构参考上面的控件资源组织结构。
208 | - 关于 `setCss()`、`setJs` 方法 和 `addCss()` 、 `addJs()` 方法的作用和区别。
209 | - 都是为控件指定需要的css和js资源。
210 | - 直接使用 `this.setCss()` 和 `this.setCss()` 时,参数为一个字符串或数组,值为资源路径,表示直接为此控件指定所需要的css和js资源。
211 | - 使用 `this.addCss()` 和 `this.addJs()` 方法,传递一个资源的路径时,此时是在父类css或js资源的基础上新加入指定的css或js文件。
212 |
213 | **关于用户使用时配置的约定**
214 |
215 | - 配置 `cfg` 必须为一个对象。
216 | - 配置对象中的 `events` 属性为一个对象,键名为事件名称,值为处理函数。用于绑定控件在初始化过程中就需要的事件。没有则无需指定。
217 |
218 | ## 继承基类创建一个新的控件
219 |
220 | 继承固定格式如下:
221 |
222 | ```js
223 | // 新控件的构造函数
224 | epctrl.Calendar = function (cfg) {
225 | epctrl.Calendar.superclass.constructor.apply(this, arguments);
226 |
227 | // TODO 控件上的其他操作
228 | };
229 | // 继承关系
230 | epctrl.extend('Calendar', 'Control', {
231 | // 此控件类型
232 | type:"Calendar",
233 | // 创建方法,在资源加载完成后自动调用
234 | create: function () {
235 | // TODO 此控件的创建方法
236 | }
237 | });
238 | ```
239 |
240 | 以上展示了继承基类创建一个控件名称为 **Calendar** 的新控件的固定写法和必要部分。
241 |
242 | 可分为两个部分:构造函数和继承关系
243 |
244 | **构造函数**
245 |
246 | 1. 首先在控件机制的命名空间下新增一个名为 `Calendar` 的构造函数,参数为此控件的配置。
247 | 1. 在构造函数内部首先调用此控件的 `superclass.constructor` 方法,作用为先执行一遍父类的构造函数,固定写法为: `epctrl.新增的控件.superclass.constructor.apply(this, arguments)` , 此处的Calendar控件,即:`epctrl.Calendar.superclass.constructor.apply(this, arguments);`
248 |
249 | **继承关系**
250 |
251 | 1. 直接调用 `epctrl.extend` 方法,第一个参数为当前控件的名称,第二个参数为父类控件的名称,第三个参数为要新增或覆盖的属性、方法集合。
252 | 1. 在第三个参数中,必须有 `type` 属性和 `create()` 方法,`type` 属性用于唯一标识当前控件的名称,值为一个字符串,约定值和当前控件构造函数名称相同;`create()` 方法为此控件的真实创建方法。
253 |
254 | ## 继承已有控件创建一个新的控件
255 |
256 | 使用和基于基类创建控件类似,首先也是创建自己的构造函数,并在构造函数内首先调用父类的构造函数,之后指定此控件的继承关系和需要新增或重写的方法即可。
257 |
258 | 例如继承上面创建的 `Calendar` 控件,来创建一个新的 `ExtraCalendar` :
259 |
260 | ```js
261 | // 新控件构造函数
262 | epctrl.ExtraCalendar = function (cfg) {
263 | epctrl.ExtraCalendar.superclass.constructor.apply(this, arguments);
264 |
265 | // 到此 所有在Calendar构造函数中处理的内容已经执行,之后在加入或修改即可。
266 | // TODO
267 | };
268 |
269 | // 继承关系
270 | epctrl.extend('ExtraCalendar', 'Calendar', {
271 | // 此控件类型 必须重写
272 | type:"ExtraCalendar",
273 | // 创建方法,在资源加载完成后自动调用
274 | // 如果此控件的创建方法可以复用Calendar中的,则可以不用重写
275 | create: function () {
276 | // TODO 此控件的创建方法
277 | }
278 | });
279 | ```
280 |
281 | ## 重写一个已有控件
282 |
283 | 如果已有控件的某些方法不符合目前的需求,则可以重写这个已有控件的部分属性和方法。
284 |
285 | 使用 `epctrl.overwrite(destName, proto)` 方法即可。
286 |
287 | 如重写之前创建的 `ExtraCalendar` 控件:
288 |
289 | ```js
290 | // 重写控件
291 | epctrl.overwrite('ExtraCalendar', {
292 | // 需要新增或者重写的属性方法
293 | // ...
294 | });
295 | ```
296 |
--------------------------------------------------------------------------------
/component/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一套组件机制
3 | * author: Douglas Chen
4 | */
5 |
6 | /* global epctrl*/
7 |
8 | (function (win, $) {
9 |
10 | if (!$ || typeof $ != 'function') {
11 | throw new Error('epctrl require jQuery');
12 | }
13 |
14 | var isIE67 = '\v' == 'v',
15 | isIE8 = !!document.all && document.querySelector && !document.addEventListener;
16 |
17 | var getPathByName = function (name) {
18 | name = name.toLowerCase();
19 |
20 | var url = epctrl._rootPath + '/' + name + '/' + name + '.js';
21 |
22 | return url;
23 | };
24 |
25 | var throwError = function (msg) {
26 | throw new Error(msg);
27 | };
28 |
29 | /**
30 | * 同步加载资源,用于继承和重写时自动引入父类的定义
31 | *
32 | * @param {String} url 父类名称
33 | * @returns jQuery.promise
34 | */
35 | var loadSupperSync = function (name) {
36 | return $.ajax({
37 | url: getPathByName(name),
38 | type: 'get',
39 | dataType: 'TEXT',
40 | async: false
41 | }).done(function (data) {
42 | var script = document.createElement('script');
43 | script.text = data;
44 | document.getElementsByTagName('head')[0].appendChild(script);
45 | }).fail(throwError);
46 | };
47 |
48 | /**
49 | * 加载父类资源
50 | *
51 | * @param {String} supperCls 父类控件名称
52 | * @returns undefined
53 | */
54 | var loadSupperCls = function (supperCls) {
55 | // 已经存在则不用操作
56 | if (typeof epctrl[supperCls] == 'function') {
57 | return;
58 | }
59 |
60 | // 否则同步加载父类资源
61 | loadSupperSync(supperCls);
62 | };
63 |
64 | /**
65 | * 继承实现
66 | *
67 | * @param {Function} newClass 新Class的构造函数
68 | * @param {Function} sp 父类的构造函数
69 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合
70 | * @returns 继承后的Class
71 | */
72 | var extend = function (newClass, sp, overrides) {
73 | overrides = overrides || {};
74 |
75 | if (typeof sp != 'function') {
76 | return this;
77 | }
78 |
79 | // 自动给新控件中加入用于记录资源是否加载的记录对象
80 | if (!overrides._jsPromise) {
81 | // js资源的promise
82 | overrides._jsPromise = {};
83 | }
84 |
85 | var sb = newClass,
86 | sbp = sb.prototype,
87 | spp = sp.prototype;
88 | if (sb.superclass == spp) {
89 | return;
90 | }
91 | sb.superclass = spp;
92 | sb.superclass.constructor = sp;
93 |
94 | var p;
95 |
96 | for (p in spp) {
97 | sbp[p] = spp[p];
98 | }
99 | if (overrides) {
100 | for (p in overrides) {
101 | sbp[p] = overrides[p];
102 | }
103 | }
104 | return sb;
105 | };
106 |
107 | /**
108 | * 重写实现
109 | *
110 | * @param {Object} dest 要重写的控件
111 | * @param {Object} proto 要重写的属性、方法集合
112 | * @returns 重写后的控件
113 | */
114 | var overwrite = function (dest, proto) {
115 | var d;
116 | if (typeof dest == 'function') {
117 | d = dest.prototype;
118 | } else if (typeof dest == 'object') {
119 | d = dest;
120 | } else {
121 | return this;
122 | }
123 |
124 | for (var c in proto) {
125 | d[c] = proto[c];
126 | }
127 | return dest;
128 | };
129 |
130 | var Control = function () {
131 | // 添加事件存储对象
132 | this._events = {};
133 | // 添加css和js资源属性
134 | this.cssUrl = [];
135 | this.jsUrl = [];
136 | // html 加载
137 | this.htmlUrl = '';
138 | this.htmlContainer = false;
139 |
140 | // 缓存需要迟延执行的方法
141 | this._FnQueues = [];
142 |
143 | // 处理初始化的事件绑定
144 | // 统一约定初始化就需要的事件放在参数的events中,键名为事件名称,值为处理函数
145 | var cfg = arguments[0],
146 | events = cfg.events;
147 | // 加入事件
148 | if (events) {
149 | for (var p in events) {
150 | if (events.hasOwnProperty(p)) {
151 | this.on(p, events[p]);
152 | }
153 | }
154 | }
155 |
156 | // 绑定一个创建完成事件,处理迟延执行方法队列
157 | this.on('afterCreate', this._onCreate);
158 | };
159 |
160 | Control.prototype = {
161 | // 控件唯一标识
162 | type: 'control',
163 | constructor: Control,
164 | // js资源的promise 在继承方法中自动重写此属性保证每个控件资源加载独立
165 | _jsPromise: {},
166 | // 控件所在路径
167 | _getControlPath: function () {
168 | return 'frame/fui/js/widgets/epcontrols/' + this.type.toLowerCase() + '/';
169 | },
170 | // 事件实现
171 | /**
172 | * 绑定事件
173 | *
174 | * @param {String} type 事件类型
175 | * @param {Function} fn 事件处理函数
176 | * @param {Object} scope 要为事件处理函数绑定的执行上下文
177 | * @returns 当前实例对象
178 | */
179 | on: function (type, fn, scope) {
180 | if (typeof type != 'string') {
181 | // console && console.error && console.error('the first arguments type is requird as string');
182 | this.print('the first arguments type is requird as string', 'error');
183 | return this;
184 | }
185 | if (typeof fn != 'function') {
186 | // console && console.error && console.error('the second arguments fn is requird as function');
187 | this.print('the second arguments fn is requird as function', 'error');
188 | return this;
189 | }
190 |
191 | type = type.toLowerCase();
192 |
193 | if (!this._events[type]) {
194 | this._events[type] = [];
195 | }
196 |
197 | this._events[type].push(scope ? [fn, scope] : [fn]);
198 |
199 | return this;
200 | },
201 |
202 | /**
203 | * 取消绑定一个事件
204 | *
205 | * @param {String} type 取消绑定的事件名称
206 | * @param {Function} fn 要取消绑定的事件处理函数,不指定则移除当前事件类型下的全部处理函数
207 | * @returns 当前实例对象
208 | */
209 | off: function (type, fn) {
210 | type = type.toLowerCase();
211 |
212 | var eventArr = this._events[type];
213 |
214 | if (!eventArr || !eventArr.length) {
215 | return this;
216 | }
217 |
218 | if (!fn) {
219 | this._events[type] = eventArr = [];
220 | } else {
221 | for (var i = 0; i < eventArr.length; ++i) {
222 | if (fn === eventArr[i][0]) {
223 | eventArr.splice(i, 1);
224 | // 1、找到后不能立即 break 肯存在一个事件一个函数绑定多次的情况
225 | // 删除后数组改变,下一个仍然需要遍历处理!
226 | --i;
227 | }
228 | }
229 | }
230 | return this;
231 | },
232 | /**
233 | * 触发事件
234 | *
235 | * @param {String} type 触发事件的名称
236 | * @param {Anything} data 要额外传递的数据,事件处理函数参数如下
237 | * event = {
238 | // 事件类型
239 | type: type,
240 | // 绑定的源,始终为当前实例对象
241 | origin: this,
242 | // 事件处理函数中的执行上下文 为 this 或用户指定的上下文对象
243 | scope :this/scope
244 | // 其他数据 为fire时传递的数据
245 | data: data
246 | }
247 | * @returns 事件对象
248 | */
249 | fire: function (type, data) {
250 | type = type.toLowerCase();
251 |
252 | var eventArr = this._events[type];
253 |
254 | var fn,
255 | // event = {
256 | // // 事件类型
257 | // type: type,
258 | // // 绑定的源
259 | // origin: this,
260 | // // scope 为 this 或用户指定的上下文,
261 | // // 其他数据
262 | // data: data,
263 | // // 是否取消
264 | // cancel: false
265 | // };
266 | // 上面对自定义参数的处理不便于使用 将相关属性直接合并到事件参数中
267 | event = $.extend({
268 | // 事件类型
269 | type: type,
270 | // 绑定的源
271 | origin: this,
272 | // scope 为 this 或用户指定的上下文,
273 | // 其他数据
274 | // data: data,
275 | // 是否取消
276 | cancel: false
277 | }, data);
278 |
279 | if (!eventArr) {
280 | return event;
281 | }
282 |
283 | for (var i = 0, l = eventArr.length; i < l; ++i) {
284 | fn = eventArr[i][0];
285 | event.scope = eventArr[i][1] || this;
286 | fn.call(event.scope, event);
287 | }
288 | return event;
289 | },
290 | /**
291 | * 绑定一个只执行一次的事件
292 | *
293 | * @param {String} type 事件类型
294 | * @param {Function} fn 事件处理函数
295 | * @param {Object} scope 要为事件处理函数绑定的执行上下文
296 | * @returns 当前实例对象
297 | */
298 | one: function (type, fn, scope) {
299 | var that = this;
300 |
301 | function nfn() {
302 | // 执行时 先取消绑定
303 | that.off(type, nfn);
304 | // 再执行函数
305 | fn.apply(scope || that, arguments);
306 | }
307 |
308 | this.on(type, nfn, scope);
309 |
310 | return this;
311 | },
312 | // 资源加载实现
313 | // 动态加载js
314 | _loadJsPromise: function (url) {
315 |
316 | // 无 url 则直接成功
317 | if (!url) {
318 | return $.Deferred().resolve().promise();
319 | }
320 |
321 | // 已经加载 则返回之前的promise
322 | if (this._jsPromise[url]) {
323 | return this._jsPromise[url];
324 | }
325 |
326 | var that = this,
327 | dtd = $.Deferred(),
328 | script = document.createElement('script');
329 |
330 | script.type = 'text/javascript';
331 |
332 | // IE8- IE9+ 已经支持onload等,控制更加精确
333 | if ((isIE67 || isIE8) && script.readyState) {
334 | script.onreadystatechange = function () {
335 | if (script.readyState == 'loaded' || script.readyState == 'complete') {
336 | dtd.resolve();
337 | script.onreadystatechange = null;
338 | }
339 | };
340 | } else {
341 | script.onload = function () {
342 | dtd.resolve();
343 | script.onload = null;
344 | };
345 | script.onerror = function () {
346 | dtd.reject();
347 | script.onerror = null;
348 | that._jsPromise[url] = null;
349 | throwError(url + ':此资源加载失败!');
350 | };
351 | }
352 |
353 | this.fire('beforeOneJsload', {
354 | jsUrl: url
355 | });
356 |
357 | script.src = epctrl._rootPath + '/' + url;
358 | // append to head
359 | document.getElementsByTagName('head')[0].appendChild(script);
360 |
361 | return (this._jsPromise[url] = dtd.promise());
362 | },
363 | loadJs: function (urls, callback) {
364 |
365 | var successDtd = $.Deferred();
366 |
367 | if (!urls || !urls.length) {
368 | // jq 3.0以下版本的此处有问题 此处会立即执行 是一个bug 不能直接使用
369 | // return $.Deferred().resolve().promise().then(function () {
370 | // callback && callback();
371 | // });
372 |
373 | if ($.fn.jquery >= '3') {
374 | return $.Deferred().resolve().promise().then(function () {
375 | callback && callback();
376 | });
377 | } else {
378 |
379 | // 用迟延模拟异步
380 | setTimeout(function () {
381 | successDtd.resolve();
382 | });
383 | return successDtd.promise().then(function () {
384 | callback && callback();
385 | });
386 | }
387 | }
388 |
389 | var that = this;
390 | if (typeof urls == 'string') {
391 | urls = [urls];
392 | }
393 |
394 | this.fire('beforeJsload', {
395 | jsUrl: urls
396 | });
397 |
398 | var loads = JSON.parse(JSON.stringify(urls));
399 |
400 | function loadNext() {
401 | // 单个资源加载完成
402 | that.fire('afterOneJsload', {
403 | js: loads[0]
404 | });
405 |
406 | // 删除第一个元素
407 | loads.splice(0, 1);
408 |
409 | if (loads.length) {
410 | // 还有时继续加载
411 | return that._loadJsPromise(loads[0])
412 | .done(loadNext)
413 | .fail(that.throwError);
414 | } else {
415 | // 待加载的js中没有元素时代表全部资源加载完毕
416 | that.fire('afterJsLoad', {
417 | jsUrl: urls
418 | });
419 | // callback && callback();
420 | return $.Deferred().resolve().promise().then(function () {
421 | callback && callback();
422 | });
423 | }
424 | }
425 |
426 | return this._loadJsPromise(loads[0])
427 | .then(loadNext);
428 | },
429 | /**
430 | * 加载css
431 | *
432 | * @param {String/Array} urls 要加载的css资源的路径,多个用数组表示。可为根路径写起或相对路径
433 | * @param {HTMLElement} target 要插入到的目标位置,默认为head元素
434 | * @param {String} pos 要插入的位置,值为After或Before
435 | */
436 | loadCss: function (urls, target, pos) {
437 | // 外部组件需要引用的css可能有多个
438 | if (typeof urls == 'string') {
439 | urls = [urls];
440 | }
441 |
442 | var link = null,
443 | head = document.getElementsByTagName('head')[0];
444 | for (var i = 0, l = urls.length; i < l; ++i) {
445 |
446 | if (document.getElementById('style-' + this.type + (l > 1 ? '-' + i : ''))) {
447 | // 当前需要的css已经存在是则跳过加载 避免重复加载
448 | continue;
449 | }
450 |
451 | link = document.createElement('link');
452 | link.rel = 'stylesheet';
453 | // 如果仅一个 则id为 style-当前控件类型
454 | // 多个css时 id 为 style-当前控件类型-i
455 | link.id = 'style-' + this.type + (l > 1 ? '-' + i : '');
456 |
457 | link.href = epctrl._rootPath + '/' + urls[i];
458 |
459 | if (!target) {
460 | head.appendChild(link);
461 | } else {
462 | if (pos == 'Before') {
463 | head.insertBefore(link, target);
464 | } else {
465 | head.appendChild(link);
466 | }
467 | }
468 | }
469 | },
470 | /**
471 | * 加载一段html
472 | *
473 | * @param {String} url 加载地址
474 | * @returns 当前实例对象
475 | */
476 | loadHtml: function (url) {
477 | // 无url则表示不用加载 直接返回成功的
478 | if (!url || !this.htmlContainer) {
479 | return $.Deferred().resolve().promise();
480 | }
481 |
482 | var ev = this.fire('beforeHTMLLoad', {
483 | htmlUrl: url
484 | });
485 |
486 | this.htmlUrl = url = ev.htmlUrl;
487 |
488 | var that = this;
489 |
490 | return $.ajax({
491 | url: epctrl._rootPath + '/' + url,
492 | type: 'GET',
493 | async: true,
494 | dataType: 'HTML'
495 | }).done(function (data) {
496 | var ev = that.fire('beforeHtmlInsert', {
497 | htmlUrl: url,
498 | content: data,
499 | htmlContainer: that.htmlContainer
500 | });
501 |
502 | // 处理用户修改
503 | data = ev.content;
504 | that.htmlContainer = ev.htmlContainer;
505 |
506 | // 插入到页面
507 | data && that.htmlContainer && $(data).appendTo(that.htmlContainer);
508 | that.fire('afterHTMLLoad', {
509 | htmlUrl: url,
510 | content: data,
511 | htmlContainer: that.htmlContainer
512 | });
513 | });
514 | },
515 | // 用于添加额外的css资源
516 | // 如控件中只加载了基础的资源,有些插件的某些功能还需要其他额外内容来支持
517 | // 直接作为统一的加载好久比较浪费了
518 | // 故提供此方法,用于在控件实例化之前将其加入即可
519 | addCss: function (cssUrl) {
520 |
521 | if (!cssUrl) {
522 | return;
523 | }
524 |
525 | var cssSources = this.cssUrl;
526 |
527 | // 如果是字符串则转化为数组
528 | if (cssUrl + '' === cssUrl) {
529 | cssUrl = [cssUrl];
530 | }
531 |
532 | $.each(cssUrl, function (i, item) {
533 | cssSources.push(item);
534 | });
535 | },
536 | // 为控件设置css资源
537 | setCss: function (url) {
538 | if (url + '' === url) {
539 | return this.cssUrl.push(url);
540 | } else {
541 | return (this.cssUrl = url);
542 | }
543 | },
544 | // 为控件设置js资源
545 | setJs: function (url) {
546 | if (url + '' === url) {
547 | return this.jsUrl.push(url);
548 | } else {
549 | return (this.jsUrl = url);
550 | }
551 | },
552 | // 设置控件需要的html资源
553 | setHtml: function (url) {
554 | return (this.htmlUrl = url);
555 | },
556 | // 设置HTML插入的容器
557 | setHtmlContainer: function (selector) {
558 | return (this.htmlContainer = selector);
559 | },
560 | getHtmlContainer: function () {
561 | return this.htmlContainer;
562 | },
563 | getHtml: function () {
564 | return this.htmlUrl;
565 | },
566 | getCss: function () {
567 | return this.cssUrl;
568 | },
569 | getJs: function () {
570 | return this.jsUrl;
571 | },
572 | // 用于添加额外的css资源
573 | addJs: function (jsUrl) {
574 |
575 | if (!jsUrl) {
576 | return;
577 | }
578 |
579 | var jsSources = this.jsUrl;
580 |
581 | if (jsUrl + '' === jsUrl) {
582 | jsUrl = [jsUrl];
583 | }
584 |
585 | $.each(jsUrl, function (i, item) {
586 | jsSources.push(item);
587 | });
588 | },
589 | /**
590 | * 自动加载此控件的所有资源,并在资源加载完成后自动调用控件的create方法
591 | */
592 | loadSource: function () {
593 | var that = this;
594 |
595 | // 触发资源加载前事件
596 | var sourseEv = this.fire('beforeSourceLoad', {
597 | cssUrl: this.cssUrl,
598 | jsUrl: this.jsUrl
599 | });
600 |
601 | // 同步事件中的修改
602 | this.cssUrl = sourseEv.cssUrl;
603 | this.jsUrl = sourseEv.jsUrl;
604 |
605 | // css 资源 异步加载 不影响
606 | if (this.cssUrl) {
607 | this.loadCss(this.cssUrl);
608 | }
609 |
610 | // 加载html和js资源
611 | this.loadHtml(this.htmlUrl)
612 | .then(function () {
613 | return that.loadJs(that.jsUrl, function () {
614 | that.fire('afterSourceLoad', {
615 | cssUrl: that.cssUrl,
616 | jsUrl: that.jsUrl
617 | });
618 | // 此处仅能保证css和js加载完毕,不能确定控件是否创建完毕,因为有的控件的初始化和创建仍然是异步的
619 | that.create();
620 | });
621 | });
622 | },
623 |
624 | // 不同控件的初始化方法各不相同,需要每个控件自行实现这个方法,进入此方法时 相关资源已经加载完毕
625 | create: function () {
626 | // 触发创建前事件
627 | // this.fire('beforeCreate');
628 |
629 | this.throwError('控件未提供初始化方法,初始化失败!');
630 |
631 | // 在这里实现控件的创建或初始化机制 完成后触发完成创建的事件
632 | // this.fire('afterCreate');
633 | },
634 | // 在创建完成后检查缓存队列
635 | _onCreate: function (e) {
636 | // 真实的触发源
637 | var origin = e.origin,
638 | cache = origin._FnQueues,
639 | item;
640 |
641 | while (cache.length) {
642 | item = cache.splice(0, 1)[0];
643 |
644 | item[0].apply(origin, item[1]);
645 | }
646 | },
647 | /**
648 | * 缓存要在创建完成后再执行的方法
649 | *
650 | * @param {Function} fn 要迟延执行的方法
651 | * @param {any} 此方法执行时的其他参数
652 | * @returns
653 | */
654 | _cacheFn: function (fn) {
655 | if (typeof fn != 'function') {
656 | this.throwError('The first argument must be a function');
657 | return;
658 | }
659 |
660 | var args = Array.prototype.slice.call(arguments, 1);
661 |
662 | this._FnQueues.push([fn, args]);
663 | },
664 |
665 | // 抛出一个错误,参数为错误文本或错误对象
666 | throwError: throwError,
667 | // 控制台打印信息
668 | print: function (msg, type) {
669 | // type = type || 'info';
670 | if (!win.console || !win.console.log) {
671 | return;
672 | }
673 |
674 | switch (type) {
675 | case 'info':
676 | console.log(msg);
677 | break;
678 | case 'warn':
679 | console.warn(msg);
680 | break;
681 | case 'error':
682 | console.error(msg);
683 | break;
684 | default:
685 | console.log(msg);
686 | break;
687 | }
688 | }
689 |
690 | };
691 |
692 | win.epctrl = {
693 | // 组件存放的目录
694 | _rootPath: '/component',
695 | /**
696 | * 继承实现
697 | *
698 | * @param {Function} newClass 新Class的构造函数
699 | * @param {String} spName 父类的名称
700 | * @param {Object} overrides 要新增或重写到原型上的属性和方法集合
701 | * @returns 继承后的Class
702 | */
703 | extend: function (sbName, spName, overrides) {
704 | // 首字符转化为大写
705 | var name = sbName.substr(0, 1).toUpperCase() + sbName.substr(1),
706 | newClass = this[name];
707 |
708 | if (typeof newClass != 'function') {
709 | throw new Error('在epctrl下未找到' + sbName + '的定义,请确认控件名称正确!');
710 | }
711 |
712 | loadSupperCls(spName);
713 | return extend(newClass, epctrl[spName], overrides);
714 | },
715 | // 重写实现
716 | overwrite: function (destName, proto) {
717 | loadSupperCls(destName);
718 | return overwrite(epctrl[destName], proto);
719 | },
720 | // control 基类
721 | Control: Control,
722 | // 统一的初始化方法
723 | init: function (name, cfg) {
724 | var controlName = name.substr(0, 1).toUpperCase() + name.substr(1);
725 |
726 | if (!this[controlName] || typeof this[controlName] != 'function') {
727 | throw new Error('指定的控件:' + name + ' 未找到,请确认控件名称正确!');
728 | }
729 |
730 | var control = new this[controlName](cfg);
731 |
732 | control.loadSource();
733 |
734 | return control;
735 | }
736 | };
737 |
738 | })(this, jQuery);
739 |
--------------------------------------------------------------------------------
/component/calendar/calendar.css:
--------------------------------------------------------------------------------
1 | .ep-calendar {
2 | position: relative;
3 |
4 | -webkit-box-sizing: border-box;
5 | -moz-box-sizing: border-box;
6 | box-sizing: border-box;
7 | width: 100%;
8 | height: 100%;
9 | min-height: 260px;
10 |
11 | border: 1px solid #d2d7dd;
12 | }
13 |
14 |
15 | /* 头部 */
16 |
17 | .ep-calendar-header {
18 | position: relative;
19 |
20 | -webkit-box-sizing: border-box;
21 | -moz-box-sizing: border-box;
22 | box-sizing: border-box;
23 | width: 100%;
24 | height: 40px;
25 |
26 | text-align: center;
27 |
28 | border-bottom: 1px solid #d2d7dd;
29 | }
30 |
31 | .ep-calendar-header:after,
32 | .ep-calendar-header-center:after {
33 | display: table;
34 | clear: both;
35 |
36 | content: "";
37 | }
38 |
39 | .ep-calendar-header-left {
40 | float: left;
41 | }
42 |
43 | .ep-calendar-header-right {
44 | float: right;
45 | }
46 |
47 | .ep-calendar-header-center {
48 | line-height: 30px;
49 |
50 | display: inline-block;
51 |
52 | margin-top: 9px;
53 | }
54 |
55 | .ep-calendar-header-btn,
56 | .ep-calendar-title {
57 | float: left;
58 |
59 | cursor: pointer;
60 | }
61 |
62 | .ep-calendar-btn-prev,
63 | .ep-calendar-btn-next {
64 | width: 30px;
65 | height: 30px;
66 |
67 | cursor: pointer;
68 |
69 | background-repeat: no-repeat;
70 | background-position: center center;
71 | }
72 |
73 | .ep-calendar-btn-prev {
74 | background-image: url("./images/btn-prev.png");
75 | }
76 |
77 | .ep-calendar-btn-next {
78 | background-image: url("./images/btn-next.png");
79 | }
80 |
81 |
82 | /* 主体部分 */
83 |
84 | .ep-calendar-body {
85 | width: 100%;
86 |
87 | table-layout: fixed;
88 | }
89 |
90 |
91 | /* 星期 */
92 |
93 | .ep-calendar-daysheader {
94 | line-height: 30px;
95 |
96 | height: 30px;
97 | }
98 |
99 | .ep-calendar-day,
100 | .ep-calendar-date {
101 | width: 14.285%;
102 |
103 | text-align: center;
104 | }
105 |
106 |
107 | /* 日期 */
108 |
109 | .ep-calendar-dates {
110 | /* */
111 | }
112 |
113 | .ep-calendar-date-inner {
114 | position: relative;
115 |
116 | overflow: hidden;
117 |
118 | width: 100%;
119 | height: 100%;
120 | }
121 | .ep-calendar-week {
122 | line-height: 30px;
123 |
124 | height: 30px;
125 | }
126 |
127 | .ep-calendar-date {
128 | overflow: hidden;
129 |
130 | padding: 0 3px;
131 |
132 | text-align: center;
133 | vertical-align: top;
134 |
135 | color: #333;
136 | }
137 |
138 | .ep-calendar-date .ep-calendar-date-text {
139 | line-height: 20px;
140 |
141 | display: inline-block;
142 |
143 | width: 20px;
144 | height: 20px;
145 |
146 | border: 1px solid transparent;
147 | }
148 |
149 | .ep-calenday-weekend .ep-calendar-date-text {
150 | color: #e02d2d;
151 | }
152 |
153 | .ep-calendar-othermonth .ep-calendar-date-text {
154 | color: #999;
155 | }
156 |
157 | .ep-calendar-today .ep-calendar-date-text {
158 | color: #fff;
159 | border-color: #56b1ff;
160 | background: #56b1ff;
161 | }
162 |
163 | .ep-calendar-selected .ep-calendar-date-text {
164 | border-color: #56b1ff;
165 | -webkit-border-radius: 2px;
166 | -moz-border-radius: 2px;
167 | border-radius: 2px;
168 | }
169 |
170 | .ep-calendar-menu {
171 | position: absolute;
172 | top: 40px;
173 | left: 50%;
174 |
175 | display: none;
176 |
177 | width: 220px;
178 | height: 226px;
179 | margin-left: -110px;
180 |
181 | text-align: center;
182 |
183 | border: 1px solid #d5dae1;
184 | background: #fff;
185 | -webkit-box-shadow: 0 2px 3px rgba(0,0,0,.15);
186 | -moz-box-shadow: 0 2px 3px rgba(0,0,0,.15);
187 | box-shadow: 0 2px 3px rgba(0,0,0,.15);
188 | }
189 | .ep-calendar-menu.show {
190 | display: block;
191 | }
192 | .ep-calendar-menu-months {
193 | line-height: 24px;
194 |
195 | display: block;
196 |
197 | margin: 10px 14px;
198 | }
199 | .ep-calendar-menu-months:after,
200 | .ep-calendar-menu-years-inner:after {
201 | display: table;
202 | clear: both;
203 |
204 | content: "";
205 | }
206 | .ep-calendar-menu-month {
207 | font-family: Tahoma,Verdana,宋体;
208 | font-size: 9pt;
209 |
210 | float: left;
211 |
212 | width: 26px;
213 | height: 24px;
214 | margin: 0 10px 4px;
215 |
216 | cursor: pointer;
217 |
218 | border: 1px solid transparent;
219 | -webkit-border-radius: 2px;
220 | -moz-border-radius: 2px;
221 | border-radius: 2px;
222 | }
223 | .ep-calendar-menu-month.selected,
224 | .ep-calendar-menu-month:hover {
225 | color: #f96d41;
226 | border-color: #f9a68c;
227 | background: #fff7f4;
228 | }
229 | .ep-calendar-menu-years {
230 | position: relative;
231 |
232 | height: 56px;
233 | margin: 0;
234 | padding: 10px 15px;
235 |
236 | background: #f6f6f6;
237 | }
238 | .ep-calendar-prev-year,
239 | .ep-calendar-next-year {
240 | position: absolute;
241 | top: 50%;
242 |
243 | display: block;
244 |
245 | width: 15px;
246 | height: 15px;
247 | margin-top: -8px;
248 |
249 | cursor: pointer;
250 |
251 | background-repeat: no-repeat;
252 | background-position: center center;
253 | }
254 | .ep-calendar-prev-year {
255 | left: 0;
256 |
257 | background-image: url("./images/btn-prev.png");
258 | }
259 | .ep-calendar-next-year {
260 | right: 0;
261 |
262 | background-image: url("./images/btn-next.png");
263 | }
264 |
265 | .ep-calendar-menu-year {
266 | font-family: Tahoma,Verdana,宋体;
267 | font-size: 9pt;
268 | line-height: 20px;
269 |
270 | /* display: inline; */
271 |
272 | float: left;
273 |
274 | width: 32px;
275 | height: 21px;
276 | margin: 0 1px 4px;
277 | padding: 1px;
278 |
279 | cursor: pointer;
280 |
281 | border: 1px solid transparent;
282 | -webkit-border-radius: 2px;
283 | -moz-border-radius: 2px;
284 | border-radius: 2px;
285 | }
286 | .ep-calendar-menu-year.selected,
287 | .ep-calendar-menu-year:hover {
288 | color: #fff;
289 | border-color: #f96d41;
290 | background: #f96d41;
291 | }
292 | .ep-calendar-menu-footer {
293 | position: absolute;
294 | bottom: 0;
295 | left: 0;
296 |
297 | width: 100%;
298 | height: 24px;
299 | padding: 0;
300 | padding-top: 8px;
301 | padding-bottom: 8px;
302 |
303 | text-align: center;
304 |
305 | border-top: 1px solid #e6e7eb;
306 | background: #f6f6f6;
307 | }
308 | .ep-calendar-okbtn,
309 | .ep-calendar-cancelbtn {
310 | line-height: 14px;
311 |
312 | display: inline-block;
313 |
314 | width: 52px;
315 | margin: 0 3px;
316 | padding: 5px 0;
317 |
318 | cursor: pointer;
319 | -webkit-transition: all .6s cubic-bezier(.175,.885,.32,1);
320 | -moz-transition: all .6s cubic-bezier(.175,.885,.32,1);
321 | -o-transition: all .6s cubic-bezier(.175,.885,.32,1);
322 | transition: all .6s cubic-bezier(.175,.885,.32,1);
323 |
324 | color: #fff;
325 | border: 1px solid transparent;
326 | -webkit-border-radius: 2px;
327 | -moz-border-radius: 2px;
328 | border-radius: 2px;
329 | }
330 | .ep-calendar-okbtn {
331 | border-color: #f96d41;
332 | background-color: #f96d41;
333 | }
334 | .ep-calendar-okbtn:hover {
335 | border-color: #f97e41;
336 | background-color: #f97e41;
337 | }
338 | .ep-calendar-cancelbtn {
339 | border-color: #59c2e6;
340 | background-color: #59c2e6;
341 | }
342 | .ep-calendar-cancelbtn:hover {
343 | border-color: #51b6d9;
344 | background-color: #51b6d9;
345 | }
346 |
--------------------------------------------------------------------------------
/component/calendar/calendar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 月视图视图日历
3 | */
4 | /* global epctrl,moment */
5 |
6 | epctrl.Calendar = function (cfg) {
7 | epctrl.Calendar.superclass.constructor.apply(this, arguments);
8 |
9 | // 添加控件的css和js资源 如果是在父类的基础上新增css资源 则使用以下两个方法
10 | // this.addCss();
11 | // this.addJs();
12 | // 如果要给当前控件新的css、js资源,则通过this.setjs()和this.setCss()进行配置,参数为一个字符串或数组。
13 | this.setCss('calendar/calendar.css');
14 |
15 | // 当前无moment时 自动引入moment
16 | if (!window.moment || typeof window.moment != 'function') {
17 | this.setJs('../lib/moment.min.js');
18 | }
19 |
20 | // 加入传递的配置
21 | this.cfg = cfg || {};
22 | };
23 |
24 | epctrl.extend('Calendar', 'Control', {
25 | // 控件类型 必须指定,值即为控件的名称
26 | type: 'Calendar',
27 | // 选中日期的样式类名
28 | selectedCls: 'ep-calendar-selected',
29 |
30 | _defaultCfg: {
31 | // 不指定时 占满指定元素的宽高度
32 | width: '',
33 | height: '',
34 |
35 | // 初始化渲染的年月 格式如:'2017-9'
36 | date: '',
37 | // 是否显示日历外边框
38 | showBorder: true,
39 |
40 | // 设置的选中日期不在当前视图中时,是否自动重绘
41 | autoReRender: true,
42 | // 星期从周日开始?
43 | dayStartFromSunday: false,
44 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调
45 | headerHeight: '',
46 | // 日历底部高度,用不到可以不用管
47 | footerHeight: '',
48 | // 日历年月选择弹窗的宽高
49 | menuHeigt: 240,
50 | menuWidth: 220
51 | },
52 |
53 | // 初始化必要结构
54 | _initContainer: function () {
55 | this.$container.empty();
56 |
57 | // 整个日历
58 | var el = document.createElement('div');
59 | el.className = 'ep-calendar';
60 |
61 | this.cfg.width && (el.style.width = this.cfg.width);
62 | this.cfg.height && (el.style.height = this.cfg.height);
63 |
64 | if (!this.showBorder) {
65 | el.style.border = 'none';
66 | }
67 |
68 | this.el = el;
69 |
70 | // 头部
71 | var header = document.createElement('div');
72 | header.className = 'ep-calendar-header';
73 | header.innerHTML = '
';
74 |
75 | this.cfg.headerHeight && (header.style.height = this.cfg.headerHeight);
76 |
77 | this._header = header;
78 | this._headerLeft = header.firstChild;
79 | this._headerRight = header.lastChild;
80 | // this._headerCenter = jQuery('.ep-weekcalendar-header-center', header)[0];
81 | this._headerCenter = this._headerLeft.nextSibling;
82 | // this._prevBtn = jQuery('.ep-weekcalendar-header-btn-prev', this._headerCenter)[0];
83 | this._prevBtn = this._headerCenter.firstChild;
84 | this._titleEl = this._prevBtn.nextSibling;
85 |
86 | this._nextBtn = this._headerCenter.lastChild;
87 | this.el.appendChild(header);
88 |
89 | // 主体
90 | var body = document.createElement('table'),
91 | thead = document.createElement('thead'),
92 | tbody = document.createElement('tbody'),
93 | tr = document.createElement('tr');
94 |
95 | body.className = 'ep-calendar-body';
96 | tr.className = 'ep-calendar-daysheader';
97 | tbody.className = 'ep-calendar-days';
98 |
99 | thead.appendChild(tr);
100 | body.appendChild(thead);
101 | body.appendChild(tbody);
102 |
103 | // innerHTML IE8下无法操作
104 | // body.innerHTML = '';
105 |
106 | // this._daysHeader = jQuery('.ep-calendar-daysheader', body)[0];
107 | // this._daysBody = jQuery('.ep-calendar-days', body)[0];
108 |
109 | this._daysHeader = tr;
110 | this._daysBody = tbody;
111 |
112 | this.el.appendChild(body);
113 | this._body = body;
114 |
115 | // 底部
116 | var footer = document.createElement('div');
117 | footer.className = 'ep-calendar-footer';
118 |
119 | this.cfg.footerHeight && (header.style.height = this.cfg.footerHeight);
120 |
121 | this.el.appendChild(footer);
122 | this._footer = footer;
123 |
124 | // 年月选择弹出面板
125 | var menu = document.createElement('div');
126 | menu.className = 'ep-calendar-menu';
127 |
128 | this.cfg.menuHeigt && (menu.style.height = this.cfg.menuHeigt);
129 | this.cfg.menuWidth && (menu.style.menuWidth = this.cfg.menuWidth);
130 | this.menu = menu;
131 | this.el.appendChild(menu);
132 |
133 | this.container.appendChild(this.el);
134 |
135 | this._calcAndSetHeight();
136 | },
137 | // 计算并设置日历主体部分高度
138 | _calcAndSetHeight: function () {
139 | var head_h = jQuery(this._header).outerHeight(),
140 | footer_h = jQuery(this._footer).outerHeight(),
141 | body_h = jQuery(this.el).height() - head_h - footer_h;
142 |
143 | this._body.style.height = body_h + 'px';
144 | },
145 |
146 | _weekHeaderDays: {
147 | Sun: ['日', '一', '二', '三', '四', '五', '六'],
148 | Sun_full: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
149 | Sun_en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'],
150 | Sun_en_full: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thurday', 'Friday', 'Saturday '],
151 |
152 | Mon: ['一', '二', '三', '四', '五', '六', '日'],
153 | Mon_full: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
154 | Mon_en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'],
155 | Mon_en_full: ['Monday', 'Tuesday', 'Wednesday', 'Thurday', 'Friday', 'Saturday', 'Sunday']
156 | },
157 |
158 | // 渲染头部的星期
159 | _renderWeek: function () {
160 | var weekDays = this.dayStartFromSunday ? this._weekHeaderDays.Sun : this._weekHeaderDays.Mon;
161 |
162 | for (var i = 0; i < 7; ++i) {
163 |
164 | var cell = document.createElement('th'),
165 | cellInner = document.createElement('div'),
166 | text = document.createElement('span'),
167 | isoWeekday = this.dayStartFromSunday ? (i === 0 ? 7 : i) : i + 1;
168 |
169 | cell.className = 'ep-calendar-day';
170 | cellInner.className = 'ep-calendar-day-inner';
171 | // iso星期 周一至周日为1-7
172 | cell.setAttribute('data-isoweekday', isoWeekday);
173 | cellInner.appendChild(text);
174 | cell.appendChild(cellInner);
175 |
176 | // 单元格渲染事件 还未插入页面
177 | var renderEv = this.fire('cellRender', {
178 | // isoweekday 周一至周日为1-7
179 | isoWeekday: isoWeekday,
180 | // 星期th的class
181 | dayCls: 'ep-calendar-day-text',
182 | // 星期的内容
183 | dayText: weekDays[i],
184 | // 日历dom
185 | el: this.el,
186 | // 当前单元格dom
187 | tdEl: cell,
188 | // 额外注入的html
189 | extraHtml: '',
190 | // 是否表格头 即是否为头部星期几
191 | isHeader: true
192 | });
193 |
194 | // 对修改的处理
195 | text.className = renderEv.dayCls;
196 | text.innerText = renderEv.dayText;
197 |
198 | // 添加新增内容
199 | if (renderEv.extraHtml) {
200 | jQuery(renderEv.extraHtml).appendTo(cellInner);
201 | }
202 |
203 | this._daysHeader.appendChild(cell);
204 |
205 | // 单元格渲染事件
206 | this.fire('afterCellRender', {
207 | isoWeekday: isoWeekday,
208 | dayCls: text.className,
209 | dayText: text.innerText,
210 | el: this.el,
211 | isHeader: true,
212 | extraHtml: renderEv.extraHtml,
213 | tdEl: cell
214 | });
215 | }
216 | },
217 |
218 | _renderTitle: function () {
219 | this._titleEl.innerHTML = moment(this.currMonth, 'YYYY-MM').format('YYYY年MM月');
220 | this.fire('titleRender', {
221 | titleEl: this._titleEl,
222 | currMonth: this.currMonth
223 | });
224 | },
225 |
226 | // 清空已渲染的日历
227 | _clearDays: function () {
228 | var trs = this._daysBody.childNodes;
229 |
230 | for (var i = trs.length; i > 0; --i) {
231 | this._daysBody.removeChild(trs[i - 1]);
232 | }
233 | },
234 | // 每天的渲染
235 | _renderDay: function (date, currTr) {
236 | var td = document.createElement('td'),
237 | tdInner = document.createElement('div'),
238 | text = document.createElement('span'),
239 | day = date.isoWeekday(),
240 | // 返回的月份是0-11
241 | month = date.month() + 1;
242 |
243 | tdInner.appendChild(text);
244 | td.appendChild(tdInner);
245 |
246 | td.className = 'ep-calendar-date';
247 | tdInner.className = 'ep-calendar-date-inner';
248 | // 完整日期
249 | td.setAttribute('data-date', date.format('YYYY-MM-DD'));
250 | // 对应的iso星期
251 | td.setAttribute('data-isoweekday', day);
252 | // text.className = 'ep-calendar-date-text';
253 | // text.innerText = date.date();
254 |
255 | // 周末标记text.className
256 | if (day === 6 || day === 7) {
257 | td.className += ' ep-calenday-weekend';
258 | }
259 | // 非本月标记
260 | // substr 在ie8下有问题
261 | // if (month != parseInt(this.currMonth.substr(-2))) {
262 | if (month != parseInt(this.currMonth.substr(5), 10)) {
263 | td.className += ' ep-calendar-othermonth';
264 | }
265 | // 今天标记
266 | if (this.today == date.format('YYYY-MM-DD')) {
267 | td.className += ' ep-calendar-today';
268 | }
269 |
270 | // 每天渲染时发生 还未插入页面
271 | var renderEvent = this.fire('cellRender', {
272 | // 当天的完整日期
273 | date: date.format('YYYY-MM-DD'),
274 | // 当天的iso星期
275 | isoWeekday: day,
276 | // 日历dom
277 | el: this.el,
278 | // 当前单元格
279 | tdEl: td,
280 | // 日期文本
281 | dateText: date.date(),
282 | // 日期class
283 | dateCls: 'ep-calendar-date-text',
284 | // 需要注入的额外的html
285 | extraHtml: '',
286 |
287 | isHeader: false
288 | });
289 |
290 | // 处理对dayText内容和样式的更改
291 | text.innerText = renderEvent.dateText;
292 | text.className = renderEvent.dateCls;
293 |
294 | // 添加新增内容
295 | if (renderEvent.extraHtml) {
296 | jQuery(renderEvent.extraHtml).appendTo(tdInner);
297 | }
298 |
299 | currTr.appendChild(renderEvent.tdEl);
300 |
301 | // 每天渲染后发生 插入到页面
302 | this.fire('afterCellRender', {
303 | date: date.format('YYYY-MM-DD'),
304 | isoWeekday: day,
305 | el: this.el,
306 | tdEl: td,
307 | dateText: text.innerText,
308 | dateCls: text.className,
309 | extraHtml: renderEvent.extraHtml,
310 | isHeader: false
311 | });
312 | },
313 |
314 | // 日历可变部分的渲染
315 | _render: function () {
316 | this._initStartEnd();
317 |
318 | var weeks = 6,
319 | days = 7,
320 | curDate = this.startDateOfMonth.clone(),
321 | tr;
322 |
323 | var dtd = jQuery.Deferred(),
324 | start = this.startDateOfMonth.format('YYYY-MM-DD'),
325 | end = this.endDateOfMonth.format('YYYY-MM-DD');
326 |
327 | // 日期渲染前触发渲染前事件
328 | // 如果需要动态获取数据则将获取数据的ajax加入到事件对象的ajax属性中
329 | var ev = this.fire('beforeDateRender', {
330 | momth: this.currMonth,
331 | startDate: start,
332 | endDate: end,
333 | ajax: null
334 | });
335 |
336 | // 没有用户加入的ajax 则直接模拟一个成功的ajax
337 | if (!ev.ajax) {
338 | ev.ajax = dtd.promise();
339 | dtd.resolve();
340 | }
341 |
342 | var me = this;
343 |
344 | ev.ajax.then(function () {
345 | // 拿到数据后再清空 并开始新的渲染
346 | me._clearDays();
347 | me._renderTitle();
348 |
349 | for (var i = 0; i < weeks; ++i) {
350 | tr = document.createElement('tr');
351 | tr.className = 'ep-calendar-week';
352 | me._daysBody.appendChild(tr);
353 |
354 | for (var j = 0; j < days; ++j) {
355 | // 渲染一天 并递增
356 | me._renderDay(curDate, tr);
357 | curDate.add(1, 'day');
358 | }
359 | }
360 |
361 | // 全部日期渲染完成
362 | me.fire('afterDateRender', {
363 | momth: me.currMonth,
364 | startDate: start,
365 | endDate: end,
366 | ajax: ev.ajax
367 | });
368 | });
369 | },
370 |
371 | render: function (ym) {
372 | // 如果指定了渲染月份 进行处理
373 | if (ym) {
374 | var date = moment(ym, 'YYYY-MM');
375 | if (date.isValid()) {
376 | this.currMonth = date.format('YYYY-MM');
377 | } else {
378 | this.throwError(ym + '指定的日期不正确!');
379 | }
380 | }
381 |
382 | // 未初始化时先初始化不变的结构
383 | if (!this._isInit) {
384 | this._initContainer();
385 | this._renderWeek();
386 | this._isInit = true;
387 | }
388 |
389 | this._render();
390 |
391 | },
392 |
393 | // 强制重新渲染
394 | reRedner: function () {
395 | if (!this._isInit) {
396 | this.throwError('日历还未渲染过,无需重新渲染!');
397 | }
398 |
399 | this._initContainer();
400 | this._renderWeek();
401 | this.render();
402 | },
403 |
404 | // 初始化当前月份的开始日期和结束日期
405 | _initStartEnd: function () {
406 | // 当月1号
407 | var currMonth = moment(this.currMonth, 'YYYY-MM'),
408 | // 当月1号是周几 the ISO day of the week with 1 being Monday and 7 being Sunday.
409 | firstDay_weekday = currMonth.isoWeekday(),
410 | startDateOfMonth,
411 | endDateOfMonth;
412 | if (!this.dayStartFromSunday) {
413 | // 开始为周一 则向前减少周几的天数-1即为 开始的日期
414 | startDateOfMonth = currMonth.subtract(firstDay_weekday - 1, 'day');
415 | } else {
416 | // 开始为周日 则直接向前周几的天数即可
417 | startDateOfMonth = currMonth.subtract(firstDay_weekday, 'day');
418 | }
419 |
420 | endDateOfMonth = startDateOfMonth.clone().add(41, 'day');
421 |
422 | this.startDateOfMonth = startDateOfMonth;
423 | this.endDateOfMonth = endDateOfMonth;
424 | },
425 |
426 | // 设置月份
427 | setMonth: function (ym) {
428 | var date = moment(ym, 'YYYY-MM');
429 |
430 | if (date.isValid()) {
431 | var oldMonth = this.currMonth,
432 | aimMonth = date.format('YYYY-MM');
433 |
434 | // 月份变动前
435 | this.fire('beforeMonthChange', {
436 | el: this.el,
437 | oldMonth: oldMonth,
438 | newMonth: aimMonth
439 | });
440 |
441 | this.currMonth = aimMonth;
442 | this.render();
443 |
444 | // 月份变动后
445 | this.fire('afterMonthChange', {
446 | el: this.el,
447 | oldMonth: oldMonth,
448 | newMonth: aimMonth
449 | });
450 |
451 | } else {
452 | throw new Error(ym + '是一个不合法的日期');
453 | }
454 | },
455 | getMonth: function () {
456 | return this.currMonth;
457 | },
458 | // 上一月
459 | prevMonth: function () {
460 | var prevDate = moment(this.currMonth, 'YYYY-MM').subtract(1, 'M');
461 | this.setMonth(prevDate);
462 | },
463 | // 下一月
464 | nextMonth: function () {
465 | var nextDate = moment(this.currMonth, 'YYYY-MM').add(1, 'M');
466 | this.setMonth(nextDate);
467 | },
468 |
469 | // 设置选中的日期
470 | setSelected: function (ymd) {
471 | // 是否只输入了一个仅包含日的日期,此时将仅在当前月中查找
472 | var isCurrMonthDate = /^\d{1,2}$/.test(ymd),
473 | // 所有日期
474 | $days = jQuery(this._daysBody).find('.ep-calendar-date'),
475 | // 目标日期
476 | $day,
477 | date;
478 |
479 | if (isCurrMonthDate) {
480 | date = moment(this.currMonth + '-' + ymd, 'YYYY-MM-DD');
481 |
482 | if (!date.isValid()) {
483 | this.throwError('日期不合法');
484 | }
485 |
486 | // $day = $days.filter('[data-date="' + date.format('YYYY-MM-DD') + '"]');
487 | $day = this._getTd(date, $days);
488 |
489 | if ($day.length) {
490 | $days.removeClass(this.selectedCls);
491 | $day.addClass(this.selectedCls);
492 | } else {
493 | this.throwError('当前月中没有' + ymd + '这一天!');
494 | }
495 |
496 | return;
497 | }
498 |
499 | // 否则则应该是一个完整的日期
500 | date = moment(ymd, 'YYYY-MM-DD');
501 |
502 | if (date.isValid()) {
503 |
504 | // $day = $days.filter('[data-date="' + dateStr + '"]');
505 | $day = this._getTd(date, $days);
506 |
507 | if ($day.length) {
508 | // 有则直接显示
509 | $days.removeClass(this.selectedCls);
510 | $day.addClass(this.selectedCls);
511 | } else if (this.autoReRender) {
512 | // 不在 但是支持自动重绘 则渲染并设置
513 | this.render(date.format('YYYY-MM'));
514 | this.setSelected(ymd);
515 | } else {
516 | // 日期不存在 且 不自动重绘时 抛出错误
517 | this.throwError('指定日期不在当前视图,且未开启自动切换,无法设置!');
518 | }
519 |
520 | } else {
521 | this.throwError(ymd + ',此日期不合法!');
522 | }
523 | },
524 | getSelected: function () {
525 | var selectedTd = jQuery('.' + this.selectedCls, this._daysBody)[0];
526 |
527 | return selectedTd ? moment(selectedTd.getAttribute('data-date'), 'YYYY-MM-DD').format('YYYY-MM-DD') : '';
528 | },
529 | // 获取当前视图中的开始日期和结束日期
530 | getStartEnd: function () {
531 | return {
532 | start: this.startDateOfMonth.format('YYYY-MM-DD'),
533 | end: this.endDateOfMonth.format('YYYY-MM-DD')
534 | };
535 | },
536 | // 根据日期获取td
537 | _getTd: function (date, $days) {
538 |
539 | $days = $days || jQuery(this._daysBody).find('.ep-calendar-date');
540 |
541 | return $days.filter('[data-date="' + date.format('YYYY-MM-DD') + '"]');
542 | },
543 |
544 | getELByDate: function (md) {
545 |
546 | var date;
547 |
548 | if (/^\d{1,2}$/.test(md)) {
549 | // 只提供一个日期 不包含月份,则在当前月中查找
550 | date = moment(this.currMonth + '-' + md, 'YYYY-MM-DD');
551 | } else if (/^\d{1,2}-\d{1,2}$/.test(md)) {
552 | // 月-日 的形式
553 | date = moment((new Date()).getFullYear() + '-' + md, 'YYYY-MM-DD');
554 | } else {
555 | // 否则直接认为是一个 完整日期
556 | date = moment(md, 'YYYY-MM-DD');
557 | }
558 |
559 | // if (!date.isValid()) {
560 | // this.throwError(md + ',此日期不合法!');
561 | // }
562 |
563 | var $days = this._getTd(date);
564 |
565 | return $days.length ? $days[0] : null;
566 |
567 | },
568 |
569 | // 回到今天的视图
570 | goToday: function () {
571 | this.today = moment().format('YYYY-MM-DD');
572 | return this.setMonth(this.today);
573 | },
574 |
575 | // 回到初始化的视图
576 | reset: function () {
577 | return this.setMonth(this.cfg.date || new Date());
578 | },
579 |
580 | // 初始化事件
581 | _initEvent: function () {
582 | var my = this;
583 | jQuery(this.el)
584 | // 日期单元格
585 | .on('click', '.ep-calendar-date', function (e) {
586 | var date = this.getAttribute('data-date'),
587 | ev = my.fire('dayClick', {
588 | ev: e,
589 | date: date,
590 | day: this.getAttribute('data-isoweekday'),
591 | el: my.el,
592 | tdEl: this
593 | });
594 |
595 | // 如果修改事件对象的cancel为true后 则不进行后续的选中操作
596 | if (!ev.cancel) {
597 | my.setSelected(date);
598 | }
599 |
600 | })
601 | // 头部星期
602 | .on('click', '.ep-calendar-day', function (e) {
603 | my.fire('dayHeaderClick', {
604 | ev: e,
605 | day: this.getAttribute('data-isoweekday'),
606 | el: my.el,
607 | tdEl: this
608 | });
609 | })
610 | // 上一月
611 | .on('click', '.ep-calendar-btn-prev', function (e) {
612 | var ev = my.fire('prevBtnClick', {
613 | ev: e,
614 | currMonth: my.currMonth,
615 | el: my.el
616 | });
617 |
618 | if (!ev.cancel) {
619 | my.prevMonth();
620 | }
621 | })
622 | // 下一月
623 | .on('click', '.ep-calendar-btn-next', function (e) {
624 | var ev = my.fire('nextBtnClick', {
625 | ev: e,
626 | currMonth: my.currMonth,
627 | el: my.el
628 | });
629 |
630 | if (!ev.cancel) {
631 | my.nextMonth();
632 | }
633 | })
634 | // title 点击
635 | .on('click', '.ep-calendar-title', function (e) {
636 | // 已经显示则隐藏
637 | if (/ show/.test(my.menu.className)) {
638 | my.hideMenu();
639 | return;
640 | }
641 |
642 | var ev = my.fire('beforeTitleClick', {
643 | ev: e,
644 | currMonth: my.currMonth,
645 | el: my.el
646 | });
647 |
648 | if (!ev.cancel) {
649 | // showMonth 格式为"2017-10" 不指定时 选择面板渲染出当前年月 如果有指定 则渲染出的面板显示到能显示指定的年月
650 | my._onMenuShow(ev.showMonth);
651 | }
652 | })
653 | // 年份、月份选择
654 | .on('click', '.ep-calendar-menu-month,.ep-calendar-menu-year', function () {
655 |
656 | jQuery(this).addClass('selected')
657 | .siblings('.selected').removeClass('selected');
658 | })
659 | // 确认
660 | .on('click', '.ep-calendar-okbtn', function () {
661 | var year = jQuery(my.menu_year).find('.ep-calendar-menu-year.selected').data('year'),
662 | month = jQuery(my.menu_month).find('.ep-calendar-menu-month.selected').data('month');
663 |
664 | if (!year || !month) {
665 | my.hideMenu();
666 | return;
667 | }
668 |
669 | my.setMonth(year + '-' + month);
670 | my.hideMenu();
671 | })
672 | // 取消
673 | .on('click', '.ep-calendar-cancelbtn', function () {
674 | my.hideMenu();
675 | })
676 | // 年份选择翻页
677 | .on('click', '.ep-calendar-next-year,.ep-calendar-prev-year', function () {
678 | var $this = jQuery(this),
679 | firstYear = parseInt(my.menu_year.firstChild.getAttribute('data-year'), 10),
680 | aimStartYear;
681 |
682 | if ($this.hasClass('ep-calendar-prev-year')) {
683 | aimStartYear = firstYear - 10;
684 | } else if ($this.hasClass('ep-calendar-next-year')) {
685 | aimStartYear = firstYear + 10;
686 | }
687 |
688 | my._renderYear(aimStartYear);
689 | });
690 | // 空白处点击收起年月选择
691 | jQuery(document.body).on('click', function (e) {
692 | var $target = jQuery(e.target);
693 |
694 | if (!$target.closest('.ep-calendar-menu').length && !$target.closest('.ep-calendar-title').length) {
695 | my.hideMenu();
696 | }
697 | });
698 | },
699 | hideMenu: function () {
700 | this.menu.className = this.menu.className.replace(' show', '');
701 | },
702 |
703 | showMenu: function (ym) {
704 | this._onMenuShow(ym);
705 | },
706 |
707 | // 年月面板显示时
708 | _onMenuShow: function (ym) {
709 | ym = ym || this.currMonth;
710 |
711 | this.menu.className += ' show';
712 |
713 | var temp = ym.split('-'),
714 | y = parseInt(temp[0], 10),
715 | m = parseInt(temp[1], 10);
716 |
717 | if (!this.menuIsInit) {
718 | this._initMenu(y, m);
719 | this.menuIsInit = true;
720 | } else {
721 | // 重新渲染年份 并重置月份的正确选中状态
722 | this._renderYear(y);
723 | jQuery(this.menu_month).find('[data-month="' + m + '"]').addClass('selected')
724 | .siblings().removeClass('selected');
725 | }
726 | },
727 | // 初始化年月选择
728 | _initMenu: function (y, m) {
729 | var month = document.createElement('div'),
730 | year = document.createElement('div'),
731 | yearInner = document.createElement('div'),
732 | footer = document.createElement('div'),
733 | okBtn = document.createElement('span'),
734 | cancelBtn = document.createElement('span'),
735 | prevYear = document.createElement('span'),
736 | nextYear = document.createElement('span');
737 |
738 | month.className = 'ep-calendar-menu-months';
739 | year.className = 'ep-calendar-menu-years';
740 | yearInner.className = 'ep-calendar-menu-years-inner';
741 | footer.className = 'ep-calendar-menu-footer';
742 | okBtn.className = 'ep-calendar-okbtn';
743 | okBtn.innerText = '确定';
744 | cancelBtn.innerText = '取消';
745 | cancelBtn.className = 'ep-calendar-cancelbtn';
746 | prevYear.className = 'ep-calendar-prev-year';
747 | nextYear.className = 'ep-calendar-next-year';
748 |
749 | footer.appendChild(okBtn);
750 | footer.appendChild(cancelBtn);
751 | year.appendChild(yearInner);
752 | year.appendChild(prevYear);
753 | year.appendChild(nextYear);
754 |
755 | this.menu_month = month;
756 | this.menu_year = yearInner;
757 |
758 | this.menu.appendChild(month);
759 | this.menu.appendChild(year);
760 | this.menu.appendChild(footer);
761 |
762 | this._renderMonth(m);
763 | this._renderYear(y, y);
764 | },
765 |
766 | // 渲染月份选择
767 | _renderMonth: function (currMonth) {
768 |
769 | var i = 1,
770 | months = this.menu_month,
771 | month = document.createElement('span'),
772 | node;
773 |
774 | month.className = 'ep-calendar-menu-month';
775 |
776 | while (i < 13) {
777 | node = month.cloneNode(true);
778 | if (i == currMonth) {
779 | node.className += ' selected';
780 | }
781 | node.innerText = i + '月';
782 | node.setAttribute('data-month', i);
783 |
784 | months.appendChild(node);
785 | ++i;
786 | }
787 |
788 | },
789 |
790 | // 渲染年份选择
791 | _renderYear: function (start, curr) {
792 | curr = curr || parseInt(this.currMonth.split('-')[0], 10);
793 | var i = 0,
794 | years = this.menu_year,
795 | year = document.createElement('span'),
796 | thisYear,
797 | node;
798 | years.innerHTML = '';
799 |
800 | year.className += 'ep-calendar-menu-year';
801 |
802 | while (i < 10) {
803 | node = year.cloneNode();
804 | thisYear = start + i;
805 |
806 | if (thisYear === curr) {
807 | node.className += ' selected';
808 | }
809 | node.innerText = thisYear;
810 | node.setAttribute('data-year', thisYear);
811 | years.appendChild(node);
812 |
813 | ++i;
814 | }
815 | },
816 |
817 | // 初始化必要的配置
818 | initCfg: function () {
819 | // 将一些常用配置读出
820 | if (!this.cfg || !this.cfg.el) {
821 | throw new Error('必须指定日历渲染的html元素');
822 | }
823 |
824 | // 合并默认配置
825 | this.cfg = jQuery.extend({}, this._defaultCfg, this.cfg);
826 |
827 | // 日历渲染容器
828 | this.$container = jQuery(this.cfg.el);
829 | this.container = this.$container[0];
830 |
831 | // 当前显示的月份 优先取当前的指定的日期,没有则取今天
832 |
833 | this.currMonth = this.cfg.date ? moment(this.cfg.date, 'YYYY-MM').format('YYYY-MM') : moment().format('YYYY-MM');
834 |
835 |
836 | // 今天
837 | this.today = moment().format('YYYY-MM-DD');
838 |
839 | // 星期从周日开始?
840 | this.dayStartFromSunday = this.cfg.dayStartFromSunday;
841 |
842 | // 设置的选中日期不在当前视图中时,是否自动重绘
843 | this.autoReRender = this.cfg.autoReRender;
844 |
845 | // 是否显示日历外边框
846 | this.showBorder = this.cfg.showBorder;
847 | },
848 |
849 | // 此方法在js等资源加载完成后自动调用
850 | create: function () {
851 |
852 | // 触发创建前事件
853 | this.fire('beforeCreate');
854 |
855 | this.initCfg();
856 | this.render();
857 |
858 | this._initEvent();
859 |
860 | this.fire('afterCreate');
861 | }
862 | });
863 |
--------------------------------------------------------------------------------
/component/calendar/demo/calendar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 月视图日历
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/component/calendar/images/btn-next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/btn-next.png
--------------------------------------------------------------------------------
/component/calendar/images/btn-prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/btn-prev.png
--------------------------------------------------------------------------------
/component/calendar/images/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/images/info.png
--------------------------------------------------------------------------------
/component/calendar/readme.md:
--------------------------------------------------------------------------------
1 | # 日历控件
2 |
3 | 此日历控件是基于框架组件机制,自行实现的一个月视图日历,主要功能在于支持日历中每天的单元格中插入任意内容,支持异步获取内容。
4 |
5 | - [在线演示](https://cdswyda.github.io/component-frame/component/calendar/demo/calendar.html)
6 | - [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/calendar)
7 |
8 | ## 使用说明
9 |
10 | **资源引入**
11 |
12 | - 引入框架控件机制的基类js文件。
13 |
14 | - 引入此日历控件的js文件。
15 |
16 | **初始化**
17 |
18 | 使用框架控件机制的统一初始化方法 `epctrl.init(controlName, cfg)` 即可,第一个参数为控件名称,此处即 `Calendar`,第二个参数为控件的配置。
19 |
20 | 使用如下所示:
21 |
22 | ```js
23 | var calendar = epctrl.init('Calendar', {
24 | el: '#calendar',
25 | date: '2017-09'
26 | });
27 | ```
28 |
29 | 详细配置如下:
30 |
31 | ```js
32 | {
33 | // 不指定时 占满指定元素的宽高度
34 | width: '',
35 | height: '',
36 |
37 | // 初始化渲染的年月 格式如:'2017-9'
38 | date: '',
39 | // 是否显示日历外边框
40 | showBorder: true,
41 |
42 | // 设置的选中日期不在当前视图中时,是否自动重绘为可展示目标日期的月份
43 | autoReRender: true,
44 | // 星期从周日开始?
45 | dayStartFromSunday: false,
46 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调
47 | headerHeight: '',
48 | // 日历底部高度,用不到可以不用管
49 | footerHeight: '',
50 | // 日历年月选择弹窗的宽高
51 | menuHeigt: 240,
52 | menuWidth: 220
53 | }
54 | ```
55 |
56 | ## 方法说明
57 |
58 | 方法名| 作用 | 参数 | 返回 | 说明
59 | -- | -- | -- | -- | --
60 | `setMonth(ym)` | 设置日历显示的月份 | 要显示的年月,值为一个字符串,格式 `YYYY-MM` | 设置日历中显示的月份,此过程中触发 `beforeMonthChange` 和 `beforeMonthChange` 事件。
输入一个不合法的日期将抛出错误。
61 | `getMonth()` | 获取当前显示的月份 | | 当前正在显示的月份,格式 `YYYY-MM` |
62 | `prevMonth()` | 上一月 | | |
63 | `nextMonth()` | 下一月 | | |
64 | `setSelected(ymd)` | 设置选中的日期 | 参数ymd为一个字符串,表示完整的日期,格式 `YYYY-MM-DD`
或直接指定当月日期(如当前视图为2017-10,设置选中1号,可直接传入1即可),则直接在当月日期中选择。 | | 传入日期不合法时将抛出错误。指定日期在当前显示的视图中不存在,且 `autoReRender` 为false,会抛出错误,无法选中。
65 | `getSelected` | 获取当前选中的日期 | | 当前选中的日期,格式 `YYYY-MM`
无选中日期时,返回 `""` |
66 | `getStartEnd` | 返回当前视图中的开始日期和结束日期 | | `{start: '2017-09-25', end: '2017-11-5'}` |
67 | `getELByDate(md)` | 根据日期获取对应的dom元素 | 日期字符串,格式 `[[YYYY-]MM-]DD` | `HTMLElement` or `null` | 参数可以直接指定当月日期,格式 `DD`。或 `MM-DD` 和 `YYYY-MM-DD` 的形式
68 | `goToday()` | 回到能显示今天的视图 | | |
69 | `reset()` | 回到初始化时的视图 | | |
70 |
71 | ## 事件说明
72 |
73 | ### beforeCreate
74 |
75 | 日历控件开始创建前触发
76 |
77 | ### afterCreate
78 |
79 | 日历控件创建完成后触发
80 |
81 | ### beforeDateRender
82 |
83 | 日历中可变部分(即日期部分)渲染前触发
84 |
85 | 事件参数的基本属性见控件机制中的描述,此事件参数为:
86 |
87 | ```js
88 | {
89 | // 要渲染的月份
90 | momth: String,
91 | // 此月渲染时的开始日期,格式YYYY-MM-DD
92 | startDate: String,
93 | // 此月份日期渲染的结束日期
94 | endDate: String,
95 | // ajax对象
96 | ajax: null
97 | }
98 | ```
99 |
100 | 如渲染2017-11月份的日期,则 `month` 为 `'2017-11'`, `startDate` : `'2017-10-30'` , `endDate` : `'2017-12-10'` 。 其中开始日期和结束日期是和日历的周一开始和周日开始有关的,上述为周一开始。
101 |
102 | 如果需要动态获取数据,以便在日期渲染时使用,可将获取数据的ajax对象加到事件参数的ajax属性上。如果用户加入了ajax,则之后的日期渲染会等待此ajax请求完成后才会触发。
103 |
104 | 如:
105 |
106 | ```js
107 | // 日期部分渲染前
108 | testCalendar.on('beforeDateRender', function (e) {
109 | var startDate = e.startDate,
110 | endDate = e.endDate;
111 | // 如果需要动态获取数据
112 | // 则将获取数据的ajax加到事件对象的ajax属性上即可
113 | // 日期渲染的cellRender事件将在ajax成功获取数据后执行
114 | e.ajax = $.ajax({
115 | url: 'getDateInfo.xxx',
116 | data: {
117 | start: startDate,
118 | end: endDate
119 | }
120 | });
121 | });
122 | ```
123 |
124 | ### afterDateRender
125 |
126 | 日历中可变部分(即日期部分)渲染前触发
127 |
128 | 事件参数同 `beforeDateRender`。
129 |
130 | 如果用户指定了获取数据的ajax,则时间参数中的ajax即用获取数据的ajax,否则ajax属性会替换成一个jQuery中Deferred对象模拟的Promise。
131 |
132 | ### cellRender
133 |
134 | 日历中星期头和日期单元格渲染时触发,基本结构已经生成,还未插入页面。
135 |
136 | 事件参数的基本属性见控件机制中的描述,此事件参数为:
137 |
138 | ```js
139 | {
140 | // isoweekday 周一至周日为1-7
141 | isoWeekday: Number,
142 | // 星期th的class 日期渲染时时无此属性
143 | dayCls: String,
144 | // 日期td 的class 星期渲染时无此属性
145 | dateCls: String,
146 | // 星期的内容 日期渲染时时无此属性
147 | dayText: String,
148 | // 当天的完整日期,YYYY-MM-DD 星期渲染时无此属性
149 | date: String,
150 | // 日期文本 星期渲染时无此属性
151 | dateText: String,
152 | // 日历dom
153 | el: HTMLElement,
154 | // 当前单元格dom
155 | tdEl: HTMLElement,
156 | // 额外注入的html 此部分html将会被自动插入到内容中
157 | extraHtml: String,
158 | // 是否表格头 即是否为头部星期几
159 | isHeader: Boolean
160 | }
161 | ```
162 |
163 | ### afterCellRender
164 |
165 | 一个单元格渲染完成后触发。基本结构以及cellRender中自定义的内容已经插入页面。
166 |
167 | 参数同cellRender。
168 |
169 | ### titleRender
170 |
171 | 日历头部的“YYYY年MM月”改变时触发,还未插入页面
172 |
173 | ```js
174 | {
175 | // title dom对象
176 | titleEl: HTMLElement,
177 | // 当前月份 格式 YYYY-MM
178 | currMonth: String,
179 | // 实际显示的内容,支持HTML片段,修改此值即可自定义title
180 | titleContent: String
181 | }
182 | ```
183 |
184 | ### afterTitleRender
185 |
186 | 日历头部的“YYYY年MM月”改变完成时触发,已经修改到页面。
187 |
188 | ### beforeMonthChange
189 |
190 | 日历显示月份变化前触发
191 |
192 | ```js
193 | {
194 | // 日历dom
195 | el: HTMLElement,
196 | // 之前的月份 YYYY-MM
197 | oldMonth: String,
198 | // 新的月份
199 | newMonth: String
200 | }
201 | ```
202 |
203 | ### afterMonthChange
204 |
205 | 日历显示月份变化后触发,参数同 `beforeMonthChange`。
206 |
207 | ### dayClick
208 |
209 | 日期单元格点击时触发
210 |
211 | ```js
212 | {
213 | // jq封装的事件参数
214 | ev: Object,
215 | // 当前日期
216 | date: String,
217 | // iso星期
218 | day: String,
219 | // 日历dom
220 | el: HTMLElement,
221 | // 当前单元格dom
222 | tdEl: HTMLElement
223 | }
224 | ```
225 |
226 | 一个日期的点击,默认导致此日期选中。在事件中设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。
227 |
228 | ### dayHeaderClick
229 |
230 | 日历中头部星期单元格点击时触发
231 |
232 | ```js
233 | {
234 | // jq封装的事件参数
235 | ev: Object,
236 | // 此星期的iso星期 1-7 为周一至周日
237 | day: String,
238 | // 日历dom
239 | el: HTMLElement,
240 | // 当前单元格dom
241 | tdEl: HTMLElement
242 | }
243 | ```
244 |
245 | ### prevBtnClick 和 nextBtnClick
246 |
247 | 前一月和后一月点击事件
248 |
249 | ```js
250 | {
251 | // jq封装的事件参数
252 | ev: Object,
253 | // 当前显示的月份
254 | currMonth: String,
255 | // 日历dom
256 | el: HTMLElement
257 | }
258 | ```
259 |
260 | 默认操作为视图重新渲染为前一月或后一月,设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。
261 |
262 | ### titleClick
263 |
264 | 日历title(YYYY年MM月)点击时触发
265 |
266 | ```js
267 | {
268 | // jq封装的事件参数
269 | ev: Object,
270 | // 当前显示的月份
271 | currMonth: String,
272 | // 日历dom
273 | el: HTMLElement,
274 | // 年月选择面板中展示出的年月 YYYY-MM
275 | // 默认为空,展示后显示出当前年月
276 | showMonth: String
277 | }
278 | ```
279 |
280 | 此事件后续默认操作为展示年月选择面板,设置事件参数的 `cancel` 属性为 `true` 可以阻止此操作。
281 |
--------------------------------------------------------------------------------
/component/calendar/themes/grace/skins/default/skin.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/calendar/themes/grace/skins/default/skin.css
--------------------------------------------------------------------------------
/component/weekcalendar/demo/test.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | Mock.setup({
3 | timeout: '50-600'
4 | });
5 |
6 | function getParams(query) {
7 | var o = {};
8 | $.each(query.split('&'), function (i, item) {
9 | var r = item.split('=');
10 |
11 | o[r[0]] = r[1];
12 | });
13 | return o;
14 | }
15 |
16 | var timeRanges = [
17 | ['07:00', '16:00'],
18 | ['08:00', '17:00'],
19 | ['09:00', '18:00'],
20 | ['10:00', '19:00'],
21 | ['05:00', '09:00'],
22 | ['08:00', '24:00'],
23 | ['08:33', '21:55'],
24 | ['17:44', '09:44']
25 | ];
26 |
27 | function random() {
28 | return (Math.random() * 17 >> 0).toString(16);
29 | }
30 |
31 | function getColor() {
32 | return '#' + random() + random() + random()+ random() + random() + random();
33 | }
34 |
35 | function mockDate(range) {
36 | var s = range.start,
37 | e = range.end,
38 | dateArr = [],
39 | data = [],
40 | i = 0,
41 | temp;
42 |
43 | while (i < 7) {
44 | temp = moment(s, 'YYYY-MM-DD');
45 | dateArr.push(temp.add(i, 'day').format('YYYY-MM-DD'));
46 | i++;
47 | }
48 |
49 | i = 0;
50 | while (i < 7) {
51 | data.push({
52 | id:i,
53 | categoryId: 'cate-1',
54 | title: '用车',
55 | content: '',
56 | start: dateArr[i] + ' ' + timeRanges[i][0],
57 | end: dateArr[i] + ' ' + timeRanges[i][1],
58 | bgColor: getColor()
59 | });
60 | i++;
61 | }
62 |
63 | data.push({
64 | id:i++,
65 | categoryId: 'cate-1',
66 | title: '车',
67 | content: '',
68 | start: dateArr[0] + ' 00:00' ,
69 | end: dateArr[0] + ' 08:00',
70 | bgColor: getColor()
71 | });
72 | data.push({
73 | id:i++,
74 | categoryId: 'cate-1',
75 | title: '车',
76 | content: '',
77 | start: dateArr[0] + ' 14:00' ,
78 | end: dateArr[0] + ' 20:00',
79 | bgColor: getColor()
80 | });
81 |
82 | data.push({
83 | id:i++,
84 | categoryId: 'cate-2',
85 | title: '本周用车',
86 | content: '',
87 | start: dateArr[0] + ' ' + timeRanges[7][0],
88 | end: dateArr[5] + ' ' + timeRanges[7][1],
89 | bgColor: getColor()
90 | });
91 |
92 | data.push({
93 | id:i++,
94 | categoryId: 'cate-3',
95 | title: '本周用车2',
96 | content: '',
97 | start: dateArr[0] + ' 00:00',
98 | end: dateArr[6] + ' 24:00',
99 | bgColor: getColor()
100 | });
101 |
102 | return data;
103 | }
104 |
105 | Mock.mock('./test.json', function (opt) {
106 |
107 | return mockDate(getParams(opt.body));
108 |
109 | });
110 |
111 |
112 | })();
--------------------------------------------------------------------------------
/component/weekcalendar/demo/test.json:
--------------------------------------------------------------------------------
1 | [{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-12 07:00","end":"2017-11-12 16:00","bgColor":"#ab9"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-13 08:00","end":"2017-11-13 17:00","bgColor":"#5d8"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-14 09:00","end":"2017-11-14 18:00","bgColor":"#43e"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-15 10:00","end":"2017-11-15 19:00","bgColor":"#696"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-16 05:00","end":"2017-11-16 09:00","bgColor":"#ba7"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-17 08:00","end":"2017-11-17 24:00","bgColor":"#2de"},{"categoryId":"cate-1","title":"用车","content":"","start":"2017-11-18 08:33","end":"2017-11-18 21:55","bgColor":"#ec8"},{"categoryId":"cate-2","title":"本周用车","content":"","start":"2017-11-12 17:44","end":"2017-11-17 09:44","bgColor":"#7cb"},{"categoryId":"cate-3","title":"本周用车2","content":"","start":"2017-11-12 00:00","end":"2017-11-18 24:00","bgColor":"#4d2"}]
--------------------------------------------------------------------------------
/component/weekcalendar/demo/week.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | 二维周视图日历
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/component/weekcalendar/images/btn-next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/weekcalendar/images/btn-next.png
--------------------------------------------------------------------------------
/component/weekcalendar/images/btn-prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/magicds/component-frame/8091c56dcab960b04a8068e2a01718c442547032/component/weekcalendar/images/btn-prev.png
--------------------------------------------------------------------------------
/component/weekcalendar/readme.md:
--------------------------------------------------------------------------------
1 | # 周视图二维日历控件
2 |
3 | 此日历控件是基于框架控件机制,自行实现的一个周视图的二维日历。
4 |
5 | 主要特色在于:
6 |
7 | - 由分类和日期形成一个二维视图
8 | - 可以在二维视图中插入任意小部件(而且自动根据开始结束时间计算好了在视图中占据的位置)
9 | - 点击事件中获取当前分类和日期,并支持日期的范围选择。
10 |
11 | 可用于一周内的日程展示、会议室安排、车辆安排等。
12 |
13 | - [在线演示](https://cdswyda.github.io/component-frame/component/weekcalendar/demo/week.html)
14 | - [说明文档](https://github.com/cdswyda/component-frame/tree/master/component/weekcalendar)
15 |
16 | ## 使用说明
17 |
18 | **资源引入**
19 |
20 | - 引入框架控件机制的基类js文件。
21 |
22 | - 引入此日历控件的js文件。
23 |
24 | **初始化**
25 |
26 | 使用框架控件机制的统一初始化方法 epctrl.init(controlName, cfg) 即可,第一个参数为控件名称,此处即 WeekCalendar,第二个参数为控件的配置。
27 |
28 | 使用如下所示:
29 |
30 | ```js
31 | var calendar = epctrl.init('WeekCalendar', {
32 | el: '#calendar',
33 | date: '2017-11-11'
34 | });
35 | ```
36 |
37 | 相关配置如下:
38 |
39 | ```js
40 | {
41 | // 日历渲染的容器 必须指定 值为一个jq选择器
42 | el: '#calendar',
43 | // 日历宽高度 不指定时 占满指定元素的宽高度
44 | width: '',
45 | height: '',
46 |
47 | // 初始化渲染的日期 格式如:'2017-11-10'
48 | // 初始化的周视图中必定能够显示此日期,不知道则取当天日期
49 | date: '',
50 | // 是否显示边框
51 | showBorder: true,
52 |
53 | // 星期从周日开始?
54 | dayStartFromSunday: false,
55 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调
56 | headerHeight: '',
57 | // 左侧分类标题
58 | categoryTitle: '',
59 | // 左侧分类数据
60 | // 格式如:
61 | // [{
62 | // id: 'cate-1',
63 | // name: '大众',
64 | // content: '苏A00000'
65 | // }, {
66 | // id: 'cate-2',
67 | // name: '雷诺',
68 | // content: '苏A00001'
69 | // }, {
70 | // id: 'cate-3',
71 | // name: '红旗',
72 | // content: '苏A00003'
73 | // }]
74 | category: [],
75 | // 日期时间分隔符 默认为空 对应格式为 '2017-11-11 20:00'
76 | // 对于'2017-11-11T20:00' 这样的格式务必指定正确的日期和时间之间的分隔符
77 | dateTimeSplit: ' ',
78 | // 计算插入部件位置精度的单位 支持秒和分钟 即 second/minute 默认为 second
79 | posUnit: 'second'
80 | }
81 | ```
82 |
83 | ## 方法说明
84 |
85 | 方法名 | 作用 | 参数 | 返回
86 | -- | -- | -- | --
87 | `setCategoryTitle(title)` | 设置分类标题 | `title` 字符串类型,分类名称 | -
88 | `getCategoryTitle()` | 获取分类标题 | | 分类的标题
89 | `setCategory(data)` | 设置左侧分类数据,
设置后分类和网格部分将重绘 | `data` 一个数组,表示左侧分类的数据,详细格式见配置信息 |
90 | `getCategory()` | 获取分类数据 | | 分类数据的数组
91 | `getGridElByPos(i,j)` | 根据位置获取日历网格dom元素 | `i` 、 `j` 为一个数字,表示行列索引,从0开始 | 对应的 `HTMLElement` 或 `null`
92 | `setDate(dateText)` | 指定一个日期渲染到能显示此日期的日历视图 | `dateText` 表示日期的字符串,格式为 `'11-11'` 或 `'2017-11-11'` 不指定年份时则取今天所在年份|
93 | `prevWeek()` | 上一周 | |
94 | `nextWeek()` | 下一周 | |
95 | `goToday()` | 回到今天的视图,今天已经在显示则不处理 | | 今天已经在视图中时返回 `false`
96 | `addWidget(data)` | 在日历中新增一个小部件 | `data` 生成小部件的数据对象。
属性如下:
`id`: 部件id
`categoryId`: 分类id
`title`: 部件title
`content`: 部件内容 可选
`start`: 开始时间 日期格式为2017-11-11 时间格式为08:00:00 日期和时间之间的分隔符可通过`dateTimeSplit`自行配置。如:"2017-11-11 08:22:23"
`end`: 结束时间
`bgColor`: 部件背景色 可选,有默认背景色 | 分类不存在或者用户取消添加时,返回`false`,否则返回新增部件的 `HTMLElement`
97 | `removeWidget(id)` | 移除一个小部件 | `id` 小部件ID | 部件不存在时,返回 `false` ,否则返回移除后的部件 `HTMLElement`。
98 | `getStartEnd(dateStr)` | 指定一个日期,获取对应的周视图中的开始日期和结束日期 | `dateStr` 日期字符串 格式 "2017-11-11" | 一个对象,start属性为开始日期,end为结束日期
99 |
100 | ## 事件说明
101 |
102 | ### beforeCreate
103 |
104 | 日历控件开始创建前触发
105 |
106 | ### afterCreate
107 |
108 | 日历控件创建完成后触发
109 |
110 | ### categoryRender
111 |
112 | 分类渲染时触发,结构生成还未插入页面
113 |
114 | ```js
115 | {
116 | categoryEl: HTMLElement, // 当前分类的HTML元素
117 | titleEl: HTMLElement, // 此分类标题的HTML元素
118 | contentEl: HTMLElement // 此分类内容的HTML元素
119 | }
120 | ```
121 |
122 | ### agterCategoryRender
123 |
124 | 分类渲染时触发,已经插入页面,参数同上。
125 |
126 | ### dateRender
127 |
128 | 日历日期渲染时发生,结构生成还未插入页面。
129 |
130 | ```js
131 | {
132 | // 当前完整日期 2017-11-11
133 | date: String,
134 | // iso星期 1-7 表示周一至周日
135 | isoWeekday: Nomber,
136 | // 显示的文本 默认为 周一 11-13
137 | dateText: String,
138 | // 当前日期don元素的className
139 | dateCls: String,
140 | // 日历el
141 | el: HTMLElement,
142 | // 当前el
143 | dateEl: HTMLElement
144 | }
145 | ```
146 |
147 | 对 `dateText` 和 `dateCls` 的处理会自动更新到日历控件上。`dateCls`通常可以新增一个类,不能直接覆盖默认的类名。
148 |
149 | ### afterDateRender
150 |
151 | 日历日期渲染完成时发生,已经插入页面。参数同上。
152 |
153 | ### titleChanged
154 |
155 | 日历title改变时触发。
156 |
157 | ```js
158 | {
159 | startDate: String, // 当前视图开始日期 格式 YYYY-MM-DD
160 | endDate: String, // 结束日期
161 | // 显示的文本 默认形式为2017年11月13日-19日 或 2017年11月27日-12月3日
162 | title: String,
163 | // 日历title的dom元素
164 | titleEl: HTMLElement
165 | }
166 | ```
167 |
168 | ### dateChanged
169 |
170 | 日历日期改变后触发
171 |
172 | ```js
173 | {
174 | startDate: String, // 开始日期 格式 YYYY-MM-DD
175 | endDate: String, // 结束日期
176 | categorys: Array // 分类id集合
177 | }
178 | ```
179 |
180 | ### dateClick
181 |
182 | 日期点击时触发,仅指显示日期的7天。
183 |
184 | ```js
185 | {
186 | ev: Object, // 原始click事件对象
187 | date: String, // 日期
188 | isoWeekday: Number, // iso星期
189 | dateEl: HTMLElement // 此日期的dom元素
190 | }
191 | ```
192 |
193 | ### categoryClick
194 |
195 | 分类点击时触发
196 |
197 | ```js
198 | {
199 | ev: Object, // 原始click事件对象
200 | categoryId:String, // 此分类的id
201 | title: String, // 此分类名称
202 | categoryIndex: Number // 此分类的索引
203 | }
204 | ```
205 |
206 | ### prevBtnClick
207 |
208 | 上一周点击时触发
209 |
210 | ```js
211 | {
212 | ev: Object, // 原始click事件对象
213 | start: String, // 当前视图开始日期 格式 YYYY-MM-DD
214 | end: String // 结束日期
215 | }
216 | ```
217 |
218 | 设置事件对象的 `cancel` 为 `true` 可取消跳转到上一周
219 |
220 | ### nextWeekClick
221 |
222 | 下一周点击时触发,参数同上。
223 |
224 | ### widgetClick
225 |
226 | 日历中部件点击时触发
227 |
228 | ```js
229 | {
230 | ev: Object, // 原始click事件对象
231 | categoryId: String, // 所属分类id
232 | widgetId: String, // 部件id
233 | start: String, // 此部件开始日期时间
234 | end: String, // 结束日期时间
235 | title: String // 部件名称
236 | }
237 | ```
238 |
239 | ### cellClick
240 |
241 | 日历中分类和日期形成的网格点击时触发,按下鼠标移动再松开,则可以选择一段时间。
242 |
243 | ```js
244 | {
245 | // 分类id
246 | categoryId: String,
247 | // 开始日期
248 | startDate: String,
249 | // 结束日期
250 | endDate: String,
251 | // 行索引
252 | rowIndex: Number,
253 | // 列索引范围
254 | columnIndexs: Array
255 | }
256 | ```
257 |
258 | ### widgetOccupied
259 |
260 | 添加部件和现有部件冲突(重叠)时发生。
261 |
262 | ```js
263 | {
264 | occupiedWidgets: Array, // 和当前添加部件发生冲突的部件的集合,每个成员为部件的 HTMLElement
265 | currWidget: HTMLElement, // 当前部件的HTMLElement
266 | widgetData: Object // 此部件数据
267 | }
268 | ```
269 |
270 | 在此事件中可对发生重叠的部件进行处理,或直接设置其 `cancel` 为 `true` 取消当前的添加。
271 |
272 | ### widgetAdd
273 |
274 | 部件添加时触发,已经检测过是否重叠,还未插入页面。
275 |
276 | ```js
277 | {
278 | widgetId: String, // 部件id
279 | categoryId: String, // 所属分类id
280 | start: String, // 开始日期时间
281 | end: String, // 结束日期时间
282 | startPos: Number, // 开始时间距离左侧的百分比
283 | endPos: Number, // 结束时间换算后距离左侧的百分比
284 | widgetEl: HTMLElement // 部件HTMLElement
285 | }
286 | ```
287 |
288 | 设置其 `cancel` 为 `true` 取消当前的添加。
289 |
290 | ### afterWidgetAdd
291 |
292 | 部件添加完成时触发,参数同上。
293 |
--------------------------------------------------------------------------------
/component/weekcalendar/weekcalendar.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | .ep-weekcalendar {
3 | position: relative;
4 |
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
9 |
10 | /* 头部 */
11 |
12 | .ep-weekcalendar-header {
13 | position: relative;
14 |
15 | height: 42px;
16 | margin-bottom: 10px;
17 | padding: 8px 16px 0;
18 |
19 | text-align: center;
20 |
21 | background-color: #e0f2ff;
22 | }
23 |
24 | .ep-weekcalendar-header:after {
25 | display: table;
26 | clear: both;
27 |
28 | content: "";
29 | }
30 |
31 | .ep-weekcalendar-header-left {
32 | float: left;
33 |
34 | height: 100%;
35 | }
36 |
37 | .ep-weekcalendar-header-right {
38 | float: right;
39 |
40 | height: 100%;
41 | }
42 |
43 | .ep-weekcalendar-header-center {
44 | font-size: 14px;
45 | line-height: 30px;
46 |
47 | position: absolute;
48 | left: 50%;
49 |
50 | width: 300px;
51 | height: 30px;
52 | margin-left: -150px;
53 | }
54 |
55 | .ep-weekcalendar-header-btn {
56 | display: block;
57 |
58 | width: 30px;
59 | height: 30px;
60 |
61 | cursor: pointer;
62 |
63 | background-repeat: no-repeat;
64 | background-position: center center;
65 | }
66 |
67 | .ep-weekcalendar-header-btn-prev {
68 | float: left;
69 |
70 | background-image: url("./images/btn-prev.png");
71 | }
72 |
73 | .ep-weekcalendar-header-btn-next {
74 | float: right;
75 |
76 | background-image: url("./images/btn-next.png");
77 | }
78 |
79 | .ep-weekcalendar-title {
80 | display: inline-block;
81 |
82 | width: 230px;
83 | height: 100%;
84 | }
85 |
86 |
87 | /* body */
88 |
89 | .ep-weekcalendar-body {
90 | position: absolute;
91 | top: 60px;
92 | right: 0;
93 | bottom: 0;
94 | left: 0;
95 | }
96 |
97 |
98 | /* 左侧分类 */
99 |
100 | .ep-weekcalendar-category-area {
101 | position: absolute;
102 | top: 0;
103 |
104 | width: 200px;
105 | }
106 |
107 | .ep-weekcalendar-category-header {
108 | height: 36px;
109 | margin-right: 10px;
110 | padding-right: 18px;
111 |
112 | text-align: center;
113 |
114 | color: #fff;
115 | -webkit-border-radius: 0 18px 18px 0;
116 | -moz-border-radius: 0 18px 18px 0;
117 | border-radius: 0 18px 18px 0;
118 | background: #51a6ef;
119 | }
120 |
121 | .ep-weekcalendar-category-title {
122 | font-size: 14px;
123 | line-height: 36px;
124 | }
125 |
126 | .ep-weekcalendar-category-list {
127 | list-style: none;
128 | margin: 0;
129 | padding: 0;
130 | }
131 |
132 | .ep-weekcalendar-category {
133 | box-sizing: border-box;
134 | height: 50px;
135 | }
136 |
137 | .ep-weekcalendar-category .title,
138 | .ep-weekcalendar-category .content {
139 | display: block;
140 |
141 | box-sizing: border-box;
142 | height: 50%;
143 | padding-left: 20px;
144 |
145 | color: #333;
146 | }
147 |
148 | .ep-weekcalendar-category .content {
149 | color: #888;
150 | }
151 |
152 |
153 | /* 日历中间 */
154 |
155 | .ep-weekcalendar-time-area {
156 | min-height: 100px;
157 | margin-left: 200px;
158 |
159 | background: #fdfdfd;
160 | }
161 |
162 |
163 | /* 头部星期 */
164 |
165 | .ep-weekcalendar-weeks {
166 | line-height: 36px;
167 |
168 | -webkit-box-sizing: border-box;
169 | -moz-box-sizing: border-box;
170 | box-sizing: border-box;
171 | height: 36px;
172 |
173 | text-align: center;
174 | }
175 |
176 | .ep-weekcalendar-weeks:after {
177 | display: table;
178 | clear: both;
179 |
180 | content: "";
181 | }
182 |
183 | .ep-weekcalendar-week {
184 | float: left;
185 | overflow: hidden;
186 |
187 | -webkit-box-sizing: border-box;
188 | -moz-box-sizing: border-box;
189 | box-sizing: border-box;
190 | width: 14.2857%;
191 |
192 | white-space: nowrap;
193 | text-overflow: ellipsis;
194 |
195 | -o-text-overflow: ellipsis;
196 | }
197 |
198 | .ep-weekcalendar-week.weekend {
199 | background: #f2f2f2;
200 | }
201 |
202 | .ep-weekcalendar-main {
203 | position: relative;
204 | }
205 |
206 |
207 | /* 网格 */
208 |
209 | .ep-weekcalendar-grid {
210 | box-sizing: border-box;
211 | margin-top: -1px;
212 | }
213 |
214 | .ep-weekcalendar-grid-row {
215 | height: 50px;
216 | }
217 |
218 | .ep-weekcalendar-grid-row:after {
219 | display: table;
220 | clear: both;
221 |
222 | content: "";
223 | }
224 |
225 | .ep-weekcalendar-grid-item {
226 | display: block;
227 | float: left;
228 |
229 | box-sizing: border-box;
230 | width: 14.2857%;
231 | height: 100%;
232 | }
233 |
234 | .ep-weekcalendar-grid-item.weekend {
235 | background: #f2f2f2;
236 | }
237 |
238 | .ep-weekcalendar-grid-item.selected {
239 | background: #eaf8fa;
240 | }
241 |
242 |
243 | /* content */
244 |
245 | .ep-weekcalendar-content {
246 | line-height: 24px;
247 |
248 | position: absolute;
249 | z-index: 1;
250 | top: 0;
251 | right: 0;
252 | bottom: 0;
253 | left: 0;
254 | }
255 |
256 | .ep-weekcalendar-content-row {
257 | position: relative;
258 |
259 | overflow: hidden;
260 |
261 | box-sizing: border-box;
262 | height: 50px;
263 | }
264 |
265 | .ep-weekcalendar-content-widget {
266 | position: absolute;
267 |
268 | box-sizing: border-box;
269 | height: 100%;
270 | padding: 0 5px;
271 |
272 | color: #fff;
273 | background-color: #6bb2ee;
274 | }
275 |
276 | .ep-weekcalendar.border {
277 | /* 日历网格 */
278 | /* 内容行也需要一个透明边框 防止被内容盖住边框*/
279 | }
280 |
281 | .ep-weekcalendar.border .ep-weekcalendar-category-list {
282 | border-left: 1px solid #ddd;
283 | }
284 |
285 | .ep-weekcalendar.border .ep-weekcalendar-category {
286 | border-bottom: 1px solid #ddd;
287 | }
288 |
289 | .ep-weekcalendar.border .ep-weekcalendar-weeks {
290 | line-height: 34px;
291 |
292 | border: 1px solid #ddd;
293 | border-right: none;
294 | }
295 |
296 | .ep-weekcalendar.border .ep-weekcalendar-week {
297 | border-right: 1px solid #ddd;
298 | }
299 |
300 | .ep-weekcalendar.border .ep-weekcalendar-grid {
301 | border-right: 1px solid #ddd;
302 | border-bottom: 1px solid #ddd;
303 | }
304 |
305 | .ep-weekcalendar.border .ep-weekcalendar-grid-item {
306 | border-top: 1px solid #ddd;
307 | border-left: 1px solid #ddd;
308 | }
309 |
310 | .ep-weekcalendar.border .ep-weekcalendar-content-row {
311 | border: 1px solid transparent;
312 | border-bottom: none;
313 | }
314 |
315 |
316 | /*# sourceMappingURL=weekcalendar.css.map */
317 |
--------------------------------------------------------------------------------
/component/weekcalendar/weekcalendar.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 二维周视图日历
3 | * author: chends
4 | */
5 | /* global epctrl,moment*/
6 |
7 | epctrl.WeekCalendar = function (cfg) {
8 | epctrl.WeekCalendar.superclass.constructor.apply(this, arguments);
9 |
10 | // 添加控件的css和js资源 如果是在父类的基础上新增css资源 则使用以下两个方法
11 | // this.addCss();
12 | // this.addJs();
13 | // 如果要给当前控件新的css、js资源,则通过this.setjs()和this.setCss()进行配置,参数为一个字符串或数组。
14 | this.setCss('weekcalendar/weekcalendar.css');
15 |
16 | // 当前无moment时 自动引入moment
17 | if (!window.moment || typeof window.moment != 'function') {
18 | this.setJs('../lib/moment.min.js');
19 | }
20 |
21 | // 加入传递的配置
22 | this.cfg = cfg || {};
23 | };
24 |
25 | epctrl.extend('WeekCalendar', 'Control', {
26 | // 控件类型 必须指定,值即为控件的名称
27 | type: 'WeekCalendar',
28 |
29 | // 选中的单元格的class
30 | _selectedCls: 'selected',
31 |
32 | // 一周分钟数
33 | _WEEKMINUTES: 7 * 24 * 60,
34 | // 一周秒数
35 | _WEEKSECONDS: 7 * 24 * 3600,
36 | // 一天的分钟数秒数
37 | _DAYMINUTES: 24 * 60,
38 | _DAYSCONDS: 24 * 3600,
39 |
40 | // 计算位置的精度 取值second 或 minute
41 | posUnit: 'second',
42 |
43 | // 日期和时间之前的分隔符
44 | _dateTimeSplit: ' ',
45 |
46 | _defaultCfg: {
47 | // 不指定时 占满指定元素的宽高度
48 | width: '',
49 | height: '',
50 |
51 | // 初始化渲染的日期 格式如:'2017-11-10'
52 | date: '',
53 | // 是否显示边框
54 | showBorder: true,
55 |
56 | // 星期从周日开始?
57 | dayStartFromSunday: false,
58 | // 头部高度 不指定以css样式为准 如果指定了 也需要自行修改css 否则可能不协调
59 | headerHeight: '',
60 | // 左侧分类标题
61 | categoryTitle: '',
62 | // 左侧分类数据
63 | category: []
64 | },
65 |
66 | // 初始化配置
67 | _initCfg: function () {
68 | this.cfg = jQuery.extend({}, this._defaultCfg, this.cfg);
69 |
70 | if (!this.cfg.el) {
71 | this.throwError('必须指定日历渲染容器');
72 | }
73 |
74 | // 日历渲染容器
75 | this.$container = jQuery(this.cfg.el);
76 | this.container = this.$container[0];
77 |
78 | // 星期从周日开始?
79 | this.dayStartFromSunday = this.cfg.dayStartFromSunday;
80 |
81 | // 是否显示边框
82 | this.showBorder = this.cfg.showBorder;
83 |
84 | // 今天
85 | this.today = moment().format('YYYY-MM-DD');
86 |
87 | // 需要展示的日期
88 | this.date = this.cfg.date || this.today;
89 |
90 | // 分类标题
91 | this._categoryTitle = this.cfg.categoryTitle;
92 |
93 | // 分类数据
94 | this._categoryData = this.cfg.category;
95 |
96 | // 日期时间分隔符
97 | this._dateTimeSplit = this.cfg.dateTimeSplit || this._dateTimeSplit;
98 | // 位置精度
99 | this.posUnit = this.cfg.posUnit || this.posUnit;
100 | },
101 |
102 | // 渲染
103 | render: function () {
104 |
105 | this._initContainer();
106 |
107 | this._renderCatagories();
108 |
109 | this._renderChanged();
110 | // this._renderWeeks();
111 | // this._renderGrid();
112 | // this._rednerContent();
113 |
114 | },
115 |
116 | // 初始化结构
117 | _initContainer: function () {
118 | this.$container.empty();
119 |
120 | var el = document.createElement('div');
121 | el.className = 'ep-weekcalendar' + (this.showBorder ? ' border' : '');
122 |
123 | this.cfg.width && (el.style.width = this.cfg.width);
124 | this.cfg.height && (el.style.height = this.cfg.height);
125 |
126 | this.el = el;
127 |
128 | // header
129 | var header = document.createElement('div');
130 | header.className = 'ep-weekcalendar-header';
131 | header.innerHTML = '';
132 |
133 | this.cfg.headerHeight && (header.style.height = this.cfg.headerHeight);
134 |
135 | this._headerEl = header;
136 |
137 | this._headerLeft = header.firstChild;
138 | this._headerRight = header.lastChild;
139 | // this._headerCenter = jQuery('.ep-weekcalendar-header-center', header)[0];
140 | this._headerCenter = this._headerLeft.nextSibling;
141 | // this._prevBtn = jQuery('.ep-weekcalendar-header-btn-prev', this._headerCenter)[0];
142 | this._prevBtn = this._headerCenter.firstChild;
143 | this._titleEl = this._prevBtn.nextSibling;
144 |
145 | this._nextBtn = this._headerCenter.lastChild;
146 | this.el.appendChild(header);
147 |
148 | // 主体部分 左侧分类 右侧日历
149 | var body = document.createElement('div');
150 | body.className = 'ep-weekcalendar-body';
151 |
152 | // 左侧分类
153 | var categoryArea = document.createElement('div'),
154 | cateHeader = document.createElement('div'),
155 | cateTitle = document.createElement('span'),
156 | cateList = document.createElement('ul');
157 |
158 | categoryArea.className = 'ep-weekcalendar-category-area';
159 | cateHeader.className = 'ep-weekcalendar-category-header';
160 | cateTitle.className = 'ep-weekcalendar-category-title';
161 | cateList.className = 'ep-weekcalendar-category-list';
162 | cateHeader.appendChild(cateTitle);
163 | categoryArea.appendChild(cateHeader);
164 | categoryArea.appendChild(cateList);
165 |
166 | // 右侧区域
167 | var timeArea = document.createElement('div');
168 | timeArea.className = 'ep-weekcalendar-time-area';
169 |
170 | // 头部星期
171 | var weeks = document.createElement('div');
172 | weeks.className = 'ep-weekcalendar-weeks';
173 |
174 | // 时间部分中分两块 一个画日历作为背景 一个用于显示内容
175 | var main = document.createElement('div'),
176 | grid = document.createElement('div'),
177 | content = document.createElement('div');
178 | main.className = 'ep-weekcalendar-main';
179 | grid.className = 'ep-weekcalendar-grid';
180 | content.className = 'ep-weekcalendar-content';
181 |
182 | timeArea.appendChild(weeks);
183 | main.appendChild(grid);
184 | main.appendChild(content);
185 | timeArea.appendChild(main);
186 | body.appendChild(categoryArea);
187 | body.appendChild(timeArea);
188 |
189 | el.appendChild(body);
190 |
191 | this._categoryTitleEl = cateTitle;
192 | this._categoryListEl = cateList;
193 | this._weeksEl = weeks;
194 | this._gridEl = grid;
195 | this._contentEl = content;
196 | this.container.appendChild(this.el);
197 | },
198 |
199 | // 左侧分类渲染
200 | _renderCatagories: function () {
201 | this.setCategoryTitle();
202 |
203 | this._categoryListEl.innerHTML = '';
204 |
205 | var i = 0,
206 | data = this._categoryData,
207 | node = document.createElement('li'),
208 | cataEl;
209 | node.className = 'ep-weekcalendar-category';
210 |
211 | // 用行作为下标记录当前分类id集合
212 | this._categoryIndexs = [];
213 | // id为键记录索引
214 | this._categoryReocrds = {};
215 |
216 | while (i < data.length) {
217 | this._categoryIndexs.push(data[i].id);
218 | this._categoryReocrds[data[i].id] = i;
219 | cataEl = node.cloneNode(true);
220 | this._rendercategory(data[i], cataEl);
221 | i++;
222 | }
223 |
224 | // 分类重绘必定重绘网格和内容
225 | this._renderGrid();
226 | this._rednerContent();
227 |
228 | },
229 |
230 | _rendercategory: function (cate, cateEl) {
231 | cateEl.setAttribute('data-cateid', cate.id);
232 |
233 | var titleEl = document.createElement('span'),
234 | contentEl = document.createElement('span');
235 | titleEl.className = 'title';
236 | contentEl.className = 'content';
237 |
238 | titleEl.innerHTML = cate.name;
239 | contentEl.innerHTML = cate.content;
240 | cateEl.appendChild(titleEl);
241 | cateEl.appendChild(contentEl);
242 |
243 | this.fire('categoryRender', {
244 | categoryEl: cateEl,
245 | titleEl: titleEl,
246 | contentEl: contentEl
247 | });
248 |
249 | this._categoryListEl.appendChild(cateEl);
250 |
251 | this.fire('agterCategoryRender', {
252 | categoryEl: cateEl,
253 | titleEl: titleEl,
254 | contentEl: contentEl
255 | });
256 | },
257 |
258 | // 右侧网格
259 | _renderGrid: function () {
260 | this._gridEl.innerHTML = '';
261 |
262 | var rowNode = document.createElement('div'),
263 | itemNode = document.createElement('span'),
264 | rowsNum = this._categoryData.length,
265 | i = 0,
266 | j = 0,
267 | row, item;
268 |
269 | rowNode.className = 'ep-weekcalendar-grid-row';
270 | itemNode.className = 'ep-weekcalendar-grid-item';
271 |
272 | while (i < rowsNum) {
273 | row = rowNode.cloneNode();
274 | row.setAttribute('data-i', i);
275 | j = 0;
276 |
277 | while (j < 7) {
278 | item = itemNode.cloneNode();
279 | // 周末标识
280 | if (this.dayStartFromSunday) {
281 | if (j === 0 || j === 6) {
282 | item.className += ' weekend';
283 | }
284 | } else {
285 | if (j > 4) {
286 | item.className += ' weekend';
287 | }
288 | }
289 |
290 | item.setAttribute('data-i', i);
291 | item.setAttribute('data-j', j);
292 | row.appendChild(item);
293 |
294 | j++;
295 | }
296 |
297 | this._gridEl.appendChild(row);
298 |
299 | i++;
300 | }
301 |
302 | rowNode = itemNode = row = item = null;
303 | },
304 |
305 | // 右侧内容
306 | _rednerContent: function () {
307 | this._contentEl.innerHTML = '';
308 |
309 | var i = 0,
310 | node = document.createElement('div'),
311 | row;
312 |
313 | node.className = 'ep-weekcalendar-content-row';
314 |
315 | while (i < this._categoryData.length) {
316 | row = node.cloneNode();
317 | row.setAttribute('data-i', i);
318 |
319 | this._contentEl.appendChild(row);
320 | ++i;
321 | }
322 |
323 | row = node = null;
324 |
325 | },
326 |
327 | // 日期切换时清空内容
328 | _clearContent: function () {
329 | var rows = this._contentEl.childNodes,
330 | i = 0;
331 |
332 | while (i < rows.length) {
333 | rows[i].innerHTML && (rows[i].innerHTML = '');
334 | ++i;
335 | }
336 |
337 | // 部件数据清空
338 | this._widgetData = {};
339 | },
340 |
341 | // 渲染日历星期头部
342 | _renderWeeks: function () {
343 | this._weeksEl.innerHTML = '';
344 | var i = 0,
345 | currDate = this._startDate.clone(),
346 | node = document.createElement('div'),
347 | week;
348 | node.className = 'ep-weekcalendar-week';
349 |
350 | // 单元格列作为下标记录日期
351 | this._dateRecords = [];
352 |
353 | while (i++ < 7) {
354 | // 更新记录日期
355 | this._dateRecords.push(currDate.clone());
356 |
357 | week = node.cloneNode(true);
358 | this._renderWeek(currDate, week);
359 | currDate.add(1, 'day');
360 | }
361 |
362 | // 切换日期 需要重绘内容区域
363 | this._rednerContent();
364 | },
365 |
366 | _renderWeek: function (date, node) {
367 | var dateText = date.format('YYYY-MM-DD'),
368 | day = date.isoWeekday();
369 | // dateText = '周' + this._WEEKSNAME[date.isoWeekday() - 1] + ' ' + date.format('MM-DD');
370 | // node.innerText = '周' + this._WEEKSNAME[day-1] + ' ' + date.format('MM-DD');
371 |
372 | if (day > 5) {
373 | node.className += ' weekend';
374 | }
375 | if (date.isSame(this.today, 'day')) {
376 | node.className += ' today';
377 | }
378 |
379 | node.setAttribute('data-date', dateText);
380 | node.setAttribute('date-isoweekday', day);
381 |
382 | var ev = this.fire('dateRender', {
383 | // 当前完整日期
384 | date: dateText,
385 | // iso星期
386 | isoWeekday: day,
387 | // 显示的文本
388 | dateText: '周' + this._WEEKSNAME[day - 1] + ' ' + date.format('MM-DD'),
389 | // classname
390 | dateCls: node.className,
391 | // 日历el
392 | el: this.el,
393 | // 当前el
394 | dateEl: node
395 | });
396 |
397 | // 处理事件的修改
398 | node.innerHTML = ev.dateText;
399 | node.className = ev.dateCls;
400 |
401 | this._weeksEl.appendChild(node);
402 |
403 | this.fire('afterDateRender', {
404 | // 当前完整日期
405 | date: dateText,
406 | // iso星期
407 | isoWeekday: day,
408 | // 显示的文本
409 | dateText: node.innerHTML,
410 | // classname
411 | dateCls: node.className,
412 | // 日历el
413 | el: this.el,
414 | // 当前el
415 | dateEl: node
416 | });
417 | },
418 |
419 | // 绘制可变部分
420 | _renderChanged: function () {
421 | // 清空内容
422 | this._clearContent();
423 | // 初始日期范围
424 | // 渲染标题和日期
425 | this._initStartEnd();
426 |
427 | var start = this._startDate.format('YYYY-MM-DD'),
428 | end = this._endDate.format('YYYY-MM-DD');
429 |
430 | this._renderCalendarTitle();
431 | this._renderWeeks();
432 |
433 | // 渲染完成
434 | this.fire('dateChanged', {
435 | startDate: start,
436 | endDate: end,
437 | categorys: this._categoryIndexs
438 | });
439 |
440 | },
441 |
442 | // 绘制头部标题
443 | _renderCalendarTitle: function () {
444 | var start_text = this._startDate.format('YYYY年MM月DD日'),
445 | end_text =
446 | this._endDate.format(this._startDate.isSame(this._endDate, 'month') ? 'DD日' : 'MM月-DD日');
447 |
448 | // 触发标题改变事件
449 | var ev = this.fire('titleChanged', {
450 | startDate: this._startDate.format('YYYY-MM-DD'),
451 | endDate: this._endDate.format('YYYY-MM-DD'),
452 | title: start_text + ' - ' + end_text,
453 | titleEl: this._titleEl
454 | });
455 |
456 | this._titleEl.innerHTML = ev.title;
457 | },
458 |
459 | // 星期文本
460 | _WEEKSNAME: ['一', '二', '三', '四', '五', '六', '日'],
461 |
462 | // 初始一周的开始和结束
463 | _initStartEnd: function () {
464 | var startEnd = this._getStartEnd();
465 |
466 | this._startDate = startEnd.start;
467 | this._endDate = startEnd.end;
468 | },
469 |
470 | _getStartEnd: function (date) {
471 | date = moment(date || this.date, 'YYYY-MM-DD');
472 |
473 | var isoWeekday = date.isoWeekday(),
474 | start, end;
475 |
476 | if (this.dayStartFromSunday) {
477 | start = date.clone().subtract(isoWeekday === 7 ? 0 :
478 | isoWeekday, 'day');
479 | } else {
480 | start = date.clone().subtract(isoWeekday - 1, 'day');
481 | }
482 | end = start.clone().add(6, 'day');
483 | return {
484 | start: start,
485 | end: end
486 | };
487 | },
488 |
489 | // 根据指定日期获取周视图周中的开始和结束日期
490 | getStartEnd: function (date) {
491 | var o = this._getStartEnd(date);
492 | return {
493 | start: o.start.format('YYYY-MM-DD'),
494 | end: o.end.format('YYYY-MM-DD')
495 | };
496 | },
497 |
498 | // 初始化事件
499 | _initEvent: function () {
500 | var me = this;
501 |
502 | // 点击的行索引
503 | var row,
504 | // 开始列索引
505 | columnStart,
506 | // 结束列索引
507 | columnEnd,
508 | // 是否在按下、移动、松开的click中
509 | isDurringClick = false,
510 | // 是否移动过 用于处理按下没有移动直接松开的过程
511 | isMoveing = false,
512 | $columns,
513 | // 网格左侧宽度
514 | gridLeft,
515 | // 每列的宽度
516 | columnWidth;
517 | jQuery(this.el)
518 | // 按下鼠标 记录分类和开始列
519 | .on('mousedown.weekcalendar', '.ep-weekcalendar-content-row', function (e) {
520 | isDurringClick = true;
521 | gridLeft = jQuery(me._gridEl).offset().left;
522 | columnWidth = jQuery(me._gridEl).width() / 7;
523 | jQuery(me._gridEl).find('.ep-weekcalendar-grid-item').removeClass(me._selectedCls);
524 |
525 | row = this.getAttribute('data-i');
526 | $columns = jQuery(me._gridEl).find('.ep-weekcalendar-grid-row').eq(row).children();
527 |
528 | columnStart = (e.pageX - gridLeft) / columnWidth >> 0;
529 |
530 | })
531 | // 前后一周
532 | .on('click.weekcalendar', '.ep-weekcalendar-header-btn', function (e) {
533 | var $this = jQuery(this),
534 | isPrev = $this.hasClass('ep-weekcalendar-header-btn-prev'),
535 | isNext = $this.hasClass('ep-weekcalendar-header-btn-next'),
536 | ev,
537 | start = me._startDate.format('YYYY-MM-DD'),
538 | end = me._endDate.format('YYYY-MM-DD');
539 |
540 | if (isPrev) {
541 | ev = me.fire('prevWeekClick', {
542 | ev: e,
543 | start: start,
544 | end: end
545 | });
546 | } else if (isNext) {
547 | ev = me.fire('nextWeekClick', {
548 | ev: e,
549 | start: start,
550 | end: end
551 | });
552 | }
553 |
554 | if (ev && !ev.cancel) {
555 | me[(isPrev ? 'prev' : 'next') + 'Week']();
556 | }
557 |
558 | })
559 | // 小部件点击 由于之前的点击用了down move up来做 这里不能直接使用click 因为click触发太晚
560 | .on('mousedown.weekcalendar', '.ep-weekcalendar-content-widget', function (e) {
561 | e.stopPropagation();
562 | })
563 | .on('mouseup.weekcalendar', '.ep-weekcalendar-content-widget', function (e) {
564 | e.stopPropagation();
565 |
566 | me.fire('widgetClick', {
567 | ev: e,
568 | categoryId: me._categoryIndexs[this.parentNode.getAttribute('data-i')],
569 | widgetId: this.getAttribute('data-id'),
570 | start: this.getAttribute('data-start'),
571 | end: this.getAttribute('data-end'),
572 | title: this.title
573 | });
574 | })
575 | // 头部日期点击
576 | .on('click.weekcalendar', '.ep-weekcalendar-week', function (e) {
577 | me.fire('dateClick', {
578 | ev: e,
579 | date: this.getAttribute('data-date'),
580 | isoWeekday: this.getAttribute('data-isoweekday'),
581 | dateEl: this
582 | });
583 | })
584 | // 分类点击
585 | .on('click.weekcalendar', '.ep-weekcalendar-category', function (e) {
586 | var id = this.getAttribute('data-cateid');
587 | me.fire('categoryClick', {
588 | ev: e,
589 | categoryId: id,
590 | title: this.firstChild.innerHTML,
591 | categoryIndex: me._categoryReocrds[id]
592 | });
593 | });
594 |
595 | // 移动和松开 松开鼠标 记录结束列 触发点击事件
596 | // 必须绑定在body上 否则直接移除后 不能正确结束move事件
597 | jQuery('body')
598 | // 点击移动过程中 实时响应选中状态
599 | .on('mousemove.weekcalendar', function (e) {
600 | if (!isDurringClick) {
601 | return;
602 | }
603 | isMoveing = true;
604 |
605 | // 当前列索引
606 | var currColumn;
607 |
608 | currColumn = (e.pageX - gridLeft) / columnWidth >> 0;
609 |
610 | // 修正溢出
611 | currColumn = currColumn > 6 ? 6 : currColumn;
612 | currColumn = currColumn < 0 ? 0 : currColumn;
613 |
614 | $columns.removeClass(me._selectedCls);
615 |
616 | // 起止依次选中
617 | var start = Math.min(columnStart, currColumn),
618 | end = Math.max(columnStart, currColumn);
619 |
620 | do {
621 | $columns.eq(start).addClass(me._selectedCls);
622 | } while (++start <= end);
623 |
624 | })
625 | // 鼠标松开
626 | .on('mouseup.weekcalendar', function (e) {
627 | if (!isDurringClick) {
628 | return;
629 | }
630 |
631 | var startIndex = -1,
632 | endIndex = -1;
633 |
634 | columnEnd = (e.pageX - gridLeft) / columnWidth >> 0;
635 |
636 | columnEnd = columnEnd > 6 ? 6 : columnEnd;
637 |
638 | // 没有移动过时
639 | if (!isMoveing) {
640 | startIndex = endIndex = columnEnd;
641 | // 直接down up 没有move的过程则只会有一个选中的,直接以结束的作为处理即可
642 | $columns.eq(columnEnd).addClass(me._selectedCls)
643 | .siblings().removeClass(me._selectedCls);
644 | } else {
645 | startIndex = Math.min(columnStart, columnEnd);
646 | endIndex = Math.max(columnStart, columnEnd);
647 | }
648 |
649 | // 触发点击事件
650 | me.fire('cellClick', {
651 | // 分类id
652 | categoryId: me._categoryIndexs[row],
653 | // 时间1
654 | startDate: me._dateRecords[startIndex].format('YYYY-MM-DD'),
655 | // 日期2
656 | endDate: me._dateRecords[endIndex].format('YYYY-MM-DD'),
657 | // 行索引
658 | rowIndex: row,
659 | // 列范围
660 | columnIndexs: (function (i, j) {
661 | var arr = [];
662 | while (i <= j) {
663 | arr.push(i++);
664 | }
665 | return arr;
666 | }(startIndex, endIndex))
667 | });
668 |
669 | row = columnStart = columnEnd = isMoveing = isDurringClick = false;
670 | });
671 | },
672 |
673 | // 左侧分类标题
674 | setCategoryTitle: function (title) {
675 | title && (this._categoryTitle = title);
676 | this._categoryTitleEl.innerHTML = this._categoryTitle;
677 | },
678 | getCategoryTitle: function () {
679 | return this._categoryTitle;
680 | },
681 |
682 | // 左侧分类数据
683 | setCategory: function (data) {
684 | if (!(data instanceof Array)) {
685 | this.throwError('分类数据必须是一个数组');
686 | return;
687 | }
688 | this._categoryData = data;
689 |
690 | this._renderCatagories();
691 |
692 | this._renderChanged();
693 | },
694 | getCategory: function () {
695 | return this._categoryData;
696 | },
697 |
698 | // 根据行列获取日历网格
699 | getGridElByPos: function (i, j) {
700 | var $row = jQuery('.ep-weekcalendar-grid-row[data-i="' + i + '"]', this._gridEl);
701 |
702 | return $row.length ? $row.find('[data-j="' + j + '"]')[0] : null;
703 | },
704 |
705 | // 指定一个日期,渲染到能能显示当前日期的视图
706 | setDate: function (dateText) {
707 | if (/^\d{1,2}-\d{1,2}$/.test(dateText)) {
708 | dateText = (new Date()).getFullYear() + '-' + dateText;
709 | }
710 | var date = moment(dateText, 'YYYY-MM-DD');
711 |
712 | if (!date.isValid()) {
713 | this.throwError('指定日期不合法');
714 | return false;
715 | }
716 |
717 | var sn = this._getStartEnd(date);
718 |
719 | if (sn.start.isSame(this._startDate, 'day')) {
720 | // 就是当前视图
721 | return false;
722 | }
723 |
724 | this._setDate(date);
725 | },
726 |
727 | _setDate: function (date) {
728 | this.date = date.format('YYYY-MM-DD');
729 |
730 | // 调整日期和标题
731 | this._renderChanged();
732 | },
733 |
734 | // 上一周
735 | prevWeek: function () {
736 | this._setDate(this._startDate.clone().subtract(1, 'day'));
737 | },
738 |
739 | // 下一周
740 | nextWeek: function () {
741 | this._setDate(this._endDate.clone().add(1, 'day'));
742 | },
743 |
744 | // 回今天视图
745 | goToday: function () {
746 | var today = moment();
747 | if (today.isBetween(this._startDate, this._endDate)) {
748 | // 今天在 当前视图中 则不用处理
749 | return false;
750 | }
751 |
752 | this.today = today.format('YYYY-MM-DD');
753 | this._setDate(today);
754 | },
755 |
756 | // 计算指定日期的分钟或秒数
757 | _getNumByUnits: function (dateStr) {
758 | var temp = dateStr.split(this._dateTimeSplit),
759 | date = temp[0];
760 |
761 | // 处理左侧溢出
762 | if (this._startDate.isAfter(date, 'day')) {
763 | // 指定日期在开始日期之前
764 | return 0;
765 | }
766 | // 右侧溢出直接算作7天即可
767 |
768 | var times = (temp[1] || '').split(':'),
769 | days = (function (startDate) {
770 | var currDate = startDate.clone(),
771 | i = 0,
772 | d = moment(date, 'YYYY-MM-DD');
773 | while (i < 7) {
774 | if (currDate.isSame(d, 'day')) {
775 | return i;
776 | } else {
777 | currDate.add(1, 'day');
778 | ++i;
779 | }
780 | }
781 |
782 | console && console.error && console.error('计算天数时出错!');
783 | return i;
784 | }(this._startDate)),
785 | hours = parseInt(times[0], 10) || 0,
786 | minutes = parseInt(times[1], 10) || 0,
787 | seconds = parseInt(times[2], 10) || 0,
788 | // 对应分钟数
789 | result = days * this._DAYMINUTES + hours * 60 + minutes;
790 |
791 | return this.posUnit == 'minute' ? result : (result * 60 + seconds);
792 | },
793 |
794 | // 计算日期时间的百分比位置
795 | _getPos: function (dateStr) {
796 | var p = this._getNumByUnits(dateStr) / (this.posUnit == 'minute' ? this._WEEKMINUTES : this._WEEKSECONDS);
797 |
798 | return p > 1 ? 1 : p;
799 | },
800 | /**
801 | * 检查是否发生重叠
802 | *
803 | * @param {Object} data 当前要加入的数据
804 | * @returns false 或 和当前部件重叠的元素数组
805 | */
806 | _checkOccupied: function (data) {
807 |
808 | if (!this._widgetData[data.categoryId]) {
809 | return false;
810 | }
811 |
812 | var i = 0,
813 | cate = this._widgetData[data.categoryId],
814 | len = cate.length,
815 | result = false,
816 | occupied = [];
817 |
818 | for (; i < len; ++i) {
819 | if (data.start < cate[i].end && data.end > cate[i].start) {
820 | occupied.push(cate[i]);
821 |
822 | result = true;
823 | }
824 | }
825 |
826 | return result ? occupied : false;
827 | },
828 | // 缓存widget数据
829 | _cacheWidgetData: function (data) {
830 | if (!this._widgetData[data.categoryId]) {
831 | this._widgetData[data.categoryId] = [];
832 | }
833 | // 记录当前的
834 | this._widgetData[data.categoryId].push(data);
835 | },
836 | // 新增一个小部件
837 | addWidget: function (data) {
838 | var row = this._contentEl.childNodes[this._categoryReocrds[data.categoryId]];
839 |
840 | if (!row) {
841 | this.throwError('对应分类不存在,添加失败');
842 | return false;
843 | }
844 |
845 | // 先查找是否含有
846 | var $aim = jQuery('.ep-weekcalendar-content-widget[data-id="' + data.id + '"]', row);
847 |
848 | if ($aim.length) {
849 | // 已经存在则不添加
850 | return $aim[0];
851 | }
852 |
853 | // 创建部件
854 | var widget = document.createElement('div'),
855 | title = document.createElement('span'),
856 | content = document.createElement('p'),
857 | startPos = this._getPos(data.start),
858 | endPos = this._getPos(data.end),
859 | _data = {
860 | categoryId: data.categoryId,
861 | id: data.id,
862 | start: startPos,
863 | end: endPos,
864 | el: widget,
865 | data: data
866 | };
867 |
868 | widget.className = 'ep-weekcalendar-content-widget';
869 | title.className = 'ep-weekcalendar-content-widget-title';
870 | content.className = 'ep-weekcalendar-content-widget-content';
871 |
872 | widget.appendChild(title);
873 | widget.appendChild(content);
874 |
875 | widget.style.left = startPos * 100 + '%';
876 | widget.style.right = (1 - endPos) * 100 + '%';
877 | data.bgColor && (widget.style.backgroundColor = data.bgColor);
878 |
879 | data.id && widget.setAttribute('data-id', data.id);
880 | widget.setAttribute('data-start', data.start);
881 | widget.setAttribute('data-end', data.end);
882 |
883 | title.innerHTML = data.title;
884 | data.content && (content.innerHTML = data.content);
885 | widget.title = data.title + ' 从' + moment(data.start, 'YYYY-MM-DD' + this._dateTimeSplit + 'HH:mm:ss').format('M月D日 HH:mm') + ' 到 ' + moment(data.end, 'YYYY-MM-DD' + this._dateTimeSplit + 'HH:mm:ss').format('M月D日 HH:mm');
886 |
887 | // 检查是否发生重叠
888 | var isoccupied = this._checkOccupied(_data);
889 |
890 | if (isoccupied) {
891 | // 触发重叠事件
892 | var occupiedEv = this.fire('widgetoccupied', {
893 | occupiedWidgets: (function () {
894 | var arr = [];
895 | for (var i = 0, l = isoccupied.length; i < l; ++i) {
896 | arr.push(isoccupied[i].el);
897 | }
898 | return arr;
899 | })(),
900 | currWidget: widget,
901 | widgetData: data
902 | });
903 |
904 | // 取消后续执行
905 | if (occupiedEv.cancel) {
906 | return false;
907 | }
908 | }
909 |
910 | // 缓存数据
911 | this._cacheWidgetData(_data);
912 |
913 | var addEv = this.fire('widgetAdd', {
914 | widgetId: data.id,
915 | categoryId: data.categoryId,
916 | start: data.start,
917 | end: data.end,
918 | startPos: startPos,
919 | endPos: endPos,
920 | widgetEl: widget
921 | });
922 |
923 | if (addEv.cancel) {
924 | return false;
925 | }
926 |
927 | row.appendChild(widget);
928 |
929 | this.fire('afterWidgetAdd', {
930 | widgetId: data.id,
931 | categoryId: data.categoryId,
932 | start: data.start,
933 | end: data.end,
934 | startPos: startPos,
935 | endPos: endPos,
936 | widgetEl: widget
937 | });
938 |
939 | return widget;
940 | },
941 |
942 | // 移除一个小部件
943 | removeWidget: function (id) {
944 | var $widget = jQuery('.ep-weekcalendar-content-widget[data-id="' + id + '"]', this._contentEl);
945 |
946 | if (!$widget.length) {
947 | return false;
948 | }
949 |
950 | var categoryId = this._categoryIndexs[$widget.parent().data('i')],
951 | widgetData = this._widgetData[categoryId];
952 |
953 | // 移除widget数据
954 | jQuery.each(widgetData, function (i, item) {
955 | if (item.id == id) {
956 | widgetData.splice(i, 1);
957 | return false;
958 | }
959 | });
960 |
961 | return $widget.remove();
962 | },
963 |
964 | // 此方法在js等资源加载完成后自动调用
965 | create: function () {
966 |
967 | // 触发创建前事件
968 | this.fire('beforeCreate');
969 |
970 | this._initCfg();
971 | this.render();
972 |
973 | this._initEvent();
974 |
975 | this.fire('afterCreate');
976 | }
977 | });
978 |
--------------------------------------------------------------------------------
/component/weekcalendar/weekcalendar.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | // 边框颜色
4 | $border_color: #ddd;
5 | // 周末背景
6 | $weekend_bg: #f2f2f2;
7 | // 选中背景
8 | $selected_bg: #eaf8fa;
9 |
10 | .ep-weekcalendar {
11 | position: relative;
12 |
13 | width: 100%;
14 | height: 100%;
15 | }
16 |
17 |
18 | /* 头部 */
19 |
20 | .ep-weekcalendar-header {
21 | position: relative;
22 |
23 | height: 42px;
24 | margin-bottom: 10px;
25 | padding: 8px 16px 0;
26 |
27 | text-align: center;
28 |
29 | background-color: #e0f2ff;
30 |
31 | &:after {
32 | display: table;
33 | clear: both;
34 |
35 | content: "";
36 | }
37 |
38 | @at-root {
39 | .ep-weekcalendar-header-left {
40 | float: left;
41 |
42 | height: 100%;
43 | }
44 | .ep-weekcalendar-header-right {
45 | float: right;
46 |
47 | height: 100%;
48 | }
49 |
50 | .ep-weekcalendar-header-center {
51 | font-size: 14px;
52 | line-height: 30px;
53 |
54 | position: absolute;
55 | left: 50%;
56 |
57 | width: 300px;
58 | height: 30px;
59 | margin-left: -150px;
60 | }
61 |
62 | .ep-weekcalendar-header-btn {
63 | display: block;
64 |
65 | width: 30px;
66 | height: 30px;
67 |
68 | cursor: pointer;
69 |
70 | background-repeat: no-repeat;
71 | background-position: center center;
72 | }
73 | .ep-weekcalendar-header-btn-prev {
74 | float: left;
75 |
76 | background-image: url("./images/btn-prev.png");
77 | }
78 | .ep-weekcalendar-header-btn-next {
79 | float: right;
80 |
81 | background-image: url("./images/btn-next.png");
82 | }
83 | .ep-weekcalendar-title {
84 | display: inline-block;
85 |
86 | width: 230px;
87 | height: 100%;
88 | }
89 | }
90 | }
91 |
92 |
93 | /* body */
94 |
95 | .ep-weekcalendar-body {
96 | position: absolute;
97 | top: 60px;
98 | right: 0;
99 | bottom: 0;
100 | left: 0;
101 | }
102 |
103 |
104 | /* 左侧分类 */
105 |
106 | .ep-weekcalendar-category-area {
107 | position: absolute;
108 | top: 0;
109 |
110 | width: 200px;
111 | }
112 |
113 | .ep-weekcalendar-category-header {
114 | height: 36px;
115 | margin-right: 10px;
116 | padding-right: 18px;
117 |
118 | text-align: center;
119 |
120 | color: #fff;
121 | -webkit-border-radius: 0 18px 18px 0;
122 | -moz-border-radius: 0 18px 18px 0;
123 | border-radius: 0 18px 18px 0;
124 | background: #51a6ef;
125 | }
126 |
127 | .ep-weekcalendar-category-title {
128 | font-size: 14px;
129 | line-height: 36px;
130 | }
131 |
132 | .ep-weekcalendar-category-list {
133 | list-style: none;
134 | margin: 0;
135 | padding: 0;
136 | }
137 |
138 | .ep-weekcalendar-category {
139 | box-sizing: border-box;
140 | height: 50px;
141 | .title,
142 | .content {
143 | display: block;
144 |
145 | box-sizing: border-box;
146 | height: 50%;
147 | padding-left: 20px;
148 |
149 | color: #333;
150 | }
151 | .content {
152 | color: #888;
153 | }
154 | }
155 |
156 |
157 | /* 日历中间 */
158 |
159 | .ep-weekcalendar-time-area {
160 | min-height: 100px;
161 | margin-left: 200px;
162 |
163 | background: #fdfdfd;
164 | }
165 |
166 |
167 | /* 头部星期 */
168 |
169 | .ep-weekcalendar-weeks {
170 | line-height: 36px;
171 |
172 | -webkit-box-sizing: border-box;
173 | -moz-box-sizing: border-box;
174 | box-sizing: border-box;
175 | height: 36px;
176 |
177 | text-align: center;
178 | }
179 |
180 | .ep-weekcalendar-weeks:after {
181 | display: table;
182 | clear: both;
183 |
184 | content: "";
185 | }
186 |
187 | .ep-weekcalendar-week {
188 | float: left;
189 | overflow: hidden;
190 |
191 | -webkit-box-sizing: border-box;
192 | -moz-box-sizing: border-box;
193 | box-sizing: border-box;
194 | width: 14.2857%;
195 |
196 | white-space: nowrap;
197 | text-overflow: ellipsis;
198 |
199 | -o-text-overflow: ellipsis;
200 |
201 | &.weekend {
202 | background: $weekend_bg;
203 | }
204 | }
205 |
206 | .ep-weekcalendar-main {
207 | position: relative;
208 | }
209 | /* 网格 */
210 |
211 | .ep-weekcalendar-grid {
212 | box-sizing: border-box;
213 | margin-top: -1px;
214 |
215 | @at-root {
216 | .ep-weekcalendar-grid-row {
217 | height: 50px;
218 | &:after {
219 | display: table;
220 | clear: both;
221 |
222 | content: "";
223 | }
224 | }
225 | .ep-weekcalendar-grid-item {
226 | display: block;
227 | float: left;
228 |
229 | box-sizing: border-box;
230 | width: 14.2857%;
231 | height: 100%;
232 | &.weekend {
233 | background: $weekend_bg;
234 | }
235 | &.selected {
236 | background: $selected_bg;
237 | }
238 | }
239 | }
240 | }
241 | /* content */
242 | .ep-weekcalendar-content {
243 | line-height: 24px;
244 |
245 | position: absolute;
246 | z-index: 1;
247 | top: 0;
248 | right: 0;
249 | bottom: 0;
250 | left: 0;
251 |
252 | @at-root {
253 | .ep-weekcalendar-content-row {
254 | position: relative;
255 |
256 | box-sizing: border-box;
257 | height: 50px;
258 |
259 | overflow: hidden;
260 | }
261 | .ep-weekcalendar-content-widget {
262 | position: absolute;
263 |
264 | box-sizing: border-box;
265 | height: 100%;
266 | padding: 0 5px;
267 |
268 | color: #fff;
269 | background-color: #6bb2ee;
270 | }
271 | }
272 | }
273 | // 边框处理
274 | .ep-weekcalendar.border {
275 | // 左侧列表
276 | .ep-weekcalendar-category-list {
277 | border-left: 1px solid $border_color; // border-bottom:1px solid $border_color;
278 | }
279 | .ep-weekcalendar-category {
280 | border-bottom: 1px solid $border_color;
281 | } // 星期
282 | .ep-weekcalendar-weeks {
283 | line-height: 34px;
284 |
285 | border: 1px solid $border_color;
286 | border-right: none;
287 | }
288 | .ep-weekcalendar-week {
289 | border-right: 1px solid $border_color;
290 | }
291 |
292 | /* 日历网格 */
293 | .ep-weekcalendar-grid {
294 | border-right: 1px solid $border_color;
295 | border-bottom: 1px solid $border_color;
296 | }
297 | .ep-weekcalendar-grid-item {
298 | border-top: 1px solid $border_color;
299 | border-left: 1px solid $border_color;
300 | }
301 |
302 | /* 内容行也需要一个透明边框 防止被内容盖住边框*/
303 | .ep-weekcalendar-content-row {
304 | border: 1px solid transparent;
305 | border-bottom: none;
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/lib/moment.min.js:
--------------------------------------------------------------------------------
1 | //! moment.js
2 | //! version : 2.10.6
3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
4 | //! license : MIT
5 | //! momentjs.com
6 | !function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Hc.apply(null,arguments)}function b(a){Hc=a}function c(a){return"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in Jc)d=Jc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function n(b){m(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),Kc===!1&&(Kc=!0,a.updateOffset(this),Kc=!1)}function o(a){return a instanceof n||null!=a&&null!=a._isAMomentObject}function p(a){return 0>a?Math.ceil(a):Math.floor(a)}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=p(b)),c}function r(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function s(){}function t(a){return a?a.toLowerCase().replace("_","-"):a}function u(a){for(var b,c,d,e,f=0;f0;){if(d=v(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&r(e,c,!0)>=b-1)break;b--}f++}return null}function v(a){var b=null;if(!Lc[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Ic._abbr,require("./locale/"+a),w(b)}catch(c){}return Lc[a]}function w(a,b){var c;return a&&(c="undefined"==typeof b?y(a):x(a,b),c&&(Ic=c)),Ic._abbr}function x(a,b){return null!==b?(b.abbr=a,Lc[a]=Lc[a]||new s,Lc[a].set(b),w(a),Lc[a]):(delete Lc[a],null)}function y(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Ic;if(!c(a)){if(b=v(a))return b;a=[a]}return u(a)}function z(a,b){var c=a.toLowerCase();Mc[c]=Mc[c+"s"]=Mc[b]=a}function A(a){return"string"==typeof a?Mc[a]||Mc[a.toLowerCase()]:void 0}function B(a){var b,c,d={};for(c in a)f(a,c)&&(b=A(c),b&&(d[b]=a[c]));return d}function C(b,c){return function(d){return null!=d?(E(this,b,d),a.updateOffset(this,c),this):D(this,b)}}function D(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function E(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function F(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=A(a),"function"==typeof this[a])return this[a](b);return this}function G(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function H(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Qc[a]=e),b&&(Qc[b[0]]=function(){return G(e.apply(this,arguments),b[1],b[2])}),c&&(Qc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function I(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function J(a){var b,c,d=a.match(Nc);for(b=0,c=d.length;c>b;b++)Qc[d[b]]?d[b]=Qc[d[b]]:d[b]=I(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function K(a,b){return a.isValid()?(b=L(b,a.localeData()),Pc[b]=Pc[b]||J(b),Pc[b](a)):a.localeData().invalidDate()}function L(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Oc.lastIndex=0;d>=0&&Oc.test(a);)a=a.replace(Oc,c),Oc.lastIndex=0,d-=1;return a}function M(a){return"function"==typeof a&&"[object Function]"===Object.prototype.toString.call(a)}function N(a,b,c){dd[a]=M(b)?b:function(a){return a&&c?c:b}}function O(a,b){return f(dd,a)?dd[a](b._strict,b._locale):new RegExp(P(a))}function P(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=q(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function X(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),T(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function Y(b){return null!=b?(X(this,b),a.updateOffset(this,!0),this):D(this,"Month")}function Z(){return T(this.year(),this.month())}function $(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[gd]<0||c[gd]>11?gd:c[hd]<1||c[hd]>T(c[fd],c[gd])?hd:c[id]<0||c[id]>24||24===c[id]&&(0!==c[jd]||0!==c[kd]||0!==c[ld])?id:c[jd]<0||c[jd]>59?jd:c[kd]<0||c[kd]>59?kd:c[ld]<0||c[ld]>999?ld:-1,j(a)._overflowDayOfYear&&(fd>b||b>hd)&&(b=hd),j(a).overflow=b),a}function _(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function aa(a,b){var c=!0;return g(function(){return c&&(_(a+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function ba(a,b){od[a]||(_(b),od[a]=!0)}function ca(a){var b,c,d=a._i,e=pd.exec(d);if(e){for(j(a).iso=!0,b=0,c=qd.length;c>b;b++)if(qd[b][1].exec(d)){a._f=qd[b][0];break}for(b=0,c=rd.length;c>b;b++)if(rd[b][1].exec(d)){a._f+=(e[6]||" ")+rd[b][0];break}d.match(ad)&&(a._f+="Z"),va(a)}else a._isValid=!1}function da(b){var c=sd.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ca(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ea(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function fa(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function ga(a){return ha(a)?366:365}function ha(a){return a%4===0&&a%100!==0||a%400===0}function ia(){return ha(this.year())}function ja(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=Da(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ka(a){return ja(a,this._week.dow,this._week.doy).week}function la(){return this._week.dow}function ma(){return this._week.doy}function na(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function oa(a){var b=ja(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function pa(a,b,c,d,e){var f,g=6+e-d,h=fa(a,0,1+g),i=h.getUTCDay();return e>i&&(i+=7),c=null!=c?1*c:e,f=1+g+7*(b-1)-i+c,{year:f>0?a:a-1,dayOfYear:f>0?f:ga(a-1)+f}}function qa(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ra(a,b,c){return null!=a?a:null!=b?b:c}function sa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function ta(a){var b,c,d,e,f=[];if(!a._d){for(d=sa(a),a._w&&null==a._a[hd]&&null==a._a[gd]&&ua(a),a._dayOfYear&&(e=ra(a._a[fd],d[fd]),a._dayOfYear>ga(e)&&(j(a)._overflowDayOfYear=!0),c=fa(e,0,a._dayOfYear),a._a[gd]=c.getUTCMonth(),a._a[hd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[id]&&0===a._a[jd]&&0===a._a[kd]&&0===a._a[ld]&&(a._nextDay=!0,a._a[id]=0),a._d=(a._useUTC?fa:ea).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[id]=24)}}function ua(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ra(b.GG,a._a[fd],ja(Da(),1,4).year),d=ra(b.W,1),e=ra(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ra(b.gg,a._a[fd],ja(Da(),f,g).year),d=ra(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=pa(c,d,e,g,f),a._a[fd]=h.year,a._dayOfYear=h.dayOfYear}function va(b){if(b._f===a.ISO_8601)return void ca(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=L(b._f,b._locale).match(Nc)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),Qc[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),S(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[id]<=12&&b._a[id]>0&&(j(b).bigHour=void 0),b._a[id]=wa(b._locale,b._a[id],b._meridiem),ta(b),$(b)}function wa(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function xa(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function ya(a){if(!a._d){var b=B(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],ta(a)}}function za(a){var b=new n($(Aa(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Aa(a){var b=a._i,e=a._f;return a._locale=a._locale||y(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),o(b)?new n($(b)):(c(e)?xa(a):e?va(a):d(b)?a._d=b:Ba(a),a))}function Ba(b){var f=b._i;void 0===f?b._d=new Date:d(f)?b._d=new Date(+f):"string"==typeof f?da(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),ta(b)):"object"==typeof f?ya(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ca(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,za(f)}function Da(a,b,c,d){return Ca(a,b,c,d,!1)}function Ea(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Da();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+G(~~(a/60),2)+b+G(~~a%60,2)})}function Ka(a){var b=(a||"").match(ad)||[],c=b[b.length-1]||[],d=(c+"").match(xd)||["-",0,0],e=+(60*d[1])+q(d[2]);return"+"===d[0]?e:-e}function La(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(o(b)||d(b)?+b:+Da(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Da(b).local()}function Ma(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Na(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ka(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ma(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?bb(this,Ya(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ma(this)}function Oa(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Pa(a){return this.utcOffset(0,a)}function Qa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ma(this),"m")),this}function Ra(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ka(this._i)),this}function Sa(a){return a=a?Da(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Ta(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ua(){if("undefined"!=typeof this._isDSTShifted)return this._isDSTShifted;var a={};if(m(a,this),a=Aa(a),a._a){var b=a._isUTC?h(a._a):Da(a._a);this._isDSTShifted=this.isValid()&&r(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Va(){return!this._isUTC}function Wa(){return this._isUTC}function Xa(){return this._isUTC&&0===this._offset}function Ya(a,b){var c,d,e,g=a,h=null;return Ia(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=yd.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:q(h[hd])*c,h:q(h[id])*c,m:q(h[jd])*c,s:q(h[kd])*c,ms:q(h[ld])*c}):(h=zd.exec(a))?(c="-"===h[1]?-1:1,g={y:Za(h[2],c),M:Za(h[3],c),d:Za(h[4],c),h:Za(h[5],c),m:Za(h[6],c),s:Za(h[7],c),w:Za(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=_a(Da(g.from),Da(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Ha(g),Ia(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function Za(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function $a(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function _a(a,b){var c;return b=La(b,a),a.isBefore(b)?c=$a(a,b):(c=$a(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function ab(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(ba(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ya(c,d),bb(this,e,a),this}}function bb(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&E(b,"Date",D(b,"Date")+g*d),h&&X(b,D(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function cb(a,b){var c=a||Da(),d=La(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(b&&b[f]||this.localeData().calendar(f,this,Da(c)))}function db(){return new n(this)}function eb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+this>+a):(c=o(a)?+a:+Da(a),c<+this.clone().startOf(b))}function fb(a,b){var c;return b=A("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=o(a)?a:Da(a),+a>+this):(c=o(a)?+a:+Da(a),+this.clone().endOf(b)b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function kb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function lb(){var a=this.clone().utc();return 0b;b++)if(this._weekdaysParse[b]||(c=Da([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Pb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Kb(a,this.localeData()),this.add(a-b,"d")):b}function Qb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Rb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Sb(a,b){H(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Tb(a,b){return b._meridiemParse}function Ub(a){return"p"===(a+"").toLowerCase().charAt(0)}function Vb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Wb(a,b){b[ld]=q(1e3*("0."+a))}function Xb(){return this._isUTC?"UTC":""}function Yb(){return this._isUTC?"Coordinated Universal Time":""}function Zb(a){return Da(1e3*a)}function $b(){return Da.apply(null,arguments).parseZone()}function _b(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function bc(){return this._invalidDate}function cc(a){return this._ordinal.replace("%d",a)}function dc(a){return a}function ec(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function gc(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function hc(a,b,c,d){var e=y(),f=h().set(d,b);return e[c](f,a)}function ic(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return hc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=hc(a,f,c,e);return g}function jc(a,b){return ic(a,b,"months",12,"month")}function kc(a,b){return ic(a,b,"monthsShort",12,"month")}function lc(a,b){return ic(a,b,"weekdays",7,"day")}function mc(a,b){return ic(a,b,"weekdaysShort",7,"day")}function nc(a,b){return ic(a,b,"weekdaysMin",7,"day")}function oc(){var a=this._data;return this._milliseconds=Wd(this._milliseconds),this._days=Wd(this._days),this._months=Wd(this._months),a.milliseconds=Wd(a.milliseconds),a.seconds=Wd(a.seconds),a.minutes=Wd(a.minutes),a.hours=Wd(a.hours),a.months=Wd(a.months),a.years=Wd(a.years),this}function pc(a,b,c,d){var e=Ya(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function qc(a,b){return pc(this,a,b,1)}function rc(a,b){return pc(this,a,b,-1)}function sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*sc(vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=p(f/1e3),i.seconds=a%60,b=p(a/60),i.minutes=b%60,c=p(b/60),i.hours=c%24,g+=p(c/24),e=p(uc(g)),h+=e,g-=sc(vc(e)),d=p(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function uc(a){return 4800*a/146097}function vc(a){return 146097*a/4800}function wc(a){var b,c,d=this._milliseconds;if(a=A(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*q(this._months/12)}function yc(a){return function(){return this.as(a)}}function zc(a){return a=A(a),this[a+"s"]()}function Ac(a){return function(){return this._data[a]}}function Bc(){return p(this.days()/7)}function Cc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Dc(a,b,c){var d=Ya(a).abs(),e=ke(d.as("s")),f=ke(d.as("m")),g=ke(d.as("h")),h=ke(d.as("d")),i=ke(d.as("M")),j=ke(d.as("y")),k=e0,k[4]=c,Cc.apply(null,k)}function Ec(a,b){return void 0===le[a]?!1:void 0===b?le[a]:(le[a]=b,!0)}function Fc(a){var b=this.localeData(),c=Dc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Gc(){var a,b,c,d=me(this._milliseconds)/1e3,e=me(this._days),f=me(this._months);a=p(d/60),b=p(a/60),d%=60,a%=60,c=p(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Hc,Ic,Jc=a.momentProperties=[],Kc=!1,Lc={},Mc={},Nc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,Oc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Pc={},Qc={},Rc=/\d/,Sc=/\d\d/,Tc=/\d{3}/,Uc=/\d{4}/,Vc=/[+-]?\d{6}/,Wc=/\d\d?/,Xc=/\d{1,3}/,Yc=/\d{1,4}/,Zc=/[+-]?\d{1,6}/,$c=/\d+/,_c=/[+-]?\d+/,ad=/Z|[+-]\d\d:?\d\d/gi,bd=/[+-]?\d+(\.\d{1,3})?/,cd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,dd={},ed={},fd=0,gd=1,hd=2,id=3,jd=4,kd=5,ld=6;H("M",["MM",2],"Mo",function(){return this.month()+1}),H("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),H("MMMM",0,0,function(a){return this.localeData().months(this,a)}),z("month","M"),N("M",Wc),N("MM",Wc,Sc),N("MMM",cd),N("MMMM",cd),Q(["M","MM"],function(a,b){b[gd]=q(a)-1}),Q(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[gd]=e:j(c).invalidMonth=a});var md="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),nd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),od={};a.suppressDeprecationWarnings=!1;var pd=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,qd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],rd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],sd=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=aa("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),H(0,["YY",2],0,function(){return this.year()%100}),H(0,["YYYY",4],0,"year"),H(0,["YYYYY",5],0,"year"),H(0,["YYYYYY",6,!0],0,"year"),z("year","y"),N("Y",_c),N("YY",Wc,Sc),N("YYYY",Yc,Uc),N("YYYYY",Zc,Vc),N("YYYYYY",Zc,Vc),Q(["YYYYY","YYYYYY"],fd),Q("YYYY",function(b,c){c[fd]=2===b.length?a.parseTwoDigitYear(b):q(b)}),Q("YY",function(b,c){c[fd]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return q(a)+(q(a)>68?1900:2e3)};var td=C("FullYear",!1);H("w",["ww",2],"wo","week"),H("W",["WW",2],"Wo","isoWeek"),z("week","w"),z("isoWeek","W"),N("w",Wc),N("ww",Wc,Sc),N("W",Wc),N("WW",Wc,Sc),R(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=q(a)});var ud={dow:0,doy:6};H("DDD",["DDDD",3],"DDDo","dayOfYear"),z("dayOfYear","DDD"),N("DDD",Xc),N("DDDD",Tc),Q(["DDD","DDDD"],function(a,b,c){c._dayOfYear=q(a)}),a.ISO_8601=function(){};var vd=aa("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return this>a?this:a}),wd=aa("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Da.apply(null,arguments);return a>this?this:a});Ja("Z",":"),Ja("ZZ",""),N("Z",ad),N("ZZ",ad),Q(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ka(a)});var xd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var yd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,zd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ya.fn=Ha.prototype;var Ad=ab(1,"add"),Bd=ab(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var Cd=aa("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});H(0,["gg",2],0,function(){return this.weekYear()%100}),H(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Db("gggg","weekYear"),Db("ggggg","weekYear"),Db("GGGG","isoWeekYear"),Db("GGGGG","isoWeekYear"),z("weekYear","gg"),z("isoWeekYear","GG"),N("G",_c),N("g",_c),N("GG",Wc,Sc),N("gg",Wc,Sc),N("GGGG",Yc,Uc),N("gggg",Yc,Uc),N("GGGGG",Zc,Vc),N("ggggg",Zc,Vc),R(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=q(a)}),R(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),H("Q",0,0,"quarter"),z("quarter","Q"),N("Q",Rc),Q("Q",function(a,b){b[gd]=3*(q(a)-1)}),H("D",["DD",2],"Do","date"),z("date","D"),N("D",Wc),N("DD",Wc,Sc),N("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),Q(["D","DD"],hd),Q("Do",function(a,b){b[hd]=q(a.match(Wc)[0],10)});var Dd=C("Date",!0);H("d",0,"do","day"),H("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),H("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),H("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),H("e",0,0,"weekday"),H("E",0,0,"isoWeekday"),z("day","d"),z("weekday","e"),z("isoWeekday","E"),N("d",Wc),N("e",Wc),N("E",Wc),N("dd",cd),N("ddd",cd),N("dddd",cd),R(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:j(c).invalidWeekday=a}),R(["d","e","E"],function(a,b,c,d){b[d]=q(a)});var Ed="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Fd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Gd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");H("H",["HH",2],0,"hour"),H("h",["hh",2],0,function(){return this.hours()%12||12}),Sb("a",!0),Sb("A",!1),z("hour","h"),N("a",Tb),N("A",Tb),N("H",Wc),N("h",Wc),N("HH",Wc,Sc),N("hh",Wc,Sc),Q(["H","HH"],id),Q(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),Q(["h","hh"],function(a,b,c){b[id]=q(a),j(c).bigHour=!0});var Hd=/[ap]\.?m?\.?/i,Id=C("Hours",!0);H("m",["mm",2],0,"minute"),z("minute","m"),N("m",Wc),N("mm",Wc,Sc),Q(["m","mm"],jd);var Jd=C("Minutes",!1);H("s",["ss",2],0,"second"),z("second","s"),N("s",Wc),N("ss",Wc,Sc),Q(["s","ss"],kd);var Kd=C("Seconds",!1);H("S",0,0,function(){return~~(this.millisecond()/100)}),H(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),H(0,["SSS",3],0,"millisecond"),H(0,["SSSS",4],0,function(){return 10*this.millisecond()}),H(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),H(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),H(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),H(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),H(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),z("millisecond","ms"),N("S",Xc,Rc),N("SS",Xc,Sc),N("SSS",Xc,Tc);var Ld;for(Ld="SSSS";Ld.length<=9;Ld+="S")N(Ld,$c);for(Ld="S";Ld.length<=9;Ld+="S")Q(Ld,Wb);var Md=C("Milliseconds",!1);H("z",0,0,"zoneAbbr"),H("zz",0,0,"zoneName");var Nd=n.prototype;Nd.add=Ad,Nd.calendar=cb,Nd.clone=db,Nd.diff=ib,Nd.endOf=ub,Nd.format=mb,Nd.from=nb,Nd.fromNow=ob,Nd.to=pb,Nd.toNow=qb,Nd.get=F,Nd.invalidAt=Cb,Nd.isAfter=eb,Nd.isBefore=fb,Nd.isBetween=gb,Nd.isSame=hb,Nd.isValid=Ab,Nd.lang=Cd,Nd.locale=rb,Nd.localeData=sb,Nd.max=wd,Nd.min=vd,Nd.parsingFlags=Bb,Nd.set=F,Nd.startOf=tb,Nd.subtract=Bd,Nd.toArray=yb,Nd.toObject=zb,Nd.toDate=xb,Nd.toISOString=lb,Nd.toJSON=lb,Nd.toString=kb,Nd.unix=wb,Nd.valueOf=vb,Nd.year=td,Nd.isLeapYear=ia,Nd.weekYear=Fb,Nd.isoWeekYear=Gb,Nd.quarter=Nd.quarters=Jb,Nd.month=Y,Nd.daysInMonth=Z,Nd.week=Nd.weeks=na,Nd.isoWeek=Nd.isoWeeks=oa,Nd.weeksInYear=Ib,Nd.isoWeeksInYear=Hb,Nd.date=Dd,Nd.day=Nd.days=Pb,Nd.weekday=Qb,Nd.isoWeekday=Rb,Nd.dayOfYear=qa,Nd.hour=Nd.hours=Id,Nd.minute=Nd.minutes=Jd,Nd.second=Nd.seconds=Kd,
7 | Nd.millisecond=Nd.milliseconds=Md,Nd.utcOffset=Na,Nd.utc=Pa,Nd.local=Qa,Nd.parseZone=Ra,Nd.hasAlignedHourOffset=Sa,Nd.isDST=Ta,Nd.isDSTShifted=Ua,Nd.isLocal=Va,Nd.isUtcOffset=Wa,Nd.isUtc=Xa,Nd.isUTC=Xa,Nd.zoneAbbr=Xb,Nd.zoneName=Yb,Nd.dates=aa("dates accessor is deprecated. Use date instead.",Dd),Nd.months=aa("months accessor is deprecated. Use month instead",Y),Nd.years=aa("years accessor is deprecated. Use year instead",td),Nd.zone=aa("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Oa);var Od=Nd,Pd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Qd={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Rd="Invalid date",Sd="%d",Td=/\d{1,2}/,Ud={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Vd=s.prototype;Vd._calendar=Pd,Vd.calendar=_b,Vd._longDateFormat=Qd,Vd.longDateFormat=ac,Vd._invalidDate=Rd,Vd.invalidDate=bc,Vd._ordinal=Sd,Vd.ordinal=cc,Vd._ordinalParse=Td,Vd.preparse=dc,Vd.postformat=dc,Vd._relativeTime=Ud,Vd.relativeTime=ec,Vd.pastFuture=fc,Vd.set=gc,Vd.months=U,Vd._months=md,Vd.monthsShort=V,Vd._monthsShort=nd,Vd.monthsParse=W,Vd.week=ka,Vd._week=ud,Vd.firstDayOfYear=ma,Vd.firstDayOfWeek=la,Vd.weekdays=Lb,Vd._weekdays=Ed,Vd.weekdaysMin=Nb,Vd._weekdaysMin=Gd,Vd.weekdaysShort=Mb,Vd._weekdaysShort=Fd,Vd.weekdaysParse=Ob,Vd.isPM=Ub,Vd._meridiemParse=Hd,Vd.meridiem=Vb,w("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=aa("moment.lang is deprecated. Use moment.locale instead.",w),a.langData=aa("moment.langData is deprecated. Use moment.localeData instead.",y);var Wd=Math.abs,Xd=yc("ms"),Yd=yc("s"),Zd=yc("m"),$d=yc("h"),_d=yc("d"),ae=yc("w"),be=yc("M"),ce=yc("y"),de=Ac("milliseconds"),ee=Ac("seconds"),fe=Ac("minutes"),ge=Ac("hours"),he=Ac("days"),ie=Ac("months"),je=Ac("years"),ke=Math.round,le={s:45,m:45,h:22,d:26,M:11},me=Math.abs,ne=Ha.prototype;ne.abs=oc,ne.add=qc,ne.subtract=rc,ne.as=wc,ne.asMilliseconds=Xd,ne.asSeconds=Yd,ne.asMinutes=Zd,ne.asHours=$d,ne.asDays=_d,ne.asWeeks=ae,ne.asMonths=be,ne.asYears=ce,ne.valueOf=xc,ne._bubble=tc,ne.get=zc,ne.milliseconds=de,ne.seconds=ee,ne.minutes=fe,ne.hours=ge,ne.days=he,ne.weeks=Bc,ne.months=ie,ne.years=je,ne.humanize=Fc,ne.toISOString=Gc,ne.toString=Gc,ne.toJSON=Gc,ne.locale=rb,ne.localeData=sb,ne.toIsoString=aa("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Gc),ne.lang=Cd,H("X",0,0,"unix"),H("x",0,0,"valueOf"),N("x",_c),N("X",bd),Q("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),Q("x",function(a,b,c){c._d=new Date(q(a))}),a.version="2.10.6",b(Da),a.fn=Od,a.min=Fa,a.max=Ga,a.utc=h,a.unix=Zb,a.months=jc,a.isDate=d,a.locale=w,a.invalid=l,a.duration=Ya,a.isMoment=o,a.weekdays=lc,a.parseZone=$b,a.localeData=y,a.isDuration=Ia,a.monthsShort=kc,a.weekdaysMin=nc,a.defineLocale=x,a.weekdaysShort=mc,a.normalizeUnits=A,a.relativeTimeThreshold=Ec;var oe=a;return oe});
--------------------------------------------------------------------------------