From d59144177d86790891fdb99b0e3437312e04fda2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 18 Apr 2011 05:29:54 +0800 Subject: [PATCH 001/271] Gave external api to internal rails.js functions to make them available to be called and modified from outside of rails.js via $.rails object. Closes #98. --- src/rails.js | 90 ++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/rails.js b/src/rails.js index 98b1e507..05b5c89d 100644 --- a/src/rails.js +++ b/src/rails.js @@ -36,30 +36,29 @@ */ (function($) { + // Shorthand to make it a little easier to call public rails functions from within rails.js + var rails; + + $.rails = rails = { // Make sure that every Ajax request sends the CSRF token - function CSRFProtection(xhr) { + CSRFProtection: function(xhr) { var token = $('meta[name="csrf-token"]').attr('content'); if (token) xhr.setRequestHeader('X-CSRF-Token', token); - } - if ('ajaxPrefilter' in $) { - $.ajaxPrefilter(function(options, originalOptions, xhr){ CSRFProtection(xhr); }); - } else { - $(document).ajaxSend(function(e, xhr){ CSRFProtection(xhr); }); - } + }, // Triggers an event on an element and returns the event result - function fire(obj, name, data) { + fire: function(obj, name, data) { var event = $.Event(name); obj.trigger(event, data); return event.result !== false; - } + }, // Submits "remote" forms and links with ajax - function handleRemote(element) { + handleRemote: function(element) { var method, url, data, dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); - if (fire(element, 'ajax:before')) { + if (rails.fire(element, 'ajax:before')) { if (element.is('form')) { method = element.attr('method'); url = element.attr('action'); @@ -82,7 +81,7 @@ if (settings.dataType === undefined) { xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); } - return fire(element, 'ajax:beforeSend', [xhr, settings]); + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); }, success: function(data, status, xhr) { element.trigger('ajax:success', [data, status, xhr]); @@ -95,11 +94,11 @@ } }); } - } + }, // Handles "data-method" on links such as: // Delete - function handleMethod(link) { + handleMethod: function(link) { var href = link.attr('href'), method = link.data('method'), csrf_token = $('meta[name=csrf-token]').attr('content'), @@ -113,54 +112,54 @@ form.hide().append(metadata_input).appendTo('body'); form.submit(); - } + }, - function disableFormElements(form) { + disableFormElements: function(form) { form.find('input[data-disable-with], button[data-disable-with]').each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; element.data('ujs:enable-with', element[method]()); element[method](element.data('disable-with')); element.attr('disabled', 'disabled'); }); - } + }, - function enableFormElements(form) { + enableFormElements: function(form) { form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled').each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); element.removeAttr('disabled'); }); - } + }, - function allowAction(element) { + allowAction: function(element) { var message = element.data('confirm'); - return !message || (fire(element, 'confirm') && confirm(message)); - } + return !message || (rails.fire(element, 'confirm') && confirm(message)); + }, - function blankInputs(form, specifiedSelector) { + blankInputs: function(form, specifiedSelector) { var blankExists = false, selector = specifiedSelector || 'input'; form.find(selector).each(function() { if (!$(this).val()) blankExists = true; }); return blankExists; - } + }, - function nonBlankInputs(form, specifiedSelector) { + nonBlankInputs: function(form, specifiedSelector) { var nonBlankExists = false, selector = specifiedSelector || 'input'; form.find(selector).each(function() { if ($(this).val()) nonBlankExists = true; }); return nonBlankExists; - } + }, - function stopEverything(e) { + stopEverything: function(e) { e.stopImmediatePropagation(); return false; - } + }, - function callFormSubmitBindings(form) { + callFormSubmitBindings: function(form) { var events = form.data('events'), continuePropagation = true; if (events != undefined && events['submit'] != undefined) { $.each(events['submit'], function(i, obj){ @@ -169,58 +168,65 @@ } return continuePropagation; } + }; + + if ('ajaxPrefilter' in $) { + $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); + } else { + $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); + } $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) { var link = $(this); - if (!allowAction(link)) return stopEverything(e); + if (!rails.allowAction(link)) return rails.stopEverything(e); if (link.data('remote') != undefined) { - handleRemote(link); + rails.handleRemote(link); return false; } else if (link.data('method')) { - handleMethod(link); + rails.handleMethod(link); return false; } }); $('form').live('submit.rails', function(e) { var form = $(this), remote = form.data('remote') != undefined; - if (!allowAction(form)) return stopEverything(e); + if (!rails.allowAction(form)) return rails.stopEverything(e); // skip other logic when required values are missing or file upload is present - if (blankInputs(form, 'input[name][required]') && fire(form, 'ajax:aborted:required')) { + if (rails.blankInputs(form, 'input[name][required]') && rails.fire(form, 'ajax:aborted:required')) { return !remote; } - if (nonBlankInputs(form, 'input:file')) { - return fire(form, 'ajax:aborted:file'); + if (rails.nonBlankInputs(form, 'input:file')) { + return rails.fire(form, 'ajax:aborted:file'); } // If browser does not support submit bubbling, then this live-binding will be called before direct // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. - if (!$.support.submitBubbles && callFormSubmitBindings(form) == false) return stopEverything(e) + if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) == false) return rails.stopEverything(e) if (remote) { - handleRemote(form); + rails.handleRemote(form); return false; } else { // slight timeout so that the submit button gets properly serialized - setTimeout(function(){ disableFormElements(form); }, 13); + setTimeout(function(){ rails.disableFormElements(form); }, 13); } }); $('form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])').live('click.rails', function() { var button = $(this); - if (!allowAction(button)) return stopEverything(e); + if (!rails.allowAction(button)) return rails.stopEverything(e); // register the pressed submit button var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; button.closest('form').data('ujs:submit-button', data); }); $('form').live('ajax:beforeSend.rails', function(event) { - if (this == event.target) disableFormElements($(this)); + if (this == event.target) rails.disableFormElements($(this)); }); $('form').live('ajax:complete.rails', function(event) { - if (this == event.target) enableFormElements($(this)); + if (this == event.target) rails.enableFormElements($(this)); }); })( jQuery ); From 73290132ddad4654fce8eddf6a83b1063b5da676 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 18 Apr 2011 11:33:30 +0800 Subject: [PATCH 002/271] Added CHANGELOG. --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0f652a09 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +CHANGELOG +========= + +Here is a non-exhaustive list of notable changes to jquery-ujs (oldest +to newest): + +- [`085d910a5ec07b69`](https://github.com/rails/jquery-ujs/commit/085d910a5ec07b69f31beabce286141aa26f3005) last version before callback names updated +- [`72d875a8d57c6bb4`](https://github.com/rails/jquery-ujs/commit/72d875a8d57c6bb466170980a5142c66ac74e8f0) callback name updates completed +- [`e076121248913143`](https://github.com/rails/jquery-ujs/commit/e0761212489131437402a92fa8f548a78f685ae2) dropped support for jQuery 1.4, 1.4.1, 1.4.2 (separate [v1.4 branch](https://github.com/rails/jquery-ujs/commits/v1.4) created) +- [`498b35e24cdb14f2`](https://github.com/rails/jquery-ujs/commit/498b35e24cdb14f2d94486e8a1f4a1f661091426) last version before `.callRemote()` removed +- [`ec96408a746d3b06`](https://github.com/rails/jquery-ujs/commit/ec96408a746d3b0692da9249f218a3943fbffc28) `ACCEPTS` header data-type default changed to prefer `:js` but not require it +- [`fc639928d1e15c88`](https://github.com/rails/jquery-ujs/commit/fc639928d1e15c885b85de5b517346db7f963f44) default form method changed from `POST` to `GET` +- [`e9311550fdb3afeb`](https://github.com/rails/jquery-ujs/commit/e9311550fdb3afeb2917bcb1fef39767bf715003) added CSRF Protection to remote requests +- [`a284dd706e7d76e8`](https://github.com/rails/jquery-ujs/commit/a284dd706e7d76e85471ef39ab3efdf07feef374) CSRF fixed - changed to only add if token is present +- [`f9b21b3a3c7c4684`](https://github.com/rails/jquery-ujs/commit/f9b21b3a3c7c46840fed8127a90def26911fad3d) `ajax:before` added back +- [`ca575e184e93b3ef`](https://github.com/rails/jquery-ujs/commit/ca575e184e93b3efe1a858cf598f8a37f0a760cc) added `ajax:aborted:required` and `ajax:aborted:file` event hooks +- [`d2abd6f9df4e4a42`](https://github.com/rails/jquery-ujs/commit/d2abd6f9df4e4a426c17c218b7d5e05004c768d0) fixed submit and bubbling behavior for IE +- [`d59144177d867908`](https://github.com/rails/jquery-ujs/commit/d59144177d86790891fdb99b0e3437312e04fda2) created external api via `$.rails` object From 1335ba4d82b005f5df92f0b16d212316840d684e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 09:25:06 -0400 Subject: [PATCH 003/271] fix lint warnings --- src/rails.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index 05b5c89d..288a2277 100644 --- a/src/rails.js +++ b/src/rails.js @@ -164,7 +164,7 @@ if (events != undefined && events['submit'] != undefined) { $.each(events['submit'], function(i, obj){ if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); - }) + }); } return continuePropagation; } @@ -203,7 +203,7 @@ // If browser does not support submit bubbling, then this live-binding will be called before direct // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. - if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) == false) return rails.stopEverything(e) + if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); if (remote) { rails.handleRemote(form); From 8635365111a325cc9323b2297b8523e4a70947d3 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 15:28:07 -0400 Subject: [PATCH 004/271] wrap long lines so that one could read comments on github without horizontal scroll --- src/rails.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/rails.js b/src/rails.js index 288a2277..c7b6cef6 100644 --- a/src/rails.js +++ b/src/rails.js @@ -8,8 +8,8 @@ * * 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 browser to submit the form normally. - * You may intercept the form submission and implement your own work-around for submitting file uploads without refreshing the page, - * by binding a handler function that returns false to the `ajax:aborted:file` hook. + * You may intercept the form submission and implement your own work-around for submitting file uploads without + * refreshing the page, by binding a handler function that returns false to the `ajax:aborted:file` hook. * * Ex: * $('form').live('ajax:aborted:file', function(){ @@ -21,13 +21,16 @@ * a) file-type input field is detected, and * b) the value of the input:file field is not blank. * - * 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. + * 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. * - * Similarly, rails.js aborts AJAX form submissions if any non-blank input[required] fields are detected, providing the `ajax:aborted:required` hook. + * Similarly, rails.js aborts AJAX form submissions if any non-blank input[required] fields are detected, + * providing the `ajax:aborted:required` hook. * Unlike file uploads, however, blank required input fields cancel the whole form submission by default. * - * The default behavior for aborting the remote form submission when required inputs are missing may be canceled (thereby submitting the form via AJAX anyway) - * by binding a handler function that returns false to the `ajax:aborted:required` hook. + * The default behavior for aborting the remote form submission when required inputs are missing may be + * canceled (thereby submitting the form via AJAX anyway) by binding a handler function that returns + * false to the `ajax:aborted:required` hook. * * Ex: * $('form').live('ajax:aborted:required', function(){ From 5ee23ebfbc50fac8d9f5e73854a90b207b109f1f Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 15:38:05 -0400 Subject: [PATCH 005/271] fix indentations --- src/rails.js | 265 ++++++++++++++++++++++++++------------------------- 1 file changed, 135 insertions(+), 130 deletions(-) diff --git a/src/rails.js b/src/rails.js index c7b6cef6..bf5fbfb4 100644 --- a/src/rails.js +++ b/src/rails.js @@ -39,139 +39,143 @@ */ (function($) { - // Shorthand to make it a little easier to call public rails functions from within rails.js - var rails; - - $.rails = rails = { - // Make sure that every Ajax request sends the CSRF token - CSRFProtection: function(xhr) { - var token = $('meta[name="csrf-token"]').attr('content'); - if (token) xhr.setRequestHeader('X-CSRF-Token', token); - }, - - // Triggers an event on an element and returns the event result - fire: function(obj, name, data) { - var event = $.Event(name); - obj.trigger(event, data); - return event.result !== false; - }, - - // Submits "remote" forms and links with ajax - handleRemote: function(element) { - var method, url, data, - dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); - - if (rails.fire(element, 'ajax:before')) { - if (element.is('form')) { - method = element.attr('method'); - url = element.attr('action'); - data = element.serializeArray(); - // memoized value from clicked submit button - var button = element.data('ujs:submit-button'); - if (button) { - data.push(button); - element.data('ujs:submit-button', null); - } - } else { - method = element.data('method'); - url = element.attr('href'); - data = null; - } - $.ajax({ - url: url, type: method || 'GET', data: data, dataType: dataType, - // stopping the "ajax:beforeSend" event will cancel the ajax request - beforeSend: function(xhr, settings) { - if (settings.dataType === undefined) { - xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + + // Shorthand to make it a little easier to call public rails functions from within rails.js + var rails; + + $.rails = rails = { + + // Make sure that every Ajax request sends the CSRF token + CSRFProtection: function(xhr) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + }, + + // Triggers an event on an element and returns the event result + fire: function(obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + + // Submits "remote" forms and links with ajax + handleRemote: function(element) { + var method, url, data, + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); + + if (rails.fire(element, 'ajax:before')) { + + if (element.is('form')) { + method = element.attr('method'); + url = element.attr('action'); + data = element.serializeArray(); + // memoized value from clicked submit button + var button = element.data('ujs:submit-button'); + if (button) { + data.push(button); + element.data('ujs:submit-button', null); } - return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); - }, - success: function(data, status, xhr) { - element.trigger('ajax:success', [data, status, xhr]); - }, - complete: function(xhr, status) { - element.trigger('ajax:complete', [xhr, status]); - }, - error: function(xhr, status, error) { - element.trigger('ajax:error', [xhr, status, error]); + } else { + method = element.data('method'); + url = element.attr('href'); + data = null; } + + $.ajax({ + url: url, type: method || 'GET', data: data, dataType: dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend: function(xhr, settings) { + if (settings.dataType === undefined) { + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + } + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); + }, + success: function(data, status, xhr) { + element.trigger('ajax:success', [data, status, xhr]); + }, + complete: function(xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + }, + error: function(xhr, status, error) { + element.trigger('ajax:error', [xhr, status, error]); + } + }); + } + }, + + // Handles "data-method" on links such as: + // Delete + handleMethod: function(link) { + var href = link.attr('href'), + method = link.data('method'), + csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'), + form = $('
'), + metadata_input = ''; + + if (csrf_param !== undefined && csrf_token !== undefined) { + metadata_input += ''; + } + + form.hide().append(metadata_input).appendTo('body'); + form.submit(); + }, + + disableFormElements: function(form) { + form.find('input[data-disable-with], button[data-disable-with]').each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + element.data('ujs:enable-with', element[method]()); + element[method](element.data('disable-with')); + element.attr('disabled', 'disabled'); }); - } - }, - - // Handles "data-method" on links such as: - // Delete - handleMethod: function(link) { - var href = link.attr('href'), - method = link.data('method'), - csrf_token = $('meta[name=csrf-token]').attr('content'), - csrf_param = $('meta[name=csrf-param]').attr('content'), - form = $('
'), - metadata_input = ''; - - if (csrf_param !== undefined && csrf_token !== undefined) { - metadata_input += ''; - } + }, - form.hide().append(metadata_input).appendTo('body'); - form.submit(); - }, - - disableFormElements: function(form) { - form.find('input[data-disable-with], button[data-disable-with]').each(function() { - var element = $(this), method = element.is('button') ? 'html' : 'val'; - element.data('ujs:enable-with', element[method]()); - element[method](element.data('disable-with')); - element.attr('disabled', 'disabled'); - }); - }, - - enableFormElements: function(form) { - form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled').each(function() { - var element = $(this), method = element.is('button') ? 'html' : 'val'; - if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); - element.removeAttr('disabled'); - }); - }, - - allowAction: function(element) { - var message = element.data('confirm'); - return !message || (rails.fire(element, 'confirm') && confirm(message)); - }, - - blankInputs: function(form, specifiedSelector) { - var blankExists = false, + enableFormElements: function(form) { + form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled').each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); + element.removeAttr('disabled'); + }); + }, + + allowAction: function(element) { + var message = element.data('confirm'); + return !message || (rails.fire(element, 'confirm') && confirm(message)); + }, + + blankInputs: function(form, specifiedSelector) { + var blankExists = false, selector = specifiedSelector || 'input'; - form.find(selector).each(function() { - if (!$(this).val()) blankExists = true; - }); - return blankExists; - }, - - nonBlankInputs: function(form, specifiedSelector) { - var nonBlankExists = false, + form.find(selector).each(function() { + if (!$(this).val()) blankExists = true; + }); + return blankExists; + }, + + nonBlankInputs: function(form, specifiedSelector) { + var nonBlankExists = false, selector = specifiedSelector || 'input'; - form.find(selector).each(function() { - if ($(this).val()) nonBlankExists = true; - }); - return nonBlankExists; - }, - - stopEverything: function(e) { - e.stopImmediatePropagation(); - return false; - }, - - callFormSubmitBindings: function(form) { - var events = form.data('events'), continuePropagation = true; - if (events != undefined && events['submit'] != undefined) { - $.each(events['submit'], function(i, obj){ - if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); - }); - } - return continuePropagation; - } - }; + form.find(selector).each(function() { + if ($(this).val()) nonBlankExists = true; + }); + return nonBlankExists; + }, + + stopEverything: function(e) { + e.stopImmediatePropagation(); + return false; + }, + + callFormSubmitBindings: function(form) { + var events = form.data('events'), continuePropagation = true; + if (events != undefined && events['submit'] != undefined) { + $.each(events['submit'], function(i, obj){ + if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); + }); + } + return continuePropagation; + } + }; if ('ajaxPrefilter' in $) { $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); @@ -204,9 +208,9 @@ return rails.fire(form, 'ajax:aborted:file'); } - // If browser does not support submit bubbling, then this live-binding will be called before direct - // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. - if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); + // If browser does not support submit bubbling, then this live-binding will be called before direct + // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. + if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); if (remote) { rails.handleRemote(form); @@ -232,4 +236,5 @@ $('form').live('ajax:complete.rails', function(event) { if (this == event.target) rails.enableFormElements($(this)); }); + })( jQuery ); From a14563915e1150dfdc5a4286e9ddb839cd92cfde Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 16:20:16 -0400 Subject: [PATCH 006/271] edit comment --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index bf5fbfb4..a9f0fa76 100644 --- a/src/rails.js +++ b/src/rails.js @@ -51,7 +51,7 @@ if (token) xhr.setRequestHeader('X-CSRF-Token', token); }, - // Triggers an event on an element and returns the event result + // Triggers an event on an element and returns false if the event result is false fire: function(obj, name, data) { var event = $.Event(name); obj.trigger(event, data); From 8fe10cc048f92380a9fe77a07aadb33e5e098a5f Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 16:20:44 -0400 Subject: [PATCH 007/271] prefer triple equal over double equal --- src/rails.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index a9f0fa76..d50e31bf 100644 --- a/src/rails.js +++ b/src/rails.js @@ -168,7 +168,7 @@ callFormSubmitBindings: function(form) { var events = form.data('events'), continuePropagation = true; - if (events != undefined && events['submit'] != undefined) { + if (events !== undefined && events['submit'] !== undefined) { $.each(events['submit'], function(i, obj){ if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); }); @@ -187,7 +187,7 @@ var link = $(this); if (!rails.allowAction(link)) return rails.stopEverything(e); - if (link.data('remote') != undefined) { + if (link.data('remote') !== undefined) { rails.handleRemote(link); return false; } else if (link.data('method')) { From 325e608b8550afa0d45b7393b4023cdcd1f15889 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 16:20:55 -0400 Subject: [PATCH 008/271] blank lines and indentation --- src/rails.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index d50e31bf..d060ea0e 100644 --- a/src/rails.js +++ b/src/rails.js @@ -204,6 +204,7 @@ if (rails.blankInputs(form, 'input[name][required]') && rails.fire(form, 'ajax:aborted:required')) { return !remote; } + if (rails.nonBlankInputs(form, 'input:file')) { return rails.fire(form, 'ajax:aborted:file'); } @@ -223,9 +224,13 @@ $('form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])').live('click.rails', function() { var button = $(this); + if (!rails.allowAction(button)) return rails.stopEverything(e); + // register the pressed submit button - var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; + var name = button.attr('name'), + data = name ? {name:name, value:button.val()} : null; + button.closest('form').data('ujs:submit-button', data); }); From 7ffd00110441f2169b29782da2677618632185b0 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 17:44:13 -0400 Subject: [PATCH 009/271] edit comments --- src/rails.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/rails.js b/src/rails.js index d060ea0e..49f8f972 100644 --- a/src/rails.js +++ b/src/rails.js @@ -7,9 +7,13 @@ * 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 browser to submit the form normally. - * You may intercept the form submission and implement your own work-around for submitting file uploads without - * refreshing the page, by binding a handler function that returns false to the `ajax:aborted:file` hook. + * in the remote form, this adapter aborts the AJAX submission and submits the form through standard means. + * + * When AJAX submission is aborted then event `ajax:aborted:file` is fired and this allows you to bind your own + * handler to process the form submission the way you want. + * + * For example if you want remote form submission to be cancelled and you do not want to submit the form through + * standard means then you can write following handler. * * Ex: * $('form').live('ajax:aborted:file', function(){ @@ -28,8 +32,8 @@ * providing the `ajax:aborted:required` hook. * Unlike file uploads, however, blank required input fields cancel the whole form submission by default. * - * The default behavior for aborting the remote form submission when required inputs are missing may be - * canceled (thereby submitting the form via AJAX anyway) by binding a handler function that returns + * The default behavior of aborting the remote form submission when required inputs are missing, may be + * changed (thereby submitting the form via AJAX anyway) by binding a handler function that returns * false to the `ajax:aborted:required` hook. * * Ex: From 23a6668e01b05b1c0aa79da7e94080a566b4325e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 17:44:47 -0400 Subject: [PATCH 010/271] comments --- src/rails.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/rails.js b/src/rails.js index 49f8f972..4443a30e 100644 --- a/src/rails.js +++ b/src/rails.js @@ -170,6 +170,8 @@ return false; }, + // find all the submit events directly bound to the form and + // manually invoke them. If anyone returns false then stop the loop callFormSubmitBindings: function(form) { var events = form.data('events'), continuePropagation = true; if (events !== undefined && events['submit'] !== undefined) { @@ -181,6 +183,7 @@ } }; + // ajaxPrefilter is a jQuery 1.5 feature if ('ajaxPrefilter' in $) { $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); } else { From 818c975820b169c188f986147de76d975d69b8fe Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 17:45:14 -0400 Subject: [PATCH 011/271] terminate the loop sooner --- src/rails.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index 4443a30e..69029750 100644 --- a/src/rails.js +++ b/src/rails.js @@ -151,7 +151,10 @@ var blankExists = false, selector = specifiedSelector || 'input'; form.find(selector).each(function() { - if (!$(this).val()) blankExists = true; + if (!$(this).val()) { + blankExists = true; + return false; //this terminates the each loop + } }); return blankExists; }, @@ -160,7 +163,10 @@ var nonBlankExists = false, selector = specifiedSelector || 'input'; form.find(selector).each(function() { - if ($(this).val()) nonBlankExists = true; + if ($(this).val()) { + nonBlankExists = true; + return false; //this terminates the each loop + } }); return nonBlankExists; }, From 329040073434a66f29b8db32f7a3a0c87acde6a0 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Mon, 18 Apr 2011 17:45:35 -0400 Subject: [PATCH 012/271] prefer !== over != --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index 69029750..dbe10626 100644 --- a/src/rails.js +++ b/src/rails.js @@ -210,7 +210,7 @@ }); $('form').live('submit.rails', function(e) { - var form = $(this), remote = form.data('remote') != undefined; + var form = $(this), remote = form.data('remote') !== undefined; if (!rails.allowAction(form)) return rails.stopEverything(e); // skip other logic when required values are missing or file upload is present From 8e9b85a8fef0a7eb684c1a9769af399cc568b3ed Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 18 Apr 2011 19:00:59 -0400 Subject: [PATCH 013/271] Updated rails.js description to be a little more succinct and clear. Added short descriptions to all $.rails functions. --- src/rails.js | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/rails.js b/src/rails.js index dbe10626..1fd63da4 100644 --- a/src/rails.js +++ b/src/rails.js @@ -5,39 +5,39 @@ * 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 submits the form through standard means. + * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means. * - * When AJAX submission is aborted then event `ajax:aborted:file` is fired and this allows you to bind your own - * handler to process the form submission the way you want. - * - * For example if you want remote form submission to be cancelled and you do not want to submit the form through - * standard means then you can write following handler. + * 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', function(){ * // Implement own remote file-transfer handler here. + * // Returning false in this handler tells rails.js to disallow standard form submission * return false; * }); * - * The `ajax:aborted:file` event is fired when a form is submitted and both conditions are met: - * a) file-type input field is detected, and - * b) the value of the input:file field is not blank. + * 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. * - * Similarly, rails.js aborts AJAX form submissions if any non-blank input[required] fields are detected, - * providing the `ajax:aborted:required` hook. - * Unlike file uploads, however, blank required input fields cancel the whole form submission by default. + * Required fields in rails.js + * =========================== + * + * This adapter aborts AJAX form submissions if any non-blank input[required] fields are detected. + * Unlike file uploads, however, blank required input fields cancel the whole form submission, by default. + * + * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs. * - * The default behavior of aborting the remote form submission when required inputs are missing, may be - * changed (thereby submitting the form via AJAX anyway) by binding a handler function that returns - * false to the `ajax:aborted:required` hook. + * !! 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. * * Ex: * $('form').live('ajax:aborted:required', function(){ + * // Returning false in this handler tells rails.js to submit the form anyway * return ! confirm("Would you like to submit the form with missing info?"); * }); */ @@ -125,6 +125,11 @@ form.submit(); }, + /* Disables form elements: + - Caches element value in 'ujs:enable-with' data store + - Replaces element text with value of 'data-disable-with' attribute + - Adds disabled=disabled attribute + */ disableFormElements: function(form) { form.find('input[data-disable-with], button[data-disable-with]').each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; @@ -134,6 +139,10 @@ }); }, + /* Re-enables disabled form elements: + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Removes disabled attribute + */ enableFormElements: function(form) { form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled').each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; @@ -142,11 +151,14 @@ }); }, + // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog. + // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. allowAction: function(element) { var message = element.data('confirm'); return !message || (rails.fire(element, 'confirm') && confirm(message)); }, + // Helper function which checks for blank inputs in a form that match the specified CSS selector blankInputs: function(form, specifiedSelector) { var blankExists = false, selector = specifiedSelector || 'input'; @@ -159,6 +171,7 @@ return blankExists; }, + // Helper function which checks for non-blank inputs in a form that match the specified CSS selector nonBlankInputs: function(form, specifiedSelector) { var nonBlankExists = false, selector = specifiedSelector || 'input'; @@ -171,6 +184,7 @@ return nonBlankExists; }, + // Helper function, needed to provide consistent behavior in IE stopEverything: function(e) { e.stopImmediatePropagation(); return false; From b583a6a4c0c2118c562642fbec1498e07989cebe Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 18 Apr 2011 20:40:50 -0400 Subject: [PATCH 014/271] Refactored required field and file field abort events. -Required field matching now also matches required textareas. -Removed duplicate code in rails.nonBlankInputs function. -`ajax:aborted:required` and `ajax:aborted:file` events are now passed jQuery collection of the inputs that triggered the event. --- src/rails.js | 36 ++++++++++------------- test/public/test/call-remote-callbacks.js | 16 ++++++---- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/rails.js b/src/rails.js index 1fd63da4..d91e04b0 100644 --- a/src/rails.js +++ b/src/rails.js @@ -159,29 +159,22 @@ }, // Helper function which checks for blank inputs in a form that match the specified CSS selector - blankInputs: function(form, specifiedSelector) { - var blankExists = false, - selector = specifiedSelector || 'input'; + blankInputs: function(form, specifiedSelector, nonBlank) { + var inputs = $(), input, + selector = specifiedSelector || 'input,textarea'; form.find(selector).each(function() { - if (!$(this).val()) { - blankExists = true; - return false; //this terminates the each loop + input = $(this); + // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs + if (nonBlank ? input.val() : !input.val()) { + inputs = inputs.add(input); } }); - return blankExists; + return inputs.length ? inputs : false; }, // Helper function which checks for non-blank inputs in a form that match the specified CSS selector nonBlankInputs: function(form, specifiedSelector) { - var nonBlankExists = false, - selector = specifiedSelector || 'input'; - form.find(selector).each(function() { - if ($(this).val()) { - nonBlankExists = true; - return false; //this terminates the each loop - } - }); - return nonBlankExists; + return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank }, // Helper function, needed to provide consistent behavior in IE @@ -224,16 +217,19 @@ }); $('form').live('submit.rails', function(e) { - var form = $(this), remote = form.data('remote') !== undefined; + var form = $(this), + remote = form.data('remote') !== undefined, + blankRequiredInputs = rails.blankInputs(form, 'input[name][required],textarea[name][required]'), + nonBlankFileInputs = rails.nonBlankInputs(form, 'input:file'); if (!rails.allowAction(form)) return rails.stopEverything(e); // skip other logic when required values are missing or file upload is present - if (rails.blankInputs(form, 'input[name][required]') && rails.fire(form, 'ajax:aborted:required')) { + if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { return !remote; } - if (rails.nonBlankInputs(form, 'input:file')) { - return rails.fire(form, 'ajax:aborted:file'); + if (nonBlankFileInputs) { + return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); } // If browser does not support submit bubbling, then this live-binding will be called before direct diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 204339e0..42627a97 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -63,22 +63,26 @@ asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function }); }); -asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 2, function() { +asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() { var form = $('form[data-remote]') .append($('')) + .append($('').appendTo(form); + + form.bind('ajax:success', function(e, data) { + setTimeout(function() { + equal(data.params.user_bio, 'born, lived, died.'); + start(); + }, 13) + }) + form.trigger('submit'); + + ok(textarea.is(':disabled'), 'textarea should be disabled'); + equal(textarea.val(), 'processing ...', 'textarea should have disabled value given to it'); +}); From 1df2d3b95da5652725dc111ac0b45d3913c89efc Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Thu, 21 Apr 2011 05:21:09 +0800 Subject: [PATCH 020/271] Fixed required field description and added better example docs for ajax:aborted events. --- src/rails.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/rails.js b/src/rails.js index 5ff49d91..cf7f0e1c 100644 --- a/src/rails.js +++ b/src/rails.js @@ -13,8 +13,8 @@ * 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', function(){ - * // Implement own remote file-transfer handler here. + * $('form').live('ajax:aborted:file', function(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 * return false; * }); @@ -27,17 +27,18 @@ * Required fields in rails.js * =========================== * - * If any required input field (input[required]) is blank then the AJAX form submission is cancelled. - * Note that unlike file uploads case here the whole form submission is cancelled ,by default. + * If any blank required inputs (required="required") are detected in the remote form, the whole form submission + * is canceled. Note that this is unlike non-blank file inputs, which still allow 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. + * !! 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', function(){ - * // Returning false in this handler tells rails.js to submit the form anyway + * $('form').live('ajax:aborted:required', function(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 ! confirm("Would you like to submit the form with missing info?"); * }); */ From 1171f9c03c2025b8a6ce17596b5318770c20471c Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Thu, 21 Apr 2011 06:42:19 +0800 Subject: [PATCH 021/271] Updated description. --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index cf7f0e1c..72a85ef0 100644 --- a/src/rails.js +++ b/src/rails.js @@ -28,7 +28,7 @@ * =========================== * * If any blank required inputs (required="required") are detected in the remote form, the whole form submission - * is canceled. Note that this is unlike non-blank file inputs, which still allow non-AJAX 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. * From 45a6ff140123487baa0ce9bcdab0f533d12f6d15 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Thu, 21 Apr 2011 06:52:12 +0800 Subject: [PATCH 022/271] Refactored selectors for enable, disable, required, and file inputs to use new $.rails object. --- src/rails.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/rails.js b/src/rails.js index 72a85ef0..7acb87fe 100644 --- a/src/rails.js +++ b/src/rails.js @@ -59,6 +59,18 @@ // Form input elements bound by jquery-ujs formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', + // Form input elements disabled during form submission + disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', + + // Form input elements re-enabled after form submission + enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', + + // Form required input elements + requiredInputSelector: 'input[name][required],textarea[name][required]', + + // Form file input elements + fileInputSelector: 'input:file', + // Make sure that every Ajax request sends the CSRF token CSRFProtection: function(xhr) { var token = $('meta[name="csrf-token"]').attr('content'); @@ -141,7 +153,7 @@ - Adds disabled=disabled attribute */ disableFormElements: function(form) { - form.find('input[data-disable-with], button[data-disable-with], textarea[data-disable-with]').each(function() { + form.find(rails.disableSelector).each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; element.data('ujs:enable-with', element[method]()); element[method](element.data('disable-with')); @@ -154,7 +166,7 @@ - Removes disabled attribute */ enableFormElements: function(form) { - form.find('input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled').each(function() { + form.find(rails.enableSelector).each(function() { var element = $(this), method = element.is('button') ? 'html' : 'val'; if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); element.removeAttr('disabled'); @@ -229,8 +241,8 @@ $(rails.formSubmitSelector).live('submit.rails', function(e) { var form = $(this), remote = form.data('remote') !== undefined, - blankRequiredInputs = rails.blankInputs(form, 'input[name][required],textarea[name][required]'), - nonBlankFileInputs = rails.nonBlankInputs(form, 'input:file'); + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); if (!rails.allowAction(form)) return rails.stopEverything(e); From 07244d5ebb243095694b98e95832b79671151ddb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 29 Apr 2011 18:32:30 -0600 Subject: [PATCH 023/271] Convert tabs to spaces to follow the rest of Rails style --- src/rails.js | 484 +++++++++++++++++++++++++-------------------------- 1 file changed, 241 insertions(+), 243 deletions(-) diff --git a/src/rails.js b/src/rails.js index 7acb87fe..46b2e40e 100644 --- a/src/rails.js +++ b/src/rails.js @@ -44,248 +44,246 @@ */ (function($) { - - // Shorthand to make it a little easier to call public rails functions from within rails.js - var rails; - - $.rails = rails = { - - // Link elements bound by jquery-ujs - linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', - - // Form elements bound by jquery-ujs - formSubmitSelector: 'form', - - // Form input elements bound by jquery-ujs - formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', - - // Form input elements disabled during form submission - disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', - - // Form input elements re-enabled after form submission - enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', - - // Form required input elements - requiredInputSelector: 'input[name][required],textarea[name][required]', - - // Form file input elements - fileInputSelector: 'input:file', - - // Make sure that every Ajax request sends the CSRF token - CSRFProtection: function(xhr) { - var token = $('meta[name="csrf-token"]').attr('content'); - if (token) xhr.setRequestHeader('X-CSRF-Token', token); - }, - - // Triggers an event on an element and returns false if the event result is false - fire: function(obj, name, data) { - var event = $.Event(name); - obj.trigger(event, data); - return event.result !== false; - }, - - // Submits "remote" forms and links with ajax - handleRemote: function(element) { - var method, url, data, - dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); - - if (rails.fire(element, 'ajax:before')) { - - if (element.is('form')) { - method = element.attr('method'); - url = element.attr('action'); - data = element.serializeArray(); - // memoized value from clicked submit button - var button = element.data('ujs:submit-button'); - if (button) { - data.push(button); - element.data('ujs:submit-button', null); - } - } else { - method = element.data('method'); - url = element.attr('href'); - data = null; - } - - $.ajax({ - url: url, type: method || 'GET', data: data, dataType: dataType, - // stopping the "ajax:beforeSend" event will cancel the ajax request - beforeSend: function(xhr, settings) { - if (settings.dataType === undefined) { - xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); - } - return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); - }, - success: function(data, status, xhr) { - element.trigger('ajax:success', [data, status, xhr]); - }, - complete: function(xhr, status) { - element.trigger('ajax:complete', [xhr, status]); - }, - error: function(xhr, status, error) { - element.trigger('ajax:error', [xhr, status, error]); - } - }); - } - }, - - // Handles "data-method" on links such as: - // Delete - handleMethod: function(link) { - var href = link.attr('href'), - method = link.data('method'), - csrf_token = $('meta[name=csrf-token]').attr('content'), - csrf_param = $('meta[name=csrf-param]').attr('content'), - form = $('
'), - metadata_input = ''; - - if (csrf_param !== undefined && csrf_token !== undefined) { - metadata_input += ''; - } - - form.hide().append(metadata_input).appendTo('body'); - form.submit(); - }, - - /* Disables form elements: - - Caches element value in 'ujs:enable-with' data store - - Replaces element text with value of 'data-disable-with' attribute - - Adds disabled=disabled attribute - */ - disableFormElements: function(form) { - form.find(rails.disableSelector).each(function() { - var element = $(this), method = element.is('button') ? 'html' : 'val'; - element.data('ujs:enable-with', element[method]()); - element[method](element.data('disable-with')); - element.attr('disabled', 'disabled'); - }); - }, - - /* Re-enables disabled form elements: - - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) - - Removes disabled attribute - */ - enableFormElements: function(form) { - form.find(rails.enableSelector).each(function() { - var element = $(this), method = element.is('button') ? 'html' : 'val'; - if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); - element.removeAttr('disabled'); - }); - }, - - // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog. - // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. - allowAction: function(element) { - var message = element.data('confirm'); - return !message || (rails.fire(element, 'confirm') && confirm(message)); - }, - - // Helper function which checks for blank inputs in a form that match the specified CSS selector - blankInputs: function(form, specifiedSelector, nonBlank) { - var inputs = $(), input, - selector = specifiedSelector || 'input,textarea'; - form.find(selector).each(function() { - input = $(this); - // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs - if (nonBlank ? input.val() : !input.val()) { - inputs = inputs.add(input); - } - }); - return inputs.length ? inputs : false; - }, - - // Helper function which checks for non-blank inputs in a form that match the specified CSS selector - nonBlankInputs: function(form, specifiedSelector) { - return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank - }, - - // Helper function, needed to provide consistent behavior in IE - stopEverything: function(e) { - e.stopImmediatePropagation(); - return false; - }, - - // find all the submit events directly bound to the form and - // manually invoke them. If anyone returns false then stop the loop - callFormSubmitBindings: function(form) { - var events = form.data('events'), continuePropagation = true; - if (events !== undefined && events['submit'] !== undefined) { - $.each(events['submit'], function(i, obj){ - if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); - }); - } - return continuePropagation; - } - }; - - // ajaxPrefilter is a jQuery 1.5 feature - if ('ajaxPrefilter' in $) { - $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); - } else { - $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); - } - - $(rails.linkClickSelector).live('click.rails', function(e) { - var link = $(this); - if (!rails.allowAction(link)) return rails.stopEverything(e); - - if (link.data('remote') !== undefined) { - rails.handleRemote(link); - return false; - } else if (link.data('method')) { - rails.handleMethod(link); - return false; - } - }); - - $(rails.formSubmitSelector).live('submit.rails', function(e) { - var form = $(this), - remote = form.data('remote') !== undefined, - blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), - nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); - - if (!rails.allowAction(form)) return rails.stopEverything(e); - - // skip other logic when required values are missing or file upload is present - if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { - return !remote; - } - - if (remote) { - if (nonBlankFileInputs) { - return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); - } - - // If browser does not support submit bubbling, then this live-binding will be called before direct - // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. - if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); - - rails.handleRemote(form); - return false; - } else { - // slight timeout so that the submit button gets properly serialized - setTimeout(function(){ rails.disableFormElements(form); }, 13); - } - }); - - $(rails.formInputClickSelector).live('click.rails', function(event) { - var button = $(this); - - if (!rails.allowAction(button)) return rails.stopEverything(event); - - // register the pressed submit button - var name = button.attr('name'), - data = name ? {name:name, value:button.val()} : null; - - button.closest('form').data('ujs:submit-button', data); - }); - - $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) { - if (this == event.target) rails.disableFormElements($(this)); - }); - - $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) { - if (this == event.target) rails.enableFormElements($(this)); - }); + // Shorthand to make it a little easier to call public rails functions from within rails.js + var rails; + + $.rails = rails = { + // Link elements bound by jquery-ujs + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', + + // Form elements bound by jquery-ujs + formSubmitSelector: 'form', + + // Form input elements bound by jquery-ujs + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', + + // Form input elements disabled during form submission + disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', + + // Form input elements re-enabled after form submission + enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', + + // Form required input elements + requiredInputSelector: 'input[name][required],textarea[name][required]', + + // Form file input elements + fileInputSelector: 'input:file', + + // Make sure that every Ajax request sends the CSRF token + CSRFProtection: function(xhr) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + }, + + // Triggers an event on an element and returns false if the event result is false + fire: function(obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + + // Submits "remote" forms and links with ajax + handleRemote: function(element) { + var method, url, data, + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); + + if (rails.fire(element, 'ajax:before')) { + + if (element.is('form')) { + method = element.attr('method'); + url = element.attr('action'); + data = element.serializeArray(); + // memoized value from clicked submit button + var button = element.data('ujs:submit-button'); + if (button) { + data.push(button); + element.data('ujs:submit-button', null); + } + } else { + method = element.data('method'); + url = element.attr('href'); + data = null; + } + + $.ajax({ + url: url, type: method || 'GET', data: data, dataType: dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend: function(xhr, settings) { + if (settings.dataType === undefined) { + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + } + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); + }, + success: function(data, status, xhr) { + element.trigger('ajax:success', [data, status, xhr]); + }, + complete: function(xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + }, + error: function(xhr, status, error) { + element.trigger('ajax:error', [xhr, status, error]); + } + }); + } + }, + + // Handles "data-method" on links such as: + // Delete + handleMethod: function(link) { + var href = link.attr('href'), + method = link.data('method'), + csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'), + form = $('
'), + metadata_input = ''; + + if (csrf_param !== undefined && csrf_token !== undefined) { + metadata_input += ''; + } + + form.hide().append(metadata_input).appendTo('body'); + form.submit(); + }, + + /* Disables form elements: + - Caches element value in 'ujs:enable-with' data store + - Replaces element text with value of 'data-disable-with' attribute + - Adds disabled=disabled attribute + */ + disableFormElements: function(form) { + form.find(rails.disableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + element.data('ujs:enable-with', element[method]()); + element[method](element.data('disable-with')); + element.attr('disabled', 'disabled'); + }); + }, + + /* Re-enables disabled form elements: + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Removes disabled attribute + */ + enableFormElements: function(form) { + form.find(rails.enableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); + element.removeAttr('disabled'); + }); + }, + + // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog. + // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. + allowAction: function(element) { + var message = element.data('confirm'); + return !message || (rails.fire(element, 'confirm') && confirm(message)); + }, + + // Helper function which checks for blank inputs in a form that match the specified CSS selector + blankInputs: function(form, specifiedSelector, nonBlank) { + var inputs = $(), input, + selector = specifiedSelector || 'input,textarea'; + form.find(selector).each(function() { + input = $(this); + // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs + if (nonBlank ? input.val() : !input.val()) { + inputs = inputs.add(input); + } + }); + return inputs.length ? inputs : false; + }, + + // Helper function which checks for non-blank inputs in a form that match the specified CSS selector + nonBlankInputs: function(form, specifiedSelector) { + return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank + }, + + // Helper function, needed to provide consistent behavior in IE + stopEverything: function(e) { + e.stopImmediatePropagation(); + return false; + }, + + // find all the submit events directly bound to the form and + // manually invoke them. If anyone returns false then stop the loop + callFormSubmitBindings: function(form) { + var events = form.data('events'), continuePropagation = true; + if (events !== undefined && events['submit'] !== undefined) { + $.each(events['submit'], function(i, obj){ + if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); + }); + } + return continuePropagation; + } + }; + + // ajaxPrefilter is a jQuery 1.5 feature + if ('ajaxPrefilter' in $) { + $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); + } else { + $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); + } + + $(rails.linkClickSelector).live('click.rails', function(e) { + var link = $(this); + if (!rails.allowAction(link)) return rails.stopEverything(e); + + if (link.data('remote') !== undefined) { + rails.handleRemote(link); + return false; + } else if (link.data('method')) { + rails.handleMethod(link); + return false; + } + }); + + $(rails.formSubmitSelector).live('submit.rails', function(e) { + var form = $(this), + remote = form.data('remote') !== undefined, + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); + + if (!rails.allowAction(form)) return rails.stopEverything(e); + + // skip other logic when required values are missing or file upload is present + if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { + return !remote; + } + + if (remote) { + if (nonBlankFileInputs) { + return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); + } + + // If browser does not support submit bubbling, then this live-binding will be called before direct + // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. + if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); + + rails.handleRemote(form); + return false; + } else { + // slight timeout so that the submit button gets properly serialized + setTimeout(function(){ rails.disableFormElements(form); }, 13); + } + }); + + $(rails.formInputClickSelector).live('click.rails', function(event) { + var button = $(this); + + if (!rails.allowAction(button)) return rails.stopEverything(event); + + // register the pressed submit button + var name = button.attr('name'), + data = name ? {name:name, value:button.val()} : null; + + button.closest('form').data('ujs:submit-button', data); + }); + + $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) { + if (this == event.target) rails.disableFormElements($(this)); + }); + + $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) { + if (this == event.target) rails.enableFormElements($(this)); + }); })( jQuery ); From 61fdae0accaecfb664882dbe4e57a611d2e3467a Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sun, 1 May 2011 15:44:34 -0400 Subject: [PATCH 024/271] Added one other major commit version to changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f652a09..0601b6b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ to newest): - [`e9311550fdb3afeb`](https://github.com/rails/jquery-ujs/commit/e9311550fdb3afeb2917bcb1fef39767bf715003) added CSRF Protection to remote requests - [`a284dd706e7d76e8`](https://github.com/rails/jquery-ujs/commit/a284dd706e7d76e85471ef39ab3efdf07feef374) CSRF fixed - changed to only add if token is present - [`f9b21b3a3c7c4684`](https://github.com/rails/jquery-ujs/commit/f9b21b3a3c7c46840fed8127a90def26911fad3d) `ajax:before` added back +- [`7f2acc1811f62877`](https://github.com/rails/jquery-ujs/commit/7f2acc1811f62877611e16451530728b5e13dbe7) last version before file-upload aborting behavior added - [`ca575e184e93b3ef`](https://github.com/rails/jquery-ujs/commit/ca575e184e93b3efe1a858cf598f8a37f0a760cc) added `ajax:aborted:required` and `ajax:aborted:file` event hooks - [`d2abd6f9df4e4a42`](https://github.com/rails/jquery-ujs/commit/d2abd6f9df4e4a426c17c218b7d5e05004c768d0) fixed submit and bubbling behavior for IE - [`d59144177d867908`](https://github.com/rails/jquery-ujs/commit/d59144177d86790891fdb99b0e3437312e04fda2) created external api via `$.rails` object From cd357e492de147472a8a2524575acce5d923e640 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 3 May 2011 23:55:21 -0400 Subject: [PATCH 025/271] Added support for jQuery 1.6. Dropped support for jQuery 1.4.3. --- README.md | 2 +- src/rails.js | 2 +- test/views/index.erb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e75848cb..894c059c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Full [documentation is on the wiki][wiki], including the [list of published Ajax Requirements ------------ -- [jQuery 1.4.3][jquery] or later; +- [jQuery 1.4.4][jquery] or later; - for Ruby on Rails only: `<%= csrf_meta_tag %>` in the HEAD of your HTML layout; - HTML5 doctype (optional). diff --git a/src/rails.js b/src/rails.js index 46b2e40e..24803b13 100644 --- a/src/rails.js +++ b/src/rails.js @@ -1,7 +1,7 @@ /** * Unobtrusive scripting adapter for jQuery * - * Requires jQuery 1.4.3 or later. + * Requires jQuery 1.4.4 or later. * https://github.com/rails/jquery-ujs * Uploading file using rails.js diff --git a/test/views/index.erb b/test/views/index.erb index 935748a8..33fbb599 100644 --- a/test/views/index.erb +++ b/test/views/index.erb @@ -5,9 +5,9 @@

<%= @title %>

jQuery version: - <%= jquery_link '1.4.3' %> • <%= jquery_link '1.4.4' %> • <%= jquery_link '1.5' %> • + <%= jquery_link '1.6' %> • <%= jquery_link 'edge' if File.exist?(settings.root + '/public/vendor/jquery.js') %>

From a634e7507d45249731b79a801034097e330d27d1 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 3 May 2011 23:57:42 -0400 Subject: [PATCH 026/271] Added jQuery 1.6 support to the changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0601b6b8..6f609da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,3 +17,4 @@ to newest): - [`ca575e184e93b3ef`](https://github.com/rails/jquery-ujs/commit/ca575e184e93b3efe1a858cf598f8a37f0a760cc) added `ajax:aborted:required` and `ajax:aborted:file` event hooks - [`d2abd6f9df4e4a42`](https://github.com/rails/jquery-ujs/commit/d2abd6f9df4e4a426c17c218b7d5e05004c768d0) fixed submit and bubbling behavior for IE - [`d59144177d867908`](https://github.com/rails/jquery-ujs/commit/d59144177d86790891fdb99b0e3437312e04fda2) created external api via `$.rails` object +- [`cd357e492de14747`](https://github.com/rails/jquery-ujs/commit/cd357e492de147472a8a2524575acce5d923e640) added support for jQuery 1.6 and dropped support for jQuery 1.4.3 From 23aaf09874d5245a8f4382ec3ff3a0e5ac60a400 Mon Sep 17 00:00:00 2001 From: Lailson Bandeira Date: Sat, 7 May 2011 18:48:07 -0300 Subject: [PATCH 027/271] Firing `confirmed` event after user choose yes on confirm. --- src/rails.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/rails.js b/src/rails.js index 24803b13..0c67baa1 100644 --- a/src/rails.js +++ b/src/rails.js @@ -171,11 +171,16 @@ }); }, - // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog. - // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. + /* If message provided in 'data-confirm' attribute: + - fires `confirm` event + - shows the confirm dialog + - fires the `confirmed` event + and returns true if no function stopped the chain and user chose yes; false otherwise. + Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. + */ allowAction: function(element) { var message = element.data('confirm'); - return !message || (rails.fire(element, 'confirm') && confirm(message)); + return !message || (rails.fire(element, 'confirm') && confirm(message) && rails.fire(element, 'confirmed')); }, // Helper function which checks for blank inputs in a form that match the specified CSS selector From 50c06dcc02c1b08cb7a9b4b8eced54ed685c1c93 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 7 May 2011 21:53:06 -0400 Subject: [PATCH 028/271] Made confirm:complete event fire always, passing in result of confirm dialog as data. --- src/rails.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index 0c67baa1..fe455b06 100644 --- a/src/rails.js +++ b/src/rails.js @@ -179,8 +179,15 @@ Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. */ allowAction: function(element) { - var message = element.data('confirm'); - return !message || (rails.fire(element, 'confirm') && confirm(message) && rails.fire(element, 'confirmed')); + var message = element.data('confirm'), + answer = false, callback; + if (!message) { return true; } + + if (rails.fire(element, 'confirm')) { + answer = confirm(message); + callback = rails.fire(element, 'confirm:complete', [answer]); + } + return answer && callback; }, // Helper function which checks for blank inputs in a form that match the specified CSS selector From 8063d1d47ea6a08e545e9a6ba3e84af584200e41 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 7 May 2011 21:58:11 -0400 Subject: [PATCH 029/271] Moved default confirm and $.ajax functions to $.rails functions to allow easy overriding/customization. --- src/rails.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index fe455b06..3751c285 100644 --- a/src/rails.js +++ b/src/rails.js @@ -82,6 +82,16 @@ return event.result !== false; }, + // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm + confirm: function(message) { + return confirm(message); + }, + + // Default ajax function, may be overridden with custom function in $.rails.ajax + ajax: function(options) { + return $.ajax(options); + }, + // Submits "remote" forms and links with ajax handleRemote: function(element) { var method, url, data, @@ -105,7 +115,7 @@ data = null; } - $.ajax({ + rails.ajax({ url: url, type: method || 'GET', data: data, dataType: dataType, // stopping the "ajax:beforeSend" event will cancel the ajax request beforeSend: function(xhr, settings) { @@ -184,7 +194,7 @@ if (!message) { return true; } if (rails.fire(element, 'confirm')) { - answer = confirm(message); + answer = rails.confirm(message); callback = rails.fire(element, 'confirm:complete', [answer]); } return answer && callback; From 1f0399852f72d89b38671aa6deceb7c9c03f9fb0 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Tue, 10 May 2011 23:27:21 -0400 Subject: [PATCH 030/271] edits comment --- src/rails.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/rails.js b/src/rails.js index 3751c285..c55f217b 100644 --- a/src/rails.js +++ b/src/rails.js @@ -181,12 +181,16 @@ }); }, - /* If message provided in 'data-confirm' attribute: - - fires `confirm` event - - shows the confirm dialog - - fires the `confirmed` event - and returns true if no function stopped the chain and user chose yes; false otherwise. - Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. + /* + 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. */ allowAction: function(element) { var message = element.data('confirm'), From 19570663a3511bd3819a206af20194c95ff5eec2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 17 May 2011 10:32:43 -0400 Subject: [PATCH 031/271] Updated README with clearer installation instructions, and for latest version of jquery-rails gem. --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 894c059c..7ec41fe3 100644 --- a/README.md +++ b/README.md @@ -36,17 +36,27 @@ In Ruby on Rails 3, the `csrf_meta_tag` helper generates two meta tags containin Installation ------------ -For automated installation, use the "jquery-rails" generator: +1. For automated installation in Rails, use the "jquery-rails" gem. + Place this in your Gemfile: - # Gemfile - gem 'jquery-rails', '>= 0.2.6' + gem 'jquery-rails', '>= 1.0.3' -And run this command (add `--ui` if you want jQuery UI): + And run: - $ bundle install - $ rails generate jquery:install + $ bundle install -This will remove the Prototype.js library from Rails, add latest jQuery library and fetch the adapter. Be sure to choose to overwrite the "rails.js" file. +2. This next step depends on your version of Rails. + + a. For Rails 3.1, add these lines to the top of your app/assets/javascripts/application.js file: + + //= require jquery + //= require jquery_ujs + + b. For Rails 3.0, run this command (add `--ui` if you want jQuery UI): + + $ rails generate jquery:install + + This will remove the Prototype.js library from Rails, add latest jQuery library and fetch the adapter. Be sure to choose to overwrite the "rails.js" file. ### Manual installation From e6a3da2fc2d8391f3bd9055b976e589c9071ea0d Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 17 May 2011 10:39:00 -0400 Subject: [PATCH 032/271] Updated README again. Github-flavored markdown does weird stuff to the ordered lists. Closes #159. --- README.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7ec41fe3..c2cb78e2 100644 --- a/README.md +++ b/README.md @@ -36,27 +36,29 @@ In Ruby on Rails 3, the `csrf_meta_tag` helper generates two meta tags containin Installation ------------ -1. For automated installation in Rails, use the "jquery-rails" gem. - Place this in your Gemfile: +For automated installation in Rails, use the "jquery-rails" gem. Place this in your Gemfile: - gem 'jquery-rails', '>= 1.0.3' + gem 'jquery-rails', '>= 1.0.3' - And run: +And run: - $ bundle install + $ bundle install -2. This next step depends on your version of Rails. +This next step depends on your version of Rails. - a. For Rails 3.1, add these lines to the top of your app/assets/javascripts/application.js file: +a. For Rails 3.1, add these lines to the top of your app/assets/javascripts/application.js file: - //= require jquery - //= require jquery_ujs + //= require jquery + //= require jquery_ujs - b. For Rails 3.0, run this command (add `--ui` if you want jQuery UI): +b. For Rails 3.0, run this command (add `--ui` if you want jQuery UI): - $ rails generate jquery:install +*Be sure to get rid of the rails.js file if it exists, and instead use +the new jquery_ujs.js file that gets copied to the public directory. +Choose to overwrite jquery_ujs.js if prompted.* + + $ rails generate jquery:install - This will remove the Prototype.js library from Rails, add latest jQuery library and fetch the adapter. Be sure to choose to overwrite the "rails.js" file. ### Manual installation From 8591ff82a886ca8a909ace38b737c38cd2f52aba Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 17 May 2011 15:33:52 -0400 Subject: [PATCH 033/271] Fixed ajax:aborted:required to also abort normal submission for non-remote forms. Added ujs:everythingStopped event trigger to make testing easier. Closes #158. --- src/rails.js | 3 ++- test/public/test/call-remote-callbacks.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index c55f217b..c3c52559 100644 --- a/src/rails.js +++ b/src/rails.js @@ -225,6 +225,7 @@ // Helper function, needed to provide consistent behavior in IE stopEverything: function(e) { + $(e.target).trigger('ujs:everythingStopped'); e.stopImmediatePropagation(); return false; }, @@ -272,7 +273,7 @@ // skip other logic when required values are missing or file upload is present if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { - return !remote; + return rails.stopEverything(e); } if (remote) { diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 42627a97..148caf09 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -88,6 +88,20 @@ asyncTest('blank required form input field should abort request and trigger "aja }, 13); }); +asyncTest('blank required form input for non-remote form should abort normal submission', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .removeAttr('data-remote') + .bind('ujs:everythingStopped', function() { + ok(true, 'ujs:everythingStopped should run'); + }) + .trigger('submit'); + + setTimeout(function() { + start(); + }, 13); +}); + asyncTest('form should be submitted with blank required fields if handler is bound to "ajax:aborted:required" event that returns false', 1, function(){ var form = $('form[data-remote]') .append($('')) From a96c4e9b074998c6b6d102e4573b81c8a76f07a7 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 17 May 2011 16:29:46 -0400 Subject: [PATCH 034/271] Changed from jquery 1.6 to 1.6.1. --- test/views/index.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/views/index.erb b/test/views/index.erb index 33fbb599..a0e0e1aa 100644 --- a/test/views/index.erb +++ b/test/views/index.erb @@ -7,7 +7,7 @@ jQuery version: <%= jquery_link '1.4.4' %> • <%= jquery_link '1.5' %> • - <%= jquery_link '1.6' %> • + <%= jquery_link '1.6.1' %> • <%= jquery_link 'edge' if File.exist?(settings.root + '/public/vendor/jquery.js') %>

From e303452dc3a194209f26e131e0cebc67c22f9e16 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 18 May 2011 17:40:06 -0400 Subject: [PATCH 035/271] Added tests for 'confirm' and 'confirm:complete' event hooks. Reformatted and updated description for 'allowAction' function. --- src/rails.js | 21 ++++++------ test/public/test/data-confirm.js | 56 ++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/rails.js b/src/rails.js index c3c52559..9178e963 100644 --- a/src/rails.js +++ b/src/rails.js @@ -181,17 +181,16 @@ }); }, - /* - 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. - */ + /* 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: function(element) { var message = element.data('confirm'), answer = false, callback; diff --git a/test/public/test/data-confirm.js b/test/public/test/data-confirm.js index 758b5918..68784b84 100644 --- a/test/public/test/data-confirm.js +++ b/test/public/test/data-confirm.js @@ -14,12 +14,16 @@ module('data-confirm', { } }); -asyncTest('clicking on a link with data-confirm attribute. Confirm yes.', 4, function() { +asyncTest('clicking on a link with data-confirm attribute. Confirm yes.', 6, function() { var message; // auto-confirm: window.confirm = function(msg) { message = msg; return true }; $('a[data-confirm]') + .bind('confirm:complete', function(e, data) { + App.assert_callback_invoked('confirm:complete'); + ok(data == true, 'confirm:complete passes in confirm answer (true)'); + }) .bind('ajax:success', function(e, data, status, xhr) { App.assert_callback_invoked('ajax:success'); App.assert_request_path(data, '/echo'); @@ -31,12 +35,16 @@ asyncTest('clicking on a link with data-confirm attribute. Confirm yes.', 4, fun .trigger('click'); }); -asyncTest('clicking on a link with data-confirm attribute. Confirm No.', 1, function() { +asyncTest('clicking on a link with data-confirm attribute. Confirm No.', 3, function() { var message; // auto-decline: window.confirm = function(msg) { message = msg; return false }; $('a[data-confirm]') + .bind('confirm:complete', function(e, data) { + App.assert_callback_invoked('confirm:complete'); + ok(data == false, 'confirm:complete passes in confirm answer (false)'); + }) .bind('ajax:beforeSend', function(e, data, status, xhr) { App.assert_callback_not_invoked('ajax:beforeSend'); }) @@ -47,3 +55,47 @@ asyncTest('clicking on a link with data-confirm attribute. Confirm No.', 1, func start(); }, 50); }); + + +asyncTest('binding to confirm event and returning false', 1, function() { + // redefine confirm function so we can make sure it's not called + window.confirm = function(msg) { + ok(false, 'confirm dialog should not be called'); + }; + + $('a[data-confirm]') + .bind('confirm', function() { + App.assert_callback_invoked('confirm'); + return false; + }) + .bind('confirm:complete', function() { + App.assert_callback_not_invoked('confirm:complete') + }) + .trigger('click'); + + setTimeout(function() { + start(); + }, 50); +}); + +asyncTest('binding to confirm:complete event and returning false', 2, function() { + // auto-confirm: + window.confirm = function(msg) { + ok(true, 'confirm dialog should be called'); + return true; + }; + + $('a[data-confirm]') + .bind('confirm:complete', function() { + App.assert_callback_invoked('confirm:complete'); + return false; + }) + .bind('ajax:beforeSend', function() { + App.assert_callback_not_invoked('ajax:beforeSend'); + }) + .trigger('click'); + + setTimeout(function() { + start(); + }, 50); +}); From 8ecc2d7b327d0a571f73a3a1338c2557f033ee69 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 18 May 2011 17:51:35 -0400 Subject: [PATCH 036/271] Updated CHANGELOG with latest major updates. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f609da7..d45a2eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,3 +18,6 @@ to newest): - [`d2abd6f9df4e4a42`](https://github.com/rails/jquery-ujs/commit/d2abd6f9df4e4a426c17c218b7d5e05004c768d0) fixed submit and bubbling behavior for IE - [`d59144177d867908`](https://github.com/rails/jquery-ujs/commit/d59144177d86790891fdb99b0e3437312e04fda2) created external api via `$.rails` object - [`cd357e492de14747`](https://github.com/rails/jquery-ujs/commit/cd357e492de147472a8a2524575acce5d923e640) added support for jQuery 1.6 and dropped support for jQuery 1.4.3 +- [`50c06dcc02c1b08c`](https://github.com/rails/jquery-ujs/commit/50c06dcc02c1b08cb7a9b4b8eced54ed685c1c93) added `confirm:complete` event hook which is passed result from confirm dialog +- [`8063d1d47ea6a08e`](https://github.com/rails/jquery-ujs/commit/8063d1d47ea6a08e545e9a6ba3e84af584200e41) made $.rails.confirm and $.rails.ajax functions available in $.rails object +- [`a96c4e9b074998c6`](https://github.com/rails/jquery-ujs/commit/a96c4e9b074998c6b6d102e4573b81c8a76f07a7) added support for jQuery 1.6.1 From 078c9cf0f89221e098e1501dcb6988d2bc720296 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Fri, 20 May 2011 18:11:21 -0400 Subject: [PATCH 037/271] Fixed ajax:aborted:required test to pass in IE7. --- test/public/test/call-remote-callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 148caf09..6f27bc7c 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -66,7 +66,7 @@ asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() { var form = $('form[data-remote]') .append($('')) - .append($('')) .bind('ajax:beforeSend', function() { ok(false, 'ajax:beforeSend should not run'); }) From dad6982dc592686677e6845e681233c40d2ead27 Mon Sep 17 00:00:00 2001 From: Emerson Macedo Date: Thu, 19 May 2011 15:19:29 -0300 Subject: [PATCH 038/271] Allow link_to with remote => true for method POST with request body. I needed this in my project for a link inside another form, wich I could not insert a form inside another form. Should be usefull for other people. --- src/rails.js | 4 ++-- test/public/test/data-remote.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rails.js b/src/rails.js index 9178e963..b81c3766 100644 --- a/src/rails.js +++ b/src/rails.js @@ -112,8 +112,8 @@ } else { method = element.data('method'); url = element.attr('href'); - data = null; - } + data = element.data('params') || null; + } rails.ajax({ url: url, type: method || 'GET', data: data, dataType: dataType, diff --git a/test/public/test/data-remote.js b/test/public/test/data-remote.js index 694143b9..a498cf5f 100644 --- a/test/public/test/data-remote.js +++ b/test/public/test/data-remote.js @@ -4,6 +4,7 @@ module('data-remote', { .append($('', { href: '/echo', 'data-remote': 'true', + 'data-params': 'data1=value1&data2=value2', text: 'my address' })) .append($('
', { @@ -15,11 +16,13 @@ module('data-remote', { } }); -asyncTest('clicking on a link with data-remote attribute', 3, function() { +asyncTest('clicking on a link with data-remote attribute', 5, function() { $('a[data-remote]') .bind('ajax:success', function(e, data, status, xhr) { App.assert_callback_invoked('ajax:success'); App.assert_request_path(data, '/echo'); + equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value'); + equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value'); App.assert_get_request(data); }) .bind('ajax:complete', function() { start() }) From b73c9affcfac972aafc9034522259b10d77c25a4 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 21 May 2011 14:55:04 +0530 Subject: [PATCH 039/271] moved csrf implem for Rails 2 to the manual install section --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c2cb78e2..ee895d82 100644 --- a/README.md +++ b/README.md @@ -16,23 +16,11 @@ Requirements ------------ - [jQuery 1.4.4][jquery] or later; -- for Ruby on Rails only: `<%= csrf_meta_tag %>` in the HEAD of your HTML layout; +- for Ruby on Rails only: `<%= csrf_meta_tags %>` in the HEAD of your HTML layout (Rails 3.1) - HTML5 doctype (optional). If you don't use HTML5, adding "data" attributes to your HTML4 or XHTML pages might make them fail [W3C markup validation][validator]. However, this shouldn't create any issues for web browsers or other user agents. -In Ruby on Rails 3, the `csrf_meta_tag` helper generates two meta tags containing values necessary for [cross-site request forgery protection][csrf] built into Rails. If you're using Rails 2, here is how to implement that helper: - - # app/helpers/application_helper.rb - def csrf_meta_tag - if protect_against_forgery? - out = %(\n) - out << %() - out % [ Rack::Utils.escape_html(request_forgery_protection_token), - Rack::Utils.escape_html(form_authenticity_token) ] - end - end - Installation ------------ @@ -70,6 +58,17 @@ Configure the following in your application startup file: Now the template helper `javascript_include_tag :defaults` will generate SCRIPT tags to load jQuery and rails.js. +In Ruby on Rails 3.1, the `csrf_meta_tags` helper generates two meta tags containing values necessary for [cross-site request forgery protection][csrf] built into Rails. In Rails 3.0, the helper is named `csrf_meta_tag`. If you're using Rails 2, here is how to implement that helper: + + # app/helpers/application_helper.rb + def csrf_meta_tag + if protect_against_forgery? + out = %(\n) + out << %() + out % [ Rack::Utils.escape_html(request_forgery_protection_token), + Rack::Utils.escape_html(form_authenticity_token) ] + end + end [data]: http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes" [wiki]: https://github.com/rails/jquery-ujs/wiki From 5ca5c8f5b24b491373037f0153147825c64310e2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 21 May 2011 14:48:59 -0400 Subject: [PATCH 040/271] Updated README to be a little clearer. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ee895d82..8dfcfcdc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ Requirements ------------ - [jQuery 1.4.4][jquery] or later; -- for Ruby on Rails only: `<%= csrf_meta_tags %>` in the HEAD of your HTML layout (Rails 3.1) - HTML5 doctype (optional). If you don't use HTML5, adding "data" attributes to your HTML4 or XHTML pages might make them fail [W3C markup validation][validator]. However, this shouldn't create any issues for web browsers or other user agents. @@ -48,7 +47,7 @@ Choose to overwrite jquery_ujs.js if prompted.* $ rails generate jquery:install -### Manual installation +### Manual installation (including Rails 2) [Download jQuery][jquery] and ["rails.js"][adapter] and place them in your "javascripts" directory. @@ -58,7 +57,9 @@ Configure the following in your application startup file: Now the template helper `javascript_include_tag :defaults` will generate SCRIPT tags to load jQuery and rails.js. -In Ruby on Rails 3.1, the `csrf_meta_tags` helper generates two meta tags containing values necessary for [cross-site request forgery protection][csrf] built into Rails. In Rails 3.0, the helper is named `csrf_meta_tag`. If you're using Rails 2, here is how to implement that helper: +For Rails 2, you will need to manually implement the `csrf_meta_tag` helper and include it inside the `` of your application layout. + +The `csrf_meta_tags` (Rails 3.1) and `csrf_meta_tag` (Rails 3.0) helpers generate two meta tags containing values necessary for the [cross-site request forgery protection][csrf] built into Rails. Here is how to implement that helper in Rails 2: # app/helpers/application_helper.rb def csrf_meta_tag From 2b5d45879a16d7e1074a4a4882e43d18164643a2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 21 May 2011 15:14:31 -0400 Subject: [PATCH 041/271] Updated CHANGELOG. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d45a2eaa..bdd25b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,3 +21,4 @@ to newest): - [`50c06dcc02c1b08c`](https://github.com/rails/jquery-ujs/commit/50c06dcc02c1b08cb7a9b4b8eced54ed685c1c93) added `confirm:complete` event hook which is passed result from confirm dialog - [`8063d1d47ea6a08e`](https://github.com/rails/jquery-ujs/commit/8063d1d47ea6a08e545e9a6ba3e84af584200e41) made $.rails.confirm and $.rails.ajax functions available in $.rails object - [`a96c4e9b074998c6`](https://github.com/rails/jquery-ujs/commit/a96c4e9b074998c6b6d102e4573b81c8a76f07a7) added support for jQuery 1.6.1 +- [`dad6982dc5926866`](https://github.com/rails/jquery-ujs/commit/dad6982dc592686677e6845e681233c40d2ead27) added support for `data-params` attribute on remote links From a73ccab3a54c4ac2da4e67eddb2e426b5d8d1f50 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Tue, 24 May 2011 23:44:38 -0400 Subject: [PATCH 042/271] Fixed issue with checking for blank required fields when disabled. Closes #166. --- src/rails.js | 2 +- test/public/test/call-remote-callbacks.js | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index b81c3766..a129ef4a 100644 --- a/src/rails.js +++ b/src/rails.js @@ -64,7 +64,7 @@ enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', // Form required input elements - requiredInputSelector: 'input[name][required],textarea[name][required]', + requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])', // Form file input elements fileInputSelector: 'input:file', diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 6f27bc7c..41585828 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -66,7 +66,7 @@ asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() { var form = $('form[data-remote]') .append($('')) - .append($('')) + .append($('')) .bind('ajax:beforeSend', function() { ok(false, 'ajax:beforeSend should not run'); }) @@ -118,6 +118,23 @@ asyncTest('form should be submitted with blank required fields if handler is bou }, 13); }); +asyncTest('disabled fields should not be included in blank required check', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .append($('')) + .bind('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run'); + }) + .bind('ajax:aborted:required', function() { + ok(false, 'ajax:aborted:required should not run'); + }) + .trigger('submit'); + + setTimeout(function() { + start(); + }, 13); +}); + function skipIt() { // This test cannot work due to the security feature in browsers which makes the value // attribute of file input fields readonly, so it cannot be set with default value. From 4318e68c246271412eae7c3d00d171fc1b6ac8b2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 4 Jun 2011 00:10:19 -0400 Subject: [PATCH 043/271] Added tests specifically for live-binding ajax callback handlers. --- test/public/test/call-remote-callbacks.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 41585828..de3c74c2 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -9,6 +9,8 @@ module('call-remote-callbacks', { teardown: function() { $('form[data-remote]').die('ajax:beforeSend'); $('form[data-remote]').die('ajax:before'); + $('form[data-remote]').die('ajax:complete'); + $('form[data-remote]').die('ajax:success'); } }); @@ -233,4 +235,22 @@ asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on }); }); +asyncTest('binding to ajax callbacks via .live() triggers handlers properly', 3, function() { + $('form[data-remote]') + .live('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend handler is triggered'); + }) + .live('ajax:complete', function() { + ok(true, 'ajax:complete handler is triggered'); + }) + .live('ajax:success', function() { + ok(true, 'ajax:success handler is triggered'); + }) + .trigger('submit'); + + setTimeout(function() { + start(); + }, 13); +}); + })(); From 5e997acda67c4c52c95d6b0b394c63eabfab5950 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sat, 4 Jun 2011 00:13:45 -0400 Subject: [PATCH 044/271] Cleaned up test file. --- test/public/test/call-remote-callbacks.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index de3c74c2..126c9bb3 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -20,7 +20,7 @@ function submit(fn) { ok(true, 'ajax:complete'); start(); }); - + if (fn) fn(form); form.trigger('submit'); } @@ -39,7 +39,7 @@ asyncTest('modifying form fields with "ajax:before" sends modified data in reque }); submit(function(form) { - form.bind('ajax:success', function(e, data, status, xhr) { + form.bind('ajax:success', function(e, data, status, xhr) { equal(data.params.user_name, 'steve', 'modified field value should have been submitted'); equal(data.params.other_user_name, 'jonathan', 'added field value should have been submitted'); equal(data.params.removed_user_name, undefined, 'removed field value should be undefined'); @@ -156,7 +156,7 @@ function skipIt() { ok(true, 'ajax:aborted:file event should run'); }) .trigger('submit'); - + setTimeout(function() { form.find('input[type="file"]').val(''); form.unbind('ajax:beforeSend'); @@ -164,7 +164,7 @@ function skipIt() { }, 13); }); - syncTest('blank file input field should abort request entirely if handler bound to "ajax:aborted:file" event that returns false', 1, function() { + asyncTest('blank file input field should abort request entirely if handler bound to "ajax:aborted:file" event that returns false', 1, function() { var form = $('form[data-remote]') .append($('')) .bind('ajax:beforeSend', function() { @@ -177,7 +177,7 @@ function skipIt() { return false; }) .trigger('submit'); - + setTimeout(function() { form.find('input[type="file"]').val(''); form.unbind('ajax:beforeSend'); @@ -191,7 +191,7 @@ asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation', ok(true, 'ajax:beforeSend observed with event delegation'); return false; }); - + submit(function(form) { form.unbind('ajax:complete').bind('ajax:complete', function() { ok(false, 'ajax:complete should not run'); @@ -224,7 +224,7 @@ asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on submit(function(form) { form.attr('action', '/error'); form.bind('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') }); - form.bind('ajax:error', function(e, xhr, status, error) { + form.bind('ajax:error', function(e, xhr, status, error) { ok(xhr.getResponseHeader, 'first argument to "ajax:error" should be an XHR object'); equal(status, 'error', 'second argument to ajax:error should be a status string'); if (jQuery().jquery.indexOf('1.4') === 0) strictEqual(error, undefined) From 3c360e34fac79a951108842529f6a48f287734f5 Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Sat, 11 Jun 2011 03:15:13 -0400 Subject: [PATCH 045/271] Please don't define undefined --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index a129ef4a..5f726be0 100644 --- a/src/rails.js +++ b/src/rails.js @@ -43,7 +43,7 @@ * }); */ -(function($) { +(function($, undefined) { // Shorthand to make it a little easier to call public rails functions from within rails.js var rails; From d6c35ec37875d8aa73497b6e1f43e1919d30801e Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 15 Jun 2011 18:20:00 -0400 Subject: [PATCH 046/271] Fixed intermittently failing test in FF. --- test/public/test/call-remote-callbacks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 126c9bb3..c15b078e 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -235,6 +235,7 @@ asyncTest('"ajax:beforeSend", "ajax:error" and "ajax:complete" are triggered on }); }); +// IF THIS TEST IS FAILING, TRY INCREASING THE TIMEOUT AT THE BOTTOM TO > 100 asyncTest('binding to ajax callbacks via .live() triggers handlers properly', 3, function() { $('form[data-remote]') .live('ajax:beforeSend', function() { @@ -250,7 +251,7 @@ asyncTest('binding to ajax callbacks via .live() triggers handlers properly', 3, setTimeout(function() { start(); - }, 13); + }, 63); }); })(); From 4188a64eeca650935d0aeaa1cec181f926c99b4e Mon Sep 17 00:00:00 2001 From: Ciaran Lee Date: Fri, 27 May 2011 17:45:37 +0100 Subject: [PATCH 047/271] fixing cross domain AJAX for jQuery > 1.5 --- src/rails.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index 5f726be0..f2eb83ad 100644 --- a/src/rails.js +++ b/src/rails.js @@ -95,6 +95,7 @@ // Submits "remote" forms and links with ajax handleRemote: function(element) { var method, url, data, + crossDomain = element.data('cross-domain') || false, dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); if (rails.fire(element, 'ajax:before')) { @@ -116,7 +117,7 @@ } rails.ajax({ - url: url, type: method || 'GET', data: data, dataType: dataType, + url: url, type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain, // stopping the "ajax:beforeSend" event will cancel the ajax request beforeSend: function(xhr, settings) { if (settings.dataType === undefined) { @@ -244,7 +245,7 @@ // ajaxPrefilter is a jQuery 1.5 feature if ('ajaxPrefilter' in $) { - $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); + $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); } else { $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); } From 616e4fe354ce0ae5137636f9b5475782a7c9e896 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 15 Jun 2011 18:02:25 -0400 Subject: [PATCH 048/271] Created test for CSRF token being excluded when data-cross-domain=true. Fixed token-header-exclusion for jquery 1.4.4. Also created test for intelligently guessing cross-domain behavior when target url is on a different domain, and fixed behavior for this case. See Issue #167 for all relevant discussion. --- src/rails.js | 4 ++-- test/public/test/call-remote.js | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index f2eb83ad..5397d669 100644 --- a/src/rails.js +++ b/src/rails.js @@ -95,7 +95,7 @@ // Submits "remote" forms and links with ajax handleRemote: function(element) { var method, url, data, - crossDomain = element.data('cross-domain') || false, + crossDomain = element.data('cross-domain') || null, dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); if (rails.fire(element, 'ajax:before')) { @@ -247,7 +247,7 @@ if ('ajaxPrefilter' in $) { $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); } else { - $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); + $(document).ajaxSend(function(e, xhr, options){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); } $(rails.linkClickSelector).live('click.rails', function(e) { diff --git a/test/public/test/call-remote.js b/test/public/test/call-remote.js index 33f5c59d..b2373695 100644 --- a/test/public/test/call-remote.js +++ b/test/public/test/call-remote.js @@ -92,4 +92,41 @@ asyncTest('sends CSRF token in custom header', 1, function() { }); }); +asyncTest('does not send CSRF token in custom header if crossDomain', 1, function() { + build_form({ 'data-cross-domain': 'true' }); + $('#qunit-fixture').append(''); + + // Manually set request header to be XHR, since setting crossDomain: true in .ajax() + // causes jQuery to skip setting the request header, to prevent our test/server.rb from + // raising an an error (when request.xhr? is false). + $('#qunit-fixture').find('form').bind('ajax:beforeSend', function(e, xhr) { + xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest"); + }); + + submit(function(e, data, status, xhr) { + equal(data.HTTP_X_CSRF_TOKEN, undefined, 'X-CSRF-Token header should NOT be sent'); + }); +}); + +asyncTest('intelligently guesses crossDomain behavior when target URL is a different domain', 1, function(e, xhr) { + + // Don't set data-cross-domain here, just set action to be a different domain than localhost + build_form({ action: 'http://www.alfajango.com' }); + $('#qunit-fixture').append(''); + + $('#qunit-fixture').find('form') + .bind('ajax:beforeSend', function(e, xhr, settings) { + + // crossDomain doesn't work with jquery 1.4, because it wasn't added until 1.5 + if (jQuery().jquery.indexOf('1.4') === 0) strictEqual(settings.crossDomain, null) + else equal(settings.crossDomain, true, 'crossDomain should be set to true'); + + // prevent request from actually getting sent off-domain + return false; + }) + .trigger('submit'); + + setTimeout(function() { start(); }, 13); +}); + })(); From e33f3f7a156709949d8ec7db99fbff4ac9b8f5dd Mon Sep 17 00:00:00 2001 From: Otavio Medeiros Date: Tue, 7 Jun 2011 23:24:04 -0300 Subject: [PATCH 049/271] added remote => true to on change event in select option --- src/rails.js | 18 +++++++++++++++++- test/public/test/data-remote.js | 25 ++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/rails.js b/src/rails.js index a129ef4a..44e097f3 100644 --- a/src/rails.js +++ b/src/rails.js @@ -51,6 +51,9 @@ // Link elements bound by jquery-ujs linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', + // Select elements bound by jquery-ujs + selectChangeSelector: 'select[data-remote]', + // Form elements bound by jquery-ujs formSubmitSelector: 'form', @@ -109,7 +112,12 @@ data.push(button); element.data('ujs:submit-button', null); } - } else { + } else if (element.is('select')) { + method = element.data('method'); + url = element.data('url'); + data = element.serialize(); + if (element.data('params')) data = data + "&" + element.data('params'); + } else { method = element.data('method'); url = element.attr('href'); data = element.data('params') || null; @@ -262,6 +270,14 @@ } }); + $(rails.selectChangeSelector).live('change.rails', function(e) { + var link = $(this); + if (!rails.allowAction(link)) return rails.stopEverything(e); + + rails.handleRemote(link); + return false; + }); + $(rails.formSubmitSelector).live('submit.rails', function(e) { var form = $(this), remote = form.data('remote') !== undefined, diff --git a/test/public/test/data-remote.js b/test/public/test/data-remote.js index a498cf5f..19bdcbca 100644 --- a/test/public/test/data-remote.js +++ b/test/public/test/data-remote.js @@ -12,7 +12,16 @@ module('data-remote', { 'data-remote': 'true', method: 'post' })) - .find('form').append($('')); + .find('form').append($('')) + .append($('')) - .append($('')); + } }); @@ -39,6 +31,18 @@ asyncTest('clicking on a link with data-remote attribute', 5, function() { }); asyncTest('changing a select option with data-remote attribute', 5, function() { + $('form') + .append( + $('')) + .attr("novalidate", "novalidate") + .bind('ajax:beforeSend', function() { + ok(true, 'ajax:beforeSend should run'); + }) + .bind('ajax:complete', function() { + ok(true, 'ajax:complete should run') + }) + .trigger('submit'); + + setTimeout(function() { + start(); + }, 13); +}); + +asyncTest('blank required form input for non-remote form with "novalidate" attribute should not abort normal submission', 1, function() { + var form = $('form[data-remote]') + .append($('')) + .removeAttr('data-remote') + .attr("novalidate","novalidate") + .bind('iframe:loading', function() { + ok(true, 'form should get submitted'); + }) + .trigger('submit'); + + setTimeout(function() { + start(); + }, 13); +}); + function skipIt() { // This test cannot work due to the security feature in browsers which makes the value // attribute of file input fields readonly, so it cannot be set with default value. From 36a81a3e9a44246391b554f105e7e83f86a66c75 Mon Sep 17 00:00:00 2001 From: Rafael Barbosa Date: Sat, 30 Jul 2011 14:52:33 -0300 Subject: [PATCH 058/271] Changing the order of evaluation so the abort:required event won't be fired on forms with the novalidate attribute. --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index 5da48602..b17d5e79 100644 --- a/src/rails.js +++ b/src/rails.js @@ -287,7 +287,7 @@ if (!rails.allowAction(form)) return rails.stopEverything(e); // skip other logic when required values are missing or file upload is present - if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs]) && form.attr("novalidate") == undefined) { + if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { return rails.stopEverything(e); } From cd619df9f0daad3303aacd4f992fff19158b1e5d Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 10 Aug 2011 16:27:01 -0400 Subject: [PATCH 059/271] Fixed up test for novalidate attribute --- test/public/test/call-remote-callbacks.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/test/public/test/call-remote-callbacks.js b/test/public/test/call-remote-callbacks.js index 57d4f59c..939f523f 100644 --- a/test/public/test/call-remote-callbacks.js +++ b/test/public/test/call-remote-callbacks.js @@ -120,7 +120,7 @@ asyncTest('form should be submitted with blank required fields if handler is bou }, 13); }); -asyncTest('disabled fields should not be included in blank required check', 1, function() { +asyncTest('disabled fields should not be included in blank required check', 2, function() { var form = $('form[data-remote]') .append($('')) .append($('')) @@ -129,12 +129,9 @@ asyncTest('disabled fields should not be included in blank required check', 1, f }) .bind('ajax:aborted:required', function() { ok(false, 'ajax:aborted:required should not run'); - }) - .trigger('submit'); + }); - setTimeout(function() { - start(); - }, 13); + submit(); }); asyncTest('form should be submitted with blank required fields if it has the "novalidate" attribute', 2, function(){ @@ -144,14 +141,11 @@ asyncTest('form should be submitted with blank required fields if it has the "no .bind('ajax:beforeSend', function() { ok(true, 'ajax:beforeSend should run'); }) - .bind('ajax:complete', function() { - ok(true, 'ajax:complete should run') - }) - .trigger('submit'); + .bind('ajax:aborted:required', function() { + ok(false, 'ajax:aborted:required should not run'); + }); - setTimeout(function() { - start(); - }, 13); + submit(); }); asyncTest('blank required form input for non-remote form with "novalidate" attribute should not abort normal submission', 1, function() { From 4bf967e8daa0421287af1bf9242eb4ce41b5c469 Mon Sep 17 00:00:00 2001 From: Radoslav Stankov Date: Tue, 16 Aug 2011 15:06:16 +0300 Subject: [PATCH 060/271] Prevent global variable leak --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index b17d5e79..017f32b7 100644 --- a/src/rails.js +++ b/src/rails.js @@ -124,7 +124,7 @@ data = element.data('params') || null; } - options = { + var options = { type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain, // stopping the "ajax:beforeSend" event will cancel the ajax request beforeSend: function(xhr, settings) { From b4e3e1a3f8aba47b4d456ccf38c973ca90e8d3b8 Mon Sep 17 00:00:00 2001 From: Radoslav Stankov Date: Tue, 16 Aug 2011 15:07:06 +0300 Subject: [PATCH 061/271] Minor optimization --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index 017f32b7..236cfa40 100644 --- a/src/rails.js +++ b/src/rails.js @@ -144,7 +144,7 @@ } }; // Do not pass url to `ajax` options if blank - if (url) { $.extend(options, { url: url }); } + if (url) { options.url = url; } rails.ajax(options); } From 296815268b4469ccdd2f23970cc2efe985fa6fa3 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 29 Aug 2011 18:59:46 -0400 Subject: [PATCH 062/271] Updated options var --- src/rails.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rails.js b/src/rails.js index 236cfa40..d6e585fa 100644 --- a/src/rails.js +++ b/src/rails.js @@ -99,7 +99,8 @@ handleRemote: function(element) { var method, url, data, crossDomain = element.data('cross-domain') || null, - dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType), + options; if (rails.fire(element, 'ajax:before')) { @@ -124,7 +125,7 @@ data = element.data('params') || null; } - var options = { + options = { type: method || 'GET', data: data, dataType: dataType, crossDomain: crossDomain, // stopping the "ajax:beforeSend" event will cancel the ajax request beforeSend: function(xhr, settings) { @@ -143,7 +144,7 @@ element.trigger('ajax:error', [xhr, status, error]); } }; - // Do not pass url to `ajax` options if blank + // Only pass url to `ajax` options if not blank if (url) { options.url = url; } rails.ajax(options); From d9f55ec9ee66504ab17959fee3cb14a9afadfc52 Mon Sep 17 00:00:00 2001 From: Akzhan Abdulin Date: Wed, 31 Aug 2011 17:02:37 +0400 Subject: [PATCH 063/271] GitHub syntax highlighting in README --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8e50a82c..5e13d998 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,17 @@ For Rails 2, you will need to manually implement the `csrf_meta_tag` helper and The `csrf_meta_tags` (Rails 3.1) and `csrf_meta_tag` (Rails 3.0) helpers generate two meta tags containing values necessary for the [cross-site request forgery protection][csrf] built into Rails. Here is how to implement that helper in Rails 2: - # app/helpers/application_helper.rb - def csrf_meta_tag - if protect_against_forgery? - out = %(\n) - out << %() - out % [ Rack::Utils.escape_html(request_forgery_protection_token), - Rack::Utils.escape_html(form_authenticity_token) ] - end +```ruby + # app/helpers/application_helper.rb + def csrf_meta_tag + if protect_against_forgery? + out = %(\n) + out << %() + out % [ Rack::Utils.escape_html(request_forgery_protection_token), + Rack::Utils.escape_html(form_authenticity_token) ] end + end +``` [data]: http://dev.w3.org/html5/spec/elements.html#embedding-custom-non-visible-data-with-the-data-attributes "Embedding custom non-visible data with the data-* attributes" [wiki]: https://github.com/rails/jquery-ujs/wiki From 9b8af35a817517e1f7158ba0e7eb61d219e168a5 Mon Sep 17 00:00:00 2001 From: Akzhan Abdulin Date: Wed, 31 Aug 2011 17:05:10 +0400 Subject: [PATCH 064/271] more syntax highlights --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5e13d998..0a200d8e 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,9 @@ Installation For automated installation in Rails, use the "jquery-rails" gem. Place this in your Gemfile: - gem 'jquery-rails', '>= 1.0.12' +```ruby +gem 'jquery-rails', '>= 1.0.12' +``` And run: @@ -35,8 +37,10 @@ This next step depends on your version of Rails. a. For Rails 3.1, add these lines to the top of your app/assets/javascripts/application.js file: - //= require jquery - //= require jquery_ujs +```javascript +//= require jquery +//= require jquery_ujs +``` b. For Rails 3.0, run this command (add `--ui` if you want jQuery UI): @@ -53,7 +57,9 @@ Choose to overwrite jquery_ujs.js if prompted.* Configure the following in your application startup file: - config.action_view.javascript_expansions[:defaults] = %w(jquery rails) +```ruby + config.action_view.javascript_expansions[:defaults] = %w(jquery rails) +``` Now the template helper `javascript_include_tag :defaults` will generate SCRIPT tags to load jQuery and rails.js. From ee275d15342c41f9088137829b1a9b9c0cf304be Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Wed, 31 Aug 2011 10:44:17 -0400 Subject: [PATCH 065/271] Fixed blank url test for jquery 1.6.2 --- test/public/test/call-remote.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/public/test/call-remote.js b/test/public/test/call-remote.js index 0a420b13..b5e3a8d3 100644 --- a/test/public/test/call-remote.js +++ b/test/public/test/call-remote.js @@ -98,7 +98,7 @@ asyncTest('allow empty form "action"', 1, function() { } // Actual location (strip out settings.data that jQuery serializes and appends) - ajaxLocation = settings.url.replace(settings.data,"").replace(/&$/, ""); + ajaxLocation = settings.url.replace(settings.data,"").replace(/&$/, "").replace(/\?$/, ""); equal(ajaxLocation.match(/^(.*)/)[1], currentLocation, 'URL should be current page by default'); // Prevent the request from actually getting sent to the current page and From 840ab6ac76b2d5ab931841bc3d8567e5b57f183e Mon Sep 17 00:00:00 2001 From: Akzhan Abdulin Date: Fri, 2 Sep 2011 12:05:59 +0400 Subject: [PATCH 066/271] Add jQuery version 1.6.3 to tests, and use it by default --- test/server.rb | 8 +++++++- test/views/index.erb | 10 ++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/test/server.rb b/test/server.rb index 095eed0e..8a9bc259 100644 --- a/test/server.rb +++ b/test/server.rb @@ -1,6 +1,8 @@ require 'sinatra' require 'json' +JQUERY_VERSIONS = %w[ 1.6 1.6.1 1.6.2 1.6.3 ].freeze + use Rack::Static, :urls => ["/src"], :root => File.expand_path('..', settings.root) helpers do @@ -39,10 +41,14 @@ def script_tag src src = "/test/#{src}.js" unless src.index('/') %() end + + def jquery_versions + JQUERY_VERSIONS + end end get '/' do - params[:version] ||= '1.6.2' + params[:version] ||= JQUERY_VERSIONS.last params[:cdn] ||= 'jquery' erb :index end diff --git a/test/views/index.erb b/test/views/index.erb index 3ec4c6f2..21c7bc6c 100644 --- a/test/views/index.erb +++ b/test/views/index.erb @@ -10,10 +10,12 @@
jQuery version: - <%= jquery_link '1.6' %> • - <%= jquery_link '1.6.1' %> • - <%= jquery_link '1.6.2' %> • - <%= jquery_link 'edge' if File.exist?(settings.root + '/public/vendor/jquery.js') %> + + <% jquery_versions.each do |v| %> + <%= ' • ' if v != jquery_versions.first %> + <%= jquery_link v %> + <% end %> + <%= (' • ' + jquery_link('edge')) if File.exist?(settings.root + '/public/vendor/jquery.js') %>

From ba5808e73111fb65e91610b078577bb014d9b6d8 Mon Sep 17 00:00:00 2001 From: Surendra Singhi Date: Fri, 2 Sep 2011 16:40:23 +0530 Subject: [PATCH 067/271] add checkboxes support to jquery-ujs --- src/rails.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rails.js b/src/rails.js index d6e585fa..1d5ba64a 100644 --- a/src/rails.js +++ b/src/rails.js @@ -52,7 +52,7 @@ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', // Select elements bound by jquery-ujs - selectChangeSelector: 'select[data-remote]', + selectChangeSelector: 'select[data-remote], input[type="checkbox"][data-remote]', // Form elements bound by jquery-ujs formSubmitSelector: 'form', @@ -114,15 +114,15 @@ data.push(button); element.data('ujs:submit-button', null); } - } else if (element.is('select')) { + } else if (element.is('select') || element.is("input[type='checkbox']")) { method = element.data('method'); url = element.data('url'); data = element.serialize(); - if (element.data('params')) data = data + "&" + element.data('params'); + if (element.data('params')) data = data + "&" + element.data('params'); } else { method = element.data('method'); url = element.attr('href'); - data = element.data('params') || null; + data = element.data('params') || null; } options = { @@ -277,7 +277,7 @@ rails.handleRemote(link); return false; - }); + }); $(rails.formSubmitSelector).live('submit.rails', function(e) { var form = $(this), From 6e9a06d45eaf2da1036d4c2ead25ff57d0127d03 Mon Sep 17 00:00:00 2001 From: Markus Doits Date: Mon, 29 Aug 2011 12:35:16 +0200 Subject: [PATCH 068/271] Allow 'data-disable-with' to work on links - Simple element's inner html substitution by what is in 'data-disable-with' - Element is reenabled after a possible remote call - Element is not disabled if a confirmation returns false - Test tdb --- src/rails.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index b17d5e79..ed3c252f 100644 --- a/src/rails.js +++ b/src/rails.js @@ -49,7 +49,7 @@ $.rails = rails = { // Link elements bound by jquery-ujs - linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]', // Select elements bound by jquery-ujs selectChangeSelector: 'select[data-remote]', @@ -72,6 +72,9 @@ // Form file input elements fileInputSelector: 'input:file', + // Link onClick disable selector with possible reenable after remote submission + linkDisableSelector: 'a[data-disable-with]', + // Make sure that every Ajax request sends the CSRF token CSRFProtection: function(xhr) { var token = $('meta[name="csrf-token"]').attr('content'); @@ -252,15 +255,41 @@ }); } return continuePropagation; + }, + + // replace element's html with the 'data-disable-with' after storing original html + // and prevent clicking on it + disableElement: function(element) { + element.data('ujs:enable-with', element.html()); // store enabled state + element.html(element.data('disable-with')); // set to disabled state + element.bind('click.railsDisable', function(e) { // prevent further clicking + return rails.stopEverything(e) + }); + }, + + // restore element to its original state which was disabled by 'disableElement' above + enableElement: function(element) { + if (element.data('ujs:enable-with') !== undefined) { + element.html(element.data('ujs:enable-with')); // set to old enabled state + element.removeData('ujs:enable-with'); // clean up cache + } + element.unbind('click.railsDisable'); // enable element } + }; $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); + $(rails.linkDisableSelector).live('ajax:complete', function() { + rails.enableElement($(this)); + }); + $(rails.linkClickSelector).live('click.rails', function(e) { var link = $(this); if (!rails.allowAction(link)) return rails.stopEverything(e); + if (link.is(rails.linkDisableSelector)) rails.disableElement(link); + if (link.data('remote') !== undefined) { rails.handleRemote(link); return false; From dd9384f6332d450721354dbab6f959d961b06bc9 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sun, 4 Sep 2011 17:45:05 -0400 Subject: [PATCH 069/271] Fixed up blank form action test. Kept jquery 1.6.2 as default until 1.6.3 is fixed. --- test/public/test/call-remote.js | 1 + test/server.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/public/test/call-remote.js b/test/public/test/call-remote.js index b5e3a8d3..37ba0a2c 100644 --- a/test/public/test/call-remote.js +++ b/test/public/test/call-remote.js @@ -96,6 +96,7 @@ asyncTest('allow empty form "action"', 1, function() { currentLocation.href = ""; currentLocation = currentLocation.href; } + currentLocation = currentLocation.replace(/\?$/, ''); // Actual location (strip out settings.data that jQuery serializes and appends) ajaxLocation = settings.url.replace(settings.data,"").replace(/&$/, "").replace(/\?$/, ""); diff --git a/test/server.rb b/test/server.rb index 8a9bc259..a0a71873 100644 --- a/test/server.rb +++ b/test/server.rb @@ -48,7 +48,7 @@ def jquery_versions end get '/' do - params[:version] ||= JQUERY_VERSIONS.last + params[:version] ||= '1.6.2' params[:cdn] ||= 'jquery' erb :index end From 89396108ce574080f9b877cad74573c5d1ae9aa2 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sun, 4 Sep 2011 18:10:36 -0400 Subject: [PATCH 070/271] Added change selector handling for all remote inputs, selects, and textareas. Closes #199 --- src/rails.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rails.js b/src/rails.js index 1d5ba64a..262d43ba 100644 --- a/src/rails.js +++ b/src/rails.js @@ -52,7 +52,7 @@ linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', // Select elements bound by jquery-ujs - selectChangeSelector: 'select[data-remote], input[type="checkbox"][data-remote]', + inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', // Form elements bound by jquery-ujs formSubmitSelector: 'form', @@ -114,7 +114,7 @@ data.push(button); element.data('ujs:submit-button', null); } - } else if (element.is('select') || element.is("input[type='checkbox']")) { + } else if (element.is(rails.inputChangeSelector)) { method = element.data('method'); url = element.data('url'); data = element.serialize(); @@ -271,7 +271,7 @@ } }); - $(rails.selectChangeSelector).live('change.rails', function(e) { + $(rails.inputChangeSelector).live('change.rails', function(e) { var link = $(this); if (!rails.allowAction(link)) return rails.stopEverything(e); From 3af83ecf4670202abe81f2e817fcef6b35f137d9 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sun, 4 Sep 2011 20:04:12 -0400 Subject: [PATCH 071/271] formatting --- src/rails.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rails.js b/src/rails.js index 262d43ba..39efde25 100644 --- a/src/rails.js +++ b/src/rails.js @@ -120,9 +120,9 @@ data = element.serialize(); if (element.data('params')) data = data + "&" + element.data('params'); } else { - method = element.data('method'); - url = element.attr('href'); - data = element.data('params') || null; + method = element.data('method'); + url = element.attr('href'); + data = element.data('params') || null; } options = { From eb732f4ea15f854ccf874d4936cbbdfdbbf7c180 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Sun, 4 Sep 2011 22:06:43 -0400 Subject: [PATCH 072/271] Created tests for disabling links. Fixed removing data for bug in jquery --- src/rails.js | 4 ++- test/public/test/data-disable.js | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index 98c5c9ad..1504a183 100644 --- a/src/rails.js +++ b/src/rails.js @@ -272,7 +272,9 @@ enableElement: function(element) { if (element.data('ujs:enable-with') !== undefined) { element.html(element.data('ujs:enable-with')); // set to old enabled state - element.removeData('ujs:enable-with'); // clean up cache + // this should be element.removeData('ujs:enable-with') + // but, there is currently a bug in jquery which makes hyphenated data attributes not get removed + element.data('ujs:enable-with', false); // clean up cache } element.unbind('click.railsDisable'); // enable element } diff --git a/test/public/test/data-disable.js b/test/public/test/data-disable.js index 2fa04b1a..9f5a802e 100644 --- a/test/public/test/data-disable.js +++ b/test/public/test/data-disable.js @@ -15,6 +15,12 @@ module('data-disable', { .find('form:last') // WEEIRDD: the form won't submit to an iframe if the button is name="submit" (??!) .append($('')); + + $('#qunit-fixture').append($('
', { + text: 'Click me', + href: '/echo', + 'data-disable-with': 'clicking...' + })); } }); @@ -133,3 +139,44 @@ asyncTest('form textarea with "data-disable-with" attribute', 3, function() { ok(textarea.is(':disabled'), 'textarea should be disabled'); equal(textarea.val(), 'processing ...', 'textarea should have disabled value given to it'); }); + +asyncTest('link with "data-disable-with" attribute disables', 4, function() { + var link = $('a[data-disable-with]'); + + ok(!link.data('ujs:enable-with'), 'link should not be disabled'); + equal(link.html(), 'Click me', 'link should have value given to it'); + + function checkDisabledLink() { + ok(link.data('ujs:enable-with'), 'link should be disabled'); + equal(link.html(), 'clicking...'); + } + + link.trigger('click'); + checkDisabledLink(); + start(); +}); + +asyncTest('remote link with "data-disable-with" attribute disables and re-enables', 6, function() { + var link = $('a[data-disable-with]').attr('data-remote', true); + + function checkEnabledLink() { + ok(!link.data('ujs:enable-with'), 'link should not be disabled'); + equal(link.html(), 'Click me', 'link should have value given to it'); + } + checkEnabledLink(); + + function checkDisabledLink() { + ok(link.data('ujs:enable-with'), 'link should be disabled'); + equal(link.html(), 'clicking...'); + } + + link + .bind('ajax:beforeSend', function() { + checkDisabledLink(); + }) + .live('ajax:complete', function() { + checkEnabledLink(); + start(); + }) + .trigger('click'); +}); From c01215c3d48ebb9f9f1059f26efa0c0c9092da2b Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 12 Sep 2011 22:37:29 -0400 Subject: [PATCH 073/271] Added jquery 1.6.4 to test suite --- test/server.rb | 2 +- test/views/layout.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/server.rb b/test/server.rb index a0a71873..d33cbc57 100644 --- a/test/server.rb +++ b/test/server.rb @@ -1,7 +1,7 @@ require 'sinatra' require 'json' -JQUERY_VERSIONS = %w[ 1.6 1.6.1 1.6.2 1.6.3 ].freeze +JQUERY_VERSIONS = %w[ 1.6 1.6.1 1.6.2 1.6.3 1.6.4 ].freeze use Rack::Static, :urls => ["/src"], :root => File.expand_path('..', settings.root) diff --git a/test/views/layout.erb b/test/views/layout.erb index ec9a7540..28041d92 100644 --- a/test/views/layout.erb +++ b/test/views/layout.erb @@ -8,7 +8,7 @@ color: #8699A4; text-align: right; font-family: sans-serif; line-height: 1; margin-top: -1.8em; padding: 0 2em .8em 0; } - #jquery-cdn { margin-right: 300px; } + #jquery-cdn { margin-right: 375px; } #jquery-cdn a, #jquery-version a { color: white; text-decoration: underline; } From 44df81adbb31b83f77ec59b4e616a4183861688e Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Thu, 15 Sep 2011 12:06:27 -0400 Subject: [PATCH 074/271] Updated formInputClickSelector for button[type] IE7 issue. Closes #206. --- src/rails.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rails.js b/src/rails.js index 67d4f58d..40223c0b 100644 --- a/src/rails.js +++ b/src/rails.js @@ -58,7 +58,7 @@ formSubmitSelector: 'form', // Form input elements bound by jquery-ujs - formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not(button[type])', // Form input elements disabled during form submission disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', From 888be8adcb7d609f442b3883dc722c5427e49354 Mon Sep 17 00:00:00 2001 From: Steve Schwartz Date: Mon, 3 Oct 2011 00:25:55 -0400 Subject: [PATCH 075/271] Carry over target attribute for a[data-method] links to form. Closes --- src/rails.js | 3 +++ test/public/test/data-method.js | 23 +++++++++++++++++------ test/public/test/settings.js | 5 +++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/rails.js b/src/rails.js index 40223c0b..6ef7f777 100644 --- a/src/rails.js +++ b/src/rails.js @@ -159,6 +159,7 @@ handleMethod: function(link) { var href = link.attr('href'), method = link.data('method'), + target = link.attr('target'), csrf_token = $('meta[name=csrf-token]').attr('content'), csrf_param = $('meta[name=csrf-param]').attr('content'), form = $(''), @@ -168,6 +169,8 @@ metadata_input += ''; } + if (target) { form.attr('target', target); } + form.hide().append(metadata_input).appendTo('body'); form.submit(); }, diff --git a/test/public/test/data-method.js b/test/public/test/data-method.js index 34c282e0..ef50500f 100644 --- a/test/public/test/data-method.js +++ b/test/public/test/data-method.js @@ -1,11 +1,15 @@ (function(){ -module('data-method'); +module('data-method', { + setup: function() { + $('#qunit-fixture').append($('
', { + href: '/echo', 'data-method': 'delete', text: 'destroy!' + })); + } +}); -function submit(fn) { - $('#qunit-fixture'). - append($('', { href: '/echo', 'data-method': 'delete', text: 'destroy!' })) - .find('a') +function submit(fn, options) { + $('#qunit-fixture').find('a') .bind('iframe:loaded', function(e, data) { fn(data); start(); @@ -31,4 +35,11 @@ asyncTest('link with "data-method" and CSRF', 1, function() { }); }); -})(); \ No newline at end of file +asyncTest('link "target" should be carried over to generated form', 1, function() { + $('a[data-method]').attr('target', 'super-special-frame'); + submit(function(data) { + equal(data.params._target, 'super-special-frame'); + }); +}); + +})(); diff --git a/test/public/test/settings.js b/test/public/test/settings.js index c58734d7..e23bf7ed 100644 --- a/test/public/test/settings.js +++ b/test/public/test/settings.js @@ -26,10 +26,11 @@ $(document).bind('submit', function(e) { if (!e.isDefaultPrevented()) { var form = $(e.target), action = form.attr('action'), name = 'form-frame' + jQuery.guid++, - iframe = $('