Source: search-service-client.js

  1. /**
  2. * @file Provides the (http) client/connection to the search backend service.
  3. * @version {@link https://github.com/JohT/search-menu-ui/releases/latest latest version}
  4. * @author JohT
  5. * @version ${project.version}
  6. */
  7. "use strict";
  8. var module = datarestructorInternalCreateIfNotExists(module); // Fallback for vanilla js without modules
  9. function datarestructorInternalCreateIfNotExists(objectToCheck) {
  10. return objectToCheck || {};
  11. }
  12. /**
  13. * Search-Menu Service-Client.
  14. * It provides the (http) client/connection to the search backend service.
  15. * @module searchMenuServiceClient
  16. */
  17. var searchMenuServiceClient = (module.exports = {}); // Export module for npm...
  18. searchMenuServiceClient.internalCreateIfNotExists = datarestructorInternalCreateIfNotExists;
  19. var xmlHttpRequest = xmlHttpRequest || require("../../src/js/ponyfills/xmlHttpRequestPonyfill"); // supports vanilla js & npm
  20. searchMenuServiceClient.HttpSearchConfig = (function () {
  21. /**
  22. * Configures and builds the {@link module:searchMenuServiceClient.HttpClient}.
  23. * DescribedDataField is the main element of the restructured data and therefore considered "public".
  24. * @constructs HttpSearchConfig
  25. * @alias module:searchMenuServiceClient.HttpSearchConfig
  26. */
  27. function HttpSearchConfig() {
  28. /**
  29. * HTTP Search Configuration.
  30. * @property {string} searchUrlTemplate URL that is called for every search request. It may include variables in double curly brackets like `{{searchtext}}`.
  31. * @property {string} [searchMethod="POST"] HTTP Method, that is used for every search request.
  32. * @property {string} [searchContentType="application/json"] HTTP MIME-Type of the body, that is used for every search request.
  33. * @property {string} searchBodyTemplate HTTP body template, that is used for every search request. It may include variables in double curly brackets like `{{jsonSearchParameters}}`.
  34. * @property {XMLHttpRequest} [httpRequest=new XMLHttpRequest()] Contains the XMLHttpRequest that is used to handle HTTP requests and responses. Defaults to XMLHttpRequest.
  35. * @property {boolean} [debugMode=false] Adds detailed logging for development and debugging.
  36. */
  37. this.config = {
  38. searchUrlTemplate: "",
  39. searchMethod: "POST",
  40. searchContentType: "application/json",
  41. searchBodyTemplate: null,
  42. /**
  43. * Resolves variables in the search url template based on the given search parameters object.
  44. * The variable {{jsonSearchParameters}} will be replaced by the JSON of all search parameters.
  45. * @param {Object} searchParameters object properties will be used to replace the variables of the searchUrlTemplate
  46. */
  47. resolveSearchUrl: function (searchParameters) {
  48. return resolveTemplate(this.searchUrlTemplate, searchParameters, this.debugMode);
  49. },
  50. /**
  51. * Resolves variables in the search body template based on the given search parameters object.
  52. * The variable {{jsonSearchParameters}} will be replaced by the JSON of all search parameters.
  53. * @param {Object} searchParameters object properties will be used to replace the variables of the searchBodyTemplate
  54. */
  55. resolveSearchBody: function (searchParameters) {
  56. return resolveTemplate(this.searchBodyTemplate, searchParameters, this.debugMode);
  57. },
  58. httpRequest: null,
  59. debugMode: false
  60. };
  61. /**
  62. * Sets the url for the HTTP request for the search.
  63. * It may include variables in double curly brackets like {{searchtext}}.
  64. * @param {String} value
  65. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  66. */
  67. this.searchUrlTemplate = function (value) {
  68. this.config.searchUrlTemplate = value;
  69. return this;
  70. };
  71. /**
  72. * Sets the HTTP method for the search. Defaults to "POST".
  73. * @param {String} value
  74. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  75. */
  76. this.searchMethod = function (value) {
  77. this.config.searchMethod = value;
  78. return this;
  79. };
  80. /**
  81. * Sets the HTTP content type of the request body. Defaults to "application/json".
  82. * @param {String} value
  83. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  84. */
  85. this.searchContentType = function (value) {
  86. this.config.searchContentType = value;
  87. return this;
  88. };
  89. /**
  90. * Sets the HTTP request body template that may contain variables (e.g. {{searchParameters}}) in double curly brackets, or null if there is none.
  91. * @param {String} value
  92. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  93. */
  94. this.searchBodyTemplate = function (value) {
  95. this.config.searchBodyTemplate = value;
  96. return this;
  97. };
  98. /**
  99. * Sets the HTTP-Request-Object. Defaults to XMLHttpRequest if not set.
  100. * @param {String} value
  101. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  102. */
  103. this.httpRequest = function (value) {
  104. this.config.httpRequest = value;
  105. return this;
  106. };
  107. /**
  108. * Sets the debug mode, that prints some more info to the console.
  109. * @param {boolean} value
  110. * @return {module:searchMenuServiceClient.HttpSearchConfig}
  111. */
  112. this.debugMode = function (value) {
  113. this.config.debugMode = value === true;
  114. return this;
  115. };
  116. /**
  117. * Uses the configuration to build the http client that provides the function "search" (parameters: searchParameters, onSuccess callback).
  118. * @returns {module:searchMenuServiceClient.HttpClient}
  119. */
  120. this.build = function () {
  121. if (!this.config.httpRequest) {
  122. this.config.httpRequest = xmlHttpRequest.getXMLHttpRequest();
  123. }
  124. return new searchMenuServiceClient.HttpClient(this.config);
  125. };
  126. }
  127. /**
  128. * Resolves variables in the template based on the given search parameters object.
  129. * The variable {{jsonSearchParameters}} will be replaced by the JSON of all search parameters.
  130. * @param {String} template contains variables in double curly brackets that should be replaced by the values of the parameterSourceObject.
  131. * @param {Object} parameterSourceObject object properties will be used to replace the variables of the template
  132. * @param {boolean} debugMode enables/disables extended logging for debugging
  133. * @memberof module:searchMenuServiceClient.HttpSearchConfig
  134. * @protected
  135. */
  136. function resolveTemplate(template, parameterSourceObject, debugMode) {
  137. if (template == null) {
  138. return null;
  139. }
  140. var jsonSearchParameters = JSON.stringify(parameterSourceObject);
  141. var resolvedBody = template;
  142. resolvedBody = resolveVariableInTemplate(resolvedBody, "jsonSearchParameters", jsonSearchParameters);
  143. resolvedBody = resolveVariablesInTemplate(resolvedBody, parameterSourceObject);
  144. if (debugMode) {
  145. console.log("template=" + template);
  146. console.log("{{jsonSearchParameters}}=" + jsonSearchParameters);
  147. console.log("resolved template=" + resolvedBody);
  148. }
  149. return resolvedBody;
  150. }
  151. function resolveVariablesInTemplate(templateString, sourceDataObject) {
  152. var resolvedString = templateString;
  153. forEachFieldsIn(sourceDataObject, function (fieldName, fieldValue) {
  154. resolvedString = resolveVariableInTemplate(resolvedString, fieldName, fieldValue);
  155. });
  156. return resolvedString;
  157. }
  158. function resolveVariableInTemplate(templateString, fieldName, fieldValue) {
  159. //TODO could there be a better compatible solution to replace ALL occurrences instead of creating regular expressions?
  160. var variableReplaceRegExp = new RegExp("\\{\\{" + escapeCharsForRegEx(fieldName) + "\\}\\}", "gm");
  161. return templateString.replace(variableReplaceRegExp, fieldValue);
  162. }
  163. function escapeCharsForRegEx(characters) {
  164. var nonWordCharactersRegEx = new RegExp("([^-\\w])", "gi");
  165. return characters.replace(nonWordCharactersRegEx, "\\$1");
  166. }
  167. function forEachFieldsIn(object, fieldNameAndValueConsumer) {
  168. var fieldNames = Object.keys(object);
  169. var index, fieldName, fieldValue;
  170. for (index = 0; index < fieldNames.length; index += 1) {
  171. fieldName = fieldNames[index];
  172. fieldValue = object[fieldName];
  173. fieldNameAndValueConsumer(fieldName, fieldValue);
  174. }
  175. }
  176. return HttpSearchConfig;
  177. }());
  178. /**
  179. * This function will be called, when search results are available.
  180. * @callback module:searchMenuServiceClient.HttpClient.SearchServiceResultAvailable
  181. * @param {Object} searchResultData already parsed data object containing the result of the search
  182. */
  183. searchMenuServiceClient.HttpClient = (function () {
  184. /**
  185. * HttpClient.
  186. *
  187. * Contains the "backend-connection" of the search bar. It submits the search query,
  188. * parses the results and informs the callback as soon as these results are available.
  189. * @example new searchMenuServiceClient.HttpSearchConfig()....build();
  190. * @param {module:searchMenuServiceClient.HttpSearchConfig} config
  191. * @constructs HttpClient
  192. * @alias module:searchMenuServiceClient.HttpClient
  193. */
  194. var instance = function (config) {
  195. /**
  196. * Configuration for the search HTTP requests.
  197. * @type {module:searchMenuServiceClient.HttpSearchConfig}
  198. */
  199. this.config = config;
  200. /**
  201. * This function will be called to trigger search (calling the search backend).
  202. * @function
  203. * @param {Object} searchParameters object that contains all parameters as properties. It will be converted to JSON.
  204. * @param {module:searchMenuServiceClient.HttpClient.SearchServiceResultAvailable} onSearchResultsAvailable will be called when search results are available.
  205. */
  206. this.search = createSearchFunction(this.config, this.config.httpRequest);
  207. };
  208. /**
  209. * Creates the search service function that can be bound to the search menu.
  210. * @param {module:searchMenuServiceClient.HttpSearchConfig} config Configuration for the search HTTP requests.
  211. * @param {XMLHttpRequest} httpRequest Takes the HTTP-Request-Object.
  212. * @returns {module:searchMenuServiceClient.SearchService}
  213. * @memberof module:searchMenuServiceClient.HttpClient
  214. * @private
  215. */
  216. function createSearchFunction(config, httpRequest) {
  217. return function (searchParameters, onJsonResultReceived) {
  218. var onFailure = function (resultText, httpStatus) {
  219. console.error("search failed with status code " + httpStatus + ": " + resultText);
  220. };
  221. var searchUrl = config.resolveSearchUrl(searchParameters);
  222. var searchBody = config.resolveSearchBody(searchParameters);
  223. var request = { url: searchUrl, method: config.searchMethod, contentType: config.searchContentType, body: searchBody };
  224. if (config.debugMode) {
  225. onJsonResultReceived = loggedSuccess(onJsonResultReceived);
  226. }
  227. httpRequestJson(request, httpRequest, onJsonResultReceived, onFailure);
  228. };
  229. }
  230. function loggedSuccess(onSuccess) {
  231. return function (jsonResult, status) {
  232. console.log("successful search response with code " + status + ": " + JSON.stringify(jsonResult, null, 2));
  233. onSuccess(jsonResult, status);
  234. };
  235. }
  236. /**
  237. * This function will be called when a already parsed response of the HTTP request is available.
  238. * @callback module:searchMenuServiceClient.HttpClient.ParsedHttpResponseAvailable
  239. * @param {Object} resultData already parsed data object containing the results of the HTTP request
  240. * @param {number} httpStatus HTTP response status
  241. */
  242. /**
  243. * This function will be called when a response of the HTTP request is available as text.
  244. * @callback module:searchMenuServiceClient.HttpClient.TextHttpResponseAvailable
  245. * @param {Object} resultText response body as text
  246. * @param {number} httpStatus HTTP response status
  247. */
  248. /**
  249. * Executes an HTTP "AJAX" request.
  250. *
  251. * @param {Object} request - flattened json from search query result
  252. * @param {string} request.url - name of the property in hierarchical order separated by points
  253. * @param {string} request.method - value of the property as string
  254. * @param {string} request.contentType - value of the property as string
  255. * @param {string} request.body - value of the property as string
  256. * @param {Object} httpRequest - Browser provided object to use for the HTTP request.
  257. * @param {module:searchMenuServiceClient.HttpClient.ParsedHttpResponseAvailable} onSuccess - will be called when the request was successful.
  258. * @param {module:searchMenuServiceClient.HttpClient.TextHttpResponseAvailable} onFailure - will be called with the error message as text
  259. * @memberof module:searchMenuServiceClient.HttpClient
  260. * @private
  261. */
  262. function httpRequestJson(request, httpRequest, onSuccess, onFailure) {
  263. httpRequest.onreadystatechange = function () {
  264. if (httpRequest.readyState === 4) {
  265. if (httpRequest.status >= 200 && httpRequest.status <= 299) {
  266. var jsonResult = JSON.parse(httpRequest.responseText);
  267. onSuccess(jsonResult, httpRequest.status);
  268. } else {
  269. onFailure(httpRequest.responseText, httpRequest.status);
  270. }
  271. }
  272. };
  273. httpRequest.open(request.method, request.url, true);
  274. httpRequest.setRequestHeader("Content-Type", request.contentType);
  275. httpRequest.send(request.body);
  276. }
  277. return instance;
  278. }());