Library Reference Field

Create a field that references a library to include on the page.

This is for a component library concept I'm working on. I would like to write html and attach a library to demo how a component can be used.

First, generate a field in a custom module, with a library_id string property. That will create FieldFormatter, FieldType, and FieldWidget plugins.

We need a way to get a list of libraries. I copied this code from Manage Libraries and stripped it down to 2 functions. It extends the core LibraryDiscovery service with use of library_handler and module_handler to get a list of libraries for enabled themes and modules. See the full class file for the overriden LibraryDiscovery.

src/LibraryDiscovery

/**
 * {@inheritdoc}
 */
public function getLibraries() {
  $libraries = [];
  foreach ($this->getEnabledExtensions() as $extension) {
    foreach ($this->getLibrariesByExtension($extension) as $library_name => $library_info) {
      $library_info['extension'] = $extension;
      $library_info['name'] = $library_name;
      $libraries[$library_info['extension'] . '/' . $library_info['name']] = $library_info;
    }
  }

  return $libraries;
}

/**
 * Returns enabled extensions.
 *
 * @return array
 *   List of enabled extension including core.
 */
protected function getEnabledExtensions() {
  $enabled_extensions = ['core'];

  $listing = new ExtensionDiscovery(DRUPAL_ROOT);
  $listing->setProfileDirectories([]);

  $extensions = $listing->scan('module', TRUE);
  $extensions += $listing->scan('profile', TRUE);
  $extensions += $listing->scan('theme', TRUE);

  foreach ($extensions as $extension) {
    $extension_name = $extension->getName();
    // Do not check libraries for disabled extensions.
    if ($extension->getType() == 'theme') {
      if (!$this->themeHandler->themeExists($extension_name)) {
        continue;
      }
    }
    elseif (!$this->moduleHandler->moduleExists($extension_name)) {
      continue;
    }
    $enabled_extensions[] = $extension_name;
  }

  return $enabled_extensions;
}

The getLibraries() function can be used to populate the field's select list.

src/Plugin/Field/FieldWidget/LibraryWidget

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state): array {
  $libraries = $this->libraryManagerDiscovery->getLibraries();

  $element['library_id'] = [
    '#type' => 'select',
    '#options' => array_merge(['' => '-none-'], array_combine(array_keys($libraries), array_keys($libraries))),
    '#default_value' => $items[$delta]->library_id ?? NULL
  ];
  
  ...

Now we can use the value to attach the library.

src/Plugin/Field/FieldFormatter/LibraryDefaultFormatter

public function viewElements(FieldItemListInterface $items, $langcode): array {
  $element = [];
  foreach ($items as $delta => $item) {
    if ($item->library_id) {
      $element['#attached']['library'][] = $item->library_id;
    }
  }
}

This is working! I can put rendered markup for an element into a text field, and load the referenced library to display it correctly. This worked for a dropbutton component.

One thing to note is that some libraries extend others and can't be called directly or else the dependencies won't load. Instead load the root library.

Tags
Libraries