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.

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