Russian TYPO3 community Форум больше не используется. Присоединяйтесь к каналу #community-ru в Slack for TYPO3 community  

Вернуться   Russian TYPO3 community > Обсуждение общих технических вопросов > Мастер-класс

Ответ
 
Опции темы Опции просмотра
Старый 25.04.2016, 17:18   #1
alexk
Senior Member
 
Регистрация: 26.10.2007
Сообщений: 108
По умолчанию GEOPicker(Typo3-7, FLUID, PHP, JS)

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

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

И так модуль я написал, без окошка но в модальном окне!


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

Итак поехали:
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 соберетесь поменять, не забудьте сделать это везде.

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

Последний раз редактировалось alexk; 27.04.2016 в 11:34
alexk вне форума   Ответить с цитированием
Ответ

Опции темы
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB code is Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Странные ошибки. M31 Установка 2 24.12.2008 12:33
Включение кода PHP Костик Общие вопросы 2 30.10.2008 18:10
Трабл с загрузкой *.t3x в Extension Manager Raven2000 Общие вопросы 6 08.08.2007 18:15
Проблемы c php 5.2.3 Archual Общие вопросы 4 01.07.2007 15:11
Форма + валидация > php > БД Artem Общие вопросы 3 08.01.2007 23:39


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


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

Хостинг и техническая поддержка: TYPO3 Лаборатория