You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1168 lines
39 KiB

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