воскресенье, 10 июля 2011 г.

Не теряйте домены!

У ребят и брайн куба проэкспайрился домен. В их блоге есть были довольно интресные наработки по CI 2 Template Library. Копипастю себе в блог, для истории.
В части 2 цикла статей о подготовке фреймворка мы работаем над шаблонизатором. Определим наши задачи:
  • Один файл шаблона с header и footer областями, разный контент.
  • Возможность подгружать любые файлы в header и footer области шаблона, а так же выбирать разный шаблон.
  • Возможность скреплять методы add_header и add_footer в цепочки используя method chaining.
  • Предусмотреть поддержку AJAX.

Зачем это нужно

Шаблонизатор позволяет избегать повторов в разметке: постоянно копировать header и footer области в каждом файле view. Конечно, всегда можно создать 2 файла header_view.php и footer_view.php, и подгружать их отдельно. Однако, это требует повторов в коде и определенной концентрации внимания на подобных мелочах. Метод, который я предлагаю, намного удобнее. Идея этого шаблонизатора взята из дебрей nettuts, за что им огромный респект.

Как это будет работать

В каждом контроллере мы будем использовать новую библиотеку (library), которая будет осуществлять все операции по подгрузке шаблона. Вот как будет выглядеть контроллер Home из первой части цикла статей:
class Home extends Controller {

 function  __construct() {
  parent::__construct();

  $this->tpl->set_title('My title');
 }

 public function index() {
  $data = array();
  if ($this->_isAjaxRequest()) {
   $this->load->view('home_view_ajax', $data);
  }
  else {
   $this->tpl->view('home_view', $data);
  }
 }
 
 private function _isAjaxRequest() {
  return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
 }
}

Создаем нашу библиотеку

Создадим app/libraries/Tpl.php. Обратите внимание, первая буква названия написана в верхнем регистре, это важно. Обращаться к нашей библиотеке из CI мы будем тоже как «Tpl»
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/*
 * Code by BrainCube.ru - Smart solutions for the Web.
 * @author Max Degterev <max@braincube.ru>
 * @copyright (c) 2010 BrainCube
 */

class Tpl {
 private $CI;

 private $title_for_layout = NULL;

 private $title_separator = ' | ';

 private $header = array();
 private $footer = array();

 public function __construct() {
  $this->CI =& get_instance();
 }

 public function set_title($title) {
  $this->title_for_layout = $title;
  return $this;
 }

 public function view($view_name, $params = array(), $layout = 'default') {
  $rendered_view = $this->CI->load->view($view_name, $params, TRUE);

  if ($this->title_for_layout !== NULL) {
   $this->title_for_layout = $this->title_separator . $this->title_for_layout;
  }

  $this->CI->load->view('layouts/' . $layout, array(
   'content_for_layout' => $rendered_view,
   'title_for_layout' => $this->title_for_layout
  ));
 }

 public function __call($name, $arguments) {
  if (substr($name,0,4) == 'get_') {
   $property = substr($name,4);

   return $this->_parseIncludes($property);
  }
  else if (substr($name,0,4) == 'add_') {
   $property = substr($name,4);
   
   if (!isset($arguments[1])) {
    $this->CI->load->helper('url'); // Just in case!
    $project = ((stripos(base_url(), 'http://') !== FALSE) ? base_url() : 'http://'.base_url());
    array_push($this->$property, $project . $arguments[0]);
   }
   else {
    array_push($this->$property, $arguments[0]);
   }  
  }

  return $this;
 }

 private function _parseIncludes($arrayname) {
  $final_includes = '';

  foreach ($this->$arrayname as $include) {
   if (preg_match('/js$/', $include)) {
    $final_includes .= '<script src="' . $include . '"></script>';
   }
   elseif (preg_match('/css$/', $include)) {
    $final_includes .= '<link rel="stylesheet" href="' . $include . '" />';
   }
  }

  return $final_includes;
 }
}
Часть методов вполне понятна сама по себе. Дополнительные пояснения к коду:
  • 9-16 строки инициализируют переменные. $CI требуется для утилизации API CI, $title_separator – разделитель для тега <title> шаблона. Остальные переменные содержат необходимые для работы данные (сам заголовок, массив с файлами header и footer)
  • 18-20 получаем инстанцию CI для работы с API. Этот трюк необходим при работе над любой библиотекой CI т.к. прямой доступ к API мы имеем только в контроллерах.
  • 40-60 магия PHP5. Чтобы не дублировать одинаковый код для методов add_header и add_footer, мы воспользуемся magic методом __call. $name — вызываемый метод, $arguments — передаваемые ему аргументы. Сначала мы определяем требуемое действие. В случае вызова «get_» метода, мы передаем действие сторонней функции, которую мы рассмотрим чуть ниже. В случае «add_» метода мы добавляем в соответствующий массив ($header или $footer) новый элемент. Далее мы возвращаем инстанцию класса $this для возможности создавать цепочки методов. Обратите внимание на строку 51, этого действия можно избежать. В случае, если вы вводили адрес сайта в виде строки в переменной $config['base_url']. Поскольку мы внесли изменения в конфигурацию, при работе с localhost будет ошибка с загрузкой css и javascript. Чтобы исправить это недоразумение требуется небольшая проверка.
  • 62-75 метод, отвечающий за выдачу файлов в header и footer области шаблона. Определяем тип элемента и создаем соответствующие теги.
  • 27-38 метод, загружающий шаблон. Его мы будем вызывать из контроллеров. Ожидаемые параметры: файл view, массив данных и файл шаблона. По умолчанию используется «default». Далее мы получаем view в виде строки, готовим заголовок документа и вызываем шаблон. Данные из view файла передаются в общем массиве с заголовком и загружаются в шаблон.

