[TASK] Move TCEForms JS Code to external file - part 1 71/18971/5
authorBenjamin Mack <benni@typo3.org>
Tue, 2 Jul 2013 12:23:05 +0000 (14:23 +0200)
committerBenjamin Mack <benni@typo3.org>
Sat, 8 Feb 2014 10:17:12 +0000 (11:17 +0100)
Parts of the JavaScript code used in TCEforms is put
inline, coming from the PHP file. There is no concept of
using HTML5-data attributes  to store information.
Plus, most of the JS functions are inline in the HTML
output and called in onclick etc. handlers in the HTML
elements.

Thus, the functionality cannot be abstracted from the
markup, and the JS functionality cannot be cached by
the browser.

Previously, the JS functionality is based on plain
MSIE4-compatible JS code without any framework
(no prototype, jQuery etc), and are not in any namespace.

This approach extracts TCEforms JS code to an external
file (AMD module / RequireJS), and rewrite it to current
jQuery and JS standards. But since the JS code
of TCEforms is very complicated,
it is hard to do it all at once, and the task is split
into separate parts.

The beginning marks the functionality around select
fields in TCEforms, the functions for importing values
from the Element browser, from any other source.

The patch does this:

* Extract the static functions from the PHP code to
an external JS file, in a new RequireJS module named
"TYPO3/CMS/Backend/FormEngine.js"
* Add options that are page-specific (backPath,
formName, callback fn) to PHP so JS can access it.
* Add classes and data-attributes and remove
onclick... functionality in order to reduce the HTML
code and split the data from the logic.
* Functionality for icons like "move up" etc are rewritten
* Functionality for adding elements from a two-select
(like "Access" element in pages) is rewritten, using
CSS classes and data attributes.

By keeping the global variables for the old functionality,
the old functions still work as expected.

Resolves: #46357
Releases: 6.2
Change-Id: I3b9936143342d99eec3058cd55fefdb006060cd4
Reviewed-on: https://review.typo3.org/18971
Reviewed-by: Andreas Wolf
Reviewed-by: Stefan Neufeind
Tested-by: Stefan Neufeind
Tested-by: Felix Kopp
Reviewed-by: Benjamin Mack
Tested-by: Benjamin Mack
typo3/sysext/backend/Classes/Form/FormEngine.php
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js [new file with mode: 0644]
typo3/sysext/t3skin/Resources/Public/Css/structure/element_tceforms.css
typo3/sysext/t3skin/Resources/Public/Css/visual/main_content.css

index 9b9432d..dc72601 100644 (file)
@@ -2339,15 +2339,9 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
                        $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="' . htmlspecialchars($config['itemListStyle']) . '"' : ' style="' . $this->defaultMultipleSelectorStyle . '"';
                        $size = (int)$config['size'];
                        $size = $config['autoSizeMax'] ? MathUtility::forceIntegerInRange(count($itemArray) + 1, MathUtility::forceIntegerInRange($size, 1), $config['autoSizeMax']) : $size;
-                       if ($config['exclusiveKeys']) {
-                               $sOnChange = 'setFormValueFromBrowseWin(\'' . $PA['itemFormElName'] . '\',this.options[this.selectedIndex].value, this.options[this.selectedIndex].text, this.options[this.selectedIndex].title,\'' . $config['exclusiveKeys'] . '\'); ';
-                       } else {
-                               $sOnChange = 'setFormValueFromBrowseWin(\'' . $PA['itemFormElName'] . '\',this.options[this.selectedIndex].value, this.options[this.selectedIndex].text, this.options[this.selectedIndex].title); ';
-                       }
-                       $sOnChange .= implode('', $PA['fieldChangeFunc']);
-                       $multiSelectId = uniqid('tceforms-multiselect-');
+                       $sOnChange = implode('', $PA['fieldChangeFunc']);
                        $itemsToSelect = '
-                               <select id="' . $multiSelectId . '" name="' . $PA['itemFormElName'] . '_sel"' . $this->insertDefStyle('select', 'tceforms-multiselect tceforms-itemstoselect') . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus'] . $selector_itemListStyle . '>
+                               <select data-relatedfieldname="' . $PA['itemFormElName'] . '" data-exclusivevalues="' . $config['exclusiveKeys'] . '" id="' . uniqid('tceforms-multiselect-') . '" name="' . $PA['itemFormElName'] . '_sel"' . $this->insertDefStyle('select', 'tceforms-multiselect tceforms-itemstoselect t3-form-select-itemstoselect') . ($size ? ' size="' . $size . '"' : '') . ' onchange="' . htmlspecialchars($sOnChange) . '"' . $PA['onFocus'] . $selector_itemListStyle . '>
                                        ' . implode('
                                        ', $opt) . '
                                </select>';
