网站
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

639 lines
15 KiB

  1. /*!
  2. * PopupJS
  3. * Date: 2014-11-09
  4. * https://github.com/aui/popupjs
  5. * (c) 2009-2014 TangBin, http://www.planeArt.cn
  6. *
  7. * This is licensed under the GNU LGPL, version 2.1 or later.
  8. * For details, see: http://www.gnu.org/licenses/lgpl-2.1.html
  9. */
  10. define(function (require) {
  11. var $ = require('jquery');
  12. var _count = 0;
  13. var _isIE6 = !('minWidth' in $('html')[0].style);
  14. var _isFixed = !_isIE6;
  15. function Popup () {
  16. this.destroyed = false;
  17. this.__popup = $('<div />')
  18. /*使用 <dialog /> 元素可能导致 z-index 永远置顶的问题(chrome)*/
  19. .css({
  20. display: 'none',
  21. position: 'absolute',
  22. /*
  23. left: 0,
  24. top: 0,
  25. bottom: 'auto',
  26. right: 'auto',
  27. margin: 0,
  28. padding: 0,
  29. border: '0 none',
  30. background: 'transparent'
  31. */
  32. outline: 0
  33. })
  34. .attr('tabindex', '-1')
  35. .html(this.innerHTML)
  36. .appendTo('body');
  37. this.__backdrop = this.__mask = $('<div />')
  38. .css({
  39. opacity: .7,
  40. background: '#000'
  41. });
  42. // 使用 HTMLElement 作为外部接口使用,而不是 jquery 对象
  43. // 统一的接口利于未来 Popup 移植到其他 DOM 库中
  44. this.node = this.__popup[0];
  45. this.backdrop = this.__backdrop[0];
  46. _count ++;
  47. }
  48. $.extend(Popup.prototype, {
  49. /**
  50. * 初始化完毕事件,在 show()、showModal() 执行
  51. * @name Popup.prototype.onshow
  52. * @event
  53. */
  54. /**
  55. * 关闭事件,在 close() 执行
  56. * @name Popup.prototype.onclose
  57. * @event
  58. */
  59. /**
  60. * 销毁前事件,在 remove() 前执行
  61. * @name Popup.prototype.onbeforeremove
  62. * @event
  63. */
  64. /**
  65. * 销毁事件,在 remove() 执行
  66. * @name Popup.prototype.onremove
  67. * @event
  68. */
  69. /**
  70. * 重置事件,在 reset() 执行
  71. * @name Popup.prototype.onreset
  72. * @event
  73. */
  74. /**
  75. * 焦点事件,在 foucs() 执行
  76. * @name Popup.prototype.onfocus
  77. * @event
  78. */
  79. /**
  80. * 失焦事件,在 blur() 执行
  81. * @name Popup.prototype.onblur
  82. * @event
  83. */
  84. /** 浮层 DOM 素节点[*] */
  85. node: null,
  86. /** 遮罩 DOM 节点[*] */
  87. backdrop: null,
  88. /** 是否开启固定定位[*] */
  89. fixed: false,
  90. /** 判断对话框是否删除[*] */
  91. destroyed: true,
  92. /** 判断对话框是否显示 */
  93. open: false,
  94. /** close 返回值 */
  95. returnValue: '',
  96. /** 是否自动聚焦 */
  97. autofocus: true,
  98. /** 对齐方式[*] */
  99. align: 'bottom left',
  100. /** 内部的 HTML 字符串 */
  101. innerHTML: '',
  102. /** CSS 类名 */
  103. className: 'ui-popup',
  104. /**
  105. * 显示浮层
  106. * @param {HTMLElement, Event} 指定位置(可选)
  107. */
  108. show: function (anchor) {
  109. if (this.destroyed) {
  110. return this;
  111. }
  112. var that = this;
  113. var popup = this.__popup;
  114. var backdrop = this.__backdrop;
  115. this.__activeElement = this.__getActive();
  116. this.open = true;
  117. this.follow = anchor || this.follow;
  118. // 初始化 show 方法
  119. if (!this.__ready) {
  120. popup
  121. .addClass(this.className)
  122. .attr('role', this.modal ? 'alertdialog' : 'dialog')
  123. .css('position', this.fixed ? 'fixed' : 'absolute');
  124. if (!_isIE6) {
  125. $(window).on('resize', $.proxy(this.reset, this));
  126. }
  127. // 模态浮层的遮罩
  128. if (this.modal) {
  129. var backdropCss = {
  130. position: 'fixed',
  131. left: 0,
  132. top: 0,
  133. width: '100%',
  134. height: '100%',
  135. overflow: 'hidden',
  136. userSelect: 'none',
  137. zIndex: this.zIndex || Popup.zIndex
  138. };
  139. popup.addClass(this.className + '-modal');
  140. if (!_isFixed) {
  141. $.extend(backdropCss, {
  142. position: 'absolute',
  143. width: $(window).width() + 'px',
  144. height: $(document).height() + 'px'
  145. });
  146. }
  147. backdrop
  148. .css(backdropCss)
  149. .attr({tabindex: '0'})
  150. .on('focus', $.proxy(this.focus, this));
  151. // 锁定 tab 的焦点操作
  152. this.__mask = backdrop
  153. .clone(true)
  154. .attr('style', '')
  155. .insertAfter(popup);
  156. backdrop
  157. .addClass(this.className + '-backdrop')
  158. .insertBefore(popup);
  159. this.__ready = true;
  160. }
  161. if (!popup.html()) {
  162. popup.html(this.innerHTML);
  163. }
  164. }
  165. popup
  166. .addClass(this.className + '-show')
  167. .show();
  168. backdrop.show();
  169. this.reset().focus();
  170. this.__dispatchEvent('show');
  171. return this;
  172. },
  173. /** 显示模态浮层。参数参见 show() */
  174. showModal: function () {
  175. this.modal = true;
  176. return this.show.apply(this, arguments);
  177. },
  178. /** 关闭浮层 */
  179. close: function (result) {
  180. if (!this.destroyed && this.open) {
  181. if (result !== undefined) {
  182. this.returnValue = result;
  183. }
  184. this.__popup.hide().removeClass(this.className + '-show');
  185. this.__backdrop.hide();
  186. this.open = false;
  187. this.blur();// 恢复焦点,照顾键盘操作的用户
  188. this.__dispatchEvent('close');
  189. }
  190. return this;
  191. },
  192. /** 销毁浮层 */
  193. remove: function () {
  194. if (this.destroyed) {
  195. return this;
  196. }
  197. this.__dispatchEvent('beforeremove');
  198. if (Popup.current === this) {
  199. Popup.current = null;
  200. }
  201. // 从 DOM 中移除节点
  202. this.__popup.remove();
  203. this.__backdrop.remove();
  204. this.__mask.remove();
  205. if (!_isIE6) {
  206. $(window).off('resize', this.reset);
  207. }
  208. this.__dispatchEvent('remove');
  209. for (var i in this) {
  210. delete this[i];
  211. }
  212. return this;
  213. },
  214. /** 重置位置 */
  215. reset: function () {
  216. var elem = this.follow;
  217. if (elem) {
  218. this.__follow(elem);
  219. } else {
  220. this.__center();
  221. }
  222. this.__dispatchEvent('reset');
  223. return this;
  224. },
  225. /** 让浮层获取焦点 */
  226. focus: function () {
  227. var node = this.node;
  228. var popup = this.__popup;
  229. var current = Popup.current;
  230. var index = this.zIndex = Popup.zIndex ++;
  231. if (current && current !== this) {
  232. current.blur(false);
  233. }
  234. // 检查焦点是否在浮层里面
  235. if (!$.contains(node, this.__getActive())) {
  236. var autofocus = popup.find('[autofocus]')[0];
  237. if (!this._autofocus && autofocus) {
  238. this._autofocus = true;
  239. } else {
  240. autofocus = node;
  241. }
  242. this.__focus(autofocus);
  243. }
  244. // 设置叠加高度
  245. popup.css('zIndex', index);
  246. //this.__backdrop.css('zIndex', index);
  247. Popup.current = this;
  248. popup.addClass(this.className + '-focus');
  249. this.__dispatchEvent('focus');
  250. return this;
  251. },
  252. /** 让浮层失去焦点。将焦点退还给之前的元素,照顾视力障碍用户 */
  253. blur: function () {
  254. var activeElement = this.__activeElement;
  255. var isBlur = arguments[0];
  256. if (isBlur !== false) {
  257. this.__focus(activeElement);
  258. }
  259. this._autofocus = false;
  260. this.__popup.removeClass(this.className + '-focus');
  261. this.__dispatchEvent('blur');
  262. return this;
  263. },
  264. /**
  265. * 添加事件
  266. * @param {String} 事件类型
  267. * @param {Function} 监听函数
  268. */
  269. addEventListener: function (type, callback) {
  270. this.__getEventListener(type).push(callback);
  271. return this;
  272. },
  273. /**
  274. * 删除事件
  275. * @param {String} 事件类型
  276. * @param {Function} 监听函数
  277. */
  278. removeEventListener: function (type, callback) {
  279. var listeners = this.__getEventListener(type);
  280. for (var i = 0; i < listeners.length; i ++) {
  281. if (callback === listeners[i]) {
  282. listeners.splice(i--, 1);
  283. }
  284. }
  285. return this;
  286. },
  287. // 获取事件缓存
  288. __getEventListener: function (type) {
  289. var listener = this.__listener;
  290. if (!listener) {
  291. listener = this.__listener = {};
  292. }
  293. if (!listener[type]) {
  294. listener[type] = [];
  295. }
  296. return listener[type];
  297. },
  298. // 派发事件
  299. __dispatchEvent: function (type) {
  300. var listeners = this.__getEventListener(type);
  301. if (this['on' + type]) {
  302. this['on' + type]();
  303. }
  304. for (var i = 0; i < listeners.length; i ++) {
  305. listeners[i].call(this);
  306. }
  307. },
  308. // 对元素安全聚焦
  309. __focus: function (elem) {
  310. // 防止 iframe 跨域无权限报错
  311. // 防止 IE 不可见元素报错
  312. try {
  313. // ie11 bug: iframe 页面点击会跳到顶部
  314. if (this.autofocus && !/^iframe$/i.test(elem.nodeName)) {
  315. elem.focus();
  316. }
  317. } catch (e) {}
  318. },
  319. // 获取当前焦点的元素
  320. __getActive: function () {
  321. try {// try: ie8~9, iframe #26
  322. var activeElement = document.activeElement;
  323. var contentDocument = activeElement.contentDocument;
  324. var elem = contentDocument && contentDocument.activeElement || activeElement;
  325. return elem;
  326. } catch (e) {}
  327. },
  328. // 居中浮层
  329. __center: function () {
  330. var popup = this.__popup;
  331. var $window = $(window);
  332. var $document = $(document);
  333. var fixed = this.fixed;
  334. var dl = fixed ? 0 : $document.scrollLeft();
  335. var dt = fixed ? 0 : $document.scrollTop();
  336. var ww = $window.width();
  337. var wh = $window.height();
  338. var ow = popup.width();
  339. var oh = popup.height();
  340. var left = (ww - ow) / 2 + dl;
  341. var top = (wh - oh) * 382 / 1000 + dt;// 黄金比例
  342. var style = popup[0].style;
  343. style.left = Math.max(parseInt(left), dl) + 'px';
  344. style.top = Math.max(parseInt(top), dt) + 'px';
  345. },
  346. // 指定位置 @param {HTMLElement, Event} anchor
  347. __follow: function (anchor) {
  348. var $elem = anchor.parentNode && $(anchor);
  349. var popup = this.__popup;
  350. if (this.__followSkin) {
  351. popup.removeClass(this.__followSkin);
  352. }
  353. // 隐藏元素不可用
  354. if ($elem) {
  355. var o = $elem.offset();
  356. if (o.left * o.top < 0) {
  357. return this.__center();
  358. }
  359. }
  360. var that = this;
  361. var fixed = this.fixed;
  362. var $window = $(window);
  363. var $document = $(document);
  364. var winWidth = $window.width();
  365. var winHeight = $window.height();
  366. var docLeft = $document.scrollLeft();
  367. var docTop = $document.scrollTop();
  368. var popupWidth = popup.width();
  369. var popupHeight = popup.height();
  370. var width = $elem ? $elem.outerWidth() : 0;
  371. var height = $elem ? $elem.outerHeight() : 0;
  372. var offset = this.__offset(anchor);
  373. var x = offset.left;
  374. var y = offset.top;
  375. var left = fixed ? x - docLeft : x;
  376. var top = fixed ? y - docTop : y;
  377. var minLeft = fixed ? 0 : docLeft;
  378. var minTop = fixed ? 0 : docTop;
  379. var maxLeft = minLeft + winWidth - popupWidth;
  380. var maxTop = minTop + winHeight - popupHeight;
  381. var css = {};
  382. var align = this.align.split(' ');
  383. var className = this.className + '-';
  384. var reverse = {top: 'bottom', bottom: 'top', left: 'right', right: 'left'};
  385. var name = {top: 'top', bottom: 'top', left: 'left', right: 'left'};
  386. var temp = [{
  387. top: top - popupHeight,
  388. bottom: top + height,
  389. left: left - popupWidth,
  390. right: left + width
  391. }, {
  392. top: top,
  393. bottom: top - popupHeight + height,
  394. left: left,
  395. right: left - popupWidth + width
  396. }];
  397. var center = {
  398. left: left + width / 2 - popupWidth / 2,
  399. top: top + height / 2 - popupHeight / 2
  400. };
  401. var range = {
  402. left: [minLeft, maxLeft],
  403. top: [minTop, maxTop]
  404. };
  405. // 超出可视区域重新适应位置
  406. $.each(align, function (i, val) {
  407. // 超出右或下边界:使用左或者上边对齐
  408. if (temp[i][val] > range[name[val]][1]) {
  409. val = align[i] = reverse[val];
  410. }
  411. // 超出左或右边界:使用右或者下边对齐
  412. if (temp[i][val] < range[name[val]][0]) {
  413. align[i] = reverse[val];
  414. }
  415. });
  416. // 一个参数的情况
  417. if (!align[1]) {
  418. name[align[1]] = name[align[0]] === 'left' ? 'top' : 'left';
  419. temp[1][align[1]] = center[name[align[1]]];
  420. }
  421. //添加follow的css, 为了给css使用
  422. className += align.join('-') + ' '+ this.className+ '-follow';
  423. that.__followSkin = className;
  424. if ($elem) {
  425. popup.addClass(className);
  426. }
  427. css[name[align[0]]] = parseInt(temp[0][align[0]]);
  428. css[name[align[1]]] = parseInt(temp[1][align[1]]);
  429. popup.css(css);
  430. },
  431. // 获取元素相对于页面的位置(包括iframe内的元素)
  432. // 暂时不支持两层以上的 iframe 套嵌
  433. __offset: function (anchor) {
  434. var isNode = anchor.parentNode;
  435. var offset = isNode ? $(anchor).offset() : {
  436. left: anchor.pageX,
  437. top: anchor.pageY
  438. };
  439. anchor = isNode ? anchor : anchor.target;
  440. var ownerDocument = anchor.ownerDocument;
  441. var defaultView = ownerDocument.defaultView || ownerDocument.parentWindow;
  442. if (defaultView == window) {// IE <= 8 只能使用两个等于号
  443. return offset;
  444. }
  445. // {Element: Ifarme}
  446. var frameElement = defaultView.frameElement;
  447. var $ownerDocument = $(ownerDocument);
  448. var docLeft = $ownerDocument.scrollLeft();
  449. var docTop = $ownerDocument.scrollTop();
  450. var frameOffset = $(frameElement).offset();
  451. var frameLeft = frameOffset.left;
  452. var frameTop = frameOffset.top;
  453. return {
  454. left: offset.left + frameLeft - docLeft,
  455. top: offset.top + frameTop - docTop
  456. };
  457. }
  458. });
  459. /** 当前叠加高度 */
  460. Popup.zIndex = 1024;
  461. /** 顶层浮层的实例 */
  462. Popup.current = null;
  463. return Popup;
  464. });