Skip to content

Instantly share code, notes, and snippets.

@vendethiel
Created July 6, 2012 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vendethiel/3062690 to your computer and use it in GitHub Desktop.
Save vendethiel/3062690 to your computer and use it in GitHub Desktop.
Rails UJS plugin rewritten using LiveScript
$ <-! jQuery
/** ###
* Unobtrusive scripting adapter for jQuery
*
* Requires jQuery 1.6.0 or later.
* https://github.com/rails/jquery-ujs
* Uploading file using rails.js
* =============================
*
* By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields
* in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means.
*
* The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish.
*
* Ex:
* $ 'form' .live 'ajax:aborted:file' (event, elements) ->
* # Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`.
* # Returning false in this handler tells rails.js to disallow standard form submission
* false
*
* The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value.
*
* Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use
* techniques like the iframe method to upload the file instead.
*
* Required fields in rails.js
* ===========================
*
* If any blank required inputs (required="required") are detected in the remote form, the whole form submission
* is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission.
*
* The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs.
*
* !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never
* get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior.
*
* Ex:
* $ 'form' .live 'ajax:aborted:required' (event, elements) ->
* # Returning false in this handler tells rails.js to submit the form anyway.
* # The blank required inputs are passed to this function in `elements`.
* return not confirm "Would you like to submit the form with missing info?"
### */
window.rails = $.rails = class rails
# Link elements bound by jquery-ujs
@link-click-selector = [
'a[data-confirm]'
'a[data-method]'
'a[data-remote]'
'a[data-disable-with]'
]join!
# Select elements bound by jquery-ujs
@input-change-selector = [
'select[data-remote]'
'input[data-remote]'
'textarea[data-remote]'
]join!
# Form elements bound by jquery-ujs
@form-submit-selector = 'form'
# Form input elements bound by jquery-ujs
@form-input-slick-selector = [
'form'
'form input[type=submit]'
'form input[type=image]'
'form button[type=submit]'
'form button:not(button[type])'
]join!
# Form input elements disabled during form submission
@disable-selector = [
'input[data-disable-with]'
'button[data-disable-with]'
'textarea[data-disable-with]'
]join!
# Form input elements re-enabled after form submission
@enable-selector = [
'input[data-disable-with]:disabled'
'button[data-disable-with]:disabled'
'textarea[data-disable-with]:disabled'
]join!
# Form required input elements
@required-input-selector = [
'input[name][required]:not([disabled])'
'textarea[name][required]:not([disabled])'
]join!
# Form file input elements
@file-input-selector = 'input:file'
# Link onClick disable selector with possible reenable after remote submission
@link-disable-selector = [
'a[data-disable-with]'
]join!
# Wasn't cached at first, was there a reason?
@meta-csrf = $ 'meta[name="csrf-token"]'
# Make sure that every Ajax request sends the CSRF token
@CSRF-protection = (xhr) ->
if rails.meta-csrf.attr 'content'
xhr.setRequestHeader 'X-CSRF-Token' that
# Triggers an event on an element and returns false if the event result is false
@fire = (obj, name, data) ->
event = $.Event name
obj.trigger event, data
event.result is not false
# Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
@confirm = confirm _
# Default ajax function, may be overridden with custom function in $.rails.ajax
@ajax = $.ajax _
# Default way to get an element's href. May be overridden at $.rails.href.
@href = (.attr 'href')
# Submits "remote" forms and links with ajax
@handle-remote = (element) !->
if rails.fire element, 'ajax:before'
cross-domain = element.data('cross-domain') || null
data-type = element.data 'type' or ($.ajax-settings and $.ajax-settings.data-type)
if element.is 'form'
method = element.attr 'method'
url = element.attr 'action'
data = element.serialize-array!
# memoized value from clicked submit button
if element.data 'ujs:submit-button'
data.push that
element.data 'ujs:submit-button' null
else if element.is rails.input-change-selector
method = element.data 'method'
url = element.data 'url'
data = element.serialize!
if element.data 'params'
data += "&" + that
else
method = element.data 'method'
url = rails.href element
data = element.data('params') || null
options = {
type: method or 'GET'
data
data-type
cross-domain
# stopping the "ajax:beforeSend" event will cancel the ajax request
before-send: (xhr, settings) ->
if settings.data-type?
xhr.set-request-header 'accept' '*/*;q=0.5, ' + settings.accepts.script
rails.fire element, 'ajax:beforeSend' [xhr, settings]
success: (data, status, xhr) ->
element.trigger 'ajax:success' [data, status, xhr]
complete: (xhr, status) ->
element.trigger 'ajax:complete' [xhr, status]
error: (xhr, status, error) ->
element.trigger 'ajax:error', [xhr, status, error]
}
# Only pass url to `ajax` options if not blank
if url then options.url = url
rails.ajax options
else
false
/* Handles "data-method" on links such as:
<a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
*/
@handle-method = (link) ->
href = rails.href link
method = link.data 'method'
csrf_token = $ 'meta[name=csrf-token]' .attr 'content'
csrf_param = $ 'meta[name=csrf-param]' .attr 'content'
form = $ '<form method="post" action="' + href + '"></form>'
metadata_input = '<input name="_method" value="' + method + '" type="hidden" />'
if csrf_param? && csrf_token?
metadata_input += "<input name='#csrf_param' value='#csrf_token' type='hidden' />"
if link.attr 'target'
form.attr 'target' that
form
.hide!
.append metadata_input
.appendTo 'body'
.submit!
/* 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
*/
@disable-form-elements = !->
<-! it.find rails.disable-selector .each
with $ this
method = if @is 'button' then 'html' else 'val'
@data 'ujs:enable-with' element[method]!
@[method] element.data 'disable-with'
@prop {+disabled}
/* 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 = !->
<-! it.find rails.enable-selector .each
with $ this
method = if @is 'button' then 'html' else 'val'
if @data 'ujs:enable-with'
@[method] that
@prop {-disabled}
/* 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.data 'confirm'
answer = false
return true unless message
if rails.fire element, 'confirm'
answer = rails.confirm message
callback = rails.fire element, 'confirm:complete', [answer]
answer and callback
/* Helper function which checks for blank inputs in a form that match the specified CSS selector
*/
@blank-inputs = (form, selector or 'input,textarea', non-blank) ->
inputs = $!
form.find selector .each !->
input = $ @
# Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
if (if non-blank then input.val! else not input.val!)
inputs .= add input
if inputs.length then inputs else false
/* Helper function which checks for non-blank inputs in a form that match the specified CSS selector
*/
@non-blank-inputs = (form, selector) ->
rails.blank-inputs form, selector, true #true = non blank
/* Helper function, needed to provide consistent behavior in IE
*/
@stop-everything = (e) ->
$ it.target .trigger 'ujs:everythingStopped'
it.stop-immediate-propagation!
false
/* find all the submit events directly bound to the form and
manually invoke them. If anyone returns false then stop the loop
*/
@call-form-submit-bindings = (form, event) ->
events = form.data 'events'
continue-propagation = true
for obj in events?@@submit
if typeof obj.handler is 'function'
continue-propagation = obj.handler event
break
continue-propagation
/* replace element's html with the 'data-disable-with' after storing original html
and prevent clicking on it
*/
@disable-element = !->
with it
@data 'ujs:enable-with' element.html! # store enabled state
@html @data 'disable-with' # set to disabled state
@bind 'click.railsDisable' (e) -> # prevent further clicking
rails.stop-everything e
/* restore element to its original state which was disabled by 'disable-element' above
*/
@enable-element = !->
with it
if @data('ujs:enable-with')?
@html @data 'ujs:enable-with' # set to old enabled state
# this should be @remove-data('ujs:enable-with')
# but, there is currently a bug in jquery which makes hyphenated data attributes not get removed
@data 'ujs:enable-with' false # clean up cache
@unbind 'click.railsDisable' # enable element
$.ajax-prefilter (options, original-options, xhr) ->
unless options.cross-domain
rails.CSRF-protection xhr
$document = $ document
$document.delegate rails.link-disable-selector, 'ajax:complete' !->
rails.enable-element $ this
$document.delegate rails.link-click-selector, 'click.rails' (e) ->
link = $ this
method = link.data 'method'
data = link.data 'params'
unless rails.allow-action link
return rails.stop-everything e
if link.is rails.link-disable-selector
rails.disable-element link
if link.data('remote')?
if (e.meta-key or e.ctrl-key)
and (not method or method is 'GET')
and not data
then
return true
if false is rails.handle-remote link
rails.enable-element link
false
else if link.data 'method'
rails.handleMethod link
false
$document.delegate rails.input-change-selector, 'change.rails' (e) ->
link = $ this
unless rails.allow-action link
return rails.stop-everything e
rails.handle-remote link
false
$document.delegate rails.form-submit-selector, 'submit.rails' (e) !->
form = $ this
remote = form.data('remote')?
blank-required-inputs = rails.blank-inputs form, rails.required-input-selector
non-blank-file-inputs = rails.non-blank-inputs form, rails.file-input-selector
$document.delegate rails.form-input-click-selector, 'click.rails' (event) !->
button = $ this
unless rails.allow-action button
return rails.stop-everything event
# register the pressed submit button
name = button.attr 'name'
data = if name then {name, value: button.val!}
button
.closest 'form'
.data 'ujs:submit-button' data
$document.delegate rails.form-submit-selector, 'ajax:beforeSend.rails' (event) !->
if this is event.target
rails.disable-form-elements $ this
$document.delegate rails.form-submit-selector, 'ajax:complete.rails' (event) !->
if this is event.target
rails.enable-form-elements $ this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment