1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-10 00:37:02 +02:00

Add support for PHP-defined header actions for Inputfields as requested by @Toutouwai. These work the same as those defined in JS via Inputfields.addHeaderAction() except the method can now also be called from Inputfield objects in PHP. Also added support for 'link' type actions that open a link in either the current or a modal window.

This commit is contained in:
Ryan Cramer
2024-05-24 14:42:23 -04:00
parent 049efa7c3b
commit 3c5205721b
4 changed files with 177 additions and 18 deletions

View File

@@ -3,7 +3,7 @@
/**
* ProcessWire Inputfield - base class for Inputfield modules.
*
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
* An Inputfield for an actual form input field widget, and this is provided as the base class
@@ -397,6 +397,14 @@ abstract class Inputfield extends WireData implements Module {
*/
protected $editable = true;
/**
* Header icon definitions
*
* @var array
*
*/
protected $headerActions = array();
/**
* Construct the Inputfield, setting defaults for all properties
*
@@ -2081,6 +2089,60 @@ abstract class Inputfield extends WireData implements Module {
return $this->editable;
}
/**
* Add header action
*
* This adds a clickable icon to the right side of the Inputfield header.
* There are three types of actions: 'click', 'toggle' and 'link'. The 'click'
* action simply triggers your JS event whenever it is clicked. The 'toggle' action
* has an on/off state, and you can specify the JS event to trigger for each.
* This function will automatically figure out whether you want a `click`,
* `toggle` or 'link' action based on what you provide in the $settings argument.
* Below is a summary of these settings:
*
* Settings for 'click' or 'link' type actions:
*
* - `icon` (string): Name of font-awesome icon to use.
* - `tooltip` (string): Optional tooltip text to display when icon hovered.
* - `event` (string): Event name to trigger in JS when clicked ('click' actions only).
* - `href` (string): URL to open ('link' actions only).
* - `modal` (bool): Specify true to open link in modal ('link' actions only).
*
* Settings for 'toggle' (on/off) type actions:
*
* - `on` (bool): Start with the 'on' state? (default=false)
* - `onIcon` (string): Name of font-awesome icon to show for on state.
* - `onEvent` (string): JS event name to trigger when toggled on.
* - `onTooltip` (string): Tooltip text to show when on icon is hovered.
* - `offIcon` (string): Name of font-awesome icon to show for off state.
* - `offEvent` (string): JS event name to trigger when toggled off.
* - `offTooltip` (string): Tooltip text to show when off icon is hovered.
*
* Other/optional settings (applies to all types):
*
* - `name` (string): Name of this action (-_a-zA-Z0-9).
* - `overIcon` (string): Name of font-awesome icon to show when hovered.
* - `overEvent` (string): JS event name to trigger when mouse is over the icon.
* - `cursor` (string): CSS cursor name to show when mouse is over the icon.
* - `setAll` (array): Set all of the header actions in one call, replaces any existing.
* Note: to get all actions, call the method and omit the $settings argument.
*
* @param array $settings Specify array containing the appropriate settings above.
* @return array Returns all currently added actions.
* @since 3.0.240
*
*/
public function addHeaderAction(array $settings = array()) {
if(!empty($settings['setAll'])) {
if(is_array($settings['setAll'])) {
$this->headerActions = array_values($settings['setAll']);
}
} else {
$this->headerActions[] = $settings; // add new action
}
return $this->headerActions; // return all
}
/**
* debugInfo PHP 5.6+ magic method
*

View File

@@ -3,7 +3,7 @@
/**
* ProcessWire InputfieldWrapper
*
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
* ProcessWire 3.x, Copyright 2024 by Ryan Cramer
* https://processwire.com
*
* About InputfieldWrapper
@@ -885,6 +885,10 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
if($toggle && strpos($toggle, 'title=') === false) {
$toggle = str_replace("class=", "title='" . $this->_('Toggle open/close') . "' class=", $toggle);
}
$headerActions = $inputfield->addHeaderAction();
if(count($headerActions)) {
$label .= $this->renderHeaderActions($inputfield, $headerActions);
}
if($skipLabel === Inputfield::skipLabelHeader || $quietMode) {
// label only shows when field is collapsed
$label = str_replace('{out}', $icon . $label . $toggle, $markup['item_label_hidden']);
@@ -992,6 +996,52 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
return $out;
}
/**
* Render Inputfield header actions
*
* @param Inputfield $inputfield
* @param array $actions
* @return string
* @since 3.0.240
*
*/
protected function renderHeaderActions(Inputfield $inputfield, array $actions) {
$sanitizer = $this->wire()->sanitizer;
$out = '';
$modal = false;
foreach($actions as $a) {
$icon = '';
$type = '';
if(isset($a['icon'])) {
$icon = $a['icon'];
if(isset($a['href'])) {
$type = 'link';
if(!empty($a['modal'])) $modal = true;
} else {
$type = 'click';
}
} else if(isset($a['offIcon'])) {
$type = 'toggle';
if(!isset($a['onIcon'])) $a['onIcon'] = $a['offIcon'];
} else if(isset($a['onIcon'])) {
$type = 'toggle';
$a['offIcon'] = $a['onIcon'];
}
if($type === 'toggle') $icon = $a['on'] ? $a['onIcon'] : $a['offIcon'];
if(empty($icon) || empty($type)) continue;
$a['type'] = $type;
if(strpos($icon, 'fa-') !== 0) $icon = "fa-$icon";
$data = $sanitizer->entities(json_encode($a));
$out .= "<i class='_InputfieldHeaderAction fa fa-fw $icon' data-action='$data' hidden></i>";
}
if($modal) {
/** @var JqueryUI $jQueryUI */
$jQueryUI = $this->wire()->modules->get('JqueryUI');
$jQueryUI->use('modal');
}
return $out;
}
/**
* Render the output of this Inputfield and its children, showing values only (no inputs)
*

View File

@@ -1067,25 +1067,31 @@ var Inputfields = {
* Add an Inputfield header icon action
*
* This adds a clickable icon to the right side of the Inputfield header.
* There are two types of actions: 'click' and 'toggle'. The 'click' action
* There are three types of actions: 'click', 'toggle' and 'link'. The 'click' action
* simply executes your callback whenever it is clicked. The 'toggle' action
* has an on/off state, and you can provide callbacks and icons for either.
* This function will automatically figure out whether you want a `click`
* or `toggle` action based on what you provide in the settings argument.
* This function will automatically figure out whether you want a `click`,
* `toggle` or 'link' action based on what you provide in the settings argument.
* Below is a summary of these settings:
*
* Settings for 'click' type actions:
* Settings for 'click' and 'link' type actions:
* - `icon` (string): Class to use for icon, i.e. 'fa-cog'.
* - `callback` (function): Callback function when action icon is clicked.
* - `tooltip` (string): Optional tooltip to describe what the action does.
*
* - `event` (string): Event name to trigger in JS when clicked ('click' actions only).
* - `tooltip` (string): Optional tooltip to describe what the action does.
* - `href` (string): URL to open ('link' actions only).
* - `modal` (bool): Specify true to make a link open in a modal window ('link' actions only).
* (requires that /wire/modules/JqueryUI/JqueryUI/modal.js is loaded)
*
* Settings for 'toggle' (on/off) type actions:
* - `on` (bool): True if action is currently ON, false if not (default=false).
* - `onIcon` (string): Icon class when action is ON and clicking would toggle OFF, i.e. 'fa-toggle-off'.
* - `onCallback` (function): Callback function when action is clicked to turn ON.
* - `onEvent` (string): JS event name to trigger when toggled ON (alternative to onCallback).
* - `onTooltip` (string): Optional tooltip text for when action is ON.
* - `offIcon` (string): Icon class when action is OFF and clicking would toggle ON, i.e. 'fa-toggle-on'.
* - `offCallback` (function): Callback function when action is clicked to turn OFF.
* - `offEvent` (string): JS event name to trigger when toggled OFF (alternative to offCallback).
* - `offTooltip` (string): Optional tooltip text for when action is OFF.
* - Note that if 'offIcon' or 'offTooltip' are omitted, they will use their 'on' equivalent.
*
@@ -1140,43 +1146,60 @@ var Inputfields = {
icon: '',
callback: null,
tooltip: '',
event: '',
// for link actions (addable from PHP side only):
href: '',
target: '',
modal: false,
// for toggle actions:
on: false,
onIcon: '',
onCallback: null,
onTooltip: '',
onEvent: '',
offIcon: '',
offCallback: null,
offTooltip: '',
offEvent: '',
// for optional mouseover state:
overIcon: '',
overCallback: null,
overEvent: '',
cursor: '',
// other
name: '',
iconTag: '<i class="fa fa-fw"></i>',
// icon element if already present
$iconElement: null,
};
settings = $.extend(defaults, settings);
var $header = this.header($inputfield);
var $icon = $(settings.iconTag);
var $icon = settings.$iconElement ? settings.$iconElement : $(settings.iconTag);
var cls, tooltip, actionType = 'click';
var useFA = $icon.hasClass('fa')
function fa(cls) {
return (useFA && cls.indexOf('fa-') !== 0 ? 'fa-' + cls : cls);
}
$icon.addClass('InputfieldHeaderAction')
$icon.addClass('InputfieldHeaderAction').removeClass('_InputfieldHeaderAction')
.css({ float: 'right', lineHeight: $header.css('line-height') });
if(settings.onIcon.length) actionType = 'toggle';
if(settings.href.length) actionType = 'link';
if(settings.name.length) $icon.addClass('InputfieldHeaderAction-' + settings.name);
if(settings.cursor.length) $icon.css('cursor', settings.cursor);
if(actionType === 'link') {
if(!settings.tooltip.length) {
settings.tooltip = settings.href;
}
}
if(actionType === 'toggle') {
if(!settings.offIcon.length) settings.offIcon= settings.onIcon;
if(!settings.offIcon.length) settings.offIcon = settings.onIcon;
if(!settings.offTooltip.length) settings.offTooltip = settings.onTooltip;
if(settings.on) {
$icon.addClass(fa(settings.onIcon)).data('on', true);
@@ -1194,31 +1217,41 @@ var Inputfields = {
if(tooltip.length) $icon.attr('title', tooltip);
$icon.on('click', function() {
if(actionType === 'toggle') {
if(actionType === 'link') {
if(settings.modal) {
pwModalWindow(settings.href);
} else {
window.location.href = settings.href;
}
} else if(actionType === 'toggle') {
if($icon.data('on')) {
$icon.removeClass(fa(settings.onIcon)).addClass(fa(settings.offIcon))
if(settings.offTooltip.length) $icon.attr('title', settings.offTooltip);
if(settings.offCallback) settings.offCallback($icon);
$icon.data('on', false);
if(settings.offEvent) $icon.trigger(settings.offEvent, [ $icon ]);
} else {
$icon.removeClass(fa(settings.offIcon)).addClass(fa(settings.onIcon));
if(settings.onTooltip.length) $icon.attr('title', settings.onTooltip);
if(settings.onCallback) settings.onCallback($icon);
$icon.data('on', true);
if(settings.onEvent) $icon.trigger(settings.onEvent, [ $icon ]);
}
} else {
if(settings.callback) settings.callback($icon);
if(settings.event) settings.trigger(settings.event, [ $icon ]);
}
return false;
});
if(settings.overIcon.length || settings.overCallback) {
if(settings.overIcon.length || settings.overCallback || settings.overEvent) {
$icon.on('mouseover', function() {
if(settings.overIcon.length) {
var cls = $icon.data('on') ? settings.onIcon : settings.offIcon;
$icon.removeClass(fa(cls)).addClass(fa(settings.overIcon));
}
if(settings.overCallback) settings.overCallback($icon);
if(settings.overEvent) $icon.trigger(settings.overEvent, [ $icon ]);
});
if(settings.overIcon.length) {
$icon.on('mouseout', function() {
@@ -1227,8 +1260,9 @@ var Inputfields = {
});
}
}
$header.append($icon);
if($icon.prop('hidden')) $icon.prop('hidden', false);
return $icon;
},
@@ -2648,6 +2682,16 @@ function InputfieldRequirements($target) {
});
}
function InputfieldHeaderActions($target) {
jQuery('._InputfieldHeaderAction', $target).each(function() {
var $i = $(this);
var data = JSON.parse($i.attr('data-action'));
data.$iconElement = $i;
var $inputfield = $i.closest('.Inputfield');
Inputfields.addHeaderAction($inputfield, data);
});
}
/**
* Event handler called when 'reload' event is triggered on an Inputfield
*
@@ -2709,6 +2753,7 @@ function InputfieldsInit($target) {
InputfieldStates($target);
InputfieldDependencies($target);
InputfieldRequirements($target);
InputfieldHeaderActions($target);
setTimeout(function() { InputfieldColumnWidths(); }, 100);
}
@@ -2740,8 +2785,10 @@ jQuery(document).ready(function($) {
setTimeout(InputfieldWindowResizeActions2, 500);
return true;
});
InputfieldRequirements($('.InputfieldForm'));
var $form = $('.InputfieldForm');
InputfieldRequirements($form);
InputfieldHeaderActions($form);
$(document).on('reload', '.Inputfield', InputfieldReloadEvent);

File diff suppressed because one or more lines are too long