UNPKG

40 kBJavaScriptView Raw
1/*!
2 * # Semantic UI - API
3 * http://github.com/semantic-org/semantic-ui/
4 *
5 *
6 * Released under the MIT license
7 * http://opensource.org/licenses/MIT
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
19var
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 // use window context if none specified
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 // internal aliases
53 namespace = settings.namespace,
54 metadata = settings.metadata,
55 selector = settings.selector,
56 error = settings.error,
57 className = settings.className,
58
59 // define namespaces for modules
60 eventNamespace = '.' + namespace,
61 moduleNamespace = 'module-' + namespace,
62
63 // element that creates request
64 $module = $(this),
65 $form = $module.closest(selector.form),
66
67 // context used for state
68 $context = (settings.stateContext)
69 ? $(settings.stateContext)
70 : $module,
71
72 // request details
73 ajaxSettings,
74 requestSettings,
75 url,
76 data,
77 requestStartTime,
78
79 // standard module
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 // isnt json string
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 // pass element metadata to url (value, text)
195 if(settings.defaultData) {
196 $.extend(true, settings.urlData, module.get.defaultData());
197 }
198
199 // Add form content
200 if(settings.serializeForm) {
201 settings.data = module.add.formData(settings.data);
202 }
203
204 // call beforesend and get any settings changes
205 requestSettings = module.get.settings();
206
207 // check if before send cancelled request
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 // get url
218 url = module.get.templatedURL();
219
220 if(!url && !module.is.mocked()) {
221 module.error(error.missingURL);
222 return;
223 }
224
225 // replace variables
226 url = module.add.urlData( url );
227 // missing url parameters
228 if( !url && !module.is.mocked()) {
229 return;
230 }
231
232 requestSettings.url = settings.base + url;
233
234 // look for jQuery ajax parameters in settings
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 // allow legacy {$var} style
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 // remove value
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 // allow legacy {/$var} style
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 // optional replacement
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 // remove preceding slash if set
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 // nothing special
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 // have to guess callback parameters based on request success
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 // pull response from xhr if available
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 // make sure we have an error to display to console
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 // api request promise
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 // xhr does not simulate these properties of xhr but must return them
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 // simulating response
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 // ajax request promise
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 // use json error message
709 : (settings.error[status] !== undefined) // use server error message
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 // reset state
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 //'Element' : element,
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 // object containing all templates endpoints
1041 api : {},
1042
1043 // whether to cache responses
1044 cache : true,
1045
1046 // whether new requests should abort previous requests
1047 interruptRequests : true,
1048
1049 // event binding
1050 on : 'auto',
1051
1052 // context for applying state classes
1053 stateContext : false,
1054
1055 // duration for loading state
1056 loadingDuration : 0,
1057
1058 // whether to hide errors after a period of time
1059 hideError : 'auto',
1060
1061 // duration for error state
1062 errorDuration : 2000,
1063
1064 // whether parameters should be encoded with encodeURIComponent
1065 encodeParameters : true,
1066
1067 // API action to use
1068 action : false,
1069
1070 // templated URL to use
1071 url : false,
1072
1073 // base URL to apply to all endpoints
1074 base : '',
1075
1076 // data that will
1077 urlData : {},
1078
1079 // whether to add default data to url data
1080 defaultData : true,
1081
1082 // whether to serialize closest form
1083 serializeForm : false,
1084
1085 // how long to wait before request should occur
1086 throttle : 0,
1087
1088 // whether to throttle first request or only repeated
1089 throttleFirstRequest : true,
1090
1091 // standard ajax settings
1092 method : 'get',
1093 data : {},
1094 dataType : 'json',
1095
1096 // mock response
1097 mockResponse : false,
1098 mockResponseAsync : false,
1099
1100 // aliases for mock
1101 response : false,
1102 responseAsync : false,
1103
1104// whether onResponse should work with response value without force converting into an object
1105 rawResponse : false,
1106
1107 // callbacks before request
1108 beforeSend : function(settings) { return settings; },
1109 beforeXHR : function(xhr) {},
1110 onRequest : function(promise, xhr) {},
1111
1112 // after request
1113 onResponse : false, // function(response) { },
1114
1115 // response was successful, if JSON passed validation
1116 onSuccess : function(response, $module) {},
1117
1118 // request finished without aborting
1119 onComplete : function(response, $module) {},
1120
1121 // failed JSON success test
1122 onFailure : function(response, $module) {},
1123
1124 // server error
1125 onError : function(errorMessage, $module) {},
1126
1127 // request aborted
1128 onAbort : function(errorMessage, $module) {},
1129
1130 successTest : false,
1131
1132 // errors
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 );