Source: templateResolver.js

  1. /**
  2. * @file Provides a simple template resolver, that replaces variables in double curly brackets with the values of a given object.
  3. * @version {@link https://github.com/JohT/data-restructor-js/releases/latest latest version}
  4. * @author JohT
  5. * @version ${project.version}
  6. */
  7. "use strict";
  8. var module = templateResolverInternalCreateIfNotExists(module); // Fallback for vanilla js without modules
  9. function templateResolverInternalCreateIfNotExists(objectToCheck) {
  10. return objectToCheck || {};
  11. }
  12. /**
  13. * Provides a simple template resolver, that replaces variables in double curly brackets with the values of a given object.
  14. * @module template_resolver
  15. */
  16. var template_resolver = (module.exports = {}); // Export module for npm...
  17. template_resolver.internalCreateIfNotExists = templateResolverInternalCreateIfNotExists;
  18. var internal_object_tools = internal_object_tools || require("../../lib/js/flattenToArray"); // supports vanilla js & npm
  19. template_resolver.Resolver = (function () {
  20. var removeArrayBracketsRegEx = new RegExp("\\[\\d+\\]", "gi");
  21. /**
  22. * Resolver. Is used inside this repository. It could also be used outside.
  23. * @param {*} sourceDataObject The properties of this object will be used to replace the placeholders in the template.
  24. * @constructs Resolver
  25. * @alias module:template_resolver.Resolver
  26. */
  27. function Resolver(sourceDataObject) {
  28. /**
  29. * The properties of this source data object will be used to replace the placeholders in the template.
  30. */
  31. this.sourceDataObject = sourceDataObject;
  32. /**
  33. * Resolves the given template.
  34. *
  35. * The template may contain variables in double curly brackets.
  36. * Supported variables are all properties of this object, e.g. "{{fieldName}}", "{{displayName}}", "{{value}}".
  37. * Since this object may also contains (described) groups of sub objects, they can also be used, e.g. "{{summaries[0].value}}"
  38. * Parts of the index can be inserted by using e.g. "{{index[1]}}".
  39. *
  40. * @param {string} template
  41. * @returns {string} resolved template
  42. */
  43. this.resolveTemplate = function (template) {
  44. return this.replaceResolvableFields(template, addFieldsPerGroup(this.resolvableFieldsOfAll(this.sourceDataObject)));
  45. };
  46. /**
  47. * Returns a map like object, that contains all resolvable fields and their values as properties.
  48. * This function takes a variable count of input parameters,
  49. * each containing an object that contains resolvable fields to extract from.
  50. *
  51. * The recursion depth is limited to 3, so that an object,
  52. * that contains an object can contain another object (but not further).
  53. *
  54. * Properties beginning with an underscore in their name will be filtered out, since they are considered as internal fields.
  55. *
  56. * @param {...object} varArgs variable count of parameters. Each parameter contains an object that fields should be resolvable for variables.
  57. * @returns {object} object with resolvable field names and their values.
  58. * @public
  59. */
  60. this.resolvableFieldsOfAll = function () {
  61. var map = {};
  62. var ignoreInternalFields = function (propertyName) {
  63. return propertyName.indexOf("_") !== 0 && propertyName.indexOf("._") < 0;
  64. };
  65. var index;
  66. for (index = 0; index < arguments.length; index += 1) {
  67. addToFilteredMapObject(internal_object_tools.flattenToArray(arguments[index], 3), map, ignoreInternalFields);
  68. }
  69. return map;
  70. };
  71. /**
  72. * Replaces all variables in double curly brackets, e.g. {{property}},
  73. * with the value of that property from the resolvableProperties.
  74. *
  75. * Supported property types: string, number, boolean
  76. * @param {string} stringContainingVariables
  77. * @param {object[]} resolvableFields (name=value)
  78. */
  79. this.replaceResolvableFields = function (stringContainingVariables, resolvableFields) {
  80. var replaced = stringContainingVariables;
  81. var propertyNames = Object.keys(resolvableFields);
  82. var propertyIndex = 0;
  83. var propertyName = "";
  84. var propertyValue = "";
  85. for (propertyIndex = 0; propertyIndex < propertyNames.length; propertyIndex += 1) {
  86. propertyName = propertyNames[propertyIndex];
  87. propertyValue = resolvableFields[propertyName];
  88. replaced = replaced.replace("{{" + propertyName + "}}", propertyValue);
  89. }
  90. return replaced;
  91. };
  92. }
  93. /**
  94. * Adds the value of the "fieldName" property (including its group prefix) and its associated "value" property content.
  95. * For example: detail[2].fieldName="name", detail[2].value="Smith" lead to the additional property detail.name="Smith".
  96. * @param {object} object with resolvable field names and their values.
  97. * @returns {object} object with resolvable field names and their values.
  98. * @protected
  99. * @memberof module:template_resolver.Resolver
  100. */
  101. function addFieldsPerGroup(map) {
  102. var propertyNames = Object.keys(map);
  103. var i, fullPropertyName, propertyInfo, propertyValue;
  104. for (i = 0; i < propertyNames.length; i += 1) {
  105. fullPropertyName = propertyNames[i];
  106. propertyValue = map[fullPropertyName];
  107. propertyInfo = getPropertyNameInfos(fullPropertyName);
  108. // Supports fields that are defined by a property named "fieldName" (containing the name)
  109. // and a property named "value" inside the same sub object (containing its value).
  110. // Ignore custom fields that are named "fieldName"(propertyValue), since this would lead to an unpredictable behavior.
  111. // TODO could make "fieldName" and "value" configurable
  112. if (propertyInfo.name === "fieldName" && propertyValue !== "fieldName") {
  113. map[propertyInfo.groupWithoutArrayIndices + propertyValue] = map[propertyInfo.group + "value"];
  114. }
  115. }
  116. return map;
  117. }
  118. /**
  119. * Infos about the full property name including the name of the group (followed by the separator) and the name of the property itself.
  120. * @param {String} fullPropertyName
  121. * @returns {Object} Contains "group" (empty or group name including trailing separator "."), "groupWithoutArrayIndices" and "name" (property name).
  122. * @protected
  123. * @memberof module:template_resolver.Resolver
  124. */
  125. function getPropertyNameInfos(fullPropertyName) {
  126. var positionOfRightMostSeparator = fullPropertyName.lastIndexOf(".");
  127. var propertyName = fullPropertyName;
  128. if (positionOfRightMostSeparator > 0) {
  129. propertyName = fullPropertyName.substr(positionOfRightMostSeparator + 1);
  130. }
  131. var propertyGroup = "";
  132. if (positionOfRightMostSeparator > 0) {
  133. propertyGroup = fullPropertyName.substr(0, positionOfRightMostSeparator + 1); //includes the trailing ".".
  134. }
  135. var propertyGroupWithoutArrayIndices = propertyGroup.replace(removeArrayBracketsRegEx, "");
  136. return { group: propertyGroup, groupWithoutArrayIndices: propertyGroupWithoutArrayIndices, name: propertyName };
  137. }
  138. /**
  139. * Collects all flattened name-value-pairs into one object using the property names as keys and their values as values (map-like).
  140. * Example: `{name: "accountNumber", value: "12345"}` becomes `mapObject["accountNumber"]="12345"`.
  141. *
  142. * @param {NameValuePair[]} elements flattened array of name-value-pairs
  143. * @param {object} mapObject container to collect the results. Needs to be created before e.g. using `{}`.
  144. * @param {function} filterMatchesFunction takes the property name as string argument and returns true (include) or false (exclude).
  145. * @protected
  146. * @memberof module:template_resolver.Resolver
  147. */
  148. function addToFilteredMapObject(elements, mapObject, filterMatchesFunction) {
  149. var index, element;
  150. for (index = 0; index < elements.length; index += 1) {
  151. element = elements[index];
  152. if (typeof filterMatchesFunction === "function" && filterMatchesFunction(element.name)) {
  153. mapObject[element.name] = element.value;
  154. }
  155. }
  156. return mapObject;
  157. }
  158. return Resolver;
  159. }());