Browse Source

Finishes recode of mock and complete callbacks from API. Aims to improve comprensiveness of debug statements. Adds ability for mocked responses to use all the settings available to regular API requests like loadingDuration

pull/2492/head
jlukic 10 years ago
parent
commit
66f809067d
1 changed files with 185 additions and 136 deletions
  1. 321
      src/definitions/behaviors/api.js

321
src/definitions/behaviors/api.js

@ -103,7 +103,7 @@ $.api = $.fn.api = function(parameters) {
triggerEvent = module.get.event()
;
if( triggerEvent ) {
module.debug('Attaching API events to element', triggerEvent);
module.verbose('Attaching API events to element', triggerEvent);
$module
.on(triggerEvent + eventNamespace, module.event.trigger)
;
@ -180,13 +180,8 @@ $.api = $.fn.api = function(parameters) {
}
// Add form content
if(settings.serializeForm !== false || $context.is('form')) {
if(settings.serializeForm == 'json') {
$.extend(true, settings.data, module.get.formData());
}
else {
settings.data = module.get.formData();
}
if(settings.serializeForm) {
settings.data = module.add.formData(settings.data);
}
// call beforesend and get any settings changes
@ -204,6 +199,7 @@ $.api = $.fn.api = function(parameters) {
// get url
url = module.get.templatedURL();
if(!url && !module.is.mocked()) {
module.error(error.missingURL);
return;
@ -240,12 +236,12 @@ $.api = $.fn.api = function(parameters) {
}
if( !settings.throttle ) {
module.debug('Sending data', data, ajaxSettings.method);
module.debug('Sending request', data, ajaxSettings.method);
module.send.request();
}
else {
if(!settings.throttleFirstRequest && !module.timer) {
module.debug('Sending data', data, ajaxSettings.method);
module.debug('Sending request', data, ajaxSettings.method);
module.send.request();
module.timer = setTimeout(function(){}, settings.throttle);
}
@ -266,7 +262,7 @@ $.api = $.fn.api = function(parameters) {
is: {
disabled: function() {
return ($module.filter(settings.filter).length > 0);
return ($module.filter(selector.disabled).length > 0);
},
form: function() {
return $module.is('form');
@ -279,6 +275,31 @@ $.api = $.fn.api = function(parameters) {
},
loading: function() {
return (module.request && module.request.state() == 'pending');
},
abortedRequest: function(xhr) {
if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) {
module.verbose('XHR request determined to be aborted');
return true;
}
else {
module.verbose('XHR request was not aborted');
return false;
}
},
validResponse: function(response) {
if( settings.dataType !== 'json' || !$.isFunction(settings.successTest) ) {
module.verbose('Response is not JSON, skipping validation', settings.successTest, response);
return true;
}
module.debug('Checking JSON returned success', settings.successTest, response);
if( settings.successTest(response) ) {
module.debug('Response passed success test', response);
return true;
}
else {
module.debug('Response failed success test', response);
return false;
}
}
},
@ -370,6 +391,34 @@ $.api = $.fn.api = function(parameters) {
}
}
return url;
},
formData: function(data) {
var
canSerialize = ($.fn.serializeObject !== undefined),
formData = (canSerialize)
? $form.serializeObject()
: $form.serialize(),
hasOtherData
;
data = data || settings.data;
hasOtherData = $.isPlainObject(data);
if(hasOtherData) {
if(canSerialize) {
module.debug('Extending existing data with form data', data, formData);
data = $.extend(true, {}, data, formData);
}
else {
module.error(error.missingSerialize);
module.debug('Cant extend data. Replacing data with form data', data, formData);
data = formData;
}
}
else {
module.debug('Adding form data', formData);
data = formData;
}
return data;
}
},
@ -377,12 +426,16 @@ $.api = $.fn.api = function(parameters) {
request: function() {
module.set.loading();
module.request = module.create.request();
module.xhr = module.create.xhr();
if( module.is.mocked() ) {
module.mockedXHR = module.create.mockedXHR();
}
else {
module.xhr = module.create.xhr();
}
settings.onRequest.call(context, module.request, module.xhr);
}
},
event: {
trigger: function(event) {
module.query();
@ -394,18 +447,33 @@ $.api = $.fn.api = function(parameters) {
always: function() {
// calculate if loading time was below minimum threshold
},
done: function(response) {
done: function(response, textStatus, xhr) {
var
context = this,
elapsedTime = (new Date().getTime() - requestStartTime),
timeLeft = (settings.loadingDuration - elapsedTime)
timeLeft = (settings.loadingDuration - elapsedTime),
translatedResponse = ( $.isFunction(settings.onResponse) )
? settings.onResponse.call(context, $.extend(true, {}, response))
: false
;
timeLeft = (timeLeft > 0)
? timeLeft
: 0
;
if(translatedResponse) {
module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response);
response = translatedResponse;
}
if(timeLeft > 0) {
module.debug('Response completed early delaying state change by', timeLeft);
}
setTimeout(function() {
module.request.resolveWith(context, [response]);
if( module.is.validResponse(response) ) {
module.request.resolveWith(context, [response]);
}
else {
module.request.rejectWith(context, [xhr, 'invalid']);
}
}, timeLeft);
},
fail: function(xhr, status, httpMessage) {
@ -418,13 +486,15 @@ $.api = $.fn.api = function(parameters) {
? timeLeft
: 0
;
// page triggers abort on navigation, dont show error
if(timeLeft > 0) {
module.debug('Response completed early delaying state change by', timeLeft);
}
setTimeout(function() {
if(xhr.readyState !== undefined && xhr.readyState === 0) {
if( module.is.abortedRequest(xhr) ) {
module.request.rejectWith(context, [xhr, 'aborted', httpMessage]);
}
else {
module.request.rejectWith(context, [xhr, status, httpMessage]);
module.request.rejectWith(context, [xhr, 'error', status, httpMessage]);
}
}, timeLeft);
}
@ -435,86 +505,48 @@ $.api = $.fn.api = function(parameters) {
settings.onComplete.call(context, response, $module);
},
done: function(response) {
var
translatedResponse = ( $.isFunction(settings.onResponse) )
? settings.onResponse.call(context, $.extend(true, {}, response))
: false
;
module.debug('API Response Received', response);
module.debug('Successful API Response', response);
if(settings.cache === 'local' && url) {
module.write.cachedResponse(url, response);
module.debug('Adding url to local cache', module.cache);
}
if(translatedResponse) {
module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response);
response = translatedResponse;
}
if(settings.dataType == 'json') {
if( $.isFunction(settings.successTest) ) {
module.debug('Checking JSON returned success', settings.successTest, response);
if( settings.successTest(response) ) {
settings.onSuccess.call(context, response, $module);
}
else {
module.debug('JSON test specified by user and response failed', response);
settings.onFailure.call(context, response, $module);
}
}
else {
settings.onSuccess.call(context, response, $module);
}
}
else {
settings.onSuccess.call(context, response, $module);
module.debug('Saving server response locally', module.cache);
}
settings.onSuccess.call(context, response, $module);
},
fail: function(xhr, status, httpMessage) {
var
errorMessage = (settings.error[status] !== undefined)
? settings.error[status]
: httpMessage,
abortedRequest = false,
response
response = $.isPlainObject(xhr)
? (xhr.responseText)
: false,
errorMessage = ($.isPlainObject(response) && response.error !== undefined)
? response.error // use json error message
: (settings.error[status] !== undefined) // use server error message
? settings.error[status]
: httpMessage
;
// request aborted, don't show error state
if(status == 'aborted') {
module.debug('Request Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage);
module.reset();
module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage);
settings.onAbort.call(context, status, $module);
abortedRequest = true;
return;
}
else if(status == 'invalid') {
module.debug('JSON did not pass success test. A server-side error has most likely occurred', response);
}
else if(status == 'error') {
if(xhr !== undefined && !abortedRequest) {
// if http status code returned and json returned error, look for it
if( xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') {
module.error(error.statusMessage + httpMessage, ajaxSettings.url);
}
else {
if(status == 'error' && settings.dataType == 'json') {
try {
response = $.parseJSON(xhr.responseText);
if(response && response.error !== undefined) {
errorMessage = response.error;
}
}
catch(e) {
module.error(error.JSONParse);
}
if(xhr !== undefined) {
module.debug('XHR produced a server error', status, httpMessage);
// make sure we have an error to display to console
if( xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') {
module.error(error.statusMessage + httpMessage, ajaxSettings.url);
}
settings.onError.call(context, errorMessage, $module);
}
module.remove.loading();
// show error state if specified with length
if(settings.errorDuration !== false) {
module.set.error();
setTimeout(module.remove.error, settings.errorDuration);
}
module.debug('API Request errored', errorMessage);
settings.onError.call(context, errorMessage, $module);
}
if(settings.errorDuration) {
module.set.error();
setTimeout(module.remove.error, settings.errorDuration);
}
module.debug('API Request failed', errorMessage, xhr);
settings.onFailure.call(context, response, $module);
}
}
@ -522,52 +554,73 @@ $.api = $.fn.api = function(parameters) {
create: {
// api promise
request: function() {
// api request promise
return $.Deferred()
.always(module.event.request.complete)
.done(module.event.request.done)
.fail(module.event.request.fail)
;
},
// xhr promise
xhr: function() {
mockedXHR: function () {
var
callback
// xhr does not simulate these properties of xhr but must return them
textStatus = false,
status = false,
httpMessage = false,
asyncCallback,
response,
mockedXHR
;
if( module.is.mocked() ) {
if(settings.mockResponse) {
if($.isFunction(settings.mockResponse)) {
module.debug('Using sync mocked response callback', settings.mockResponse);
module.request.resolveWith(context, [ settings.mockResponse.call(context, settings) ]);
}
else {
module.debug('Using mocked response', settings.mockResponse);
module.request.resolveWith(context, [ settings.mockResponse ]);
}
mockedXHR = $.Deferred()
.always(module.event.xhr.complete)
.done(module.event.xhr.done)
.fail(module.event.xhr.fail)
;
if(settings.mockResponse) {
if( $.isFunction(settings.mockResponse) ) {
module.debug('Using mocked callback returning response', settings.mockResponse);
response = settings.mockResponse.call(context, settings);
}
else if( $.isFunction(settings.mockResponseAsync) ) {
callback = function(response) {
module.verbose('Async callback returned response', response);
if(response) {
module.request.resolveWith(context, [response]);
}
else {
module.request.rejectWith(context, [true]);
}
};
module.debug('Using async mocked response', settings.mockResponseAsync);
settings.mockResponseAsync.call(context, settings, callback);
else {
module.debug('Using specified response', settings.mockResponse);
response = settings.mockResponse;
}
// simulating response
mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
}
else {
return $.ajax(ajaxSettings)
.always(module.event.xhr.always)
.done(module.event.xhr.done)
.fail(module.event.xhr.fail)
;
else if( $.isFunction(settings.mockResponseAsync) ) {
asyncCallback = function(response) {
module.debug('Async callback returned response', response);
if(response) {
mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
}
else {
mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]);
}
};
module.debug('Using async mocked response', settings.mockResponseAsync);
settings.mockResponseAsync.call(context, settings, asyncCallback);
}
return mockedXHR;
},
xhr: function() {
var
xhr
;
// ajax request promise
xhr = $.ajax(ajaxSettings)
.always(module.event.xhr.always)
.done(module.event.xhr.done)
.fail(module.event.xhr.fail)
;
module.verbose('Created server request', xhr);
return xhr;
}
},
@ -673,20 +726,6 @@ $.api = $.fn.api = function(parameters) {
return settings.on;
}
},
formData: function() {
var
formData
;
if($module.serializeObject !== undefined) {
formData = $form.serializeObject();
}
else {
module.error(error.missingSerialize);
formData = $form.serialize();
}
module.debug('Retrieved form data', formData);
return formData;
},
templatedURL: function(action) {
action = action || $module.data(metadata.action) || settings.action || false;
url = $module.data(metadata.url) || settings.url || false;
@ -921,7 +960,6 @@ $.api.settings = {
// event binding
on : 'auto',
filter : '.disabled',
stateContext : false,
// state
@ -958,10 +996,20 @@ $.api.settings = {
// after request
onResponse : false, // function(response) { },
// response was successful, if JSON passed validation
onSuccess : function(response, $module) {},
// request finished without aborting
onComplete : function(response, $module) {},
onFailure : function(errorMessage, $module) {},
// failed JSON success test
onFailure : function(response, $module) {},
// server error
onError : function(errorMessage, $module) {},
// request aborted
onAbort : function(errorMessage, $module) {},
successTest : false,
@ -975,7 +1023,7 @@ $.api.settings = {
legacyParameters : 'You are using legacy API success callback names',
method : 'The method you called is not defined',
missingAction : 'API action used but no url was defined',
missingSerialize : 'Required dependency jquery-serialize-object missing, using basic serialize',
missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object',
missingURL : 'No URL specified for api event',
noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.',
noStorage : 'Caching respopnses locally requires session storage',
@ -996,7 +1044,8 @@ $.api.settings = {
},
selector: {
form: 'form'
disabled : '.disabled',
form : 'form'
},
metadata: {

Loading…
Cancel
Save