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.

1090 lines
36 KiB

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