@ -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 : {