Commit 1460e9ec authored by Benni Mack's avatar Benni Mack Committed by Oliver Hader
Browse files

[CLEANUP] Rewrite JS/HTML part of Flexform sections

* Adds a jQuery version of the JavaScript features
   for TCEform FlexForms, and include them in
   the TYPO3 rendering call when accessing
   TCEforms FlexForms.
 * Adds clean "t3-flex" prefixed CSS classes for all
   components of FlexForms sections. Added styling
   for the components with the new classes
 * Removes all existing inline JS code for FlexForms
 * Adds minor bug fixes (no title output for section
   elements, preview text will be rendered on load on
   hidden section elements)
 * Reviewed existing ACTION code in TCEmain
   for flexform elements

One minor side note is that sorting is not working, which
will immediately be replaced with jquery ui in a followup
patch.

Resolves: #38736
Releases: master
Change-Id: I9fadf06d3271dd771489fcdd182cddf9542b59fd
Reviewed-on: http://review.typo3.org/12698

Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent 7c5cec20
......@@ -103,6 +103,8 @@ class FlexElement extends AbstractFormElement {
$tabsToTraverse = array($sheet);
}
$this->getControllerDocumentTemplate()->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/FormEngineFlexForm');
/** @var $elementConditionMatcher \TYPO3\CMS\Backend\Form\ElementConditionMatcher */
$elementConditionMatcher = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Form\ElementConditionMatcher::class);
......@@ -172,7 +174,7 @@ class FlexElement extends AbstractFormElement {
}
// Render flexform:
$tRows = $this->getSingleField_typeFlex_draw($dataStruct['ROOT']['el'], $editData['data'][$sheet][$lang], $table, $field, $row, $additionalInformation, '[data][' . $sheet . '][' . $lang . ']');
$sheetContent = '<div class="typo3-TCEforms-flexForm">' . $tRows . '</div>';
$sheetContent = '<div class="typo3-TCEforms-flexForm t3-form-flexform">' . $tRows . '</div>';
// Pop the sheet level tab from DynNestedStack
if (is_array($dataStructArray['sheets'])) {
$this->formEngine->popFromDynNestedStack('tab', $tabIdentString . '-' . (count($tabParts) + 1));
......@@ -296,7 +298,7 @@ class FlexElement extends AbstractFormElement {
$onClickInsert = 'var ' . $var . ' = "' . 'idx"+(new Date()).getTime();'
// Do not replace $isTagPrefix in setActionStatus() because it needs section id!
. 'new Insertion.Bottom($("' . $idTagPrefix . '"), ' . json_encode($newElementTemplate)
. '.' . $replace . '); setActionStatus("' . $idTagPrefix . '");'
. '.' . $replace . '); TYPO3.jQuery("#' . $idTagPrefix . '").t3FormEngineFlexFormElement();'
. 'eval(unescape("' . rawurlencode(implode(';', $this->formEngine->additionalJS_post)) . '").' . $replace . ');'
. 'TBE_EDITOR.addActionChecks("submit", unescape("'
. rawurlencode(implode(';', $this->formEngine->additionalJS_submit)) . '").' . $replace . ');'
......@@ -318,28 +320,32 @@ class FlexElement extends AbstractFormElement {
}
// Reverting internal variables we don't want to change:
$this->formEngine->requiredElements = $TEMP_requiredElements;
// Adding the sections:
// Adding the sections
// add the "toggle all" button for the sections
$toggleAll = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.toggleall', TRUE);
$output .= '
<div class="t3-form-field-toggle-flexsection">
<a href="#" onclick="flexFormToggleSubs(\'' . htmlspecialchars($idTagPrefix) . '\'); return false;">'
. IconUtility::getSpriteIcon('actions-move-right', array('title' => $toggleAll)) . $toggleAll . '
</a>
<div class="t3-form-field-toggle-flexsection t3-form-flexsection-toggle">
<a href="#">'. IconUtility::getSpriteIcon('actions-move-right', array('title' => $toggleAll)) . $toggleAll . '</a>
</div>
<div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsection t3-flex-container" data-t3-flex-allow-restructure="' . ($mayRestructureFlexforms ? 1 : 0) . '">' . implode('', $tRows) . '</div>';
// add the "new" link
if ($mayRestructureFlexforms) {
$output .= '<div class="t3-form-field-add-flexsection"><strong>'
. $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.addnew', TRUE)
. ':</strong> ' . implode(' | ', $newElementsLinks) . '</div>';
}
<div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsection">' . implode('', $tRows) . '</div>';
$output .= $mayRestructureFlexforms ? '<div class="t3-form-field-add-flexsection"><strong>'
. $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.addnew', TRUE)
. ':</strong> ' . implode(' | ', $newElementsLinks) . '</div>' : '';
$output = '<div class="t3-form-field-container t3-form-flex">' . $output . '</div>';
} else {
// It is a container
$toggleIcon_open = IconUtility::getSpriteIcon('actions-move-down');
$toggleIcon_close = IconUtility::getSpriteIcon('actions-move-right');
// Create on-click actions.
$onClickRemove = 'if (confirm("Are you sure?")){/*###REMOVE###*/;$("' . $idTagPrefix
. '").hide();setActionStatus("' . $idPrefix . '");} return false;';
$onClickToggle = 'flexFormToggle("' . $idTagPrefix . '"); return false;';
$onMove = 'flexFormSortable("' . $idPrefix . '")';
// It is a container of a single section
$toggleIconOpenState = ($toggleClosed ? 'display: none;' : '');
$toggleIconCloseState = (!$toggleClosed ? 'display: none;' : '');
$toggleIcons = IconUtility::getSpriteIcon('actions-move-down', array('class' => 't3-flex-control-toggle-icon-open', 'style' => $toggleIconOpenState));
$toggleIcons .= IconUtility::getSpriteIcon('actions-move-right', array('class' => 't3-flex-control-toggle-icon-close', 'style' => $toggleIconCloseState));
// Notice: Creating "new" elements after others seemed to be too difficult to do
// and since moving new elements created in the bottom is now so easy
// with drag'n'drop I didn't see the need.
......@@ -351,21 +357,20 @@ class FlexElement extends AbstractFormElement {
// be possible to move as a sortable. But this way a new sortable is initialized every time
// someone tries to move and it will always work.
$ctrlHeader = '
<table class="t3-form-field-header-flexsection" onmousedown="' . ($mayRestructureFlexforms ? htmlspecialchars($onMove) : '') . '">
<tr>
<td>
<a href="#" onclick="' . htmlspecialchars($onClickToggle) . '" id="' . $idTagPrefix . '-toggle">
' . ($toggleClosed ? $toggleIcon_close : $toggleIcon_open) . '
</a>
<strong>' . $theTitle . '</strong> <em><span id="' . $idTagPrefix . '-preview"></span></em>
</td>
<td align="right">'
. ($mayRestructureFlexforms ? IconUtility::getSpriteIcon('actions-move-move', array('title' => 'Drag to Move')) : '')
. ($mayRestructureFlexforms ? '<a href="#" onclick="' . htmlspecialchars($onClickRemove) . '">'
. IconUtility::getSpriteIcon('actions-edit-delete', array('title' => 'Delete')) : '')
. '</td>
</tr>
</table>';
<div class="pull-left">
<a href="#" class="t3-flex-control-toggle-button">' . $toggleIcons . '</a>
<span class="t3-record-title">' . $theTitle . '</span>
</div>';
if ($mayRestructureFlexforms) {
$ctrlHeader .= '<div class="pull-right">'
. IconUtility::getSpriteIcon('actions-move-move', array('title' => 'Drag to Move'))
. IconUtility::getSpriteIcon('actions-edit-delete', array('title' => 'Delete', 'class' => 't3-delete'))
. '</div>';
}
$ctrlHeader = '<div class="t3-form-field-header-flexsection t3-flex-section-header">' . $ctrlHeader . '</div>';
$s = GeneralUtility::revExplode('[]', $formPrefix, 2);
$actionFieldName = '_ACTION_FLEX_FORM' . $PA['itemFormElName'] . $s[0] . '][_ACTION][' . $s[1];
// Push the container to DynNestedStack as it may be toggled
......@@ -376,14 +381,14 @@ class FlexElement extends AbstractFormElement {
$editData[$key]['el'], $table, $field, $row, $PA,
($formPrefix . '[' . $key . '][el]'), ($level + 1), $idTagPrefix);
$output .= '
<div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsections">
<input id="' . $idTagPrefix . '-action" type="hidden" name="' . htmlspecialchars($actionFieldName) . '" value=""/>
<div id="' . $idTagPrefix . '" class="t3-form-field-container-flexsections t3-flex-section">
<input class="t3-flex-control t3-flex-control-action" type="hidden" name="' . htmlspecialchars($actionFieldName) . '" value=""/>
' . $ctrlHeader . '
<div class="t3-form-field-record-flexsection" id="' . $idTagPrefix . '-content"'
<div class="t3-form-field-record-flexsection t3-flex-section-content"'
. ($toggleClosed ? ' style="display:none;"' : '') . '>' . $singleField_typeFlex_draw . '
</div>
<input id="' . $idTagPrefix . '-toggleClosed" type="hidden" name="'
<input class="t3-flex-control t3-flex-control-toggle" id="' . $idTagPrefix . '-toggleClosed" type="hidden" name="'
. htmlspecialchars('data[' . $table . '][' . $row['uid'] . '][' . $field . ']' . $formPrefix . '[_TOGGLE]')
. '" value="' . ($toggleClosed ? 1 : 0) . '" />
</div>';
......
......@@ -3994,73 +3994,11 @@ class FormEngine {
$this->loadJavascriptLib('sysext/backend/Resources/Public/JavaScript/jsfunc.tceforms_selectboxfilter.js');
}
}
// Toggle icons:
$toggleIcon_open = IconUtility::getSpriteIcon('actions-move-down', array('title' => 'Open'));
$toggleIcon_close = IconUtility::getSpriteIcon('actions-move-right', array('title' => 'Close'));
$out .= '
function getOuterHTML(idTagPrefix) { // Function getting the outerHTML of an element with id
var str=($(idTagPrefix).inspect()+$(idTagPrefix).innerHTML+"</"+$(idTagPrefix).tagName.toLowerCase()+">");
return str;
}
function flexFormToggle(id) { // Toggling flexform elements on/off:
Element.toggle(""+id+"-content");
if (Element.visible(id+"-content")) {
$(id+"-toggle").update(\'' . $toggleIcon_open . '\');
$(id+"-toggleClosed").value = 0;
} else {
$(id+"-toggle").update(\'' . $toggleIcon_close . '\');
$(id+"-toggleClosed").value = 1;
}
var previewContent = "";
var children = $(id+"-content").getElementsByTagName("input");
for (var i = 0, length = children.length; i < length; i++) {
if (children[i].type=="text" && children[i].value) previewContent+= (previewContent?" / ":"")+children[i].value;
}
if (previewContent.length>80) {
previewContent = previewContent.substring(0,67)+"...";
}
$(id+"-preview").update(previewContent);
}
function flexFormToggleSubs(id) { // Toggling sub flexform elements on/off:
var descendants = $(id).immediateDescendants();
var isOpen=0;
var isClosed=0;
// Traverse and find how many are open or closed:
for (var i = 0, length = descendants.length; i < length; i++) {
if (descendants[i].id) {
if (Element.visible(descendants[i].id+"-content")) {isOpen++;} else {isClosed++;}
}
}
// Traverse and toggle
for (var i = 0, length = descendants.length; i < length; i++) {
if (descendants[i].id) {
if (isOpen!=0 && isClosed!=0) {
if (Element.visible(descendants[i].id+"-content")) {flexFormToggle(descendants[i].id);}
} else {
flexFormToggle(descendants[i].id);
}
}
}
}
function flexFormSortable(id) { // Create sortables for flexform sections
Position.includeScrollOffsets = true;
Sortable.create(id, {tag:\'div\',constraint: false, onChange:function(){
setActionStatus(id);
} });
}
function setActionStatus(id) { // Updates the "action"-status for a section. This is used to move and delete elements.
var descendants = $(id).immediateDescendants();
// Traverse and find how many are open or closed:
for (var i = 0, length = descendants.length; i < length; i++) {
if (descendants[i].id) {
$(descendants[i].id+"-action").value = descendants[i].visible() ? i : "DELETE";
}
}
}
TBE_EDITOR.images.req.src = "' . IconUtility::skinImg($this->backPath, 'gfx/required_h.gif', '', 1) . '";
TBE_EDITOR.images.cm.src = "' . IconUtility::skinImg($this->backPath, 'gfx/content_client.gif', '', 1) . '";
......
/**
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* contains all JS functions related to TYPO3 Flexforms
* available under the latest jQuery version
* can be used by $('myflexform').t3FormEngineFlexFormElement({options});, all .t3-flex-form containers will be called on load
*
* currently TYPO3.FormEngine.FlexFormElement represents one Flexform element
* which can contain one ore more sections
*/
define('TYPO3/CMS/Backend/FormEngineFlexForm', ['jquery', 'TYPO3/CMS/Backend/FormEngine'], function ($) {
TYPO3.FormEngine.FlexFormElement = function(el, options) {
var me = this; // avoid scope issues
var opts; // shorthand options notation
// initialization function; private
me.initialize = function() {
// store DOM element and jQuery object for later use
me.el = el;
me.$el = $(el);
// remove any existing backups
var old_me = me.$el.data('TYPO3.FormEngine.FlexFormElement');
if (old_me !== undefined) {
me.$el.removeData('TYPO3.FormEngine.FlexFormElement');
}
// add a reverse reference to the DOM element
me.$el.data('TYPO3.FormEngine.FlexFormElement', me);
if (!options) {
options = {};
}
// set some values from existing properties
options.allowRestructure = me.$el.data('t3-flex-allow-restructure');
options.flexformId = me.$el.attr('id');
// store options and merge with default options
opts = me.options = $.extend({}, TYPO3.FormEngine.FlexFormElement.defaults, options);
// initialize events
me.initializeEvents();
// generate the preview text if a section is hidden on load
me.$el.find(opts.sectionSelector).each(function() {
me.generateSectionPreview($(this));
});
return me;
};
// init all events related to the flexform
me.initializeEvents = function() {
// Toggling all sections on/off by clicking all toggle buttons of each section
me.$el.prev(opts.flexFormToggleAllSectionsSelector).on('click', function() {
me.$el.find(opts.sectionToggleButtonSelector).trigger('click');
});
if (opts.allowRestructure) {
// create a sortable when dragging on the header of a section
me.createSortable();
// allow delete of a single section
me.$el.on('click', opts.deleteIconSelector, function(evt) {
evt.preventDefault();
// @todo: make this text localizable
if (window.confirm('Are you sure?')) {
$(this).closest(opts.sectionSelector).hide().addClass(opts.sectionDeletedClass);
me.setActionStatus();
}
});
// allow the toggle open/close of the main selection
me.$el.on('click', opts.sectionToggleButtonSelector, function(evt) {
evt.preventDefault();
var $sectionEl = $(this).closest(opts.sectionSelector);
me.toggleSection($sectionEl);
});
}
return me;
};
// initialize ourself
me.initialize();
};
// setting some default values
TYPO3.FormEngine.FlexFormElement.defaults = {
'deleteIconSelector': '.t3-delete',
'sectionSelector': '.t3-flex-section',
'sectionContentSelector': '.t3-flex-section-content',
'sectionHeaderSelector': '.t3-flex-section-header',
'sectionHeaderPreviewSelector': '.t3-flex-section-header-preview',
'sectionActionInputFieldSelector': '.t3-flex-control-action',
'sectionToggleInputFieldSelector': '.t3-flex-control-toggle',
'sectionToggleIconOpenSelector': '.t3-flex-control-toggle-icon-open',
'sectionToggleIconCloseSelector': '.t3-flex-control-toggle-icon-close',
'sectionToggleButtonSelector': '.t3-flex-control-toggle-button',
'flexFormToggleAllSectionsSelector': '.t3-form-field-toggle-flexsection',
'sectionDeletedClass': 't3-flex-section-deleted',
'allowRestructure': 0, // whether the form can be modified
'flexformId': false
};
// Create sortables for flexform sections
TYPO3.FormEngine.FlexFormElement.prototype.createSortable = function() {
var me = this;
// @todo: use something else than scriptaculous sortable
Position.includeScrollOffsets = true;
Sortable.create(me.options.flexformId, {
tag: 'div',
constraint: false,
handle: me.options.sectionHeaderSelector.replace(/\./, ''),
onChange: function() {
me.setActionStatus();
}
});
};
// Updates the "action"-status for a section. This is used to move and delete elements.
TYPO3.FormEngine.FlexFormElement.prototype.setActionStatus = function() {
var me = this;
// Traverse and find how many sections are open or closed, and save the value accordingly
me.$el.find(me.options.sectionActionInputFieldSelector).each(function(index) {
var actionValue = ($(this).parents(me.options.sectionSelector).hasClass(me.options.sectionDeletedClass) ? 'DELETE' : index);
$(this).val(actionValue);
});
};
// Toggling flexform elements on/off
// hides the flexform section and shows a preview text
// or shows the form parts
TYPO3.FormEngine.FlexFormElement.prototype.toggleSection = function($sectionEl) {
var $contentEl = $sectionEl.find(this.options.sectionContentSelector);
// display/hide the content of this flexform section
$contentEl.toggle();
if ($contentEl.is(':visible')) {
// show the open icon, and set the hidden field for toggling to "hidden"
$sectionEl.find(this.options.sectionToggleIconOpenSelector).show();
$sectionEl.find(this.options.sectionToggleIconCloseSelector).hide();
$sectionEl.find(this.options.sectionToggleInputFieldSelector).val(0);
} else {
// show the close icon, and set the hidden field for toggling to "1"
$sectionEl.find(this.options.sectionToggleIconOpenSelector).hide();
$sectionEl.find(this.options.sectionToggleIconCloseSelector).show();
$sectionEl.find(this.options.sectionToggleInputFieldSelector).val(1);
}
// see if the preview content needs to be generated
this.generateSectionPreview($sectionEl);
};
// function to generate the section preview in the header
// if the section content is hidden
// called on load and when toggling an icon
TYPO3.FormEngine.FlexFormElement.prototype.generateSectionPreview = function($sectionEl) {
var $contentEl = $sectionEl.find(this.options.sectionContentSelector);
var previewContent = '';
if (!$contentEl.is(':visible')) {
$contentEl.find('input[type=text], textarea').each(function() {
previewContent += (previewContent ? ' / ' : '') + $(this).val();
});
}
// create a preview container span element
if ($sectionEl.find(this.options.sectionHeaderPreviewSelector).length == 0) {
$sectionEl.find(this.options.sectionHeaderSelector).children(':first').append('<span class="' + this.options.sectionHeaderPreviewSelector.replace(/\./, '') + '"></span>');
}
$sectionEl.find(this.options.sectionHeaderPreviewSelector).text(previewContent);
};
// register the flex functions as jQuery Plugin
$.fn.t3FormEngineFlexFormElement = function(options) {
// apply all util functions to ourself (for use in templates, etc.)
return this.each(function() {
(new TYPO3.FormEngine.FlexFormElement(this, options));
});
};
// Initialization Code
$(document).ready(function() {
// run the flexform functions on all containers (which contains one or more sections)
$('.t3-flex-container').t3FormEngineFlexFormElement();
});
});
\ No newline at end of file
......@@ -2170,7 +2170,8 @@ class DataHandler {
\TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($currentValueArray, $arrValue);
$xmlValue = $this->checkValue_flexArray2Xml($currentValueArray, TRUE);
}
// Action commands (sorting order and removals of elements)
// Action commands (sorting order and removals of elements) for flexform sections,
// see FormEngine for the use of this GP parameter
$actionCMDs = GeneralUtility::_GP('_ACTION_FLEX_FORMdata');
if (is_array($actionCMDs[$table][$id][$field]['data'])) {
$arrValue = GeneralUtility::xml2array($xmlValue);
......@@ -2203,12 +2204,13 @@ class DataHandler {
/**
* Actions for flex form element (move, delete)
* allows to remove and move flexform sections
*
* @param array &$valueArrayToRemoveFrom by reference
* @param array $deleteCMDS
* @return void
*/
public function _ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs) {
protected function _ACTION_FLEX_FORMdata(&$valueArray, $actionCMDs) {
if (is_array($valueArray) && is_array($actionCMDs)) {
foreach ($actionCMDs as $key => $value) {
if ($key == '_ACTION') {
......
......@@ -568,6 +568,16 @@ TCEforms Inline-Relational-Record-Editing
/* - - - - - - - - - - - - - - - - - - - - -
TCEforms Sections
- - - - - - - - - - - - - - - - - - - - - */
.t3-flex-section-header .t3-record-title {
font-weight: bold;
}
.t3-flex-section {
clear: both;
margin: 5px 0;
}
.t3-flex-section-header,
.t3-form-field-container-flexsection {
padding-left: 20px;
}
......@@ -591,18 +601,32 @@ TCEforms Sections
padding: 10px 5px 5px 20px;
}
.t3-form-flex,
.t3-form-field-container-flexsections {
background: #dadada;
margin: 5px 0;
clear: both;
}
.t3-flex-section-content,
.t3-form-field-record-flexsection {
background: #eee;
padding: 5px 0;
}
.t3-flex-section-content > div,
.t3-form-field-record-flexsection > div {
margin:0 10px;
margin: 0 10px;
}
.t3-flex-section-header-preview {
font-style: italic;
width: 100px;
display: inline-block;
padding-left: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* preview image in sys_file records */
......
......@@ -8516,6 +8516,14 @@ TCEforms Inline-Relational-Record-Editing
/* - - - - - - - - - - - - - - - - - - - - -
TCEforms Sections
- - - - - - - - - - - - - - - - - - - - - */
.t3-flex-section-header .t3-record-title {
font-weight: bold;
}
.t3-flex-section {
clear: both;
margin: 5px 0;
}
.t3-flex-section-header,
.t3-form-field-container-flexsection {
padding-left: 20px;
}
......@@ -8534,17 +8542,30 @@ TCEforms Sections
border-top: 1px solid #cdcdcd;
padding: 10px 5px 5px 20px;
}
.t3-form-flex,
.t3-form-field-container-flexsections {
background: #dadada;
margin: 5px 0;
clear: both;
}
.t3-flex-section-content,
.t3-form-field-record-flexsection {
background: #eee;
padding: 5px 0;
}
.t3-flex-section-content > div,
.t3-form-field-record-flexsection > div {
margin: 0 10px;
}
.t3-flex-section-header-preview {
font-style: italic;
width: 100px;
display: inline-block;
padding-left: 10px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* preview image in sys_file records */
img.t3-tceforms-sysfile-imagepreview {
float: left;
......
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