Как создать плагин источник данных

Компонент Яндекс карты Joomla, позволяет создавать собственные объекты на карте, задавать для них название, иконку и описание. Однако, обычно задачей в интеграции с Яндекс Картами, является вывод уже существующих объектов некоего компонента.

Компонент, на примере плагинов К2 - Источник данных, Zoo - истоинчк данных, материалы - Источник данных и т.д. демонстрирует пример такой интеграции. В этой статье мы подробно рассмотрим - Как создать собственный плагин источник данных для компонента Яндекс Карта на Joomla. После прочтения данной статьи, вы без труда выведите элементы из своего компонента на карте Яндекс.

Приступим

Предположим у нас есть некая таблица с нашими товарами(статьями, фотографиями и т.д). Назовем ее items. Ваше название, как и сама таблица может быть какими угодно. Для примера

CREATE TABLE  `items` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT  'ID',
`title` VARCHAR( 255 ) NOT NULL COMMENT  'Название',
`description` TEXT NOT NULL COMMENT  'Описание',
`price` INT NOT NULL COMMENT  'Цена',
INDEX (  `title` )
) ENGINE = INNODB COMMENT =  'Товары';

В этой таблице нам не хватает широты, долготы и масштаба. Добавим их.

ALTER TABLE  `items` ADD  `lat` DECIMAL( 18, 14 ) NULL COMMENT  'Широта',
ADD  `lan` DECIMAL( 18, 14 ) NULL COMMENT  'Долгота',
ADD  `zoom` TINYINT NULL COMMENT  'Масштаб',
ADD INDEX (  `lat` ,  `lan` );

В реальном примере у вас уже могут быть эти поля. О том как их заполнять и вы должны позаботится сами. Тут лишь будет описано, что можно для этого использовать.

Заполнение значений

У Joomla есть механизм добавления новых полей для редактирования к форме. Если компонент спроектирован по канонам Joomla, то при редактирование элемента item поля будут браться из administrator/components/com_items/models/forms/item.xml Так вот, обычно достаточно добавить в этот файл

<field
    name="lat"
    type="text"
    label="Широта"
    default="53"
/>
<field
    name="lan"
    type="text"
    label="Долгота"
    default="34"
/>
<field
    name="zoom"
    type="number"
    label="Масштаб"
    default="10"
/>

И данные автоматически будут редактироваться/добавляться. Это самый простой путь. Но такое редактирование координат не всегда удобно. Гораздо удобнее указать местоположение на карте, или ввести адрес. И то и другое позволяет поле address компонента joomla Яндекс карты. Для того чтобы его использовать вам потребуется сперва подключить папку, в которой Joomla будет искать это поле. Добавляем к элементу fields или fieldset атрибут addfieldpath

<fieldset
    addfieldpath="/administrator/components/com_yandex_maps/models/fields"
>

Теперь Joomla будет знать, откуда брать поля компонента. И теперь мы можем использовать все поля из папки /administrator/components/com_yandex_maps/models/fields Их там много. Но нам сейчас нужно только address. Предыдущие поля удалять ен нужно, они нам еще потребуются.

<field
    name="lat"
    type="text"
    label="Широта"
    default="53"
/>
<field
    name="lan"
    type="text"
    label="Долгота"
    default="34"
/>
<field
    name="zoom"
    type="number"
    label="Масштаб"
    default="10"
/>
<field
    name="address"
    type="address"
    label="Адрес"
    composite="true"
    lat_field="lat"
    lan_field="lan"
    zoom_field="zoom"
/>

Атрибут composite=true говорит о том, что поле не будет само хранить значение, а будет передавать его в поля выше. По умолчанию поле сохраняет данные в json формате. Это можно использовать, когда у вас нет возможности менять структуру таблицы данных. Но у вас есть поле params. Плагины материалы - Источник данных так и работает. Он не меняет структуру таблицы #__contents. Теперь при редактировании элемента item в админке на месте этого поля будет такое:

Пример поля

Структура плагина

Предположим, вы заполнили таблицу items. Теперь, приступим к основному - напишем Joomla плагин, который будет выдавать все элементы items в компонент карт. Создание плагина Joomla начинается с двух файлов. Создайте папку items, а в ней items.php и items.xml

items.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" version="3.0" group="yandexmapssource" method="upgrade">
    <name>plg_yandexmapssource_items</name>
    <version>1.0.0</version>
    <files>
        <filename plugin="items">items.php</filename>
    </files>
    <config>
        <fields name="params"></fields>
    </config>
</extension>

Важным тут будет yandexmapssource. Надо создавать плагин именно в этой группе. Тогда компонент сможет использовать его.

items.php

defined('_JEXEC') or die;
jimport('joomla.plugin.plugin');
class plgYandexMapsSourceItems extends JPlugin {
    public $id = 'items'; // идентификатор источника
    public function onGetName(&$names) {
        $names[$this->id] = 'Товары для дома';
    }
    public function onGetCategories($map) {
        $cats = array();
        return array();
    }
    public function getObjectsCount($map, $filter, $bound = array(), $exclude = array(), $search = false, $forse_id = 0) {
        return int;
    }
    public function onGetObjectsByBound($map, $bound = array(), $offset = 0, $limit = 500, $exclude = array(), $search = false, $forse_id = 0) {
        // главный метод
        return array();
    }
    public function generateDescription(& $description, $object) {}
    public function onOpenMap(& $html, $map) {}
    public function onGenerateFilterWhere($map, & $filters, & $join, & $select) {}
    public function generateFilter(& $filters, $map) {}
}

Методы

Методы generateDescription,onOpenMap,onGenerateFilterWhere,generateFilter не обязательные. Строго говоря они все не обязательные. Но без первых трех, плагин не будет работать вовсе.

onGetName(&$names)

Задает название для источника данных. Этот метод вызывается при настройке карты. И значение будет видно в поле источник

Источник данных

onGetCategories($map)

Возвращает категории вашего компонента. Тогда они могут быть отображены в фильтре или в боковом списке. Если вы решили использовать свои категории. То код должен быть примерно таким

public function onGetCategories($map) {
    $categories = array();
    if (!$map or $map->settings->get('source')!=$this->id) {
        return $categories;
    }
    $db = JFactory::getDBO();
    $db->setQuery(
        'select 
            a.*, 
            CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END as slug
        from 
            #__items_categories  as a
        where
            a.published in (1)
    );
    $data = $db->loadObjectList();
    foreach ($data as $category) {
        $category->lat = $map->lat;
        $category->lan = $map->lan;
        $category->zoom = $map->zoom;
        $category->link = JRoute::_('index.php?option=com_items&view=category&id='.$category->slug);
        $category->id = $this->id.$category->id;
        $categories[] = $category;
    }
    return $categories;
}

onGetObjectsByBound($map, $bound = array(), $offset = 0, $limit = 500, $exclude = array(), $search = false, $forse_id = 0)

Главный метод из-за которого все и затевалось. Он возвращает массив объектов.

  • $map модель карты из папки administrator/components/com_yandex_maps/models/maps.php С помощью нее в примере выше, мы определили, что в карте источником будет наш плагин.
  • $bound массив из 2-х точек(массовов [lat,lan]). Верхний левый и нижний правый угол карты. Если элементов много, то можно отсеить те, которые в данный момент не находятся в области видимости. Тогда, если пользователь перетащит карту, будут загружены только те объекты, которые он сейчас долеж увидеть.
  • $offset и $limit если объектов в вашем компонент много, то на карту они будут загружаться порционно. Используйте смещение в SQL запросе и ограничивайте вывод.
  • $exclude массив аналогичен $bound только содержит значение предыдущей загрузки. Т.е. предыдущее положение карты. Т.е. пользователь открыл карту. По $bound загрузил все объекты карты входящие в область и чуть сдвинул карту. Тогда те объекты которые он уже загрузил, можно отфильтровать при помощи $exclude
  • $search строка, может быть использована для поиска по объектам.
  • $forse_id если задан этот параметр, то надо загрузить этот элемент в выборку несмотря ни на какие фильтры выше

Для примера, код метода может быть таким:

public function onGetCategories($map, $bound = array(), $offset = 0, $limit = 500, $exclude = array(), $search = false, $forse_id = 0) {
    $objects = array();

    if (!$map or $map->settings->get('source')!=$this->id) {
        return $objects;
    }
    $db = JFactory::getDBO();
    $filter = array('(a.active=1)');
    if (!$forse_id) {
        if ($bound && isset($bound[0]) && $bound[0]>0) {
            $filter[] = '(a.lat > '.((float)$bound[0][0]).' and
                a.lan > '.((float)$bound[0][1]).' and
                a.lat < '.((float)$bound[1][0]).' and
                a.lan < '.((float)$bound[1][1]).')';
        }
        if ($filter && $exclude && isset($exclude[0]) && $exclude[0]>0) {
            $filter[] = '!(a.lat > '.((float)$exclude[0][0]).' and
            a.lan > '.((float)$exclude[0][1]).' and
            a.lat < '.((float)$exclude[1][0]).' and
            a.lan < '.((float)$exclude[1][1]).')';
        }
        if ($search) {
            $filter[] = ' (
                a.title like "%'.$db->escape( $search, true ).'%" 
            )';
        }
    } else {
        $filter[] ='(a.id='.((int)$forse_id).')';
    }

    $db->setQuery(
        'select * 
        from `items` as a
        where '.implode(' and ', $filter).'
        order by a.id asc
        limit '.((int)$offset).','.((int)$limit)
    );
    $data = $db->loadObjectList();
    foreach ($data as $object) {
        $object->type = 'placemark';
        $object->link = JRoute::_('index.php?option=com_items&view=object&id='.$object->id);
        $object->map_id = $map->id;
        $object->options = json_encode((object)array(
            'preset'=>$this->params->get('object_preset', 'islands#greenStretchyIcon'), 
            'iconColor'=>$this->params->get('object_iconColor','green'),
        ));
        $object->properties = json_encode((object)array('metaType'=>'Point', 'iconContent'=>$object->title));
        $object->coordinates = json_encode(array($object->lat, $object->lan));

        // для работы фильтров также должны быть заполнены следующие поля
        //$object->category_id = $this->id.$cat->id;
        //$object->category_slug = $cat->alias;
        //$object->category_title = $cat->name;
        //$object->category_link = JRoute::_('index.php?option=com_items&view=category&id='.$category->slug);

        $objects[] = $object;
    }
    return $objects;
}

Пояснять по коду думаю особо не нужно. Через SQL мы выудили только те элементы, которые находятся в текущей области видимости. Затем аполнили обязательные для меток поля options, properies и coordinates Эти поля отвечают за внешний вид метки. Как их заполнять вам лучше посмотреть в официальной документации Плагин по сути готов. Он будет отдавать ваши метки на карту. Теперь кратко о остальных методах.

getObjectsCount($map, $filter, $bound = array(), $exclude = array(), $search = false, $forse_id = 0)

Делает то же самое но возвращает общее количесвто найденных точек. Т.е. в select должен быть count(*) или что-то подобное. И без limitов.

generateDescription(& $description, $object)

По умолчанию содержимое балуна формируется просто отдачей поля $description. Но ведь в вашем компоненте у товара могут быть еще и фотографии. Метод generateDescription позволяет задать какое угодно содержимое для метки. Он будет вызван для каждой! вашей метки и вы можете записать в поле $description все что хотите. К примеру добавим к описанию картинку

public function generateDescription(& $description, $object) {
    $description = '<img src="images/photo.png" alt="">'.$description;
}

Вот и все. Теперь содержание балуна будет ограничивать только ваша фантазия.

onOpenMap(& $html, $map)

В предыдущем примере, мы добавили к описанию картинку. А что если мы заходим добавить целую галерею на jQuery. После откртия балуна, ее надо будет инициализировать. Кроме того вы можеет добавить любой код, к коду инициализации карты. Лучше всего проиллюстрировать на примере. Тут после карты еще добавляется содержимое файла plugins/system/yandex_maps_arendator/tmpl/book.php.

public function onOpenMap(& $html, $map) {
    Jhtml::_('xdwork.dialog');
    Jhtml::_('xdwork.includecss', JURI::root().'plugins/system/yandex_maps_arendator/assets/style.css');
    Jhtml::_('xdwork.includejs', JURI::root().'plugins/system/yandex_maps_arendator/assets/profile.js');
    $html.='<script>
        map'.$map->id.'.events.on("ballonOpen", function () {
            setTimeout(function () {
                jQuery(".owl-carousel").owlCarousel({
                    items: 1,
                    navigation: true,
                    autoHeight: true
                });
            }, 300);
        });
    </script>';
    $html.=JHtml::_('xdwork.includePHP', 'plugins/system/yandex_maps_arendator/tmpl/book.php', true);
} ```
#### onGenerateFilterWhere($map, & $filters, & $join, & $select)
Метод позволяет создавать свои собственные фильтры к тем, что уже есть у карты. Он не связан с нашей задачей. Он может фильтровать конкретно объекты самого компонента.
К примеру, добавим некий фильтр по дате обращения
```php
public function onGenerateFilterWhere($map, & $filters, & $join, & $select) {
    $filters[] = '(
        a.id in (select object_id from #__yandex_maps_datetimes where date_value = "'.(date('Y-m-d', strtotime($input->get('yma_start_period', false)))).'")
    )';
}

generateFilter(& $filters, $map)

Метод вызывается при создании фильтра на карте. Он отбражается на самой карте и вы вольны добавить в него свои элементы.

public function generateFilter(& $filters, $map) {
    $filters[] = JHtml::_('xdwork.includePHP', 'plugins/system/yandex_maps_arendator/tmpl/filter.php', true, array('map'=>$map, 'params' => $this->params));
}

К примеру для плагина Яндекс карты - Арендатор фильтр выглядит так

Фильтр плагина Яндекс Карты Арендатор

Осталось собрать оба файла в zip архив и установить через Менеджер расширений Joomla Не забудьте выключить его в менеджере плагинов после установки. После этого ваш плагин появится в списке источников при настройке карты. Задавайте ваши вопросы в комментариях. буду рад ответить на них.