tryitout.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. window.abortControllers = {};
  2. function tryItOut(endpointId) {
  3. document.querySelector(`#btn-tryout-${endpointId}`).hidden = true;
  4. document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false;
  5. document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false;
  6. // Show all input fields
  7. document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
  8. .forEach(el => el.hidden = false);
  9. if (document.querySelector(`#form-${endpointId}`).dataset.authed === "1") {
  10. const authElement = document.querySelector(`#auth-${endpointId}`);
  11. authElement && (authElement.hidden = false);
  12. }
  13. // Expand all nested fields
  14. document.querySelectorAll(`#form-${endpointId} details`)
  15. .forEach(el => el.open = true);
  16. }
  17. function cancelTryOut(endpointId) {
  18. if (window.abortControllers[endpointId]) {
  19. window.abortControllers[endpointId].abort();
  20. delete window.abortControllers[endpointId];
  21. }
  22. document.querySelector(`#btn-tryout-${endpointId}`).hidden = false;
  23. const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
  24. executeBtn.hidden = true;
  25. executeBtn.textContent = "Send Request 💥";
  26. document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true;
  27. // Hide inputs
  28. document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
  29. .forEach(el => el.hidden = true);
  30. document.querySelectorAll(`#form-${endpointId} details`)
  31. .forEach(el => el.open = false);
  32. const authElement = document.querySelector(`#auth-${endpointId}`);
  33. authElement && (authElement.hidden = true);
  34. document.querySelector('#execution-results-' + endpointId).hidden = true;
  35. document.querySelector('#execution-error-' + endpointId).hidden = true;
  36. // Revert to sample code blocks
  37. document.querySelector('#example-requests-' + endpointId).hidden = false;
  38. document.querySelector('#example-responses-' + endpointId).hidden = false;
  39. }
  40. function makeAPICall(method, path, body, query, headers, endpointId) {
  41. console.log({endpointId, path, body, query, headers});
  42. if (!(body instanceof FormData)) {
  43. body = JSON.stringify(body)
  44. }
  45. const url = new URL(window.baseUrl + '/' + path.replace(/^\//, ''));
  46. // We need this function because if you try to set an array or object directly to a URLSearchParams object,
  47. // you'll get [object Object] or the array.toString()
  48. function addItemToSearchParamsObject(key, value, searchParams) {
  49. if (Array.isArray(value)) {
  50. value.forEach((v, i) => {
  51. // Append {filters: [first, second]} as filters[0]=first&filters[1]second
  52. addItemToSearchParamsObject(key + '[' + i + ']', v, searchParams);
  53. })
  54. } else if (typeof value === 'object' && value !== null) {
  55. Object.keys(value).forEach((i) => {
  56. // Append {filters: {name: first}} as filters[name]=first
  57. addItemToSearchParamsObject(key + '[' + i + ']', value[i], searchParams);
  58. });
  59. } else {
  60. searchParams.append(key, value);
  61. }
  62. }
  63. Object.keys(query)
  64. .forEach(key => addItemToSearchParamsObject(key, query[key], url.searchParams));
  65. window.abortControllers[endpointId] = new AbortController();
  66. return fetch(url, {
  67. method,
  68. headers,
  69. body: method === 'GET' ? undefined : body,
  70. signal: window.abortControllers[endpointId].signal
  71. })
  72. .then(response => Promise.all([response.status, response.text(), response.headers]));
  73. }
  74. function hideCodeSamples(endpointId) {
  75. document.querySelector('#example-requests-' + endpointId).hidden = true;
  76. document.querySelector('#example-responses-' + endpointId).hidden = true;
  77. }
  78. function handleResponse(endpointId, response, status, headers) {
  79. hideCodeSamples(endpointId);
  80. // Hide error views
  81. document.querySelector('#execution-error-' + endpointId).hidden = true;
  82. const responseContentEl = document.querySelector('#execution-response-content-' + endpointId);
  83. // Prettify it if it's JSON
  84. let isJson = false;
  85. try {
  86. const jsonParsed = JSON.parse(response);
  87. if (jsonParsed !== null) {
  88. isJson = true;
  89. response = JSON.stringify(jsonParsed, null, 4);
  90. }
  91. } catch (e) {
  92. }
  93. responseContentEl.textContent = response === '' ? '<Empty response>' : response;
  94. isJson && window.hljs.highlightBlock(responseContentEl);
  95. const statusEl = document.querySelector('#execution-response-status-' + endpointId);
  96. statusEl.textContent = ` (${status})`;
  97. document.querySelector('#execution-results-' + endpointId).hidden = false;
  98. statusEl.scrollIntoView({behavior: "smooth", block: "center"});
  99. }
  100. function handleError(endpointId, err) {
  101. hideCodeSamples(endpointId);
  102. // Hide response views
  103. document.querySelector('#execution-results-' + endpointId).hidden = true;
  104. // Show error views
  105. let errorMessage = err.message || err;
  106. errorMessage += "\n\nTip: Check that you're properly connected to the network.";
  107. errorMessage += "\nIf you're a maintainer of ths API, verify that your API is running and you've enabled CORS.";
  108. errorMessage += "\nYou can check the Dev Tools console for debugging information.";
  109. document.querySelector('#execution-error-message-' + endpointId).textContent = errorMessage;
  110. const errorEl = document.querySelector('#execution-error-' + endpointId);
  111. errorEl.hidden = false;
  112. errorEl.scrollIntoView({behavior: "smooth", block: "center"});
  113. }
  114. async function executeTryOut(endpointId, form) {
  115. const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
  116. executeBtn.textContent = "⏱ Sending...";
  117. executeBtn.scrollIntoView({behavior: "smooth", block: "center"});
  118. let body;
  119. let setter;
  120. if (form.dataset.hasfiles === "0") {
  121. body = {};
  122. setter = (name, value) => _.set(body, name, value);
  123. } else {
  124. body = new FormData();
  125. setter = (name, value) => body.append(name, value);
  126. }
  127. const bodyParameters = form.querySelectorAll('input[data-component=body]');
  128. bodyParameters.forEach(el => {
  129. let value = el.value;
  130. if (el.type === 'file' && el.files[0]) {
  131. setter(el.name, el.files[0]);
  132. return;
  133. }
  134. if (el.type !== 'radio') {
  135. if (value === "" && el.required === false) {
  136. // Don't include empty optional values in the request
  137. return;
  138. }
  139. setter(el.name, value);
  140. return;
  141. }
  142. if (el.checked) {
  143. value = (value === 'false') ? false : true;
  144. setter(el.name, value);
  145. }
  146. });
  147. const query = {};
  148. const queryParameters = form.querySelectorAll('input[data-component=query]');
  149. queryParameters.forEach(el => _.set(query, el.name, el.value));
  150. let path = form.dataset.path;
  151. const urlParameters = form.querySelectorAll('input[data-component=url]');
  152. urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value)));
  153. const headers = JSON.parse(form.dataset.headers);
  154. // Check for auth param that might go in header
  155. if (form.dataset.authed === "1") {
  156. const authHeaderEl = form.querySelector('input[data-component=header]');
  157. if (authHeaderEl) headers[authHeaderEl.name] = authHeaderEl.dataset.prefix + authHeaderEl.value;
  158. }
  159. // When using FormData, the browser sets the correct content-type + boundary
  160. let method = form.dataset.method;
  161. if (body instanceof FormData) {
  162. delete headers['Content-Type'];
  163. // When using FormData with PUT or PATCH, use method spoofing so PHP can access the post body
  164. if (['PUT', 'PATCH'].includes(form.dataset.method)) {
  165. method = 'POST';
  166. setter('_method', form.dataset.method);
  167. }
  168. }
  169. makeAPICall(method, path, body, query, headers, endpointId)
  170. .then(([responseStatus, responseContent, responseHeaders]) => {
  171. handleResponse(endpointId, responseContent, responseStatus, responseHeaders)
  172. })
  173. .catch(err => {
  174. if (err.name === "AbortError") {
  175. console.log("Request cancelled");
  176. return;
  177. }
  178. console.log("Error while making request: ", err);
  179. handleError(endpointId, err);
  180. })
  181. .finally(() => {
  182. executeBtn.textContent = "Send Request 💥";
  183. });
  184. }