wp-pointer.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /**
  2. * @output wp-includes/js/wp-pointer.js
  3. */
  4. /* global wpPointerL10n */
  5. /**
  6. * Initializes the wp-pointer widget using jQuery UI Widget Factory.
  7. */
  8. (function($){
  9. var identifier = 0,
  10. zindex = 9999;
  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. /**
  38. * A class that represents a WordPress pointer.
  39. *
  40. * @since 3.3.0
  41. * @private
  42. *
  43. * @constructs $.widget.wp.pointer
  44. */
  45. _create: function() {
  46. var positioning,
  47. family;
  48. this.content = $('<div class="wp-pointer-content"></div>');
  49. this.arrow = $('<div class="wp-pointer-arrow"><div class="wp-pointer-arrow-inner"></div></div>');
  50. family = this.element.parents().add( this.element );
  51. positioning = 'absolute';
  52. if ( family.filter(function(){ return 'fixed' === $(this).css('position'); }).length )
  53. positioning = 'fixed';
  54. this.pointer = $('<div />')
  55. .append( this.content )
  56. .append( this.arrow )
  57. .attr('id', 'wp-pointer-' + identifier++)
  58. .addClass( this.options.pointerClass )
  59. .css({'position': positioning, 'width': this.options.pointerWidth+'px', 'display': 'none'})
  60. .appendTo( this.options.document.body );
  61. },
  62. /**
  63. * Sets an option on the pointer instance.
  64. *
  65. * There are 4 special values that do something extra:
  66. *
  67. * - `document` will transfer the pointer to the body of the new document
  68. * specified by the value.
  69. * - `pointerClass` will change the class of the pointer element.
  70. * - `position` will reposition the pointer.
  71. * - `content` will update the content of the pointer.
  72. *
  73. * @since 3.3.0
  74. * @private
  75. *
  76. * @param {string} key The key of the option to set.
  77. * @param {*} value The value to set the option to.
  78. */
  79. _setOption: function( key, value ) {
  80. var o = this.options,
  81. tip = this.pointer;
  82. // Handle document transfer
  83. if ( key === 'document' && value !== o.document ) {
  84. tip.detach().appendTo( value.body );
  85. // Handle class change
  86. } else if ( key === 'pointerClass' ) {
  87. tip.removeClass( o.pointerClass ).addClass( value );
  88. }
  89. // Call super method.
  90. $.Widget.prototype._setOption.apply( this, arguments );
  91. // Reposition automatically
  92. if ( key === 'position' ) {
  93. this.reposition();
  94. // Update content automatically if pointer is open
  95. } else if ( key === 'content' && this.active ) {
  96. this.update();
  97. }
  98. },
  99. /**
  100. * Removes the pointer element from of the DOM.
  101. *
  102. * Makes sure that the widget and all associated bindings are destroyed.
  103. *
  104. * @since 3.3.0
  105. */
  106. destroy: function() {
  107. this.pointer.remove();
  108. $.Widget.prototype.destroy.call( this );
  109. },
  110. /**
  111. * Returns the pointer element.
  112. *
  113. * @since 3.3.0
  114. *
  115. * @return {Object} Pointer The pointer object.
  116. */
  117. widget: function() {
  118. return this.pointer;
  119. },
  120. /**
  121. * Updates the content of the pointer.
  122. *
  123. * This function doesn't update the content of the pointer itself. That is done
  124. * by the `_update` method. This method will make sure that the `_update` method
  125. * is called with the right content.
  126. *
  127. * The content in the options can either be a string or a callback. If it is a
  128. * callback the result of this callback is used as the content.
  129. *
  130. * @since 3.3.0
  131. *
  132. * @param {Object} event The event that caused the update.
  133. *
  134. * @return {Promise} Resolves when the update has been executed.
  135. */
  136. update: function( event ) {
  137. var self = this,
  138. o = this.options,
  139. dfd = $.Deferred(),
  140. content;
  141. if ( o.disabled )
  142. return;
  143. dfd.done( function( content ) {
  144. self._update( event, content );
  145. });
  146. // Either o.content is a string...
  147. if ( typeof o.content === 'string' ) {
  148. content = o.content;
  149. // ...or o.content is a callback.
  150. } else {
  151. content = o.content.call( this.element[0], dfd.resolve, event, this._handoff() );
  152. }
  153. // If content is set, then complete the update.
  154. if ( content )
  155. dfd.resolve( content );
  156. return dfd.promise();
  157. },
  158. /**
  159. * Updates the content of the pointer.
  160. *
  161. * Will make sure that the pointer is correctly positioned.
  162. *
  163. * @since 3.3.0
  164. * @private
  165. *
  166. * @param {Object} event The event that caused the update.
  167. * @param {*} content The content object. Either a string or a jQuery tree.
  168. */
  169. _update: function( event, content ) {
  170. var buttons,
  171. o = this.options;
  172. if ( ! content )
  173. return;
  174. // Kill any animations on the pointer.
  175. this.pointer.stop();
  176. this.content.html( content );
  177. buttons = o.buttons.call( this.element[0], event, this._handoff() );
  178. if ( buttons ) {
  179. buttons.wrap('<div class="wp-pointer-buttons" />').parent().appendTo( this.content );
  180. }
  181. this.reposition();
  182. },
  183. /**
  184. * Repositions the pointer.
  185. *
  186. * Makes sure the pointer is the correct size for its content and makes sure it
  187. * is positioned to point to the right element.
  188. *
  189. * @since 3.3.0
  190. */
  191. reposition: function() {
  192. var position;
  193. if ( this.options.disabled )
  194. return;
  195. position = this._processPosition( this.options.position );
  196. // Reposition pointer.
  197. this.pointer.css({
  198. top: 0,
  199. left: 0,
  200. zIndex: zindex++ // Increment the z-index so that it shows above other opened pointers.
  201. }).show().position($.extend({
  202. of: this.element,
  203. collision: 'fit none'
  204. }, position )); // the object comes before this.options.position so the user can override position.of.
  205. this.repoint();
  206. },
  207. /**
  208. * Sets the arrow of the pointer to the correct side of the pointer element.
  209. *
  210. * @since 3.3.0
  211. */
  212. repoint: function() {
  213. var o = this.options,
  214. edge;
  215. if ( o.disabled )
  216. return;
  217. edge = ( typeof o.position == 'string' ) ? o.position : o.position.edge;
  218. // Remove arrow classes.
  219. this.pointer[0].className = this.pointer[0].className.replace( /wp-pointer-[^\s'"]*/, '' );
  220. // Add arrow class.
  221. this.pointer.addClass( 'wp-pointer-' + edge );
  222. },
  223. /**
  224. * Calculates the correct position based on a position in the settings.
  225. *
  226. * @since 3.3.0
  227. * @private
  228. *
  229. * @param {string|Object} position Either a side of a pointer or an object
  230. * containing a pointer.
  231. *
  232. * @return {Object} result An object containing position related data.
  233. */
  234. _processPosition: function( position ) {
  235. var opposite = {
  236. top: 'bottom',
  237. bottom: 'top',
  238. left: 'right',
  239. right: 'left'
  240. },
  241. result;
  242. // If the position object is a string, it is shorthand for position.edge.
  243. if ( typeof position == 'string' ) {
  244. result = {
  245. edge: position + ''
  246. };
  247. } else {
  248. result = $.extend( {}, position );
  249. }
  250. if ( ! result.edge )
  251. return result;
  252. if ( result.edge == 'top' || result.edge == 'bottom' ) {
  253. result.align = result.align || 'left';
  254. result.at = result.at || result.align + ' ' + opposite[ result.edge ];
  255. result.my = result.my || result.align + ' ' + result.edge;
  256. } else {
  257. result.align = result.align || 'top';
  258. result.at = result.at || opposite[ result.edge ] + ' ' + result.align;
  259. result.my = result.my || result.edge + ' ' + result.align;
  260. }
  261. return result;
  262. },
  263. /**
  264. * Opens the pointer.
  265. *
  266. * Only opens the pointer widget in case it is closed and not disabled, and
  267. * calls 'update' before doing so. Calling update makes sure that the pointer
  268. * is correctly sized and positioned.
  269. *
  270. * @since 3.3.0
  271. *
  272. * @param {Object} event The event that triggered the opening of this pointer.
  273. */
  274. open: function( event ) {
  275. var self = this,
  276. o = this.options;
  277. if ( this.active || o.disabled || this.element.is(':hidden') )
  278. return;
  279. this.update().done( function() {
  280. self._open( event );
  281. });
  282. },
  283. /**
  284. * Opens and shows the pointer element.
  285. *
  286. * @since 3.3.0
  287. * @private
  288. *
  289. * @param {Object} event An event object.
  290. */
  291. _open: function( event ) {
  292. var self = this,
  293. o = this.options;
  294. if ( this.active || o.disabled || this.element.is(':hidden') )
  295. return;
  296. this.active = true;
  297. this._trigger( 'open', event, this._handoff() );
  298. this._trigger( 'show', event, this._handoff({
  299. opened: function() {
  300. self._trigger( 'opened', event, self._handoff() );
  301. }
  302. }));
  303. },
  304. /**
  305. * Closes and hides the pointer element.
  306. *
  307. * @since 3.3.0
  308. *
  309. * @param {Object} event An event object.
  310. */
  311. close: function( event ) {
  312. if ( !this.active || this.options.disabled )
  313. return;
  314. var self = this;
  315. this.active = false;
  316. this._trigger( 'close', event, this._handoff() );
  317. this._trigger( 'hide', event, this._handoff({
  318. closed: function() {
  319. self._trigger( 'closed', event, self._handoff() );
  320. }
  321. }));
  322. },
  323. /**
  324. * Puts the pointer on top by increasing the z-index.
  325. *
  326. * @since 3.3.0
  327. */
  328. sendToTop: function() {
  329. if ( this.active )
  330. this.pointer.css( 'z-index', zindex++ );
  331. },
  332. /**
  333. * Toggles the element between shown and hidden.
  334. *
  335. * @since 3.3.0
  336. *
  337. * @param {Object} event An event object.
  338. */
  339. toggle: function( event ) {
  340. if ( this.pointer.is(':hidden') )
  341. this.open( event );
  342. else
  343. this.close( event );
  344. },
  345. /**
  346. * Extends the pointer and the widget element with the supplied parameter, which
  347. * is either an element or a function.
  348. *
  349. * @since 3.3.0
  350. * @private
  351. *
  352. * @param {Object} extend The object to be merged into the original object.
  353. *
  354. * @return {Object} The extended object.
  355. */
  356. _handoff: function( extend ) {
  357. return $.extend({
  358. pointer: this.pointer,
  359. element: this.element
  360. }, extend);
  361. }
  362. });
  363. })(jQuery);