1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | ;(function ($, window, document, undefined) {
|
12 |
|
13 | 'use strict';
|
14 |
|
15 | $.isWindow = $.isWindow || function(obj) {
|
16 | return obj != null && obj === obj.window;
|
17 | };
|
18 |
|
19 | var
|
20 | window = (typeof window != 'undefined' && window.Math == Math)
|
21 | ? window
|
22 | : (typeof self != 'undefined' && self.Math == Math)
|
23 | ? self
|
24 | : Function('return this')()
|
25 | ;
|
26 |
|
27 | $.api = $.fn.api = function(parameters) {
|
28 |
|
29 | var
|
30 |
|
31 | $allModules = $.isFunction(this)
|
32 | ? $(window)
|
33 | : $(this),
|
34 | moduleSelector = $allModules.selector || '',
|
35 | time = new Date().getTime(),
|
36 | performance = [],
|
37 |
|
38 | query = arguments[0],
|
39 | methodInvoked = (typeof query == 'string'),
|
40 | queryArguments = [].slice.call(arguments, 1),
|
41 |
|
42 | returnedValue
|
43 | ;
|
44 |
|
45 | $allModules
|
46 | .each(function() {
|
47 | var
|
48 | settings = ( $.isPlainObject(parameters) )
|
49 | ? $.extend(true, {}, $.fn.api.settings, parameters)
|
50 | : $.extend({}, $.fn.api.settings),
|
51 |
|
52 |
|
53 | namespace = settings.namespace,
|
54 | metadata = settings.metadata,
|
55 | selector = settings.selector,
|
56 | error = settings.error,
|
57 | className = settings.className,
|
58 |
|
59 |
|
60 | eventNamespace = '.' + namespace,
|
61 | moduleNamespace = 'module-' + namespace,
|
62 |
|
63 |
|
64 | $module = $(this),
|
65 | $form = $module.closest(selector.form),
|
66 |
|
67 |
|
68 | $context = (settings.stateContext)
|
69 | ? $(settings.stateContext)
|
70 | : $module,
|
71 |
|
72 |
|
73 | ajaxSettings,
|
74 | requestSettings,
|
75 | url,
|
76 | data,
|
77 | requestStartTime,
|
78 |
|
79 |
|
80 | element = this,
|
81 | context = $context[0],
|
82 | instance = $module.data(moduleNamespace),
|
83 | module
|
84 | ;
|
85 |
|
86 | module = {
|
87 |
|
88 | initialize: function() {
|
89 | if(!methodInvoked) {
|
90 | module.bind.events();
|
91 | }
|
92 | module.instantiate();
|
93 | },
|
94 |
|
95 | instantiate: function() {
|
96 | module.verbose('Storing instance of module', module);
|
97 | instance = module;
|
98 | $module
|
99 | .data(moduleNamespace, instance)
|
100 | ;
|
101 | },
|
102 |
|
103 | destroy: function() {
|
104 | module.verbose('Destroying previous module for', element);
|
105 | $module
|
106 | .removeData(moduleNamespace)
|
107 | .off(eventNamespace)
|
108 | ;
|
109 | },
|
110 |
|
111 | bind: {
|
112 | events: function() {
|
113 | var
|
114 | triggerEvent = module.get.event()
|
115 | ;
|
116 | if( triggerEvent ) {
|
117 | module.verbose('Attaching API events to element', triggerEvent);
|
118 | $module
|
119 | .on(triggerEvent + eventNamespace, module.event.trigger)
|
120 | ;
|
121 | }
|
122 | else if(settings.on == 'now') {
|
123 | module.debug('Querying API endpoint immediately');
|
124 | module.query();
|
125 | }
|
126 | }
|
127 | },
|
128 |
|
129 | decode: {
|
130 | json: function(response) {
|
131 | if(response !== undefined && typeof response == 'string') {
|
132 | try {
|
133 | response = JSON.parse(response);
|
134 | }
|
135 | catch(e) {
|
136 |
|
137 | }
|
138 | }
|
139 | return response;
|
140 | }
|
141 | },
|
142 |
|
143 | read: {
|
144 | cachedResponse: function(url) {
|
145 | var
|
146 | response
|
147 | ;
|
148 | if(window.Storage === undefined) {
|
149 | module.error(error.noStorage);
|
150 | return;
|
151 | }
|
152 | response = sessionStorage.getItem(url);
|
153 | module.debug('Using cached response', url, response);
|
154 | response = module.decode.json(response);
|
155 | return response;
|
156 | }
|
157 | },
|
158 | write: {
|
159 | cachedResponse: function(url, response) {
|
160 | if(response && response === '') {
|
161 | module.debug('Response empty, not caching', response);
|
162 | return;
|
163 | }
|
164 | if(window.Storage === undefined) {
|
165 | module.error(error.noStorage);
|
166 | return;
|
167 | }
|
168 | if( $.isPlainObject(response) ) {
|
169 | response = JSON.stringify(response);
|
170 | }
|
171 | sessionStorage.setItem(url, response);
|
172 | module.verbose('Storing cached response for url', url, response);
|
173 | }
|
174 | },
|
175 |
|
176 | query: function() {
|
177 |
|
178 | if(module.is.disabled()) {
|
179 | module.debug('Element is disabled API request aborted');
|
180 | return;
|
181 | }
|
182 |
|
183 | if(module.is.loading()) {
|
184 | if(settings.interruptRequests) {
|
185 | module.debug('Interrupting previous request');
|
186 | module.abort();
|
187 | }
|
188 | else {
|
189 | module.debug('Cancelling request, previous request is still pending');
|
190 | return;
|
191 | }
|
192 | }
|
193 |
|
194 |
|
195 | if(settings.defaultData) {
|
196 | $.extend(true, settings.urlData, module.get.defaultData());
|
197 | }
|
198 |
|
199 |
|
200 | if(settings.serializeForm) {
|
201 | settings.data = module.add.formData(settings.data);
|
202 | }
|
203 |
|
204 |
|
205 | requestSettings = module.get.settings();
|
206 |
|
207 |
|
208 | if(requestSettings === false) {
|
209 | module.cancelled = true;
|
210 | module.error(error.beforeSend);
|
211 | return;
|
212 | }
|
213 | else {
|
214 | module.cancelled = false;
|
215 | }
|
216 |
|
217 |
|
218 | url = module.get.templatedURL();
|
219 |
|
220 | if(!url && !module.is.mocked()) {
|
221 | module.error(error.missingURL);
|
222 | return;
|
223 | }
|
224 |
|
225 |
|
226 | url = module.add.urlData( url );
|
227 |
|
228 | if( !url && !module.is.mocked()) {
|
229 | return;
|
230 | }
|
231 |
|
232 | requestSettings.url = settings.base + url;
|
233 |
|
234 |
|
235 | ajaxSettings = $.extend(true, {}, settings, {
|
236 | type : settings.method || settings.type,
|
237 | data : data,
|
238 | url : settings.base + url,
|
239 | beforeSend : settings.beforeXHR,
|
240 | success : function() {},
|
241 | failure : function() {},
|
242 | complete : function() {}
|
243 | });
|
244 |
|
245 | module.debug('Querying URL', ajaxSettings.url);
|
246 | module.verbose('Using AJAX settings', ajaxSettings);
|
247 | if(settings.cache === 'local' && module.read.cachedResponse(url)) {
|
248 | module.debug('Response returned from local cache');
|
249 | module.request = module.create.request();
|
250 | module.request.resolveWith(context, [ module.read.cachedResponse(url) ]);
|
251 | return;
|
252 | }
|
253 |
|
254 | if( !settings.throttle ) {
|
255 | module.debug('Sending request', data, ajaxSettings.method);
|
256 | module.send.request();
|
257 | }
|
258 | else {
|
259 | if(!settings.throttleFirstRequest && !module.timer) {
|
260 | module.debug('Sending request', data, ajaxSettings.method);
|
261 | module.send.request();
|
262 | module.timer = setTimeout(function(){}, settings.throttle);
|
263 | }
|
264 | else {
|
265 | module.debug('Throttling request', settings.throttle);
|
266 | clearTimeout(module.timer);
|
267 | module.timer = setTimeout(function() {
|
268 | if(module.timer) {
|
269 | delete module.timer;
|
270 | }
|
271 | module.debug('Sending throttled request', data, ajaxSettings.method);
|
272 | module.send.request();
|
273 | }, settings.throttle);
|
274 | }
|
275 | }
|
276 |
|
277 | },
|
278 |
|
279 | should: {
|
280 | removeError: function() {
|
281 | return ( settings.hideError === true || (settings.hideError === 'auto' && !module.is.form()) );
|
282 | }
|
283 | },
|
284 |
|
285 | is: {
|
286 | disabled: function() {
|
287 | return ($module.filter(selector.disabled).length > 0);
|
288 | },
|
289 | expectingJSON: function() {
|
290 | return settings.dataType === 'json' || settings.dataType === 'jsonp';
|
291 | },
|
292 | form: function() {
|
293 | return $module.is('form') || $context.is('form');
|
294 | },
|
295 | mocked: function() {
|
296 | return (settings.mockResponse || settings.mockResponseAsync || settings.response || settings.responseAsync);
|
297 | },
|
298 | input: function() {
|
299 | return $module.is('input');
|
300 | },
|
301 | loading: function() {
|
302 | return (module.request)
|
303 | ? (module.request.state() == 'pending')
|
304 | : false
|
305 | ;
|
306 | },
|
307 | abortedRequest: function(xhr) {
|
308 | if(xhr && xhr.readyState !== undefined && xhr.readyState === 0) {
|
309 | module.verbose('XHR request determined to be aborted');
|
310 | return true;
|
311 | }
|
312 | else {
|
313 | module.verbose('XHR request was not aborted');
|
314 | return false;
|
315 | }
|
316 | },
|
317 | validResponse: function(response) {
|
318 | if( (!module.is.expectingJSON()) || !$.isFunction(settings.successTest) ) {
|
319 | module.verbose('Response is not JSON, skipping validation', settings.successTest, response);
|
320 | return true;
|
321 | }
|
322 | module.debug('Checking JSON returned success', settings.successTest, response);
|
323 | if( settings.successTest(response) ) {
|
324 | module.debug('Response passed success test', response);
|
325 | return true;
|
326 | }
|
327 | else {
|
328 | module.debug('Response failed success test', response);
|
329 | return false;
|
330 | }
|
331 | }
|
332 | },
|
333 |
|
334 | was: {
|
335 | cancelled: function() {
|
336 | return (module.cancelled || false);
|
337 | },
|
338 | succesful: function() {
|
339 | return (module.request && module.request.state() == 'resolved');
|
340 | },
|
341 | failure: function() {
|
342 | return (module.request && module.request.state() == 'rejected');
|
343 | },
|
344 | complete: function() {
|
345 | return (module.request && (module.request.state() == 'resolved' || module.request.state() == 'rejected') );
|
346 | }
|
347 | },
|
348 |
|
349 | add: {
|
350 | urlData: function(url, urlData) {
|
351 | var
|
352 | requiredVariables,
|
353 | optionalVariables
|
354 | ;
|
355 | if(url) {
|
356 | requiredVariables = url.match(settings.regExp.required);
|
357 | optionalVariables = url.match(settings.regExp.optional);
|
358 | urlData = urlData || settings.urlData;
|
359 | if(requiredVariables) {
|
360 | module.debug('Looking for required URL variables', requiredVariables);
|
361 | $.each(requiredVariables, function(index, templatedString) {
|
362 | var
|
363 |
|
364 | variable = (templatedString.indexOf('$') !== -1)
|
365 | ? templatedString.substr(2, templatedString.length - 3)
|
366 | : templatedString.substr(1, templatedString.length - 2),
|
367 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
|
368 | ? urlData[variable]
|
369 | : ($module.data(variable) !== undefined)
|
370 | ? $module.data(variable)
|
371 | : ($context.data(variable) !== undefined)
|
372 | ? $context.data(variable)
|
373 | : urlData[variable]
|
374 | ;
|
375 |
|
376 | if(value === undefined) {
|
377 | module.error(error.requiredParameter, variable, url);
|
378 | url = false;
|
379 | return false;
|
380 | }
|
381 | else {
|
382 | module.verbose('Found required variable', variable, value);
|
383 | value = (settings.encodeParameters)
|
384 | ? module.get.urlEncodedValue(value)
|
385 | : value
|
386 | ;
|
387 | url = url.replace(templatedString, value);
|
388 | }
|
389 | });
|
390 | }
|
391 | if(optionalVariables) {
|
392 | module.debug('Looking for optional URL variables', requiredVariables);
|
393 | $.each(optionalVariables, function(index, templatedString) {
|
394 | var
|
395 |
|
396 | variable = (templatedString.indexOf('$') !== -1)
|
397 | ? templatedString.substr(3, templatedString.length - 4)
|
398 | : templatedString.substr(2, templatedString.length - 3),
|
399 | value = ($.isPlainObject(urlData) && urlData[variable] !== undefined)
|
400 | ? urlData[variable]
|
401 | : ($module.data(variable) !== undefined)
|
402 | ? $module.data(variable)
|
403 | : ($context.data(variable) !== undefined)
|
404 | ? $context.data(variable)
|
405 | : urlData[variable]
|
406 | ;
|
407 |
|
408 | if(value !== undefined) {
|
409 | module.verbose('Optional variable Found', variable, value);
|
410 | url = url.replace(templatedString, value);
|
411 | }
|
412 | else {
|
413 | module.verbose('Optional variable not found', variable);
|
414 |
|
415 | if(url.indexOf('/' + templatedString) !== -1) {
|
416 | url = url.replace('/' + templatedString, '');
|
417 | }
|
418 | else {
|
419 | url = url.replace(templatedString, '');
|
420 | }
|
421 | }
|
422 | });
|
423 | }
|
424 | }
|
425 | return url;
|
426 | },
|
427 | formData: function(data) {
|
428 | var
|
429 | canSerialize = ($.fn.serializeObject !== undefined),
|
430 | formData = (canSerialize)
|
431 | ? $form.serializeObject()
|
432 | : $form.serialize(),
|
433 | hasOtherData
|
434 | ;
|
435 | data = data || settings.data;
|
436 | hasOtherData = $.isPlainObject(data);
|
437 |
|
438 | if(hasOtherData) {
|
439 | if(canSerialize) {
|
440 | module.debug('Extending existing data with form data', data, formData);
|
441 | data = $.extend(true, {}, data, formData);
|
442 | }
|
443 | else {
|
444 | module.error(error.missingSerialize);
|
445 | module.debug('Cant extend data. Replacing data with form data', data, formData);
|
446 | data = formData;
|
447 | }
|
448 | }
|
449 | else {
|
450 | module.debug('Adding form data', formData);
|
451 | data = formData;
|
452 | }
|
453 | return data;
|
454 | }
|
455 | },
|
456 |
|
457 | send: {
|
458 | request: function() {
|
459 | module.set.loading();
|
460 | module.request = module.create.request();
|
461 | if( module.is.mocked() ) {
|
462 | module.mockedXHR = module.create.mockedXHR();
|
463 | }
|
464 | else {
|
465 | module.xhr = module.create.xhr();
|
466 | }
|
467 | settings.onRequest.call(context, module.request, module.xhr);
|
468 | }
|
469 | },
|
470 |
|
471 | event: {
|
472 | trigger: function(event) {
|
473 | module.query();
|
474 | if(event.type == 'submit' || event.type == 'click') {
|
475 | event.preventDefault();
|
476 | }
|
477 | },
|
478 | xhr: {
|
479 | always: function() {
|
480 |
|
481 | },
|
482 | done: function(response, textStatus, xhr) {
|
483 | var
|
484 | context = this,
|
485 | elapsedTime = (new Date().getTime() - requestStartTime),
|
486 | timeLeft = (settings.loadingDuration - elapsedTime),
|
487 | translatedResponse = ( $.isFunction(settings.onResponse) )
|
488 | ? module.is.expectingJSON() && !settings.rawResponse
|
489 | ? settings.onResponse.call(context, $.extend(true, {}, response))
|
490 | : settings.onResponse.call(context, response)
|
491 | : false
|
492 | ;
|
493 | timeLeft = (timeLeft > 0)
|
494 | ? timeLeft
|
495 | : 0
|
496 | ;
|
497 | if(translatedResponse) {
|
498 | module.debug('Modified API response in onResponse callback', settings.onResponse, translatedResponse, response);
|
499 | response = translatedResponse;
|
500 | }
|
501 | if(timeLeft > 0) {
|
502 | module.debug('Response completed early delaying state change by', timeLeft);
|
503 | }
|
504 | setTimeout(function() {
|
505 | if( module.is.validResponse(response) ) {
|
506 | module.request.resolveWith(context, [response, xhr]);
|
507 | }
|
508 | else {
|
509 | module.request.rejectWith(context, [xhr, 'invalid']);
|
510 | }
|
511 | }, timeLeft);
|
512 | },
|
513 | fail: function(xhr, status, httpMessage) {
|
514 | var
|
515 | context = this,
|
516 | elapsedTime = (new Date().getTime() - requestStartTime),
|
517 | timeLeft = (settings.loadingDuration - elapsedTime)
|
518 | ;
|
519 | timeLeft = (timeLeft > 0)
|
520 | ? timeLeft
|
521 | : 0
|
522 | ;
|
523 | if(timeLeft > 0) {
|
524 | module.debug('Response completed early delaying state change by', timeLeft);
|
525 | }
|
526 | setTimeout(function() {
|
527 | if( module.is.abortedRequest(xhr) ) {
|
528 | module.request.rejectWith(context, [xhr, 'aborted', httpMessage]);
|
529 | }
|
530 | else {
|
531 | module.request.rejectWith(context, [xhr, 'error', status, httpMessage]);
|
532 | }
|
533 | }, timeLeft);
|
534 | }
|
535 | },
|
536 | request: {
|
537 | done: function(response, xhr) {
|
538 | module.debug('Successful API Response', response);
|
539 | if(settings.cache === 'local' && url) {
|
540 | module.write.cachedResponse(url, response);
|
541 | module.debug('Saving server response locally', module.cache);
|
542 | }
|
543 | settings.onSuccess.call(context, response, $module, xhr);
|
544 | },
|
545 | complete: function(firstParameter, secondParameter) {
|
546 | var
|
547 | xhr,
|
548 | response
|
549 | ;
|
550 |
|
551 | if( module.was.succesful() ) {
|
552 | response = firstParameter;
|
553 | xhr = secondParameter;
|
554 | }
|
555 | else {
|
556 | xhr = firstParameter;
|
557 | response = module.get.responseFromXHR(xhr);
|
558 | }
|
559 | module.remove.loading();
|
560 | settings.onComplete.call(context, response, $module, xhr);
|
561 | },
|
562 | fail: function(xhr, status, httpMessage) {
|
563 | var
|
564 |
|
565 | response = module.get.responseFromXHR(xhr),
|
566 | errorMessage = module.get.errorFromRequest(response, status, httpMessage)
|
567 | ;
|
568 | if(status == 'aborted') {
|
569 | module.debug('XHR Aborted (Most likely caused by page navigation or CORS Policy)', status, httpMessage);
|
570 | settings.onAbort.call(context, status, $module, xhr);
|
571 | return true;
|
572 | }
|
573 | else if(status == 'invalid') {
|
574 | module.debug('JSON did not pass success test. A server-side error has most likely occurred', response);
|
575 | }
|
576 | else if(status == 'error') {
|
577 | if(xhr !== undefined) {
|
578 | module.debug('XHR produced a server error', status, httpMessage);
|
579 |
|
580 | if( (xhr.status < 200 || xhr.status >= 300) && httpMessage !== undefined && httpMessage !== '') {
|
581 | module.error(error.statusMessage + httpMessage, ajaxSettings.url);
|
582 | }
|
583 | settings.onError.call(context, errorMessage, $module, xhr);
|
584 | }
|
585 | }
|
586 |
|
587 | if(settings.errorDuration && status !== 'aborted') {
|
588 | module.debug('Adding error state');
|
589 | module.set.error();
|
590 | if( module.should.removeError() ) {
|
591 | setTimeout(module.remove.error, settings.errorDuration);
|
592 | }
|
593 | }
|
594 | module.debug('API Request failed', errorMessage, xhr);
|
595 | settings.onFailure.call(context, response, $module, xhr);
|
596 | }
|
597 | }
|
598 | },
|
599 |
|
600 | create: {
|
601 |
|
602 | request: function() {
|
603 |
|
604 | return $.Deferred()
|
605 | .always(module.event.request.complete)
|
606 | .done(module.event.request.done)
|
607 | .fail(module.event.request.fail)
|
608 | ;
|
609 | },
|
610 |
|
611 | mockedXHR: function () {
|
612 | var
|
613 |
|
614 | textStatus = false,
|
615 | status = false,
|
616 | httpMessage = false,
|
617 | responder = settings.mockResponse || settings.response,
|
618 | asyncResponder = settings.mockResponseAsync || settings.responseAsync,
|
619 | asyncCallback,
|
620 | response,
|
621 | mockedXHR
|
622 | ;
|
623 |
|
624 | mockedXHR = $.Deferred()
|
625 | .always(module.event.xhr.complete)
|
626 | .done(module.event.xhr.done)
|
627 | .fail(module.event.xhr.fail)
|
628 | ;
|
629 |
|
630 | if(responder) {
|
631 | if( $.isFunction(responder) ) {
|
632 | module.debug('Using specified synchronous callback', responder);
|
633 | response = responder.call(context, requestSettings);
|
634 | }
|
635 | else {
|
636 | module.debug('Using settings specified response', responder);
|
637 | response = responder;
|
638 | }
|
639 |
|
640 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
|
641 | }
|
642 | else if( $.isFunction(asyncResponder) ) {
|
643 | asyncCallback = function(response) {
|
644 | module.debug('Async callback returned response', response);
|
645 |
|
646 | if(response) {
|
647 | mockedXHR.resolveWith(context, [ response, textStatus, { responseText: response }]);
|
648 | }
|
649 | else {
|
650 | mockedXHR.rejectWith(context, [{ responseText: response }, status, httpMessage]);
|
651 | }
|
652 | };
|
653 | module.debug('Using specified async response callback', asyncResponder);
|
654 | asyncResponder.call(context, requestSettings, asyncCallback);
|
655 | }
|
656 | return mockedXHR;
|
657 | },
|
658 |
|
659 | xhr: function() {
|
660 | var
|
661 | xhr
|
662 | ;
|
663 |
|
664 | xhr = $.ajax(ajaxSettings)
|
665 | .always(module.event.xhr.always)
|
666 | .done(module.event.xhr.done)
|
667 | .fail(module.event.xhr.fail)
|
668 | ;
|
669 | module.verbose('Created server request', xhr, ajaxSettings);
|
670 | return xhr;
|
671 | }
|
672 | },
|
673 |
|
674 | set: {
|
675 | error: function() {
|
676 | module.verbose('Adding error state to element', $context);
|
677 | $context.addClass(className.error);
|
678 | },
|
679 | loading: function() {
|
680 | module.verbose('Adding loading state to element', $context);
|
681 | $context.addClass(className.loading);
|
682 | requestStartTime = new Date().getTime();
|
683 | }
|
684 | },
|
685 |
|
686 | remove: {
|
687 | error: function() {
|
688 | module.verbose('Removing error state from element', $context);
|
689 | $context.removeClass(className.error);
|
690 | },
|
691 | loading: function() {
|
692 | module.verbose('Removing loading state from element', $context);
|
693 | $context.removeClass(className.loading);
|
694 | }
|
695 | },
|
696 |
|
697 | get: {
|
698 | responseFromXHR: function(xhr) {
|
699 | return $.isPlainObject(xhr)
|
700 | ? (module.is.expectingJSON())
|
701 | ? module.decode.json(xhr.responseText)
|
702 | : xhr.responseText
|
703 | : false
|
704 | ;
|
705 | },
|
706 | errorFromRequest: function(response, status, httpMessage) {
|
707 | return ($.isPlainObject(response) && response.error !== undefined)
|
708 | ? response.error
|
709 | : (settings.error[status] !== undefined)
|
710 | ? settings.error[status]
|
711 | : httpMessage
|
712 | ;
|
713 | },
|
714 | request: function() {
|
715 | return module.request || false;
|
716 | },
|
717 | xhr: function() {
|
718 | return module.xhr || false;
|
719 | },
|
720 | settings: function() {
|
721 | var
|
722 | runSettings
|
723 | ;
|
724 | runSettings = settings.beforeSend.call(context, settings);
|
725 | if(runSettings) {
|
726 | if(runSettings.success !== undefined) {
|
727 | module.debug('Legacy success callback detected', runSettings);
|
728 | module.error(error.legacyParameters, runSettings.success);
|
729 | runSettings.onSuccess = runSettings.success;
|
730 | }
|
731 | if(runSettings.failure !== undefined) {
|
732 | module.debug('Legacy failure callback detected', runSettings);
|
733 | module.error(error.legacyParameters, runSettings.failure);
|
734 | runSettings.onFailure = runSettings.failure;
|
735 | }
|
736 | if(runSettings.complete !== undefined) {
|
737 | module.debug('Legacy complete callback detected', runSettings);
|
738 | module.error(error.legacyParameters, runSettings.complete);
|
739 | runSettings.onComplete = runSettings.complete;
|
740 | }
|
741 | }
|
742 | if(runSettings === undefined) {
|
743 | module.error(error.noReturnedValue);
|
744 | }
|
745 | if(runSettings === false) {
|
746 | return runSettings;
|
747 | }
|
748 | return (runSettings !== undefined)
|
749 | ? $.extend(true, {}, runSettings)
|
750 | : $.extend(true, {}, settings)
|
751 | ;
|
752 | },
|
753 | urlEncodedValue: function(value) {
|
754 | var
|
755 | decodedValue = window.decodeURIComponent(value),
|
756 | encodedValue = window.encodeURIComponent(value),
|
757 | alreadyEncoded = (decodedValue !== value)
|
758 | ;
|
759 | if(alreadyEncoded) {
|
760 | module.debug('URL value is already encoded, avoiding double encoding', value);
|
761 | return value;
|
762 | }
|
763 | module.verbose('Encoding value using encodeURIComponent', value, encodedValue);
|
764 | return encodedValue;
|
765 | },
|
766 | defaultData: function() {
|
767 | var
|
768 | data = {}
|
769 | ;
|
770 | if( !$.isWindow(element) ) {
|
771 | if( module.is.input() ) {
|
772 | data.value = $module.val();
|
773 | }
|
774 | else if( module.is.form() ) {
|
775 |
|
776 | }
|
777 | else {
|
778 | data.text = $module.text();
|
779 | }
|
780 | }
|
781 | return data;
|
782 | },
|
783 | event: function() {
|
784 | if( $.isWindow(element) || settings.on == 'now' ) {
|
785 | module.debug('API called without element, no events attached');
|
786 | return false;
|
787 | }
|
788 | else if(settings.on == 'auto') {
|
789 | if( $module.is('input') ) {
|
790 | return (element.oninput !== undefined)
|
791 | ? 'input'
|
792 | : (element.onpropertychange !== undefined)
|
793 | ? 'propertychange'
|
794 | : 'keyup'
|
795 | ;
|
796 | }
|
797 | else if( $module.is('form') ) {
|
798 | return 'submit';
|
799 | }
|
800 | else {
|
801 | return 'click';
|
802 | }
|
803 | }
|
804 | else {
|
805 | return settings.on;
|
806 | }
|
807 | },
|
808 | templatedURL: function(action) {
|
809 | action = action || $module.data(metadata.action) || settings.action || false;
|
810 | url = $module.data(metadata.url) || settings.url || false;
|
811 | if(url) {
|
812 | module.debug('Using specified url', url);
|
813 | return url;
|
814 | }
|
815 | if(action) {
|
816 | module.debug('Looking up url for action', action, settings.api);
|
817 | if(settings.api[action] === undefined && !module.is.mocked()) {
|
818 | module.error(error.missingAction, settings.action, settings.api);
|
819 | return;
|
820 | }
|
821 | url = settings.api[action];
|
822 | }
|
823 | else if( module.is.form() ) {
|
824 | url = $module.attr('action') || $context.attr('action') || false;
|
825 | module.debug('No url or action specified, defaulting to form action', url);
|
826 | }
|
827 | return url;
|
828 | }
|
829 | },
|
830 |
|
831 | abort: function() {
|
832 | var
|
833 | xhr = module.get.xhr()
|
834 | ;
|
835 | if( xhr && xhr.state() !== 'resolved') {
|
836 | module.debug('Cancelling API request');
|
837 | xhr.abort();
|
838 | }
|
839 | },
|
840 |
|
841 |
|
842 | reset: function() {
|
843 | module.remove.error();
|
844 | module.remove.loading();
|
845 | },
|
846 |
|
847 | setting: function(name, value) {
|
848 | module.debug('Changing setting', name, value);
|
849 | if( $.isPlainObject(name) ) {
|
850 | $.extend(true, settings, name);
|
851 | }
|
852 | else if(value !== undefined) {
|
853 | if($.isPlainObject(settings[name])) {
|
854 | $.extend(true, settings[name], value);
|
855 | }
|
856 | else {
|
857 | settings[name] = value;
|
858 | }
|
859 | }
|
860 | else {
|
861 | return settings[name];
|
862 | }
|
863 | },
|
864 | internal: function(name, value) {
|
865 | if( $.isPlainObject(name) ) {
|
866 | $.extend(true, module, name);
|
867 | }
|
868 | else if(value !== undefined) {
|
869 | module[name] = value;
|
870 | }
|
871 | else {
|
872 | return module[name];
|
873 | }
|
874 | },
|
875 | debug: function() {
|
876 | if(!settings.silent && settings.debug) {
|
877 | if(settings.performance) {
|
878 | module.performance.log(arguments);
|
879 | }
|
880 | else {
|
881 | module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
882 | module.debug.apply(console, arguments);
|
883 | }
|
884 | }
|
885 | },
|
886 | verbose: function() {
|
887 | if(!settings.silent && settings.verbose && settings.debug) {
|
888 | if(settings.performance) {
|
889 | module.performance.log(arguments);
|
890 | }
|
891 | else {
|
892 | module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
|
893 | module.verbose.apply(console, arguments);
|
894 | }
|
895 | }
|
896 | },
|
897 | error: function() {
|
898 | if(!settings.silent) {
|
899 | module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
|
900 | module.error.apply(console, arguments);
|
901 | }
|
902 | },
|
903 | performance: {
|
904 | log: function(message) {
|
905 | var
|
906 | currentTime,
|
907 | executionTime,
|
908 | previousTime
|
909 | ;
|
910 | if(settings.performance) {
|
911 | currentTime = new Date().getTime();
|
912 | previousTime = time || currentTime;
|
913 | executionTime = currentTime - previousTime;
|
914 | time = currentTime;
|
915 | performance.push({
|
916 | 'Name' : message[0],
|
917 | 'Arguments' : [].slice.call(message, 1) || '',
|
918 |
|
919 | 'Execution Time' : executionTime
|
920 | });
|
921 | }
|
922 | clearTimeout(module.performance.timer);
|
923 | module.performance.timer = setTimeout(module.performance.display, 500);
|
924 | },
|
925 | display: function() {
|
926 | var
|
927 | title = settings.name + ':',
|
928 | totalTime = 0
|
929 | ;
|
930 | time = false;
|
931 | clearTimeout(module.performance.timer);
|
932 | $.each(performance, function(index, data) {
|
933 | totalTime += data['Execution Time'];
|
934 | });
|
935 | title += ' ' + totalTime + 'ms';
|
936 | if(moduleSelector) {
|
937 | title += ' \'' + moduleSelector + '\'';
|
938 | }
|
939 | if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
|
940 | console.groupCollapsed(title);
|
941 | if(console.table) {
|
942 | console.table(performance);
|
943 | }
|
944 | else {
|
945 | $.each(performance, function(index, data) {
|
946 | console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
|
947 | });
|
948 | }
|
949 | console.groupEnd();
|
950 | }
|
951 | performance = [];
|
952 | }
|
953 | },
|
954 | invoke: function(query, passedArguments, context) {
|
955 | var
|
956 | object = instance,
|
957 | maxDepth,
|
958 | found,
|
959 | response
|
960 | ;
|
961 | passedArguments = passedArguments || queryArguments;
|
962 | context = element || context;
|
963 | if(typeof query == 'string' && object !== undefined) {
|
964 | query = query.split(/[\. ]/);
|
965 | maxDepth = query.length - 1;
|
966 | $.each(query, function(depth, value) {
|
967 | var camelCaseValue = (depth != maxDepth)
|
968 | ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
|
969 | : query
|
970 | ;
|
971 | if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
|
972 | object = object[camelCaseValue];
|
973 | }
|
974 | else if( object[camelCaseValue] !== undefined ) {
|
975 | found = object[camelCaseValue];
|
976 | return false;
|
977 | }
|
978 | else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
|
979 | object = object[value];
|
980 | }
|
981 | else if( object[value] !== undefined ) {
|
982 | found = object[value];
|
983 | return false;
|
984 | }
|
985 | else {
|
986 | module.error(error.method, query);
|
987 | return false;
|
988 | }
|
989 | });
|
990 | }
|
991 | if ( $.isFunction( found ) ) {
|
992 | response = found.apply(context, passedArguments);
|
993 | }
|
994 | else if(found !== undefined) {
|
995 | response = found;
|
996 | }
|
997 | if(Array.isArray(returnedValue)) {
|
998 | returnedValue.push(response);
|
999 | }
|
1000 | else if(returnedValue !== undefined) {
|
1001 | returnedValue = [returnedValue, response];
|
1002 | }
|
1003 | else if(response !== undefined) {
|
1004 | returnedValue = response;
|
1005 | }
|
1006 | return found;
|
1007 | }
|
1008 | };
|
1009 |
|
1010 | if(methodInvoked) {
|
1011 | if(instance === undefined) {
|
1012 | module.initialize();
|
1013 | }
|
1014 | module.invoke(query);
|
1015 | }
|
1016 | else {
|
1017 | if(instance !== undefined) {
|
1018 | instance.invoke('destroy');
|
1019 | }
|
1020 | module.initialize();
|
1021 | }
|
1022 | })
|
1023 | ;
|
1024 |
|
1025 | return (returnedValue !== undefined)
|
1026 | ? returnedValue
|
1027 | : this
|
1028 | ;
|
1029 | };
|
1030 |
|
1031 | $.api.settings = {
|
1032 |
|
1033 | name : 'API',
|
1034 | namespace : 'api',
|
1035 |
|
1036 | debug : false,
|
1037 | verbose : false,
|
1038 | performance : true,
|
1039 |
|
1040 |
|
1041 | api : {},
|
1042 |
|
1043 |
|
1044 | cache : true,
|
1045 |
|
1046 |
|
1047 | interruptRequests : true,
|
1048 |
|
1049 |
|
1050 | on : 'auto',
|
1051 |
|
1052 |
|
1053 | stateContext : false,
|
1054 |
|
1055 |
|
1056 | loadingDuration : 0,
|
1057 |
|
1058 |
|
1059 | hideError : 'auto',
|
1060 |
|
1061 |
|
1062 | errorDuration : 2000,
|
1063 |
|
1064 |
|
1065 | encodeParameters : true,
|
1066 |
|
1067 |
|
1068 | action : false,
|
1069 |
|
1070 |
|
1071 | url : false,
|
1072 |
|
1073 |
|
1074 | base : '',
|
1075 |
|
1076 |
|
1077 | urlData : {},
|
1078 |
|
1079 |
|
1080 | defaultData : true,
|
1081 |
|
1082 |
|
1083 | serializeForm : false,
|
1084 |
|
1085 |
|
1086 | throttle : 0,
|
1087 |
|
1088 |
|
1089 | throttleFirstRequest : true,
|
1090 |
|
1091 |
|
1092 | method : 'get',
|
1093 | data : {},
|
1094 | dataType : 'json',
|
1095 |
|
1096 |
|
1097 | mockResponse : false,
|
1098 | mockResponseAsync : false,
|
1099 |
|
1100 |
|
1101 | response : false,
|
1102 | responseAsync : false,
|
1103 |
|
1104 |
|
1105 | rawResponse : false,
|
1106 |
|
1107 |
|
1108 | beforeSend : function(settings) { return settings; },
|
1109 | beforeXHR : function(xhr) {},
|
1110 | onRequest : function(promise, xhr) {},
|
1111 |
|
1112 |
|
1113 | onResponse : false,
|
1114 |
|
1115 |
|
1116 | onSuccess : function(response, $module) {},
|
1117 |
|
1118 |
|
1119 | onComplete : function(response, $module) {},
|
1120 |
|
1121 |
|
1122 | onFailure : function(response, $module) {},
|
1123 |
|
1124 |
|
1125 | onError : function(errorMessage, $module) {},
|
1126 |
|
1127 |
|
1128 | onAbort : function(errorMessage, $module) {},
|
1129 |
|
1130 | successTest : false,
|
1131 |
|
1132 |
|
1133 | error : {
|
1134 | beforeSend : 'The before send function has aborted the request',
|
1135 | error : 'There was an error with your request',
|
1136 | exitConditions : 'API Request Aborted. Exit conditions met',
|
1137 | JSONParse : 'JSON could not be parsed during error handling',
|
1138 | legacyParameters : 'You are using legacy API success callback names',
|
1139 | method : 'The method you called is not defined',
|
1140 | missingAction : 'API action used but no url was defined',
|
1141 | missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object',
|
1142 | missingURL : 'No URL specified for api event',
|
1143 | noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.',
|
1144 | noStorage : 'Caching responses locally requires session storage',
|
1145 | parseError : 'There was an error parsing your request',
|
1146 | requiredParameter : 'Missing a required URL parameter: ',
|
1147 | statusMessage : 'Server gave an error: ',
|
1148 | timeout : 'Your request timed out'
|
1149 | },
|
1150 |
|
1151 | regExp : {
|
1152 | required : /\{\$*[A-z0-9]+\}/g,
|
1153 | optional : /\{\/\$*[A-z0-9]+\}/g,
|
1154 | },
|
1155 |
|
1156 | className: {
|
1157 | loading : 'loading',
|
1158 | error : 'error'
|
1159 | },
|
1160 |
|
1161 | selector: {
|
1162 | disabled : '.disabled',
|
1163 | form : 'form'
|
1164 | },
|
1165 |
|
1166 | metadata: {
|
1167 | action : 'action',
|
1168 | url : 'url'
|
1169 | }
|
1170 | };
|
1171 |
|
1172 |
|
1173 |
|
1174 | })( jQuery, window, document );
|