@@ 19706-20862 (lines=1157) @@ | ||
19703 | * |
|
19704 | */ |
|
19705 | ||
19706 | ;(function ($, window, document, undefined) { |
|
19707 | ||
19708 | "use strict"; |
|
19709 | ||
19710 | var |
|
19711 | window = (typeof window != 'undefined' && window.Math == Math) |
|
19712 | ? window |
|
19713 | : (typeof self != 'undefined' && self.Math == Math) |
|
19714 | ? self |
|
19715 | : Function('return this')() |
|
19716 | ; |
|
19717 | ||
19718 | $.api = $.fn.api = function(parameters) { |
|
19719 | ||
19720 | var |
|
19721 | // use window context if none specified |
|
19722 | $allModules = $.isFunction(this) |
|
19723 | ? $(window) |
|
19724 | : $(this), |
|
19725 | moduleSelector = $allModules.selector || '', |
|
19726 | time = new Date().getTime(), |
|
19727 | performance = [], |
|
19728 | ||
19729 | query = arguments[0], |
|
19730 | methodInvoked = (typeof query == 'string'), |
|
19731 | queryArguments = [].slice.call(arguments, 1), |
|
19732 | ||
19733 | returnedValue |
|
19734 | ; |
|
19735 | ||
19736 | $allModules |
|
19737 | .each(function() { |
|
19738 | var |
|
19739 | settings = ( $.isPlainObject(parameters) ) |
|
19740 | ? $.extend(true, {}, $.fn.api.settings, parameters) |
|
19741 | : $.extend({}, $.fn.api.settings), |
|
19742 | ||
19743 | // internal aliases |
|
19744 | namespace = settings.namespace, |
|
19745 | metadata = settings.metadata, |
|
19746 | selector = settings.selector, |
|
19747 | error = settings.error, |
|
19748 | className = settings.className, |
|
19749 | ||
19750 | // define namespaces for modules |
|
19751 | eventNamespace = '.' + namespace, |
|
19752 | moduleNamespace = 'module-' + namespace, |
|
19753 | ||
19754 | // element that creates request |
|
19755 | $module = $(this), |
|
19756 | $form = $module.closest(selector.form), |
|
19757 | ||
19758 | // context used for state |
|
19759 | $context = (settings.stateContext) |
|
19760 | ? $(settings.stateContext) |
|
19761 | : $module, |
|
19762 | ||
19763 | // request details |
|
19764 | ajaxSettings, |
|
19765 | requestSettings, |
|
19766 | url, |
|
19767 | data, |
|
19768 | requestStartTime, |
|
19769 | ||
19770 | // standard module |
|
19771 | element = this, |
|
19772 | context = $context[0], |
|
19773 | instance = $module.data(moduleNamespace), |
|
19774 | module |
|
19775 | ; |
|
19776 | ||
19777 | module = { |
|
19778 | ||
19779 | initialize: function() { |
|
19780 | if(!methodInvoked) { |
|
19781 | module.bind.events(); |
|
19782 | } |
|
19783 | module.instantiate(); |
|
19784 | }, |
|
19785 | ||
19786 | instantiate: function() { |
|
19787 | module.verbose('Storing instance of module', module); |
|
19788 | instance = module; |
|
19789 | $module |
|
19790 | .data(moduleNamespace, instance) |
|
19791 | ; |
|
19792 | }, |
|
19793 | ||
19794 | destroy: function() { |
|
19795 | module.verbose('Destroying previous module for', element); |
|
19796 | $module |
|
19797 | .removeData(moduleNamespace) |
|
19798 | .off(eventNamespace) |
|
19799 | ; |
|
19800 | }, |
|
19801 | ||
19802 | bind: { |
|
19803 | events: function() { |
|
19804 | var |
|
19805 | triggerEvent = module.get.event() |
|
19806 | ; |
|
19807 | if( triggerEvent ) { |
|
19808 | module.verbose('Attaching API events to element', triggerEvent); |
|
19809 | $module |
|
19810 | .on(triggerEvent + eventNamespace, module.event.trigger) |
|
19811 | ; |
|
19812 | } |
|
19813 | else if(settings.on == 'now') { |
|
19814 | module.debug('Querying API endpoint immediately'); |
|
19815 | module.query(); |
|
19816 | } |
|
19817 | } |
|
19818 | }, |
|
19819 | ||
19820 | decode: { |
|
19821 | json: function(response) { |
|
19822 | if(response !== undefined && typeof response == 'string') { |
|
19823 | try { |
|
19824 | response = JSON.parse(response); |
|
19825 | } |
|
19826 | catch(e) { |
|
19827 | // isnt json string |
|
19828 | } |
|
19829 | } |
|
19830 | return response; |
|
19831 | } |
|
19832 | }, |
|
19833 | ||
19834 | read: { |
|
19835 | cachedResponse: function(url) { |
|
19836 | var |
|
19837 | response |
|
19838 | ; |
|
19839 | if(window.Storage === undefined) { |
|
19840 | module.error(error.noStorage); |
|
19841 | return; |
|
19842 | } |
|
19843 | response = sessionStorage.getItem(url); |
|
19844 | module.debug('Using cached response', url, response); |
|
19845 | response = module.decode.json(response); |
|
19846 | return response; |
|
19847 | } |
|
19848 | }, |
|
19849 | write: { |
|
19850 | cachedResponse: function(url, response) { |
|
19851 | if(response && response === '') { |
|
19852 | module.debug('Response empty, not caching', response); |
|
19853 | return; |
|
19854 | } |
|
19855 | if(window.Storage === undefined) { |
|
19856 | module.error(error.noStorage); |
|
19857 | return; |
|
19858 | } |
|
19859 | if( $.isPlainObject(response) ) { |
|
19860 | response = JSON.stringify(response); |
|
19861 | } |
|
19862 | sessionStorage.setItem(url, response); |
|
19863 | module.verbose('Storing cached response for url', url, response); |
|
19864 | } |
|
19865 | }, |
|
19866 | ||
19867 | query: function() { |
|
19868 | ||
19869 | if(module.is.disabled()) { |
|
19870 | module.debug('Element is disabled API request aborted'); |
|
19871 | return; |
|
19872 | } |
|
19873 | ||
19874 | if(module.is.loading()) { |
|
19875 | if(settings.interruptRequests) { |
|
19876 | module.debug('Interrupting previous request'); |
|
19877 | module.abort(); |
|
19878 | } |
|
19879 | else { |
|
19880 | module.debug('Cancelling request, previous request is still pending'); |
|
19881 | return; |
|
19882 | } |
|
19883 | } |
|
19884 | ||
19885 | // pass element metadata to url (value, text) |
|
19886 | if(settings.defaultData) { |
|
19887 | $.extend(true, settings.urlData, module.get.defaultData()); |
|
19888 | } |
|
19889 | ||
19890 | // Add form content |
|
19891 | if(settings.serializeForm) { |
|
19892 | settings.data = module.add.formData(settings.data); |
|
19893 | } |
|
19894 | ||
19895 | // call beforesend and get any settings changes |
|
19896 | requestSettings = module.get.settings(); |
|
19897 | ||
19898 | // check if before send cancelled request |
|
19899 | if(requestSettings === false) { |
|
19900 | module.cancelled = true; |
|
19901 | module.error(error.beforeSend); |
|
19902 | return; |
|
19903 | } |
|
19904 | else { |
|
19905 | module.cancelled = false; |
|
19906 | } |
|
19907 | ||
19908 | // get url |
|
19909 | url = module.get.templatedURL(); |
|
19910 | ||
19911 | if(!url && !module.is.mocked()) { |
|
19912 | module.error(error.missingURL); |
|
19913 | return; |
|
19914 | } |
|
19915 | ||
19916 | // replace variables |
|
19917 | url = module.add.urlData( url ); |
|
19918 | // missing url parameters |
|
19919 | if( !url && !module.is.mocked()) { |
|
19920 | return; |
|
19921 | } |
|
19922 | ||
19923 | requestSettings.url = settings.base + url; |
|
19924 | ||
19925 | // look for jQuery ajax parameters in settings |
|
19926 | ajaxSettings = $.extend(true, {}, settings, { |
|
19927 | type : settings.method || settings.type, |
|
19928 | data : data, |
|
19929 | url : settings.base + url, |
|
19930 | beforeSend : settings.beforeXHR, |
|
19931 | success : function() {}, |
|
19932 | failure : function() {}, |
|
19933 | complete : function() {} |
|
19934 | }); |
|
19935 | ||
19936 | module.debug('Querying URL', ajaxSettings.url); |
|
19937 | module.verbose('Using AJAX settings', ajaxSettings); |
|
19938 | if(settings.cache === 'local' && module.read.cachedResponse(url)) { |
|
19939 | module.debug('Response returned from local cache'); |
|
19940 | module.request = module.create.request(); |
|
19941 | module.request.resolveWith(context, [ module.read.cachedResponse(url) ]); |
|
19942 | return; |
|
19943 | } |
|
19944 | ||
19945 | if( !settings.throttle ) { |
|
19946 | module.debug('Sending request', data, ajaxSettings.method); |
|
19947 | module.send.request(); |
|
19948 | } |
|
19949 | else { |
|
19950 | if(!settings.throttleFirstRequest && !module.timer) { |
|
19951 | module.debug('Sending request', data, ajaxSettings.method); |
|
19952 | module.send.request(); |
|
19953 | module.timer = setTimeout(function(){}, settings.throttle); |
|
19954 | } |
|
19955 | else { |
|
19956 | module.debug('Throttling request', settings.throttle); |
|
19957 | clearTimeout(module.timer); |
|
19958 | module.timer = setTimeout(function() { |
|
19959 | if(module.timer) { |
|
19960 | delete module.timer; |
|
19961 | } |
|
19962 | module.debug('Sending throttled request', data, ajaxSettings.method); |
|
19963 | module.send.request(); |
|
19964 | }, settings.throttle); |
|
19965 | } |
|
19966 | } |
|
19967 | ||
19968 | }, |
|
19969 | ||
19970 | should: { |
|
19971 | removeError: function() { |
|
19972 | return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) ); |
|
19973 | } |
|
19974 | }, |
|
19975 | ||
19976 | is: { |
|
19977 | disabled: function() { |
|
19978 | return ($module.filter(selector.disabled).length > 0); |
|
19979 | }, |
|
19980 | expectingJSON: function() { |
|
19981 | return settings.dataType === 'json' || settings.dataType === 'jsonp'; |
|
19982 | }, |
|
19983 | form: function() { |
|
19984 | return $module.is('form') || $context.is('form'); |
|
19985 | }, |
|
19986 | mocked: function() { |
|
19987 | return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync); |
|
19988 | }, |
|
19989 | input: function() { |
|
19990 | return $module.is('input'); |
|
19991 | }, |
|
19992 | loading: function() { |
|
19993 | return (module.request) |
|
19994 | ? (module.request.state() == 'pending') |
|
19995 | : false |
|
19996 | ; |
|
19997 | }, |
|
19998 | abortedRequest: function(xhr) { |
|
19999 | if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) { |
|
20000 | module.verbose('XHR request determined to be aborted'); |
|
20001 | return true; |
|
20002 | } |
|
20003 | else { |
|
20004 | module.verbose('XHR request was not aborted'); |
|
20005 | return false; |
|
20006 | } |
|
20007 | }, |
|
20008 | validResponse: function(response) { |
|
20009 | if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) { |
|
20010 | module.verbose('Response is not JSON, skipping validation', settings.successTest, response); |
|
20011 | return true; |
|
20012 | } |
|
20013 | module.debug('Checking JSON returned success', settings.successTest, response); |
|
20014 | if( settings.successTest(response) ) { |
|
20015 | module.debug('Response passed success test', response); |
|
20016 | return true; |
|
20017 | } |
|
20018 | else { |
|
20019 | module.debug('Response failed success test', response); |
|
20020 | return false; |
|
20021 | } |
|
20022 | } |
|
20023 | }, |
|
20024 | ||
20025 | was: { |
|
20026 | cancelled: function() { |
|
20027 | return (module.cancelled || false); |
|
20028 | }, |
|
20029 | succesful: function() { |
|
20030 | return (module.request && module.request.state() == 'resolved'); |
|
20031 | }, |
|
20032 | failure: function() { |
|
20033 | return (module.request && module.request.state() == 'rejected'); |
|
20034 | }, |
|
20035 | complete: function() { |
|
20036 | return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') ); |
|
20037 | } |
|
20038 | }, |
|
20039 | ||
20040 | add: { |
|
20041 | urlData: function(url, urlData) { |
|
20042 | var |
|
20043 | requiredVariables, |
|
20044 | optionalVariables |
|
20045 | ; |
|
20046 | if(url) { |
|
20047 | requiredVariables = url.match(settings.regExp.required); |
|
20048 | optionalVariables = url.match(settings.regExp.optional); |
|
20049 | urlData = urlData || settings.urlData; |
|
20050 | if(requiredVariables) { |
|
20051 | module.debug('Looking for required URL variables', requiredVariables); |
|
20052 | $.each(requiredVariables, function(index, templatedString) { |
|
20053 | var |
|
20054 | // allow legacy {$var} style |
|
20055 | variable = (templatedString.indexOf('$') !== -1) |
|
20056 | ? templatedString.substr(2, templatedString.length - 3) |
|
20057 | : templatedString.substr(1, templatedString.length - 2), |
|
20058 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) |
|
20059 | ? urlData[variable] |
|
20060 | : ($module.data(variable) !== undefined) |
|
20061 | ? $module.data(variable) |
|
20062 | : ($context.data(variable) !== undefined) |
|
20063 | ? $context.data(variable) |
|
20064 | : urlData[variable] |
|
20065 | ; |
|
20066 | // remove value |
|
20067 | if(value === undefined) { |
|
20068 | module.error(error.requiredParameter, variable, url); |
|
20069 | url = false; |
|
20070 | return false; |
|
20071 | } |
|
20072 | else { |
|
20073 | module.verbose('Found required variable', variable, value); |
|
20074 | value = (settings.encodeParameters) |
|
20075 | ? module.get.urlEncodedValue(value) |
|
20076 | : value |
|
20077 | ; |
|
20078 | url = url.replace(templatedString, value); |
|
20079 | } |
|
20080 | }); |
|
20081 | } |
|
20082 | if(optionalVariables) { |
|
20083 | module.debug('Looking for optional URL variables', requiredVariables); |
|
20084 | $.each(optionalVariables, function(index, templatedString) { |
|
20085 | var |
|
20086 | // allow legacy {/$var} style |
|
20087 | variable = (templatedString.indexOf('$') !== -1) |
|
20088 | ? templatedString.substr(3, templatedString.length - 4) |
|
20089 | : templatedString.substr(2, templatedString.length - 3), |
|
20090 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) |
|
20091 | ? urlData[variable] |
|
20092 | : ($module.data(variable) !== undefined) |
|
20093 | ? $module.data(variable) |
|
20094 | : ($context.data(variable) !== undefined) |
|
20095 | ? $context.data(variable) |
|
20096 | : urlData[variable] |
|
20097 | ; |
|
20098 | // optional replacement |
|
20099 | if(value !== undefined) { |
|
20100 | module.verbose('Optional variable Found', variable, value); |
|
20101 | url = url.replace(templatedString, value); |
|
20102 | } |
|
20103 | else { |
|
20104 | module.verbose('Optional variable not found', variable); |
|
20105 | // remove preceding slash if set |
|
20106 | if(url.indexOf('/' + templatedString) !== -1) { |
|
20107 | url = url.replace('/' + templatedString, ''); |
|
20108 | } |
|
20109 | else { |
|
20110 | url = url.replace(templatedString, ''); |
|
20111 | } |
|
20112 | } |
|
20113 | }); |
|
20114 | } |
|
20115 | } |
|
20116 | return url; |
|
20117 | }, |
|
20118 | formData: function(data) { |
|
20119 | var |
|
20120 | canSerialize = ($.fn.serializeObject !== undefined), |
|
20121 | formData = (canSerialize) |
|
20122 | ? $form.serializeObject() |
|
20123 | : $form.serialize(), |
|
20124 | hasOtherData |
|
20125 | ; |
|
20126 | data = data || settings.data; |
|
20127 | hasOtherData = $.isPlainObject(data); |
|
20128 | ||
20129 | if(hasOtherData) { |
|
20130 | if(canSerialize) { |
|
20131 | module.debug('Extending existing data with form data', data, formData); |
|
20132 | data = $.extend(true, {}, data, formData); |
|
20133 | } |
|
20134 | else { |
|
20135 | module.error(error.missingSerialize); |
|
20136 | module.debug('Cant extend data. Replacing data with form data', data, formData); |
|
20137 | data = formData; |
|
20138 | } |
|
20139 | } |
|
20140 | else { |
|
20141 | module.debug('Adding form data', formData); |
|
20142 | data = formData; |
|
20143 | } |
|
20144 | return data; |
|
20145 | } |
|
20146 | }, |
|
20147 | ||
20148 | send: { |
|
20149 | request: function() { |
|
20150 | module.set.loading(); |
|
20151 | module.request = module.create.request(); |
|
20152 | if( module.is.mocked() ) { |
|
20153 | module.mockedXHR = module.create.mockedXHR(); |
|
20154 | } |
|
20155 | else { |
|
20156 | module.xhr = module.create.xhr(); |
|
20157 | } |
|
20158 | settings.onRequest.call(context, module.request, module.xhr); |
|
20159 | } |
|
20160 | }, |
|
20161 | ||
20162 | event: { |
|
20163 | trigger: function(event) { |
|
20164 | module.query(); |
|
20165 | if(event.type == 'submit' || event.type == 'click') { |
|
20166 | event.preventDefault(); |
|
20167 | } |
|
20168 | }, |
|
20169 | xhr: { |
|
20170 | always: function() { |
|
20171 | // nothing special |
|
20172 | }, |
|
20173 | done: function(response, textStatus, xhr) { |
|
20174 | var |
|
20175 | context = this, |
|
20176 | elapsedTime = (new Date().getTime() - requestStartTime), |
|
20177 | timeLeft = (settings.loadingDuration - elapsedTime), |
|
20178 | translatedResponse = ( $.isFunction(settings.onResponse) ) |
|
20179 | ? module.is.expectingJSON() |
|
20180 | ? settings.onResponse.call(context, $.extend(true, {}, response)) |
|
20181 | : settings.onResponse.call(context, response) |
|
20182 | : false |
|
20183 | ; |
|
20184 | timeLeft = (timeLeft > 0) |
|
20185 | ? timeLeft |
|
20186 | : 0 |
|
20187 | ; |
|
20188 | if(translatedResponse) { |
|
20189 | module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response); |
|
20190 | response = translatedResponse; |
|
20191 | } |
|
20192 | if(timeLeft > 0) { |
|
20193 | module.debug('Response completed early delaying state change by', timeLeft); |
|
20194 | } |
|
20195 | setTimeout(function() { |
|
20196 | if( module.is.validResponse(response) ) { |
|
20197 | module.request.resolveWith(context, [response, xhr]); |
|
20198 | } |
|
20199 | else { |
|
20200 | module.request.rejectWith(context, [xhr, 'invalid']); |
|
20201 | } |
|
20202 | }, timeLeft); |
|
20203 | }, |
|
20204 | fail: function(xhr, status, httpMessage) { |
|
20205 | var |
|
20206 | context = this, |
|
20207 | elapsedTime = (new Date().getTime() - requestStartTime), |
|
20208 | timeLeft = (settings.loadingDuration - elapsedTime) |
|
20209 | ; |
|
20210 | timeLeft = (timeLeft > 0) |
|
20211 | ? timeLeft |
|
20212 | : 0 |
|
20213 | ; |
|
20214 | if(timeLeft > 0) { |
|
20215 | module.debug('Response completed early delaying state change by', timeLeft); |
|
20216 | } |
|
20217 | setTimeout(function() { |
|
20218 | if( module.is.abortedRequest(xhr) ) { |
|
20219 | module.request.rejectWith(context, [xhr, 'aborted', httpMessage]); |
|
20220 | } |
|
20221 | else { |
|
20222 | module.request.rejectWith(context, [xhr, 'error', status, httpMessage]); |
|
20223 | } |
|
20224 | }, timeLeft); |
|
20225 | } |
|
20226 | }, |
|
20227 | request: { |
|
20228 | done: function(response, xhr) { |
|
20229 | module.debug('Successful API Response', response); |
|
20230 | if(settings.cache === 'local' && url) { |
|
20231 | module.write.cachedResponse(url, response); |
|
20232 | module.debug('Saving server response locally', module.cache); |
|
20233 | } |
|
20234 | settings.onSuccess.call(context, response, $module, xhr); |
|
20235 | }, |
|
20236 | complete: function(firstParameter, secondParameter) { |
|
20237 | var |
|
20238 | xhr, |
|
20239 | response |
|
20240 | ; |
|
20241 | // have to guess callback parameters based on request success |
|
20242 | if( module.was.succesful() ) { |
|
20243 | response = firstParameter; |
|
20244 | xhr = secondParameter; |
|
20245 | } |
|
20246 | else { |
|
20247 | xhr = firstParameter; |
|
20248 | response = module.get.responseFromXHR(xhr); |
|
20249 | } |
|
20250 | module.remove.loading(); |
|
20251 | settings.onComplete.call(context, response, $module, xhr); |
|
20252 | }, |
|
20253 | fail: function(xhr, status, httpMessage) { |
|
20254 | var |
|
20255 | // pull response from xhr if available |
|
20256 | response = module.get.responseFromXHR(xhr), |
|
20257 | errorMessage = module.get.errorFromRequest(response, status, httpMessage) |
|
20258 | ; |
|
20259 | if(status == 'aborted') { |
|
20260 | module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage); |
|
20261 | settings.onAbort.call(context, status, $module, xhr); |
|
20262 | return true; |
|
20263 | } |
|
20264 | else if(status == 'invalid') { |
|
20265 | module.debug('JSON did not pass success test. A server-side error has most likely occurred', response); |
|
20266 | } |
|
20267 | else if(status == 'error') { |
|
20268 | if(xhr !== undefined) { |
|
20269 | module.debug('XHR produced a server error', status, httpMessage); |
|
20270 | // make sure we have an error to display to console |
|
20271 | if( xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') { |
|
20272 | module.error(error.statusMessage + httpMessage, ajaxSettings.url); |
|
20273 | } |
|
20274 | settings.onError.call(context, errorMessage, $module, xhr); |
|
20275 | } |
|
20276 | } |
|
20277 | ||
20278 | if(settings.errorDuration && status !== 'aborted') { |
|
20279 | module.debug('Adding error state'); |
|
20280 | module.set.error(); |
|
20281 | if( module.should.removeError() ) { |
|
20282 | setTimeout(module.remove.error, settings.errorDuration); |
|
20283 | } |
|
20284 | } |
|
20285 | module.debug('API Request failed', errorMessage, xhr); |
|
20286 | settings.onFailure.call(context, response, $module, xhr); |
|
20287 | } |
|
20288 | } |
|
20289 | }, |
|
20290 | ||
20291 | create: { |
|
20292 | ||
20293 | request: function() { |
|
20294 | // api request promise |
|
20295 | return $.Deferred() |
|
20296 | .always(module.event.request.complete) |
|
20297 | .done(module.event.request.done) |
|
20298 | .fail(module.event.request.fail) |
|
20299 | ; |
|
20300 | }, |
|
20301 | ||
20302 | mockedXHR: function () { |
|
20303 | var |
|
20304 | // xhr does not simulate these properties of xhr but must return them |
|
20305 | textStatus = false, |
|
20306 | status = false, |
|
20307 | httpMessage = false, |
|
20308 | responder = settings.mockResponse || settings.response, |
|
20309 | asyncResponder = settings.mockResponseAsync || settings.responseAsync, |
|
20310 | asyncCallback, |
|
20311 | response, |
|
20312 | mockedXHR |
|
20313 | ; |
|
20314 | ||
20315 | mockedXHR = $.Deferred() |
|
20316 | .always(module.event.xhr.complete) |
|
20317 | .done(module.event.xhr.done) |
|
20318 | .fail(module.event.xhr.fail) |
|
20319 | ; |
|
20320 | ||
20321 | if(responder) { |
|
20322 | if( $.isFunction(responder) ) { |
|
20323 | module.debug('Using specified synchronous callback', responder); |
|
20324 | response = responder.call(context, requestSettings); |
|
20325 | } |
|
20326 | else { |
|
20327 | module.debug('Using settings specified response', responder); |
|
20328 | response = responder; |
|
20329 | } |
|
20330 | // simulating response |
|
20331 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); |
|
20332 | } |
|
20333 | else if( $.isFunction(asyncResponder) ) { |
|
20334 | asyncCallback = function(response) { |
|
20335 | module.debug('Async callback returned response', response); |
|
20336 | ||
20337 | if(response) { |
|
20338 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); |
|
20339 | } |
|
20340 | else { |
|
20341 | mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]); |
|
20342 | } |
|
20343 | }; |
|
20344 | module.debug('Using specified async response callback', asyncResponder); |
|
20345 | asyncResponder.call(context, requestSettings, asyncCallback); |
|
20346 | } |
|
20347 | return mockedXHR; |
|
20348 | }, |
|
20349 | ||
20350 | xhr: function() { |
|
20351 | var |
|
20352 | xhr |
|
20353 | ; |
|
20354 | // ajax request promise |
|
20355 | xhr = $.ajax(ajaxSettings) |
|
20356 | .always(module.event.xhr.always) |
|
20357 | .done(module.event.xhr.done) |
|
20358 | .fail(module.event.xhr.fail) |
|
20359 | ; |
|
20360 | module.verbose('Created server request', xhr, ajaxSettings); |
|
20361 | return xhr; |
|
20362 | } |
|
20363 | }, |
|
20364 | ||
20365 | set: { |
|
20366 | error: function() { |
|
20367 | module.verbose('Adding error state to element', $context); |
|
20368 | $context.addClass(className.error); |
|
20369 | }, |
|
20370 | loading: function() { |
|
20371 | module.verbose('Adding loading state to element', $context); |
|
20372 | $context.addClass(className.loading); |
|
20373 | requestStartTime = new Date().getTime(); |
|
20374 | } |
|
20375 | }, |
|
20376 | ||
20377 | remove: { |
|
20378 | error: function() { |
|
20379 | module.verbose('Removing error state from element', $context); |
|
20380 | $context.removeClass(className.error); |
|
20381 | }, |
|
20382 | loading: function() { |
|
20383 | module.verbose('Removing loading state from element', $context); |
|
20384 | $context.removeClass(className.loading); |
|
20385 | } |
|
20386 | }, |
|
20387 | ||
20388 | get: { |
|
20389 | responseFromXHR: function(xhr) { |
|
20390 | return $.isPlainObject(xhr) |
|
20391 | ? (module.is.expectingJSON()) |
|
20392 | ? module.decode.json(xhr.responseText) |
|
20393 | : xhr.responseText |
|
20394 | : false |
|
20395 | ; |
|
20396 | }, |
|
20397 | errorFromRequest: function(response, status, httpMessage) { |
|
20398 | return ($.isPlainObject(response) && response.error !== undefined) |
|
20399 | ? response.error // use json error message |
|
20400 | : (settings.error[status] !== undefined) // use server error message |
|
20401 | ? settings.error[status] |
|
20402 | : httpMessage |
|
20403 | ; |
|
20404 | }, |
|
20405 | request: function() { |
|
20406 | return module.request || false; |
|
20407 | }, |
|
20408 | xhr: function() { |
|
20409 | return module.xhr || false; |
|
20410 | }, |
|
20411 | settings: function() { |
|
20412 | var |
|
20413 | runSettings |
|
20414 | ; |
|
20415 | runSettings = settings.beforeSend.call(context, settings); |
|
20416 | if(runSettings) { |
|
20417 | if(runSettings.success !== undefined) { |
|
20418 | module.debug('Legacy success callback detected', runSettings); |
|
20419 | module.error(error.legacyParameters, runSettings.success); |
|
20420 | runSettings.onSuccess = runSettings.success; |
|
20421 | } |
|
20422 | if(runSettings.failure !== undefined) { |
|
20423 | module.debug('Legacy failure callback detected', runSettings); |
|
20424 | module.error(error.legacyParameters, runSettings.failure); |
|
20425 | runSettings.onFailure = runSettings.failure; |
|
20426 | } |
|
20427 | if(runSettings.complete !== undefined) { |
|
20428 | module.debug('Legacy complete callback detected', runSettings); |
|
20429 | module.error(error.legacyParameters, runSettings.complete); |
|
20430 | runSettings.onComplete = runSettings.complete; |
|
20431 | } |
|
20432 | } |
|
20433 | if(runSettings === undefined) { |
|
20434 | module.error(error.noReturnedValue); |
|
20435 | } |
|
20436 | if(runSettings === false) { |
|
20437 | return runSettings; |
|
20438 | } |
|
20439 | return (runSettings !== undefined) |
|
20440 | ? $.extend(true, {}, runSettings) |
|
20441 | : $.extend(true, {}, settings) |
|
20442 | ; |
|
20443 | }, |
|
20444 | urlEncodedValue: function(value) { |
|
20445 | var |
|
20446 | decodedValue = window.decodeURIComponent(value), |
|
20447 | encodedValue = window.encodeURIComponent(value), |
|
20448 | alreadyEncoded = (decodedValue !== value) |
|
20449 | ; |
|
20450 | if(alreadyEncoded) { |
|
20451 | module.debug('URL value is already encoded, avoiding double encoding', value); |
|
20452 | return value; |
|
20453 | } |
|
20454 | module.verbose('Encoding value using encodeURIComponent', value, encodedValue); |
|
20455 | return encodedValue; |
|
20456 | }, |
|
20457 | defaultData: function() { |
|
20458 | var |
|
20459 | data = {} |
|
20460 | ; |
|
20461 | if( !$.isWindow(element) ) { |
|
20462 | if( module.is.input() ) { |
|
20463 | data.value = $module.val(); |
|
20464 | } |
|
20465 | else if( module.is.form() ) { |
|
20466 | ||
20467 | } |
|
20468 | else { |
|
20469 | data.text = $module.text(); |
|
20470 | } |
|
20471 | } |
|
20472 | return data; |
|
20473 | }, |
|
20474 | event: function() { |
|
20475 | if( $.isWindow(element) || settings.on == 'now' ) { |
|
20476 | module.debug('API called without element, no events attached'); |
|
20477 | return false; |
|
20478 | } |
|
20479 | else if(settings.on == 'auto') { |
|
20480 | if( $module.is('input') ) { |
|
20481 | return (element.oninput !== undefined) |
|
20482 | ? 'input' |
|
20483 | : (element.onpropertychange !== undefined) |
|
20484 | ? 'propertychange' |
|
20485 | : 'keyup' |
|
20486 | ; |
|
20487 | } |
|
20488 | else if( $module.is('form') ) { |
|
20489 | return 'submit'; |
|
20490 | } |
|
20491 | else { |
|
20492 | return 'click'; |
|
20493 | } |
|
20494 | } |
|
20495 | else { |
|
20496 | return settings.on; |
|
20497 | } |
|
20498 | }, |
|
20499 | templatedURL: function(action) { |
|
20500 | action = action || $module.data(metadata.action) || settings.action || false; |
|
20501 | url = $module.data(metadata.url) || settings.url || false; |
|
20502 | if(url) { |
|
20503 | module.debug('Using specified url', url); |
|
20504 | return url; |
|
20505 | } |
|
20506 | if(action) { |
|
20507 | module.debug('Looking up url for action', action, settings.api); |
|
20508 | if(settings.api[action] === undefined && !module.is.mocked()) { |
|
20509 | module.error(error.missingAction, settings.action, settings.api); |
|
20510 | return; |
|
20511 | } |
|
20512 | url = settings.api[action]; |
|
20513 | } |
|
20514 | else if( module.is.form() ) { |
|
20515 | url = $module.attr('action') || $context.attr('action') || false; |
|
20516 | module.debug('No url or action specified, defaulting to form action', url); |
|
20517 | } |
|
20518 | return url; |
|
20519 | } |
|
20520 | }, |
|
20521 | ||
20522 | abort: function() { |
|
20523 | var |
|
20524 | xhr = module.get.xhr() |
|
20525 | ; |
|
20526 | if( xhr && xhr.state() !== 'resolved') { |
|
20527 | module.debug('Cancelling API request'); |
|
20528 | xhr.abort(); |
|
20529 | } |
|
20530 | }, |
|
20531 | ||
20532 | // reset state |
|
20533 | reset: function() { |
|
20534 | module.remove.error(); |
|
20535 | module.remove.loading(); |
|
20536 | }, |
|
20537 | ||
20538 | setting: function(name, value) { |
|
20539 | module.debug('Changing setting', name, value); |
|
20540 | if( $.isPlainObject(name) ) { |
|
20541 | $.extend(true, settings, name); |
|
20542 | } |
|
20543 | else if(value !== undefined) { |
|
20544 | if($.isPlainObject(settings[name])) { |
|
20545 | $.extend(true, settings[name], value); |
|
20546 | } |
|
20547 | else { |
|
20548 | settings[name] = value; |
|
20549 | } |
|
20550 | } |
|
20551 | else { |
|
20552 | return settings[name]; |
|
20553 | } |
|
20554 | }, |
|
20555 | internal: function(name, value) { |
|
20556 | if( $.isPlainObject(name) ) { |
|
20557 | $.extend(true, module, name); |
|
20558 | } |
|
20559 | else if(value !== undefined) { |
|
20560 | module[name] = value; |
|
20561 | } |
|
20562 | else { |
|
20563 | return module[name]; |
|
20564 | } |
|
20565 | }, |
|
20566 | debug: function() { |
|
20567 | if(!settings.silent && settings.debug) { |
|
20568 | if(settings.performance) { |
|
20569 | module.performance.log(arguments); |
|
20570 | } |
|
20571 | else { |
|
20572 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
20573 | module.debug.apply(console, arguments); |
|
20574 | } |
|
20575 | } |
|
20576 | }, |
|
20577 | verbose: function() { |
|
20578 | if(!settings.silent && settings.verbose && settings.debug) { |
|
20579 | if(settings.performance) { |
|
20580 | module.performance.log(arguments); |
|
20581 | } |
|
20582 | else { |
|
20583 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
20584 | module.verbose.apply(console, arguments); |
|
20585 | } |
|
20586 | } |
|
20587 | }, |
|
20588 | error: function() { |
|
20589 | if(!settings.silent) { |
|
20590 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
20591 | module.error.apply(console, arguments); |
|
20592 | } |
|
20593 | }, |
|
20594 | performance: { |
|
20595 | log: function(message) { |
|
20596 | var |
|
20597 | currentTime, |
|
20598 | executionTime, |
|
20599 | previousTime |
|
20600 | ; |
|
20601 | if(settings.performance) { |
|
20602 | currentTime = new Date().getTime(); |
|
20603 | previousTime = time || currentTime; |
|
20604 | executionTime = currentTime - previousTime; |
|
20605 | time = currentTime; |
|
20606 | performance.push({ |
|
20607 | 'Name' : message[0], |
|
20608 | 'Arguments' : [].slice.call(message, 1) || '', |
|
20609 | //'Element' : element, |
|
20610 | 'Execution Time' : executionTime |
|
20611 | }); |
|
20612 | } |
|
20613 | clearTimeout(module.performance.timer); |
|
20614 | module.performance.timer = setTimeout(module.performance.display, 500); |
|
20615 | }, |
|
20616 | display: function() { |
|
20617 | var |
|
20618 | title = settings.name + ':', |
|
20619 | totalTime = 0 |
|
20620 | ; |
|
20621 | time = false; |
|
20622 | clearTimeout(module.performance.timer); |
|
20623 | $.each(performance, function(index, data) { |
|
20624 | totalTime += data['Execution Time']; |
|
20625 | }); |
|
20626 | title += ' ' + totalTime + 'ms'; |
|
20627 | if(moduleSelector) { |
|
20628 | title += ' \'' + moduleSelector + '\''; |
|
20629 | } |
|
20630 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
20631 | console.groupCollapsed(title); |
|
20632 | if(console.table) { |
|
20633 | console.table(performance); |
|
20634 | } |
|
20635 | else { |
|
20636 | $.each(performance, function(index, data) { |
|
20637 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
20638 | }); |
|
20639 | } |
|
20640 | console.groupEnd(); |
|
20641 | } |
|
20642 | performance = []; |
|
20643 | } |
|
20644 | }, |
|
20645 | invoke: function(query, passedArguments, context) { |
|
20646 | var |
|
20647 | object = instance, |
|
20648 | maxDepth, |
|
20649 | found, |
|
20650 | response |
|
20651 | ; |
|
20652 | passedArguments = passedArguments || queryArguments; |
|
20653 | context = element || context; |
|
20654 | if(typeof query == 'string' && object !== undefined) { |
|
20655 | query = query.split(/[\. ]/); |
|
20656 | maxDepth = query.length - 1; |
|
20657 | $.each(query, function(depth, value) { |
|
20658 | var camelCaseValue = (depth != maxDepth) |
|
20659 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
20660 | : query |
|
20661 | ; |
|
20662 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
20663 | object = object[camelCaseValue]; |
|
20664 | } |
|
20665 | else if( object[camelCaseValue] !== undefined ) { |
|
20666 | found = object[camelCaseValue]; |
|
20667 | return false; |
|
20668 | } |
|
20669 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
20670 | object = object[value]; |
|
20671 | } |
|
20672 | else if( object[value] !== undefined ) { |
|
20673 | found = object[value]; |
|
20674 | return false; |
|
20675 | } |
|
20676 | else { |
|
20677 | module.error(error.method, query); |
|
20678 | return false; |
|
20679 | } |
|
20680 | }); |
|
20681 | } |
|
20682 | if ( $.isFunction( found ) ) { |
|
20683 | response = found.apply(context, passedArguments); |
|
20684 | } |
|
20685 | else if(found !== undefined) { |
|
20686 | response = found; |
|
20687 | } |
|
20688 | if($.isArray(returnedValue)) { |
|
20689 | returnedValue.push(response); |
|
20690 | } |
|
20691 | else if(returnedValue !== undefined) { |
|
20692 | returnedValue = [returnedValue, response]; |
|
20693 | } |
|
20694 | else if(response !== undefined) { |
|
20695 | returnedValue = response; |
|
20696 | } |
|
20697 | return found; |
|
20698 | } |
|
20699 | }; |
|
20700 | ||
20701 | if(methodInvoked) { |
|
20702 | if(instance === undefined) { |
|
20703 | module.initialize(); |
|
20704 | } |
|
20705 | module.invoke(query); |
|
20706 | } |
|
20707 | else { |
|
20708 | if(instance !== undefined) { |
|
20709 | instance.invoke('destroy'); |
|
20710 | } |
|
20711 | module.initialize(); |
|
20712 | } |
|
20713 | }) |
|
20714 | ; |
|
20715 | ||
20716 | return (returnedValue !== undefined) |
|
20717 | ? returnedValue |
|
20718 | : this |
|
20719 | ; |
|
20720 | }; |
|
20721 | ||
20722 | $.api.settings = { |
|
20723 | ||
20724 | name : 'API', |
|
20725 | namespace : 'api', |
|
20726 | ||
20727 | debug : false, |
|
20728 | verbose : false, |
|
20729 | performance : true, |
|
20730 | ||
20731 | // object containing all templates endpoints |
|
20732 | api : {}, |
|
20733 | ||
20734 | // whether to cache responses |
|
20735 | cache : true, |
|
20736 | ||
20737 | // whether new requests should abort previous requests |
|
20738 | interruptRequests : true, |
|
20739 | ||
20740 | // event binding |
|
20741 | on : 'auto', |
|
20742 | ||
20743 | // context for applying state classes |
|
20744 | stateContext : false, |
|
20745 | ||
20746 | // duration for loading state |
|
20747 | loadingDuration : 0, |
|
20748 | ||
20749 | // whether to hide errors after a period of time |
|
20750 | hideError : 'auto', |
|
20751 | ||
20752 | // duration for error state |
|
20753 | errorDuration : 2000, |
|
20754 | ||
20755 | // whether parameters should be encoded with encodeURIComponent |
|
20756 | encodeParameters : true, |
|
20757 | ||
20758 | // API action to use |
|
20759 | action : false, |
|
20760 | ||
20761 | // templated URL to use |
|
20762 | url : false, |
|
20763 | ||
20764 | // base URL to apply to all endpoints |
|
20765 | base : '', |
|
20766 | ||
20767 | // data that will |
|
20768 | urlData : {}, |
|
20769 | ||
20770 | // whether to add default data to url data |
|
20771 | defaultData : true, |
|
20772 | ||
20773 | // whether to serialize closest form |
|
20774 | serializeForm : false, |
|
20775 | ||
20776 | // how long to wait before request should occur |
|
20777 | throttle : 0, |
|
20778 | ||
20779 | // whether to throttle first request or only repeated |
|
20780 | throttleFirstRequest : true, |
|
20781 | ||
20782 | // standard ajax settings |
|
20783 | method : 'get', |
|
20784 | data : {}, |
|
20785 | dataType : 'json', |
|
20786 | ||
20787 | // mock response |
|
20788 | mockResponse : false, |
|
20789 | mockResponseAsync : false, |
|
20790 | ||
20791 | // aliases for mock |
|
20792 | response : false, |
|
20793 | responseAsync : false, |
|
20794 | ||
20795 | // callbacks before request |
|
20796 | beforeSend : function(settings) { return settings; }, |
|
20797 | beforeXHR : function(xhr) {}, |
|
20798 | onRequest : function(promise, xhr) {}, |
|
20799 | ||
20800 | // after request |
|
20801 | onResponse : false, // function(response) { }, |
|
20802 | ||
20803 | // response was successful, if JSON passed validation |
|
20804 | onSuccess : function(response, $module) {}, |
|
20805 | ||
20806 | // request finished without aborting |
|
20807 | onComplete : function(response, $module) {}, |
|
20808 | ||
20809 | // failed JSON success test |
|
20810 | onFailure : function(response, $module) {}, |
|
20811 | ||
20812 | // server error |
|
20813 | onError : function(errorMessage, $module) {}, |
|
20814 | ||
20815 | // request aborted |
|
20816 | onAbort : function(errorMessage, $module) {}, |
|
20817 | ||
20818 | successTest : false, |
|
20819 | ||
20820 | // errors |
|
20821 | error : { |
|
20822 | beforeSend : 'The before send function has aborted the request', |
|
20823 | error : 'There was an error with your request', |
|
20824 | exitConditions : 'API Request Aborted. Exit conditions met', |
|
20825 | JSONParse : 'JSON could not be parsed during error handling', |
|
20826 | legacyParameters : 'You are using legacy API success callback names', |
|
20827 | method : 'The method you called is not defined', |
|
20828 | missingAction : 'API action used but no url was defined', |
|
20829 | missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object', |
|
20830 | missingURL : 'No URL specified for api event', |
|
20831 | noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.', |
|
20832 | noStorage : 'Caching responses locally requires session storage', |
|
20833 | parseError : 'There was an error parsing your request', |
|
20834 | requiredParameter : 'Missing a required URL parameter: ', |
|
20835 | statusMessage : 'Server gave an error: ', |
|
20836 | timeout : 'Your request timed out' |
|
20837 | }, |
|
20838 | ||
20839 | regExp : { |
|
20840 | required : /\{\$*[A-z0-9]+\}/g, |
|
20841 | optional : /\{\/\$*[A-z0-9]+\}/g, |
|
20842 | }, |
|
20843 | ||
20844 | className: { |
|
20845 | loading : 'loading', |
|
20846 | error : 'error' |
|
20847 | }, |
|
20848 | ||
20849 | selector: { |
|
20850 | disabled : '.disabled', |
|
20851 | form : 'form' |
|
20852 | }, |
|
20853 | ||
20854 | metadata: { |
|
20855 | action : 'action', |
|
20856 | url : 'url' |
|
20857 | } |
|
20858 | }; |
|
20859 | ||
20860 | ||
20861 | ||
20862 | })( jQuery, window, document ); |
|
20863 | ||
20864 | /*! |
|
20865 | * # Semantic UI 2.2.11 - State |
@@ 11-1167 (lines=1157) @@ | ||
8 | * |
|
9 | */ |
|
10 | ||
11 | ;(function ($, window, document, undefined) { |
|
12 | ||
13 | "use strict"; |
|
14 | ||
15 | var |
|
16 | window = (typeof window != 'undefined' && window.Math == Math) |
|
17 | ? window |
|
18 | : (typeof self != 'undefined' && self.Math == Math) |
|
19 | ? self |
|
20 | : Function('return this')() |
|
21 | ; |
|
22 | ||
23 | $.api = $.fn.api = function(parameters) { |
|
24 | ||
25 | var |
|
26 | // use window context if none specified |
|
27 | $allModules = $.isFunction(this) |
|
28 | ? $(window) |
|
29 | : $(this), |
|
30 | moduleSelector = $allModules.selector || '', |
|
31 | time = new Date().getTime(), |
|
32 | performance = [], |
|
33 | ||
34 | query = arguments[0], |
|
35 | methodInvoked = (typeof query == 'string'), |
|
36 | queryArguments = [].slice.call(arguments, 1), |
|
37 | ||
38 | returnedValue |
|
39 | ; |
|
40 | ||
41 | $allModules |
|
42 | .each(function() { |
|
43 | var |
|
44 | settings = ( $.isPlainObject(parameters) ) |
|
45 | ? $.extend(true, {}, $.fn.api.settings, parameters) |
|
46 | : $.extend({}, $.fn.api.settings), |
|
47 | ||
48 | // internal aliases |
|
49 | namespace = settings.namespace, |
|
50 | metadata = settings.metadata, |
|
51 | selector = settings.selector, |
|
52 | error = settings.error, |
|
53 | className = settings.className, |
|
54 | ||
55 | // define namespaces for modules |
|
56 | eventNamespace = '.' + namespace, |
|
57 | moduleNamespace = 'module-' + namespace, |
|
58 | ||
59 | // element that creates request |
|
60 | $module = $(this), |
|
61 | $form = $module.closest(selector.form), |
|
62 | ||
63 | // context used for state |
|
64 | $context = (settings.stateContext) |
|
65 | ? $(settings.stateContext) |
|
66 | : $module, |
|
67 | ||
68 | // request details |
|
69 | ajaxSettings, |
|
70 | requestSettings, |
|
71 | url, |
|
72 | data, |
|
73 | requestStartTime, |
|
74 | ||
75 | // standard module |
|
76 | element = this, |
|
77 | context = $context[0], |
|
78 | instance = $module.data(moduleNamespace), |
|
79 | module |
|
80 | ; |
|
81 | ||
82 | module = { |
|
83 | ||
84 | initialize: function() { |
|
85 | if(!methodInvoked) { |
|
86 | module.bind.events(); |
|
87 | } |
|
88 | module.instantiate(); |
|
89 | }, |
|
90 | ||
91 | instantiate: function() { |
|
92 | module.verbose('Storing instance of module', module); |
|
93 | instance = module; |
|
94 | $module |
|
95 | .data(moduleNamespace, instance) |
|
96 | ; |
|
97 | }, |
|
98 | ||
99 | destroy: function() { |
|
100 | module.verbose('Destroying previous module for', element); |
|
101 | $module |
|
102 | .removeData(moduleNamespace) |
|
103 | .off(eventNamespace) |
|
104 | ; |
|
105 | }, |
|
106 | ||
107 | bind: { |
|
108 | events: function() { |
|
109 | var |
|
110 | triggerEvent = module.get.event() |
|
111 | ; |
|
112 | if( triggerEvent ) { |
|
113 | module.verbose('Attaching API events to element', triggerEvent); |
|
114 | $module |
|
115 | .on(triggerEvent + eventNamespace, module.event.trigger) |
|
116 | ; |
|
117 | } |
|
118 | else if(settings.on == 'now') { |
|
119 | module.debug('Querying API endpoint immediately'); |
|
120 | module.query(); |
|
121 | } |
|
122 | } |
|
123 | }, |
|
124 | ||
125 | decode: { |
|
126 | json: function(response) { |
|
127 | if(response !== undefined && typeof response == 'string') { |
|
128 | try { |
|
129 | response = JSON.parse(response); |
|
130 | } |
|
131 | catch(e) { |
|
132 | // isnt json string |
|
133 | } |
|
134 | } |
|
135 | return response; |
|
136 | } |
|
137 | }, |
|
138 | ||
139 | read: { |
|
140 | cachedResponse: function(url) { |
|
141 | var |
|
142 | response |
|
143 | ; |
|
144 | if(window.Storage === undefined) { |
|
145 | module.error(error.noStorage); |
|
146 | return; |
|
147 | } |
|
148 | response = sessionStorage.getItem(url); |
|
149 | module.debug('Using cached response', url, response); |
|
150 | response = module.decode.json(response); |
|
151 | return response; |
|
152 | } |
|
153 | }, |
|
154 | write: { |
|
155 | cachedResponse: function(url, response) { |
|
156 | if(response && response === '') { |
|
157 | module.debug('Response empty, not caching', response); |
|
158 | return; |
|
159 | } |
|
160 | if(window.Storage === undefined) { |
|
161 | module.error(error.noStorage); |
|
162 | return; |
|
163 | } |
|
164 | if( $.isPlainObject(response) ) { |
|
165 | response = JSON.stringify(response); |
|
166 | } |
|
167 | sessionStorage.setItem(url, response); |
|
168 | module.verbose('Storing cached response for url', url, response); |
|
169 | } |
|
170 | }, |
|
171 | ||
172 | query: function() { |
|
173 | ||
174 | if(module.is.disabled()) { |
|
175 | module.debug('Element is disabled API request aborted'); |
|
176 | return; |
|
177 | } |
|
178 | ||
179 | if(module.is.loading()) { |
|
180 | if(settings.interruptRequests) { |
|
181 | module.debug('Interrupting previous request'); |
|
182 | module.abort(); |
|
183 | } |
|
184 | else { |
|
185 | module.debug('Cancelling request, previous request is still pending'); |
|
186 | return; |
|
187 | } |
|
188 | } |
|
189 | ||
190 | // pass element metadata to url (value, text) |
|
191 | if(settings.defaultData) { |
|
192 | $.extend(true, settings.urlData, module.get.defaultData()); |
|
193 | } |
|
194 | ||
195 | // Add form content |
|
196 | if(settings.serializeForm) { |
|
197 | settings.data = module.add.formData(settings.data); |
|
198 | } |
|
199 | ||
200 | // call beforesend and get any settings changes |
|
201 | requestSettings = module.get.settings(); |
|
202 | ||
203 | // check if before send cancelled request |
|
204 | if(requestSettings === false) { |
|
205 | module.cancelled = true; |
|
206 | module.error(error.beforeSend); |
|
207 | return; |
|
208 | } |
|
209 | else { |
|
210 | module.cancelled = false; |
|
211 | } |
|
212 | ||
213 | // get url |
|
214 | url = module.get.templatedURL(); |
|
215 | ||
216 | if(!url && !module.is.mocked()) { |
|
217 | module.error(error.missingURL); |
|
218 | return; |
|
219 | } |
|
220 | ||
221 | // replace variables |
|
222 | url = module.add.urlData( url ); |
|
223 | // missing url parameters |
|
224 | if( !url && !module.is.mocked()) { |
|
225 | return; |
|
226 | } |
|
227 | ||
228 | requestSettings.url = settings.base + url; |
|
229 | ||
230 | // look for jQuery ajax parameters in settings |
|
231 | ajaxSettings = $.extend(true, {}, settings, { |
|
232 | type : settings.method || settings.type, |
|
233 | data : data, |
|
234 | url : settings.base + url, |
|
235 | beforeSend : settings.beforeXHR, |
|
236 | success : function() {}, |
|
237 | failure : function() {}, |
|
238 | complete : function() {} |
|
239 | }); |
|
240 | ||
241 | module.debug('Querying URL', ajaxSettings.url); |
|
242 | module.verbose('Using AJAX settings', ajaxSettings); |
|
243 | if(settings.cache === 'local' && module.read.cachedResponse(url)) { |
|
244 | module.debug('Response returned from local cache'); |
|
245 | module.request = module.create.request(); |
|
246 | module.request.resolveWith(context, [ module.read.cachedResponse(url) ]); |
|
247 | return; |
|
248 | } |
|
249 | ||
250 | if( !settings.throttle ) { |
|
251 | module.debug('Sending request', data, ajaxSettings.method); |
|
252 | module.send.request(); |
|
253 | } |
|
254 | else { |
|
255 | if(!settings.throttleFirstRequest && !module.timer) { |
|
256 | module.debug('Sending request', data, ajaxSettings.method); |
|
257 | module.send.request(); |
|
258 | module.timer = setTimeout(function(){}, settings.throttle); |
|
259 | } |
|
260 | else { |
|
261 | module.debug('Throttling request', settings.throttle); |
|
262 | clearTimeout(module.timer); |
|
263 | module.timer = setTimeout(function() { |
|
264 | if(module.timer) { |
|
265 | delete module.timer; |
|
266 | } |
|
267 | module.debug('Sending throttled request', data, ajaxSettings.method); |
|
268 | module.send.request(); |
|
269 | }, settings.throttle); |
|
270 | } |
|
271 | } |
|
272 | ||
273 | }, |
|
274 | ||
275 | should: { |
|
276 | removeError: function() { |
|
277 | return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) ); |
|
278 | } |
|
279 | }, |
|
280 | ||
281 | is: { |
|
282 | disabled: function() { |
|
283 | return ($module.filter(selector.disabled).length > 0); |
|
284 | }, |
|
285 | expectingJSON: function() { |
|
286 | return settings.dataType === 'json' || settings.dataType === 'jsonp'; |
|
287 | }, |
|
288 | form: function() { |
|
289 | return $module.is('form') || $context.is('form'); |
|
290 | }, |
|
291 | mocked: function() { |
|
292 | return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync); |
|
293 | }, |
|
294 | input: function() { |
|
295 | return $module.is('input'); |
|
296 | }, |
|
297 | loading: function() { |
|
298 | return (module.request) |
|
299 | ? (module.request.state() == 'pending') |
|
300 | : false |
|
301 | ; |
|
302 | }, |
|
303 | abortedRequest: function(xhr) { |
|
304 | if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) { |
|
305 | module.verbose('XHR request determined to be aborted'); |
|
306 | return true; |
|
307 | } |
|
308 | else { |
|
309 | module.verbose('XHR request was not aborted'); |
|
310 | return false; |
|
311 | } |
|
312 | }, |
|
313 | validResponse: function(response) { |
|
314 | if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) { |
|
315 | module.verbose('Response is not JSON, skipping validation', settings.successTest, response); |
|
316 | return true; |
|
317 | } |
|
318 | module.debug('Checking JSON returned success', settings.successTest, response); |
|
319 | if( settings.successTest(response) ) { |
|
320 | module.debug('Response passed success test', response); |
|
321 | return true; |
|
322 | } |
|
323 | else { |
|
324 | module.debug('Response failed success test', response); |
|
325 | return false; |
|
326 | } |
|
327 | } |
|
328 | }, |
|
329 | ||
330 | was: { |
|
331 | cancelled: function() { |
|
332 | return (module.cancelled || false); |
|
333 | }, |
|
334 | succesful: function() { |
|
335 | return (module.request && module.request.state() == 'resolved'); |
|
336 | }, |
|
337 | failure: function() { |
|
338 | return (module.request && module.request.state() == 'rejected'); |
|
339 | }, |
|
340 | complete: function() { |
|
341 | return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') ); |
|
342 | } |
|
343 | }, |
|
344 | ||
345 | add: { |
|
346 | urlData: function(url, urlData) { |
|
347 | var |
|
348 | requiredVariables, |
|
349 | optionalVariables |
|
350 | ; |
|
351 | if(url) { |
|
352 | requiredVariables = url.match(settings.regExp.required); |
|
353 | optionalVariables = url.match(settings.regExp.optional); |
|
354 | urlData = urlData || settings.urlData; |
|
355 | if(requiredVariables) { |
|
356 | module.debug('Looking for required URL variables', requiredVariables); |
|
357 | $.each(requiredVariables, function(index, templatedString) { |
|
358 | var |
|
359 | // allow legacy {$var} style |
|
360 | variable = (templatedString.indexOf('$') !== -1) |
|
361 | ? templatedString.substr(2, templatedString.length - 3) |
|
362 | : templatedString.substr(1, templatedString.length - 2), |
|
363 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) |
|
364 | ? urlData[variable] |
|
365 | : ($module.data(variable) !== undefined) |
|
366 | ? $module.data(variable) |
|
367 | : ($context.data(variable) !== undefined) |
|
368 | ? $context.data(variable) |
|
369 | : urlData[variable] |
|
370 | ; |
|
371 | // remove value |
|
372 | if(value === undefined) { |
|
373 | module.error(error.requiredParameter, variable, url); |
|
374 | url = false; |
|
375 | return false; |
|
376 | } |
|
377 | else { |
|
378 | module.verbose('Found required variable', variable, value); |
|
379 | value = (settings.encodeParameters) |
|
380 | ? module.get.urlEncodedValue(value) |
|
381 | : value |
|
382 | ; |
|
383 | url = url.replace(templatedString, value); |
|
384 | } |
|
385 | }); |
|
386 | } |
|
387 | if(optionalVariables) { |
|
388 | module.debug('Looking for optional URL variables', requiredVariables); |
|
389 | $.each(optionalVariables, function(index, templatedString) { |
|
390 | var |
|
391 | // allow legacy {/$var} style |
|
392 | variable = (templatedString.indexOf('$') !== -1) |
|
393 | ? templatedString.substr(3, templatedString.length - 4) |
|
394 | : templatedString.substr(2, templatedString.length - 3), |
|
395 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined) |
|
396 | ? urlData[variable] |
|
397 | : ($module.data(variable) !== undefined) |
|
398 | ? $module.data(variable) |
|
399 | : ($context.data(variable) !== undefined) |
|
400 | ? $context.data(variable) |
|
401 | : urlData[variable] |
|
402 | ; |
|
403 | // optional replacement |
|
404 | if(value !== undefined) { |
|
405 | module.verbose('Optional variable Found', variable, value); |
|
406 | url = url.replace(templatedString, value); |
|
407 | } |
|
408 | else { |
|
409 | module.verbose('Optional variable not found', variable); |
|
410 | // remove preceding slash if set |
|
411 | if(url.indexOf('/' + templatedString) !== -1) { |
|
412 | url = url.replace('/' + templatedString, ''); |
|
413 | } |
|
414 | else { |
|
415 | url = url.replace(templatedString, ''); |
|
416 | } |
|
417 | } |
|
418 | }); |
|
419 | } |
|
420 | } |
|
421 | return url; |
|
422 | }, |
|
423 | formData: function(data) { |
|
424 | var |
|
425 | canSerialize = ($.fn.serializeObject !== undefined), |
|
426 | formData = (canSerialize) |
|
427 | ? $form.serializeObject() |
|
428 | : $form.serialize(), |
|
429 | hasOtherData |
|
430 | ; |
|
431 | data = data || settings.data; |
|
432 | hasOtherData = $.isPlainObject(data); |
|
433 | ||
434 | if(hasOtherData) { |
|
435 | if(canSerialize) { |
|
436 | module.debug('Extending existing data with form data', data, formData); |
|
437 | data = $.extend(true, {}, data, formData); |
|
438 | } |
|
439 | else { |
|
440 | module.error(error.missingSerialize); |
|
441 | module.debug('Cant extend data. Replacing data with form data', data, formData); |
|
442 | data = formData; |
|
443 | } |
|
444 | } |
|
445 | else { |
|
446 | module.debug('Adding form data', formData); |
|
447 | data = formData; |
|
448 | } |
|
449 | return data; |
|
450 | } |
|
451 | }, |
|
452 | ||
453 | send: { |
|
454 | request: function() { |
|
455 | module.set.loading(); |
|
456 | module.request = module.create.request(); |
|
457 | if( module.is.mocked() ) { |
|
458 | module.mockedXHR = module.create.mockedXHR(); |
|
459 | } |
|
460 | else { |
|
461 | module.xhr = module.create.xhr(); |
|
462 | } |
|
463 | settings.onRequest.call(context, module.request, module.xhr); |
|
464 | } |
|
465 | }, |
|
466 | ||
467 | event: { |
|
468 | trigger: function(event) { |
|
469 | module.query(); |
|
470 | if(event.type == 'submit' || event.type == 'click') { |
|
471 | event.preventDefault(); |
|
472 | } |
|
473 | }, |
|
474 | xhr: { |
|
475 | always: function() { |
|
476 | // nothing special |
|
477 | }, |
|
478 | done: function(response, textStatus, xhr) { |
|
479 | var |
|
480 | context = this, |
|
481 | elapsedTime = (new Date().getTime() - requestStartTime), |
|
482 | timeLeft = (settings.loadingDuration - elapsedTime), |
|
483 | translatedResponse = ( $.isFunction(settings.onResponse) ) |
|
484 | ? module.is.expectingJSON() |
|
485 | ? settings.onResponse.call(context, $.extend(true, {}, response)) |
|
486 | : settings.onResponse.call(context, response) |
|
487 | : false |
|
488 | ; |
|
489 | timeLeft = (timeLeft > 0) |
|
490 | ? timeLeft |
|
491 | : 0 |
|
492 | ; |
|
493 | if(translatedResponse) { |
|
494 | module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response); |
|
495 | response = translatedResponse; |
|
496 | } |
|
497 | if(timeLeft > 0) { |
|
498 | module.debug('Response completed early delaying state change by', timeLeft); |
|
499 | } |
|
500 | setTimeout(function() { |
|
501 | if( module.is.validResponse(response) ) { |
|
502 | module.request.resolveWith(context, [response, xhr]); |
|
503 | } |
|
504 | else { |
|
505 | module.request.rejectWith(context, [xhr, 'invalid']); |
|
506 | } |
|
507 | }, timeLeft); |
|
508 | }, |
|
509 | fail: function(xhr, status, httpMessage) { |
|
510 | var |
|
511 | context = this, |
|
512 | elapsedTime = (new Date().getTime() - requestStartTime), |
|
513 | timeLeft = (settings.loadingDuration - elapsedTime) |
|
514 | ; |
|
515 | timeLeft = (timeLeft > 0) |
|
516 | ? timeLeft |
|
517 | : 0 |
|
518 | ; |
|
519 | if(timeLeft > 0) { |
|
520 | module.debug('Response completed early delaying state change by', timeLeft); |
|
521 | } |
|
522 | setTimeout(function() { |
|
523 | if( module.is.abortedRequest(xhr) ) { |
|
524 | module.request.rejectWith(context, [xhr, 'aborted', httpMessage]); |
|
525 | } |
|
526 | else { |
|
527 | module.request.rejectWith(context, [xhr, 'error', status, httpMessage]); |
|
528 | } |
|
529 | }, timeLeft); |
|
530 | } |
|
531 | }, |
|
532 | request: { |
|
533 | done: function(response, xhr) { |
|
534 | module.debug('Successful API Response', response); |
|
535 | if(settings.cache === 'local' && url) { |
|
536 | module.write.cachedResponse(url, response); |
|
537 | module.debug('Saving server response locally', module.cache); |
|
538 | } |
|
539 | settings.onSuccess.call(context, response, $module, xhr); |
|
540 | }, |
|
541 | complete: function(firstParameter, secondParameter) { |
|
542 | var |
|
543 | xhr, |
|
544 | response |
|
545 | ; |
|
546 | // have to guess callback parameters based on request success |
|
547 | if( module.was.succesful() ) { |
|
548 | response = firstParameter; |
|
549 | xhr = secondParameter; |
|
550 | } |
|
551 | else { |
|
552 | xhr = firstParameter; |
|
553 | response = module.get.responseFromXHR(xhr); |
|
554 | } |
|
555 | module.remove.loading(); |
|
556 | settings.onComplete.call(context, response, $module, xhr); |
|
557 | }, |
|
558 | fail: function(xhr, status, httpMessage) { |
|
559 | var |
|
560 | // pull response from xhr if available |
|
561 | response = module.get.responseFromXHR(xhr), |
|
562 | errorMessage = module.get.errorFromRequest(response, status, httpMessage) |
|
563 | ; |
|
564 | if(status == 'aborted') { |
|
565 | module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage); |
|
566 | settings.onAbort.call(context, status, $module, xhr); |
|
567 | return true; |
|
568 | } |
|
569 | else if(status == 'invalid') { |
|
570 | module.debug('JSON did not pass success test. A server-side error has most likely occurred', response); |
|
571 | } |
|
572 | else if(status == 'error') { |
|
573 | if(xhr !== undefined) { |
|
574 | module.debug('XHR produced a server error', status, httpMessage); |
|
575 | // make sure we have an error to display to console |
|
576 | if( xhr.status != 200 && httpMessage !== undefined && httpMessage !== '') { |
|
577 | module.error(error.statusMessage + httpMessage, ajaxSettings.url); |
|
578 | } |
|
579 | settings.onError.call(context, errorMessage, $module, xhr); |
|
580 | } |
|
581 | } |
|
582 | ||
583 | if(settings.errorDuration && status !== 'aborted') { |
|
584 | module.debug('Adding error state'); |
|
585 | module.set.error(); |
|
586 | if( module.should.removeError() ) { |
|
587 | setTimeout(module.remove.error, settings.errorDuration); |
|
588 | } |
|
589 | } |
|
590 | module.debug('API Request failed', errorMessage, xhr); |
|
591 | settings.onFailure.call(context, response, $module, xhr); |
|
592 | } |
|
593 | } |
|
594 | }, |
|
595 | ||
596 | create: { |
|
597 | ||
598 | request: function() { |
|
599 | // api request promise |
|
600 | return $.Deferred() |
|
601 | .always(module.event.request.complete) |
|
602 | .done(module.event.request.done) |
|
603 | .fail(module.event.request.fail) |
|
604 | ; |
|
605 | }, |
|
606 | ||
607 | mockedXHR: function () { |
|
608 | var |
|
609 | // xhr does not simulate these properties of xhr but must return them |
|
610 | textStatus = false, |
|
611 | status = false, |
|
612 | httpMessage = false, |
|
613 | responder = settings.mockResponse || settings.response, |
|
614 | asyncResponder = settings.mockResponseAsync || settings.responseAsync, |
|
615 | asyncCallback, |
|
616 | response, |
|
617 | mockedXHR |
|
618 | ; |
|
619 | ||
620 | mockedXHR = $.Deferred() |
|
621 | .always(module.event.xhr.complete) |
|
622 | .done(module.event.xhr.done) |
|
623 | .fail(module.event.xhr.fail) |
|
624 | ; |
|
625 | ||
626 | if(responder) { |
|
627 | if( $.isFunction(responder) ) { |
|
628 | module.debug('Using specified synchronous callback', responder); |
|
629 | response = responder.call(context, requestSettings); |
|
630 | } |
|
631 | else { |
|
632 | module.debug('Using settings specified response', responder); |
|
633 | response = responder; |
|
634 | } |
|
635 | // simulating response |
|
636 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); |
|
637 | } |
|
638 | else if( $.isFunction(asyncResponder) ) { |
|
639 | asyncCallback = function(response) { |
|
640 | module.debug('Async callback returned response', response); |
|
641 | ||
642 | if(response) { |
|
643 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]); |
|
644 | } |
|
645 | else { |
|
646 | mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]); |
|
647 | } |
|
648 | }; |
|
649 | module.debug('Using specified async response callback', asyncResponder); |
|
650 | asyncResponder.call(context, requestSettings, asyncCallback); |
|
651 | } |
|
652 | return mockedXHR; |
|
653 | }, |
|
654 | ||
655 | xhr: function() { |
|
656 | var |
|
657 | xhr |
|
658 | ; |
|
659 | // ajax request promise |
|
660 | xhr = $.ajax(ajaxSettings) |
|
661 | .always(module.event.xhr.always) |
|
662 | .done(module.event.xhr.done) |
|
663 | .fail(module.event.xhr.fail) |
|
664 | ; |
|
665 | module.verbose('Created server request', xhr, ajaxSettings); |
|
666 | return xhr; |
|
667 | } |
|
668 | }, |
|
669 | ||
670 | set: { |
|
671 | error: function() { |
|
672 | module.verbose('Adding error state to element', $context); |
|
673 | $context.addClass(className.error); |
|
674 | }, |
|
675 | loading: function() { |
|
676 | module.verbose('Adding loading state to element', $context); |
|
677 | $context.addClass(className.loading); |
|
678 | requestStartTime = new Date().getTime(); |
|
679 | } |
|
680 | }, |
|
681 | ||
682 | remove: { |
|
683 | error: function() { |
|
684 | module.verbose('Removing error state from element', $context); |
|
685 | $context.removeClass(className.error); |
|
686 | }, |
|
687 | loading: function() { |
|
688 | module.verbose('Removing loading state from element', $context); |
|
689 | $context.removeClass(className.loading); |
|
690 | } |
|
691 | }, |
|
692 | ||
693 | get: { |
|
694 | responseFromXHR: function(xhr) { |
|
695 | return $.isPlainObject(xhr) |
|
696 | ? (module.is.expectingJSON()) |
|
697 | ? module.decode.json(xhr.responseText) |
|
698 | : xhr.responseText |
|
699 | : false |
|
700 | ; |
|
701 | }, |
|
702 | errorFromRequest: function(response, status, httpMessage) { |
|
703 | return ($.isPlainObject(response) && response.error !== undefined) |
|
704 | ? response.error // use json error message |
|
705 | : (settings.error[status] !== undefined) // use server error message |
|
706 | ? settings.error[status] |
|
707 | : httpMessage |
|
708 | ; |
|
709 | }, |
|
710 | request: function() { |
|
711 | return module.request || false; |
|
712 | }, |
|
713 | xhr: function() { |
|
714 | return module.xhr || false; |
|
715 | }, |
|
716 | settings: function() { |
|
717 | var |
|
718 | runSettings |
|
719 | ; |
|
720 | runSettings = settings.beforeSend.call(context, settings); |
|
721 | if(runSettings) { |
|
722 | if(runSettings.success !== undefined) { |
|
723 | module.debug('Legacy success callback detected', runSettings); |
|
724 | module.error(error.legacyParameters, runSettings.success); |
|
725 | runSettings.onSuccess = runSettings.success; |
|
726 | } |
|
727 | if(runSettings.failure !== undefined) { |
|
728 | module.debug('Legacy failure callback detected', runSettings); |
|
729 | module.error(error.legacyParameters, runSettings.failure); |
|
730 | runSettings.onFailure = runSettings.failure; |
|
731 | } |
|
732 | if(runSettings.complete !== undefined) { |
|
733 | module.debug('Legacy complete callback detected', runSettings); |
|
734 | module.error(error.legacyParameters, runSettings.complete); |
|
735 | runSettings.onComplete = runSettings.complete; |
|
736 | } |
|
737 | } |
|
738 | if(runSettings === undefined) { |
|
739 | module.error(error.noReturnedValue); |
|
740 | } |
|
741 | if(runSettings === false) { |
|
742 | return runSettings; |
|
743 | } |
|
744 | return (runSettings !== undefined) |
|
745 | ? $.extend(true, {}, runSettings) |
|
746 | : $.extend(true, {}, settings) |
|
747 | ; |
|
748 | }, |
|
749 | urlEncodedValue: function(value) { |
|
750 | var |
|
751 | decodedValue = window.decodeURIComponent(value), |
|
752 | encodedValue = window.encodeURIComponent(value), |
|
753 | alreadyEncoded = (decodedValue !== value) |
|
754 | ; |
|
755 | if(alreadyEncoded) { |
|
756 | module.debug('URL value is already encoded, avoiding double encoding', value); |
|
757 | return value; |
|
758 | } |
|
759 | module.verbose('Encoding value using encodeURIComponent', value, encodedValue); |
|
760 | return encodedValue; |
|
761 | }, |
|
762 | defaultData: function() { |
|
763 | var |
|
764 | data = {} |
|
765 | ; |
|
766 | if( !$.isWindow(element) ) { |
|
767 | if( module.is.input() ) { |
|
768 | data.value = $module.val(); |
|
769 | } |
|
770 | else if( module.is.form() ) { |
|
771 | ||
772 | } |
|
773 | else { |
|
774 | data.text = $module.text(); |
|
775 | } |
|
776 | } |
|
777 | return data; |
|
778 | }, |
|
779 | event: function() { |
|
780 | if( $.isWindow(element) || settings.on == 'now' ) { |
|
781 | module.debug('API called without element, no events attached'); |
|
782 | return false; |
|
783 | } |
|
784 | else if(settings.on == 'auto') { |
|
785 | if( $module.is('input') ) { |
|
786 | return (element.oninput !== undefined) |
|
787 | ? 'input' |
|
788 | : (element.onpropertychange !== undefined) |
|
789 | ? 'propertychange' |
|
790 | : 'keyup' |
|
791 | ; |
|
792 | } |
|
793 | else if( $module.is('form') ) { |
|
794 | return 'submit'; |
|
795 | } |
|
796 | else { |
|
797 | return 'click'; |
|
798 | } |
|
799 | } |
|
800 | else { |
|
801 | return settings.on; |
|
802 | } |
|
803 | }, |
|
804 | templatedURL: function(action) { |
|
805 | action = action || $module.data(metadata.action) || settings.action || false; |
|
806 | url = $module.data(metadata.url) || settings.url || false; |
|
807 | if(url) { |
|
808 | module.debug('Using specified url', url); |
|
809 | return url; |
|
810 | } |
|
811 | if(action) { |
|
812 | module.debug('Looking up url for action', action, settings.api); |
|
813 | if(settings.api[action] === undefined && !module.is.mocked()) { |
|
814 | module.error(error.missingAction, settings.action, settings.api); |
|
815 | return; |
|
816 | } |
|
817 | url = settings.api[action]; |
|
818 | } |
|
819 | else if( module.is.form() ) { |
|
820 | url = $module.attr('action') || $context.attr('action') || false; |
|
821 | module.debug('No url or action specified, defaulting to form action', url); |
|
822 | } |
|
823 | return url; |
|
824 | } |
|
825 | }, |
|
826 | ||
827 | abort: function() { |
|
828 | var |
|
829 | xhr = module.get.xhr() |
|
830 | ; |
|
831 | if( xhr && xhr.state() !== 'resolved') { |
|
832 | module.debug('Cancelling API request'); |
|
833 | xhr.abort(); |
|
834 | } |
|
835 | }, |
|
836 | ||
837 | // reset state |
|
838 | reset: function() { |
|
839 | module.remove.error(); |
|
840 | module.remove.loading(); |
|
841 | }, |
|
842 | ||
843 | setting: function(name, value) { |
|
844 | module.debug('Changing setting', name, value); |
|
845 | if( $.isPlainObject(name) ) { |
|
846 | $.extend(true, settings, name); |
|
847 | } |
|
848 | else if(value !== undefined) { |
|
849 | if($.isPlainObject(settings[name])) { |
|
850 | $.extend(true, settings[name], value); |
|
851 | } |
|
852 | else { |
|
853 | settings[name] = value; |
|
854 | } |
|
855 | } |
|
856 | else { |
|
857 | return settings[name]; |
|
858 | } |
|
859 | }, |
|
860 | internal: function(name, value) { |
|
861 | if( $.isPlainObject(name) ) { |
|
862 | $.extend(true, module, name); |
|
863 | } |
|
864 | else if(value !== undefined) { |
|
865 | module[name] = value; |
|
866 | } |
|
867 | else { |
|
868 | return module[name]; |
|
869 | } |
|
870 | }, |
|
871 | debug: function() { |
|
872 | if(!settings.silent && settings.debug) { |
|
873 | if(settings.performance) { |
|
874 | module.performance.log(arguments); |
|
875 | } |
|
876 | else { |
|
877 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
878 | module.debug.apply(console, arguments); |
|
879 | } |
|
880 | } |
|
881 | }, |
|
882 | verbose: function() { |
|
883 | if(!settings.silent && settings.verbose && settings.debug) { |
|
884 | if(settings.performance) { |
|
885 | module.performance.log(arguments); |
|
886 | } |
|
887 | else { |
|
888 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':'); |
|
889 | module.verbose.apply(console, arguments); |
|
890 | } |
|
891 | } |
|
892 | }, |
|
893 | error: function() { |
|
894 | if(!settings.silent) { |
|
895 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':'); |
|
896 | module.error.apply(console, arguments); |
|
897 | } |
|
898 | }, |
|
899 | performance: { |
|
900 | log: function(message) { |
|
901 | var |
|
902 | currentTime, |
|
903 | executionTime, |
|
904 | previousTime |
|
905 | ; |
|
906 | if(settings.performance) { |
|
907 | currentTime = new Date().getTime(); |
|
908 | previousTime = time || currentTime; |
|
909 | executionTime = currentTime - previousTime; |
|
910 | time = currentTime; |
|
911 | performance.push({ |
|
912 | 'Name' : message[0], |
|
913 | 'Arguments' : [].slice.call(message, 1) || '', |
|
914 | //'Element' : element, |
|
915 | 'Execution Time' : executionTime |
|
916 | }); |
|
917 | } |
|
918 | clearTimeout(module.performance.timer); |
|
919 | module.performance.timer = setTimeout(module.performance.display, 500); |
|
920 | }, |
|
921 | display: function() { |
|
922 | var |
|
923 | title = settings.name + ':', |
|
924 | totalTime = 0 |
|
925 | ; |
|
926 | time = false; |
|
927 | clearTimeout(module.performance.timer); |
|
928 | $.each(performance, function(index, data) { |
|
929 | totalTime += data['Execution Time']; |
|
930 | }); |
|
931 | title += ' ' + totalTime + 'ms'; |
|
932 | if(moduleSelector) { |
|
933 | title += ' \'' + moduleSelector + '\''; |
|
934 | } |
|
935 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) { |
|
936 | console.groupCollapsed(title); |
|
937 | if(console.table) { |
|
938 | console.table(performance); |
|
939 | } |
|
940 | else { |
|
941 | $.each(performance, function(index, data) { |
|
942 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms'); |
|
943 | }); |
|
944 | } |
|
945 | console.groupEnd(); |
|
946 | } |
|
947 | performance = []; |
|
948 | } |
|
949 | }, |
|
950 | invoke: function(query, passedArguments, context) { |
|
951 | var |
|
952 | object = instance, |
|
953 | maxDepth, |
|
954 | found, |
|
955 | response |
|
956 | ; |
|
957 | passedArguments = passedArguments || queryArguments; |
|
958 | context = element || context; |
|
959 | if(typeof query == 'string' && object !== undefined) { |
|
960 | query = query.split(/[\. ]/); |
|
961 | maxDepth = query.length - 1; |
|
962 | $.each(query, function(depth, value) { |
|
963 | var camelCaseValue = (depth != maxDepth) |
|
964 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1) |
|
965 | : query |
|
966 | ; |
|
967 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) { |
|
968 | object = object[camelCaseValue]; |
|
969 | } |
|
970 | else if( object[camelCaseValue] !== undefined ) { |
|
971 | found = object[camelCaseValue]; |
|
972 | return false; |
|
973 | } |
|
974 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) { |
|
975 | object = object[value]; |
|
976 | } |
|
977 | else if( object[value] !== undefined ) { |
|
978 | found = object[value]; |
|
979 | return false; |
|
980 | } |
|
981 | else { |
|
982 | module.error(error.method, query); |
|
983 | return false; |
|
984 | } |
|
985 | }); |
|
986 | } |
|
987 | if ( $.isFunction( found ) ) { |
|
988 | response = found.apply(context, passedArguments); |
|
989 | } |
|
990 | else if(found !== undefined) { |
|
991 | response = found; |
|
992 | } |
|
993 | if($.isArray(returnedValue)) { |
|
994 | returnedValue.push(response); |
|
995 | } |
|
996 | else if(returnedValue !== undefined) { |
|
997 | returnedValue = [returnedValue, response]; |
|
998 | } |
|
999 | else if(response !== undefined) { |
|
1000 | returnedValue = response; |
|
1001 | } |
|
1002 | return found; |
|
1003 | } |
|
1004 | }; |
|
1005 | ||
1006 | if(methodInvoked) { |
|
1007 | if(instance === undefined) { |
|
1008 | module.initialize(); |
|
1009 | } |
|
1010 | module.invoke(query); |
|
1011 | } |
|
1012 | else { |
|
1013 | if(instance !== undefined) { |
|
1014 | instance.invoke('destroy'); |
|
1015 | } |
|
1016 | module.initialize(); |
|
1017 | } |
|
1018 | }) |
|
1019 | ; |
|
1020 | ||
1021 | return (returnedValue !== undefined) |
|
1022 | ? returnedValue |
|
1023 | : this |
|
1024 | ; |
|
1025 | }; |
|
1026 | ||
1027 | $.api.settings = { |
|
1028 | ||
1029 | name : 'API', |
|
1030 | namespace : 'api', |
|
1031 | ||
1032 | debug : false, |
|
1033 | verbose : false, |
|
1034 | performance : true, |
|
1035 | ||
1036 | // object containing all templates endpoints |
|
1037 | api : {}, |
|
1038 | ||
1039 | // whether to cache responses |
|
1040 | cache : true, |
|
1041 | ||
1042 | // whether new requests should abort previous requests |
|
1043 | interruptRequests : true, |
|
1044 | ||
1045 | // event binding |
|
1046 | on : 'auto', |
|
1047 | ||
1048 | // context for applying state classes |
|
1049 | stateContext : false, |
|
1050 | ||
1051 | // duration for loading state |
|
1052 | loadingDuration : 0, |
|
1053 | ||
1054 | // whether to hide errors after a period of time |
|
1055 | hideError : 'auto', |
|
1056 | ||
1057 | // duration for error state |
|
1058 | errorDuration : 2000, |
|
1059 | ||
1060 | // whether parameters should be encoded with encodeURIComponent |
|
1061 | encodeParameters : true, |
|
1062 | ||
1063 | // API action to use |
|
1064 | action : false, |
|
1065 | ||
1066 | // templated URL to use |
|
1067 | url : false, |
|
1068 | ||
1069 | // base URL to apply to all endpoints |
|
1070 | base : '', |
|
1071 | ||
1072 | // data that will |
|
1073 | urlData : {}, |
|
1074 | ||
1075 | // whether to add default data to url data |
|
1076 | defaultData : true, |
|
1077 | ||
1078 | // whether to serialize closest form |
|
1079 | serializeForm : false, |
|
1080 | ||
1081 | // how long to wait before request should occur |
|
1082 | throttle : 0, |
|
1083 | ||
1084 | // whether to throttle first request or only repeated |
|
1085 | throttleFirstRequest : true, |
|
1086 | ||
1087 | // standard ajax settings |
|
1088 | method : 'get', |
|
1089 | data : {}, |
|
1090 | dataType : 'json', |
|
1091 | ||
1092 | // mock response |
|
1093 | mockResponse : false, |
|
1094 | mockResponseAsync : false, |
|
1095 | ||
1096 | // aliases for mock |
|
1097 | response : false, |
|
1098 | responseAsync : false, |
|
1099 | ||
1100 | // callbacks before request |
|
1101 | beforeSend : function(settings) { return settings; }, |
|
1102 | beforeXHR : function(xhr) {}, |
|
1103 | onRequest : function(promise, xhr) {}, |
|
1104 | ||
1105 | // after request |
|
1106 | onResponse : false, // function(response) { }, |
|
1107 | ||
1108 | // response was successful, if JSON passed validation |
|
1109 | onSuccess : function(response, $module) {}, |
|
1110 | ||
1111 | // request finished without aborting |
|
1112 | onComplete : function(response, $module) {}, |
|
1113 | ||
1114 | // failed JSON success test |
|
1115 | onFailure : function(response, $module) {}, |
|
1116 | ||
1117 | // server error |
|
1118 | onError : function(errorMessage, $module) {}, |
|
1119 | ||
1120 | // request aborted |
|
1121 | onAbort : function(errorMessage, $module) {}, |
|
1122 | ||
1123 | successTest : false, |
|
1124 | ||
1125 | // errors |
|
1126 | error : { |
|
1127 | beforeSend : 'The before send function has aborted the request', |
|
1128 | error : 'There was an error with your request', |
|
1129 | exitConditions : 'API Request Aborted. Exit conditions met', |
|
1130 | JSONParse : 'JSON could not be parsed during error handling', |
|
1131 | legacyParameters : 'You are using legacy API success callback names', |
|
1132 | method : 'The method you called is not defined', |
|
1133 | missingAction : 'API action used but no url was defined', |
|
1134 | missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object', |
|
1135 | missingURL : 'No URL specified for api event', |
|
1136 | noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.', |
|
1137 | noStorage : 'Caching responses locally requires session storage', |
|
1138 | parseError : 'There was an error parsing your request', |
|
1139 | requiredParameter : 'Missing a required URL parameter: ', |
|
1140 | statusMessage : 'Server gave an error: ', |
|
1141 | timeout : 'Your request timed out' |
|
1142 | }, |
|
1143 | ||
1144 | regExp : { |
|
1145 | required : /\{\$*[A-z0-9]+\}/g, |
|
1146 | optional : /\{\/\$*[A-z0-9]+\}/g, |
|
1147 | }, |
|
1148 | ||
1149 | className: { |
|
1150 | loading : 'loading', |
|
1151 | error : 'error' |
|
1152 | }, |
|
1153 | ||
1154 | selector: { |
|
1155 | disabled : '.disabled', |
|
1156 | form : 'form' |
|
1157 | }, |
|
1158 | ||
1159 | metadata: { |
|
1160 | action : 'action', |
|
1161 | url : 'url' |
|
1162 | } |
|
1163 | }; |
|
1164 | ||
1165 | ||
1166 | ||
1167 | })( jQuery, window, document ); |
|
1168 |