Туториал: компонент интерактивной SVG картограммы для InstantCMS 2

от автора

Сложность: средняя.
Необходимое время: 30 мин.
В статье представлена инструкция по созданию своего компонента для движка InstantCMS2. В конце статьи приведена ссылка на архив с исходными кодами и содержимым всех файлов из этой инструкции. Пример внешнего вида компонента, который можно создать, используя данный туториал, представлен на иллюстрации (картинка кликабельна).

Наглядное представление реального уровня зарплат в Российской Федерации

Для начала несколько слов про движок соц.сети / сообщества / блогосоциальной сети InstantCMS2. Эта бесплатная CMS может являться отличным компромиссом, возможно, лучшим из существующих.
В базовой версии уже заложен более богатый функционал по сравнению с LiveStreet CMS.

Скриншот сравнения функционала не привожу, потому что по ссылке дана информация не по самой последней версии движка InstantCMS.

Достоинства и недостатки InstantCMS2

Из минусов — количество модулей, дополнений, тем для данного движка достаточно ограничено. Качество технической поддержки немного хромает. Живого активного сообщества вокруг данного движка нет, а регистрация на форуме вообще только по приглашению. Но все эти минусы с лихвой перекрывает факт бесплатности движка InstantCMS 2.

Из плюсов — из коробки предоставляется сразу: форум, профили пользователей с возможность добавления в друзья, статьи, блоги, новости, группы, фотоальбомы, статичные страницы сайта. А также комментарии, ленты RSS, поля RSS, возможность настраивать главное меню, нормальная модерация и вполне удобная админка.

Скачать движок InstantCMS 2 с функцией авто-установки можно с официального сайта проекта.
Процесс установки хорошо документирован и интуитивно понятен.

Свой компонент для InstantCMS2

Перейдем непосредственно к вопросу написания отдельного компонента.

Для создания нового компонента создайте папку, в которой будет ваш компонент (назовем его newcomponent), в директории \SiteDirectory\system\controllers\, т.е полный адрес к созданной директории будет \SiteDirectory\system\controllers\newcomponent\ — все буквы в названии компонента должны быть строчными, это важно!

Далее в этой папке создаем файл frontend.php — это главный файл, без которого компонент не будет работать.

В этом файле создаем класс с таким же названием. Название класса совпадает с названием папки. И этот класс наследуется от системного класса cmsFrontend.
В этом классе мы имеем возможность добавлять методы, описывающие действия компонента.
Что такое действия компонента? Давайте взглянем на следующее изображение:

Каждый адрес страницы состоит из нескольких сегментов:

  1. /controller — Название компонента.
  2. /action — Название действия. Каждый компонент может иметь несколько действий внутри себя.
  3. /p1/p2/p3/… — Любое количество параметров, необходимых для этого действия.

Как определяется действие компонента? Определяется публичный метод в классе компонента, который называется actionНазваниеДействияСБольшойБуквы. Для главной страницы компонента siteaddress.ru/newcomponent/ необходимо определить метод actionIndex(). Для внутренней страницы компонента siteaddress.ru/newcomponent/act/ необходимо определить метод actionAct().

Файл frontend.php

<?php  class newcomponent extends cmsFrontend {  	public function actionIndex() { 		 		$template = cmsTemplate::getInstance(); 		 		$template->render('index'); 		 	}  	public function actionAct() { 		 		$errors = false; 		 		$form = $this->getForm('newForm'); 		 		$is_submitted = $this->request->has('submit'); 		 		$newForm = $form->parse($this->request, $is_submitted); 		 		if ($is_submitted){ 			$errors = $form->validate($this, $newForm); 			 			if (!errors) { 				$choropleth = $this->model->getChoropleth($newForm); 			} 			 			if (!errors) { 				cmsUser::addSessionMessage(LANG_FORM_ERRORS, 'error'); 			}  		} 		 		$template = cmsTemplate::getInstance(); 		 		$template->render('act', array( 			'form' => $form, 			'errors' => $errors, 			'newForm' => $newForm 		)); 		 	}  }  ?> 

Внимательный читатель заметил использование метода $this->model->getChoropleth().
Для использования методов модели в директории \SiteDirectory\system\controllers\newcomponent\ создаем файл model.php
Однако читать данные мы будем из файлов, поэтому поставим здесь заглушку. Описание файла модели приведено с целью обучения.

Файл model.php

<?php  class modelNewComponent extends cmsModel {  	public function getChoropleth($average_zarplata) { 		 		$choropleth = array(); 		 		return $choropleth; 		 	}  }  ?> 

Строка $template->render(‘index’); определяет вывод настоящего шаблона, который должен быть создан в директории \SiteDirectory\templates\default\controllers\newcomponent\. Где \default — название используемой темы на сайте (можно найти и скачать новую тему с сайта сообщества instantcms и изменить используемую тему через админку), папку \newcomponent необходимо будет создать самостоятельно, это папка для шаблонов нового компонента.
В этой папке должен быть создан файл index.tpl.php для главной страницы компонента, и act.tpl.php — для внутренней.

