popover.js 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. /*
  2. * webui popover plugin - v1.2.17
  3. * A lightWeight popover plugin with jquery ,enchance the popover plugin of bootstrap with some awesome new features. It works well with bootstrap ,but bootstrap is not necessary!
  4. * https://github.com/sandywalker/webui-popover
  5. *
  6. * Made by Sandy Duan
  7. * Under MIT License
  8. */
  9. layui.define(['jquery', 'element'], function(exports) {
  10. var $=layui.$;
  11. // Create the defaults once
  12. var pluginName = 'webuiPopover';
  13. var pluginClass = 'webui-popover';
  14. var pluginType = 'webui.popover';
  15. var defaults = {
  16. placement: 'auto',
  17. container: null,
  18. width: 'auto',
  19. height: 'auto',
  20. trigger: 'click', //hover,click,sticky,manual
  21. style: '',
  22. opacity:null,
  23. selector: false, // jQuery selector, if a selector is provided, popover objects will be delegated to the specified.
  24. delay: {
  25. show: null,
  26. hide: 300
  27. },
  28. async: {
  29. type: 'GET',
  30. before: null, //function(that, xhr, settings){}
  31. success: null, //function(that, xhr){}
  32. error: null //function(that, xhr, data){}
  33. },
  34. cache: true,
  35. multi: false,
  36. arrow: true,
  37. title: '',
  38. content: '',
  39. closeable: false,
  40. padding: true,
  41. url: '',
  42. type: 'html',
  43. direction: '', // ltr,rtl
  44. animation: null,
  45. template: '<div class="webui-popover">' +
  46. '<div class="webui-arrow"></div>' +
  47. '<div class="webui-popover-inner">' +
  48. '<a href="#" class="close"></a>' +
  49. '<h3 class="webui-popover-title"></h3>' +
  50. '<div class="webui-popover-content"><i class="icon-refresh"></i> <p>&nbsp;</p></div>' +
  51. '</div>' +
  52. '</div>',
  53. backdrop: false,
  54. dismissible: true,
  55. onShow: null,
  56. onHide: null,
  57. abortXHR: true,
  58. autoHide: false,
  59. offsetTop: 0,
  60. offsetLeft: 0,
  61. iframeOptions: {
  62. frameborder: '0',
  63. allowtransparency: 'true',
  64. id: '',
  65. name: '',
  66. scrolling: '',
  67. onload: '',
  68. height: '',
  69. width: ''
  70. },
  71. hideEmpty: false
  72. };
  73. var rtlClass = pluginClass + '-rtl';
  74. var _srcElements = [];
  75. var backdrop = $('<div class="webui-popover-backdrop"></div>');
  76. var _globalIdSeed = 0;
  77. var _isBodyEventHandled = false;
  78. var _offsetOut = -2000; // the value offset out of the screen
  79. var $document = $(document);
  80. var toNumber = function (numeric, fallback) {
  81. return isNaN(numeric) ? (fallback || 0) : Number(numeric);
  82. };
  83. var getPopFromElement = function ($element) {
  84. return $element.data('plugin_' + pluginName);
  85. };
  86. var hideAllPop = function () {
  87. var pop = null;
  88. for (var i = 0; i < _srcElements.length; i++) {
  89. pop = getPopFromElement(_srcElements[i]);
  90. if (pop) {
  91. pop.hide(true);
  92. }
  93. }
  94. $document.trigger('hiddenAll.' + pluginType);
  95. };
  96. var hideOtherPops = function (currentPop) {
  97. var pop = null;
  98. for (var i = 0; i < _srcElements.length; i++) {
  99. pop = getPopFromElement(_srcElements[i]);
  100. if (pop && pop.id !== currentPop.id) {
  101. pop.hide(true);
  102. }
  103. }
  104. $document.trigger('hiddenAll.' + pluginType);
  105. };
  106. var isMobile = ('ontouchstart' in document.documentElement) && (/Mobi/.test(navigator.userAgent));
  107. var pointerEventToXY = function (e) {
  108. var out = {
  109. x: 0,
  110. y: 0
  111. };
  112. if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
  113. var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
  114. out.x = touch.pageX;
  115. out.y = touch.pageY;
  116. } else if (e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'click') {
  117. out.x = e.pageX;
  118. out.y = e.pageY;
  119. }
  120. return out;
  121. };
  122. // The actual plugin constructor
  123. function WebuiPopover(element, options) {
  124. this.$element = $(element);
  125. if (options) {
  126. if ($.type(options.delay) === 'string' || $.type(options.delay) === 'number') {
  127. options.delay = {
  128. show: options.delay,
  129. hide: options.delay
  130. }; // bc break fix
  131. }
  132. }
  133. this.options = $.extend({}, defaults, options);
  134. this._defaults = defaults;
  135. this._name = pluginName;
  136. this._targetclick = false;
  137. this.init();
  138. _srcElements.push(this.$element);
  139. return this;
  140. }
  141. WebuiPopover.prototype = {
  142. //init webui popover
  143. init: function () {
  144. if (this.$element[0] instanceof document.constructor && !this.options.selector) {
  145. throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!');
  146. }
  147. if (this.getTrigger() !== 'manual') {
  148. //init the event handlers
  149. if (isMobile) {
  150. this.$element.off('touchend', this.options.selector).on('touchend', this.options.selector, $.proxy(this.toggle, this));
  151. } else if (this.getTrigger() === 'click') {
  152. this.$element.off('click', this.options.selector).on('click', this.options.selector, $.proxy(this.toggle, this));
  153. } else if (this.getTrigger() === 'hover') {
  154. this.$element
  155. .off('mouseenter mouseleave click', this.options.selector)
  156. .on('mouseenter', this.options.selector, $.proxy(this.mouseenterHandler, this))
  157. .on('mouseleave', this.options.selector, $.proxy(this.mouseleaveHandler, this));
  158. }
  159. }
  160. this._poped = false;
  161. this._inited = true;
  162. this._opened = false;
  163. this._idSeed = _globalIdSeed;
  164. this.id = pluginName + this._idSeed;
  165. // normalize container
  166. this.options.container = $(this.options.container || document.body).first();
  167. if (this.options.backdrop) {
  168. backdrop.appendTo(this.options.container).hide();
  169. }
  170. _globalIdSeed++;
  171. if (this.getTrigger() === 'sticky') {
  172. this.show();
  173. }
  174. if (this.options.selector) {
  175. this._options = $.extend({}, this.options, {
  176. selector: ''
  177. });
  178. }
  179. },
  180. /* api methods and actions */
  181. destroy: function () {
  182. var index = -1;
  183. for (var i = 0; i < _srcElements.length; i++) {
  184. if (_srcElements[i] === this.$element) {
  185. index = i;
  186. break;
  187. }
  188. }
  189. _srcElements.splice(index, 1);
  190. this.hide();
  191. this.$element.data('plugin_' + pluginName, null);
  192. if (this.getTrigger() === 'click') {
  193. this.$element.off('click');
  194. } else if (this.getTrigger() === 'hover') {
  195. this.$element.off('mouseenter mouseleave');
  196. }
  197. if (this.$target) {
  198. this.$target.remove();
  199. }
  200. },
  201. getDelegateOptions: function () {
  202. var options = {};
  203. this._options && $.each(this._options, function (key, value) {
  204. if (defaults[key] !== value) {
  205. options[key] = value;
  206. }
  207. });
  208. return options;
  209. },
  210. /*
  211. param: force boolean value, if value is true then force hide the popover
  212. param: event dom event,
  213. */
  214. hide: function (force, event) {
  215. if (!force && this.getTrigger() === 'sticky') {
  216. return;
  217. }
  218. if (!this._opened) {
  219. return;
  220. }
  221. if (event) {
  222. event.preventDefault();
  223. event.stopPropagation();
  224. }
  225. if (this.xhr && this.options.abortXHR === true) {
  226. this.xhr.abort();
  227. this.xhr = null;
  228. }
  229. var e = $.Event('hide.' + pluginType);
  230. this.$element.trigger(e, [this.$target]);
  231. if (this.$target) {
  232. this.$target.removeClass('in').addClass(this.getHideAnimation());
  233. var that = this;
  234. setTimeout(function () {
  235. that.$target.hide();
  236. if (!that.getCache()) {
  237. that.$target.remove();
  238. //that.getTriggerElement.removeAttr('data-target');
  239. }
  240. }, that.getHideDelay());
  241. }
  242. if (this.options.backdrop) {
  243. backdrop.hide();
  244. }
  245. this._opened = false;
  246. this.$element.trigger('hidden.' + pluginType, [this.$target]);
  247. if (this.options.onHide) {
  248. this.options.onHide(this.$target);
  249. }
  250. },
  251. resetAutoHide: function () {
  252. var that = this;
  253. var autoHide = that.getAutoHide();
  254. if (autoHide) {
  255. if (that.autoHideHandler) {
  256. clearTimeout(that.autoHideHandler);
  257. }
  258. that.autoHideHandler = setTimeout(function () {
  259. that.hide();
  260. }, autoHide);
  261. }
  262. },
  263. delegate: function (eventTarget) {
  264. var self = $(eventTarget).data('plugin_' + pluginName);
  265. if (!self) {
  266. self = new WebuiPopover(eventTarget, this.getDelegateOptions());
  267. $(eventTarget).data('plugin_' + pluginName, self);
  268. }
  269. return self;
  270. },
  271. toggle: function (e) {
  272. var self = this;
  273. if (e) {
  274. e.preventDefault();
  275. e.stopPropagation();
  276. if (this.options.selector) {
  277. self = this.delegate(e.currentTarget);
  278. }
  279. }
  280. self[self.getTarget().hasClass('in') ? 'hide' : 'show']();
  281. },
  282. hideAll: function () {
  283. hideAllPop();
  284. },
  285. hideOthers: function () {
  286. hideOtherPops(this);
  287. },
  288. /*core method ,show popover */
  289. show: function () {
  290. if (this._opened) {
  291. return;
  292. }
  293. //removeAllTargets();
  294. var
  295. $target = this.getTarget().removeClass().addClass(pluginClass).addClass(this._customTargetClass);
  296. if (!this.options.multi) {
  297. this.hideOthers();
  298. }
  299. // use cache by default, if not cache setted , reInit the contents
  300. if (!this.getCache() || !this._poped || this.content === '') {
  301. this.content = '';
  302. this.setTitle(this.getTitle());
  303. if (!this.options.closeable) {
  304. $target.find('.close').off('click').remove();
  305. }
  306. if (!this.isAsync()) {
  307. this.setContent(this.getContent());
  308. } else {
  309. this.setContentASync(this.options.content);
  310. }
  311. if (this.canEmptyHide() && this.content === '') {
  312. return;
  313. }
  314. $target.show();
  315. }
  316. this.displayContent();
  317. if (this.options.onShow) {
  318. this.options.onShow($target);
  319. }
  320. this.bindBodyEvents();
  321. if (this.options.backdrop) {
  322. backdrop.show();
  323. }
  324. this._opened = true;
  325. this.resetAutoHide();
  326. },
  327. displayContent: function () {
  328. var
  329. //element postion
  330. elementPos = this.getElementPosition(),
  331. //target postion
  332. $target = this.getTarget().removeClass().addClass(pluginClass).addClass(this._customTargetClass),
  333. //target content
  334. $targetContent = this.getContentElement(),
  335. //target Width
  336. targetWidth = $target[0].offsetWidth,
  337. //target Height
  338. targetHeight = $target[0].offsetHeight,
  339. //placement
  340. placement = 'bottom',
  341. e = $.Event('show.' + pluginType);
  342. if (this.canEmptyHide()) {
  343. var content = $targetContent.children().html();
  344. if (content !== null && content.trim().length === 0) {
  345. return;
  346. }
  347. }
  348. //if (this.hasContent()){
  349. this.$element.trigger(e, [$target]);
  350. //}
  351. // support width as data attribute
  352. var optWidth = this.$element.data('width') || this.options.width;
  353. if (optWidth === '') {
  354. optWidth = this._defaults.width;
  355. }
  356. if (optWidth !== 'auto') {
  357. $target.width(optWidth);
  358. }
  359. // support height as data attribute
  360. var optHeight = this.$element.data('height') || this.options.height;
  361. if (optHeight === '') {
  362. optHeight = this._defaults.height;
  363. }
  364. if (optHeight !== 'auto') {
  365. $targetContent.height(optHeight);
  366. }
  367. if (this.options.style) {
  368. this.$target.addClass(pluginClass + '-' + this.options.style);
  369. }
  370. //check rtl
  371. if (this.options.direction === 'rtl' && !$targetContent.hasClass(rtlClass)) {
  372. $targetContent.addClass(rtlClass);
  373. }
  374. //init the popover and insert into the document body
  375. if (!this.options.arrow) {
  376. $target.find('.webui-arrow').remove();
  377. }
  378. $target.detach().css({
  379. top: _offsetOut,
  380. left: _offsetOut,
  381. display: 'block',
  382. opacity:this.options.opacity || 1
  383. });
  384. if (this.getAnimation()) {
  385. $target.addClass(this.getAnimation());
  386. }
  387. $target.appendTo(this.options.container);
  388. placement = this.getPlacement(elementPos);
  389. //This line is just for compatible with knockout custom binding
  390. this.$element.trigger('added.' + pluginType);
  391. this.initTargetEvents();
  392. if (!this.options.padding) {
  393. if (this.options.height !== 'auto') {
  394. $targetContent.css('height', $targetContent.outerHeight());
  395. }
  396. this.$target.addClass('webui-no-padding');
  397. }
  398. // add maxHeight and maxWidth support by limodou@gmail.com 2016/10/1
  399. if (this.options.maxHeight) {
  400. $targetContent.css('maxHeight', this.options.maxHeight);
  401. }
  402. if (this.options.maxWidth) {
  403. $targetContent.css('maxWidth', this.options.maxWidth);
  404. }
  405. // end
  406. targetWidth = $target[0].offsetWidth;
  407. targetHeight = $target[0].offsetHeight;
  408. var postionInfo = this.getTargetPositin(elementPos, placement, targetWidth, targetHeight);
  409. this.$target.css(postionInfo.position).addClass(placement).addClass('in');
  410. if (this.options.type === 'iframe') {
  411. var $iframe = $target.find('iframe');
  412. var iframeWidth = $target.width();
  413. var iframeHeight = $iframe.parent().height();
  414. if (this.options.iframeOptions.width !== '' && this.options.iframeOptions.width !== 'auto') {
  415. iframeWidth = this.options.iframeOptions.width;
  416. }
  417. if (this.options.iframeOptions.height !== '' && this.options.iframeOptions.height !== 'auto') {
  418. iframeHeight = this.options.iframeOptions.height;
  419. }
  420. $iframe.width(iframeWidth).height(iframeHeight);
  421. }
  422. if (!this.options.arrow) {
  423. this.$target.css({
  424. 'margin': 0
  425. });
  426. }
  427. if (this.options.arrow) {
  428. var $arrow = this.$target.find('.webui-arrow');
  429. $arrow.removeAttr('style');
  430. //prevent arrow change by content size
  431. if (placement === 'left' || placement === 'right') {
  432. $arrow.css({
  433. top: this.$target.height() / 2
  434. });
  435. } else if (placement === 'top' || placement === 'bottom') {
  436. $arrow.css({
  437. left: this.$target.width() / 2
  438. });
  439. }
  440. if (postionInfo.arrowOffset) {
  441. //hide the arrow if offset is negative
  442. if (postionInfo.arrowOffset.left === -1 || postionInfo.arrowOffset.top === -1) {
  443. $arrow.hide();
  444. } else {
  445. $arrow.css(postionInfo.arrowOffset);
  446. }
  447. }
  448. }
  449. this._poped = true;
  450. this.$element.trigger('shown.' + pluginType, [this.$target]);
  451. },
  452. isTargetLoaded: function () {
  453. return this.getTarget().find('i.glyphicon-refresh').length === 0;
  454. },
  455. /*getter setters */
  456. getTriggerElement: function () {
  457. return this.$element;
  458. },
  459. getTarget: function () {
  460. if (!this.$target) {
  461. var id = pluginName + this._idSeed;
  462. this.$target = $(this.options.template)
  463. .attr('id', id);
  464. this._customTargetClass = this.$target.attr('class') !== pluginClass ? this.$target.attr('class') : null;
  465. this.getTriggerElement().attr('data-target', id);
  466. }
  467. if (!this.$target.data('trigger-element')) {
  468. this.$target.data('trigger-element', this.getTriggerElement());
  469. }
  470. return this.$target;
  471. },
  472. removeTarget: function () {
  473. this.$target.remove();
  474. this.$target = null;
  475. this.$contentElement = null;
  476. },
  477. getTitleElement: function () {
  478. return this.getTarget().find('.' + pluginClass + '-title');
  479. },
  480. getContentElement: function () {
  481. if (!this.$contentElement) {
  482. this.$contentElement = this.getTarget().find('.' + pluginClass + '-content');
  483. }
  484. return this.$contentElement;
  485. },
  486. getTitle: function () {
  487. return this.$element.attr('data-title') || this.options.title || this.$element.attr('title');
  488. },
  489. getUrl: function () {
  490. return this.$element.attr('data-url') || this.options.url;
  491. },
  492. getAutoHide: function () {
  493. return this.$element.attr('data-auto-hide') || this.options.autoHide;
  494. },
  495. getOffsetTop: function () {
  496. return toNumber(this.$element.attr('data-offset-top')) || this.options.offsetTop;
  497. },
  498. getOffsetLeft: function () {
  499. return toNumber(this.$element.attr('data-offset-left')) || this.options.offsetLeft;
  500. },
  501. getCache: function () {
  502. var dataAttr = this.$element.attr('data-cache');
  503. if (typeof (dataAttr) !== 'undefined') {
  504. switch (dataAttr.toLowerCase()) {
  505. case 'true':
  506. case 'yes':
  507. case '1':
  508. return true;
  509. case 'false':
  510. case 'no':
  511. case '0':
  512. return false;
  513. }
  514. }
  515. return this.options.cache;
  516. },
  517. getTrigger: function () {
  518. return this.$element.attr('data-trigger') || this.options.trigger;
  519. },
  520. getDelayShow: function () {
  521. var dataAttr = this.$element.attr('data-delay-show');
  522. if (typeof (dataAttr) !== 'undefined') {
  523. return dataAttr;
  524. }
  525. return this.options.delay.show === 0 ? 0 : this.options.delay.show || 100;
  526. },
  527. getHideDelay: function () {
  528. var dataAttr = this.$element.attr('data-delay-hide');
  529. if (typeof (dataAttr) !== 'undefined') {
  530. return dataAttr;
  531. }
  532. return this.options.delay.hide === 0 ? 0 : this.options.delay.hide || 100;
  533. },
  534. getAnimation: function () {
  535. var dataAttr = this.$element.attr('data-animation');
  536. return dataAttr || this.options.animation;
  537. },
  538. getHideAnimation: function () {
  539. var ani = this.getAnimation();
  540. return ani ? ani + '-out' : 'out';
  541. },
  542. setTitle: function (title) {
  543. var $titleEl = this.getTitleElement();
  544. if (title) {
  545. //check rtl
  546. if (this.options.direction === 'rtl' && !$titleEl.hasClass(rtlClass)) {
  547. $titleEl.addClass(rtlClass);
  548. }
  549. $titleEl.html(title);
  550. } else {
  551. $titleEl.remove();
  552. }
  553. },
  554. hasContent: function () {
  555. return this.getContent();
  556. },
  557. canEmptyHide: function () {
  558. return this.options.hideEmpty && this.options.type === 'html';
  559. },
  560. getIframe: function () {
  561. var $iframe = $('<iframe></iframe>').attr('src', this.getUrl());
  562. var self = this;
  563. $.each(this._defaults.iframeOptions, function (opt) {
  564. if (typeof self.options.iframeOptions[opt] !== 'undefined') {
  565. $iframe.attr(opt, self.options.iframeOptions[opt]);
  566. }
  567. });
  568. return $iframe;
  569. },
  570. getContent: function () {
  571. if (this.getUrl()) {
  572. switch (this.options.type) {
  573. case 'iframe':
  574. this.content = this.getIframe();
  575. break;
  576. case 'html':
  577. try {
  578. this.content = $(this.getUrl());
  579. if (!this.content.is(':visible')) {
  580. this.content.show();
  581. }
  582. } catch (error) {
  583. throw new Error('Unable to get popover content. Invalid selector specified.');
  584. }
  585. break;
  586. }
  587. } else if (!this.content) {
  588. var content = '';
  589. if ($.isFunction(this.options.content)) {
  590. content = this.options.content.apply(this.$element[0], [this]);
  591. } else {
  592. content = this.options.content;
  593. }
  594. this.content = this.$element.attr('data-content') || content;
  595. if (!this.content) {
  596. var $next = this.$element.next();
  597. if ($next && $next.hasClass(pluginClass + '-content')) {
  598. this.content = $next;
  599. }
  600. }
  601. }
  602. return this.content;
  603. },
  604. setContent: function (content) {
  605. var $target = this.getTarget();
  606. var $ct = this.getContentElement();
  607. if (typeof content === 'string') {
  608. $ct.html(content);
  609. } else if (content instanceof $) {
  610. $ct.html('');
  611. //Don't want to clone too many times.
  612. if (!this.options.cache) {
  613. content.clone(true, true).removeClass(pluginClass + '-content').appendTo($ct);
  614. } else {
  615. content.removeClass(pluginClass + '-content').appendTo($ct);
  616. }
  617. }
  618. this.$target = $target;
  619. },
  620. isAsync: function () {
  621. return this.options.type === 'async';
  622. },
  623. setContentASync: function (content) {
  624. var that = this;
  625. if (this.xhr) {
  626. return;
  627. }
  628. this.xhr = $.ajax({
  629. url: this.getUrl(),
  630. type: this.options.async.type,
  631. cache: this.getCache(),
  632. beforeSend: function (xhr, settings) {
  633. if (that.options.async.before) {
  634. that.options.async.before(that, xhr, settings);
  635. }
  636. },
  637. success: function (data) {
  638. that.bindBodyEvents();
  639. if (content && $.isFunction(content)) {
  640. that.content = content.apply(that.$element[0], [data]);
  641. } else {
  642. that.content = data;
  643. }
  644. that.setContent(that.content);
  645. var $targetContent = that.getContentElement();
  646. $targetContent.removeAttr('style');
  647. that.displayContent();
  648. if (that.options.async.success) {
  649. that.options.async.success(that, data);
  650. }
  651. },
  652. complete: function () {
  653. that.xhr = null;
  654. },
  655. error: function (xhr, data) {
  656. if (that.options.async.error) {
  657. that.options.async.error(that, xhr, data);
  658. }
  659. }
  660. });
  661. },
  662. bindBodyEvents: function () {
  663. if (_isBodyEventHandled) {
  664. return;
  665. }
  666. if (this.options.dismissible && this.getTrigger() === 'click') {
  667. if (isMobile) {
  668. $document.off('touchstart.webui-popover').on('touchstart.webui-popover', $.proxy(this.bodyTouchStartHandler, this));
  669. } else {
  670. $document.off('keyup.webui-popover').on('keyup.webui-popover', $.proxy(this.escapeHandler, this));
  671. $document.off('click.webui-popover').on('click.webui-popover', $.proxy(this.bodyClickHandler, this));
  672. }
  673. } else if (this.getTrigger() === 'hover') {
  674. $document.off('touchend.webui-popover')
  675. .on('touchend.webui-popover', $.proxy(this.bodyClickHandler, this));
  676. }
  677. },
  678. /* event handlers */
  679. mouseenterHandler: function (e) {
  680. var self = this;
  681. if (e && this.options.selector) {
  682. self = this.delegate(e.currentTarget);
  683. }
  684. if (self._timeout) {
  685. clearTimeout(self._timeout);
  686. }
  687. self._enterTimeout = setTimeout(function () {
  688. if (!self.getTarget().is(':visible')) {
  689. self.show();
  690. }
  691. }, this.getDelayShow());
  692. },
  693. mouseleaveHandler: function () {
  694. var self = this;
  695. clearTimeout(self._enterTimeout);
  696. //key point, set the _timeout then use clearTimeout when mouse leave
  697. self._timeout = setTimeout(function () {
  698. self.hide();
  699. }, this.getHideDelay());
  700. },
  701. escapeHandler: function (e) {
  702. if (e.keyCode === 27) {
  703. this.hideAll();
  704. }
  705. },
  706. bodyTouchStartHandler: function (e) {
  707. var self = this;
  708. var $eventEl = $(e.currentTarget);
  709. $eventEl.on('touchend', function (e) {
  710. self.bodyClickHandler(e);
  711. $eventEl.off('touchend');
  712. });
  713. $eventEl.on('touchmove', function () {
  714. $eventEl.off('touchend');
  715. });
  716. },
  717. bodyClickHandler: function (e) {
  718. _isBodyEventHandled = true;
  719. var canHide = true;
  720. for (var i = 0; i < _srcElements.length; i++) {
  721. var pop = getPopFromElement(_srcElements[i]);
  722. if (pop && pop._opened) {
  723. var offset = pop.getTarget().offset();
  724. var popX1 = offset.left;
  725. var popY1 = offset.top;
  726. var popX2 = offset.left + pop.getTarget().width();
  727. var popY2 = offset.top + pop.getTarget().height();
  728. var pt = pointerEventToXY(e);
  729. var inPop = pt.x >= popX1 && pt.x <= popX2 && pt.y >= popY1 && pt.y <= popY2;
  730. if (inPop) {
  731. canHide = false;
  732. break;
  733. }
  734. }
  735. }
  736. if (canHide) {
  737. hideAllPop();
  738. }
  739. },
  740. /*
  741. targetClickHandler: function() {
  742. this._targetclick = true;
  743. },
  744. */
  745. //reset and init the target events;
  746. initTargetEvents: function () {
  747. if (this.getTrigger() === 'hover') {
  748. this.$target
  749. .off('mouseenter mouseleave')
  750. .on('mouseenter', $.proxy(this.mouseenterHandler, this))
  751. .on('mouseleave', $.proxy(this.mouseleaveHandler, this));
  752. }
  753. this.$target.find('.close').off('click').on('click', $.proxy(this.hide, this, true));
  754. //this.$target.off('click.webui-popover').on('click.webui-popover', $.proxy(this.targetClickHandler, this));
  755. },
  756. /* utils methods */
  757. //caculate placement of the popover
  758. getPlacement: function (pos) {
  759. var
  760. placement,
  761. container = this.options.container,
  762. clientWidth = container.innerWidth(),
  763. clientHeight = container.innerHeight(),
  764. scrollTop = container.scrollTop(),
  765. scrollLeft = container.scrollLeft(),
  766. pageX = Math.max(0, pos.left - scrollLeft),
  767. pageY = Math.max(0, pos.top - scrollTop);
  768. //arrowSize = 20;
  769. //if placement equals auto,caculate the placement by element information;
  770. if (typeof (this.options.placement) === 'function') {
  771. placement = this.options.placement.call(this, this.getTarget()[0], this.$element[0]);
  772. } else {
  773. placement = this.$element.data('placement') || this.options.placement;
  774. }
  775. var isH = placement === 'horizontal';
  776. var isV = placement === 'vertical';
  777. var detect = placement === 'auto' || isH || isV;
  778. if (detect) {
  779. if (pageX < clientWidth / 3) {
  780. if (pageY < clientHeight / 3) {
  781. placement = isH ? 'right-bottom' : 'bottom-right';
  782. } else if (pageY < clientHeight * 2 / 3) {
  783. if (isV) {
  784. placement = pageY <= clientHeight / 2 ? 'bottom-right' : 'top-right';
  785. } else {
  786. placement = 'right';
  787. }
  788. } else {
  789. placement = isH ? 'right-top' : 'top-right';
  790. }
  791. //placement= pageY>targetHeight+arrowSize?'top-right':'bottom-right';
  792. } else if (pageX < clientWidth * 2 / 3) {
  793. if (pageY < clientHeight / 3) {
  794. if (isH) {
  795. placement = pageX <= clientWidth / 2 ? 'right-bottom' : 'left-bottom';
  796. } else {
  797. placement = 'bottom';
  798. }
  799. } else if (pageY < clientHeight * 2 / 3) {
  800. if (isH) {
  801. placement = pageX <= clientWidth / 2 ? 'right' : 'left';
  802. } else {
  803. placement = pageY <= clientHeight / 2 ? 'bottom' : 'top';
  804. }
  805. } else {
  806. if (isH) {
  807. placement = pageX <= clientWidth / 2 ? 'right-top' : 'left-top';
  808. } else {
  809. placement = 'top';
  810. }
  811. }
  812. } else {
  813. //placement = pageY>targetHeight+arrowSize?'top-left':'bottom-left';
  814. if (pageY < clientHeight / 3) {
  815. placement = isH ? 'left-bottom' : 'bottom-left';
  816. } else if (pageY < clientHeight * 2 / 3) {
  817. if (isV) {
  818. placement = pageY <= clientHeight / 2 ? 'bottom-left' : 'top-left';
  819. } else {
  820. placement = 'left';
  821. }
  822. } else {
  823. placement = isH ? 'left-top' : 'top-left';
  824. }
  825. }
  826. } else if (placement === 'auto-top') {
  827. if (pageX < clientWidth / 3) {
  828. placement = 'top-right';
  829. } else if (pageX < clientWidth * 2 / 3) {
  830. placement = 'top';
  831. } else {
  832. placement = 'top-left';
  833. }
  834. } else if (placement === 'auto-bottom') {
  835. if (pageX < clientWidth / 3) {
  836. placement = 'bottom-right';
  837. } else if (pageX < clientWidth * 2 / 3) {
  838. placement = 'bottom';
  839. } else {
  840. placement = 'bottom-left';
  841. }
  842. } else if (placement === 'auto-left') {
  843. if (pageY < clientHeight / 3) {
  844. placement = 'left-top';
  845. } else if (pageY < clientHeight * 2 / 3) {
  846. placement = 'left';
  847. } else {
  848. placement = 'left-bottom';
  849. }
  850. } else if (placement === 'auto-right') {
  851. if (pageY < clientHeight / 3) {
  852. placement = 'right-bottom';
  853. } else if (pageY < clientHeight * 2 / 3) {
  854. placement = 'right';
  855. } else {
  856. placement = 'right-top';
  857. }
  858. }
  859. return placement;
  860. },
  861. getElementPosition: function () {
  862. // If the container is the body or normal conatiner, just use $element.offset()
  863. var elRect = this.$element[0].getBoundingClientRect();
  864. var container = this.options.container;
  865. var cssPos = container.css('position');
  866. if (container.is(document.body) || cssPos === 'static') {
  867. return $.extend({}, this.$element.offset(), {
  868. width: this.$element[0].offsetWidth || elRect.width,
  869. height: this.$element[0].offsetHeight || elRect.height
  870. });
  871. // Else fixed container need recalculate the position
  872. } else if (cssPos === 'fixed') {
  873. var containerRect = container[0].getBoundingClientRect();
  874. return {
  875. top: elRect.top - containerRect.top + container.scrollTop(),
  876. left: elRect.left - containerRect.left + container.scrollLeft(),
  877. width: elRect.width,
  878. height: elRect.height
  879. };
  880. } else if (cssPos === 'relative') {
  881. return {
  882. top: this.$element.offset().top - container.offset().top,
  883. left: this.$element.offset().left - container.offset().left,
  884. width: this.$element[0].offsetWidth || elRect.width,
  885. height: this.$element[0].offsetHeight || elRect.height
  886. };
  887. }
  888. },
  889. getTargetPositin: function (elementPos, placement, targetWidth, targetHeight) {
  890. var pos = elementPos,
  891. container = this.options.container,
  892. //clientWidth = container.innerWidth(),
  893. //clientHeight = container.innerHeight(),
  894. elementW = this.$element.outerWidth(),
  895. elementH = this.$element.outerHeight(),
  896. scrollTop = document.documentElement.scrollTop + container.scrollTop(),
  897. scrollLeft = document.documentElement.scrollLeft + container.scrollLeft(),
  898. position = {},
  899. arrowOffset = null,
  900. arrowSize = this.options.arrow ? 20 : 0,
  901. padding = 10,
  902. fixedW = elementW < arrowSize + padding ? arrowSize : 0,
  903. fixedH = elementH < arrowSize + padding ? arrowSize : 0,
  904. refix = 0,
  905. pageH = document.documentElement.clientHeight + scrollTop,
  906. pageW = document.documentElement.clientWidth + scrollLeft;
  907. var validLeft = pos.left + pos.width / 2 - fixedW > 0;
  908. var validRight = pos.left + pos.width / 2 + fixedW < pageW;
  909. var validTop = pos.top + pos.height / 2 - fixedH > 0;
  910. var validBottom = pos.top + pos.height / 2 + fixedH < pageH;
  911. switch (placement) {
  912. case 'bottom':
  913. position = {
  914. top: pos.top + pos.height,
  915. left: pos.left + pos.width / 2 - targetWidth / 2
  916. };
  917. break;
  918. case 'top':
  919. position = {
  920. top: pos.top - targetHeight,
  921. left: pos.left + pos.width / 2 - targetWidth / 2
  922. };
  923. break;
  924. case 'left':
  925. position = {
  926. top: pos.top + pos.height / 2 - targetHeight / 2,
  927. left: pos.left - targetWidth
  928. };
  929. break;
  930. case 'right':
  931. position = {
  932. top: pos.top + pos.height / 2 - targetHeight / 2,
  933. left: pos.left + pos.width
  934. };
  935. break;
  936. case 'top-right':
  937. position = {
  938. top: pos.top - targetHeight,
  939. left: validLeft ? pos.left - fixedW : padding
  940. };
  941. arrowOffset = {
  942. left: validLeft ? Math.min(elementW, targetWidth) / 2 + fixedW : _offsetOut
  943. };
  944. break;
  945. case 'top-left':
  946. refix = validRight ? fixedW : -padding;
  947. position = {
  948. top: pos.top - targetHeight,
  949. left: pos.left - targetWidth + pos.width + refix
  950. };
  951. arrowOffset = {
  952. left: validRight ? targetWidth - Math.min(elementW, targetWidth) / 2 - fixedW : _offsetOut
  953. };
  954. break;
  955. case 'bottom-right':
  956. position = {
  957. top: pos.top + pos.height,
  958. left: validLeft ? pos.left - fixedW : padding
  959. };
  960. arrowOffset = {
  961. left: validLeft ? Math.min(elementW, targetWidth) / 2 + fixedW : _offsetOut
  962. };
  963. break;
  964. case 'bottom-left':
  965. refix = validRight ? fixedW : -padding;
  966. position = {
  967. top: pos.top + pos.height,
  968. left: pos.left - targetWidth + pos.width + refix
  969. };
  970. arrowOffset = {
  971. left: validRight ? targetWidth - Math.min(elementW, targetWidth) / 2 - fixedW : _offsetOut
  972. };
  973. break;
  974. case 'right-top':
  975. refix = validBottom ? fixedH : -padding;
  976. position = {
  977. top: pos.top - targetHeight + pos.height + refix,
  978. left: pos.left + pos.width
  979. };
  980. arrowOffset = {
  981. top: validBottom ? targetHeight - Math.min(elementH, targetHeight) / 2 - fixedH : _offsetOut
  982. };
  983. break;
  984. case 'right-bottom':
  985. position = {
  986. top: validTop ? pos.top - fixedH : padding,
  987. left: pos.left + pos.width
  988. };
  989. arrowOffset = {
  990. top: validTop ? Math.min(elementH, targetHeight) / 2 + fixedH : _offsetOut
  991. };
  992. break;
  993. case 'left-top':
  994. refix = validBottom ? fixedH : -padding;
  995. position = {
  996. top: pos.top - targetHeight + pos.height + refix,
  997. left: pos.left - targetWidth
  998. };
  999. arrowOffset = {
  1000. top: validBottom ? targetHeight - Math.min(elementH, targetHeight) / 2 - fixedH : _offsetOut
  1001. };
  1002. break;
  1003. case 'left-bottom':
  1004. position = {
  1005. top: validTop ? pos.top - fixedH : padding,
  1006. left: pos.left - targetWidth
  1007. };
  1008. arrowOffset = {
  1009. top: validTop ? Math.min(elementH, targetHeight) / 2 + fixedH : _offsetOut
  1010. };
  1011. break;
  1012. }
  1013. position.top += this.getOffsetTop();
  1014. position.left += this.getOffsetLeft();
  1015. return {
  1016. position: position,
  1017. arrowOffset: arrowOffset
  1018. };
  1019. }
  1020. };
  1021. $.fn[pluginName] = function (options, noInit) {
  1022. var results = [];
  1023. var $result = this.each(function () {
  1024. var webuiPopover = $.data(this, 'plugin_' + pluginName);
  1025. if (!webuiPopover) {
  1026. if (!options) {
  1027. webuiPopover = new WebuiPopover(this, null);
  1028. } else if (typeof options === 'string') {
  1029. if (options !== 'destroy') {
  1030. if (!noInit) {
  1031. webuiPopover = new WebuiPopover(this, null);
  1032. results.push(webuiPopover[options]());
  1033. }
  1034. }
  1035. } else if (typeof options === 'object') {
  1036. webuiPopover = new WebuiPopover(this, options);
  1037. }
  1038. $.data(this, 'plugin_' + pluginName, webuiPopover);
  1039. } else {
  1040. if (options === 'destroy') {
  1041. webuiPopover.destroy();
  1042. } else if (typeof options === 'string') {
  1043. results.push(webuiPopover[options]());
  1044. }
  1045. }
  1046. });
  1047. return (results.length) ? results : $result;
  1048. };
  1049. //Global object exposes to window.
  1050. var webuiPopovers = (function () {
  1051. var _hideAll = function () {
  1052. hideAllPop();
  1053. };
  1054. var _create = function (selector, options) {
  1055. options = options || {};
  1056. $(selector).webuiPopover(options);
  1057. };
  1058. var _isCreated = function (selector) {
  1059. var created = true;
  1060. $(selector).each(function (i, item) {
  1061. created = created && $(item).data('plugin_' + pluginName) !== undefined;
  1062. });
  1063. return created;
  1064. };
  1065. var _show = function (selector, options) {
  1066. if (options) {
  1067. $(selector).webuiPopover(options).webuiPopover('show');
  1068. } else {
  1069. $(selector).webuiPopover('show');
  1070. }
  1071. };
  1072. var _hide = function (selector) {
  1073. $(selector).webuiPopover('hide');
  1074. };
  1075. var _setDefaultOptions = function (options) {
  1076. defaults = $.extend({}, defaults, options);
  1077. };
  1078. var _updateContent = function (selector, content) {
  1079. var pop = $(selector).data('plugin_' + pluginName);
  1080. if (pop) {
  1081. var cache = pop.getCache();
  1082. pop.options.cache = false;
  1083. pop.options.content = content;
  1084. if (pop._opened) {
  1085. pop._opened = false;
  1086. pop.show();
  1087. } else {
  1088. if (pop.isAsync()) {
  1089. pop.setContentASync(content);
  1090. } else {
  1091. pop.setContent(content);
  1092. }
  1093. }
  1094. pop.options.cache = cache;
  1095. }
  1096. };
  1097. var _updateContentAsync = function (selector, url) {
  1098. var pop = $(selector).data('plugin_' + pluginName);
  1099. if (pop) {
  1100. var cache = pop.getCache();
  1101. var type = pop.options.type;
  1102. pop.options.cache = false;
  1103. pop.options.url = url;
  1104. if (pop._opened) {
  1105. pop._opened = false;
  1106. pop.show();
  1107. } else {
  1108. pop.options.type = 'async';
  1109. pop.setContentASync(pop.content);
  1110. }
  1111. pop.options.cache = cache;
  1112. pop.options.type = type;
  1113. }
  1114. };
  1115. return {
  1116. show: _show,
  1117. hide: _hide,
  1118. create: _create,
  1119. isCreated: _isCreated,
  1120. hideAll: _hideAll,
  1121. updateContent: _updateContent,
  1122. updateContentAsync: _updateContentAsync,
  1123. setDefaultOptions: _setDefaultOptions
  1124. };
  1125. })();
  1126. window.WebuiPopovers = webuiPopovers;
  1127. exports("popover",WebuiPopovers);
  1128. })