github.com/lenfree/buffalo@v0.7.3-0.20170207163156-891616ea4064/examples/html-resource/assets/rails.js (about)

     1  (function($, undefined) {
     2  
     3  /**
     4   * Unobtrusive scripting adapter for jQuery
     5   * https://github.com/rails/jquery-ujs
     6   *
     7   * Requires jQuery 1.8.0 or later.
     8   *
     9   * Released under the MIT license
    10   *
    11   */
    12  
    13    // Cut down on the number of issues from people inadvertently including jquery_ujs twice
    14    // by detecting and raising an error when it happens.
    15    'use strict';
    16  
    17    if ( $.rails !== undefined ) {
    18      $.error('jquery-ujs has already been loaded!');
    19    }
    20  
    21    // Shorthand to make it a little easier to call public rails functions from within rails.js
    22    var rails;
    23    var $document = $(document);
    24  
    25    $.rails = rails = {
    26      // Link elements bound by jquery-ujs
    27      linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
    28  
    29      // Button elements bound by jquery-ujs
    30      buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',
    31  
    32      // Select elements bound by jquery-ujs
    33      inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
    34  
    35      // Form elements bound by jquery-ujs
    36      formSubmitSelector: 'form',
    37  
    38      // Form input elements bound by jquery-ujs
    39      formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
    40  
    41      // Form input elements disabled during form submission
    42      disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
    43  
    44      // Form input elements re-enabled after form submission
    45      enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
    46  
    47      // Form required input elements
    48      requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',
    49  
    50      // Form file input elements
    51      fileInputSelector: 'input[name][type=file]:not([disabled])',
    52  
    53      // Link onClick disable selector with possible reenable after remote submission
    54      linkDisableSelector: 'a[data-disable-with], a[data-disable]',
    55  
    56      // Button onClick disable selector with possible reenable after remote submission
    57      buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',
    58  
    59      // Up-to-date Cross-Site Request Forgery token
    60      csrfToken: function() {
    61       return $('meta[name=csrf-token]').attr('content');
    62      },
    63  
    64      // URL param that must contain the CSRF token
    65      csrfParam: function() {
    66       return $('meta[name=csrf-param]').attr('content');
    67      },
    68  
    69      // Make sure that every Ajax request sends the CSRF token
    70      CSRFProtection: function(xhr) {
    71        var token = rails.csrfToken();
    72        if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    73      },
    74  
    75      // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
    76      refreshCSRFTokens: function(){
    77        $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
    78      },
    79  
    80      // Triggers an event on an element and returns false if the event result is false
    81      fire: function(obj, name, data) {
    82        var event = $.Event(name);
    83        obj.trigger(event, data);
    84        return event.result !== false;
    85      },
    86  
    87      // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
    88      confirm: function(message) {
    89        return confirm(message);
    90      },
    91  
    92      // Default ajax function, may be overridden with custom function in $.rails.ajax
    93      ajax: function(options) {
    94        return $.ajax(options);
    95      },
    96  
    97      // Default way to get an element's href. May be overridden at $.rails.href.
    98      href: function(element) {
    99        return element[0].href;
   100      },
   101  
   102      // Checks "data-remote" if true to handle the request through a XHR request.
   103      isRemote: function(element) {
   104        return element.data('remote') !== undefined && element.data('remote') !== false;
   105      },
   106  
   107      // Submits "remote" forms and links with ajax
   108      handleRemote: function(element) {
   109        var method, url, data, withCredentials, dataType, options;
   110  
   111        if (rails.fire(element, 'ajax:before')) {
   112          withCredentials = element.data('with-credentials') || null;
   113          dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);
   114  
   115          if (element.is('form')) {
   116            method = element.data('ujs:submit-button-formmethod') || element.attr('method');
   117            url = element.data('ujs:submit-button-formaction') || element.attr('action');
   118            data = $(element[0]).serializeArray();
   119            // memoized value from clicked submit button
   120            var button = element.data('ujs:submit-button');
   121            if (button) {
   122              data.push(button);
   123              element.data('ujs:submit-button', null);
   124            }
   125            element.data('ujs:submit-button-formmethod', null);
   126            element.data('ujs:submit-button-formaction', null);
   127          } else if (element.is(rails.inputChangeSelector)) {
   128            method = element.data('method');
   129            url = element.data('url');
   130            data = element.serialize();
   131            if (element.data('params')) data = data + '&' + element.data('params');
   132          } else if (element.is(rails.buttonClickSelector)) {
   133            method = element.data('method') || 'get';
   134            url = element.data('url');
   135            data = element.serialize();
   136            if (element.data('params')) data = data + '&' + element.data('params');
   137          } else {
   138            method = element.data('method');
   139            url = rails.href(element);
   140            data = element.data('params') || null;
   141          }
   142  
   143          options = {
   144            type: method || 'GET', data: data, dataType: dataType,
   145            // stopping the "ajax:beforeSend" event will cancel the ajax request
   146            beforeSend: function(xhr, settings) {
   147              if (settings.dataType === undefined) {
   148                xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
   149              }
   150              if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
   151                element.trigger('ajax:send', xhr);
   152              } else {
   153                return false;
   154              }
   155            },
   156            success: function(data, status, xhr) {
   157              element.trigger('ajax:success', [data, status, xhr]);
   158            },
   159            complete: function(xhr, status) {
   160              element.trigger('ajax:complete', [xhr, status]);
   161            },
   162            error: function(xhr, status, error) {
   163              element.trigger('ajax:error', [xhr, status, error]);
   164            },
   165            crossDomain: rails.isCrossDomain(url)
   166          };
   167  
   168          // There is no withCredentials for IE6-8 when
   169          // "Enable native XMLHTTP support" is disabled
   170          if (withCredentials) {
   171            options.xhrFields = {
   172              withCredentials: withCredentials
   173            };
   174          }
   175  
   176          // Only pass url to `ajax` options if not blank
   177          if (url) { options.url = url; }
   178  
   179          return rails.ajax(options);
   180        } else {
   181          return false;
   182        }
   183      },
   184  
   185      // Determines if the request is a cross domain request.
   186      isCrossDomain: function(url) {
   187        var originAnchor = document.createElement('a');
   188        originAnchor.href = location.href;
   189        var urlAnchor = document.createElement('a');
   190  
   191        try {
   192          urlAnchor.href = url;
   193          // This is a workaround to a IE bug.
   194          urlAnchor.href = urlAnchor.href;
   195  
   196          // If URL protocol is false or is a string containing a single colon
   197          // *and* host are false, assume it is not a cross-domain request
   198          // (should only be the case for IE7 and IE compatibility mode).
   199          // Otherwise, evaluate protocol and host of the URL against the origin
   200          // protocol and host.
   201          return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
   202            (originAnchor.protocol + '//' + originAnchor.host ===
   203              urlAnchor.protocol + '//' + urlAnchor.host));
   204        } catch (e) {
   205          // If there is an error parsing the URL, assume it is crossDomain.
   206          return true;
   207        }
   208      },
   209  
   210      // Handles "data-method" on links such as:
   211      // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
   212      handleMethod: function(link) {
   213        var href = rails.href(link),
   214          method = link.data('method'),
   215          target = link.attr('target'),
   216          csrfToken = rails.csrfToken(),
   217          csrfParam = rails.csrfParam(),
   218          form = $('<form method="post" action="' + href + '"></form>'),
   219          metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';
   220  
   221        if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
   222          metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
   223        }
   224  
   225        if (target) { form.attr('target', target); }
   226  
   227        form.hide().append(metadataInput).appendTo('body');
   228        form.submit();
   229      },
   230  
   231      // Helper function that returns form elements that match the specified CSS selector
   232      // If form is actually a "form" element this will return associated elements outside the from that have
   233      // the html form attribute set
   234      formElements: function(form, selector) {
   235        return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
   236      },
   237  
   238      /* Disables form elements:
   239        - Caches element value in 'ujs:enable-with' data store
   240        - Replaces element text with value of 'data-disable-with' attribute
   241        - Sets disabled property to true
   242      */
   243      disableFormElements: function(form) {
   244        rails.formElements(form, rails.disableSelector).each(function() {
   245          rails.disableFormElement($(this));
   246        });
   247      },
   248  
   249      disableFormElement: function(element) {
   250        var method, replacement;
   251  
   252        method = element.is('button') ? 'html' : 'val';
   253        replacement = element.data('disable-with');
   254  
   255        if (replacement !== undefined) {
   256          element.data('ujs:enable-with', element[method]());
   257          element[method](replacement);
   258        }
   259  
   260        element.prop('disabled', true);
   261        element.data('ujs:disabled', true);
   262      },
   263  
   264      /* Re-enables disabled form elements:
   265        - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
   266        - Sets disabled property to false
   267      */
   268      enableFormElements: function(form) {
   269        rails.formElements(form, rails.enableSelector).each(function() {
   270          rails.enableFormElement($(this));
   271        });
   272      },
   273  
   274      enableFormElement: function(element) {
   275        var method = element.is('button') ? 'html' : 'val';
   276        if (element.data('ujs:enable-with') !== undefined) {
   277          element[method](element.data('ujs:enable-with'));
   278          element.removeData('ujs:enable-with'); // clean up cache
   279        }
   280        element.prop('disabled', false);
   281        element.removeData('ujs:disabled');
   282      },
   283  
   284     /* For 'data-confirm' attribute:
   285        - Fires `confirm` event
   286        - Shows the confirmation dialog
   287        - Fires the `confirm:complete` event
   288  
   289        Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
   290        Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
   291        Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
   292        return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
   293     */
   294      allowAction: function(element) {
   295        var message = element.data('confirm'),
   296            answer = false, callback;
   297        if (!message) { return true; }
   298  
   299        if (rails.fire(element, 'confirm')) {
   300          try {
   301            answer = rails.confirm(message);
   302          } catch (e) {
   303            (console.error || console.log).call(console, e.stack || e);
   304          }
   305          callback = rails.fire(element, 'confirm:complete', [answer]);
   306        }
   307        return answer && callback;
   308      },
   309  
   310      // Helper function which checks for blank inputs in a form that match the specified CSS selector
   311      blankInputs: function(form, specifiedSelector, nonBlank) {
   312        var foundInputs = $(),
   313          input,
   314          valueToCheck,
   315          radiosForNameWithNoneSelected,
   316          radioName,
   317          selector = specifiedSelector || 'input,textarea',
   318          requiredInputs = form.find(selector),
   319          checkedRadioButtonNames = {};
   320  
   321        requiredInputs.each(function() {
   322          input = $(this);
   323          if (input.is('input[type=radio]')) {
   324  
   325            // Don't count unchecked required radio as blank if other radio with same name is checked,
   326            // regardless of whether same-name radio input has required attribute or not. The spec
   327            // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
   328            radioName = input.attr('name');
   329  
   330            // Skip if we've already seen the radio with this name.
   331            if (!checkedRadioButtonNames[radioName]) {
   332  
   333              // If none checked
   334              if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
   335                radiosForNameWithNoneSelected = form.find(
   336                  'input[type=radio][name="' + radioName + '"]');
   337                foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
   338              }
   339  
   340              // We only need to check each name once.
   341              checkedRadioButtonNames[radioName] = radioName;
   342            }
   343          } else {
   344            valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
   345            if (valueToCheck === nonBlank) {
   346              foundInputs = foundInputs.add(input);
   347            }
   348          }
   349        });
   350        return foundInputs.length ? foundInputs : false;
   351      },
   352  
   353      // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
   354      nonBlankInputs: function(form, specifiedSelector) {
   355        return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
   356      },
   357  
   358      // Helper function, needed to provide consistent behavior in IE
   359      stopEverything: function(e) {
   360        $(e.target).trigger('ujs:everythingStopped');
   361        e.stopImmediatePropagation();
   362        return false;
   363      },
   364  
   365      //  Replace element's html with the 'data-disable-with' after storing original html
   366      //  and prevent clicking on it
   367      disableElement: function(element) {
   368        var replacement = element.data('disable-with');
   369  
   370        if (replacement !== undefined) {
   371          element.data('ujs:enable-with', element.html()); // store enabled state
   372          element.html(replacement);
   373        }
   374  
   375        element.bind('click.railsDisable', function(e) { // prevent further clicking
   376          return rails.stopEverything(e);
   377        });
   378        element.data('ujs:disabled', true);
   379      },
   380  
   381      // Restore element to its original state which was disabled by 'disableElement' above
   382      enableElement: function(element) {
   383        if (element.data('ujs:enable-with') !== undefined) {
   384          element.html(element.data('ujs:enable-with')); // set to old enabled state
   385          element.removeData('ujs:enable-with'); // clean up cache
   386        }
   387        element.unbind('click.railsDisable'); // enable element
   388        element.removeData('ujs:disabled');
   389      }
   390    };
   391  
   392    if (rails.fire($document, 'rails:attachBindings')) {
   393  
   394      $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});
   395  
   396      // This event works the same as the load event, except that it fires every
   397      // time the page is loaded.
   398      //
   399      // See https://github.com/rails/jquery-ujs/issues/357
   400      // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
   401      $(window).on('pageshow.rails', function () {
   402        $($.rails.enableSelector).each(function () {
   403          var element = $(this);
   404  
   405          if (element.data('ujs:disabled')) {
   406            $.rails.enableFormElement(element);
   407          }
   408        });
   409  
   410        $($.rails.linkDisableSelector).each(function () {
   411          var element = $(this);
   412  
   413          if (element.data('ujs:disabled')) {
   414            $.rails.enableElement(element);
   415          }
   416        });
   417      });
   418  
   419      $document.on('ajax:complete', rails.linkDisableSelector, function() {
   420          rails.enableElement($(this));
   421      });
   422  
   423      $document.on('ajax:complete', rails.buttonDisableSelector, function() {
   424          rails.enableFormElement($(this));
   425      });
   426  
   427      $document.on('click.rails', rails.linkClickSelector, function(e) {
   428        var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
   429        if (!rails.allowAction(link)) return rails.stopEverything(e);
   430  
   431        if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);
   432  
   433        if (rails.isRemote(link)) {
   434          if (metaClick && (!method || method === 'GET') && !data) { return true; }
   435  
   436          var handleRemote = rails.handleRemote(link);
   437          // Response from rails.handleRemote() will either be false or a deferred object promise.
   438          if (handleRemote === false) {
   439            rails.enableElement(link);
   440          } else {
   441            handleRemote.fail( function() { rails.enableElement(link); } );
   442          }
   443          return false;
   444  
   445        } else if (method) {
   446          rails.handleMethod(link);
   447          return false;
   448        }
   449      });
   450  
   451      $document.on('click.rails', rails.buttonClickSelector, function(e) {
   452        var button = $(this);
   453  
   454        if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);
   455  
   456        if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);
   457  
   458        var handleRemote = rails.handleRemote(button);
   459        // Response from rails.handleRemote() will either be false or a deferred object promise.
   460        if (handleRemote === false) {
   461          rails.enableFormElement(button);
   462        } else {
   463          handleRemote.fail( function() { rails.enableFormElement(button); } );
   464        }
   465        return false;
   466      });
   467  
   468      $document.on('change.rails', rails.inputChangeSelector, function(e) {
   469        var link = $(this);
   470        if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);
   471  
   472        rails.handleRemote(link);
   473        return false;
   474      });
   475  
   476      $document.on('submit.rails', rails.formSubmitSelector, function(e) {
   477        var form = $(this),
   478          remote = rails.isRemote(form),
   479          blankRequiredInputs,
   480          nonBlankFileInputs;
   481  
   482        if (!rails.allowAction(form)) return rails.stopEverything(e);
   483  
   484        // Skip other logic when required values are missing or file upload is present
   485        if (form.attr('novalidate') === undefined) {
   486          if (form.data('ujs:formnovalidate-button') === undefined) {
   487            blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
   488            if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
   489              return rails.stopEverything(e);
   490            }
   491          } else {
   492            // Clear the formnovalidate in case the next button click is not on a formnovalidate button
   493            // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
   494            form.data('ujs:formnovalidate-button', undefined);
   495          }
   496        }
   497  
   498        if (remote) {
   499          nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
   500          if (nonBlankFileInputs) {
   501            // Slight timeout so that the submit button gets properly serialized
   502            // (make it easy for event handler to serialize form without disabled values)
   503            setTimeout(function(){ rails.disableFormElements(form); }, 13);
   504            var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
   505  
   506            // Re-enable form elements if event bindings return false (canceling normal form submission)
   507            if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }
   508  
   509            return aborted;
   510          }
   511  
   512          rails.handleRemote(form);
   513          return false;
   514  
   515        } else {
   516          // Slight timeout so that the submit button gets properly serialized
   517          setTimeout(function(){ rails.disableFormElements(form); }, 13);
   518        }
   519      });
   520  
   521      $document.on('click.rails', rails.formInputClickSelector, function(event) {
   522        var button = $(this);
   523  
   524        if (!rails.allowAction(button)) return rails.stopEverything(event);
   525  
   526        // Register the pressed submit button
   527        var name = button.attr('name'),
   528          data = name ? {name:name, value:button.val()} : null;
   529  
   530        var form = button.closest('form');
   531        if (form.length === 0) {
   532          form = $('#' + button.attr('form'));
   533        }
   534        form.data('ujs:submit-button', data);
   535  
   536        // Save attributes from button
   537        form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
   538        form.data('ujs:submit-button-formaction', button.attr('formaction'));
   539        form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
   540      });
   541  
   542      $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
   543        if (this === event.target) rails.disableFormElements($(this));
   544      });
   545  
   546      $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
   547        if (this === event.target) rails.enableFormElements($(this));
   548      });
   549  
   550      $(function(){
   551        rails.refreshCSRFTokens();
   552      });
   553    }
   554  
   555  })( jQuery );