Расширяем функционал для Inline Elements
Приветствую коллеги! Заморочился я тут с Inline Elements, как мы уже знаем там нет функционала как в Group и добавлять существующие записи уже невозможно, а хотелось бы.. можно конечно использовать Group c "+", но тогда если нам нужно создать, нас будет кидать на новую страницу.. что совсем не камильфо, нужно здесь и сейчас, т.е. нам все таки нужен функционал Inline.
Мне вот тут приперло, так что делюсь своим решением это задачи.. опять же у кого есть задумки лучше и проще, прошу в студию.
На выходе мы получим вот такую красоту, поиск с автокомплитом и доп кнопку на "Отсоединение элемента", так что бы старые элементы не удалялись, а отсоединялись.
http://elrise.ru/inline/inline.jpg
Поехали...
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 $request, ResponseInterface $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($cells, 0, 0, true) + array('unchain' => $unchain) + array_slice($cells, 0, count($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
|