2 * jQuery confirmable plugin
4 * Released under the MIT License.
6 * @author Bartosz Dziewoński
8 * @class jQuery.plugin.confirmable
11 var identity = function ( data
) {
15 // eslint-disable-next-line valid-jsdoc
17 * Enable inline confirmation for given clickable element (like `<a />` or `<button />`).
19 * An additional inline confirmation step being shown before the default action is carried out on
22 * Calling `.confirmable( { handler: function () { … } } )` will fire the handler only after the
25 * The element will have the `jquery-confirmable-element` class added to it when it's clicked for
26 * the first time, which has `white-space: nowrap;` and `display: inline-block;` defined in CSS.
27 * If the computed values for the element are different when you make it confirmable, you might
28 * encounter unexpected behavior.
30 * @param {Object} [options]
31 * @param {string} [options.events='click'] Events to hook to.
32 * @param {Function} [options.wrapperCallback] Callback to fire when preparing confirmable
33 * interface. Receives the interface jQuery object as the only parameter.
34 * @param {Function} [options.buttonCallback] Callback to fire when preparing confirmable buttons.
35 * It is fired separately for the 'Yes' and 'No' button. Receives the button jQuery object as
36 * the first parameter and 'yes' or 'no' as the second.
37 * @param {Function} [options.handler] Callback to fire when the action is confirmed (user clicks
39 * @param {string} [options.delegate] Optional selector used for jQuery event delegation
40 * @param {string} [options.i18n] Text to use for interface elements.
41 * @param {string} [options.i18n.space] Word separator to place between the three text messages.
42 * @param {string} [options.i18n.confirm] Text to use for the confirmation question.
43 * @param {string} [options.i18n.yes] Text to use for the 'Yes' button.
44 * @param {string} [options.i18n.no] Text to use for the 'No' button.
45 * @param {string} [options.i18n.yesTitle] Title text to use for the 'Yes' button.
46 * @param {string} [options.i18n.noTitle] Title text to use for the 'No' button.
50 $.fn
.confirmable = function ( options
) {
51 options
= $.extend( true, {}, $.fn
.confirmable
.defaultOptions
, options
|| {} );
53 if ( options
.delegate
=== null ) {
54 return this.on( options
.events
, function ( e
) {
55 $.fn
.confirmable
.handler( e
, options
);
59 return this.on( options
.events
, options
.delegate
, function ( e
) {
60 $.fn
.confirmable
.handler( e
, options
);
64 $.fn
.confirmable
.handler = function ( event
, options
) {
65 var $element
, $text
, $buttonYes
, $buttonNo
, $wrapper
, $interface, $elementClone
,
66 interfaceWidth
, elementWidth
, rtl
, positionOffscreen
, positionRestore
, sideMargin
;
68 $element
= $( event
.target
);
70 if ( $element
.data( 'jquery-confirmable-button' ) ) {
71 // We're running on a clone of this element that represents the 'Yes' or 'No' button.
72 // (This should never happen for the 'No' case unless calling code does bad things.)
76 // Only prevent native event handling. Stopping other JavaScript event handlers
77 // is impossible because they might have already run (we have no control over the order).
78 event
.preventDefault();
80 rtl
= $element
.css( 'direction' ) === 'rtl';
82 positionOffscreen
= { position
: 'absolute', right
: '-9999px' };
83 positionRestore
= { position
: '', right
: '' };
84 sideMargin
= 'marginRight';
86 positionOffscreen
= { position
: 'absolute', left
: '-9999px' };
87 positionRestore
= { position
: '', left
: '' };
88 sideMargin
= 'marginLeft';
91 if ( $element
.hasClass( 'jquery-confirmable-element' ) ) {
92 $wrapper
= $element
.closest( '.jquery-confirmable-wrapper' );
93 $interface = $wrapper
.find( '.jquery-confirmable-interface' );
94 $text
= $interface.find( '.jquery-confirmable-text' );
95 $buttonYes
= $interface.find( '.jquery-confirmable-button-yes' );
96 $buttonNo
= $interface.find( '.jquery-confirmable-button-no' );
98 interfaceWidth
= $interface.data( 'jquery-confirmable-width' );
99 elementWidth
= $element
.data( 'jquery-confirmable-width' );
101 $elementClone
= $element
.clone( true );
102 $element
.addClass( 'jquery-confirmable-element' );
104 elementWidth
= $element
.width();
105 $element
.data( 'jquery-confirmable-width', elementWidth
);
107 $wrapper
= $( '<span>' )
108 .addClass( 'jquery-confirmable-wrapper' );
109 $element
.wrap( $wrapper
);
111 // Build the mini-dialog
112 $text
= $( '<span>' )
113 .addClass( 'jquery-confirmable-text' )
114 .text( options
.i18n
.confirm
);
116 // Clone original element along with event handlers to easily replicate its behavior.
117 // We could fiddle with .trigger() etc., but that is troublesome especially since
118 // Safari doesn't implement .click() on <a> links and jQuery follows suit.
119 $buttonYes
= $elementClone
.clone( true )
120 .addClass( 'jquery-confirmable-button jquery-confirmable-button-yes' )
121 .data( 'jquery-confirmable-button', true )
122 .text( options
.i18n
.yes
);
123 if ( options
.handler
) {
124 $buttonYes
.on( options
.events
, options
.handler
);
126 if ( options
.i18n
.yesTitle
) {
127 $buttonYes
.attr( 'title', options
.i18n
.yesTitle
);
129 $buttonYes
= options
.buttonCallback( $buttonYes
, 'yes' );
131 // Clone it without any events and prevent default action to represent the 'No' button.
132 $buttonNo
= $elementClone
.clone( false )
133 .addClass( 'jquery-confirmable-button jquery-confirmable-button-no' )
134 .data( 'jquery-confirmable-button', true )
135 .text( options
.i18n
.no
)
136 .on( options
.events
, function ( e
) {
137 $element
.css( sideMargin
, 0 );
138 $interface.css( 'width', 0 );
141 if ( options
.i18n
.noTitle
) {
142 $buttonNo
.attr( 'title', options
.i18n
.noTitle
);
144 $buttonNo
.removeAttr( 'title' );
146 $buttonNo
= options
.buttonCallback( $buttonNo
, 'no' );
148 // Prevent memory leaks
149 $elementClone
.remove();
151 $interface = $( '<span>' )
152 .addClass( 'jquery-confirmable-interface' )
153 .append( $text
, options
.i18n
.space
, $buttonYes
, options
.i18n
.space
, $buttonNo
);
154 $interface = options
.wrapperCallback( $interface );
156 // Render offscreen to measure real width
157 $interface.css( positionOffscreen
);
158 // Insert it in the correct place while we're at it
159 $element
.after( $interface );
160 interfaceWidth
= $interface.width();
161 $interface.data( 'jquery-confirmable-width', interfaceWidth
);
162 $interface.css( positionRestore
);
164 // Hide to animate the transition later
165 $interface.css( 'width', 0 );
168 // Hide element, show interface. This triggers both transitions.
169 // In a timeout to trigger the 'width' transition.
170 setTimeout( function () {
171 $element
.css( sideMargin
, -elementWidth
);
172 $interface.css( 'width', interfaceWidth
);
177 * Default options. Overridable primarily for internationalisation handling.
178 * @property {Object} defaultOptions
180 $.fn
.confirmable
.defaultOptions
= {
182 wrapperCallback
: identity
,
183 buttonCallback
: identity
,
188 confirm
: 'Are you sure?',