wp-pointer.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /* global wpPointerL10n */
  2. /**
  3. * Pointer jQuery widget.
  4. */
  5. (function($){
  6. var identifier = 0,
  7. zindex = 9999;
  8. /**
  9. * @class $.widget.wp.pointer
  10. */
  11. $.widget('wp.pointer',/** @lends $.widget.wp.pointer.prototype */{
  12. options: {
  13. pointerClass: 'wp-pointer',
  14. pointerWidth: 320,
  15. content: function() {
  16. return $(this).text();
  17. },
  18. buttons: function( event, t ) {
  19. var close = ( wpPointerL10n ) ? wpPointerL10n.dismiss : 'Dismiss',
  20. button = $('<a class="close" href="#">' + close + '</a>');
  21. return button.bind( 'click.pointer', function(e) {
  22. e.preventDefault();
  23. t.element.pointer('close');
  24. });
  25. },
  26. position: 'top',
  27. show: function( event, t ) {
  28. t.pointer.show();
  29. t.opened();
  30. },
  31. hide: function( event, t ) {
  32. t.pointer.hide();
  33. t.closed();
  34. },
  35. document: document
  36. },
  37. _create: function() {
  38. var positioning,
  39. family;
  40. this.content = $('<div class="wp-pointer-content"></div>');
  41. this.arrow = $('<div class="wp-pointer-arrow"><div class="wp-pointer-arrow-inner"></div></div>');
  42. family = this.element.parents().add( this.element );
  43. positioning = 'absolute';
  44. if ( family.filter(function(){ return 'fixed' === $(this).css('position'); }).length )
  45. positioning = 'fixed';
  46. this.pointer = $('<div />')
  47. .append( this.content )
  48. .append( this.arrow )
  49. .attr('id', 'wp-pointer-' + identifier++)
  50. .addClass( this.options.pointerClass )
  51. .css({'position': positioning, 'width': this.options.pointerWidth+'px', 'display': 'none'})
  52. .appendTo( this.options.document.body );
  53. },
  54. _setOption: function( key, value ) {
  55. var o = this.options,
  56. tip = this.pointer;
  57. // Handle document transfer
  58. if ( key === 'document' && value !== o.document ) {
  59. tip.detach().appendTo( value.body );
  60. // Handle class change
  61. } else if ( key === 'pointerClass' ) {
  62. tip.removeClass( o.pointerClass ).addClass( value );
  63. }
  64. // Call super method.
  65. $.Widget.prototype._setOption.apply( this, arguments );
  66. // Reposition automatically
  67. if ( key === 'position' ) {
  68. this.reposition();
  69. // Update content automatically if pointer is open
  70. } else if ( key === 'content' && this.active ) {
  71. this.update();
  72. }
  73. },
  74. destroy: function() {
  75. this.pointer.remove();
  76. $.Widget.prototype.destroy.call( this );
  77. },
  78. widget: function() {
  79. return this.pointer;
  80. },
  81. update: function( event ) {
  82. var self = this,
  83. o = this.options,
  84. dfd = $.Deferred(),
  85. content;
  86. if ( o.disabled )
  87. return;
  88. dfd.done( function( content ) {
  89. self._update( event, content );
  90. });
  91. // Either o.content is a string...
  92. if ( typeof o.content === 'string' ) {
  93. content = o.content;
  94. // ...or o.content is a callback.
  95. } else {
  96. content = o.content.call( this.element[0], dfd.resolve, event, this._handoff() );
  97. }
  98. // If content is set, then complete the update.
  99. if ( content )
  100. dfd.resolve( content );
  101. return dfd.promise();
  102. },
  103. /**
  104. * Update is separated into two functions to allow events to defer
  105. * updating the pointer (e.g. fetch content with ajax, etc).
  106. */
  107. _update: function( event, content ) {
  108. var buttons,
  109. o = this.options;
  110. if ( ! content )
  111. return;
  112. this.pointer.stop(); // Kill any animations on the pointer.
  113. this.content.html( content );
  114. buttons = o.buttons.call( this.element[0], event, this._handoff() );
  115. if ( buttons ) {
  116. buttons.wrap('<div class="wp-pointer-buttons" />').parent().appendTo( this.content );
  117. }
  118. this.reposition();
  119. },
  120. reposition: function() {
  121. var position;
  122. if ( this.options.disabled )
  123. return;
  124. position = this._processPosition( this.options.position );
  125. // Reposition pointer.
  126. this.pointer.css({
  127. top: 0,
  128. left: 0,
  129. zIndex: zindex++ // Increment the z-index so that it shows above other opened pointers.
  130. }).show().position($.extend({
  131. of: this.element,
  132. collision: 'fit none'
  133. }, position )); // the object comes before this.options.position so the user can override position.of.
  134. this.repoint();
  135. },
  136. repoint: function() {
  137. var o = this.options,
  138. edge;
  139. if ( o.disabled )
  140. return;
  141. edge = ( typeof o.position == 'string' ) ? o.position : o.position.edge;
  142. // Remove arrow classes.
  143. this.pointer[0].className = this.pointer[0].className.replace( /wp-pointer-[^\s'"]*/, '' );
  144. // Add arrow class.
  145. this.pointer.addClass( 'wp-pointer-' + edge );
  146. },
  147. _processPosition: function( position ) {
  148. var opposite = {
  149. top: 'bottom',
  150. bottom: 'top',
  151. left: 'right',
  152. right: 'left'
  153. },
  154. result;
  155. // If the position object is a string, it is shorthand for position.edge.
  156. if ( typeof position == 'string' ) {
  157. result = {
  158. edge: position + ''
  159. };
  160. } else {
  161. result = $.extend( {}, position );
  162. }
  163. if ( ! result.edge )
  164. return result;
  165. if ( result.edge == 'top' || result.edge == 'bottom' ) {
  166. result.align = result.align || 'left';
  167. result.at = result.at || result.align + ' ' + opposite[ result.edge ];
  168. result.my = result.my || result.align + ' ' + result.edge;
  169. } else {
  170. result.align = result.align || 'top';
  171. result.at = result.at || opposite[ result.edge ] + ' ' + result.align;
  172. result.my = result.my || result.edge + ' ' + result.align;
  173. }
  174. return result;
  175. },
  176. open: function( event ) {
  177. var self = this,
  178. o = this.options;
  179. if ( this.active || o.disabled || this.element.is(':hidden') )
  180. return;
  181. this.update().done( function() {
  182. self._open( event );
  183. });
  184. },
  185. _open: function( event ) {
  186. var self = this,
  187. o = this.options;
  188. if ( this.active || o.disabled || this.element.is(':hidden') )
  189. return;
  190. this.active = true;
  191. this._trigger( 'open', event, this._handoff() );
  192. this._trigger( 'show', event, this._handoff({
  193. opened: function() {
  194. self._trigger( 'opened', event, self._handoff() );
  195. }
  196. }));
  197. },
  198. close: function( event ) {
  199. if ( !this.active || this.options.disabled )
  200. return;
  201. var self = this;
  202. this.active = false;
  203. this._trigger( 'close', event, this._handoff() );
  204. this._trigger( 'hide', event, this._handoff({
  205. closed: function() {
  206. self._trigger( 'closed', event, self._handoff() );
  207. }
  208. }));
  209. },
  210. sendToTop: function() {
  211. if ( this.active )
  212. this.pointer.css( 'z-index', zindex++ );
  213. },
  214. toggle: function( event ) {
  215. if ( this.pointer.is(':hidden') )
  216. this.open( event );
  217. else
  218. this.close( event );
  219. },
  220. _handoff: function( extend ) {
  221. return $.extend({
  222. pointer: this.pointer,
  223. element: this.element
  224. }, extend);
  225. }
  226. });
  227. })(jQuery);