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.

1077 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
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
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.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. $.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, 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. defaultData: function() {
  636. var
  637. data = {}
  638. ;
  639. if( !$.isWindow(element) ) {
  640. if( module.is.input() ) {
  641. data.value = $module.val();
  642. }
  643. else if( !module.is.form() ) {
  644. }
  645. else {
  646. data.text = $module.text();
  647. }
  648. }
  649. return data;
  650. },
  651. event: function() {
  652. if( $.isWindow(element) || settings.on == 'now' ) {
  653. module.debug('API called without element, no events attached');
  654. return false;
  655. }
  656. else if(settings.on == 'auto') {
  657. if( $module.is('input') ) {
  658. return (element.oninput !== undefined)
  659. ? 'input'
  660. : (element.onpropertychange !== undefined)
  661. ? 'propertychange'
  662. : 'keyup'
  663. ;
  664. }
  665. else if( $module.is('form') ) {
  666. return 'submit';
  667. }
  668. else {
  669. return 'click';
  670. }
  671. }
  672. else {
  673. return settings.on;
  674. }
  675. },
  676. templatedURL: function(action) {
  677. action = action || $module.data(metadata.action) || settings.action || false;
  678. url = $module.data(metadata.url) || settings.url || false;
  679. if(url) {
  680. module.debug('Using specified url', url);
  681. return url;
  682. }
  683. if(action) {
  684. module.debug('Looking up url for action', action, settings.api);
  685. if(settings.api[action] === undefined && !module.is.mocked()) {
  686. module.error(error.missingAction, settings.action, settings.api);
  687. return;
  688. }
  689. url = settings.api[action];
  690. }
  691. else if( module.is.form() ) {
  692. url = $module.attr('action') || false;
  693. module.debug('No url or action specified, defaulting to form action', url);
  694. }
  695. return url;
  696. }
  697. },
  698. abort: function() {
  699. var
  700. xhr = module.get.xhr()
  701. ;
  702. if( xhr && xhr.state() !== 'resolved') {
  703. module.debug('Cancelling API request');
  704. xhr.abort();
  705. }
  706. },
  707. // reset state
  708. reset: function() {
  709. module.remove.error();
  710. module.remove.loading();
  711. },
  712. setting: function(name, value) {
  713. module.debug('Changing setting', name, value);
  714. if( $.isPlainObject(name) ) {
  715. $.extend(true, settings, name);
  716. }
  717. else if(value !== undefined) {
  718. settings[name] = value;
  719. }
  720. else {
  721. return settings[name];
  722. }
  723. },
  724. internal: function(name, value) {
  725. if( $.isPlainObject(name) ) {
  726. $.extend(true, module, name);
  727. }
  728. else if(value !== undefined) {
  729. module[name] = value;
  730. }
  731. else {
  732. return module[name];
  733. }
  734. },
  735. debug: function() {
  736. if(settings.debug) {
  737. if(settings.performance) {
  738. module.performance.log(arguments);
  739. }
  740. else {
  741. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  742. module.debug.apply(console, arguments);
  743. }
  744. }
  745. },
  746. verbose: function() {
  747. if(settings.verbose && settings.debug) {
  748. if(settings.performance) {
  749. module.performance.log(arguments);
  750. }
  751. else {
  752. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  753. module.verbose.apply(console, arguments);
  754. }
  755. }
  756. },
  757. error: function() {
  758. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  759. module.error.apply(console, arguments);
  760. },
  761. performance: {
  762. log: function(message) {
  763. var
  764. currentTime,
  765. executionTime,
  766. previousTime
  767. ;
  768. if(settings.performance) {
  769. currentTime = new Date().getTime();
  770. previousTime = time || currentTime;
  771. executionTime = currentTime - previousTime;
  772. time = currentTime;
  773. performance.push({
  774. 'Name' : message[0],
  775. 'Arguments' : [].slice.call(message, 1) || '',
  776. //'Element' : element,
  777. 'Execution Time' : executionTime
  778. });
  779. }
  780. clearTimeout(module.performance.timer);
  781. module.performance.timer = setTimeout(module.performance.display, 500);
  782. },
  783. display: function() {
  784. var
  785. title = settings.name + ':',
  786. totalTime = 0
  787. ;
  788. time = false;
  789. clearTimeout(module.performance.timer);
  790. $.each(performance, function(index, data) {
  791. totalTime += data['Execution Time'];
  792. });
  793. title += ' ' + totalTime + 'ms';
  794. if(moduleSelector) {
  795. title += ' \'' + moduleSelector + '\'';
  796. }
  797. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  798. console.groupCollapsed(title);
  799. if(console.table) {
  800. console.table(performance);
  801. }
  802. else {
  803. $.each(performance, function(index, data) {
  804. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  805. });
  806. }
  807. console.groupEnd();
  808. }
  809. performance = [];
  810. }
  811. },
  812. invoke: function(query, passedArguments, context) {
  813. var
  814. object = instance,
  815. maxDepth,
  816. found,
  817. response
  818. ;
  819. passedArguments = passedArguments || queryArguments;
  820. context = element || context;
  821. if(typeof query == 'string' && object !== undefined) {
  822. query = query.split(/[\. ]/);
  823. maxDepth = query.length - 1;
  824. $.each(query, function(depth, value) {
  825. var camelCaseValue = (depth != maxDepth)
  826. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  827. : query
  828. ;
  829. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  830. object = object[camelCaseValue];
  831. }
  832. else if( object[camelCaseValue] !== undefined ) {
  833. found = object[camelCaseValue];
  834. return false;
  835. }
  836. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  837. object = object[value];
  838. }
  839. else if( object[value] !== undefined ) {
  840. found = object[value];
  841. return false;
  842. }
  843. else {
  844. module.error(error.method, query);
  845. return false;
  846. }
  847. });
  848. }
  849. if ( $.isFunction( found ) ) {
  850. response = found.apply(context, passedArguments);
  851. }
  852. else if(found !== undefined) {
  853. response = found;
  854. }
  855. if($.isArray(returnedValue)) {
  856. returnedValue.push(response);
  857. }
  858. else if(returnedValue !== undefined) {
  859. returnedValue = [returnedValue, response];
  860. }
  861. else if(response !== undefined) {
  862. returnedValue = response;
  863. }
  864. return found;
  865. }
  866. };
  867. if(methodInvoked) {
  868. if(instance === undefined) {
  869. module.initialize();
  870. }
  871. module.invoke(query);
  872. }
  873. else {
  874. if(instance !== undefined) {
  875. instance.invoke('destroy');
  876. }
  877. module.initialize();
  878. }
  879. })
  880. ;
  881. return (returnedValue !== undefined)
  882. ? returnedValue
  883. : this
  884. ;
  885. };
  886. $.api.settings = {
  887. name : 'API',
  888. namespace : 'api',
  889. debug : true,
  890. verbose : false,
  891. performance : true,
  892. // object containing all templates endpoints
  893. api : {},
  894. // whether to cache responses
  895. cache : true,
  896. // whether new requests should abort previous requests
  897. interruptRequests : true,
  898. // event binding
  899. on : 'auto',
  900. // context for applying state classes
  901. stateContext : false,
  902. // duration for loading state
  903. loadingDuration : 0,
  904. // duration for error state
  905. errorDuration : 2000,
  906. // API action to use
  907. action : false,
  908. // templated URL to use
  909. url : false,
  910. // base URL to apply to all endpoints
  911. base : '',
  912. // data that will
  913. urlData : {},
  914. // whether to add default data to url data
  915. defaultData : true,
  916. // whether to serialize closest form
  917. serializeForm : false,
  918. // how long to wait before request should occur
  919. throttle : 0,
  920. // whether to throttle first request or only repeated
  921. throttleFirstRequest : true,
  922. // standard ajax settings
  923. method : 'get',
  924. data : {},
  925. dataType : 'json',
  926. // mock response
  927. mockResponse : false,
  928. mockResponseAsync : false,
  929. // callbacks before request
  930. beforeSend : function(settings) { return settings; },
  931. beforeXHR : function(xhr) {},
  932. onRequest : function(promise, xhr) {},
  933. // after request
  934. onResponse : false, // function(response) { },
  935. // response was successful, if JSON passed validation
  936. onSuccess : function(response, $module) {},
  937. // request finished without aborting
  938. onComplete : function(response, $module) {},
  939. // failed JSON success test
  940. onFailure : function(response, $module) {},
  941. // server error
  942. onError : function(errorMessage, $module) {},
  943. // request aborted
  944. onAbort : function(errorMessage, $module) {},
  945. successTest : false,
  946. // errors
  947. error : {
  948. beforeSend : 'The before send function has aborted the request',
  949. error : 'There was an error with your request',
  950. exitConditions : 'API Request Aborted. Exit conditions met',
  951. JSONParse : 'JSON could not be parsed during error handling',
  952. legacyParameters : 'You are using legacy API success callback names',
  953. method : 'The method you called is not defined',
  954. missingAction : 'API action used but no url was defined',
  955. missingSerialize : 'jquery-serialize-object is required to add form data to an existing data object',
  956. missingURL : 'No URL specified for api event',
  957. noReturnedValue : 'The beforeSend callback must return a settings object, beforeSend ignored.',
  958. noStorage : 'Caching respopnses locally requires session storage',
  959. parseError : 'There was an error parsing your request',
  960. requiredParameter : 'Missing a required URL parameter: ',
  961. statusMessage : 'Server gave an error: ',
  962. timeout : 'Your request timed out'
  963. },
  964. regExp : {
  965. required : /\{\$*[A-z0-9]+\}/g,
  966. optional : /\{\/\$*[A-z0-9]+\}/g,
  967. },
  968. className: {
  969. loading : 'loading',
  970. error : 'error'
  971. },
  972. selector: {
  973. disabled : '.disabled',
  974. form : 'form'
  975. },
  976. metadata: {
  977. action : 'action',
  978. url : 'url'
  979. }
  980. };
  981. })( jQuery, window , document );