Commit e6df53f7 authored by Ralf Zimmermann's avatar Ralf Zimmermann Committed by Anja Leichsenring
Browse files

[FEATURE] EXT:form - Add element selector for text editors

Adds a new button to the text editors.
This splitbutton opens a list with available form elements.
If a form element is choosen, the form element identifier will be
inserted into the property text editor.

Resolves: #79442
Releases: master
Change-Id: If40114958311200348bde220f6cf62fb6365260b
Reviewed-on: https://review.typo3.org/51424

Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Björn Jacob's avatarBjoern Jacob <bjoern.jacob@tritum.de>
Tested-by: Björn Jacob's avatarBjoern Jacob <bjoern.jacob@tritum.de>
Tested-by: default avatarAndreas Steiger <typo3@andreassteiger.de>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
parent 1a318ec3
......@@ -24,6 +24,7 @@
@text-color: #000;
@btn-default-bg: #eee;
@btn-default-border: #bbb;
@line-height-base: 1.5;
// /Build/Resources/Public/Less/Component/module.less
@module-docheader-height: 65px;
......@@ -55,6 +56,7 @@
.fade-out-gradient-effect-bottom (@color, @gradient-start-height, @gradient-height) {
&:before, &:after {
z-index: 1;
display: block;
content: '';
position: absolute;
......@@ -112,7 +114,7 @@
text-decoration: none;
}
ol,
ul {
ul:not(.dropdown-menu) {
list-style: none;
padding: 0;
}
......@@ -270,6 +272,12 @@
vertical-align: top;
}
}
.icon-size-small {
line-height: @line-height-base;
}
.input-group-btn {
position: static;
}
.t3-form-remove-element-button {
position: absolute;
top: 90px;
......@@ -285,6 +293,16 @@
cursor: pointer;
}
}
.t3-form-dropdown-buttons.open {
position: static;
.dropdown-menu {
width: 100%;
a {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
//
......@@ -660,10 +678,7 @@
right: -1px;
}
.dropdown-menu {
left: auto;
left: initial;
min-width: initial;
right: 0;
padding-left: 0;
padding-right: 0;
background-color: darken(@brand-primary, 10%);
......@@ -723,7 +738,7 @@
height: 32px;
padding: 0.6em;
font-size: 12px;
line-height: 1.5;
line-height: @line-height-base;
background-image: none;
border: 1px solid @module-docheader-border;
border-radius: 2px;
......@@ -784,7 +799,7 @@
}
.meta-label {
z-index: 1;
z-index: 2;
position: absolute;
bottom: 1em;
left: 5.5em;
......@@ -896,6 +911,9 @@
path {
fill: @brand-danger;
}
&:hover {
background: lighten(@brand-danger, 30%);
}
}
}
}
......
.. include:: ../../Includes.txt
==================================================================
Feature: #79442 - EXT:form - add element selector for text editors
==================================================================
See :issue:`79442`
Description
===========
A new button has been added to the text editors of the form editor. Clicking on this button opens an overlay with available form elements of the current form. The user has the possibility to choose one of these form elements. The process adds the dynamic identifier (e.g. "{text-1}" to the current tex editor field.
.. index:: Backend, ext:form
\ No newline at end of file
......@@ -143,6 +143,8 @@ TYPO3:
modalRemoveElementLastAvailablePageFlashMessageTitle: 'formEditor.modals.removeElement.lastAvailablePageFlashMessageTitle'
modalRemoveElementLastAvailablePageFlashMessageMessage: 'formEditor.modals.removeElement.lastAvailablePageFlashMessageMessage'
inspectorEditorFormElementSelectorNoElements: 'formEditor.inspector.editor.formelement_selector.no_elements'
paginationTitle: 'formEditor.pagination.title'
iconIdentifier: 'content-elements-mailform'
......@@ -926,6 +928,7 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.subject.label'
propertyPath: 'options.subject'
enableFormelementSelectionButton: true
propertyValidators:
10: 'NotEmpty'
20: 'FormElementIdentifierWithinCurlyBracesInclusive'
......@@ -934,6 +937,7 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientAddress.label'
propertyPath: 'options.recipientAddress'
enableFormelementSelectionButton: true
propertyValidatorsMode: 'OR'
propertyValidators:
10: 'NaiveEmail'
......@@ -943,11 +947,15 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.recipientName.label'
propertyPath: 'options.recipientName'
enableFormelementSelectionButton: true
propertyValidators:
10: 'FormElementIdentifierWithinCurlyBracesInclusive'
500:
identifier: 'senderAddress'
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderAddress.label'
propertyPath: 'options.senderAddress'
enableFormelementSelectionButton: true
propertyValidatorsMode: 'OR'
propertyValidators:
10: 'NaiveEmail'
......@@ -957,11 +965,15 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.senderName.label'
propertyPath: 'options.senderName'
enableFormelementSelectionButton: true
propertyValidators:
10: 'FormElementIdentifierWithinCurlyBracesInclusive'
700:
identifier: 'replyToAddress'
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.replyToAddress.label'
propertyPath: 'options.replyToAddress'
enableFormelementSelectionButton: true
propertyValidatorsMode: 'OR'
propertyValidators:
10: 'NaiveEmailOrEmpty'
......@@ -971,6 +983,7 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.carbonCopyAddress.label'
propertyPath: 'options.carbonCopyAddress'
enableFormelementSelectionButton: true
propertyValidatorsMode: 'OR'
propertyValidators:
10: 'NaiveEmailOrEmpty'
......@@ -980,6 +993,7 @@ TYPO3:
templateName: 'Inspector-TextEditor'
label: 'formEditor.elements.Form.finisher.EmailToSender.editor.blindCarbonCopyAddress.label'
propertyPath: 'options.blindCarbonCopyAddress'
enableFormelementSelectionButton: true
propertyValidatorsMode: 'OR'
propertyValidators:
10: 'NaiveEmailOrEmpty'
......
{namespace core = TYPO3\CMS\Core\ViewHelpers}
<div class="form-editor">
<div class="t3-form-control-group form-group">
<label><span data-template-property="label" /></label>
<div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
<input type="text" value="" data-template-property="propertyPath" class="form-control">
<span data-template-property="fieldExplanationText" />
<span data-template-property="validationErrors" />
<div class="input-group" data-identifier="inspectorEditorControlsGroup">
<div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
<input type="text" value="" data-template-property="propertyPath" class="form-control">
</div>
<span class="input-group-btn" role="group" data-identifier="inspectorEditorFormElementSelectorControlsWrapper">
<span class="btn-group t3-form-dropdown-buttons" data-identifier="inspectorEditorFormElementSelectorSplitButtonContainer">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false" title="{f:translate(key: 'LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.inspector.editor.formelement_selector.title')}">
<core:icon identifier="t3-form-icon-form-element-selector" />
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu dropdown-menu-right" data-identifier="inspectorEditorFormElementSelectorSplitButtonListContainer"></ul>
</span>
</span>
</div>
<span data-template-property="fieldExplanationText" />
<span data-template-property="validationErrors" />
</div>
</div>
\ No newline at end of file
......@@ -12,7 +12,7 @@
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<ul class="dropdown-menu dropdown-menu-right">
<li data-no-sorting>
<a href="#" data-identifier="stageElementToolbarNewElementSplitButtonInside">
<core:icon identifier="t3-form-icon-insert-in" alternativeMarkupIdentifier="inline" />
......
......@@ -685,6 +685,13 @@
<trans-unit id="formEditor.stage.toolbar.new_element.after" xml:space="preserve">
<source>After</source>
</trans-unit>
<trans-unit id="formEditor.inspector.editor.formelement_selector.title" xml:space="preserve">
<source>Insert formelement identifier</source>
</trans-unit>
<trans-unit id="formEditor.inspector.editor.formelement_selector.no_elements" xml:space="preserve">
<source>No elements available</source>
</trans-unit>
</body>
</file>
</xliff>
......@@ -22,7 +22,7 @@
text-decoration: none;
}
.t3-form-x-component ol,
.t3-form-x-component ul {
.t3-form-x-component ul:not(.dropdown-menu) {
list-style: none;
padding: 0;
}
......@@ -162,6 +162,12 @@
#t3-form-inspector h4 span[data-template-property="label"] {
vertical-align: top;
}
#t3-form-inspector .icon-size-small {
line-height: 1.5;
}
#t3-form-inspector .input-group-btn {
position: static;
}
#t3-form-inspector .t3-form-remove-element-button {
position: absolute;
top: 90px;
......@@ -175,6 +181,16 @@
#t3-form-inspector .t3-form-inspector-editor-requiredValidator label {
cursor: pointer;
}
#t3-form-inspector .t3-form-dropdown-buttons.open {
position: static;
}
#t3-form-inspector .t3-form-dropdown-buttons.open .dropdown-menu {
width: 100%;
}
#t3-form-inspector .t3-form-dropdown-buttons.open .dropdown-menu a {
overflow: hidden;
text-overflow: ellipsis;
}
.t3-form-add-collection-element {
padding-bottom: 1em;
}
......@@ -407,6 +423,7 @@
}
#t3-form-stage-container.t3-form-stage-viewmode-abstract .t3-form-element-info:before,
#t3-form-stage-container.t3-form-stage-viewmode-abstract .t3-form-element-info:after {
z-index: 1;
display: block;
content: '';
position: absolute;
......@@ -472,6 +489,7 @@
}
#t3-form-stage-container.t3-form-stage-viewmode-abstract .t3-form-validator-info .t3-form-validator-list:before,
#t3-form-stage-container.t3-form-stage-viewmode-abstract .t3-form-validator-info .t3-form-validator-list:after {
z-index: 1;
display: block;
content: '';
position: absolute;
......@@ -553,10 +571,7 @@
right: -1px;
}
#t3-form-stage-container.t3-form-stage-viewmode-abstract #t3-form-stage .t3-form-form-element-selected .btn-toolbar-container .dropdown-menu {
left: auto;
left: initial;
min-width: initial;
right: 0;
padding-left: 0;
padding-right: 0;
background-color: #005db3;
......@@ -697,7 +712,7 @@
display: none;
}
.meta-label {
z-index: 1;
z-index: 2;
position: absolute;
bottom: 1em;
left: 5.5em;
......@@ -798,6 +813,9 @@
#t3-form-inspector-panels .t3-form-validation-errors.t3-form-collection-element .t3-form-collection-element-remove-button path {
fill: #c83c3c;
}
#t3-form-inspector-panels .t3-form-validation-errors.t3-form-collection-element .t3-form-collection-element-remove-button:hover {
background: #eab3b3;
}
.form-editor-loading-spinner {
width: 150px;
margin: 5em auto 0;
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<polygon points="11,8 8,8 8,5 7,5 7,8 4,8 4,9 7,9 7,12 8,12 8,9 11,9 "/>
<path d="M2,9c0-0.6-0.4-1-1-1c0.6,0,1-0.4,1-1V3h1.8V2H2C1.4,2,1,2.4,1,3v4C0.4,7,0,7.4,0,8l0,0c0,0.6,0.4,1,1,1v4c0,0.6,0.4,1,1,1
h1.8v-1H2V9z"/>
<path d="M14,7V3c0-0.6-0.4-1-1-1h-1.8v1H13v4c0,0.6,0.4,1,1,1c-0.6,0-1,0.4-1,1v4h-1.8v1H13c0.6,0,1-0.4,1-1V9c0.6,0,1-0.4,1-1l0,0
C15,7.4,14.6,7,14,7z"/>
</svg>
......@@ -800,6 +800,15 @@ define(['jquery',
);
};
/**
* @public
*
* @return object
*/
function getNonCompositeNonToplevelFormElements() {
return _getRepository().getNonCompositeNonToplevelFormElements();
};
/**
* @public
*
......@@ -1051,6 +1060,7 @@ define(['jquery',
findEnclosingCompositeFormElementWhichIsNotOnTopLevel: findEnclosingCompositeFormElementWhichIsNotOnTopLevel,
isRootFormElementSelected: isRootFormElementSelected,
getLastFormElementWithinParentFormElement: getLastFormElementWithinParentFormElement,
getNonCompositeNonToplevelFormElements: getNonCompositeNonToplevelFormElements,
getFormElementDefinitionByType: getFormElementDefinitionByType,
getFormElementDefinition: getFormElementDefinition,
......
......@@ -1309,6 +1309,35 @@ define(['jquery'], function($) {
return formElement;
};
/**
* @return object
*/
function getNonCompositeNonToplevelFormElements() {
var collect, nonCompositeNonToplevelFormElements;
collect = function(formElement) {
var formElements, formElementTypeDefinition;
utility().assert('object' === $.type(formElement), 'Invalid parameter "formElement"', 1475364961);
formElementTypeDefinition = repository().getFormEditorDefinition('formElements', formElement.get('type'));
if (!formElementTypeDefinition['_isTopLevelFormElement'] && !formElementTypeDefinition['_isCompositeFormElement']) {
nonCompositeNonToplevelFormElements.push(formElement);
}
formElements = formElement.get('renderables');
if ('array' === $.type(formElements)) {
for (var i = 0, len = formElements.length; i < len; ++i) {
collect(formElements[i]);
}
}
};
nonCompositeNonToplevelFormElements = [];
collect(getRootFormElement());
return nonCompositeNonToplevelFormElements;
};
/**
* @param string identifier
* @returl bool
......@@ -1610,6 +1639,7 @@ define(['jquery'], function($) {
findEnclosingCompositeFormElementWhichIsNotOnTopLevel: findEnclosingCompositeFormElementWhichIsNotOnTopLevel,
findEnclosingCompositeFormElementWhichIsOnTopLevel: findEnclosingCompositeFormElementWhichIsOnTopLevel,
getIndexForEnclosingCompositeFormElementWhichIsOnTopLevelForFormElement: getIndexForEnclosingCompositeFormElementWhichIsOnTopLevelForFormElement,
getNonCompositeNonToplevelFormElements: getNonCompositeNonToplevelFormElements,
getNextFreeFormElementIdentifier: getNextFreeFormElementIdentifier,
isFormElementIdentifierUsed: isFormElementIdentifierUsed,
......
......@@ -49,6 +49,7 @@ define(['jquery',
collectionElement: 't3-form-collection-element',
finisherEditorPrefix: 't3-form-inspector-finishers-editor-',
inspectorEditor: 'form-editor',
inspectorInputGroup: 'input-group',
validatorEditorPrefix: 't3-form-inspector-validators-editor-'
},
domElementDataAttributeNames: {
......@@ -58,8 +59,13 @@ define(['jquery',
},
domElementDataAttributeValues: {
collapse: 'actions-view-table-expand',
editorControlsInputGroup: 'inspectorEditorControlsGroup',
editorControlsWrapper: 'inspectorEditorControlsWrapper',
formElementHeaderEditor: 'inspectorFormElementHeaderEditor',
formElementSelectorControlsWrapper: 'inspectorEditorFormElementSelectorControlsWrapper',
formElementSelectorSplitButtonContainer: 'inspectorEditorFormElementSelectorSplitButtonContainer',
formElementSelectorSplitButtonListContainer: 'inspectorEditorFormElementSelectorSplitButtonListContainer',
iconNotAvailable: 'actions-message-error-close',
iconPage: 'apps-pagetree-page-default',
iconTtContent: 'mimetypes-x-content-text',
inspector: 'inspector',
......@@ -1066,6 +1072,8 @@ define(['jquery',
getHelper().getTemplatePropertyDomElement('propertyPath', editorHtml).val(propertyData);
renderFormElementSelectorEditorAddition(editorConfiguration, editorHtml, propertyPath);
getHelper().getTemplatePropertyDomElement('propertyPath', editorHtml).on('keyup paste', function() {
getCurrentlySelectedFormElement().set(propertyPath, $(this).val());
_validateCollectionElement(propertyPath, editorHtml);
......@@ -1705,6 +1713,115 @@ define(['jquery',
});
};
/**
* @public
*
* @param object editorConfiguration
* @param object editorHtml
* @param string propertyPath
* @return void
* @throws 1484574704
* @throws 1484574705
* @throws 1484574706
*/
function renderFormElementSelectorEditorAddition(editorConfiguration, editorHtml, propertyPath) {
var nonCompositeNonToplevelFormElements, formElementSelectorControlsWrapper, formElementSelectorSplitButtonListContainer, itemTemplate;
assert(
'object' === $.type(editorConfiguration),
'Invalid parameter "editorConfiguration"',
1484574704
);
assert(
'object' === $.type(editorHtml),
'Invalid parameter "editorHtml"',
1484574705
);
assert(
getUtility().isNonEmptyString(propertyPath),
'Invalid parameter "propertyPath"',
1484574706
);
formElementSelectorControlsWrapper = $(
getHelper().getDomElementDataIdentifierSelector('formElementSelectorControlsWrapper'), editorHtml
);
if (editorConfiguration['enableFormelementSelectionButton'] === true) {
if (formElementSelectorControlsWrapper.length === 0) {
return;
}
formElementSelectorSplitButtonListContainer = $(
getHelper().getDomElementDataIdentifierSelector('formElementSelectorSplitButtonListContainer'), editorHtml
);
formElementSelectorSplitButtonListContainer.off().empty();
nonCompositeNonToplevelFormElements = getFormEditorApp().getNonCompositeNonToplevelFormElements();
if (nonCompositeNonToplevelFormElements.length === 0) {
Icons.getIcon(
getHelper().getDomElementDataAttributeValue('iconNotAvailable'),
Icons.sizes.small,
null,
Icons.states.default
).done(function(icon) {
itemTemplate = $('<li data-no-sorting>'
+ '<a href="#"></a>'
+ '</li>');
itemTemplate
.append($(icon))
.append(' ' + getFormElementDefinition(getRootFormElement(), 'inspectorEditorFormElementSelectorNoElements'));
formElementSelectorSplitButtonListContainer.append(itemTemplate);
});
} else {
for (var i = 0, len = nonCompositeNonToplevelFormElements.length; i < len; ++i) {
var nonCompositeNonToplevelFormElement;
nonCompositeNonToplevelFormElement = nonCompositeNonToplevelFormElements[i];
Icons.getIcon(
getFormElementDefinition(nonCompositeNonToplevelFormElement, 'iconIdentifier'),
Icons.sizes.small,
null,
Icons.states.default
).done(function(icon) {
itemTemplate = $('<li data-no-sorting>'
+ '<a href="#" data-formelement-identifier="' + nonCompositeNonToplevelFormElement.get('identifier') + '">'
+ '</a>'
+ '</li>');
$('[data-formelement-identifier="' + nonCompositeNonToplevelFormElement.get('identifier') + '"]', itemTemplate)
.append($(icon))
.append(' ' + nonCompositeNonToplevelFormElements[i].get('label'));
$('a', itemTemplate).on('click', function() {
var propertyData;
propertyData = getCurrentlySelectedFormElement().get(propertyPath);
if (propertyData.length === 0) {
propertyData = '{' + $(this).attr('data-formelement-identifier') + '}';
} else {
propertyData = propertyData + ' ' + '{' + $(this).attr('data-formelement-identifier') + '}';
}
getCurrentlySelectedFormElement().set(propertyPath, propertyData);
getHelper().getTemplatePropertyDomElement('propertyPath', editorHtml).val(propertyData);
_validateCollectionElement(propertyPath, editorHtml);
});
formElementSelectorSplitButtonListContainer.append(itemTemplate);
});
}
}
} else {
$(getHelper().getDomElementDataIdentifierSelector('editorControlsInputGroup'), editorHtml)
.removeClass(getHelper().getDomElementClassName('inspectorInputGroup'));
formElementSelectorControlsWrapper.off().empty().remove();
}
}
/**
* @public
*
......@@ -1771,6 +1888,7 @@ define(['jquery',
renderCollectionElementSelectionEditor: renderCollectionElementSelectionEditor,
renderEditors: renderEditors,
renderFormElementHeaderEditor: renderFormElementHeaderEditor,
renderFormElementSelectorEditorAddition: renderFormElementSelectorEditorAddition,
renderPropertyGridEditor: renderPropertyGridEditor,
renderRemoveElementEditor: renderRemoveElementEditor,
renderRequiredValidatorEditor: renderRequiredValidatorEditor,
......
......@@ -40,6 +40,7 @@ call_user_func(function () {
'fieldset',
'file-upload',
'finisher',
'form-element-selector',
'image-upload',
'insert-after',
'insert-in',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment