jquery.webui-popover.js 50 KB

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