aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/app/assets
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/app/assets')
-rw-r--r--actionview/app/assets/javascripts/MIT-LICENSE20
-rw-r--r--actionview/app/assets/javascripts/README.md57
-rw-r--r--actionview/app/assets/javascripts/rails-ujs.coffee39
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/BANNER.js5
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee30
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/disable.coffee91
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/method.coffee34
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/remote.coffee90
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/start.coffee71
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee97
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee4
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee25
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee35
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee68
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/form.coffee36
15 files changed, 702 insertions, 0 deletions
diff --git a/actionview/app/assets/javascripts/MIT-LICENSE b/actionview/app/assets/javascripts/MIT-LICENSE
new file mode 100644
index 0000000000..28e1b12496
--- /dev/null
+++ b/actionview/app/assets/javascripts/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007-2018 Rails Core team
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
new file mode 100644
index 0000000000..b74fa1afad
--- /dev/null
+++ b/actionview/app/assets/javascripts/README.md
@@ -0,0 +1,57 @@
+# Ruby on Rails unobtrusive scripting adapter
+
+This unobtrusive scripting support file is developed for the Ruby on Rails framework, but is not strictly tied to any specific backend. You can drop this into any application to:
+
+- force confirmation dialogs for various actions;
+- make non-GET requests from hyperlinks;
+- make forms or hyperlinks submit data asynchronously with Ajax;
+- have submit buttons become automatically disabled on form submit to prevent double-clicking.
+
+These features are achieved by adding certain [`data` attributes][data] to your HTML markup. In Rails, they are added by the framework's template helpers.
+
+## Optional prerequisites
+
+Note that the `data` attributes this library adds are a feature of HTML5. If you're not targeting HTML5, these attributes may make your HTML to fail [validation][validator]. However, this shouldn't create any issues for web browsers or other user agents.
+
+## Installation
+
+### NPM
+
+ npm install rails-ujs --save
+
+### Yarn
+
+ yarn add rails-ujs
+
+Ensure that `.yarnclean` does not include `assets` if you use [yarn autoclean](https://yarnpkg.com/lang/en/docs/cli/autoclean/).
+
+## Usage
+
+### Asset pipeline
+
+In a conventional Rails application that uses the asset pipeline, require `rails-ujs` in your `application.js` manifest:
+
+```javascript
+//= require rails-ujs
+```
+
+### ES2015+
+
+If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file:
+
+```javascript
+import Rails from 'rails-ujs';
+Rails.start()
+```
+
+## How to run tests
+
+Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser.
+
+## License
+
+rails-ujs is released under the [MIT License](MIT-LICENSE).
+
+[data]: https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes"
+[validator]: http://validator.w3.org/
+[csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee
new file mode 100644
index 0000000000..bd6e9bb881
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs.coffee
@@ -0,0 +1,39 @@
+#= require ./rails-ujs/BANNER
+#= export Rails
+#= require_self
+#= require_tree ./rails-ujs/utils
+#= require_tree ./rails-ujs/features
+#= require ./rails-ujs/start
+
+@Rails =
+ # Link elements bound by rails-ujs
+ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
+
+ # Button elements bound by rails-ujs
+ buttonClickSelector:
+ selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
+ exclude: 'form button'
+
+ # Select elements bound by rails-ujs
+ inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
+
+ # Form elements bound by rails-ujs
+ formSubmitSelector: 'form'
+
+ # Form input elements bound by rails-ujs
+ 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])'
+
+ # Form input elements disabled during form submission
+ formDisableSelector: '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'
+
+ # Form input elements re-enabled after form submission
+ formEnableSelector: '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'
+
+ # Form file input elements
+ fileInputSelector: 'input[name][type=file]:not([disabled])'
+
+ # Link onClick disable selector with possible reenable after remote submission
+ linkDisableSelector: 'a[data-disable-with], a[data-disable]'
+
+ # Button onClick disable selector with possible reenable after remote submission
+ buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
diff --git a/actionview/app/assets/javascripts/rails-ujs/BANNER.js b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
new file mode 100644
index 0000000000..47ecd66003
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/BANNER.js
@@ -0,0 +1,5 @@
+/*
+Unobtrusive JavaScript
+https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
+Released under the MIT license
+ */
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
new file mode 100644
index 0000000000..0738ffcdc9
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
@@ -0,0 +1,30 @@
+#= require_tree ../utils
+
+{ fire, stopEverything } = Rails
+
+Rails.handleConfirm = (e) ->
+ stopEverything(e) unless allowAction(this)
+
+# Default confirm dialog, may be overridden with custom confirm dialog in Rails.confirm
+Rails.confirm = (message, element) ->
+ confirm(message)
+
+# For 'data-confirm' attribute:
+# - Fires `confirm` event
+# - Shows the confirmation dialog
+# - Fires the `confirm:complete` event
+#
+# Returns `true` if no function stops the chain and user chose yes `false` otherwise.
+# Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
+# Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
+# return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
+allowAction = (element) ->
+ message = element.getAttribute('data-confirm')
+ return true unless message
+
+ answer = false
+ if fire(element, 'confirm')
+ try answer = Rails.confirm(message, element)
+ callback = fire(element, 'confirm:complete', [answer])
+
+ answer and callback
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
new file mode 100644
index 0000000000..a8c692ee62
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee
@@ -0,0 +1,91 @@
+#= require_tree ../utils
+
+{ matches, getData, setData, stopEverything, formElements } = Rails
+
+Rails.handleDisabledElement = (e) ->
+ element = this
+ stopEverything(e) if element.disabled
+
+# Unified function to enable an element (link, button and form)
+Rails.enableElement = (e) ->
+ if e instanceof Event
+ return if isXhrRedirect(e)
+ element = e.target
+ else
+ element = e
+
+ if matches(element, Rails.linkDisableSelector)
+ enableLinkElement(element)
+ else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formEnableSelector)
+ enableFormElement(element)
+ else if matches(element, Rails.formSubmitSelector)
+ enableFormElements(element)
+
+# Unified function to disable an element (link, button and form)
+Rails.disableElement = (e) ->
+ element = if e instanceof Event then e.target else e
+ if matches(element, Rails.linkDisableSelector)
+ disableLinkElement(element)
+ else if matches(element, Rails.buttonDisableSelector) or matches(element, Rails.formDisableSelector)
+ disableFormElement(element)
+ else if matches(element, Rails.formSubmitSelector)
+ disableFormElements(element)
+
+# Replace element's html with the 'data-disable-with' after storing original html
+# and prevent clicking on it
+disableLinkElement = (element) ->
+ replacement = element.getAttribute('data-disable-with')
+ if replacement?
+ setData(element, 'ujs:enable-with', element.innerHTML) # store enabled state
+ element.innerHTML = replacement
+ element.addEventListener('click', stopEverything) # prevent further clicking
+ setData(element, 'ujs:disabled', true)
+
+# Restore element to its original state which was disabled by 'disableLinkElement' above
+enableLinkElement = (element) ->
+ originalText = getData(element, 'ujs:enable-with')
+ if originalText?
+ element.innerHTML = originalText # set to old enabled state
+ setData(element, 'ujs:enable-with', null) # clean up cache
+ element.removeEventListener('click', stopEverything) # enable element
+ setData(element, 'ujs:disabled', null)
+
+# Disables form elements:
+# - Caches element value in 'ujs:enable-with' data store
+# - Replaces element text with value of 'data-disable-with' attribute
+# - Sets disabled property to true
+disableFormElements = (form) ->
+ formElements(form, Rails.formDisableSelector).forEach(disableFormElement)
+
+disableFormElement = (element) ->
+ replacement = element.getAttribute('data-disable-with')
+ if replacement?
+ if matches(element, 'button')
+ setData(element, 'ujs:enable-with', element.innerHTML)
+ element.innerHTML = replacement
+ else
+ setData(element, 'ujs:enable-with', element.value)
+ element.value = replacement
+ element.disabled = true
+ setData(element, 'ujs:disabled', true)
+
+# Re-enables disabled form elements:
+# - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
+# - Sets disabled property to false
+enableFormElements = (form) ->
+ formElements(form, Rails.formEnableSelector).forEach(enableFormElement)
+
+enableFormElement = (element) ->
+ originalText = getData(element, 'ujs:enable-with')
+ if originalText?
+ if matches(element, 'button')
+ element.innerHTML = originalText
+ else
+ element.value = originalText
+ setData(element, 'ujs:enable-with', null) # clean up cache
+ element.disabled = false
+ setData(element, 'ujs:disabled', null)
+
+isXhrRedirect = (event) ->
+ xhr = event.detail?[0]
+ xhr?.getResponseHeader("X-Xhr-Redirect")?
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/method.coffee b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
new file mode 100644
index 0000000000..d04d9414dd
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/features/method.coffee
@@ -0,0 +1,34 @@
+#= require_tree ../utils
+
+{ stopEverything } = Rails
+
+# Handles "data-method" on links such as:
+# <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
+Rails.handleMethod = (e) ->
+ link = this
+ method = link.getAttribute('data-method')
+ return unless method
+
+ href = Rails.href(link)
+ csrfToken = Rails.csrfToken()
+ csrfParam = Rails.csrfParam()
+ form = document.createElement('form')
+ formContent = "<input name='_method' value='#{method}' type='hidden' />"
+
+ if csrfParam? and csrfToken? and not Rails.isCrossDomain(href)
+ formContent += "<input name='#{csrfParam}' value='#{csrfToken}' type='hidden' />"
+
+ # Must trigger submit by click on a button, else "submit" event handler won't work!
+ # https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit
+ formContent += '<input type="submit" />'
+
+ form.method = 'post'
+ form.action = href
+ form.target = link.target
+ form.innerHTML = formContent
+ form.style.display = 'none'
+
+ document.body.appendChild(form)
+ form.querySelector('[type="submit"]').click()
+
+ stopEverything(e)
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
new file mode 100644
index 0000000000..b3448dabac
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
@@ -0,0 +1,90 @@
+#= require_tree ../utils
+
+{
+ matches, getData, setData
+ fire, stopEverything
+ ajax, isCrossDomain
+ serializeElement
+} = Rails
+
+# Checks "data-remote" if true to handle the request through a XHR request.
+isRemote = (element) ->
+ value = element.getAttribute('data-remote')
+ value? and value isnt 'false'
+
+# Submits "remote" forms and links with ajax
+Rails.handleRemote = (e) ->
+ element = this
+
+ return true unless isRemote(element)
+ unless fire(element, 'ajax:before')
+ fire(element, 'ajax:stopped')
+ return false
+
+ withCredentials = element.getAttribute('data-with-credentials')
+ dataType = element.getAttribute('data-type') or 'script'
+
+ if matches(element, Rails.formSubmitSelector)
+ # memoized value from clicked submit button
+ button = getData(element, 'ujs:submit-button')
+ method = getData(element, 'ujs:submit-button-formmethod') or element.method
+ url = getData(element, 'ujs:submit-button-formaction') or element.getAttribute('action') or location.href
+
+ # strip query string if it's a GET request
+ url = url.replace(/\?.*$/, '') if method.toUpperCase() is 'GET'
+
+ if element.enctype is 'multipart/form-data'
+ data = new FormData(element)
+ data.append(button.name, button.value) if button?
+ else
+ data = serializeElement(element, button)
+
+ setData(element, 'ujs:submit-button', null)
+ setData(element, 'ujs:submit-button-formmethod', null)
+ setData(element, 'ujs:submit-button-formaction', null)
+ else if matches(element, Rails.buttonClickSelector) or matches(element, Rails.inputChangeSelector)
+ method = element.getAttribute('data-method')
+ url = element.getAttribute('data-url')
+ data = serializeElement(element, element.getAttribute('data-params'))
+ else
+ method = element.getAttribute('data-method')
+ url = Rails.href(element)
+ data = element.getAttribute('data-params')
+
+ ajax(
+ type: method or 'GET'
+ url: url
+ data: data
+ dataType: dataType
+ # stopping the "ajax:beforeSend" event will cancel the ajax request
+ beforeSend: (xhr, options) ->
+ if fire(element, 'ajax:beforeSend', [xhr, options])
+ fire(element, 'ajax:send', [xhr])
+ else
+ fire(element, 'ajax:stopped')
+ return false
+ success: (args...) -> fire(element, 'ajax:success', args)
+ error: (args...) -> fire(element, 'ajax:error', args)
+ complete: (args...) -> fire(element, 'ajax:complete', args)
+ crossDomain: isCrossDomain(url)
+ withCredentials: withCredentials? and withCredentials isnt 'false'
+ )
+ stopEverything(e)
+
+Rails.formSubmitButtonClick = (e) ->
+ button = this
+ form = button.form
+ return unless form
+ # Register the pressed submit button
+ setData(form, 'ujs:submit-button', name: button.name, value: button.value) if button.name
+ # Save attributes from button
+ setData(form, 'ujs:formnovalidate-button', button.formNoValidate)
+ setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'))
+ setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'))
+
+Rails.handleMetaClick = (e) ->
+ link = this
+ method = (link.getAttribute('data-method') or 'GET').toUpperCase()
+ data = link.getAttribute('data-params')
+ metaClick = e.metaKey or e.ctrlKey
+ e.stopImmediatePropagation() if metaClick and method is 'GET' and not data
diff --git a/actionview/app/assets/javascripts/rails-ujs/start.coffee b/actionview/app/assets/javascripts/rails-ujs/start.coffee
new file mode 100644
index 0000000000..32a915ac0b
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/start.coffee
@@ -0,0 +1,71 @@
+{
+ fire, delegate
+ getData, $
+ refreshCSRFTokens, CSRFProtection
+ enableElement, disableElement, handleDisabledElement
+ handleConfirm
+ handleRemote, formSubmitButtonClick, handleMetaClick
+ handleMethod
+} = Rails
+
+# For backward compatibility
+if jQuery? and jQuery.ajax?
+ throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.') if jQuery.rails
+ jQuery.rails = Rails
+ jQuery.ajaxPrefilter (options, originalOptions, xhr) ->
+ CSRFProtection(xhr) unless options.crossDomain
+
+Rails.start = ->
+ # Cut down on the number of issues from people inadvertently including
+ # rails-ujs twice by detecting and raising an error when it happens.
+ throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
+
+ # This event works the same as the load event, except that it fires every
+ # time the page is loaded.
+ # See https://github.com/rails/jquery-ujs/issues/357
+ # See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
+ window.addEventListener 'pageshow', ->
+ $(Rails.formEnableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+ $(Rails.linkDisableSelector).forEach (el) ->
+ enableElement(el) if getData(el, 'ujs:disabled')
+
+ delegate document, Rails.linkDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.linkDisableSelector, 'ajax:stopped', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:complete', enableElement
+ delegate document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement
+
+ delegate document, Rails.linkClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.linkClickSelector, 'click', handleConfirm
+ delegate document, Rails.linkClickSelector, 'click', handleMetaClick
+ delegate document, Rails.linkClickSelector, 'click', disableElement
+ delegate document, Rails.linkClickSelector, 'click', handleRemote
+ delegate document, Rails.linkClickSelector, 'click', handleMethod
+
+ delegate document, Rails.buttonClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.buttonClickSelector, 'click', handleConfirm
+ delegate document, Rails.buttonClickSelector, 'click', disableElement
+ delegate document, Rails.buttonClickSelector, 'click', handleRemote
+
+ delegate document, Rails.inputChangeSelector, 'change', handleDisabledElement
+ delegate document, Rails.inputChangeSelector, 'change', handleConfirm
+ delegate document, Rails.inputChangeSelector, 'change', handleRemote
+
+ delegate document, Rails.formSubmitSelector, 'submit', handleDisabledElement
+ delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
+ delegate document, Rails.formSubmitSelector, 'submit', handleRemote
+ # Normal mode submit
+ # Slight timeout so that the submit button gets properly serialized
+ delegate document, Rails.formSubmitSelector, 'submit', (e) -> setTimeout((-> disableElement(e)), 13)
+ delegate document, Rails.formSubmitSelector, 'ajax:send', disableElement
+ delegate document, Rails.formSubmitSelector, 'ajax:complete', enableElement
+
+ delegate document, Rails.formInputClickSelector, 'click', handleDisabledElement
+ delegate document, Rails.formInputClickSelector, 'click', handleConfirm
+ delegate document, Rails.formInputClickSelector, 'click', formSubmitButtonClick
+
+ document.addEventListener('DOMContentLoaded', refreshCSRFTokens)
+ window._rails_loaded = true
+
+if window.Rails is Rails and fire(document, 'rails:attachBindings')
+ Rails.start()
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
new file mode 100644
index 0000000000..019bda635a
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -0,0 +1,97 @@
+#= require ./csp
+#= require ./csrf
+#= require ./event
+
+{ cspNonce, CSRFProtection, fire } = Rails
+
+AcceptHeaders =
+ '*': '*/*'
+ text: 'text/plain'
+ html: 'text/html'
+ xml: 'application/xml, text/xml'
+ json: 'application/json, text/javascript'
+ script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
+
+Rails.ajax = (options) ->
+ options = prepareOptions(options)
+ xhr = createXHR options, ->
+ response = processResponse(xhr.response ? xhr.responseText, xhr.getResponseHeader('Content-Type'))
+ if xhr.status // 100 == 2
+ options.success?(response, xhr.statusText, xhr)
+ else
+ options.error?(response, xhr.statusText, xhr)
+ options.complete?(xhr, xhr.statusText)
+
+ if options.beforeSend? && !options.beforeSend(xhr, options)
+ return false
+
+ if xhr.readyState is XMLHttpRequest.OPENED
+ xhr.send(options.data)
+
+prepareOptions = (options) ->
+ options.url = options.url or location.href
+ options.type = options.type.toUpperCase()
+ # append data to url if it's a GET request
+ if options.type is 'GET' and options.data
+ if options.url.indexOf('?') < 0
+ options.url += '?' + options.data
+ else
+ options.url += '&' + options.data
+ # Use "*" as default dataType
+ options.dataType = '*' unless AcceptHeaders[options.dataType]?
+ options.accept = AcceptHeaders[options.dataType]
+ options.accept += ', */*; q=0.01' if options.dataType isnt '*'
+ options
+
+createXHR = (options, done) ->
+ xhr = new XMLHttpRequest()
+ # Open and setup xhr
+ xhr.open(options.type, options.url, true)
+ xhr.setRequestHeader('Accept', options.accept)
+ # Set Content-Type only when sending a string
+ # Sending FormData will automatically set Content-Type to multipart/form-data
+ if typeof options.data is 'string'
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') unless options.crossDomain
+ # Add X-CSRF-Token
+ CSRFProtection(xhr)
+ xhr.withCredentials = !!options.withCredentials
+ xhr.onreadystatechange = ->
+ done(xhr) if xhr.readyState is XMLHttpRequest.DONE
+ xhr
+
+processResponse = (response, type) ->
+ if typeof response is 'string' and typeof type is 'string'
+ if type.match(/\bjson\b/)
+ try response = JSON.parse(response)
+ else if type.match(/\b(?:java|ecma)script\b/)
+ script = document.createElement('script')
+ script.setAttribute('nonce', cspNonce())
+ script.text = response
+ document.head.appendChild(script).parentNode.removeChild(script)
+ else if type.match(/\bxml\b/)
+ parser = new DOMParser()
+ type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
+ try response = parser.parseFromString(response, type)
+ response
+
+# Default way to get an element's href. May be overridden at Rails.href.
+Rails.href = (element) -> element.href
+
+# Determines if the request is a cross domain request.
+Rails.isCrossDomain = (url) ->
+ originAnchor = document.createElement('a')
+ originAnchor.href = location.href
+ urlAnchor = document.createElement('a')
+ try
+ urlAnchor.href = url
+ # If URL protocol is false or is a string containing a single colon
+ # *and* host are false, assume it is not a cross-domain request
+ # (should only be the case for IE7 and IE compatibility mode).
+ # Otherwise, evaluate protocol and host of the URL against the origin
+ # protocol and host.
+ !(((!urlAnchor.protocol || urlAnchor.protocol == ':') && !urlAnchor.host) ||
+ (originAnchor.protocol + '//' + originAnchor.host == urlAnchor.protocol + '//' + urlAnchor.host))
+ catch e
+ # If there is an error parsing the URL, assume it is crossDomain.
+ true
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
new file mode 100644
index 0000000000..8d2d6ce447
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csp.coffee
@@ -0,0 +1,4 @@
+# Content-Security-Policy nonce for inline scripts
+cspNonce = Rails.cspNonce = ->
+ meta = document.querySelector('meta[name=csp-nonce]')
+ meta and meta.content
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
new file mode 100644
index 0000000000..4eb5ebb414
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/csrf.coffee
@@ -0,0 +1,25 @@
+#= require ./dom
+
+{ $ } = Rails
+
+# Up-to-date Cross-Site Request Forgery token
+csrfToken = Rails.csrfToken = ->
+ meta = document.querySelector('meta[name=csrf-token]')
+ meta and meta.content
+
+# URL param that must contain the CSRF token
+csrfParam = Rails.csrfParam = ->
+ meta = document.querySelector('meta[name=csrf-param]')
+ meta and meta.content
+
+# Make sure that every Ajax request sends the CSRF token
+Rails.CSRFProtection = (xhr) ->
+ token = csrfToken()
+ xhr.setRequestHeader('X-CSRF-Token', token) if token?
+
+# Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
+Rails.refreshCSRFTokens = ->
+ token = csrfToken()
+ param = csrfParam()
+ if token? and param?
+ $('form input[name="' + param + '"]').forEach (input) -> input.value = token
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
new file mode 100644
index 0000000000..3d3c5bb330
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
@@ -0,0 +1,35 @@
+m = Element.prototype.matches or
+ Element.prototype.matchesSelector or
+ Element.prototype.mozMatchesSelector or
+ Element.prototype.msMatchesSelector or
+ Element.prototype.oMatchesSelector or
+ Element.prototype.webkitMatchesSelector
+
+# Checks if the given native dom element matches the selector
+# element::
+# native DOM element
+# selector::
+# css selector string or
+# a javascript object with `selector` and `exclude` properties
+# Examples: "form", { selector: "form", exclude: "form[data-remote='true']"}
+Rails.matches = (element, selector) ->
+ if selector.exclude?
+ m.call(element, selector.selector) and not m.call(element, selector.exclude)
+ else
+ m.call(element, selector)
+
+# get and set data on a given element using "expando properties"
+# See: https://developer.mozilla.org/en-US/docs/Glossary/Expando
+expando = '_ujsData'
+
+Rails.getData = (element, key) ->
+ element[expando]?[key]
+
+Rails.setData = (element, key, value) ->
+ element[expando] ?= {}
+ element[expando][key] = value
+
+# a wrapper for document.querySelectorAll
+# returns an Array
+Rails.$ = (selector) ->
+ Array.prototype.slice.call(document.querySelectorAll(selector))
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
new file mode 100644
index 0000000000..a7eee52060
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
@@ -0,0 +1,68 @@
+#= require ./dom
+
+{ matches } = Rails
+
+# Polyfill for CustomEvent in IE9+
+# https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
+CustomEvent = window.CustomEvent
+
+if typeof CustomEvent isnt 'function'
+ CustomEvent = (event, params) ->
+ evt = document.createEvent('CustomEvent')
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
+ evt
+
+ CustomEvent.prototype = window.Event.prototype
+
+ # Fix setting `defaultPrevented` when `preventDefault()` is called
+ # http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events
+ { preventDefault } = CustomEvent.prototype
+ CustomEvent.prototype.preventDefault = ->
+ result = preventDefault.call(this)
+ if @cancelable and not @defaultPrevented
+ Object.defineProperty(this, 'defaultPrevented', get: -> true)
+ result
+
+# Triggers a custom event on an element and returns false if the event result is false
+# obj::
+# a native DOM element
+# name::
+# string that corrspends to the event you want to trigger
+# e.g. 'click', 'submit'
+# data::
+# data you want to pass when you dispatch an event
+fire = Rails.fire = (obj, name, data) ->
+ event = new CustomEvent(
+ name,
+ bubbles: true,
+ cancelable: true,
+ detail: data,
+ )
+ obj.dispatchEvent(event)
+ !event.defaultPrevented
+
+# Helper function, needed to provide consistent behavior in IE
+Rails.stopEverything = (e) ->
+ fire(e.target, 'ujs:everythingStopped')
+ e.preventDefault()
+ e.stopPropagation()
+ e.stopImmediatePropagation()
+
+# Delegates events
+# to a specified parent `element`, which fires event `handler`
+# for the specified `selector` when an event of `eventType` is triggered
+# element::
+# parent element that will listen for events e.g. document
+# selector::
+# css selector; or an object that has `selector` and `exclude` properties (see: Rails.matches)
+# eventType::
+# string representing the event e.g. 'submit', 'click'
+# handler::
+# the event handler to be called
+Rails.delegate = (element, selector, eventType, handler) ->
+ element.addEventListener eventType, (e) ->
+ target = e.target
+ target = target.parentNode until not (target instanceof Element) or matches(target, selector)
+ if target instanceof Element and handler.call(target, e) == false
+ e.preventDefault()
+ e.stopPropagation()
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
new file mode 100644
index 0000000000..736cab08db
--- /dev/null
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/form.coffee
@@ -0,0 +1,36 @@
+#= require ./dom
+
+{ matches } = Rails
+
+toArray = (e) -> Array.prototype.slice.call(e)
+
+Rails.serializeElement = (element, additionalParam) ->
+ inputs = [element]
+ inputs = toArray(element.elements) if matches(element, 'form')
+ params = []
+
+ inputs.forEach (input) ->
+ return if !input.name || input.disabled
+ if matches(input, 'select')
+ toArray(input.options).forEach (option) ->
+ params.push(name: input.name, value: option.value) if option.selected
+ else if input.checked or ['radio', 'checkbox', 'submit'].indexOf(input.type) == -1
+ params.push(name: input.name, value: input.value)
+
+ params.push(additionalParam) if additionalParam
+
+ params.map (param) ->
+ if param.name?
+ "#{encodeURIComponent(param.name)}=#{encodeURIComponent(param.value)}"
+ else
+ param
+ .join('&')
+
+# Helper function that returns form elements that match the specified CSS selector
+# If form is actually a "form" element this will return associated elements outside the from that have
+# the html form attribute set
+Rails.formElements = (form, selector) ->
+ if matches(form, 'form')
+ toArray(form.elements).filter (el) -> matches(el, selector)
+ else
+ toArray(form.querySelectorAll(selector))