1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-12 09:44:38 +02:00

Add new page editor Inputfield visibility mode 'Tab' which makes any Inputfield display as a page editor tab. Options included for 'Tab', 'Tab (AJAX)', and 'Tab (locked)'.

This commit is contained in:
Ryan Cramer
2022-05-27 10:40:58 -04:00
parent 23a4cb455d
commit 6667caa1d0
5 changed files with 129 additions and 16 deletions

View File

@@ -59,6 +59,7 @@
* @property string $icon Optional font-awesome icon name to accompany label (excluding the "fa-") part). #pw-group-labels * @property string $icon Optional font-awesome icon name to accompany label (excluding the "fa-") part). #pw-group-labels
* @property string $requiredLabel Optional custom label to display when missing required value. @since 3.0.98 #pw-group-labels * @property string $requiredLabel Optional custom label to display when missing required value. @since 3.0.98 #pw-group-labels
* @property string $head Optional text that appears below label but above description (only used by some Inputfields). #pw-internal * @property string $head Optional text that appears below label but above description (only used by some Inputfields). #pw-internal
* @property string $tabLabel Label for tab if Inputfield rendered in its own tab via Inputfield::collapsedTab* setting. @since 3.0.201 #pw-group-labels
* @property string|null $prependMarkup Optional markup to prepend to the Inputfield content container. #pw-group-other * @property string|null $prependMarkup Optional markup to prepend to the Inputfield content container. #pw-group-other
* @property string|null $appendMarkup Optional markup to append to the Inputfield content container. #pw-group-other * @property string|null $appendMarkup Optional markup to append to the Inputfield content container. #pw-group-other
* *
@@ -218,6 +219,30 @@ abstract class Inputfield extends WireData implements Module {
*/ */
const collapsedBlankAjax = 11; const collapsedBlankAjax = 11;
/**
* Collapsed into a separate tab
* #pw-group-collapsed-constants
* @since 3.0.201
*
*/
const collapsedTab = 20;
/**
* Collapsed into a separate tab and AJAX loaded
* #pw-group-collapsed-constants
* @since 3.0.201
*
*/
const collapsedTabAjax = 21;
/**
* Collapsed into a separate tab and locked (not editable)
* #pw-group-collapsed-constants
* @since 3.0.201
*
*/
const collapsedTabLocked = 22;
/** /**
* Don't skip the label (default) * Don't skip the label (default)
* #pw-group-skipLabel-constants * #pw-group-skipLabel-constants
@@ -366,6 +391,7 @@ abstract class Inputfield extends WireData implements Module {
$this->set('notes', ''); // highlighted descriptive copy, below output of input field $this->set('notes', ''); // highlighted descriptive copy, below output of input field
$this->set('detail', ''); // text details that appear below notes $this->set('detail', ''); // text details that appear below notes
$this->set('head', ''); // below label, above description $this->set('head', ''); // below label, above description
$this->set('tabLabel', ''); // alternate label for tab when Inputfield::collapsedTab* in use
$this->set('required', 0); // set to 1 to make value required for this field $this->set('required', 0); // set to 1 to make value required for this field
$this->set('requiredIf', ''); // optional conditions to make it required $this->set('requiredIf', ''); // optional conditions to make it required
$this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value) $this->set('collapsed', ''); // see the collapsed* constants at top of class (use blank string for unset value)
@@ -1467,6 +1493,9 @@ abstract class Inputfield extends WireData implements Module {
if($this->hasFieldtype !== false) { if($this->hasFieldtype !== false) {
$field->addOption(self::collapsedYesAjax, $this->_('Closed + Load only when opened (AJAX)') . ""); $field->addOption(self::collapsedYesAjax, $this->_('Closed + Load only when opened (AJAX)') . "");
$field->notes = sprintf($this->_('Options indicated with %s may not work with all input types or placements, test to ensure compatibility.'), '†'); $field->notes = sprintf($this->_('Options indicated with %s may not work with all input types or placements, test to ensure compatibility.'), '†');
$field->addOption(self::collapsedTab, $this->_('Tab'));
$field->addOption(self::collapsedTabAjax, $this->_('Tab + Load only when clicked (AJAX)') . "");
$field->addOption(self::collapsedTabLocked, $this->_('Tab + Locked (not editable)'));
} }
$field->addOption(self::collapsedHidden, $this->_('Hidden (not shown in the editor)')); $field->addOption(self::collapsedHidden, $this->_('Hidden (not shown in the editor)'));
$field->attr('value', (int) $this->collapsed); $field->attr('value', (int) $this->collapsed);

View File

@@ -697,7 +697,13 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$classes = array(); $classes = array();
$useColumnWidth = $this->useColumnWidth; $useColumnWidth = $this->useColumnWidth;
$renderAjaxInputfield = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : null; $renderAjaxInputfield = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : null;
$lockedStates = array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked, Inputfield::collapsedBlankLocked);
$lockedStates = array(
Inputfield::collapsedNoLocked,
Inputfield::collapsedYesLocked,
Inputfield::collapsedBlankLocked,
Inputfield::collapsedTabLocked
);
if($useColumnWidth === true && isset($_classes['form']) && strpos($_classes['form'], 'InputfieldFormNoWidths') !== false) { if($useColumnWidth === true && isset($_classes['form']) && strpos($_classes['form'], 'InputfieldFormNoWidths') !== false) {
$useColumnWidth = false; $useColumnWidth = false;
@@ -988,8 +994,9 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
public function ___renderInputfield(Inputfield $inputfield, $renderValueMode = false) { public function ___renderInputfield(Inputfield $inputfield, $renderValueMode = false) {
$inputfieldID = $inputfield->attr('id'); $inputfieldID = $inputfield->attr('id');
$collapsed = $inputfield->getSetting('collapsed'); $collapsed = (int) $inputfield->getSetting('collapsed');
$ajaxInputfield = $collapsed == Inputfield::collapsedYesAjax || ($collapsed == Inputfield::collapsedBlankAjax && $inputfield->isEmpty()); $ajaxInputfield = $collapsed == Inputfield::collapsedYesAjax || $collapsed === Inputfield::collapsedTabAjax
|| ($collapsed == Inputfield::collapsedBlankAjax && $inputfield->isEmpty());
$ajaxHiddenInput = "<input type='hidden' name='processInputfieldAjax[]' value='$inputfieldID' />"; $ajaxHiddenInput = "<input type='hidden' name='processInputfieldAjax[]' value='$inputfieldID' />";
$ajaxID = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : ''; $ajaxID = $this->wire()->config->ajax ? $this->wire()->input->get('renderInputfieldAjax') : '';
$required = $inputfield->getSetting('required'); $required = $inputfield->getSetting('required');
@@ -1000,6 +1007,7 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
$ajaxInputfield = false; $ajaxInputfield = false;
if($collapsed == Inputfield::collapsedYesAjax) $inputfield->collapsed = Inputfield::collapsedYes; if($collapsed == Inputfield::collapsedYesAjax) $inputfield->collapsed = Inputfield::collapsedYes;
if($collapsed == Inputfield::collapsedBlankAjax) $inputfield->collapsed = Inputfield::collapsedBlank; if($collapsed == Inputfield::collapsedBlankAjax) $inputfield->collapsed = Inputfield::collapsedBlank;
if($collapsed == Inputfield::collapsedTabAjax) $inputfield->collapsed = Inputfield::collapsedTab;
// indicate to next processInput that this field can be processed // indicate to next processInput that this field can be processed
$inputfield->appendMarkup .= $ajaxHiddenInput; $inputfield->appendMarkup .= $ajaxHiddenInput;
} }
@@ -1172,12 +1180,20 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
Inputfield::collapsedLocked, Inputfield::collapsedLocked,
Inputfield::collapsedNoLocked, Inputfield::collapsedNoLocked,
Inputfield::collapsedBlankLocked, Inputfield::collapsedBlankLocked,
Inputfield::collapsedYesLocked Inputfield::collapsedYesLocked,
Inputfield::collapsedTabLocked,
); );
$ajaxTypes = array(
Inputfield::collapsedYesAjax,
Inputfield::collapsedBlankAjax,
Inputfield::collapsedTabAjax,
);
$collapsed = (int) $inputfield->getSetting('collapsed'); $collapsed = (int) $inputfield->getSetting('collapsed');
if(in_array($collapsed, $skipTypes)) return false; if(in_array($collapsed, $skipTypes)) return false;
if(in_array($collapsed, array(Inputfield::collapsedYesAjax, Inputfield::collapsedBlankAjax))) { if(in_array($collapsed, $ajaxTypes)) {
$processAjax = $this->wire()->input->post('processInputfieldAjax'); $processAjax = $this->wire()->input->post('processInputfieldAjax');
if(is_array($processAjax) && in_array($inputfield->attr('id'), $processAjax)) { if(is_array($processAjax) && in_array($inputfield->attr('id'), $processAjax)) {
// field can be processed (convention used by InputfieldWrapper) // field can be processed (convention used by InputfieldWrapper)

View File

@@ -8,7 +8,7 @@
* For documentation about the fields used in this class, please see: * For documentation about the fields used in this class, please see:
* /wire/core/Fieldtype.php * /wire/core/Fieldtype.php
* *
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer * ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* https://processwire.com * https://processwire.com
* *
* *
@@ -25,12 +25,19 @@ class InputfieldFieldsetTabOpen extends InputfieldFieldsetOpen {
public function ___getConfigInputfields() { public function ___getConfigInputfields() {
$inputfields = parent::___getConfigInputfields(); $inputfields = parent::___getConfigInputfields();
$inputfields->remove($inputfields->getChildByName('columnWidth'));
$in = $inputfields->getChildByName('columnWidth');
if($in) $inputfields->remove($in);
$in = $inputfields->getChildByName('collapsed'); $in = $inputfields->getChildByName('collapsed');
//if($in->parent) $in->parent->set('collapsed', Inputfield::collapsedYes); //if($in->parent) $in->parent->set('collapsed', Inputfield::collapsedYes);
if($in) {
foreach($in->getOptions() as $key => $value) { foreach($in->getOptions() as $key => $value) {
// tabs may not be collapsed // tabs may not be collapsed
if($key != Inputfield::collapsedNo && $key != Inputfield::collapsedYesAjax) $in->removeOption($key); if($key != Inputfield::collapsedNo && $key != Inputfield::collapsedYesAjax) {
$in->removeOption($key);
}
}
} }
// tabs don't support showIf // tabs don't support showIf
$in = $inputfields->getChildByName('showIf'); $in = $inputfields->getChildByName('showIf');
@@ -53,11 +60,12 @@ class FieldtypeFieldsetTabOpen extends FieldtypeFieldsetOpen {
} }
public function getInputfield(Page $page, Field $field) { public function getInputfield(Page $page, Field $field) {
/** @var InputfieldFieldsetTabOpen $inputfield */
$inputfield = $this->wire(new InputfieldFieldsetTabOpen()); $inputfield = $this->wire(new InputfieldFieldsetTabOpen());
$inputfield->class = $this->className(); $inputfield->class = $this->className();
if($field->modal) { if($field->get('modal')) {
$inputfield->modal = true; $inputfield->set('modal', true);
} else if($field->collapsed == Inputfield::collapsedYesAjax) { } else if($field->collapsed == Inputfield::collapsedYesAjax || $field->collapsed == Inputfield::collapsedTabAjax) {
$inputfield->collapsed = $field->collapsed; $inputfield->collapsed = $field->collapsed;
} }
return $inputfield; return $inputfield;
@@ -70,7 +78,7 @@ class FieldtypeFieldsetTabOpen extends FieldtypeFieldsetOpen {
$in->label = $this->_('Open in modal window?'); $in->label = $this->_('Open in modal window?');
$in->description = $this->_('Check the box to make this tab open in its own modal window. This can improve performance with large forms.'); $in->description = $this->_('Check the box to make this tab open in its own modal window. This can improve performance with large forms.');
$in->notes = $this->_('To solve a similar need, you might instead consider the AJAX option, available at: Input (tab) > Visibility > Presentation.'); $in->notes = $this->_('To solve a similar need, you might instead consider the AJAX option, available at: Input (tab) > Visibility > Presentation.');
if($field->modal) $in->attr('checked', 'checked'); if($field->get('modal')) $in->attr('checked', 'checked');
$inputfields->add($in); $inputfields->add($in);
return $inputfields; return $inputfields;
} }

