Skip to content

Template Binding

html
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<body>

<div id="one-way-bind">
  <h3>{{language}}</h3>
  <select id="lang">
    <option value="english">English</option>
    <option value="korean">한국어</option>
  </select>

  <h3>{{timeTable}}</h3>
  <table border="1">
    <thead>
    <tr>
      <th>{{no}}</th>
      <th>{{time}}</th>
      <th>{{updateBtn}}</th>
    </tr>
    </thead>
    <tbody>
    <tr>
      <td>1</td>
      <td>{{time1}}</td>
      <td>
        <button type="button" id="update-time-1">{{btnText}}</button>
      </td>
    </tr>
    <tr>
      <td>2</td>
      <td>{{time2}}</td>
      <td>
        <button type="button" id="update-time-2">{{btnText}}</button>
      </td>
    </tr>
    <tr>
      <td>3</td>
      <td>{{time3}}</td>
      <td>
        <button type="button" id="update-time-3">{{btnText}}</button>
      </td>
    </tr>
    </tbody>
  </table>
</div>

<script>
const useTemplate = (selector) => {
  const TEMPLATE_PROPERTY = '$template';
  const elem = document.querySelector(selector);
  const delimiters = ['{{', '}}'];
  const cacheData = {};

  const changeTextNode = elem => {
    let startPosition = 0;
    let endPosition = 0;
    let val = '';
    let copiedTemplate = '';

    //템플릿 캐싱
    if(!elem.hasOwnProperty(TEMPLATE_PROPERTY)){
      elem[TEMPLATE_PROPERTY] = elem.textContent;
    }

    copiedTemplate = elem[TEMPLATE_PROPERTY];

    while(startPosition > -1){
      val = '';
      //시작 위치를 찾는 다
      startPosition = copiedTemplate.indexOf(
        delimiters[0],
        startPosition
      );
      //데이터가 모두 변경되어 탬플릿 키워드가 없을 때
      if(startPosition === -1) break;

      //종료 위치를 찾는 다
      endPosition = copiedTemplate.indexOf(
        delimiters[1],
        startPosition + delimiters[0].length - 1
      );
      //변수 key값을 찾는 다.
      const variableName = copiedTemplate.substring(
        startPosition + delimiters[0].length,
        endPosition
      );

      //데이터에 변수값이 있을 경우 반영한다.
      if(typeof cacheData[variableName] !== 'undefined'){
        val = cacheData[variableName];
      }

      copiedTemplate = copiedTemplate.replace(
        delimiters[0] + variableName + delimiters[1],
        val,
      );
    }

    if(elem.textContent !== copiedTemplate){
      elem.textContent = copiedTemplate;
    }
  };

  const cacheObj = data => {
    for(const key in data){
      cacheData[key] = data[key];
    }
  };

  const traversal = elem => {
    elem.childNodes.forEach(node => {
      if(node.childNodes.length > 0){
        traversal(node);
      }else{
        changeTextNode(node);
      }
    });
  };

  const bindData = data => {
    cacheObj(data);
    traversal(elem);
  };

  return {bindData}
}
</script>
<script>
const LANGUAGES = {
  english: {
    no: 'No.',
    time: 'Time',
    updateBtn: 'Button',
    timeTable: 'Time Table',
    language: 'Language',
    btnText: 'Update Button'
  },
  korean: {
    no: '넘버',
    time: '시간',
    updateBtn: '버튼',
    timeTable: '시간표',
    language: '한국어',
    btnText: '시간 변경'
  }
};

const template = useTemplate('#one-way-bind');
template.bindData(LANGUAGES.english);

const updateDate = index => {
  template.bindData({
    [`time${index}`]: +new Date()
  });
};

const lang = document.querySelector('#lang');
lang.onchange = () => {
  template.bindData(LANGUAGES[lang.value]);
};

Array
  .from({length: 3}, (_v, i) => i + 1)
  .forEach((i) => {
    updateDate(i);
    document.querySelector(`#update-time-${i}`).onclick = ((index => {
      return () => {
        updateDate(index);
      };
    })(i));
  })
</script>
</body>
</html>