Pull to refresh

Шаблоны

Reading time9 min
Views3K
Сейчас на 99.9% сайтов используются PHP шаблоны. Во многих движках это является неотемлемой частью. Я хочу представить вам совершенно другой подход.
Недавно я разрабатывал тестовое задание и решил попробовать использовать JS шаблоны.

Вот, что в результате получилось:

1) Загрузка начинается с парсинга URL с помощью .htaccess и возвратом необходимой HTML-страницы.
2) Браузер подгружает с сервера javascript.
3) Запускает первую инициализацию: подгрузка файлов шаблонов (.skin) и файла необходимого языка (.lang — например, в виде XML).
4) Подгружает данные и рендерит их в браузер, используя подгруженные шаблоны.
5) Обрабатывает нажатие клавиш, ссылок итд. (про обработку back и next возможно расскажу в другой теме :) )
6) При необходимости, посылает запрос на сервер на приём новых данных.

1. AJAX Модуль

Модуль для получения XML/JSON и отправке на дальнейшее расстерзание в указанную JS-функцию. При надобности, возможен запуск нескольких последовательных запросов.
function ajax_load(url,parcer,par,next){
  var req, ab, done=0;
  this.request = function(){
   if (window.XMLHttpRequest)
     ajaxRequest = new XMLHttpRequest();
   else if (window.ActiveXObject){
     ajaxRequest = new ActiveXObject(«Msxml2.XMLHTTP»);
     if (!ajaxRequest)
      ajaxRequest = new ActiveXObject(«Microsoft.XMLHTTP»);
   }
   return ajaxRequest;
  }
 
  this.ReqStatus=function() {
   if (req.readyState == 4) {
     clearTimeout(ab);
     if (req.status == 200){
      parcer(req,par);
      if (next)
        ajax_load(next[0],next[1],next[2],next[3]);
     } else
      alert(«AJAX Error: \n» + req.statusText + "\n" + url);
   }
  }
 
  if (!url)
   return parcer(par);

  req=this.request();
  req.onreadystatechange = this.ReqStatus;
  req.open(«GET», url, true);
  req.send(null);
  ab = window.setTimeout(function(){req.abort();}, 10000);
  return req;
}
* This source code was highlighted with Source Code Highlighter.
C помощью этой функции, мы как раз и будем подгружать данные с сервера без перезагрузки основной страницы.
На входе — URL, функция JS для обработки request, дополнительные параметры для функции, [URL для следующего последовательного запроса, функция JS, дополнительные параметры,[URL...]]

2. Skin Модуль

Модуль для преобразования Шаблона с параметрами в HTML.
function Skin(){
  var that = this;
  this.skin = new Array();

  this.trace = function (file){
   var f,x;
   var rx=/<!SKIN '([\w\d]+)'>([\s\S]*)<!END '\1'>/gim
   f=that.skin[file].match(rx);
   for (var i in f){
     x=rx.exec(f[i]);
     that.skin[file+"."+x[1]]=x[2];
     that.trace(file+"."+x[1]);
     that.skin[file]=that.skin[file].replace(f[i],'');
   }
  }

  this.use = function (html){
     var str;
   do{
     if (!(str = html.match(/%([\w\d.]+)%/gim)))
       break;
     for (var i in str){
      var tp=str[i].replace(/%/gi,'');
      if (that.skin[tp])
        html=html.replace(str[i],that.skin[tp]);
      else
        html=html.replace(str[i],'');
     }
   }while(1);
   return html;
  }

  this.redraw = function (window,html){
   if (!document.getElementById(window))
     alert(«ERR with ID „+window);
   document.getElementById(window).innerHTML=that.use(html);
  }
}
* This source code was highlighted with Source Code Highlighter.

В модуле имеются функции:
trace — для генерации дополнительных шаблонов из строк, вида:
<!SKIN 'название_шаблона_1'>… ...<!END 'название_шаблона_1'>
Преобразуя их в %название_шаблона_1%, %название_шаблона_1.название_вложенного_шаблона%, %название_шаблона_2%.
use — подставление параметров в шаблон. Параметры в шаблонах хранятся в %название_параметра%.
redraw — записывает преобразованный шаблон в элемент, используя HTML id для идентификации нужного элемента.

3. Использование шаблонов

