Curly braces

How to dynamically replace curly braces with javascript

Posted by Kuligaposten on August 24, 2018

How to replace curly braces with javascript. In this example we will use the countries API.
Here is the HTML markup

<div id="countries" class="container">
	<p>{{total}} People lives in the region</p>
	<div class="row gx-4 gy-4">
		<div class="col-12 col-lg-6 col-xxl-4" app-repeat="jsonData">
			<div class="card shadow h-100">
                <img class="card-img-top w-100 d-block shadow" data-src="{{flags.svg}}" />
				<div class="card-body">
					<h4 class="text-center card-title">{{name.common}}</h4>
					<p class="card-text">Region:<span class="float-end"><strong>{{region}}</strong></span>
						<br />Subregion:<span class="float-end"><strong>{{subregion}}</strong></span>
						<br />Captital:<span class="float-end"><strong>{{capital}}</strong></span>
						<br />Population:<span class="float-end"><strong>{{population}}</strong></span>
					</p>
				</div>
			</div>
		</div>
	</div>
</div>

Here is the ReplaceCurlyBraces class. Feel free to copy the class and use it in your project

class ReplaceCurlyBraces {
  constructor(url, ids, totalPropertyName, filterPropertyName) {
    this.app = {
      template: {},
      data: {},
      originalData: null,
    };
    this.ids = ids;
    this.url = url;
    this.totalPropertyName = totalPropertyName || false;
    this.filterPropertyName = filterPropertyName || false;
    this.totalProperty = 'jsonData';
    this.initialize();
  }

  initialize() {
    this.getData(this.url, this.ids);
  }

  displayData(id, data) {
    let htmlObj = document.getElementById(id);
    if (!htmlObj) {
      alert(`The id ${id} doesn't exist on the page`);
      return;
    }

    let templateHTML,
      template = htmlObj.cloneNode(true);
    if (this.app.template.hasOwnProperty(id)) templateHTML = this.app.template[id];
    else (this.app.template[id] = template), (templateHTML = template);

    let html = templateHTML.cloneNode(true),
      arr = [...html.getElementsByTagName('*')].filter((elem) => elem.getAttribute('app-repeat') !== null);

    const replaceData = (element, data) => {
      Array.from(element.attributes).forEach((attribute) => {
        attribute.value = replaceCurlyBraces(attribute.value, data);
      });
      element.innerHTML = replaceCurlyBraces(element.innerHTML, data);
      const image = element.querySelector('img');
      if (image) image.src = image.getAttribute('data-src');
      return element;
    };

    const replaceCurlyBraces = (str, data) => {
      return str.replace(/{{(.+?)}}/g, (_, g1) => {
        const keys = g1.split('.');
        let value = data;
        for (let key of keys) {
          if (value.hasOwnProperty(key)) {
            value = value[key];
          } else {
            return g1;
          }
        }
        return value;
      });
    };

    arr.forEach((item) => {
      let repeatThis = data[item.getAttribute('app-repeat')];
      item.removeAttribute('app-repeat');
      if (repeatThis && typeof repeatThis === 'object') {
        if (!repeatThis.length) repeatThis = [repeatThis];
        repeatThis.forEach((obj, index) => {
          let rowClone = item.cloneNode(true);
          rowClone = replaceData(rowClone, obj);
          if (index === repeatThis.length - 1) item.parentNode.replaceChild(rowClone, item);
          else item.parentNode.insertBefore(rowClone, item);
        });
        html.innerHTML = html.innerHTML.replace(/{{(.+?)}}/g, (_, g1) => data[g1] || g1);
        htmlObj.parentNode.replaceChild(html, htmlObj);
      }
    });
  }

  getData(url, ids) {
    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        this.app.data[this.totalProperty] = [...data];
        this.app.originalData = { ...this.app.data };
        this.calculateTotal();
        ids.forEach((id) => this.displayData(id, this.app.data));
      })
      .catch((error) => {
        console.warn('Error:', error);
      });
  }

  filterAndTotal(filter, id) {
    if (!this.app.data) {
      this.getData(this.url, this.ids);
    }

    if (this.filterPropertyName) {
      const filtered = this.app.originalData[this.totalProperty].filter((item) => {
        return item[this.filterPropertyName].toLowerCase().includes(filter.toLowerCase());
      });

      if (this.totalProperty) {
        if (this.totalPropertyName) {
          const total = filtered.reduce((currentTotal, item) => {
            return item[this.totalPropertyName] + currentTotal;
          }, 0);
          this.app.data.total = total;
        }
      }
      if (filter === '*') {
        this.app.data[this.totalProperty] = this.app.originalData[this.totalProperty].slice();
        if (this.totalProperty) {
          if (this.totalPropertyName) {
            const total = this.app.data[this.totalProperty].reduce((currentTotal, item) => {
              return item[this.totalPropertyName] + currentTotal;
            }, 0);
            this.app.data.total = total;
          }
        }
      } else {
        this.app.data[this.totalProperty] = filtered.slice();
      }
      this.displayData(id, this.app.data);
    } else {
      console.warn('filterPropertyName is not provided.');
    }
  }

  calculateTotal() {
    if (this.totalProperty) {
      if (this.totalPropertyName) {
        const total = this.app.data[this.totalProperty].reduce((currentTotal, item) => {
          return item[this.totalPropertyName] + currentTotal;
        }, 0);
        this.app.data.total = total;
      } else {
        console.warn('totalPropertyName is not provided.');
      }
    }
  }
}

CSS just one class for the image in the card

.card-img-top {
  height: 200px;
  width: 100%;
  object-fit: cover;
}

This is how we can use the ReplaceCurlyBraces class

const $ = (el, all = false) => {
  el = el.trim();
  if (all) return [...document.querySelectorAll(el)];
  else return document.querySelector(el);
};

// EventListeners
const on = (type, el, listener, all = false) => {
  let selectEl = $(el, all);
  if (selectEl) {
    if (all) selectEl.forEach((e) => e.addEventListener(type, listener));
    else selectEl.addEventListener(type, listener);
  }
};
// Create an instance of the ReplaceCurlyBraces class
const fields = 'fields=name,capital,region,subregion,flags,population';
const url = `https://restcountries.com/v3.1/all?${fields}`;
const ids = ['countries'];
const myApp = new ReplaceCurlyBraces(url, ids, 'population', 'region');

on('click', '#americas', () => myApp.filterAndTotal('Americas', ids));
on('click', '#asia', () => myApp.filterAndTotal('Asia', ids));
on('click', '#europe', () => myApp.filterAndTotal('Europe', ids));
on('click', '#africa', () => myApp.filterAndTotal('Africa', ids));
on('click', '#oceania', () => myApp.filterAndTotal('Oceania', ids));
on('click', '#all', () => myApp.getData(url, ids));

go back

Here is a live example of the code above, click first the loadJson button. Normally you don't have the load json button in the DOM. It's here so you easy can see the markup

{{total}} People lives in the region

{{name.common}}

Region:{{region}}
Subregion:{{subregion}}
Captital:{{capital}}
Population:{{population}}