Создаем шаблон по умолчанию

Для этой задачи возьмем за основу наработки замечательного ресурса HTML5 ★ Boilerplate. Архив содержит множество интересных файлов и папок, и определенно заслуживает отдельной статьи. Однако, нам требуются только папки css, js; и файлы index.html, apple-touch-icon.png, favicon.ico. Переместим все, кроме index.html, с которым нам придется поработать, в корень нашего CI. apple-touch-icon.png, favicon.ico это иконки нашего сайта для аппаратов Apple и обычных браузеров соответственно.
Содержимое архива
Содержимое архива с HTML5 Boilerplate
Создадим app/views/layouts/default.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
 * Code by BrainCube.ru - Smart solutions for the Web.
 * @author Max Degterev <max@braincube.ru>
 * @copyright (c) 2010 BrainCube
 */
 
// Get year property for copyright and some other stuff.
$year = (date("Y") == '2010') ? date("Y") : ('2010-'.date("Y"));
$project = ((stripos(base_url(), 'http://') !== FALSE) ? base_url() : 'http://'.base_url());
?>
<!doctype html>

<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
<!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
<!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
<!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
<head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

 <meta name="description" content="" />
 <meta name="author" content="BrainCube.ru - Smart solutions for the Web" />
 <meta name="copyright" content="" />
 <title>Company<?php echo $title_for_layout ?></title>

 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="shortcut icon" href="<?php echo $project;?>favicon.ico">
 <link rel="apple-touch-icon" href="<?php echo $project;?>apple-touch-icon.png">

 <link rel="stylesheet" href="<?php echo $project;?>css/style.css?v=2">
 <script src="<?php echo $project;?>js/libs/modernizr-1.6.min.js"></script>
 <?php echo $this->tpl->get_header(); ?>
</head>

<body>

 <header>
  
 </header>

 <?php echo $content_for_layout; ?>

 <footer>
  <?php echo $year; ?> &copy; Company
 </footer>
 
 <script src="//ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js"></script>
 <script>!window.jQuery && document.write(unescape('%3Cscript src="<?php echo $project;?>js/libs/jquery-1.4.4.js"%3E%3C/script%3E'))</script>
 <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.6/jquery-ui.min.js"></script>
 <script>!window.jQuery && document.write(unescape('%3Cscript src="<?php echo $project;?>js/libs/jquery-ui-1.8.6.custom.min.js"%3E%3C/script%3E'));</script>
 
 <script src="<?php echo $project;?>js/script.js"></script>
 <?php echo $this->tpl->get_footer(); ?>

 <!--[if lt IE 7 ]>
  <script src="<?php echo $project;?>js/libs/dd_belatedpng.js"></script>
  <script src="<?php echo $project;?>js/ie6fix.js"></script>
 <![endif]-->

</body>
</html>
HTML прост как топор. Небольшие пояснения по части PHP:
  • 9-10 получаем переменные $year — требуется для футера, а также $project, которая указывает абсолютный путь до файлов в зависимости от base_url().
  • 26, 43 выводят передаваемые в шаблон заголовок и контент.
  • 34, 55 отвечают за выгрузку дополнительных файлов в области хедера и футера.

Проверим в действии!

Для этого добавим нашу библиотеку в автозагрузку. В app/config/autoload.php:
$autoload['libraries'] = array('Tpl');
Приведем наш контроллер app/controllers/home.php к следующему виду:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/*
 * Code by BrainCube.ru - Smart solutions for the Web.
 * @author Max Degterev <max@braincube.ru>
 * @copyright (c) 2010 BrainCube
 */

class Home extends Controller {

 function  __construct() {
  parent::__construct();
  $this->tpl
    ->set_title('Welcome to our index page')
    ->add_header('css/handheld.css')
    ->add_footer('js/plugins.js');
 }

 public function index() {
  $data = array();
  if ($this->_isAjaxRequest()) {
   $this->load->view('home_view_ajax', $data);
  }
  else {
   $this->tpl->view('home_view', $data);
  }
 }
 
 private function _isAjaxRequest() {
  return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
 }
}
Разберемся, что тут происходит:
  • 12 строка мы загружаем в нашу библиотеку данные о заголовке и дополнительных файлах областей header и footer.
  • 17-22 мы определяем тип запроса. В случае, если это AJAX запрос, шаблонизатор нам не требуется, и мы просто загружаем файл view.

Заключение

Можете смело похлопать себя по плечу. Эта штука работает!
После проделанной работы у нас на руках гибкий шаблонизатор с массой возможностей. Однако, это еще не все. В части 3 мы рассмотрим как изменить стандартную страницу ошибки 404 фреймворка.

Комментариев нет:

Отправить комментарий