Файл index.tpl.php

<?php  	$this->setPageTitle('Заголовок страницы в названии окна браузера'); 	$this->addBreadcrumb('Название страницы в цепи хлебных крошек'); 	 	$this->addToolButton(array( 		'class' => 'item', 		'title' => 'Название кнопки в меню действий для перехода на внутреннюю страницу компонента', 		'href' => $this->href_to('act') 	)); 	 ?>  <h1>Главный заголовок страницы</h1> <p>Содержание страницы</p> 

Внутренняя страница компонента будет содержать форму выбора параметров.
Для начала создадим папку \forms\ в папке нашего компонента \SiteDirectory\system\controllers\newcomponent\.
В директории \SiteDirectory\system\controllers\newcomponent\forms\ создаем файл form_newForm.php
Форма будет очень простой, она предлагает пользователю выбрать два параметра из выпадающих списков.

Файл form_newForm.php

<?php  class formNewcomponentnewform extends cmsForm {  	public function init() { 		 		return array( 		 			array( 				'type' => 'fieldset', 				'childs' => array ( 					new fieldList('par1', array( 						'title' => 'Параметр1', 						'items' => array ( 							"ТекстовыйИдентификатор1"	 =>   	"ТекстовыйПараметр1", 							"ТекстовыйИдентификатор2"	 =>   	"ТекстовыйПараметр2"  						) 					)), 					new fieldList('par2', array( 						'title' => 'Параметр2', 						'items' => array ( 							1	 =>   	"1", 							2	 =>   	"2"  						) 					)) 				 				) 				 			) 		 		); 		 	}  }  ?> 
Интерактивная SVG картограмма

После этого перейдем к созданию шаблона для внутренней страницы компонента /act — создаем файл act.tpl.php и размещаем его в директории \SiteDirectory\templates\default\controllers\newcomponent\.
Для создания уникального сервиса воспользуемся разработкой пользователя KoGor (пользуясь случаем, хочу передать огромную благодарность за проведенный KoGor ‘ом труд и хорошо оформленную и интуитивно понятную статью) — инфограммой карты Российской Федерации с распределением по регионам.

В результате, у нас должна получится примерно такая приятная карта России:
Уровень зарплат работников здравоохранения по регионам России в 2014 году по данным Росстата

Файл act.tpl.php