View File

@@ -1176,6 +1176,15 @@ class ProcessField extends Process implements ConfigurableModule {
$form->append($field); $form->append($field);
} }
// move the 'tabLabel' input right below the 'collapsed' input
$f1 = $form->getChildByName('tabLabel');
$fs = $form->getChildByName('visibility');
$f2 = $fs ? $fs->getChildByName('collapsed') : null;
if($f1 && $f2) {
$f1->getParent()->remove($f1);
$fs->insertAfter($f1, $f2);
}
$focus = $input->get('focus'); $focus = $input->get('focus');
if($focus) { if($focus) {
$focus = $sanitizer->fieldName($focus); $focus = $sanitizer->fieldName($focus);
@@ -1650,6 +1659,19 @@ class ProcessField extends Process implements ConfigurableModule {
$form->add($field); $form->add($field);
$languageFields[] = $field; $languageFields[] = $field;
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
$field->label = $this->_('Label for tab');
$field->attr('name', 'tabLabel');
$field->attr('value', (string) $this->field->get('tabLabel'));
$field->icon = 'tag';
$field->description = $this->_('If field is displayed in its own tab, optionally specify an alternate tab label if different from the field label.');
$field->notes = $this->_('The tab label should ideally be very short, like just one word.');
$field->collapsed = Inputfield::collapsedBlank;
$field->showIf = 'collapsed=' . Inputfield::collapsedTab . '|' . Inputfield::collapsedTabAjax . '|' . Inputfield::collapsedTabLocked;
$form->add($field);
$languageFields[] = $field;
if($languages) foreach($languageFields as $field) { if($languages) foreach($languageFields as $field) {
$field->useLanguages = true; $field->useLanguages = true;
$name = $field->name; $name = $field->name;
@@ -2286,7 +2308,7 @@ class ProcessField extends Process implements ConfigurableModule {
} }
if($name === 'tags') { if($name === 'tags') {
$value = $sanitizer->words($value); $value = $sanitizer->getTextTools()->strtolower($sanitizer->words($value));
} }
$this->field->set($name, $value); $this->field->set($name, $value);

View File

@@ -914,6 +914,7 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
Inputfield::collapsedNoLocked, Inputfield::collapsedNoLocked,
Inputfield::collapsedBlankLocked, Inputfield::collapsedBlankLocked,
Inputfield::collapsedYesLocked, Inputfield::collapsedYesLocked,
Inputfield::collapsedTabLocked,
); );
$collapsed = $inputfield->getSetting('collapsed'); $collapsed = $inputfield->getSetting('collapsed');
if($collapsed > 0 && !in_array($collapsed, $skipCollapsed)) { if($collapsed > 0 && !in_array($collapsed, $skipCollapsed)) {
@@ -932,6 +933,42 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
$tabWrap = null; $tabWrap = null;
$tabOpen = null; $tabOpen = null;
$tabViewable = null; $tabViewable = null;
$fieldsetTab = null;
$collapsedTabTypes = array(
Inputfield::collapsedTab => 1,
Inputfield::collapsedTabAjax => 1,
Inputfield::collapsedTabLocked => 1,
);
// identify fields displayed as tabs and add fieldset open/close around them
foreach($contentTab as $inputfield) {
/** @var Inputfield $inputfield */
if(!isset($collapsedTabTypes[$inputfield->collapsed])) continue;
/** @var InputfieldFieldsetTabOpen $tab */
if(!$fieldsetTab) {
$fieldsetTab = $this->modules->get('FieldtypeFieldsetTabOpen');
$this->modules->get('FieldtypeFieldsetClose');
}
$tab = new InputfieldFieldsetTabOpen();
$this->wire($tab);
$tab->attr('name', '_tab_' . $inputfield->attr('name'));
$tab->attr('id', '_tab_' . $inputfield->attr('id'));
$tab->label = $inputfield->getSetting('tabLabel|label');
if($inputfield->collapsed === Inputfield::collapsedTabAjax) {
$tab->collapsed = Inputfield::collapsedYesAjax;
$inputfield->collapsed = Inputfield::collapsedNo;
if($this->isPost && !$contentTab->isProcessable($tab)) {
$contentTab->remove($inputfield);
continue;
}
}
$contentTab->insertBefore($tab, $inputfield);
/** @var InputfieldFieldsetClose $tabClose */
$tabClose = new InputfieldFieldsetClose();
$this->wire($tabClose);
$tabClose->attr('id+name', $tab->attr('name') . '_END');
$contentTab->insertAfter($tabClose, $inputfield);
}
foreach($contentTab as $inputfield) { foreach($contentTab as $inputfield) {
if(!$tabOpen && $inputfield->className() === 'InputfieldFieldsetTabOpen') { if(!$tabOpen && $inputfield->className() === 'InputfieldFieldsetTabOpen') {
@@ -945,6 +982,7 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
continue; continue;
} }
$tabOpen = $inputfield; $tabOpen = $inputfield;
/** @var InputfieldWrapper $tabWrap */
$tabWrap = $this->wire(new InputfieldWrapper()); $tabWrap = $this->wire(new InputfieldWrapper());
$tabWrap->attr('title', $tabOpen->getSetting('label')); $tabWrap->attr('title', $tabOpen->getSetting('label'));
$tabWrap->id = $tabOpen->attr('id'); $tabWrap->id = $tabOpen->attr('id');