├── .gitignore ├── .npmignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bower.json ├── data-confirm-modal.gemspec ├── lib ├── data-confirm-modal.rb └── data-confirm-modal │ ├── engine.rb │ └── version.rb ├── package.json └── vendor └── assets └── javascripts └── data-confirm-modal.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | node_modules 19 | *.tgz 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Use an allowlist approach to ignore everything but /vendor 2 | # LICENSE, package.json, and README (with its varations) are never ignored 3 | # https://npm.github.io/publishing-pkgs-docs/publishing/the-npmignore-file.html 4 | * 5 | !/vendor 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Marcello Barnaba 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data-Confirm Modal 2 | 3 | [![Gem Version](https://badge.fury.io/rb/data-confirm-modal.svg)](https://badge.fury.io/rb/data-confirm-modal) 4 | 5 | Uses [Bootstrap's modals](https://twitter.github.io/bootstrap/javascript.html#modals) 6 | in place of the browser's builtin `confirm()` API for links generated through Rails' 7 | helpers with the `:confirm` option. 8 | 9 | Any link with the `data-confirm` attribute will trigger a Bootstrap modal. 10 | 11 | HTML in the modal supported, and also the ability to have the user input a 12 | certain value, for extra willingness confirmation (inspired by GitHub's 13 | "delete repository" function). 14 | 15 | ## Installation 16 | 17 | ### Sprockets 18 | 19 | Add this line to your application's Gemfile: 20 | 21 | gem 'data-confirm-modal' 22 | 23 | The library supports Bootstrap 3 and 4. If you are stuck on Bootstrap 2.3, you must 24 | use the `bootstrap2` branch: 25 | 26 | gem 'data-confirm-modal', github: 'ifad/data-confirm-modal', branch: 'bootstrap2' 27 | 28 | Then execute: 29 | 30 | $ bundle 31 | 32 | And then require the Javascript from your `application.js`: 33 | 34 | //= require data-confirm-modal 35 | 36 | ### Webpacker 37 | 38 | Gem is not required for this use case. 39 | 40 | Add the following package: 41 | 42 | yarn add data-confirm-modal 43 | 44 | Autoload jQuery in `config/webpack/environment.js`: 45 | 46 | const { environment } = require('@rails/webpacker') 47 | 48 | const webpack = require('webpack') 49 | 50 | environment.plugins.prepend( 51 | 'Provide', 52 | new webpack.ProvidePlugin({ 53 | jQuery: 'jquery', 54 | }) 55 | ) 56 | 57 | module.exports = environment 58 | 59 | And then require the JavaScript from your pack's `application.js`, after `require('@rails/ujs').start()`: 60 | 61 | require('data-confirm-modal') 62 | 63 | ## Usage 64 | 65 | ### With Rails: [example B3](https://jsfiddle.net/zpu4u6mh/), [example B4](https://jsfiddle.net/02t20nwx/) 66 | 67 | By default, the Gem's Javascript overrides Rails' [data-confirm behaviour][] 68 | for you, with no change required to your code. The modal is applicable to 69 | ``, `' 172 | 173 | var modalTitle = '<'+titleElement+' id="'+id+'Label" class="'+titleClass+'"> ' 174 | var modalHeader; 175 | 176 | // Bootstrap 3 and 4 have different DOMs and different CSS. In B4, the 177 | // modalHeader is display:flex and the modalClose uses negative margins, 178 | // so it can stay after the modalTitle. 179 | // 180 | // In B3, the close button floats to the right, so it must stay before 181 | // the modalTitle. 182 | // 183 | switch (bootstrapVersion) { 184 | case 3: 185 | modalHeader = modalClose + modalTitle; 186 | break; 187 | case 4: 188 | modalHeader = modalTitle + modalClose; 189 | break; 190 | } 191 | 192 | var modal = $( 193 | '' 207 | ); 208 | 209 | // Make sure it's always the top zindex 210 | var highest, current; 211 | highest = current = settings.zIndex; 212 | $('.modal.in').not('#'+id).each(function() { 213 | current = parseInt($(this).css('z-index'), 10); 214 | if(current > highest) { 215 | highest = current 216 | } 217 | }); 218 | modal.css('z-index', parseInt(highest) + 1); 219 | 220 | modal.find('.modal-title').text(options.title || settings.title); 221 | 222 | var body = modal.find('.modal-body'); 223 | 224 | $.each((options.text||'').split(/\n{2}/), function (i, piece) { 225 | body.append($('

