From fba4ee35041c1db92672dbadb2502ba96d51ef00 Mon Sep 17 00:00:00 2001
From: Akzhan Abdulin
Date: Sun, 3 Jul 2011 14:03:37 +0200
Subject: [PATCH 1/7] Use Deferred object as result of $.rails.confirm. It
allows to override confirm in an asynchronic fashion.
---
src/rails.js | 162 +++++++++++++++++++++++++++++++++++----------------
1 file changed, 113 insertions(+), 49 deletions(-)
diff --git a/src/rails.js b/src/rails.js
index d6e585fa..9eee8f1b 100644
--- a/src/rails.js
+++ b/src/rails.js
@@ -51,8 +51,8 @@
// 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]',
+ // Select elements bound by jquery-ujs
+ selectChangeSelector: 'select[data-remote]',
// Form elements bound by jquery-ujs
formSubmitSelector: 'form',
@@ -85,9 +85,21 @@
return event.result !== false;
},
+ resolveOrReject: function(deferred, resolved) {
+ if (resolved) {
+ deferred.resolve();
+ } else {
+ deferred.reject();
+ }
+ return deferred;
+ },
+
// Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
confirm: function(message) {
- return confirm(message);
+ var res = confirm(message),
+ answer = $.Deferred();
+ rails.resolveOrReject(answer, res);
+ return answer.promise();
},
// Default ajax function, may be overridden with custom function in $.rails.ajax
@@ -207,14 +219,25 @@
*/
allowAction: function(element) {
var message = element.data('confirm'),
- answer = false, callback;
- if (!message) { return true; }
+ confirmAnswer, answer = $.Deferred();
+ if (!message) { return $.when(true); }
if (rails.fire(element, 'confirm')) {
- answer = rails.confirm(message);
- callback = rails.fire(element, 'confirm:complete', [answer]);
+ confirmAnswer = rails.confirm(message);
+ confirmAnswer.then(
+ function() {
+ var callbackOk = rails.fire(element, 'confirm:complete', [ true ]);
+ rails.resolveOrReject(answer, callbackOk);
+ },
+ function() {
+ rails.fire(element, 'confirm:complete', [ false ]);
+ answer.reject();
+ }
+ );
+ return answer.promise();
}
- return answer && callback;
+ answer.reject();
+ return answer.promise();
},
// Helper function which checks for blank inputs in a form that match the specified CSS selector
@@ -260,65 +283,105 @@
$(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;
+ if (link.data('rails:click:inner')) {
+ return;
}
+ e.preventDefault();
+
+ rails.allowAction(link).then(
+ function() {
+ if (link.data('remote') !== undefined) {
+ rails.handleRemote(link);
+ } else if (link.data('method')) {
+ rails.handleMethod(link);
+ } else {
+ link.data('rails:click:inner', true);
+ link.click();
+ link.data('rails:click:inner', false);
+ }
+ },
+ function() {
+ rails.stopEverything(e);
+ });
});
- $(rails.selectChangeSelector).live('change.rails', function(e) {
+ $(rails.selectChangeSelector).live('change.rails', function(e) {
var link = $(this);
- if (!rails.allowAction(link)) return rails.stopEverything(e);
-
- rails.handleRemote(link);
- return false;
- });
+ e.preventDefault();
+ rails.allowAction(link).then(
+ function() {
+ rails.handleRemote(link);
+ },
+ function() {
+ rails.stopEverything(e);
+ }
+ );
+ });
$(rails.formSubmitSelector).live('submit.rails', function(e) {
var form = $(this),
- remote = form.data('remote') !== undefined,
+ 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 && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
- return rails.stopEverything(e);
+ if (form.data('rails:form:submit:inner')) {
+ return;
}
- if (remote) {
- if (nonBlankFileInputs) {
- return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
- }
+ e.preventDefault();
- // 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.allowAction(form).then(
+ function() {
+ // skip other logic when required values are missing or file upload is present
+ if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
+ return rails.stopEverything(e);
+ }
- $(rails.formInputClickSelector).live('click.rails', function(event) {
- var button = $(this);
+ if (remote) {
+ if (nonBlankFileInputs) {
+ return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);
+ }
- if (!rails.allowAction(button)) return rails.stopEverything(event);
+ // 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);
- // register the pressed submit button
- var name = button.attr('name'),
- data = name ? {name:name, value:button.val()} : null;
+ rails.handleRemote(form);
+ } else {
+ // slight timeout so that the submit button gets properly serialized
+ setTimeout(function() {
+ rails.disableFormElements(form);
+ form.data('rails:form:submit:inner', true);
+ form.submit();
+ form.data('rails:form:submit:inner', false);
+ }, 13);
+ }
+ },
+ function() {
+ rails.stopEverything(e);
+ }
+ );
+ });
- button.closest('form').data('ujs:submit-button', data);
+ $(rails.formInputClickSelector).live('click.rails', function(event) {
+ var button = $(this);
+ e.preventDefault();
+
+ rails.allowAction(button).then(
+ function() {
+ // register the pressed submit button
+ var name = button.attr('name'), form,
+ data = name ? {name:name, value:button.val()} : null;
+
+ form = button.closest('form');
+ form.data('ujs:submit-button', data);
+ form.submit();
+ },
+ function() {
+ rails.stopEverything(event);
+ }
+ );
});
$(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
@@ -330,3 +393,4 @@
});
})( jQuery );
+
From e090f2beb5cc377a8ba74c00f66d656499240665 Mon Sep 17 00:00:00 2001
From: Steve Schwartz
Date: Sun, 4 Sep 2011 16:29:44 -0400
Subject: [PATCH 2/7] Reformatted some stuff and moved preventDefault events to
be less obtrusive
---
src/rails.js | 76 +++++++++++++++++++++++++++++++---------------------
1 file changed, 46 insertions(+), 30 deletions(-)
diff --git a/src/rails.js b/src/rails.js
index 9eee8f1b..a2cee9f0 100644
--- a/src/rails.js
+++ b/src/rails.js
@@ -97,7 +97,8 @@
// Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
confirm: function(message) {
var res = confirm(message),
- answer = $.Deferred();
+ answer = $.Deferred();
+
rails.resolveOrReject(answer, res);
return answer.promise();
},
@@ -109,10 +110,10 @@
// Submits "remote" forms and links with ajax
handleRemote: function(element) {
- var method, url, data,
- crossDomain = element.data('cross-domain') || null,
- dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
- options;
+ var method, url, data, button,
+ crossDomain = element.data('cross-domain') || null,
+ dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType),
+ options;
if (rails.fire(element, 'ajax:before')) {
@@ -121,7 +122,7 @@
url = element.attr('action');
data = element.serializeArray();
// memoized value from clicked submit button
- var button = element.data('ujs:submit-button');
+ button = element.data('ujs:submit-button');
if (button) {
data.push(button);
element.data('ujs:submit-button', null);
@@ -132,9 +133,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 = {
@@ -167,11 +168,11 @@
// 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 = '';
+ 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 += '';
@@ -188,7 +189,9 @@
*/
disableFormElements: function(form) {
form.find(rails.disableSelector).each(function() {
- var element = $(this), method = element.is('button') ? 'html' : 'val';
+ 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');
@@ -201,7 +204,9 @@
*/
enableFormElements: function(form) {
form.find(rails.enableSelector).each(function() {
- var element = $(this), method = element.is('button') ? 'html' : 'val';
+ 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');
});
@@ -219,7 +224,9 @@
*/
allowAction: function(element) {
var message = element.data('confirm'),
- confirmAnswer, answer = $.Deferred();
+ confirmAnswer,
+ answer = $.Deferred();
+
if (!message) { return $.when(true); }
if (rails.fire(element, 'confirm')) {
@@ -235,15 +242,18 @@
}
);
return answer.promise();
+ // If `confirm` event handler returned false...
+ } else {
+ answer.reject();
+ return answer.promise();
}
- answer.reject();
- return answer.promise();
},
// 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';
+ selector = specifiedSelector || 'input,textarea';
+
form.find(selector).each(function() {
input = $(this);
// Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs
@@ -270,6 +280,7 @@
// 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);
@@ -283,10 +294,10 @@
$(rails.linkClickSelector).live('click.rails', function(e) {
var link = $(this);
+
if (link.data('rails:click:inner')) {
return;
}
- e.preventDefault();
rails.allowAction(link).then(
function() {
@@ -302,12 +313,15 @@
},
function() {
rails.stopEverything(e);
- });
+ }
+ );
+
+ e.preventDefault();
});
$(rails.selectChangeSelector).live('change.rails', function(e) {
var link = $(this);
- e.preventDefault();
+
rails.allowAction(link).then(
function() {
rails.handleRemote(link);
@@ -316,21 +330,20 @@
rails.stopEverything(e);
}
);
+
+ e.preventDefault();
});
$(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);
+ remote = (form.data('remote') !== undefined),
+ blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
+ nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
if (form.data('rails:form:submit:inner')) {
return;
}
- e.preventDefault();
-
-
rails.allowAction(form).then(
function() {
// skip other logic when required values are missing or file upload is present
@@ -362,11 +375,12 @@
rails.stopEverything(e);
}
);
+
+ e.preventDefault();
});
$(rails.formInputClickSelector).live('click.rails', function(event) {
var button = $(this);
- e.preventDefault();
rails.allowAction(button).then(
function() {
@@ -382,6 +396,8 @@
rails.stopEverything(event);
}
);
+
+ e.preventDefault();
});
$(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) {
From bffb813c1ec115f866025381c8da275a8c6a7826 Mon Sep 17 00:00:00 2001
From: Steve Schwartz
Date: Mon, 3 Oct 2011 19:18:13 -0400
Subject: [PATCH 3/7] Rewrote portion of rails.confirm deferred calls to
eliminate double-triggering of directly bound handlers outside of jquery-ujs.
See #196.
---
src/rails.js | 36 +++++++++++--------------
test/public/test/settings.js | 51 +++++++++++++++++++++++++++---------
2 files changed, 54 insertions(+), 33 deletions(-)
diff --git a/src/rails.js b/src/rails.js
index a2cee9f0..01829a6f 100644
--- a/src/rails.js
+++ b/src/rails.js
@@ -168,14 +168,19 @@
// Delete
handleMethod: function(link) {
var href = link.attr('href'),
- method = link.data('method'),
+ method = link.data('method') || 'GET',
csrf_token = $('meta[name=csrf-token]').attr('content'),
csrf_param = $('meta[name=csrf-param]').attr('content'),
- form = $(''),
- metadata_input = '';
+ form = $('', { action: href }),
+ metadata_input = '';
- if (csrf_param !== undefined && csrf_token !== undefined) {
- metadata_input += '';
+ if (method !== 'GET') {
+ form.attr('method', 'POST');
+ metadata_input += '';
+
+ if (csrf_param !== undefined && csrf_token !== undefined) {
+ metadata_input += '';
+ }
}
form.hide().append(metadata_input).appendTo('body');
@@ -295,20 +300,12 @@
$(rails.linkClickSelector).live('click.rails', function(e) {
var link = $(this);
- if (link.data('rails:click:inner')) {
- return;
- }
-
rails.allowAction(link).then(
function() {
if (link.data('remote') !== undefined) {
rails.handleRemote(link);
- } else if (link.data('method')) {
- rails.handleMethod(link);
} else {
- link.data('rails:click:inner', true);
- link.click();
- link.data('rails:click:inner', false);
+ rails.handleMethod(link);
}
},
function() {
@@ -340,10 +337,6 @@
blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector),
nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
- if (form.data('rails:form:submit:inner')) {
- return;
- }
-
rails.allowAction(form).then(
function() {
// skip other logic when required values are missing or file upload is present
@@ -365,9 +358,10 @@
// slight timeout so that the submit button gets properly serialized
setTimeout(function() {
rails.disableFormElements(form);
- form.data('rails:form:submit:inner', true);
- form.submit();
- form.data('rails:form:submit:inner', false);
+ // Submit the form from dom-level js (i.e. *not* via jquery),
+ // which will skip all submit bindings (including this live-binding),
+ // since they have already been called.
+ form.get(0).submit();
}, 13);
}
},
diff --git a/test/public/test/settings.js b/test/public/test/settings.js
index c58734d7..488cf241 100644
--- a/test/public/test/settings.js
+++ b/test/public/test/settings.js
@@ -22,15 +22,42 @@ App.assert_request_path = function(request_env, path) {
// hijacks normal form submit; lets it submit to an iframe to prevent
// navigating away from the test suite
-$(document).bind('submit', function(e) {
- if (!e.isDefaultPrevented()) {
- var form = $(e.target), action = form.attr('action'),
- name = 'form-frame' + jQuery.guid++,
- iframe = $('