Возьмём маленькую часть от интернет магазина. Пусть она просто показывает- был пользователь или нет и выводила некоторую информацию в виде таблицы.
К примеру, пусть шаблон (main.skin) выглядит так:
<!SKIN 'BODY'>
<div id=“menu»>
 <span class=«goods_name» onclick=«ShowInfo();»>%LANG.INFO%</span>
 <span class=«goods_name» onclick=«ChangeLang('rus')»>%LANG.RUSSIAN%</span>
 <span class=«goods_name» onclick=«ChangeLang('eng')»>%LANG.ENGLISH%</span>
</div>
<div id=«main»>
 <!SKIN 'WELCOME'>
  <!SKIN 'FIRST'>%LANG.WELCOME_NEW%<!END 'FIRST'>
  <!SKIN 'DATE'>%DAY%.%MONTH%.%YEAR%<!END 'DATE'>
  <!SKIN 'LAST'>%LANG.WELCOME_OLD% %SKIN.WELCOME.DATE%<!END 'LAST'>
 <!END 'WELCOME'>
</div>



<!SKIN 'ORDER'>
 <table class=«price» width=«500px»>
  %PRICES%
  <!SKIN 'PIECE'><tr id="%GOODS_ID%"><td class=«order_name»>%GOODS.NUM%. %GOODS.NAME%</td><td>%GOODS.PRICE%</td></tr><!END 'PIECE'>
 </table>
<!END 'ORDER'>
<!SKIN 'LOADING'>
 %LANG.LOADING%
<!END 'LOADING'>


<!END 'BODY'>
* This source code was highlighted with Source Code Highlighter.
А XML файл с языковым интерфейсом (rus.xml):
<?xml version=«1.0» encoding=«windows-1251»?><objects>
<language>rus</language>
<word name=«ENCODING»>windows-1251</word>
<word name=«INFO»>Информация</word>
<word name=«SUPPORT»>Связаться с нами</word>
<word name=«ENGLISH»>English</word>
<word name=«RUSSIAN»>Русский</word>
<word name=«WELCOME_NEW»>Добро пожаловать, новый пользователь.</word>
<word name=«WELCOME_OLD»>Ваш последний визит был</word>
<word name=«LOADING»>Идёт соединение с Базой сайта</word>
</objects>
* This source code was highlighted with Source Code Highlighter.
Теперь создаём JS-код для начальной загрузки шаблона и передачи управления (main.js):
//Создаём объект для шаблонов
var skins=new Skin();

//Добавление в шаблоны, загруженного в виде XML, шаблонов языка %LANG.xxxxxx%
function SetLanguageXML(req){
 var r=req.responseXML.getElementsByTagName(«word»),i;
 for (i=0;i<r.length;i++)
  skins.skin['LANG.'+r[i].attributes.getNamedItem(«name»).value]=r[i].firstChild.nodeValue;
}

//Парсинг загруженного шаблона в %SKIN.xxxxxx%
function TraceResponse(req){
 skins.skin['SKIN']=req.responseText;
 skins.trace('SKIN');
}

//Загружаем язык, загружаем шаблон и после всего этого выходим на инициализацию
ajax_load(«rus.xml»,SetLanguageXML,null,[«main.skin»,TraceResponse,null,[null,RedrawAll]]);
* This source code was highlighted with Source Code Highlighter.

Добавляем нехватающие функции, которые выводят шаблон в окно браузера после инициализации и те, которые используются в шаблоне на onclick:
//Функция для изменения языка по onclick
  function ChangeLang(lang){
   //Если иметь rus.xml и eng.xml — то переход на другой язык потребует только перезагрузки файла с языком.
    ajax_load(lang+".xml",SetLanguageXML,null,[null,RedrawAll]);
  }
  
//Функция для регенерации начальной страницы при инициализации
  function RedrawAll(req){
   //Надо сказать, что в html обязанно быть объявлен элемент с id='body'. Например, <div id='body'></div>.
    skins.redraw('body',skins.skin['SKIN.BODY']);
    DrawWelcome();
  }