@@ -3901,12 +3895,28 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
                        }
                        if (!$params['dontShowMoveIcons']) {
                                if ($sSize >= 5) {
-                                       $icons['L'][] = '<a href="#" onclick="setFormValueManipulate(\'' . $fName . '\',\'Top\'); return false;">' . IconUtility::getSpriteIcon('actions-move-to-top', array('title' => htmlspecialchars($this->getLL('l_move_to_top')))) . '</a>';
+                                       $icons['L'][] = IconUtility::getSpriteIcon('actions-move-to-top', array(
+                                               'data-fieldname' => $fName,
+                                               'class' => 't3-btn t3-btn-moveoption-top',
+                                               'title' => htmlspecialchars($this->getLL('l_move_to_top'))
+                                       ));
                                }
-                               $icons['L'][] = '<a href="#" onclick="setFormValueManipulate(\'' . $fName . '\',\'Up\'); return false;">' . IconUtility::getSpriteIcon('actions-move-up', array('title' => htmlspecialchars($this->getLL('l_move_up')))) . '</a>';
-                               $icons['L'][] = '<a href="#" onclick="setFormValueManipulate(\'' . $fName . '\',\'Down\'); return false;">' . IconUtility::getSpriteIcon('actions-move-down', array('title' => htmlspecialchars($this->getLL('l_move_down')))) . '</a>';
+                               $icons['L'][] = IconUtility::getSpriteIcon('actions-move-up', array(
+                                       'data-fieldname' => $fName,
+                                       'class' => 't3-btn t3-btn-moveoption-up',
+                                       'title' => htmlspecialchars($this->getLL('l_move_up'))
+                               ));
+                               $icons['L'][] = IconUtility::getSpriteIcon('actions-move-down', array(
+                                       'data-fieldname' => $fName,
+                                       'class' => 't3-btn t3-btn-moveoption-down',
+                                       'title' => htmlspecialchars($this->getLL('l_move_down'))
+                               ));
                                if ($sSize >= 5) {
-                                       $icons['L'][] = '<a href="#" onclick="setFormValueManipulate(\'' . $fName . '\',\'Bottom\'); return false;">' . IconUtility::getSpriteIcon('actions-move-to-bottom', array('title' => htmlspecialchars($this->getLL('l_move_to_bottom')))) . '</a>';
+                                       $icons['L'][] = IconUtility::getSpriteIcon('actions-move-to-bottom', array(
+                                               'data-fieldname' => $fName,
+                                               'class' => 't3-btn t3-btn-moveoption-bottom',
+                                               'title' => htmlspecialchars($this->getLL('l_move_to_bottom'))
+                                       ));
                                }
                        }
                        $clipElements = $this->getClipboardElements($allowed, $mode);
@@ -3928,8 +3938,13 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
                        }
                }
                if (!$params['readOnly'] && !$params['noDelete']) {
-                       $rOnClick = $rOnClickInline . 'setFormValueManipulate(\'' . $fName . '\',\'Remove\'); return false';
-                       $icons['L'][] = '<a href="#" onclick="' . htmlspecialchars($rOnClick) . '">' . IconUtility::getSpriteIcon('actions-selection-delete', array('title' => htmlspecialchars($this->getLL('l_remove_selected')))) . '</a>';
+                       $icons['L'][] = IconUtility::getSpriteIcon('actions-selection-delete', array(
+                               'onclick' => $rOnClickInline,
+                               'data-fieldname' => $fName,
+                               'class' => 't3-btn t3-btn-removeoption',
+                               'title' => htmlspecialchars($this->getLL('l_remove_selected'))
+
+                       ));
                }
                $imagesOnly = FALSE;
                if ($params['thumbnails'] && $params['info']) {
@@ -3988,7 +4003,7 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
                $str .= '</td>
                                        <td valign="top" class="icons">' . implode('<br />', $icons['L']) . '</td>
                                        <td valign="top" class="icons">' . implode('<br />', $icons['R']) . '</td>
-                                       <td valign="top" class="thumbnails">' . $rightbox . '</td>
+                                       <td valign="top">' . $rightbox . '</td>
                        </tr>
                </table>';
                // Creating the hidden field which contains the actual value as a comma list.
@@ -5543,6 +5558,8 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
                        }
                        /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
                        $pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
+                       // load the main module for FormEngine with all important JS functions
+                       $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/FormEngine');
                        $pageRenderer->loadPrototype();
                        $pageRenderer->loadJquery();
                        $pageRenderer->loadExtJS();
