popover.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. angular.module('bravoUiPopover', [])
  2. .directive("bravoPopover", function($compile, $position, $sce){
  3. // $parse : ng表达式 {{1+2}} {{text}}
  4. // $compile : 编译一段html字符串(可以包括ng表达式)
  5. return {
  6. restrict: "A",
  7. scope: {
  8. confirm: '&bravoPopoverConfirm'
  9. },
  10. compile: function (elem, attr) {
  11. var confirm_template = attr['bravoPopoverConfirm'] ? '<span>' +
  12. '<a class="btn btn-danger" ng-click="on_confirm()">Confirm</a> ' +
  13. '<a class="btn btn-info" ng-click="on_cancel()">Cancel</a>' +
  14. '</span>' : '';
  15. var template =
  16. '<div class="popover fade {{placement}} in" ng-show="popoover_show == \'in\'">' +
  17. '<div class="arrow"></div>' +
  18. '<h3 class="popover-title">{{title}}</h3>' +
  19. '<div class="popover-content" ng-bind-html="content">' +
  20. '</div>' +
  21. '<div class="popover-content" ng-show="show_confirm_template">' +
  22. confirm_template +
  23. '</div>' +
  24. '</div>';
  25. var linker = $compile(template);
  26. return function (scope, elem, attr) {
  27. scope.popoover_show = "";
  28. scope.title = attr['title'];
  29. scope.content = $sce.trustAsHtml(attr['content']);
  30. scope.placement = attr['placement'] ? attr['placement'] : 'top';
  31. scope.trigger = attr['bravoPopoverConfirm'] ? 'click' : attr['trigger'];
  32. scope.show_confirm_template = attr['bravoPopoverConfirm'] ? true : false;
  33. var tooltip = linker(scope, function (o) {
  34. elem.after(o);
  35. });
  36. tooltip.css({ top: 0, left: 0, display: 'block' });
  37. if (!scope.trigger || scope.trigger == 'click') {
  38. elem.on('click', function (event) {
  39. toggle();
  40. });
  41. } else {
  42. var eventIn = scope.trigger == 'hover' ? 'mouseenter' : 'focus';
  43. var eventOut = scope.trigger == 'hover' ? 'mouseleave' : 'blur';
  44. elem.on(eventIn, function (event) {
  45. show_popover();
  46. });
  47. elem.on(eventOut, function () {
  48. hide_popover();
  49. });
  50. }
  51. var toggle = function() {
  52. scope.popoover_show == 'in'? hide_popover() : show_popover();
  53. render_css(tooltip);
  54. };
  55. var show_popover = function() {
  56. scope.popoover_show = "in";
  57. scope.$apply();
  58. render_css(tooltip);
  59. };
  60. var hide_popover = function() {
  61. scope.popoover_show = "";
  62. scope.$apply();
  63. };
  64. var render_css = function (scope_element) {
  65. var ttPosition = $position.positionElements(elem, scope_element, scope.placement, false);
  66. ttPosition.top += 'px';
  67. ttPosition.left += 'px';
  68. // Now set the calculated positioning.
  69. scope_element.css( ttPosition );
  70. };
  71. render_css(tooltip);
  72. scope.on_cancel = function () {
  73. scope.popoover_show = "";
  74. };
  75. scope.on_confirm = function () {
  76. scope.confirm();
  77. scope.popoover_show = "";
  78. };
  79. }
  80. }
  81. };
  82. })
  83. .factory('$position', ['$document', '$window', function ($document, $window) {
  84. function getStyle(el, cssprop) {
  85. if (el.currentStyle) { //IE
  86. return el.currentStyle[cssprop];
  87. } else if ($window.getComputedStyle) {
  88. return $window.getComputedStyle(el)[cssprop];
  89. }
  90. // finally try and get inline style
  91. return el.style[cssprop];
  92. }
  93. /**
  94. * Checks if a given element is statically positioned
  95. * @param element - raw DOM element
  96. */
  97. function isStaticPositioned(element) {
  98. return (getStyle(element, 'position') || 'static' ) === 'static';
  99. }
  100. /**
  101. * returns the closest, non-statically positioned parentOffset of a given element
  102. * @param element
  103. */
  104. var parentOffsetEl = function (element) {
  105. var docDomEl = $document[0];
  106. var offsetParent = element.offsetParent || docDomEl;
  107. while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
  108. offsetParent = offsetParent.offsetParent;
  109. }
  110. return offsetParent || docDomEl;
  111. };
  112. return {
  113. /**
  114. * Provides read-only equivalent of jQuery's position function:
  115. * http://api.jquery.com/position/
  116. */
  117. position: function (element) {
  118. var elBCR = this.offset(element);
  119. var offsetParentBCR = { top: 0, left: 0 };
  120. var offsetParentEl = parentOffsetEl(element[0]);
  121. if (offsetParentEl != $document[0]) {
  122. offsetParentBCR = this.offset(angular.element(offsetParentEl));
  123. offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
  124. offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
  125. }
  126. var boundingClientRect = element[0].getBoundingClientRect();
  127. return {
  128. width: boundingClientRect.width || element.prop('offsetWidth'),
  129. height: boundingClientRect.height || element.prop('offsetHeight'),
  130. top: elBCR.top - offsetParentBCR.top,
  131. left: elBCR.left - offsetParentBCR.left
  132. };
  133. },
  134. /**
  135. * Provides read-only equivalent of jQuery's offset function:
  136. * http://api.jquery.com/offset/
  137. */
  138. offset: function (element) {
  139. var boundingClientRect = element[0].getBoundingClientRect();
  140. return {
  141. width: boundingClientRect.width || element.prop('offsetWidth'),
  142. height: boundingClientRect.height || element.prop('offsetHeight'),
  143. top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
  144. left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
  145. };
  146. },
  147. /**
  148. * Provides coordinates for the targetEl in relation to hostEl
  149. */
  150. positionElements: function (hostEl, targetEl, positionStr, appendToBody) {
  151. var positionStrParts = positionStr.split('-');
  152. var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
  153. var hostElPos,
  154. targetElWidth,
  155. targetElHeight,
  156. targetElPos;
  157. hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
  158. targetElWidth = targetEl.prop('offsetWidth');
  159. targetElHeight = targetEl.prop('offsetHeight');
  160. var shiftWidth = {
  161. center: function () {
  162. return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
  163. },
  164. left: function () {
  165. return hostElPos.left;
  166. },
  167. right: function () {
  168. return hostElPos.left + hostElPos.width;
  169. }
  170. };
  171. var shiftHeight = {
  172. center: function () {
  173. return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
  174. },
  175. top: function () {
  176. return hostElPos.top;
  177. },
  178. bottom: function () {
  179. return hostElPos.top + hostElPos.height;
  180. }
  181. };
  182. switch (pos0) {
  183. case 'right':
  184. targetElPos = {
  185. top: shiftHeight[pos1](),
  186. left: shiftWidth[pos0]()
  187. };
  188. break;
  189. case 'left':
  190. targetElPos = {
  191. top: shiftHeight[pos1](),
  192. left: hostElPos.left - targetElWidth
  193. };
  194. break;
  195. case 'bottom':
  196. targetElPos = {
  197. top: shiftHeight[pos0](),
  198. left: shiftWidth[pos1]()
  199. };
  200. break;
  201. default:
  202. targetElPos = {
  203. top: hostElPos.top - targetElHeight,
  204. left: shiftWidth[pos1]()
  205. };
  206. break;
  207. }
  208. return targetElPos;
  209. }
  210. };
  211. }]);