').html(piece)); 226 | }); 227 | 228 | var commit = modal.find('.commit'); 229 | commit.text(options.commit || settings.commit); 230 | commit.addClass(options.commitClass || settings.commitClass); 231 | 232 | var cancel = modal.find('.cancel'); 233 | cancel.text(options.cancel || settings.cancel); 234 | cancel.addClass(options.cancelClass || settings.cancelClass); 235 | 236 | if (options.remote) { 237 | commit.attr('data-dismiss', 'modal'); 238 | } 239 | 240 | if (options.verify || options.verifyRegexp) { 241 | commit.prop('disabled', true); 242 | 243 | var isMatch; 244 | if (options.verifyRegexp) { 245 | var caseInsensitive = options.verifyRegexpCaseInsensitive; 246 | var regexp = options.verifyRegexp; 247 | var re = new RegExp(regexp, caseInsensitive ? 'i' : ''); 248 | 249 | isMatch = function (input) { return input.match(re) }; 250 | } else { 251 | isMatch = function (input) { return options.verify == input }; 252 | } 253 | 254 | var verification = $('', {"type": 'text', "class": settings.verifyClass}).on('keyup', function () { 255 | commit.prop('disabled', !isMatch($(this).val())); 256 | }); 257 | 258 | modal.on('shown.bs.modal', function () { 259 | verification.trigger('focus'); 260 | }); 261 | 262 | modal.on('hidden.bs.modal', function () { 263 | verification.val('').trigger('keyup'); 264 | }); 265 | 266 | if (options.verifyLabel) 267 | body.append($('

', {text: options.verifyLabel})) 268 | 269 | body.append(verification); 270 | } 271 | 272 | var focus_element; 273 | if (options.focus) { 274 | focus_element = options.focus; 275 | } else if (options.method == 'delete') { 276 | focus_element = 'cancel' 277 | } else { 278 | focus_element = settings.focus; 279 | } 280 | focus_element = modal.find('.' + focus_element); 281 | 282 | modal.on('shown.bs.modal', function () { 283 | focus_element.trigger('focus'); 284 | }); 285 | 286 | $('body').append(modal); 287 | 288 | modal.spawn = function() { 289 | return modal.modal($.extend({}, { 290 | backdrop: options.backdrop, 291 | keyboard: options.keyboard, 292 | show: options.show 293 | })); 294 | }; 295 | 296 | return modal; 297 | }; 298 | 299 | 300 | /** 301 | * Returns a modal already built for the given element or builds a new one, 302 | * caching it into the element's `confirm-modal` data attribute. 303 | */ 304 | $.fn.getConfirmModal = function () { 305 | var element = $(this), modal = element.data('confirm-modal'); 306 | 307 | if (!modal) { 308 | modal = buildElementModal(element); 309 | element.data('confirm-modal', modal); 310 | } 311 | 312 | return modal; 313 | }; 314 | 315 | $.fn.confirmModal = function () { 316 | var modal = $(this).getConfirmModal(); 317 | 318 | modal.spawn(); 319 | 320 | return modal; 321 | }; 322 | 323 | if (window.Rails || $.rails) { 324 | /** 325 | * Attaches to Rails' UJS adapter's 'confirm' event, triggered on elements 326 | * having a `data-confirm` attribute set. 327 | * 328 | * If the modal is not visible, then it is spawned and the default Rails 329 | * confirmation dialog is canceled. 330 | * 331 | * If the modal is visible, it means the handler is being called by the 332 | * modal commit button click handler, as such the user has successfully 333 | * clicked on the confirm button. In this case Rails' confirm function 334 | * is briefly overriden, and afterwards reset when the modal is closed. 335 | * 336 | */ 337 | var window_confirm = window.confirm; 338 | 339 | $(document).on('confirm', settings.elements.join(', '), function() { 340 | var modal = $(this).getConfirmModal(); 341 | 342 | if (!modal.is(':visible')) { 343 | modal.spawn(); 344 | 345 | // Cancel Rails' confirmation 346 | return false; 347 | 348 | } else { 349 | // Modal has been confirmed. Override Rails' handler 350 | window.confirm = function () { 351 | return true; 352 | } 353 | 354 | modal.one('hidden.bs.modal', function() { 355 | // Reset it after modal is closed. 356 | window.confirm = window_confirm; 357 | }); 358 | 359 | // Proceed with Rails' handlers 360 | return true; 361 | } 362 | }); 363 | } 364 | 365 | })(jQuery); 366 | --------------------------------------------------------------------------------