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.

1136 lines
38 KiB

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