//Функции для работы с Cookie
  function createCookie(name,value,days) {
    if (days) {
      var date = new Date();
      date.setTime(date.getTime()+(days*24*60*60*1000));
      var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
  }
  
  function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
      var c = ca[i];
      while (c.charAt(0)==' ') c = c.substring(1,c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
  }

//Выводим Welcome screen ;)
  function DrawWelcome(){
    var date,t;
   //Есть ли Cookie? Тогда пользователь у нас уже был. Считываем, когда и выводим эту информацию
    if (t=readCookie('date')){
      date=new Date;
      date.setTime(t*1000);
      skins.skin['DAY']=date.getDate();
      skins.skin['MONTH']=date.getMonth();
      skins.skin['YEAR']=date.getFullYear();
      show=skins.skin['SKIN.BODY.WELCOME.LAST'];
    }else{
   //Если в первый раз на сайте — создаём Cookie и выводим другую информацию
      date = new Date;
      createCookie('date',parseInt(date.getTime() / 1000),7);
      show=skins.skin['SKIN.BODY.WELCOME.FIRST'];
    }

    skins.redraw('main',show);
  }
* This source code was highlighted with Source Code Highlighter.
Замечу, что во многих случаях движек для шаблонов позволяет делать 'исполняеммые' шаблоны, что правда делает их сложными в разборе. Здесь это легко реализованно с помощью Javascript:
//Подгрузить с сервера и вывести info.xml в ясном формате (onclick функция)
  function ShowInfo(){
    skins.redraw('main',skins.skin['SKIN.BODY.LOADING']);
    ajax_load(«info.xml»,DrawInfo);
  }

  function DrawInfo(req){
    var i;
    var p=req.responseXML.getElementsByTagName(«pricelist»);
    //Обнуляем список товаров
    skins.skin['PRICES']="";
    skins.skin['GOODS.NUM']=1;
    for (i=0;i<p.length;i++){
      //Устанавливаем параметры для шаблона
      skins.skin['GOODS.NAME']=p[i].getElementsByTagName(«name»)[0].firstChild.nodeValue;
      skins.skin['GOODS.PRICE']=p[i].getElementsByTagName(«price»)[0].firstChild.nodeValue;
      skins.skin['GOODS.ID']=p[i].getElementsByTagName(«id»)[0].firstChild.nodeValue;
      //Добавляем в список товаров, готовые (отрендеренные) части таблицы
      skins.skin['PRICES']+=skins.use(skins.skin['SKIN.BODY.ORDER.PIECE']);
      skins.skin['GOODS.NUM']++;
    }
    skins.redraw('main',skins.skin['SKIN.BODY.ORDER']);
  }
* This source code was highlighted with Source Code Highlighter.
При этом файл info.xml необходимо будет генерить в таком виде:
<?xml version=«1.0» encoding=«windows-1251»?><objects>
  <pricelist>
    <id>1</id>
    <name>Хлеб</name>
    <price>12</price>
  </pricelist>
  <pricelist>
    <id>2</id>
    <name>Арбуз</name>
    <price>200</price>
  </pricelist>
  <pricelist>
    <id>3</id>
    <name>Картошка</name>
    <price>32</price>
  </pricelist>
</objects>
* This source code was highlighted with Source Code Highlighter.
Ну и наконец, index.html. Да, я не ошибся- именно html:
<html><head>
  <meta http-equiv=«Content-Type» content=«text/html; charset=windows-1251»>
</head>
<body><div id='body'></div>
<script type=«text/javascript» src=«main.js»></script>
</body></html>
* This source code was highlighted with Source Code Highlighter.

Работающий пример можно посмотреть здесь: zcn.ru/market

4. Недостатки и плюсы данного подхода

Недостатков мало, но они достаточно существенны:
— Невозможность без введения дополнительных средств работать с URL.
— Невозможно использовать клавиши «назад» и «вперед». Если автор не предусмотрел эмуляцию.
— Поисковики не умеют парсить Javascript и AJAX.
— Вероятность отключенного JS у пользователя.

О данных проблемах и методах решения было сказанно сдесь — habrahabr.ru/blog/webdev/44753.html, по этому я не буду заострять внимание на них.

Плюсы тоже существенны:
— Довольно сильно (при хорошей оптимизации) снижается нагрузка на сервер.
— Для изменения вида сайта не требуется никакой доработки PHP-кода — только ccs/javascript и шаблоны.
— Прозрачный и достаточно ясный код Шаблонов и обработчика. Очень просто добавлять новые «страницы».
— Простое изменение различных настроек, простой подгрузкой других XML-файлов. Языка, Валюты (доллар, руб) и пр. Без перезагрузки страницы.
Tags:
Hubs:
Total votes 58: ↑51 and ↓7+44
Comments92

Articles