@@ -5730,343 +5747,35 @@ TBE_EDITOR.customEvalFunctions[\'' . $evalData . '\'] = function(value) {
         *
         * @param string $formObj Form object reference (including "document.")
         * @return string JavaScript functions/code (NOT contained in a <script>-element)
-        * @todo Define visibility
+        * @deprecated since TYPO3 6.2, remove two versions later. This is now done in an external file, see printNeededJSfunctions
         */
        public function dbFileCon($formObj = 'document.forms[0]') {
-               // @TODO: Export this to an own file, it is more static than dynamic JavaScript -- olly
-               $str = '
-
-                       // ***************
-                       // Used to connect the db/file browser with this document and the formfields on it!
-                       // ***************
-
-                       var browserWin="";
-
-                       function setFormValueOpenBrowser(mode,params) { //
-                               var url = "' . $this->backPath . 'browser.php?mode="+mode+"&bparams="+params;
-
-                               browserWin = window.open(url,"Typo3WinBrowser","height=650,width="+(mode=="db"?650:600)+",status=0,menubar=0,resizable=1,scrollbars=1");
-                               browserWin.focus();
-                       }
-                       function setFormValueFromBrowseWin(fName,value,label,title,exclusiveValues) {
-                               var formObj = setFormValue_getFObj(fName), fObj, isMultiple = false, isList = false, isCheckboxList = false, len;
-                               if (formObj && value !== "--div--") {
-                                               // Check if the form object has a "_list" element or not
-                                               // The "_list" element exists for multiple selection select types
-                                       if (formObj[fName + "_list"]) {
-                                               fObj = formObj[fName + "_list"];
-                                               isMultiple =  fObj.multiple && fObj.getAttribute("size") != "1";
-                                               isList = true;
-                                       } else {
-                                               isCheckboxList = formObj[fName].className == "select-checkbox";
-                                               fObj = formObj[fName];
-                                       }
-
-                                       if (isMultiple || isList) {
-                                               if (!isMultiple) {
-                                                               // clear field before adding value, if configured so (maxitems==1)
-                                                       if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fName] != "undefined") {
-                                                               clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fName];
-                                                               setFormValueManipulate(fName, "Remove");
-
-                                                                       // Clear the upload field
-                                                               var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
-                                                               if(filesContainer) {
-                                                                       filesContainer.innerHTML = filesContainer.innerHTML;
-                                                               }
-
-                                                               // update len after removing value
-                                                               len = fObj.length;
-                                                       }
-
-                                                               // If multiple values are not allowed, clear anything that is in the control already
-                                                       fObj.options.length = 0;
-                                                       fObj.length = 0; // Note: this is dangerous! "length" on the object is a reserved JS attribute!
-                                               }
-                                               len = fObj.length;
-
-                                                       // Clear elements if exclusive values are found
-                                               if (exclusiveValues) {
-                                                       var m = new RegExp("(^|,)" + value + "($|,)");
-                                                       if (exclusiveValues.match(m)) {
-                                                                       // the new value is exclusive
-                                                               for (a = len - 1; a >= 0; a--) {
-                                                                       fObj[a] = null; // This is dangerous because it works on the object\'s numeric properties directly instead of using a custom attribute!
-                                                               }
-                                                               len = 0;
-                                                       } else if (len == 1) {
-                                                               m = new RegExp("(^|,)" + fObj.options[0].value + "($|,)");
-                                                               if (exclusiveValues.match(m)) {
-                                                                               // the old value is exclusive
-                                                                       fObj[0] = null;
-                                                                       len = 0;
-                                                               }
-                                                       }
-                                               }
-                                                       // Inserting element
-                                               var setOK = true;
-                                               if (!formObj[fName + "_mul"] || formObj[fName + "_mul"].value == 0) {
-                                                       for (a = 0; a < len; a++) {
-                                                               if (fObj.options[a].value == value) {
-                                                                       setOK = false;
-                                                               }
-                                                       }
-                                               }
-                                               if (setOK) {
-                                                       fObj.length++;
-                                                       fObj.options[len].value = value;
-                                                       fObj.options[len].text = unescape(label);
-                                                       fObj.options[len].title = title;
-
-                                                               // Traversing list and set the hidden-field
-                                                       setHiddenFromList(fObj,formObj[fName]);
-                                                       ' . $this->TBE_EDITOR_fieldChanged_func . '
-                                               }
-                                       } else if (isCheckboxList) {
-                                               var i=0;
-                                               while (formObj[fName + "[" + i + "]"]) {
-                                                       if (formObj[fName + "[" + i + "]"].value == value) {
-                                                               fObj = formObj[fName + "[" + i + "]"];
-                                                               break;
-                                                       }
-                                                       i++;
-                                               };
-
-                                               if (fObj && !fObj.checked) {
-                                                       fObj.click();
-                                                       ' . str_replace('_list', '', $this->TBE_EDITOR_fieldChanged_func) . '
-                                               }
-                                       } else {
-                                                       // The incoming value consists of the table name, an underscore and the uid
-                                                       // For a single selection field we need only the uid, so we extract it
-                                               var uidValue = value;
-                                               var pattern = /_(\\d+)$/;
-                                               var result = value.match(pattern);
-                                               if (result != null) {
-                                                       uidValue = result[1];
-                                               }
-                                                       // Change the selected value
-                                               fObj.value = uidValue;
-                                       }
-                               }
-                       }
-                       function setHiddenFromList(fObjSel,fObjHid) {   //
-                               l=fObjSel.length;
-                               fObjHid.value="";
-                               for (a=0;a<l;a++) {
-                                       fObjHid.value+=fObjSel.options[a].value+",";
-                               }
-                       }
-                       function setFormValueManipulate(fName, type, maxLength) {
-                               var formObj = setFormValue_getFObj(fName);
-                               if (formObj) {
-                                       var localArray_V = new Array();
-                                       var localArray_L = new Array();
-                                       var localArray_S = new Array();
-                                       var localArray_T = new Array();
-                                       var fObjSel = formObj[fName+"_list"];
-                                       var l=fObjSel.length;
-                                       var c=0;
-
-                                       if (type == "RemoveFirstIfFull") {
-                                               if (maxLength == 1) {
-                                                       for (a = 1; a < l; a++) {
-                                                               if (fObjSel.options[a].selected != 1) {
-                                                                       localArray_V[c] = fObjSel.options[a].value;
-                                                                       localArray_L[c] = fObjSel.options[a].text;
-                                                                       localArray_S[c] = 0;
-                                                                       localArray_T[c] = fObjSel.options[a].title;
-                                                                       c++;
-                                                               }
-                                                       }
-                                               } else {
-                                                       return;
-                                               }
-                                       }
-
-                                       if ((type=="Remove" && fObjSel.size > 1) || type=="Top" || type=="Bottom") {
-                                               if (type=="Top") {
-                                                       for (a=0;a<l;a++) {
-                                                               if (fObjSel.options[a].selected==1) {
-                                                                       localArray_V[c]=fObjSel.options[a].value;
-                                                                       localArray_L[c]=fObjSel.options[a].text;
-                                                                       localArray_S[c]=1;
-                                                                       localArray_T[c] = fObjSel.options[a].title;
-                                                                       c++;
-                                                               }
-                                                       }
-                                               }
-                                               for (a=0;a<l;a++) {
-                                                       if (fObjSel.options[a].selected!=1) {
-                                                               localArray_V[c]=fObjSel.options[a].value;
-                                                               localArray_L[c]=fObjSel.options[a].text;
-                                                               localArray_S[c]=0;
-                                                               localArray_T[c] = fObjSel.options[a].title;
-                                                               c++;
-                                                       }
-                                               }
-                                               if (type=="Bottom") {
-                                                       for (a=0;a<l;a++) {
-                                                               if (fObjSel.options[a].selected==1) {
-                                                                       localArray_V[c]=fObjSel.options[a].value;
-                                                                       localArray_L[c]=fObjSel.options[a].text;
-                                                                       localArray_S[c]=1;
-                                                                       localArray_T[c] = fObjSel.options[a].title;
-                                                                       c++;
-                                                               }
-                                                       }
-                                               }
-                                       }
-                                       if (type=="Down") {
-                                               var tC = 0;
-                                               var tA = new Array();
-
-                                               for (a=0;a<l;a++) {
-                                                       if (fObjSel.options[a].selected!=1) {
-                                                                       // Add non-selected element:
-                                                               localArray_V[c]=fObjSel.options[a].value;
-                                                               localArray_L[c]=fObjSel.options[a].text;
-                                                               localArray_S[c]=0;
-                                                               localArray_T[c] = fObjSel.options[a].title;
-                                                               c++;
-
-                                                                       // Transfer any accumulated and reset:
-                                                               if (tA.length > 0) {
-                                                                       for (aa=0;aa<tA.length;aa++) {
-                                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
-                                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
-                                                                               localArray_S[c]=1;
-                                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
-                                                                               c++;
-                                                                       }
-
-                                                                       var tC = 0;
-                                                                       var tA = new Array();
-                                                               }
-                                                       } else {
-                                                               tA[tC] = a;
-                                                               tC++;
-                                                       }
-                                               }
-                                                       // Transfer any remaining:
-                                               if (tA.length > 0) {
-                                                       for (aa=0;aa<tA.length;aa++) {
-                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
-                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
-                                                               localArray_S[c]=1;
-                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
-                                                               c++;
-                                                       }
-                                               }
-                                       }
-                                       if (type=="Up") {
-                                               var tC = 0;
-                                               var tA = new Array();
-                                               var c = l-1;
-
-                                               for (a=l-1;a>=0;a--) {
-                                                       if (fObjSel.options[a].selected!=1) {
-
-                                                                       // Add non-selected element:
-                                                               localArray_V[c]=fObjSel.options[a].value;
-                                                               localArray_L[c]=fObjSel.options[a].text;
-                                                               localArray_S[c]=0;
-                                                               localArray_T[c] = fObjSel.options[a].title;
-                                                               c--;
-
-                                                                       // Transfer any accumulated and reset:
-                                                               if (tA.length > 0) {
-                                                                       for (aa=0;aa<tA.length;aa++) {
-                                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
-                                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
-                                                                               localArray_S[c]=1;
-                                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
-                                                                               c--;
-                                                                       }
-
-                                                                       var tC = 0;
-                                                                       var tA = new Array();
-                                                               }
-                                                       } else {
-                                                               tA[tC] = a;
-                                                               tC++;
-                                                       }
-                                               }
-                                                       // Transfer any remaining:
-                                               if (tA.length > 0) {
-                                                       for (aa=0;aa<tA.length;aa++) {
-                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
-                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
-                                                               localArray_S[c]=1;
-                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
-                                                               c--;
-                                                       }
-                                               }
-                                               c=l;    // Restore length value in "c"
-                                       }
-
-                                               // Transfer items in temporary storage to list object:
-                                       fObjSel.length = c;
-                                       for (a=0;a<c;a++) {
-                                               fObjSel.options[a].value = localArray_V[a];
-                                               fObjSel.options[a].text = localArray_L[a];
-                                               fObjSel.options[a].selected = localArray_S[a];
-                                               fObjSel.options[a].title = localArray_T[a];
-                                       }
-                                       setHiddenFromList(fObjSel,formObj[fName]);
-
-                                       ' . $this->TBE_EDITOR_fieldChanged_func . '
-                               }
-                       }
-                       function setFormValue_getFObj(fName) {  //
-                               var formObj = ' . $formObj . ';
-                               if (formObj) {
-                                               // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
-                                       if (formObj[fName] &&
-                                               (
-                                                       (formObj[fName].type == "select-one") ||
-                                                       (formObj[fName].className == "select-checkbox") ||
-                                                       (formObj[fName + "_list"] && formObj[fName + "_list"].type.match(/select-(one|multiple)/))
-                                               )
-                                       ) {
-                                               return formObj;
-                                       } else {
-                                               alert("Formfields missing:\\n fName: " + formObj[fName] + "\\n fName_list:" + formObj[fName + "_list"] + "\\n type:" + formObj[fName + "_list"].type + "\\n fName:" + fName);
-                                       }
-                               }
-                               return "";
-                       }
-
-                       // END: dbFileCon parts.
-               ';
-               return $str;
+               GeneralUtility::logDeprecatedFunction();
        }
 
        /**
         * Prints necessary JavaScript for TCEforms (after the form HTML).
+        * currently this is used to transform page-specific options in the TYPO3.Settings array for JS
+        * so the JS module can access these values
         *
         * @return void
         * @todo Define visibility
         */
        public function printNeededJSFunctions() {
-               // JS evaluation:
-               $out = $this->JSbottom($this->formName);
-               // Integrate JS functions for the element browser if such fields or IRRE fields or suggest wizard were processed:
-               if ($this->printNeededJS['dbFileIcons'] > 0 || $this->inline->inlineCount > 0 || $this->suggest->suggestCount > 0) {
-                       $out .= '
-
 
+               /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
+               $pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
 
-                       <!--
-                               JavaScript after the form has been drawn:
-                       -->
+               // set variables to be accessible for JS
+               $pageRenderer->addInlineSetting('FormEngine', 'formName', $this->formName);
+               $pageRenderer->addInlineSetting('FormEngine', 'backPath', $this->backPath);
 
-                       <script type="text/javascript">
-                               /*<![CDATA[*/
-                       ' . $this->dbFileCon('document.' . $this->formName) . '
-                               /*]]>*/
-                       </script>';
+               // Integrate JS functions for the element browser if such fields or IRRE fields were processed
+               if ($this->printNeededJS['dbFileIcons'] || $this->inline->inlineCount > 0 || $this->suggest->suggestCount > 0) {
+                       $pageRenderer->addInlineSetting('FormEngine', 'legacyFieldChangedCb', 'function() { ' . $this->TBE_EDITOR_fieldChanged_func . ' };');
                }
+
+               $out = $this->JSbottom($this->formName);
                return $out;
        }
 
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
new file mode 100644 (file)
index 0000000..84b8bcd
--- /dev/null
@@ -0,0 +1,573 @@
+/**
+ * (c) 2013 Benjamin Mack
+ * Released under the GPL v2+, part of TYPO3
+ *
+ * contains all JS functions related to TYPO3 TCEforms/FormEngine
+ *
+ * there are separate issues in this main object
+ *   - functions, related to Element Browser ("Popup Window") and select fields
+ *   - filling select fields (by wizard etc) from outside, formerly known via "setFormValueFromBrowseWin"
+ *   - select fields: move selected items up and down via buttons, remove items etc
+ *   -
+ *
+ */
+
+// add legacy functions to be accessible in the global scope
+var setFormValueOpenBrowser
+       ,setFormValueFromBrowseWin
+       ,setHiddenFromList
+       ,setFormValueManipulate
+       ,setFormValue_getFObj
+
+
+define('TYPO3/CMS/Backend/FormEngine', ['jquery'], function ($) {
+
+       // main options
+       var FormEngine = {
+               formName: TYPO3.settings.FormEngine.formName
+               ,backPath: TYPO3.settings.FormEngine.backPath
+               ,openedPopupWindow: null
+               ,legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); }
+       };
+
+
+       // functions to connect the db/file browser with this document and the formfields on it!
+
+       /**
+        * opens a popup window with the element browser (browser.php)
+        *
+        * @param mode can be "db" or "file"
+        * @param params additional params for the browser window
+        */
+       FormEngine.openPopupWindow = setFormValueOpenBrowser = function(mode, params) {
+               var url = FormEngine.backPath + 'browser.php?mode=' + mode + '&bparams=' + params;
+               FormEngine.openedPopupWindow = window.open(url, 'Typo3WinBrowser', 'height=650,width=' + (mode == 'db' ? 650 : 600) + ',status=0,menubar=0,resizable=1,scrollbars=1');
+               FormEngine.openedPopupWindow.focus();
+       };
+
+
+       /**
+        * properly fills the select field from the popup window (element browser, link browser)
+        * or from a multi-select (two selects side-by-side)
+        * previously known as "setFormValueFromBrowseWin"
+        *
+        * @param fieldName formerly known as "fName" name of the field, like [tt_content][2387][header]
+        * @param value the value to fill in (could be an integer)
+        * @param label the visible name in the selector
+        * @param title the title when hovering over it
+        * @param exclusiveValues if the select field has exclusive options that are not combine-able
+        */
+       FormEngine.setSelectOptionFromExternalSource = setFormValueFromBrowseWin = function(fieldName, value, label, title, exclusiveValues) {
+               var $originalFieldEl = $fieldEl = FormEngine.getFieldElement(fieldName)
+                               ,isMultiple = false
+                               ,isList = false;
+
+               if ($originalFieldEl.length == 0 || value === '--div--') {
+                       return;
+               }
+
+               // Check if the form object has a "_list" element
+               // The "_list" element exists for multiple selection select types
+               var $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
+               if ($listFieldEl.length > 0) {
+                       $fieldEl = $listFieldEl;
+                       isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
+                       isList = true;
+               }
+
+               // clear field before adding value, if configured so (maxitems==1)
+               // @todo: clean this code
+               if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName] != 'undefined') {
+                       clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName];
+                       $fieldEl.empty();
+
+                               // Clear the upload field
+                       var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
+                       if (filesContainer) {
+                               filesContainer.innerHTML = filesContainer.innerHTML;
+                       }
+               }
+
+               if (isMultiple || isList) {
+
+                       // If multiple values are not allowed, clear anything that is in the control already
+                       if (!isMultiple) {
+                               $fieldEl.empty();
+                       }
+
+                       // Clear elements if exclusive values are found
+                       if (exclusiveValues) {
+                               var m = new RegExp('(^|,)' + value + '($|,)');
+                               // the new value is exclusive => remove all existing values
+                               if (exclusiveValues.match(m)) {
+                                       $fieldEl.empty();
+
+                               // there is an old value and it was exclusive => it has to be removed
+                               } else if ($fieldEl.children('option').length == 1) {
+                                       m = new RegExp("(^|,)" + $fieldEl.children('option').prop('value') + "($|,)");
+                                       if (exclusiveValues.match(m)) {
+                                               $fieldEl.empty();
+                                       }
+                               }
+                       }
+
+                       // Inserting the new element
+                       var addNewValue = true;
+
+                       // check if there is a "_mul" field (a field on the right) and if the field was already added
+                       var $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
+                       if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
+                               $fieldEl.children('option').each(function(k, optionEl) {
+                                       if ($(optionEl).prop('value') == value) {
+                                               addNewValue = false;
+                                               return false;
+                                       }
+                               });
+                       }
+
+                       // element can be added
+                       if (addNewValue) {
+                               // finally add the option
+                               $fieldEl.append('<option value="' + value + '" title="' + title + '">' + decodeURI(label) + '</option>');
+
+                               // set the hidden field
+                               FormEngine.updateHiddenFieldValueFromSelect($fieldEl, $originalFieldEl);
+
+                               // execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
+                               FormEngine.legacyFieldChangedCb();
+                       }
+
+               } else {
+
+                       // The incoming value consists of the table name, an underscore and the uid
+                       // For a single selection field we need only the uid, so we extract it
+                       var pattern = /_(\\d+)$/
+                                       ,result = value.match(pattern);
+
+                       if (result != null) {
+                               value = result[1];
+                       }
+
+                       // Change the selected value
+                       $fieldEl.val(value);
+               }
+       };
+
+       /**
+        * sets the value of the hidden field, from the select list, always executed after the select field was updated
+        * previously known as global function setHiddenFromList()
+        *
+        * @param selectFieldEl the select field
+        * @param originalFieldEl the hidden form field
+        */
+       FormEngine.updateHiddenFieldValueFromSelect = setHiddenFromList = function(selectFieldEl, originalFieldEl) {
+               var selectedValues = [];
+               $(selectFieldEl).children('option').each(function() {
+                       selectedValues.push($(this).prop('value'));
+               });
+
+               // make a comma-seperated list, if it is a multi-select
+               // set the values to the final hidden field
+               $(originalFieldEl).val(selectedValues.join(','));
+       };
+
+       // legacy function, can be removed once this function is not in use anymore
+       setFormValueManipulate = function(fName, type, maxLength) {
+               var $formEl = FormEngine.getFormElement(fName);
+               if ($formEl.length > 0) {
+                       var formObj = formEl.get();
+                       var localArray_V = new Array();
+                       var localArray_L = new Array();
+                       var localArray_S = new Array();
+                       var localArray_T = new Array();
+                       var fObjSel = formObj[fName + '_list'];
+                       var l = fObjSel.length;
+                       var c = 0;
+
+                       if (type == 'RemoveFirstIfFull') {
+                               if (maxLength == 1) {
+                                       for (a = 1; a < l; a++) {
+                                               if (fObjSel.options[a].selected != 1) {
+                                                       localArray_V[c] = fObjSel.options[a].value;
+                                                       localArray_L[c] = fObjSel.options[a].text;
+                                                       localArray_S[c] = 0;
+                                                       localArray_T[c] = fObjSel.options[a].title;
+                                                       c++;
+                                               }
+                                       }
+                               } else {
+                                       return;
+                               }
+                       }
+
+                       if ((type=="Remove" && fObjSel.size > 1) || type=="Top" || type=="Bottom") {
+                               if (type=="Top") {
+                                       for (a=0;a<l;a++) {
+                                               if (fObjSel.options[a].selected==1) {
+                                                       localArray_V[c]=fObjSel.options[a].value;
+                                                       localArray_L[c]=fObjSel.options[a].text;
+                                                       localArray_S[c]=1;
+                                                       localArray_T[c] = fObjSel.options[a].title;
+                                                       c++;
+                                               }
+                                       }
+                               }
+                               for (a=0;a<l;a++) {
+                                       if (fObjSel.options[a].selected!=1) {
+                                               localArray_V[c]=fObjSel.options[a].value;
+                                               localArray_L[c]=fObjSel.options[a].text;
+                                               localArray_S[c]=0;
+                                               localArray_T[c] = fObjSel.options[a].title;
+                                               c++;
+                                       }
+                               }
+                               if (type=="Bottom") {
+                                       for (a=0;a<l;a++) {
+                                               if (fObjSel.options[a].selected==1) {
+                                                       localArray_V[c]=fObjSel.options[a].value;
+                                                       localArray_L[c]=fObjSel.options[a].text;
+                                                       localArray_S[c]=1;
+                                                       localArray_T[c] = fObjSel.options[a].title;
+                                                       c++;
+                                               }
+                                       }
+                               }
+                       }
+                       if (type=="Down") {
+                               var tC = 0;
+                               var tA = new Array();
+
+                               for (a=0;a<l;a++) {
+                                       if (fObjSel.options[a].selected!=1) {
+                                                       // Add non-selected element:
+                                               localArray_V[c]=fObjSel.options[a].value;
+                                               localArray_L[c]=fObjSel.options[a].text;
+                                               localArray_S[c]=0;
+                                               localArray_T[c] = fObjSel.options[a].title;
+                                               c++;
+
+                                                       // Transfer any accumulated and reset:
+                                               if (tA.length > 0) {
+                                                       for (aa=0;aa<tA.length;aa++) {
+                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
+                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
+                                                               localArray_S[c]=1;
+                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
+                                                               c++;
+                                                       }
+
+                                                       var tC = 0;
+                                                       var tA = new Array();
+                                               }
+                                       } else {
+                                               tA[tC] = a;
+                                               tC++;
+                                       }
+                               }
+                                       // Transfer any remaining:
+                               if (tA.length > 0) {
+                                       for (aa=0;aa<tA.length;aa++) {
+                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
+                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
+                                               localArray_S[c]=1;
+                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
+                                               c++;
+                                       }
+                               }
+                       }
+                       if (type=="Up") {
+                               var tC = 0;
+                               var tA = new Array();
+                               var c = l-1;
+
+                               for (a=l-1;a>=0;a--) {
+                                       if (fObjSel.options[a].selected!=1) {
+
+                                                       // Add non-selected element:
+                                               localArray_V[c]=fObjSel.options[a].value;
+                                               localArray_L[c]=fObjSel.options[a].text;
+                                               localArray_S[c]=0;
+                                               localArray_T[c] = fObjSel.options[a].title;
+                                               c--;
+
+                                                       // Transfer any accumulated and reset:
+                                               if (tA.length > 0) {
+                                                       for (aa=0;aa<tA.length;aa++) {
+                                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
+                                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
+                                                               localArray_S[c]=1;
+                                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
+                                                               c--;
+                                                       }
+
+                                                       var tC = 0;
+                                                       var tA = new Array();
+                                               }
+                                       } else {
+                                               tA[tC] = a;
+                                               tC++;
+                                       }
+                               }
+                                       // Transfer any remaining:
+                               if (tA.length > 0) {
+                                       for (aa=0;aa<tA.length;aa++) {
+                                               localArray_V[c]=fObjSel.options[tA[aa]].value;
+                                               localArray_L[c]=fObjSel.options[tA[aa]].text;
+                                               localArray_S[c]=1;
+                                               localArray_T[c] = fObjSel.options[tA[aa]].title;
+                                               c--;
+                                       }
+                               }
+                               c=l;    // Restore length value in "c"
+                       }
+
+                               // Transfer items in temporary storage to list object:
+                       fObjSel.length = c;
+                       for (a = 0; a < c; a++) {
+                               fObjSel.options[a].value = localArray_V[a];
+                               fObjSel.options[a].text = localArray_L[a];
+                               fObjSel.options[a].selected = localArray_S[a];
+                               fObjSel.options[a].title = localArray_T[a];
+                       }
+                       FormEngine.updateHiddenFieldValueFromSelect(fObjSel, formObj[fName]);
+
+                       FormEngine.legacyFieldChangedCb();
+               }
+       };
+
+
+       /**
+        * legacy function
+        * returns the DOM object for the given form name of the current form,
+        * but only if the given field name is valid, legacy function, use "getFormElement" instead
+        *
+        * @param fieldName the name of the field name
+        * @returns {*|DOMElement}
+        */
+       setFormValue_getFObj = function(fieldName) {
+               var $formEl = FormEngine.getFormElement(fieldName);
+               if ($formEl.length > 0) {
+                       // return the DOM element of the form object
+                       return $formEl.get();
+               } else {
+                       return null;
+               }
+       };
+
+       /**
+        * returns a jQuery object for the given form name of the current form,
+        * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
+        * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
+        *
+        * @param fieldName the field name to check for, optional
+        * @returns {*|HTMLElement}
+        */
+       FormEngine.getFormElement = function(fieldName) {
+               var $formEl = $('form[name="' + FormEngine.formName + '"]:first');
+               if (fieldName) {
+                       var $fieldEl = FormEngine.getFieldElement(fieldName)
+                                       ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
+
+                       // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
+                       if ($fieldEl.length > 0 &&
+                               (
+                                       ($fieldEl.prop('type') == 'select-one') ||
+                                       ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
+                               )
+                       ) {
+                               return $formEl;
+                       } else {
+                               console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
+                               alert('Form field is invalid');
+                       }
+               } else {
+                       return $formEl;
+               }
+       };
+
+
+       /**
+        * returns a jQuery object of the field DOM element of the current form, can also be used to
+        * request an alternative field like "_hr", "_list" or "_mul"
+        *
+        * @param fieldName the name of the field (<input name="fieldName">)
+        * @param appendix optional
+        * @param noFallback if set, then the appendix value is returned no matter if it exists or not
+        * @returns {*|HTMLElement}
+        */
+       FormEngine.getFieldElement = function(fieldName, appendix, noFallback) {
+               var $formEl = FormEngine.getFormElement();
+
+               // if an appendix is set, return the field with the appendix (like _mul or _list)
+               if (appendix) {
+                       var $fieldEl = $(':input[name="' + fieldName + appendix + '"]', $formEl);
+                       if ($fieldEl.length > 0 || noFallback === true) {
+                               return $fieldEl;
+                       }
+               }
+
+               return $(':input[name="' + fieldName + '"]', $formEl);
+       };
+
+
+
+       /**************************************************
+        * manipulate existing options in a select field
+        **************************************************/
+
+       /**
+        * moves currently selected options from a select field to the very top,
+        * can be multiple entries as well
+        *
+        * @param $fieldEl a jQuery object, containing the select field
+        */
+       FormEngine.moveOptionToTop = function($fieldEl) {
+               // remove the selected options
+               var selectedOptions = $fieldEl.find(':selected').detach();
+               // and add them on first position again
+               $fieldEl.prepend(selectedOptions);
+       };
+
+
+       /**
+        * moves currently selected options from a select field up by one position,
+        * can be multiple entries as well
+        *
+        * @param $fieldEl a jQuery object, containing the select field
+        */
+       FormEngine.moveOptionUp = function($fieldEl) {
+               // remove the selected options and add it before the previous sibling
+               $.each($fieldEl.find(':selected'), function(k, optionEl) {
+                       var $optionEl = $(optionEl)
+                                       ,$optionBefore = $optionEl.prev();
+
+                       // stop if first option to move is already the first one
+                       if (k == 0 && $optionBefore.length === 0) {
+                               return false;
+                       }
+
+                       $optionBefore.before($optionEl.detach());
+               });
+       };
+
+
+       /**
+        * moves currently selected options from a select field down one position,
+        * can be multiple entries as well
+        *
+        * @param $fieldEl a jQuery object, containing the select field
+        */
+       FormEngine.moveOptionDown = function($fieldEl) {
+               // remove the selected options and add it after the next sibling
+               // however, this time, we need to go from the last to the first
+               var selectedOptions = $fieldEl.find(':selected');
+               selectedOptions = $.makeArray(selectedOptions);
+               selectedOptions.reverse();
+               $.each(selectedOptions, function(k, optionEl) {
+                       var $optionEl = $(optionEl)
+                                       ,$optionAfter = $optionEl.next();
+
+                       // stop if first option to move is already the last one
+                       if (k == 0 && $optionAfter.length === 0) {
+                               return false;
+                       }
+
+                       $optionAfter.after($optionEl.detach());
+               });
+       };
+
+
+       /**
+        * moves currently selected options from a select field as the very last entries
+        *
+        * @param $fieldEl a jQuery object, containing the select field
+        */
+       FormEngine.moveOptionToBottom = function($fieldEl) {
+               // remove the selected options
+               var selectedOptions = $fieldEl.find(':selected').detach();
+               // and add them on last position again
+               $fieldEl.append(selectedOptions);
+       };
+
+       /**
+        * removes currently selected options from a select field
+        *
+        * @param $fieldEl a jQuery object, containing the select field
+        */
+       FormEngine.removeOption = function($fieldEl) {
+               // remove the selected options
+               $fieldEl.find(':selected').remove();
+       };
+
+
+       /**
+        * initialize events for all form engine relevant tasks
+        */
+       FormEngine.initializeEvents = function() {
+
+               // track the arrows "Up", "Down", "Clear" etc in multi-select boxes
+               $(document).on('click', '.t3-btn-moveoption-top, .t3-btn-moveoption-up, .t3-btn-moveoption-down, .t3-btn-moveoption-bottom, .t3-btn-removeoption', function(evt) {
+                       var $el = $(this)
+                                       ,fieldName = $el.data('fieldname')
+                                       ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
+
+                       if ($listFieldEl.length > 0) {
+
+                               if ($el.hasClass('t3-btn-moveoption-top')) {
+                                       FormEngine.moveOptionToTop($listFieldEl);
+                               } else if ($el.hasClass('t3-btn-moveoption-up')) {
+                                       FormEngine.moveOptionUp($listFieldEl);
+                               } else if ($el.hasClass('t3-btn-moveoption-down')) {
+                                       FormEngine.moveOptionDown($listFieldEl);
+                               } else if ($el.hasClass('t3-btn-moveoption-bottom')) {
+                                       FormEngine.moveOptionToBottom($listFieldEl);
+                               } else if ($el.hasClass('t3-btn-removeoption')) {
+                                       FormEngine.removeOption($listFieldEl);
+                               }
+
+                               // make sure to update the hidden field value when modifying the select value
+                               FormEngine.updateHiddenFieldValueFromSelect($listFieldEl, FormEngine.getFieldElement(fieldName));
+                               FormEngine.legacyFieldChangedCb();
+                       }
+               });
+
+               // in multi-select environments with two (e.g. "Access"), on click the item from the right should go to the left
+               $(document).on('click', '.t3-form-select-itemstoselect', function(evt) {
+                       var $el = $(this)
+                                       ,fieldName = $el.data('relatedfieldname')
+                                       ,exclusiveValues = $el.data('exclusivevalues');
+
+                       if (fieldName) {
+                               // try to add each selected field to the "left" select field
+                               $el.find(':selected').each(function() {
+                                       var $optionEl = $(this);
+                                       FormEngine.setSelectOptionFromExternalSource(fieldName, $optionEl.prop('value'), $optionEl.text(), $optionEl.prop('title'), exclusiveValues);
+                               });
+                       }
+               });
+       };
+
+
+
+       // initialize function, always require possible post-render hooks return the main object
+       var initializeModule = function(options) {
+
+               FormEngine.initializeEvents();
+
+               // load required modules to hook in the post initialize function
+               if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
+                       $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine'], function(pos, moduleName) {
+                               require([moduleName]);
+                       });
+               }
+
+               // make the form engine object publically visible for other objects in the TYPO3 namespace
+               TYPO3.FormEngine = FormEngine;
+
+               // return the object in the global space
+               return FormEngine;
+       };
+
+       // call the main initialize function and execute the hooks
+       return initializeModule();
+});
index 87cd842..7bf5bef 100644 (file)
@@ -217,15 +217,15 @@ select option.c-divider {
 }
 
 .typo3-TCEforms td.icons {
-       padding: 6px 2px;
+       padding: 2px;
 }
 
 .typo3-TCEforms td.thumbnails {
-       padding-top: 6px;
+       padding-top: 2px;
 }
 
 .typo3-TCEforms div.imagethumbs {
-       padding-top: 6px;
+       padding-top: 2px;
        white-space: normal;
        width: 253px;
 }
index ef1b24b..2f68af0 100644 (file)
@@ -228,3 +228,9 @@ div.warningbox {
 span.warningboxheader {
        margin-left: 5px;
 }
+
+
+/* all buttons have the "click" mouse cursor */
+.t3-btn {
+       cursor: pointer;
+}