Merge "Use HTMLForm to generate the form on Special:ListFiles"
[lhc/web/wiklou.git] / resources / src / jquery / jquery.confirmable.js
1 /**
2 * jQuery confirmable plugin
3 *
4 * Released under the MIT License.
5 *
6 * @author Bartosz Dziewoński
7 *
8 * @class jQuery.plugin.confirmable
9 */
10 ( function ( $ ) {
11 var identity = function ( data ) {
12 return data;
13 };
14
15 /**
16 * Enable inline confirmation for given clickable element (like `<a />` or `<button />`).
17 *
18 * An additional inline confirmation step being shown before the default action is carried out on
19 * click.
20 *
21 * Calling `.confirmable( { handler: function () { … } } )` will fire the handler only after the
22 * confirmation step.
23 *
24 * The element will have the `jquery-confirmable-element` class added to it when it's clicked for
25 * the first time, which has `white-space: nowrap;` and `display: inline-block;` defined in CSS.
26 * If the computed values for the element are different when you make it confirmable, you might
27 * encounter unexpected behavior.
28 *
29 * @param {Object} [options]
30 * @param {string} [options.events='click'] Events to hook to.
31 * @param {Function} [options.wrapperCallback] Callback to fire when preparing confirmable
32 * interface. Receives the interface jQuery object as the only parameter.
33 * @param {Function} [options.buttonCallback] Callback to fire when preparing confirmable buttons.
34 * It is fired separately for the 'Yes' and 'No' button. Receives the button jQuery object as
35 * the first parameter and 'yes' or 'no' as the second.
36 * @param {Function} [options.handler] Callback to fire when the action is confirmed (user clicks
37 * the 'Yes' button).
38 * @param {string} [options.i18n] Text to use for interface elements.
39 * @param {string} [options.i18n.confirm] Text to use for the confirmation question.
40 * @param {string} [options.i18n.yes] Text to use for the 'Yes' button.
41 * @param {string} [options.i18n.no] Text to use for the 'No' button.
42 *
43 * @chainable
44 */
45 $.fn.confirmable = function ( options ) {
46 options = $.extend( true, {}, $.fn.confirmable.defaultOptions, options || {} );
47
48 return this.on( options.events, function ( e ) {
49 var $element, $text, $buttonYes, $buttonNo, $wrapper, $interface, $elementClone,
50 interfaceWidth, elementWidth, rtl, positionOffscreen, positionRestore, sideMargin;
51
52 $element = $( this );
53
54 if ( $element.data( 'jquery-confirmable-button' ) ) {
55 // We're running on a clone of this element that represents the 'Yes' or 'No' button.
56 // (This should never happen for the 'No' case unless calling code does bad things.)
57 return;
58 }
59
60 // Only prevent native event handling. Stopping other JavaScript event handlers
61 // is impossible because they might have already run (we have no control over the order).
62 e.preventDefault();
63
64 rtl = $element.css( 'direction' ) === 'rtl';
65 if ( rtl ) {
66 positionOffscreen = { position: 'absolute', right: '-9999px' };
67 positionRestore = { position: '', right: '' };
68 sideMargin = 'marginRight';
69 } else {
70 positionOffscreen = { position: 'absolute', left: '-9999px' };
71 positionRestore = { position: '', left: '' };
72 sideMargin = 'marginLeft';
73 }
74
75 if ( $element.hasClass( 'jquery-confirmable-element' ) ) {
76 $wrapper = $element.closest( '.jquery-confirmable-wrapper' );
77 $interface = $wrapper.find( '.jquery-confirmable-interface' );
78 $text = $interface.find( '.jquery-confirmable-text' );
79 $buttonYes = $interface.find( '.jquery-confirmable-button-yes' );
80 $buttonNo = $interface.find( '.jquery-confirmable-button-no' );
81
82 interfaceWidth = $interface.data( 'jquery-confirmable-width' );
83 elementWidth = $element.data( 'jquery-confirmable-width' );
84 } else {
85 $elementClone = $element.clone( true );
86 $element.addClass( 'jquery-confirmable-element' );
87
88 elementWidth = $element.width();
89 $element.data( 'jquery-confirmable-width', elementWidth );
90
91 $wrapper = $( '<span>' )
92 .addClass( 'jquery-confirmable-wrapper' );
93 $element.wrap( $wrapper );
94
95 // Build the mini-dialog
96 $text = $( '<span>' )
97 .addClass( 'jquery-confirmable-text' )
98 .text( options.i18n.confirm );
99
100 // Clone original element along with event handlers to easily replicate its behavior.
101 // We could fiddle with .trigger() etc., but that is troublesome especially since
102 // Safari doesn't implement .click() on <a> links and jQuery follows suit.
103 $buttonYes = $elementClone.clone( true )
104 .addClass( 'jquery-confirmable-button jquery-confirmable-button-yes' )
105 .data( 'jquery-confirmable-button', true )
106 .text( options.i18n.yes );
107 if ( options.handler ) {
108 $buttonYes.on( options.events, options.handler );
109 }
110 $buttonYes = options.buttonCallback( $buttonYes, 'yes' );
111
112 // Clone it without any events and prevent default action to represent the 'No' button.
113 $buttonNo = $elementClone.clone( false )
114 .addClass( 'jquery-confirmable-button jquery-confirmable-button-no' )
115 .data( 'jquery-confirmable-button', true )
116 .text( options.i18n.no )
117 .on( options.events, function ( e ) {
118 $element.css( sideMargin, 0 );
119 $interface.css( 'width', 0 );
120 e.preventDefault();
121 } );
122 $buttonNo = options.buttonCallback( $buttonNo, 'no' );
123
124 // Prevent memory leaks
125 $elementClone.remove();
126
127 $interface = $( '<span>' )
128 .addClass( 'jquery-confirmable-interface' )
129 .append( $text, $buttonYes, $buttonNo );
130 $interface = options.wrapperCallback( $interface );
131
132 // Render offscreen to measure real width
133 $interface.css( positionOffscreen );
134 // Insert it in the correct place while we're at it
135 $element.after( $interface );
136 interfaceWidth = $interface.width();
137 $interface.data( 'jquery-confirmable-width', interfaceWidth );
138 $interface.css( positionRestore );
139
140 // Hide to animate the transition later
141 $interface.css( 'width', 0 );
142 }
143
144 // Hide element, show interface. This triggers both transitions.
145 // In a timeout to trigger the 'width' transition.
146 setTimeout( function () {
147 $element.css( sideMargin, -elementWidth );
148 $interface.css( 'width', interfaceWidth );
149 }, 1 );
150 } );
151 };
152
153 /**
154 * Default options. Overridable primarily for internationalisation handling.
155 * @property {Object} defaultOptions
156 */
157 $.fn.confirmable.defaultOptions = {
158 events: 'click',
159 wrapperCallback: identity,
160 buttonCallback: identity,
161 handler: null,
162 i18n: {
163 confirm: 'Are you sure?',
164 yes: 'Yes',
165 no: 'No'
166 }
167 };
168 }( jQuery ) );