<?php  	$this->setPageTitle('Заголовок страницы в названии окна браузера'); 	$this->addBreadcrumb('Название главной страницы компонента в цепи хлебных крошек', $this->href_to('')); 	$this->addBreadcrumb('Название страницы в цепи хлебных крошек');  	$arr_par1_id = array('ТекстовыйИдентификатор1'	=>	1	, 						'ТекстовыйИдентификатор2'	=>	2		 					);   	$filename='/upload/zarplata-'.$arr_par1_id[$_GET['par1']].'-'.$_GET['par2'].'.csv';	 	if (!isset ($_GET['par1']) || !isset ($_GET['par2'])) $filename='/upload/zarplata-1-1.csv';  	$this->renderForm($form, $newForm, array( 		'action' => '', 		'method' => 'get', 		'toolbar' => false 	), $errors); ?>    <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>   <script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>   <script type="text/javascript" src="http://d3js.org/topojson.v0.min.js"></script>   <!-- <script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script> -->    <style>  path {   stroke:white;   stroke-width: 1px; }  body {   font-family: Arial, sans-serif; }  .city {   font: 10px sans-serif;   font-weight: bold; }  .legend {   font-size: 12px; }  div.tooltip {      position: absolute;              text-align: center;              width: 150px;                     height: 25px;                    padding: 2px;                font-size: 10px;        background: #FFFFE0;   border: 1px;         border-radius: 8px;              pointer-events: none;          }         </style>    <script type="text/javascript">   var width = 720,   height = 375;    // Setting color domains(intervals of values) for our map    var color_domain = [10000, 15000, 20000, 30000, 50000]   var ext_color_domain = [0, 10000, 15000, 20000, 30000, 50000]   var legend_labels = ["до 10000 руб.", "10000-15000 руб.", "15000-20000 руб.", "20000-30000 руб.", "30000-50000 руб.", "от 50000 руб."]                 var color = d3.scale.threshold()   .domain(color_domain)   .range(["#ff1300", "#ff4e40", "#ff7d73", "#ffba00", "#ffcb40", "#adfcad"]);    var div = d3.select("form").append("div")      .attr("class", "tooltip")                  .style("opacity", 0);    var svg = d3.select("form").append("svg")   .attr("width", width)   .attr("height", height)   .style("margin", "10px auto");    var projection = d3.geo.albers()   .rotate([-105, 0])   .center([-10, 65])   .parallels([52, 64])   .scale(500)   .translate([width / 2, height / 2]);    var path = d3.geo.path().projection(projection);    //Reading map file and data    queue()   .defer(d3.json, "/upload/russia.json")   .defer(d3.csv, "<?php echo $filename; ?>")   .await(ready);    //Start of Choropleth drawing    function ready(error, map, data) {    var rateById = {};    var nameById = {};     data.forEach(function(d) {     rateById[d.RegionCode] = +d.AverageZarplata;     nameById[d.RegionCode] = d.RegionName;   });    //Drawing Choropleth    svg.append("g")   .attr("class", "region")   .selectAll("path")   .data(topojson.object(map, map.objects.russia).geometries)   //.data(topojson.feature(map, map.objects.russia).features) <-- in case topojson.v1.js   .enter().append("path")   .attr("d", path)   .style("fill", function(d) {     return color(rateById[d.properties.region]);    })   .style("opacity", 0.8)    //Adding mouseevents   .on("mouseover", function(d) {     d3.select(this).transition().duration(300).style("opacity", 1);     div.transition().duration(300)     .style("opacity", 1)     div.text(nameById[d.properties.region] + " : " + rateById[d.properties.region])     .style("left", (d3.event.pageX) + "px")     .style("top", (d3.event.pageY -30) + "px");   })   .on("mouseout", function() {     d3.select(this)     .transition().duration(300)     .style("opacity", 0.8);     div.transition().duration(300)     .style("opacity", 0);   })       // Adding cities on the map    d3.tsv("/upload/cities.tsv", function(error, data) {     var city = svg.selectAll("g.city")     .data(data)     .enter()     .append("g")     .attr("class", "city")     .attr("transform", function(d) { return "translate(" + projection([d.lon, d.lat]) + ")"; });      city.append("circle")     .attr("r", 3)     .style("fill", "lime")     .style("opacity", 0.75);      city.append("text")     .attr("x", 5)     .text(function(d) { return d.City; });   });      }; // <-- End of Choropleth drawing     //Adding legend for our Choropleth    var legend = svg.selectAll("g.legend")   .data(ext_color_domain)   .enter().append("g")   .attr("class", "legend");    var ls_w = 20, ls_h = 20;    legend.append("rect")   .attr("x", 20)   .attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})   .attr("width", ls_w)   .attr("height", ls_h)   .style("fill", function(d, i) { return color(d); })   .style("opacity", 0.8);    legend.append("text")   .attr("x", 50)   .attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;})   .text(function(d, i){ return legend_labels[i]; });    </script> 

Исходные данные для картограммы

Для того, чтобы карта заработала, остался последний шаг. Размещаем файлы cities.tsv, russia.json, zarplata-1-1.csv, zarplata-1-2.csv, zarplata-2-1.csv, zarplata-2-2.csv (приведены в архиве, ссылка на который есть в конце статьи) в директории \SiteDirectory\upload\.

Заходим по адресу siteaddress.ru/newcomponent/act/ — здесь все регионы на карте России подкрашены темно-серым цветом и при наведении появляется название региона — NaN. Для отображения каких-нибудь реальных данных замените значения в последнем столбце .csv файлов на численные данные.

Демо компонента

Подобный модуль разработан мною для сайта ЗарплатаБюджетников.РФ в разделе Карта зарплат. Демо модуля можно посмотреть по ссылке.
Бонус

Напоследок, небольшой хинт. В дефолтном шаблоне по умолчанию в InstantCMS 2 боковая колонка вместе с меню действий пропадает при уменьшении ширины окна браузера. Но на мобильных девайсах исчезновение боковой колонки и меню действий очень не удобно, т.к. у пользователей пропадает довольно таки много возможных действий. Для изменения этой ситуации можно проделать следующее. Найдите в директории \templates\default\css\ файл theme-layout.css, и замените в нем кусочек кода

/* Media Queries ============================================================ */  @media screen and (max-width: 980px) {     #body section { width:100% !important; }     #body aside { display:none; } }  @media screen and (max-width: 800px) {     #body section { width:100% !important; }     #body aside { display:none; } 

на

/* Media Queries ============================================================ */  @media screen and (max-width: 980px) {     #body section { width:100% !important; }     #body aside { width:100% !important; } }  @media screen and (max-width: 800px) {     #body section { width:100% !important; }     #body aside { width:100% !important; } 

Т.е. по факту необходимо исправить всего 2 строчки #body aside { display:none; } на #body aside { width:100% !important; } — после этого боковая колонка при уменьшении ширины браузера будет съезжать в основную колонку после находящегося в нем контента (перед футером).

Исходные коды

Все описанные скрипты и файлы можно скачать в архиве, который нужно будет просто загрузить и распаковать в корневую директорию вашего сайта, работающего на InstantCMS2.

P.S. О замеченных опечатках, ошибках или неточностях прошу писать в личные сообщения.

ссылка на оригинал статьи http://habrahabr.ru/post/264519/