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.

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