template.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /* eslint-disable no-shadow */
  6. define([
  7. 'jquery',
  8. 'underscore',
  9. 'mage/utils/objects',
  10. 'mage/utils/strings'
  11. ], function ($, _, utils, stringUtils) {
  12. 'use strict';
  13. var tmplSettings = _.templateSettings,
  14. interpolate = /\$\{([\s\S]+?)\}/g,
  15. opener = '${',
  16. template,
  17. hasStringTmpls;
  18. /**
  19. * Identifies whether ES6 templates are supported.
  20. */
  21. hasStringTmpls = (function () {
  22. var testString = 'var foo = "bar"; return `${ foo }` === foo';
  23. try {
  24. return Function(testString)();
  25. } catch (e) {
  26. return false;
  27. }
  28. })();
  29. if (hasStringTmpls) {
  30. /*eslint-disable no-unused-vars, no-eval*/
  31. /**
  32. * Evaluates template string using ES6 templates.
  33. *
  34. * @param {String} tmpl - Template string.
  35. * @param {Object} $ - Data object used in a template.
  36. * @returns {String} Compiled template.
  37. */
  38. template = function (tmpl, $) {
  39. return eval('`' + tmpl + '`');
  40. };
  41. /*eslint-enable no-unused-vars, no-eval*/
  42. } else {
  43. /**
  44. * Fallback function used when ES6 templates are not supported.
  45. * Uses underscore templates renderer.
  46. *
  47. * @param {String} tmpl - Template string.
  48. * @param {Object} data - Data object used in a template.
  49. * @returns {String} Compiled template.
  50. */
  51. template = function (tmpl, data) {
  52. var cached = tmplSettings.interpolate;
  53. tmplSettings.interpolate = interpolate;
  54. tmpl = _.template(tmpl, {
  55. variable: '$'
  56. })(data);
  57. tmplSettings.interpolate = cached;
  58. return tmpl;
  59. };
  60. }
  61. /**
  62. * Checks if provided value contains template syntax.
  63. *
  64. * @param {*} value - Value to be checked.
  65. * @returns {Boolean}
  66. */
  67. function isTemplate(value) {
  68. return typeof value === 'string' &&
  69. value.indexOf(opener) !== -1 &&
  70. // the below pattern almost always indicates an accident which should not cause template evaluation
  71. // refuse to evaluate
  72. value.indexOf('${{') === -1;
  73. }
  74. /**
  75. * Iteratively processes provided string
  76. * until no templates syntax will be found.
  77. *
  78. * @param {String} tmpl - Template string.
  79. * @param {Object} data - Data object used in a template.
  80. * @param {Boolean} [castString=false] - Flag that indicates whether template
  81. * should be casted after evaluation to a value of another type or
  82. * that it should be leaved as a string.
  83. * @returns {*} Compiled template.
  84. */
  85. function render(tmpl, data, castString) {
  86. var last = tmpl;
  87. while (~tmpl.indexOf(opener)) {
  88. tmpl = template(tmpl, data);
  89. if (tmpl === last) {
  90. break;
  91. }
  92. last = tmpl;
  93. }
  94. return castString ?
  95. stringUtils.castString(tmpl) :
  96. tmpl;
  97. }
  98. return {
  99. /**
  100. * Applies provided data to the template.
  101. *
  102. * @param {Object|String} tmpl
  103. * @param {Object} [data] - Data object to match with template.
  104. * @param {Boolean} [castString=false] - Flag that indicates whether template
  105. * should be casted after evaluation to a value of another type or
  106. * that it should be leaved as a string.
  107. * @returns {*}
  108. *
  109. * @example Template defined as a string.
  110. * var source = { foo: 'Random Stuff', bar: 'Some' };
  111. *
  112. * utils.template('${ $.bar } ${ $.foo }', source);
  113. * => 'Some Random Stuff';
  114. *
  115. * @example Template defined as an object.
  116. * var tmpl = {
  117. * key: {'${ $.$data.bar }': '${ $.$data.foo }'},
  118. * foo: 'bar',
  119. * x1: 2, x2: 5,
  120. * delta: '${ $.x2 - $.x1 }',
  121. * baz: 'Upper ${ $.foo.toUpperCase() }'
  122. * };
  123. *
  124. * utils.template(tmpl, source);
  125. * => {
  126. * key: {'Some': 'Random Stuff'},
  127. * foo: 'bar',
  128. * x1: 2, x2: 5,
  129. * delta: 3,
  130. * baz: 'Upper BAR'
  131. * };
  132. */
  133. template: function (tmpl, data, castString, dontClone) {
  134. if (typeof tmpl === 'string') {
  135. return render(tmpl, data, castString);
  136. }
  137. if (!dontClone) {
  138. tmpl = utils.copy(tmpl);
  139. }
  140. tmpl.$data = data || {};
  141. /**
  142. * Template iterator function.
  143. */
  144. _.each(tmpl, function iterate(value, key, list) {
  145. if (key === '$data') {
  146. return;
  147. }
  148. if (isTemplate(key)) {
  149. delete list[key];
  150. key = render(key, tmpl);
  151. list[key] = value;
  152. }
  153. if (isTemplate(value)) {
  154. list[key] = render(value, tmpl, castString);
  155. } else if ($.isPlainObject(value) || Array.isArray(value)) {
  156. _.each(value, iterate);
  157. }
  158. });
  159. delete tmpl.$data;
  160. return tmpl;
  161. }
  162. };
  163. });