PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/files/tempo/1.6/tempo.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 693 lines | 545 code | 100 blank | 48 comment | 113 complexity | d74cb79df00955ffe81825b1a84a52fa MD5 | raw file
  1. /*!
  2. * Tempo Template Engine 1.6
  3. *
  4. * http://tempojs.com/
  5. */
  6. function TempoEvent(type, item, element) {
  7. this.type = type;
  8. this.item = item;
  9. this.element = element;
  10. return this;
  11. }
  12. TempoEvent.Types = {
  13. RENDER_STARTING : 'render_starting',
  14. ITEM_RENDER_STARTING : 'item_render_starting',
  15. ITEM_RENDER_COMPLETE : 'item_render_complete',
  16. RENDER_COMPLETE : 'render_complete'
  17. };
  18. var Tempo = (function (tempo) {
  19. /*!
  20. * Constants
  21. */
  22. var NUMBER_FORMAT_REGEX = /(\d+)(\d{3})/;
  23. /*!
  24. * Helpers
  25. */
  26. var utils = {
  27. memberRegex : function (obj) {
  28. var member_regex = '';
  29. for (var member in obj) {
  30. if (obj.hasOwnProperty(member)) {
  31. if (member_regex.length > 0) {
  32. member_regex += '|';
  33. }
  34. member_regex += member;
  35. }
  36. }
  37. return member_regex;
  38. },
  39. pad : function (val, pad, size) {
  40. while (val.length < size) {
  41. val = pad + val;
  42. }
  43. return val;
  44. },
  45. trim : function (str) {
  46. return str.replace(/^\s*([\S\s]*?)\s*$/, '$1');
  47. },
  48. startsWith : function (str, prefix) {
  49. return (str.indexOf(prefix) === 0);
  50. },
  51. clearContainer : function (el) {
  52. if (el !== undefined && el.childNodes !== undefined) {
  53. for (var i = el.childNodes.length; i >= 0; i--) {
  54. if (el.childNodes[i] !== undefined && el.childNodes[i].getAttribute !== undefined && el.childNodes[i].getAttribute('data-template') !== null) {
  55. el.childNodes[i].parentNode.removeChild(el.childNodes[i]);
  56. }
  57. }
  58. }
  59. },
  60. isNested : function (el) {
  61. var p = el.parentNode;
  62. while (p) {
  63. if (p.getAttribute !== undefined && p.getAttribute('data-template') !== null) {
  64. return true;
  65. }
  66. p = p.parentNode;
  67. }
  68. return false;
  69. },
  70. equalsIgnoreCase : function (str1, str2) {
  71. return str1.toLowerCase() === str2.toLowerCase();
  72. },
  73. getElement : function (template, html) {
  74. if (utils.equalsIgnoreCase(template.tagName, 'tr')) {
  75. // Wrapping to get around read-only innerHTML
  76. var el = document.createElement('div');
  77. el.innerHTML = '<table><tbody>' + html + '</tbody></table>';
  78. var depth = 3;
  79. while (depth--) {
  80. el = el.lastChild;
  81. }
  82. return el;
  83. } else {
  84. // No need to wrap
  85. template.innerHTML = html;
  86. return template;
  87. }
  88. },
  89. typeOf : function (obj) {
  90. if (typeof(obj) === "object") {
  91. if (obj === null) {
  92. return "null";
  93. }
  94. if (obj.constructor === ([]).constructor) {
  95. return "array";
  96. }
  97. if (obj.constructor === (new Date()).constructor) {
  98. return "date";
  99. }
  100. if (obj.constructor === (new RegExp()).constructor) {
  101. return "regex";
  102. }
  103. return "object";
  104. }
  105. return typeof(obj);
  106. },
  107. notify : function (listener, event) {
  108. if (listener !== undefined) {
  109. listener(event);
  110. }
  111. }
  112. };
  113. function Templates(params, nestedItem) {
  114. this.params = params;
  115. this.defaultTemplate = null;
  116. this.namedTemplates = {};
  117. this.container = null;
  118. this.nestedItem = nestedItem !== undefined ? nestedItem : null;
  119. this.var_brace_left = '\\{\\{';
  120. this.var_brace_right = '\\}\\}';
  121. this.tag_brace_left = '\\{%';
  122. this.tag_brace_right = '%\\}';
  123. if (typeof params !== 'undefined') {
  124. for (var prop in params) {
  125. if (prop === 'var_braces') {
  126. this.var_brace_left = params[prop].substring(0, params[prop].length / 2);
  127. this.var_brace_right = params[prop].substring(params[prop].length / 2);
  128. } else if (prop === 'tag_braces') {
  129. this.tag_brace_left = params[prop].substring(0, params[prop].length / 2);
  130. this.tag_brace_right = params[prop].substring(params[prop].length / 2);
  131. } else if (typeof this[prop] !== 'undefined') {
  132. this[prop] = params[prop];
  133. }
  134. }
  135. }
  136. return this;
  137. }
  138. Templates.prototype = {
  139. parse: function (container) {
  140. this.container = container;
  141. var children = container.getElementsByTagName('*');
  142. for (var i = 0; i < children.length; i++) {
  143. if (children[i].getAttribute !== undefined && children[i].getAttribute('data-template') !== null && (this.nestedItem === children[i].getAttribute('data-template') || children[i].getAttribute('data-template') === '' || children[i].getAttribute('data-template') === 'data-template' && !utils.isNested(children[i]))) {
  144. this.createTemplate(children[i]);
  145. } else if (children[i].getAttribute !== undefined && children[i].getAttribute('data-template-fallback') !== null) {
  146. // Hiding the fallback template
  147. children[i].style.display = 'none';
  148. }
  149. }
  150. // If there is no default template (data-template) then create one from container
  151. if (this.defaultTemplate === null) {
  152. // Creating a template inside the container
  153. var el = document.createElement('div');
  154. el.setAttribute('data-template', '');
  155. el.innerHTML = this.container.innerHTML;
  156. // Clearing container before adding the wrapped contents
  157. this.container.innerHTML = '';
  158. // There is now a default template present with a data-template attribute
  159. this.container.appendChild(el);
  160. this.createTemplate(el);
  161. }
  162. utils.clearContainer(this.container);
  163. },
  164. createTemplate : function (node) {
  165. var element = node.cloneNode(true);
  166. // Clear display: none;
  167. if (element.style.removeAttribute) {
  168. element.style.removeAttribute('display');
  169. }
  170. else {
  171. element.style.removeProperty('display');
  172. }
  173. // Remapping container element in case template
  174. // is deep in container
  175. this.container = node.parentNode;
  176. // Element is a template
  177. var nonDefault = false;
  178. for (var a = 0; a < element.attributes.length; a++) {
  179. var attr = element.attributes[a];
  180. // If attribute
  181. if (utils.startsWith(attr.name, 'data-if-')) {
  182. var val;
  183. if (attr.value === '') {
  184. val = true;
  185. } else {
  186. val = '\'' + attr.value + '\'';
  187. }
  188. this.namedTemplates[attr.name.substring(8, attr.name.length) + '==' + val] = element;
  189. element.removeAttribute(attr.name);
  190. nonDefault = true;
  191. }
  192. }
  193. // Setting as default template, last one wins
  194. if (!nonDefault) {
  195. this.defaultTemplate = element;
  196. }
  197. },
  198. templateFor: function (i) {
  199. for (var templateName in this.namedTemplates) {
  200. if (eval('i.' + templateName)) {
  201. return this.namedTemplates[templateName].cloneNode(true);
  202. }
  203. }
  204. if (this.defaultTemplate) {
  205. return this.defaultTemplate.cloneNode(true);
  206. }
  207. }
  208. };
  209. /*!
  210. * Renderer for populating containers with data using templates.
  211. */
  212. function Renderer(templates) {
  213. this.templates = templates;
  214. this.listener = undefined;
  215. this.started = false;
  216. this.varRegex = new RegExp(this.templates.var_brace_left + '[ ]?([A-Za-z0-9$\\._\\[\\]]*?)([ ]?\\|[ ]?.*?)?[ ]?' + this.templates.var_brace_right, 'g');
  217. this.tagRegex = new RegExp(this.templates.tag_brace_left + '[ ]?([\\s\\S]*?)( [\\s\\S]*?)?[ ]?' + this.templates.tag_brace_right + '(([\\s\\S]*?)(?=' + this.templates.tag_brace_left + '[ ]?end\\1[ ]?' + this.templates.tag_brace_right + '))?', 'g');
  218. return this;
  219. }
  220. Renderer.prototype = {
  221. notify : function (listener) {
  222. this.listener = listener;
  223. return this;
  224. },
  225. _replaceVariables : function (renderer, _tempo, i, str) {
  226. return str.replace(this.varRegex, function (match, variable, args) {
  227. try {
  228. var val = null;
  229. // Handling tempo_info variable
  230. if (utils.startsWith(variable, '_tempo.')) {
  231. return eval(variable);
  232. }
  233. if (utils.typeOf(i) === 'array') {
  234. val = eval('i' + variable);
  235. } else {
  236. val = eval('i.' + variable);
  237. }
  238. // Handle filters
  239. var filterSplitter = new RegExp('\\|[ ]?(?=' + utils.memberRegex(renderer.filters) + ')', 'g');
  240. if (args !== undefined && args !== '') {
  241. var filters = utils.trim(utils.trim(args).substring(1)).split(filterSplitter);
  242. for (var p = 0; p < filters.length; p++) {
  243. var filter = utils.trim(filters[p]);
  244. var filter_args = [];
  245. // If there is a space, there must be arguments
  246. if (filter.indexOf(' ') > -1) {
  247. var f = filter.substring(filter.indexOf(' ')).replace(/^[ ']*|[ ']*$/g, '');
  248. filter_args = f.split(/(?:[\'"])[ ]?,[ ]?(?:[\'"])/);
  249. filter = filter.substring(0, filter.indexOf(' '));
  250. }
  251. val = renderer.filters[filter](val, filter_args);
  252. }
  253. }
  254. if (val !== undefined) {
  255. return val;
  256. }
  257. } catch (err) {
  258. }
  259. return '';
  260. });
  261. },
  262. _replaceObjects : function (renderer, _tempo, i, str) {
  263. var regex = new RegExp('(?:__[\\.]?)((_tempo|\\[|' + utils.memberRegex(i) + ')([A-Za-z0-9$\\._\\[\\]]+)?)', 'g');
  264. return str.replace(regex, function (match, variable, args) {
  265. try {
  266. var val = null;
  267. // Handling tempo_info variable
  268. if (utils.startsWith(variable, '_tempo.')) {
  269. return eval(variable);
  270. }
  271. if (utils.typeOf(i) === 'array') {
  272. val = eval('i' + variable);
  273. } else {
  274. val = eval('i.' + variable);
  275. }
  276. if (val !== undefined) {
  277. if (utils.typeOf(val) === 'string') {
  278. return '\'' + val + '\'';
  279. } else {
  280. return val;
  281. }
  282. }
  283. } catch (err) {
  284. }
  285. return undefined;
  286. });
  287. },
  288. _applyAttributeSetters : function (renderer, item, str) {
  289. return str.replace(/([A-z0-9]+?)(?==).*?data-\1="(.*?)"/g, function (match, attr, data_value) {
  290. if (data_value !== '') {
  291. return attr + '="' + data_value + '"';
  292. }
  293. return match;
  294. });
  295. },
  296. _applyTags : function (renderer, item, str) {
  297. return str.replace(this.tagRegex, function (match, tag, args, body) {
  298. if (renderer.tags.hasOwnProperty(tag)) {
  299. args = args.substring(args.indexOf(' ')).replace(/^[ ]*|[ ]*$/g, '');
  300. var filter_args = args.split(/(?:['"])[ ]?,[ ]?(?:['"])/);
  301. return renderer.tags[tag](renderer, item, match, filter_args, body);
  302. } else {
  303. return '';
  304. }
  305. });
  306. },
  307. starting : function () {
  308. // Use this to manually fire the RENDER_STARTING event e.g. just before you issue an AJAX request
  309. // Useful if you're not calling prepare immediately before render
  310. this.started = true;
  311. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
  312. return this;
  313. },
  314. renderItem : function (renderer, tempo_info, i, fragment) {
  315. var template = renderer.templates.templateFor(i);
  316. if (template && i) {
  317. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_STARTING, i, template));
  318. var nestedDeclaration = template.innerHTML.match(/data-template="(.*?)"/g);
  319. if (nestedDeclaration) {
  320. for (var p = 0; p < nestedDeclaration.length; p++) {
  321. var nested = nestedDeclaration[p].match(/"(.*?)"/)[1];
  322. var t = new Templates(renderer.templates.params, nested);
  323. t.parse(template);
  324. var r = new Renderer(t);
  325. r.render(eval('i.' + nested));
  326. }
  327. }
  328. // Dealing with HTML as a String from now on (to be reviewed)
  329. // Attribute values are escaped in FireFox so making sure there are no escaped tags
  330. var html = template.innerHTML.replace(/%7B%7B/g, '{{').replace(/%7D%7D/g, '}}');
  331. // Tags
  332. html = this._applyTags(this, i, html);
  333. // Content
  334. html = this._replaceVariables(this, tempo_info, i, html);
  335. // JavaScript objects
  336. html = this._replaceObjects(this, tempo_info, i, html);
  337. // Template class attribute
  338. if (template.getAttribute('class')) {
  339. template.className = this._replaceVariables(this, tempo_info, i, template.className);
  340. }
  341. // Template id
  342. if (template.getAttribute('id')) {
  343. template.id = this._replaceVariables(this, tempo_info, i, template.id);
  344. }
  345. html = this._applyAttributeSetters(this, i, html);
  346. fragment.appendChild(utils.getElement(template, html));
  347. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_COMPLETE, i, template));
  348. }
  349. },
  350. _createFragment : function (data) {
  351. if (data) {
  352. var tempo_info = {};
  353. var fragment = document.createDocumentFragment();
  354. // If object then wrapping in an array
  355. if (utils.typeOf(data) === 'object') {
  356. data = [data];
  357. }
  358. for (var i = 0; i < data.length; i++) {
  359. tempo_info.index = i;
  360. this.renderItem(this, tempo_info, data[i], fragment);
  361. }
  362. return fragment;
  363. }
  364. return null;
  365. },
  366. render : function (data) {
  367. // Check if starting event was manually fired
  368. if (!this.started) {
  369. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
  370. }
  371. this.clear();
  372. this.append(data);
  373. return this;
  374. },
  375. append : function (data) {
  376. // Check if starting event was manually fired
  377. if (!this.started) {
  378. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
  379. }
  380. var fragment = this._createFragment(data);
  381. if (fragment !== null) {
  382. this.templates.container.appendChild(fragment);
  383. }
  384. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, undefined, undefined));
  385. return this;
  386. },
  387. prepend : function (data) {
  388. // Check if starting event was manually fired
  389. if (!this.started) {
  390. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined));
  391. }
  392. var fragment = this._createFragment(data);
  393. if (fragment !== null) {
  394. this.templates.container.insertBefore(fragment, this.templates.container.firstChild);
  395. }
  396. utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, undefined, undefined));
  397. return this;
  398. },
  399. clear : function (data) {
  400. utils.clearContainer(this.templates.container);
  401. },
  402. tags : {
  403. 'if' : function (renderer, i, match, args, body) {
  404. var member_regex = utils.memberRegex(i);
  405. var expr = args[0].replace(/&amp;/g, '&');
  406. expr = expr.replace(new RegExp(member_regex, 'gi'), function (match) {
  407. return 'i.' + match;
  408. });
  409. var blockRegex = new RegExp(renderer.templates.tag_brace_left + '[ ]?else[ ]?' + renderer.templates.tag_brace_right, 'g');
  410. var blocks = body.split(blockRegex);
  411. if (eval(expr)) {
  412. return blocks[0];
  413. } else if (blocks.length > 1) {
  414. return blocks[1];
  415. }
  416. return '';
  417. }
  418. },
  419. filters : {
  420. 'truncate' : function (value, args) {
  421. if (value !== undefined) {
  422. var len = 0;
  423. var rep = '...';
  424. if (args.length > 0) {
  425. len = parseInt(args[0]);
  426. }
  427. if (args.length > 1) {
  428. rep = args[1];
  429. }
  430. if (value.length > len - 3) {
  431. return value.substr(0, len - 3) + '...';
  432. }
  433. return value;
  434. }
  435. },
  436. 'format' : function (value, args) {
  437. if (value !== undefined) {
  438. var x = (value + '').split('.');
  439. var x1 = x[0];
  440. var x2 = x.length > 1 ? '.' + x[1] : '';
  441. while (NUMBER_FORMAT_REGEX.test(x1)) {
  442. x1 = x1.replace(NUMBER_FORMAT_REGEX, '$1' + ',' + '$2');
  443. }
  444. return x1 + x2;
  445. }
  446. },
  447. 'upper' : function (value, args) {
  448. return value.toUpperCase();
  449. },
  450. 'lower' : function (value, args) {
  451. return value.toLowerCase();
  452. },
  453. 'trim' : function (value, args) {
  454. return utils.trim(value);
  455. },
  456. 'replace' : function (value, args) {
  457. if (value !== undefined && args.length === 2) {
  458. return value.replace(new RegExp(args[0], 'g'), args[1]);
  459. }
  460. return value;
  461. },
  462. 'append' : function (value, args) {
  463. if (value !== undefined && args.length === 1) {
  464. return value + '' + args[0];
  465. }
  466. return value;
  467. },
  468. 'prepend' : function (value, args) {
  469. if (value !== undefined && args.length === 1) {
  470. return args[0] + '' + value;
  471. }
  472. return value;
  473. },
  474. 'default' : function (value, args) {
  475. if (value !== undefined && value !== null) {
  476. return value;
  477. }
  478. if (args.length === 1) {
  479. return args[0];
  480. }
  481. return value;
  482. },
  483. 'date' : function (value, args) {
  484. if (value !== undefined && args.length === 1) {
  485. var date = new Date(value);
  486. var format = args[0];
  487. if (format === 'localedate') {
  488. return date.toLocaleDateString();
  489. } else if (format === 'localetime') {
  490. return date.toLocaleTimeString();
  491. } else if (format === 'date') {
  492. return date.toDateString();
  493. } else if (format === 'time') {
  494. return date.toTimeString();
  495. } else {
  496. var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  497. var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  498. var DATE_PATTERNS = {
  499. 'YYYY' : function (date) {
  500. return date.getFullYear();
  501. },
  502. 'YY' : function (date) {
  503. return date.getFullYear().toFixed().substring(2);
  504. },
  505. 'MMMM' : function (date) {
  506. return MONTHS[date.getMonth()];
  507. },
  508. 'MMM' : function (date) {
  509. return MONTHS[date.getMonth()].substring(0, 3);
  510. },
  511. 'MM' : function (date) {
  512. return utils.pad((date.getMonth() + 1).toFixed(), '0', 2);
  513. },
  514. 'M' : function (date) {
  515. return date.getMonth() + 1;
  516. },
  517. 'DD' : function (date) {
  518. return utils.pad(date.getDate().toFixed(), '0', 2);
  519. },
  520. 'D' : function (date) {
  521. return date.getDate();
  522. },
  523. 'EEEE' : function (date) {
  524. return DAYS[date.getDay()];
  525. },
  526. 'EEE' : function (date) {
  527. return DAYS[date.getDay()].substring(0, 3);
  528. },
  529. 'E' : function (date) {
  530. return date.getDay();
  531. },
  532. 'HH' : function (date) {
  533. return utils.pad(date.getHours().toFixed(), '0', 2);
  534. },
  535. 'H' : function (date) {
  536. return date.getHours();
  537. },
  538. 'mm' : function (date) {
  539. return utils.pad(date.getMinutes().toFixed(), '0', 2);
  540. },
  541. 'm' : function (date) {
  542. return date.getMinutes();
  543. },
  544. 'ss' : function (date) {
  545. return utils.pad(date.getSeconds().toFixed(), '0', 2);
  546. },
  547. 's' : function (date) {
  548. return date.getSeconds();
  549. },
  550. 'SSS' : function (date) {
  551. return utils.pad(date.getMilliseconds().toFixed(), '0', 3);
  552. },
  553. 'S' : function (date) {
  554. return date.getMilliseconds();
  555. },
  556. 'a' : function (date) {
  557. return date.getHours() < 12 ? 'AM' : 'PM';
  558. }
  559. };
  560. format = format.replace(/(\\)?(Y{2,4}|M{1,4}|D{1,2}|E{1,4}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|a)/g, function (match, escape, pattern) {
  561. if (!escape) {
  562. if (DATE_PATTERNS.hasOwnProperty(pattern)) {
  563. return DATE_PATTERNS[pattern](date);
  564. }
  565. }
  566. return pattern;
  567. });
  568. return format;
  569. }
  570. }
  571. return '';
  572. }
  573. }
  574. };
  575. /*!
  576. * Prepare a container for rendering, gathering templates and
  577. * clearing afterwards.
  578. */
  579. tempo.prepare = function (container, params) {
  580. if (typeof container === 'string') {
  581. container = document.getElementById(container);
  582. }
  583. var templates = new Templates(params);
  584. templates.parse(container);
  585. return new Renderer(templates);
  586. };
  587. tempo.test = {
  588. 'utils' : utils,
  589. 'templates': new Templates({}),
  590. 'renderer' : new Renderer(new Templates({}))
  591. };
  592. return tempo;
  593. })(Tempo || {});