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.

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