Skip to content
Snippets Groups Projects
Commit e2f1e238 authored by Hans van Wezenbeek's avatar Hans van Wezenbeek
Browse files

Issue #3413638 by mdolnik: Add Icon Picker Widget

parent 584931e0
No related branches found
No related tags found
No related merge requests found
### Icons Iconpicker
Provides a fonticonpicker element for the Icons module.
Refer to https://fonticonpicker.github.io
## Installation
### Option 1: Composer
1. Ensure composer is set up to install npm-assets to the `libraries` folder:
- Refer to https://www.drupal.org/docs/develop/using-composer/manage-dependencies#third-party-libraries
2. Run `composer require "npm-asset/fonticonpicker--fonticonpicker:^3.1`
### Option 2: Manual installation
1. Go to https://github.com/fontIconPicker/fontIconPicker/releases and download
`fontIconPicker.zip`.
2. Extract the `dist` folder to `libraries/fonticonpicker--fonticonpicker/dist`.
## Usage
## Widget
1. Set up one or more Icon Sets as per `Configuration` in the main module's README.
2. Add a `List (icon)` field.
3. Set the field widget to `Font Icon Picker`.
## Custom Element
Add the following render array to a form array.
```php
$element['icon_picker'] = [
'#type' => 'font_icon_picker',
'#title' => 'Icon Picker',
'#default_value' => 'icon_set_name:icon-id',
'#icon_picker_theme' => 'darkgrey',
];
```
name: 'Icons Iconpicker'
type: module
description: 'Provides a fonticonpicker element for the Icons module'
core_version_requirement: ^8 || ^9 || ^10
dependencies:
- icons:icons
fonticonpicker.element:
version: VERSION
js:
js/fonticonpicker-element.js: {}
dependencies:
- core/once
- core/jquery
- core/drupal
- core/drupalSettings
- icons_iconpicker/fonticonpicker.base
fonticonpicker.base:
version: &iconpicker_version '3.1.1'
license: &iconpicker_license
name: MIT
url: https://github.com/fontIconPicker/fontIconPicker/blob/master/LICENSE
gpl-compatible: true
remote: https://github.com/fontIconPicker/fontIconPicker/releases/download/v3.1.1/fontIconPicker.zip
js:
/libraries/fonticonpicker--fonticonpicker/dist/js/jquery.fonticonpicker.min.js: { minified: true }
css:
base:
/libraries/fonticonpicker--fonticonpicker/dist/css/base/jquery.fonticonpicker.min.css: { minified: true }
dependencies:
- core/jquery
fonticonpicker.theme.grey:
version: *iconpicker_version
license: *iconpicker_license
css:
component:
/libraries/fonticonpicker--fonticonpicker/dist/css/themes/grey-theme/jquery.fonticonpicker.grey.min.css: { minified: true }
dependencies:
- icons_iconpicker/fonticonpicker.base
fonticonpicker.theme.dark_grey:
version: *iconpicker_version
license: *iconpicker_license
css:
component:
/libraries/fonticonpicker--fonticonpicker/dist/css/themes/dark-grey-theme/jquery.fonticonpicker.darkgrey.min.css: { minified: true }
dependencies:
- icons_iconpicker/fonticonpicker.base
fonticonpicker.theme.bootstrap:
version: *iconpicker_version
license: *iconpicker_license
css:
component:
/libraries/fonticonpicker--fonticonpicker/dist/css/themes/bootstrap-theme/jquery.fonticonpicker.bootstrap.min.css: { minified: true }
dependencies:
- icons_iconpicker/fonticonpicker.base
fonticonpicker.theme.inverted:
version: *iconpicker_version
license: *iconpicker_license
css:
component:
/libraries/fonticonpicker--fonticonpicker/dist/css/themes/inverted-theme/jquery.fonticonpicker.inverted.min.css: { minified: true }
dependencies:
- icons_iconpicker/fonticonpicker.base
<?php
/**
* @file
* Contains icons_iconpicker.module.
*/
/**
* Check to make sure that the fonticonpicker library is installed.
*
* @return bool
* Flag indicating if the library is properly installed.
*/
function icons_iconpicker_check_library(): bool {
$iconpicker_library = \Drupal::service('library.discovery')->getLibraryByName('icons_iconpicker', 'fonticonpicker.base');
return file_exists(DRUPAL_ROOT . '/' . $iconpicker_library['js'][0]['data']);
}
/**
* @file
* Initialize fontIconPicker.
*/
(function ($, once) {
"use strict";
Drupal.behaviors.miconElement = {
attach: function (context) {
const settings = drupalSettings.FontIconPicker;
const $iconPickerIcon = $(once('IconsFontPickerIcon', 'input.icons-font-icon-picker', context));
$iconPickerIcon.each(function (index, element) {
$(element).fontIconPicker({
source: settings.source,
searchSource: settings.searchSource,
theme: settings.theme,
});
});
},
};
})(jQuery, once);
<?php
namespace Drupal\icons_iconpicker\Element;
use Drupal;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Textfield;
use RuntimeException;
/**
* Provides a "jQuery fontIconPicker" element to help search and select icons.
*
* Usage example:
* @code
* $form['font_icon_picker'] = [
* '#type' => 'font_icon_picker',
* '#title' => $this->t('Icon Picker'),
* '#icon_theme' => 'darkgrey',
* ];
* @endcode
*
* @FormElement("font_icon_picker")
*
* @see https://fonticonpicker.github.io/
* @see FontIconPickerWidget
*/
class FontIconPickerElement extends Textfield {
/**
* Defines valid theme names.
*/
public const PICKER_THEMES = [
'grey',
'darkgrey',
'bootstrap',
'inverted',
];
/**
* {@inheritdoc}
*/
public function getInfo(): array {
$class = get_class($this);
$info = parent::getInfo();
$info['#process'][] = [$class, 'processIconPicker'];
$info['#element_validate'][] = [$class, 'validateIconName'];
$info['#icon_picker_theme'] = 'grey';
return $info;
}
/**
* Processes an icon picker element.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*
* @throws \RuntimeException
* -
*/
public static function processIconPicker(array &$element, FormStateInterface $form_state, array &$complete_form): array {
// Throw error if library file not found.
if (!icons_iconpicker_check_library()) {
Drupal::messenger()->addWarning(t('The fontIconPicker library could not be found. Ensure the fontIconPicker library is installed correctly. Refer to the "Icons Iconpicker" submodule README file for more details.'));
}
// Add class for discovery in `fonticonpicker-element.js`.
$element['#attributes']['class'][] = 'icons-font-icon-picker';
// Add required library.
$element['#attached']['library'][] = 'icons_iconpicker/fonticonpicker.element';
// Configure the icon picker theme.
$theme = $element['#icon_picker_theme'] ?? 'grey';
if (!in_array($theme, static::PICKER_THEMES)) {
throw new RuntimeException(sprintf('%s:%s: Font Icon Picker theme "%s" is not valid. Refer to "FontIconPickerElement::VALID_THEMES"', __METHOD__, __LINE__, $theme));
}
$element['#attached']['library'][] = "icons_iconpicker/fonticonpicker.theme.$theme";
$element['#attached']['drupalSettings']['FontIconPicker']['theme'] = "fip-$theme";
// Populate the icons.
$icons_info = static::getIconInfo($element);
foreach ($icons_info as $info) {
// Ensure any attachments (libraries) provided by all icons are included
// within this element.
$element['#attached'] = array_merge_recursive($element['#attached'] ?? [], $info['#attached'] ?? []);
// Provide each icon to the JS, organized by corresponding icon set.
$set_label = $info['#icon_set_label'];
$element['#attached']['drupalSettings']['FontIconPicker']['source'][$set_label][] = $info['#icon_classes'];
$element['#attached']['drupalSettings']['FontIconPicker']['searchSource'][$set_label][] = $info['#icon_tags'];
}
return $element;
}
/**
* Gathers information on all available icons.
*
* @return array
* An array of render arrays for each icon and is keyed by their Icon ID
* in the form of `icon_set_name:icon-id`.
*/
protected static function getIconInfo(array &$element): array {
if (!empty($element['#icon_info'])) {
return $element['#icon_info'];
}
$icon_info = [];
$icon_set_storage = Drupal::service('entity_type.manager')->getStorage('icon_set');
/** @var \Drupal\icons\Entity\IconSetInterface[] $icon_sets */
$icon_sets = $icon_set_storage->loadMultiple();
foreach ($icon_sets as $icon_set) {
/** @var \Drupal\icons\IconLibraryPluginJsonBase $plugin */
$plugin = $icon_set->getPlugin();
$icons = $plugin->getIcons();
foreach ($icons as $icon_key => $icon_title) {
// Build a temporary render array of the icon to extract extra info.
$render_array = [
'#type' => 'icon',
'#theme' => 'icon',
'#icon_set' => $icon_set,
'#icon_name' => $icon_key,
];
$build = $plugin->build($render_array, $icon_set, $icon_key);
// We need to determine the final class(es) used to build the icon as
// this is what fonticonpicker uses to display the icons as well as
// uses it as the value internally.
$icon_classes = implode(' ', $build['#attributes']['class']);
// Append extra info to the render array and key by the icon ID.
$icon_id = $icon_set->id() . ':' . $icon_key;
$icon_info[$icon_id] = $build + [
'#icon_id' => $icon_id,
'#icon_title' => $icon_title,
'#icon_set_id' => $icon_set->id(),
'#icon_set_label' => $icon_set->label(),
'#icon_classes' => $icon_classes,
'#icon_tags' => implode(', ', [
$icon_title,
$icon_set->label(),
$plugin->label(),
]),
];
}
}
$element['#icon_info'] = $icon_info;
return $icon_info;
}
/**
* Validate the selected Icon ID.
*
* We need to ensure the resulting value provided by the valueCallback() is
* valid before saving.
*
* @see IconPicker::valueCallback()
*/
public static function validateIconName($element, FormStateInterface $form_state): void {
$value = $element['#value'];
if (empty($value)) {
return;
}
$icons_info = static::getIconInfo($element);
if (!isset($icons_info[$value])) {
$name = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
$form_state->setError($element, t('The submitted value %choice in the %name element is invalid.', [
'%choice' => $value,
'%name' => $name,
]));
}
}
/**
* {@inheritdoc}
*
* Transforms the value from icon class(es) string to Icon ID.
*
* @see IconPicker::preRenderTextfield()
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$input = parent::valueCallback($element, $input, $form_state);
if ($input !== NULL) {
// Transform the value from icon class(es) string to Icon ID.
$icons_info = static::getIconInfo($element);
foreach ($icons_info as $id => $info) {
if ($input === $info['#icon_classes']) {
return $id;
}
}
}
return $input;
}
/**
* {@inheritdoc}
*
* - Transforms the value from Icon ID to icon class(es).
* - Disables JS required validation.
* - Ensures the text field is hidden.
*
* @see IconPicker::valueCallback()
*/
public static function preRenderTextfield($element): array {
$element = parent::preRenderTextfield($element);
// Transform the value from Icon ID to icon class(es).
$value = $element['#value'] ?? '';
if (!empty($value)) {
$icons_info = static::getIconInfo($element);
$element['#attributes']['value'] = $icons_info[$value]['#icon_classes'] ?? '';
}
// Remove JS required validation as the original text field is hidden.
// This will not affect client-side validation which will still work.
unset($element['#attributes']['required']);
// Ensure the text field does not appear on page load, before they are
// replaced with the icon picker.
$element['#attributes']['class'][] = 'visually-hidden';
return $element;
}
}
<?php
namespace Drupal\icons_iconpicker\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\icons_iconpicker\Element\FontIconPickerElement;
/**
* Provides a "jQuery fontIconPicker" widget to help search and select icons.
*
* @FieldWidget(
* id = "font_icon_picker",
* label = @Translation("Font Icon Picker"),
* field_types = {
* "list_icon"
* },
* )
*
* @see https://fonticonpicker.github.io/
* @see FontIconPickerElement
*/
class FontIconPickerWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings(): array {
return [
'icon_picker_theme' => 'grey',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state): array {
$element['icon_picker_theme'] = [
'#type' => 'select',
'#title' => $this->t('Icon Picker Theme'),
'#default_value' => $this->getSetting('icon_picker_theme'),
'#required' => TRUE,
'#options' => array_combine(FontIconPickerElement::PICKER_THEMES, FontIconPickerElement::PICKER_THEMES),
];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary(): array {
$summary = [];
$summary[] = $this->t('Icon Picker Theme: %theme', ['%theme' => $this->getSetting('icon_picker_theme')]);
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
$element['value'] = $element + [
'#type' => 'font_icon_picker',
'#default_value' => $items[$delta]->value ?? NULL,
'#icon_picker_theme' => $this->getSetting('icon_picker_theme'),
];
return $element;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment