Russian TYPO3 community

Russian TYPO3 community (http://forum.typo3.ru/index.php)
-   Мастер-класс (http://forum.typo3.ru/forumdisplay.php?f=41)
-   -   GEOPicker(Typo3-7, FLUID, PHP, JS) (http://forum.typo3.ru/showthread.php?t=12018)

alexk 25.04.2016 17:18

GEOPicker(Typo3-7, FLUID, PHP, JS)
 
Приветствую коллеги! Пишу сейчас очень большой проект с кучей ништяков и приходится дописывать кучу плагинов своих и модулей и т.д. для typo3 7.6 (на версиях ниже не проверял и не хочу).

Возникла задача написать Гео пикер, т.е. нужно с гугла вытаскивать адрес, город, страну и лат\лонг. Есть решения типа экста geopicker, но он делает во всплывающем окошке, что выглядит старомодно, да и нет там нужных мне полей.

И так модуль я написал, без окошка но в модальном окне!
http://elrise.ru/geo/geopicker.jpg

Так что кому будет полезно или интересно, прошу читать далее.

Итак поехали:
1. создаем FrontEnd plagin (я пользую kickstarter, лени ради). Можно конечно не ФронтЭнд, а сервис..
sic!
ext_tables.php и контроллер, модели и репозитории нам ненужны.
так что структура у нас такая:

Код:

Classes
-Form
--Element
---GeoPickerElement.php  // рендерим конфиг для нового элемента
--Wizard
---GeoPickerWizard.php // Wizard для нового элемента
-Utility
--ExtConfiguration.php // Это мой хитрый файл для сбора всех конфигов, опишу ниже может пригодится.
Configuration
-Backend
--AjaxRoutes.php // добавляем ajax handler
Resources
-Private
--Language
---locallang.xlf
--Templates
---GeoPickerWizard.html
-Public
--Css
---geopicker.css
--Icons
---icon-blue.png (я использую не дефотный маркер)
--JavaScript
---GeoPosition.js
ext_icon.gif
ext_emconf.php
ext_localconf.php
ext_conf_template.txt

1.1 ext_emconf.php
PHP код:


defined
('TYPO3_MODE') or die();

$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry']['geopicker'] = array(
    
'nodeName' => 'geopicker',
    
'priority' => 100,
    
'class' => \ER\ErGeopicker\Backend\Form\Element\GeoPickerElement::class,
);
Тем кто забыл, \ER\ - Vendor, \ErGeopicker\ - название расширения
Добавляем новый тип элемента TCA
в нашем расширении (в TCA )вставляем конопку

'geopicker' => array(
    
'label' =>'Geopicker',
    
'config'=> array(
    
'type' => 'geopicker',
    
'fields' => array(
        
'country' => 'country_field', <= поля куда будем возвращать значениявсего 5 (я испоьзую два)
        
'city' => 'city_field',
        
//'latitude' => null,
        //'longitude' => null,
        //'address' => null,
        
'title' => 'title_field'
        

    )


1.2 ext_emconf.php
Тут все как обычно

1.3 ext_conf_template.txt
Настройки плагина, Google MAP API Key (для JS API), дефлотная широта и долгота испоьзуем при инициаизации карты

Код:

# cat=basic; type=string; label=Google Developers KEY
googleKey =

# cat=basic; type=string; label=Default Latitude
default_lat =

# cat=basic; type=string; label=Default Longitude
default_long =

1.4 Classes\Form\Element\GeoPickerElement.php
Тут будем рендерить наш новый элемент GEOPICKER

PHP код:

namespace ER\ErGeopicker\Backend\Form\Element;

Набор утилитDebuger для проверки

use TYPO3\CMS\Core\Utility\GeneralUtility;
use 
TYPO3\CMS\Extbase\Utility\ArrayUtility;
use 
TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
use 
TYPO3\CMS\Lang\LanguageService;
use 
TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use 
TYPO3\CMS\Core\Utility\StringUtility;
use 
TYPO3\CMS\Backend\Utility\BackendUtility;


/**
 * Generation of geopicker TCEform element
 */
class GeoPickerElement extends AbstractFormElement
{
    
/**
     * Handler for geopicker type.
     *
     * @return array As defined in initializeResultArray() of AbstractNode
     */
    
public function render()
    {
        
$resultArray $this->initializeResultArray(); // сюда будем вставять свойства нашего элемента
        
        
$languageService $this->getLanguageService(); // Просто языковой севрвис, нужен для языка карт (можно убрать и поставить язык по умолчанию)
        
        
$ll 'LLL:EXT:er_geopicker/Resources/Private/Language/locallang.xlf:'//линк на языковой файл
        
        
$row $this->data['databaseRow']; // тут модель нашего элемента куда вставлен geopicker (я вытаскиваю title и только)

        
$parameterArray $this->data['parameterArray']; // тут конфиг нашего поля TCA потом вытащим поля из него.

        
$config ArrayUtility::arrayMergeRecursiveOverrule($extConfig$parameterArray['fieldConf']['config']); // сливаем конфиги в один массив.. можно и не сливать.

        
$formFieldId StringUtility::getUniqueId('formengine-geopicker-'); // hash что бы сдеать уникальный ID нашей кнокпи.

        
$title '';
        if(
$config['fields']['title']){
            
$title $row[$config['fields']['title']];
            unset( 
$config['fields']['title']);
        }

        
$wizardData = array(
            
'default_lat' => $config['default_lat'],
            
'default_long' => $config['default_long'],
            
'title' => $title,
            
'lang' => null,
            
'unic_id' => $formFieldId
            
);

        
$wizardData['token'] = GeneralUtility::hmac(implode('|'$wizardData), 'GeoPickerWizard'); //TYPO3 encryption key

        
$buttonAttributes = array(
            
'data-url' => BackendUtility::getAjaxUrl('wizard_geo_picker'$wizardData), // wizard_geo_picker именно так /Wizard/GeoPicker. 
            
'data-severity' => 'notice',
            
'data-field' => $formFieldId
            
'data-title' => $title
            
'data-fields' => json_encode($config['fields']), // массив с полями
            
'data-table' => $this->data['tableName'// таблица нашего элемента
        
);

        if(
$languageService->lang == 'default')
            
$langflag 'en'// если в бэкэнд язык дефолтный, то и флаг у него "default" у меня дэф ангийски, т.е. для англ карты мне нужен lang=en
        
else 
            
$langflag $languageService->lang// для остальных все норм

        // рендерим кнопку и и вставляем наши скрипты, тут все должно быть понятно

        
$button '<button class="btn btn-default t3js-geopicker-trigger"';
        foreach (
$buttonAttributes as $key => $value) {
                
$button .= ' ' $key '="' htmlspecialchars($value) . '"';
            }
        
$button .= '><span class="t3-icon fa fa-map-marker"></span>';
        
$button .= $languageService->sL($ll.'geopicker.open'true);
        
$button .= '</button>';

        
$content '<div class="form-control-wrap">'.$button.'</div>';

        
$resultArray['requireJsModules'][] = '//maps.googleapis.com/maps/api/js?key='.$config['googleKey'].'&language='.$langflag;
        
$resultArray['requireJsModules'][] = array(
              
'../typo3conf/ext/er_geopicker/Resources/Public/JavaScript/GeoPosition' => 'function(GeoPosition){
                  GeoPosition.initializeTrigger('
.$config['default_lat'].', '.$config['default_long'].')}'
        
);
        
$resultArray['html'] = $content;

        return 
$resultArray;

        
// все все параметры забиты, поехали в Wizard.

    


1.5 Classes\Form\Wizard\GeoPickerWizard.php

Важно! ни каких AjaxHandler, мы юзаем
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

Тут надеюсь все более менее понятно, комментировать особо нечего.

PHP код:

namespace ER\ErGeopicker\Backend\Form\Wizard;

use 
Psr\Http\Message\ResponseInterface;
use 
Psr\Http\Message\ServerRequestInterface;
use 
TYPO3\CMS\Core\Utility\GeneralUtility;
use 
TYPO3\CMS\Fluid\View\StandaloneView;
use 
TYPO3\CMS\Extbase\Utility\DebuggerUtility;

/**
 * Wizard for rendering GoogleMap and Geopicker view
 */
class GeoPickerWizard
{
    
/**
     * @var string
     */
    
protected $templatePath 'EXT:er_geopicker/Resources/Private/Templates/';

    
/**
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface $response
     */
    
public function getWizardAction(ServerRequestInterface $requestResponseInterface $response)
    {
        
$assignedValues = array(
            
'title' => $request->getQueryParams()['title'], 
            
'id' => $request->getQueryParams()['unic_id'// сомнительная надобность в ID для DIV c гугл картой, т.к. даже при нескольких геопикерах в форме, модальное окно полностью убирается из DOM, пусть будет для красоты. 
        
);

        
$view $this->getFluidTemplateObject($this->templatePath 'GeoPickerWizard.html'); // получаем наш тэмплэйт модального окошка
        
$view->assignMultiple($assignedValues);
        
$content $view->render();
        
$response->getBody()->write($content);
        return 
$response;
       
    }

    
/**
     * Returns a new standalone view, shorthand function
     *
     * @param string $templatePathAndFileName optional the path to set the template path and filename
     * @return StandaloneView
     */
    
protected function getFluidTemplateObject($templatePathAndFileName null)
    {
        
$view GeneralUtility::makeInstance(StandaloneView::class);
        if (
$templatePathAndFileName) {
            
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
        }
        return 
$view;
    }





1.6 Classes\Utility\ExtConfiguration.php

Кокретно в этом плагине весь функционал файла неиспоьзуется, но приведу целиком, может быдет полезен. Использую его в основном для вставки полей из ext_template_conf в TCA (например root category uid) или во вспомагательных скриптах для доступа к настройкам плагина.

PHP код:

namespace ER\ErGeopicker\Utility;

use 
TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use 
TYPO3\CMS\Core\Utility\GeneralUtility;
use 
TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;

/**
 * Extension Manager configuration
 *
 */
class ExtConfiguration 


    
/**
     * Return the extension settings.
     *
     */
    
public static function getSettings() {
        
$objectManager GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');    
        
$configurationManager $objectManager->get('TYPO3\\CMS\\Extbase\\Configuration\\ConfigurationManager');
        
$settings $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS'ergeopicker'); // 'ergeopicker' как в setup.txt
        
return $settings;
    }

    
/**
     * Return the All extension settings.
     *
     */
    
public function getAllSettings() {
        return 
array_merge(self::getSettings(), self::getConfiguratuion());
    }

    
/**
     * Parse settings and return it as array
     *
     * @return array unserialized extconf settings
     */
    
public static function getConfiguratuion()
    {
        
$settings unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['er_geopicker']); // название расширения
        
if (!is_array($settings)) {
            
$settings = [];
        }
        return 
$settings;
    }



1.7 Configuration\Backend\AjaxRoutes.php

Прописываем ajax handler

PHP код:

use TYPO3\CMS\Backend\Controller;

/**
 * Definitions for routes provided by EXT:er_geopicker
 */
return [
    
'wizard_geo_picker' => [
        
'path' => '/wizard/geo-picker',
        
'target' => \ER\ErGeopicker\Backend\Form\Wizard\GeoPickerWizard::class . '::getWizardAction'
    
],  
]; 

Тут все вроде понятно должно быть.

Так с северной частью закончили, осталось Language, CSS, Template и JS
Забираем их (кроме JS, его напишем)
locallang.xlf
GeoPickerWizard.html
geopicker.css
icon-blue.png

1.8 \Resources\Public\JavaScript\GeoPosition.js
Заключительная часть кордебалета!

Сразу скажу, не занимался ювелирным кодингом на JS...Если сможите написать лучше! Бог в помощь и барабан на шею:)

Код:

/**
 * Module: ER/ErGeopicker
 * Contains all logic for the googlemap API
 */

define(['jquery', 'TYPO3/CMS/Backend/Modal'], function ($, Modal) {

        /**
        *
        * @exports ER/ErGeopicker
        */
        var GeoPosition = {
                $trigger: null,
                zoom: 12,

                $map: null,
                $pos:null,
                $image:null,

                latitude:null,
                longitude:null,
                address: null,
                city: null,
                country:null,

                $marker:[],
                $geocoder:null
        };

        Тут у меня микс глобальных и полуглобальных и вообще хз каких переменных. Чтоб были.

        GeoPosition.initializeTrigger = function(lat, long){

                GeoPosition.$pos = { lat: lat, lng: long }; // Дефолтные широта и долгота..
                var $triggers = $('.t3js-geopicker-trigger');
                // Remove existing bind function
                $triggers.off('click',  GeoPosition.buttonClick);
                // Bind new function
                $triggers.on('click', GeoPosition.buttonClick);
        }

        // кликалка с защитой от даблклика
        GeoPosition.buttonClick = function(e) {
                e.preventDefault();
                // Prevent double trigger
                if (GeoPosition.$trigger !== $(this)) {
                        GeoPosition.$trigger = $(this);
                        GeoPosition.show();
                }
        };


        // открываем модальное окошко

        GeoPosition.show = function() {
                GeoPosition.currentModal = Modal.loadUrl(
                        GeoPosition.$trigger.data('title'),
                        TYPO3.Severity.notice,
                        [],
                        GeoPosition.$trigger.data('url'),
                        GeoPosition.showMap,
                        '.modal-content'
                );

                var modalWidth = $(window).width();

                GeoPosition.currentModal.addClass('modal-dark');

                GeoPosition.currentModal.find('.modal-dialog')
                        .addClass('modal-geopicker modal-resize')
                        .css({width: modalWidth});

                GeoPosition.currentModal.find('.modal-content').css({marginTop:50});

                setTimeout(function() {
                        var sidebarWidth = GeoPosition.currentModal.find('.modal-panel-sidebar').outerWidth();
                        var bodyWidth = modalWidth-sidebarWidth-2;
                        GeoPosition.currentModal.find('.modal-panel-body').css({width:bodyWidth});
                }, 500);
                // Т.к. открытие и погрузка шаблона требует некоторого времени, то лучше все действия делать с небольшой задержкой.

        };

        //после открытия окна, callback'ом вызываем карту, опять же с задержкой.. без задержки бывает google скрипт не успевает прогружатся.. но это на локальном хосте.
        GeoPosition.showMap = function(){
                setTimeout(function() {
                        GeoPosition.initializeMap();
                }, 300);
        };

        // собственно сама карта, я делал без стилей и т.д. чисто карта как есть.

        GeoPosition.initializeMap = function(){

        $map = new google.maps.Map(GeoPosition.currentModal.find('.gmap')[0], {
                    center: GeoPosition.$pos,
                    zoom: GeoPosition.zoom
                  });

        $geocoder = new google.maps.Geocoder(); // Важно, ваш АПИ код должен быть активирован для использования geocodera

        //  наш не дефолтный маркер
        GeoPosition.$image = {
                                // This marker is 64 pixels wide by 64 pixels high.
                            size: new google.maps.Size(64, 64),
                            // The origin for this image is (0, 0).
                            origin: new google.maps.Point(0, 0),
                            // The anchor for this image is the base of the flagpole at (0, 32).
                            anchor: new google.maps.Point(24, 48),
                            scaledSize: new google.maps.Size(48, 48),
                            url: '../typo3conf/ext/er_geopicker/Resources/Public/Icons/map-icons/icon-blue.png',
                }

                // определяем текущее положение пользователя.
                if (navigator.geolocation) {
                        navigator.geolocation.getCurrentPosition(
                function( position ){
                          GeoPosition.$pos = {
                                        lat: position.coords.latitude,
                                        lng: position.coords.longitude
                                    } 
                                        GeoPosition.findByLatLng(GeoPosition.$pos);
                }
            )
        }

        // Инпут с поиском по адресу, ищем по клику
                  GeoPosition.currentModal.find('#submit').on('click', function(e){
                          var address = GeoPosition.currentModal.find('#gsearch').val();
                          GeoPosition.findByAdress(address);
                          return false;
                  });

                  // Инициализируем Сохранить и Закрыть
                  GeoPosition.initializeActions();
        };

        // поиск по адресу
        GeoPosition.findByAdress = function (address) {

                var componentRestrictions = {}; // тут можно вписать ограничения по региону и т.д.

                $geocoder.geocode({'address': address, 'componentRestrictions': componentRestrictions}, function(results, status) {

                    if (status === google.maps.GeocoderStatus.OK) {
                              $map.setCenter(results[0].geometry.location);
                              GeoPosition.addMarker(results[0].geometry.location);
                              GeoPosition.setresult(results);
                    } else {
                              console.log(status);
                    }
                });
    };

    // поиск по координатам
    GeoPosition.findByLatLng = function(pos){

            var componentRestrictions = {};

            $geocoder.geocode({'location': pos, 'componentRestrictions': componentRestrictions}, function(results, status) {
                    if (status === google.maps.GeocoderStatus.OK) {
                            GeoPosition.addMarker(pos);
                            GeoPosition.setresult(results);
                    } else {
                              console.log(status);
                    }
            })
    };

    // Создаем маркер, т.к. маркер всегда один на карте то перед созданием обнуляем маркеры на карте и в массиве
    GeoPosition.addMarker = function(position){
            GeoPosition.setMarkers(null);
                var marker = new google.maps.Marker({
                    map: $map,
                    position: position,
                    icon: GeoPosition.$image,
                        draggable: true,
                        animation: google.maps.Animation.DROP,
                        title: GeoPosition.$trigger.data('title')
                });
                GeoPosition.$marker.push(marker);
                GeoPosition.setMarkers($map);

    };

    //вставляем маркер на карту если map==null то убираем все что есть
    GeoPosition.setMarkers = function(map){
            for (var i = 0; i < GeoPosition.$marker.length; i++) {
                    GeoPosition.$marker[i].setMap(map);
                    GeoPosition.listner(GeoPosition.$marker[i]);
                }
                if(!map)
                        GeoPosition.$marker=[];
    };

    // добавляем слушателей на карту и маркер check - нужен что бы убрать даблклики и т.д. т.к. у гугл есть лимит на обращения к geocoder и не только.

    GeoPosition.listner = function(marker){
            var check = true;

            google.maps.event.addListener(marker, 'dragend', function (results) {
                   
                    if(check){
                            GeoPosition.findByLatLng(marker.getPosition());
                            check = false;
                    }
                    setTimeout(function() {
                            check = true;
                    }, 10000);
           
        });

            $map.addListener('click', function(event) {
                  if(check){
                            GeoPosition.findByLatLng(event.latLng);
                            check = false;
                    }
                    setTimeout(function() {
                            check = true;
                    }, 10000);
                });
    };

    // Из гугловского ответа выбираем только город, страну и адрес. Детали и параметры ответа можно узнать у гугла детальнее.

    GeoPosition.setresult = function(results){

            // default field: country, city, latitude, lonitude, address

            var modal = GeoPosition.currentModal;

            var checkLocality = true;
            var checkRoute = true;
                $.each(results, function(i, val){
                        $.each(val['types'], function(k,type){
                                if(type === 'locality') {
                                        if(checkLocality) {
                                                GeoPosition.city = val.address_components[0].long_name;
                                                GeoPosition.country = val.address_components[val.address_components.length-1].long_name;
                                        }
                                        checkLocality = false;
                                }
                                if(type === 'route') {
                                        if(checkRoute) {
                                                GeoPosition.address = val.formatted_address;
                                        }
                                        checkRoute = false;
                                }
                               
                        });

                });

                if(checkRoute)
                        GeoPosition.address = ''; // бывает адреса и нет...

                GeoPosition.latitude = results[0].geometry.location.lat();
                GeoPosition.longitude = results[0].geometry.location.lng();



            modal.find('#city').text(GeoPosition.city);
            modal.find('#country').text(GeoPosition.country);
            modal.find('#longitude').text(GeoPosition.longitude);
            modal.find('#latitude').text(GeoPosition.latitude);
            modal.find('#route').text(GeoPosition.address);

    };

    // А вот наши Сохранить и закрыть
        GeoPosition.initializeActions = function() {
                GeoPosition.currentModal.find('[data-method]').click(function(e) {
                        e.preventDefault();
                        var method = $(this).data('method');
                        var options = $(this).data('option') || {};
                        if (typeof GeoPosition[method] === 'function') {
                                GeoPosition[method](options);
                        }
                });
        };

        // Сохраняем, т.е. отправляем данные обатно в нашу форму.
        GeoPosition.save = function(){

            // default field: country, city, latitude, lonitude, address

            var formFields =  GeoPosition.$trigger.data('fields');
            var table = GeoPosition.$trigger.data('table');
            var        form = GeoPosition.$trigger.closest('form');

            // если бы не убрали title тот тут бы он и всплыл.. и попытался за заменится на что нибудь.
            $.each(formFields, function(key, field){
                    var selector = form.find('[data-formengine-input-name*="'+field+'"]');
                    $(selector[0]).val(GeoPosition[key]);
                    TBE_EDITOR.fieldChanged(table, 1, field, $(selector[0]).data('formengine-input-name'));
            });
            GeoPosition.dismiss();
       
    };

    // Закрыть окошко
    GeoPosition.dismiss = function() {
                if (GeoPosition.currentModal) {
                        GeoPosition.currentModal.modal('hide');
                        GeoPosition.currentModal = null;
                }
        };

        return GeoPosition;

мой плагин er_geopicker соберетесь поменять, не забудьте сделать это везде.:p

За основу и в качестве рабочего примера смотрел в плагины t3editor и в ImageManipulationElement


Часовой пояс GMT +4, время: 18:42.

Работает на vBulletin® версия 3.8.1.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot