Russian TYPO3 community  

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

Ответ
 
Опции темы Опции просмотра
Старый 02.05.2016, 03:26   #1
alexk
Senior Member
 
Регистрация: 26.10.2007
Сообщений: 106
По умолчанию Расширяем функционал для Inline Elements

Приветствую коллеги! Заморочился я тут с Inline Elements, как мы уже знаем там нет функционала как в Group и добавлять существующие записи уже невозможно, а хотелось бы.. можно конечно использовать Group c "+", но тогда если нам нужно создать, нас будет кидать на новую страницу.. что совсем не камильфо, нужно здесь и сейчас, т.е. нам все таки нужен функционал Inline.

Мне вот тут приперло, так что делюсь своим решением это задачи.. опять же у кого есть задумки лучше и проще, прошу в студию.

На выходе мы получим вот такую красоту, поиск с автокомплитом и доп кнопку на "Отсоединение элемента", так что бы старые элементы не удалялись, а отсоединялись.




Поехали...

1. создаем новый ajaxRoute -> Cofiguration/Backend/AjaxRoutes.php

PHP код:
<?php
use TYPO3\CMS\Backend\Controller;

return [
    
'wizard_get_related' => [
        
'path' => '/wizard/get-related',
        
'target' => \Extension\Backend\Form\Wizard\GetRelatedWizard::class . '::searchAction'
    
],  
];
В визард будем слать запросы на поиск.

2. в ext_tables.php добавляем иконку кнопки и Hook для InlineElements

PHP код:
$iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class);
 
$iconRegistry->registerIcon(
        
'chain-broken',
        \
TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider::class,
        [
'source' => 'EXT:iconfont/Resources/Public/Image/font-awesome/chain-broken.png']
    );

// У меня стоит ext iconfont вы можете подгрузить любую свою (svg или png)

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tceforms_inline.php']['tceformsInlineHook'][$_EXTKEY] =
            \
Extension\Hooks\InlineElementHook::class;
// Этот Хук нужен для создания кнопки Unchain 
3. Ищем FormInlineAjaxController в sysext и забираем его целиком (Можно конечно дописать свою функци, потом сделать extend.. но как по мне проще было просто скопировать)
его кладем сюда: Classes\Controller\FormInlineAjaxController.php
И меняем всего пару строк:
PHP код:
...
//ищем вот это:
if ($childChildUid) {
            
$formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
        }

// далее эту строку:
$childData $formDataCompiler->compile($formDataCompilerInput);

// меняем на  это:

if($parentConfig['addrelated'] && !$parentConfig['foreign_selector'] && $childChildUid)
            
$childData $this->compileChild($parentData$parentFieldName, (int)$childChildUid$inlineStackProcessor->getStructure());
        else
            
$childData $formDataCompiler->compile($formDataCompilerInput);

//$parentConfig['addrelated'] - это новый параметр в TCA config, что бы отсеивать рендеринг добавляем элементов. Обязательно при условии !$parentConfig['foreign_selector'] это условие что мы не рендерим все через вспомогательные таблицы и $childChildUid это что бы наверняка! 
$childData = $this->compileChild -> тут получаем всю инфу по добавляемому элементу. Это позволит получить все напрямую, без foreign_selectorи т.д. в нашем случае мы работаем с таблицей напрямую, а не через Intermediate tables.

4. Classes/Form/Wizard/GetRelatedWizard.php

Тут многое из SuggestWizard.. только в немного упрощенном виде

Думаю еще можно сократить...

PHP код:
<?php
namespace Extension\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\Lang\LanguageService;
use 
TYPO3\CMS\Core\Imaging\Icon;
use 
TYPO3\CMS\Core\Imaging\IconFactory;


class 
GetRelatedWizard
{

 
/**
     * Renders an ajax-enabled text field. Also adds required JS
     *
     * @param string $fieldname The fieldname in the form
     * @param string $table The table we render this selector for
     * @param string $field The field we render this selector for
     * @param array $row The row which is currently edited
     * @param array $config The TSconfig of the field
     * @return string The HTML code for the selector
     */
    
public function renderSuggestSelector($fieldname$table$field, array $row, array $config)
    {

        
/** @var $iconFactory IconFactory */
        
$iconFactory GeneralUtility::makeInstance(IconFactory::class);
        
$languageService $this->getLanguageService();
        
$minChars 2;
        
$uids $row[$field];

        
// fetch the TCA field type to hand it over to the JS class
        
$type 'inline';

        
$selector '
        <div class="autocomplete t3-form-suggest-container">
            <div class="input-group">
                <span class="input-group-addon">' 
$iconFactory->getIcon('actions-search'Icon::SIZE_SMALL)->render() . '</span>
                <input type="search" class="t3-form-suggest-inline form-control"
                    placeholder="' 
$languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.findRecord') . '"
                    data-fieldname="' 
$fieldname '"
                    data-table="' 
$table '"
                    data-field="' 
$field '"
                    data-uid="' 
$row['uid'] . '"
                    data-pid="' 
$row['pid'] . '"
                    data-uids="' 
$row[$field] . '" // сюда добавляем все юиды элементов которые у нас уже есть
                    data-fieldtype="' 
$type '"
                    data-minchars="' 
$minChars '"
                    data-recorddata="' 
htmlspecialchars($jsRow) . '"
                />
            </div>
        </div>'
;

        return 
$selector;
    }

     
/**
     * Ajax handler for the "suggest" feature in FormEngine.
     *
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    
public function searchAction(ServerRequestInterface $requestResponseInterface $response)
    {
        
$parsedBody $request->getParsedBody();
        
$queryParams $request->getQueryParams();
        
        
// Get parameters from $_GET/$_POST
        
$search = isset($parsedBody['value']) ? $parsedBody['value'] : $queryParams['value'];
        
$table = isset($parsedBody['table']) ? $parsedBody['table'] : $queryParams['table'];
        
$field = isset($parsedBody['field']) ? $parsedBody['field'] : $queryParams['field'];
        
$uid = isset($parsedBody['uid']) ? $parsedBody['uid'] : $queryParams['uid'];
        
$pageId = (int)(isset($parsedBody['pid']) ? $parsedBody['pid'] : $queryParams['pid']);
        
$uids =  isset($parsedBody['uids']) ? $parsedBody['uids'] : $queryParams['uids'];
        
$newRecordRow = isset($parsedBody['newRecordRow']) ? $parsedBody['newRecordRow'] : $queryParams['newRecordRow'];
        
        
        
$fieldConfig $GLOBALS['TCA'][$table]['columns'][$field]['config'];

        
$queryTables $this->getTablesToQueryFromFieldConfiguration($fieldConfig);
        
$whereClause $this->getWhereClause($fieldConfig$uids);
 
        
$resultRows = array();

        foreach (
$queryTables as $queryTable) {
            
// if the table does not exist, skip it
            
if (!is_array($GLOBALS['TCA'][$queryTable]) || empty($GLOBALS['TCA'][$queryTable])) {
                continue;
            }
            
$config = array('searchWholePhrase'=> 1);

             
// process addWhere
            
if ($whereClause) {
                
$config['addWhere'] = $whereClause;
            }

            
$config['maxItemsInResultList'] = 10;

           
$receiverClassName = \TYPO3\CMS\Backend\Form\Wizard\SuggestWizardDefaultReceiver::class;

           
$receiverObj GeneralUtility::makeInstance($receiverClassName$queryTable$config);

           
$params = array('value' => $search);
           
$rows $receiverObj->queryTable($params);

        }

       
$maxItems $config['maxItemsInResultList'];
       
$maxItems min(count($rows), $maxItems);

        
$listItems $this->createListItemsFromResultRow($rows$maxItems);

        
$response->getBody()->write(json_encode($listItems));
        return 
$response;

    }

    
/**
     * @return LanguageService
     */
    
protected function getLanguageService()
    {
        return 
$GLOBALS['LANG'];
    }

    
/**
     * Checks the given field configuration for the tables that should be used for querying and returns them as an
     * array.
     *
     * @param array $fieldConfig
     * @return array
     */
    
protected function getTablesToQueryFromFieldConfiguration(array $fieldConfig)
    {
        
$queryTables = array();

        if (isset(
$fieldConfig['allowed'])) {
            if (
$fieldConfig['allowed'] !== '*') {
                
// list of allowed tables
                
$queryTables GeneralUtility::trimExplode(','$fieldConfig['allowed']);
            } else {
                
// all tables are allowed, if the user can access them
                
foreach ($GLOBALS['TCA'] as $tableName => $tableConfig) {
                    if (!
$this->isTableHidden($tableConfig) && $this->currentBackendUserMayAccessTable($tableConfig)) {
                        
$queryTables[] = $tableName;
                    }
                }
                unset(
$tableName$tableConfig);
            }
        } elseif (isset(
$fieldConfig['foreign_table'])) {
            
// use the foreign table
            
$queryTables = array($fieldConfig['foreign_table']);
        }

        return 
$queryTables;
    }

    
/**
     * Returns the SQL WHERE clause to use for querying records. This is currently only relevant if a foreign_table
     * is configured and should be used; it could e.g. be used to limit to a certain subset of records from the
     * foreign table
     *
     * @param array $fieldConfig
     * @param string $usedUids
     * @return string
     */
    
protected function getWhereClause(array $fieldConfig$usedUids)
    {
        if(!empty(
$usedUids)){
           return 
'AND uid NOT IN ('.$usedUids.')';
        } else {
            return 
'';
        }
       
// показываем только те элементы которых нет в уже добавленных

    
}

    
/**
     * Creates a list of <li> elements from a list of results returned by the receiver.
     *
     * @param array $resultRows
     * @param int $maxItems
     * @param string $rowIdSuffix
     * @return array
     */
    
protected function createListItemsFromResultRow(array $resultRows$maxItems)
    {
        if (empty(
$resultRows)) {
            return array();
        }
        
$listItems = array();

        
// traverse all found records and sort them
        
$rowsSort = array();
        foreach (
$resultRows as $key => $row) {
            
$rowsSort[$key] = $row['text'];
        }
        
asort($rowsSort);
        
$rowsSort array_keys($rowsSort);

        
// put together the selector entries
        
for ($i 0$i $maxItems; ++$i) {
            
$listItems[] = $resultRows[$rowsSort[$i]];
        }
        return 
$listItems;
    }
}
Комментировать тут особо нечего, убрал все что ненужно и не используется.

5. Classes\Hooks\InlineElementHook.php
Тут добавим нашу новую кнопку Unchain

PHP код:
<?php

namespace Extension\Hooks;

use 
TYPO3\CMS\Core\Utility\GeneralUtility;
use 
TYPO3\CMS\Lang\LanguageService;
use 
TYPO3\CMS\Core\Imaging\IconFactory;
use 
TYPO3\CMS\Core\Imaging\Icon;
use 
TYPO3\CMS\Backend\Form\InlineStackProcessor;

/**
 * Inline Element Hook
 *
 */
class InlineElementHook implements \TYPO3\CMS\Backend\Form\Element\InlineElementHookInterface
{

    
/**
     * Initializes this hook object.
     *
     * @param \TYPO3\CMS\Backend\Form\Element\InlineElement $parentObject
     * @return void
     */
    
public function init(&$parentObject)
    {
    }

    
/**
     * Pre-processing to define which control items are enabled or disabled.
     *
     * @param string $parentUid The uid of the parent (embedding) record (uid or NEW...)
     * @param string $foreignTable The table (foreign_table) we create control-icons for
     * @param array $childRecord The current record of that foreign_table
     * @param array $childConfig TCA configuration of the current field of the child record
     * @param bool $isVirtual Defines whether the current records is only virtually shown and not physically part of the parent record
     * @param array &$enabledControls (reference) Associative array with the enabled control items
     * @return void
     */
    
public function renderForeignRecordHeaderControl_preProcess(
        
$parentUid,
        
$foreignTable,
        array 
$childRecord,
        array 
$childConfig,
        
$isVirtual,
        array &
$enabledControls
    
) {

    }

    
/**
     * Post-processing to define which control items to show. Possibly own icons can be added here.
     *
     * @param string $parentUid The uid of the parent (embedding) record (uid or NEW...)
     * @param string $foreignTable The table (foreign_table) we create control-icons for
     * @param array $childRecord The current record of that foreign_table
     * @param array $childConfig TCA configuration of the current field of the child record
     * @param bool $isVirtual Defines whether the current records is only virtually shown and not physically part of the parent record
     * @param array &$cells (reference) Associative array with the currently available control items
     * @return void
     */
    
public function renderForeignRecordHeaderControl_postProcess(
        
$parentUid,
        
$foreignTable,
        array 
$childRecord,
        array 
$childConfig,
        
$isVirtual,
        array &
$cells
    
) {

       \\ 
кнопку можно убрать и вернуть старый добрый делит.. 
        if(
$childConfig['appearance']['enabledControls']['unchain']){
;
            
$iconFactory GeneralUtility::makeInstance(IconFactory::class);
            
$languageService GeneralUtility::makeInstance(LanguageService::class);
            
$title $languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.remove_selected'true);
            
$unchain '
                    <a class="btn btn-default t3js-editform-unchain-inline-record" href="#" >
                        ' 
'<span title="' $title '">' $iconFactory->getIcon('chain-broken'Icon::SIZE_SMALL)->render() . '</span>' '
                    </a>'

            
$cells array_slice($cells00true) + array('unchain' => $unchain) + array_slice($cells0count($cells), true);

            
//' . htmlspecialchars($nameObjectFtId) . '
        
}
        
    }

}
6. Classes\UserFunc\ExetndControls.php

Тут мы срендерим наш инпут поиска

PHP код:
<?php

namespace Extension\UserFunc;


use 
TYPO3\CMS\Core\Utility\GeneralUtility;
use 
TYPO3\CMS\Lang\LanguageService;

 class 
ExtendControls {

     
/**
       * Return standard host name for the local machine
       *
     * @param array $params
     * @param array $ref
     * @return void
       */

     
public function searchRelated(array &$params, &$ref){

        
        
$languageService GeneralUtility::makeInstance(LanguageService::class);
        
        
$table $params['table'];
        
$filedname $params['nameForm'];
        
$field $params['field'];
        
$row $params['row'];

        
$title $languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.createNewRelation'true);

        
$suggestProcessor GeneralUtility::makeInstance(\ER\ErPlaces\Backend\Form\Wizard\GetRelatedWizard::class);
        
$suggest $suggestProcessor->renderSuggestSelector($filedname$table$field$row$params);
        
        
// По умолчанию extended config появится под всеми полями.. так что просто убираем поиск вверх через position:absolute

        
$html '<div style="width:300px; position: absolute; right:0; top: 23px; margin-right: 15px;" ' . ($className ' class="' $className '"' '') . 'title="' $title '">' $suggest '</div>';

       
// скрипты пока не придумал как добавить по нормальному, т.к. &$ref -> resultArray (Protected) и добавить туда что либо из юзерфунка не выйдет.. так что пока вот по глупому все будет копироваться, два инпута два скрипта.. 
       
       // ! можно конечно напрямую Backend/Classes/Form/Container/InlineControlContainer.php меняем protected $requireJsModules на public и добавляем скрипт.

      //  $ref->requireJsModules[] = array(
      //       '../typo3conf/ext/service/Resources/Public/JavaScript/GetRelated' => 'function(GetRelatedInit){GetRelatedInit(".t3-form-suggest-inline")}'
      //  );

        
$html .= '
            <script type="text/javascript">
            /*<![CDATA[*/
            require(["../typo3conf/ext/er_places/Resources/Public/JavaScript/GetRelated"], function(GetRelatedInit){GetRelatedInit(".t3-form-suggest-inline")});
            /*]]>*/
            </script>
        '
;

        return 
$html;
  
     }
 }
7. Resources/Public/JavaScript/GetRelated.js
использовать обычный FormEngineSuggest.js не выйдет, у нас другой класс формы и инициализация немного по другому + тут слушаем нашу кнопку Unchain
Код HTML:
define(['jquery', 'jquery/autocomplete'], function ($) {
        var GetRelatedInit = function(searchField){
		var $searchField = $(searchField);
			$.each($searchField, function(key, value){
				GetRelated($(value));
			})
	};
	var GetRelated = function($searchField) {
	
		var $containerElement = $searchField.closest('.t3-form-suggest-container');
			$searchField.unbind();
		var table = $searchField.data('table'),
			field = $searchField.data('field'),
			uid = $searchField.data('uid'),
			uids = $searchField.data('uids'),
			pid = $searchField.data('pid'),
			newRecordRow = $searchField.data('recorddata'),
			minimumCharacters = $searchField.data('minchars'),
			url = TYPO3.settings.ajaxUrls['wizard_get_related'],
			params = {
				'table': table,
				'field': field,
				'uid': uid,
				'pid': pid,
				'newRecordRow': newRecordRow,
				'uids': uids
			};

		$searchField.autocomplete({
			// ajax options
			serviceUrl: url,
			params: params,
			type: 'POST',
			paramName: 'value',
			dataType: 'json',
			minChars: minimumCharacters,
			groupBy: 'typeLabel',
			containerClass: 'autocomplete-results',
			appendTo: $containerElement,
			forceFixPosition: false,
			preserveInput: true,
			showNoSuggestionNotice: true,
			noSuggestionNotice: '<div class="autocomplete-info">No results</div>',
			minLength: minimumCharacters,
			// put the AJAX results in the right format
			transformResult: function(response) {
				return {
					suggestions: $.map(response, function(dataItem) {
						return { value: dataItem.text, data: dataItem };
					})
				};
			},
			// Rendering of each item
			formatResult: function(suggestion, value) {
				console.log(suggestion);
				return $('<div>').append(
							$('<a class="autocomplete-suggestion-link" href="#">' +
								suggestion.data.sprite + suggestion.data.text +
							'</a></div>').attr({
								'data-label': suggestion.data.label,
								'data-table': suggestion.data.table,
								'data-uid': suggestion.data.uid
							})).html();
			},
			onSearchComplete: function() {
				$containerElement.addClass('open');
			},
			beforeRender: function(container) {
				// Unset height, width and z-index again, should be fixed by the plugin at a later point
				container.attr('style', ' width:300px');
				$containerElement.addClass('open');
			},
			onHide: function() {
				$containerElement.removeClass('open');
			}
		});

		// set up the events
		$containerElement.on('click', '.autocomplete-suggestion-link', function(evt) {
			evt.preventDefault();
			var insertData = $(this).data('uid'),
				objectidPart1 = $(this).parents('.form-group').attr('id'),
				table = $(this).data('table'),
				objectid = $(this).parents('.form-group').attr('id')+'-'+table;


			inline.importElement(objectid, table, insertData, 'db');

			// используем import раз уж он есть
		});


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

				$(document).on('click', '.t3js-editform-unchain-inline-record', function(e) {
					e.preventDefault();
						
						var $objectid = $(this).parents('.panel-heading').attr('id').slice(0, -7), 
							shortName = inline.parseObjectId('parts', $objectid, 2, 0, true);,
							title = 'Unchain this record?',
							content = 'Are you sure you want to unchain this record?';
	
						var $modal = Modal.confirm(title, content, top.TYPO3.Severity.warning, [
							{
								text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
								active: true,
								btnClass: 'btn-default',
								name: 'no'
							},
							{
								text: 'Yes, unchain this record',
								btnClass: 'btn-warning',
								name: 'yes'
							}
						]);
						$modal.on('button.clicked', function(e) {
							if (e.target.name === 'no') {
								Modal.dismiss();
							} else if (e.target.name === 'yes') {
								inline.deleteRecord($objectid);
								if(document.getElementsByName('cmd' + shortName + '[delete]').length){
									$(document.getElementsByName('cmd' + shortName + '[delete]')).attr('disabled', 'disabled');
								}
								Modal.dismiss();
							}
						});
					});
				});
		
	}

	return GetRelatedInit;
});
По JS да же не знаю что комментировать, так что если будут вопросы спрашивайте.. а на этом вроде все.

Чуть не забыл, вот пример TCA:

PHP код:
...
'type' => 'inline',
'foreign_table' => 'tx_example_domain_model_object',
'MM' => 'tx_example_domain_model_object_contacts_mm',
// Special Field for additional search
'addrelated' => true,
...
'enabledControls' => array(
// our button UNCHAIN
'unchain' => TRUE,
'delete' => 0,
),
... 
P.S.
Мой вам совет, что бы в поиске иконка всплывала нужно в TCA для каждой таблицы указывать
'ctrl' => array (
...
'typeicon_classes' => array(
'default' => 'ext-example-icon'
),
..
)
с добавлением в ext_tables.php
__________________
Фанат TYPO!

Последний раз редактировалось alexk; 03.05.2016 в 19:12
alexk вне форума   Ответить с цитированием
Старый 01.11.2017, 20:25   #2
Ивано++
Senior Member
 
Аватар для Ивано++
 
Регистрация: 18.01.2013
Адрес: Russia , Moscow
Сообщений: 783
По умолчанию

Классная модификация получилась.
__________________
T3Club.com
Ивано++ вне форума   Ответить с цитированием
Старый 02.01.2019, 15:35   #3
Ивано++
Senior Member
 
Аватар для Ивано++
 
Регистрация: 18.01.2013
Адрес: Russia , Moscow
Сообщений: 783
По умолчанию

Ранее была картинка по адресу:
http://elrise.ru/inline/inline.jpg

Можно актуальные скриншоты?
__________________
T3Club.com
Ивано++ вне форума   Ответить с цитированием
Ответ

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

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

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

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

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
TinyMCE RTE рулит-2 (EXT: tinymce_rte) Ивано++ Инструменты разработчика 16 10.07.2013 14:12
Подойдет ли TYPO3 для информационного сайта? vold57 Вопросы выбора CMS 19 06.12.2011 22:23
есть ли функционал в TYPO3 для работы с URL 3ton Общие вопросы 8 17.02.2010 15:50
Что использовать для простого каталога? cronfy Магазины и каталоги для TYPO3 4 23.05.2008 11:36
есть ли готовый функционал для агентств? filippoff Общие вопросы 8 13.10.2006 02:29


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


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

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