Skip to content

Commit

Permalink
Merge pull request #393 from hmrc/AOSS-1135-Add-callbacks-for-AjaxFor…
Browse files Browse the repository at this point in the history
…mSubmit

[AOSS-1135] Ajax submit and callback updates
  • Loading branch information
tmikus committed Feb 9, 2016
2 parents f389b30 + 94eef0d commit 59ff9c1
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 97 deletions.
183 changes: 109 additions & 74 deletions assets/javascripts/modules/ajaxCallbacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,96 @@
require('jquery');

var ajaxCallbacks = {
clientAccessResponse: {
emailFormResponse: {
callbacks: {
beforeSend: function($element) {
$element.prop('disabled', true);
success: function(response, $element, data, helpers) {
$('input[type="text"][type!="email"][name!="email"]').val('');
helpers.base.success.apply(null, arguments);
}
}
},
dataTableSubmitResponse: {
callbacks: {
beforeSend: function($element, data, helpers) {
$('.js-datatable-submit').prop('disabled', true); // disable all data-table submit elements

helpers.base.beforeSend.apply(null, arguments);
},

success: function(response, $element, data, helpers, container, type) {
$('input[name="payeref"]').val('');
helpers.insertResponseHtml(helpers, type, data, $(container + ' .form-field:has(>input[name][type="text"])').first(), response);
success: function(response, $element, data, helpers, targets, container, type) {
var $container = $(container);

$container
.empty()
.closest('tbody').find('tr.status--confirm-success').each(function (index, element) {
$(element).removeClass('status--confirm-success');
});

$container.closest('tr').removeClass('status--unconfirmed').addClass('status--confirm-success confirmed');

helpers.base.success.apply(null, arguments);
},

error: function(response, $element, data, helpers, container) {
error: function(response, $element, data, helpers, targets, container, type) {
$('button.js-datatable-submit').prop('disabled', false);

// if session has timed out
if (response.status === 401 &&
response.responseJSON &&
response.responseJSON.redirectUri) {
if (response.status === 500) { // a service-error, disable all submit buttons
$element.find('button.js-datatable-submit').each(function (index, element) {
$(element).prop('disabled', true);
});
}

// go to login page with specified redirect URI
document.location.href = response.responseJSON.redirectUri;
$(container).closest('tr').addClass('form--field--error');

helpers.base.error.apply(null, arguments);
},

always: function(response, $element, data, helpers) {
if (response.status !== 500) { // not a service-error
$('.js-datatable-submit').prop('disabled', false); // re-enable all data-table submit elements
}

helpers.base.always.apply(null, arguments);
}
}
},
helpers: {
base: {
beforeSend: function($element, data, helpers, targets, container, type, actions) {
helpers.utilities.setFormState($element, true);
},

success: function(response, $element, data, helpers, targets, container, type, actions) {
helpers.insertResponseHtml(helpers, type, data, $(container + targets.success), response);
},

error: function(response, $element, data, helpers, targets, container, type, actions) {
if (helpers.utilities.hasSessionLapsed(response)) {
helpers.utilities.redirect(response);
}
else {
helpers.insertResponseHtml(helpers, 'before', data, $(container + ' .form-field:has(>input[name][type="text"])'), response);
helpers.insertResponseHtml(helpers, type, data, $(container + targets.error), response);
}

},

always: function(response, $element, data, helpers, container, type) {
always: function(response, $element, data, helpers, targets, container, type, actions) {
if (helpers.hasErrorType !== 'service') {
helpers.resetForms(helpers, type, data, container);
}

helpers.hasErrorType = undefined;
}
}
},
helpers: {
},

hasErrorType: undefined,

setDomHtml: function(type, $target, $node) {
if (!type.indexOf('prependTo') || !type.indexOf('appendTo') || !type.indexOf('insert')) {
var $swapNode = $target;
$target = $node;
$node = $swapNode;
}

if ($.isFunction($.fn[type]) && !!$target && !!$node) {
$.fn[type].apply($target, [$node]);
}
Expand All @@ -54,47 +106,34 @@ var ajaxCallbacks = {
nodeCount = htmlNodes.length || 0,
i = 0,
$node, $errorTarget,
isMissingClient = data.indexOf('missingclient=true') > -1,
isError = !!response.status || response.status === 500;

if (!$.isFunction($.fn[type])) {
$target.empty();
type = 'append';
}

helpers.resetErrorMessages($target.parent(), $target);

if (helpers.utilities.isFullPageError(helpers, htmlText)) {
helpers.insertFullPageErrorHtml($target, helpers, htmlText);
}
else if (helpers.utilities.isServiceError(helpers, htmlNodes)) {
helpers.insertServiceErrorHtml(helpers, htmlText);
helpers.insertServiceErrorHtml($target.closest('form'), helpers, htmlText);
}
else { // handle 'validation error' or 'success' message & state

if (!isError) {
$target.addClass('js-hidden');
}

for (; i < nodeCount; i++) {
$node = $(htmlNodes[i]);

if (isError) {
//helpers.hasErrorType = 'validation';
$errorTarget = $target.find('>input[name="' + $node.attr('data-input-for') + '"]');
$errorTarget.addClass("error-field");
$errorTarget = $target.find('>input[name="' + $node.data('input-for') + '"]');
$errorTarget.addClass('error-field');
$errorTarget.closest('.form-field').addClass('form-field--error');
$errorTarget.blur();
}

helpers.setDomHtml(type, $errorTarget || $target, $node);
}

if (isMissingClient && !isError) {
helpers.bindEvents($target, data);
}
else {
$target.removeClass('js-hidden');
helpers.setDomHtml(type, !!$errorTarget && $errorTarget.length ? $errorTarget : $target, $node);
}
}
},
Expand All @@ -105,34 +144,22 @@ var ajaxCallbacks = {

helpers.resetErrorMessages($target.parent(), $target);

$button.parent('.form-field').addClass('error');
$button.parent('.form-field').addClass('form-field--error');

helpers.setDomHtml('insertBefore',
$('<div class="alert alert--failure" data-input-for="email" id="service--error">' +
'<span class="error-message">' + $heading.text() + '</span>' +
'</div>'),
$button);
helpers.setDomHtml('insertBefore', $button,
$('<div class="alert alert--borderless alert--failure soft--ends soft--sides" data-input-for="email" id="service--error">' +
'<span class="alert__message"><strong>' + $heading.text() + '</strong></span>' +
'</div>'));
},

insertServiceErrorHtml: function(helpers, htmlText) {
var $missingClientForm = $('#missing-client-form'),
$clientAccessForms = $('.client-access-details');

// replace missing client form
$missingClientForm.empty();
helpers.setDomHtml('prepend', $missingClientForm, htmlText);

// insert into client access request forms & disable elements
$clientAccessForms.each(function(i, e) {
var $form = $(e);
$form.find('input, button[type=submit]').prop('disabled', true);
helpers.resetErrorMessages($form, $form.find('.form-field.error'));
helpers.setDomHtml('before', $form.find('.form-field:first'), htmlText);
});
insertServiceErrorHtml: function($form, helpers, htmlText) {
// replace form with Service Error message
$form.empty();
helpers.setDomHtml('prepend', $form, htmlText);
},

resetErrorMessages: function($target, $error) {
$target.find('.error').andSelf().removeClass('error');
$target.find('.form-field--error').andSelf().removeClass('form-field--error');
$target.find('.form-field:has(*[data-id="service--error"]), .alert--success, .alert--failure').remove();
},

Expand All @@ -159,23 +186,31 @@ var ajaxCallbacks = {
return emailValue || decodeURIComponent(emailMatch).replace(reEmail, '$1') || '';
},

bindEvents: function($container) {
$('#another-missing-client').one('click keydown', function(event) {
event.preventDefault();

var $this = $(event.target);

$this.parent().find('.error').removeClass('error');
$this.parent().find('.alert--success, .alert--failure').remove();
$this.parent().find('input[name="payeref"]').prop('value', null);
utilities: {
hasSessionLapsed: function(response) {
// if session has timed out
return response.status && response.status === 401 && response.responseJSON && response.responseJSON.redirectUri;
},

$this.remove();
redirect: function(response) {
// go to login page with specified redirect URI
if (response && response.responseJSON && response.responseJSON.redirectUri) {
document.location.href = response.responseJSON.redirectUri;
}
},

$container.removeClass('js-hidden');
});
},
setFormState: function($element, isDisabled) {
if ($element.prop('tagName') === 'FORM') {
$element.find('input, button, textarea').each(function(index, formElement) {
$(formElement).prop('disabled', isDisabled);
$(formElement).attr('aria-disabled', isDisabled);
});
} else {
$element.prop('disabled', isDisabled);
$element.attr('aria-disabled', isDisabled);
}
},

utilities: {
getElementInnerHtml: function(html, nodeName) {
var re = new RegExp('^(.*)<' + nodeName + '[^>]*>(.+?)<\/' + nodeName + '>(.*)$', 'gi');
return re.test(html) ? $($.parseHTML(html.replace(re, '$2'))) : '';
Expand Down
49 changes: 30 additions & 19 deletions assets/javascripts/modules/ajaxFormSubmit.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* - a 'data-callback-args' attribute containing comma separated list of argument parameters to pass to callback:
* + 1. the selector where the partial view content will be added
* + 2. the method to use when adding new content to the container - options are 'insert' or 'replace' (insert is the default)
*
*
* - a data-targets-success attribute with a selector for element(s) found within the data-container to be used by the success callback
* - a data-targets-error attribute with a selector for element(s) found within the data-container to be used by the success callback
*
* <form action="#"
* data-ajax-submit="true"
Expand All @@ -40,14 +40,15 @@ var ajaxFormSubmit = {
var _this = this,
$ajaxForm = $('form[data-ajax-submit], form:has([data-ajax-submit])'),
ajaxFormCount = $ajaxForm.length,
a = 0;
a = 0, eventData, $form, selector;

for (; a < ajaxFormCount; a++) {
var eventData = {context: _this, config: config},
$form = $($ajaxForm[a]),
eventData = {context: _this, config: config};
$form = $($ajaxForm[a]);
selector = 'input[type="submit"], button[type="submit"]';

$form.on('submit click', $('form:has[data-ajax-submit]').length ? selector.replace('submit', 'data-ajax-submit') : selector, eventData, _this.submitHandler);

$form.on('submit click', $('form:has[data-ajax-submit]').length ?
selector.replace('submit', 'data-ajax-submit') : selector, eventData, _this.submitHandler);

if ($form.find('[data-ajax-submit]').addBack().length > 1) {
// more than one button/input submit in the forms context - capture + handle enter key submit on text fields
Expand All @@ -59,32 +60,36 @@ var ajaxFormSubmit = {
keypressHandler: function(event) {
if (event.which === 13) {
event.preventDefault();
var thisContext = $(this).closest('*:has([data-ajax-submit="true"])').find('[data-ajax-submit="true"]');
event.data.context.submitHandler.apply(thisContext, [{ type: 'submit', preventDefault: function(){}, data: event.data }]);
var thisContext = $(this).closest('*:has([data-ajax-submit="true"])').find('[data-ajax-submit="true"]');
event.data.context.submitHandler.apply(thisContext, [{ type: 'submit', preventDefault: function() {}, data: event.data }]);
}
},

submitHandler: function(event) {
event.preventDefault();

var $this = $(this),
_config = event.data.config,
_this = event.data.context,
$form = $this.attr('data-ajax-submit') ? $this : $this.closest('[data-ajax-submit]'),
path = $form.attr('data-formaction') || $form.attr('formaction') || $form.attr('action'),
$scope = $form.attr('data-container') || $this,
$form = $this.data('ajax-submit') ? $this : $this.closest('[data-ajax-submit]'),
path = $form.data('formaction') || $form.attr('formaction') || $form.attr('action'),
$scope = $form.data('container') || $this,
serializedData = _this.serializeForAjax($scope),
handlers = {
config: {
name: $form.attr('data-callback-name'),
args: $form.attr('data-callback-args'),
name: $form.data('callback-name'),
args: $form.data('callback-args'),
$element: $form,
targets: {
success: $form.data('target-success') || '',
error: $form.data('target-error') || ''
},
callbacks: _config,
helpers: _config.helpers || {}
},
fn: null
};

handlers.fn = _this.getCallback(handlers.config, serializedData);

if (!!handlers) {
Expand Down Expand Up @@ -133,27 +138,33 @@ var ajaxFormSubmit = {
var parts = config.name.split('.'),
method = config.callbacks,
helpers = config.helpers,
$element = config.$element;
$element = config.$element,
targets = config.targets;

if (!!config.name) {
if (!!config.args) {
config.parameters = [].concat(config.args.split(','));
}

config.parameters.unshift(targets);
config.parameters.unshift(helpers);
config.parameters.unshift(!!data ? data : null);
config.parameters.unshift($element);

jQuery.each(parts, function(index, value) {
method = method[value];
});

return function(type, response) {
var fn = method[type] || config.helpers.base[type];

if (!!response) {
config.parameters.unshift(response);
}

method[type].apply(null, config.parameters);
if (!!fn) {
fn.apply(null, config.parameters);
}
};
}
else {
Expand Down
Loading

0 comments on commit 59ff9c1

Please sign in to comment.