Jun 29 2016
Jun 29

This one tripped me up on a recent Drupal 8 project.

Easy to miss when you're working in a development oriented environment with things like JavaScript preprocessing turned off.

A JavaScript file was being added just fine with aggregation turned off, but not getting added with it turned on.

While working on a Drupal 8 client project we were using our module's .libraries.yml file to add a custom plugin for Jquery Validation. Our plugin was using the Moment.js date library to add strict date checking so we could check for overflows. The default date validation in that plugin treats dates like 55/55/5555 as valid - because they are cast to valid JavaScript dates by the browser. We needed to detect overflows and report an error.

It was working all fine locally, but when I sent a Pull request, it didn't work in the Pull request environment (we have per pull-request environments).

After some head scratching I found the issue.

My libraries.yml definition looked like this:

moment_date:
  version: VERSION
  js:
    js/moment_date: {}
  dependencies:
    - clientside_validation/jquery.validate
    - mymodule/moment
    - core/modernizr

If you picked it, I've missed the .js suffix on the file name.

Locally I was working with developer optimised settings, so I had a settings.local.php with the following

$config['system.performance']['js']['preprocess'] = FALSE;

i.e. I was disabling JavaScript aggregation so I could rapidly iterate, something you'd normally do.

Problem was on the Pull Request environment JavaScript aggregation is turned on (as it should be).

And mysteriously this made a difference.

My libraries.yml file was just plain wrong, it should have been

moment_date:
  version: VERSION
  js:
    js/moment_date.js: {}
  dependencies:
    - clientside_validation/jquery.validate
    - mymodule/moment
    - core/modernizr

But with JavaScript aggregation turned off, my webserver was adding the file, sending moment_date.js when moment_date was requested - silently hiding the bug from me.

A tricky one, but one worth sharing.

Drupal 8 libraries JavaScript
Mar 23 2016
Mar 23

Drupal 8 logo

Views is in Drupal 8 core. We all know that by now. Twig is the new templating engine in Drupal 8. This we also know. But do we know how to interact programmatically with the first in order to theme a View using the second? Aside from overriding View templates like with any other subsystem, we have available a more powerful alternative in the form of Views plugins (Display, Style, Row and Field).

In this article, we are going to look at how we can create a custom Style plugin for Views in Drupal 8. We will use the Bootstrap tab markup as a goal and implement a tabbed output for our View results. In the View configuration, the Style settings will allow us to specify which field will be used as the tab navigation copy, leaving the rest of the fields shown in the respective tab panes. Basically, each View result will represent a tab – so this example is not suited for Views which have more than a few results. The main goal is to illustrate how we can create our own Views Style plugins in Drupal 8.

We will not cover the details on how you can use Bootstrap in your project. However, you can check out the documentation page on assets or even this article on how to make sure anonymous users can benefit from jQuery being loaded on the page. And if you want to see the code we write ahead of time, you can find it in this repository within the Demo module.

What Is the Style Plugin?

The Views Style plugin is the one responsible for rendering the listing. Notable examples of core Style plugins are Unformatted List, HTML List, Table or Grid. They are used by the Display plugin and they in turn use Row plugins that represent one item in the listing.

In Drupal 8, all Views plugin types are built using the new Plugin system and share some common functionality (they always extend from the same Views PluginBase).

Let’s now create our own such Style plugin that can be used by most Display types (Page, Block, etc) and which uses the Field row plugin.

The Bootstrap Tabs Style Plugin

The first step is to create our plugin class located in the Plugin/views/style folder of our module:

namespace Drupal\demo\Plugin\views\style;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\style\StylePluginBase;


class BootstrapTabs extends StylePluginBase {

  
  protected $usesRowPlugin = TRUE;

  
  protected $usesGrouping = FALSE;

  
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['tab_nav_field'] = array('default' => '');
    return $options;
  }

  
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    parent::buildOptionsForm($form, $form_state);
    $options = $this->displayHandler->getFieldLabels(TRUE);
    $form['tab_nav_field'] = array(
      '#title' => $this->t('The tab navigation field'),
      '#description' => $this->t('Select the field that will be used as the tab navigation. The rest of the fields will show up in the tab content.'),
      '#type' => 'select',
      '#default_value' => $this->options['tab_nav_field'],
      '#options' => $options,
    );
  }
}

The Drupal plugin type we are creating an instance of is ViewsStyle with some basic configuration passed in the annotation. Leaving aside the obvious ones, we have the theme and display_types keys that are worth mentioning. The first declares which theme function this Style plugin will use to render its data while the second declares which kinds of Display plugins this Style can be used by (in our case all Display types which don’t otherwise specify a custom type: normal). For more information on all the available annotation configuration for this plugin type, check out the Drupal\views\Annotation\ViewsStyle annotation class.

Using the two class properties, we declare that our Style uses row plugins but does not allow grouping. Make sure you check out the parent classes to learn more about what other options can be specified like this. For example, the class we are extending already declares that Views fields can be used with the Style plugin.

As mentioned before, using the two methods we create a plugin option and form element to be able to specify which field should act as the tab navigation. Using the current display handler ($this->displayHandler) we can load up all the available View fields the site builder has added to it. And this new form element will be available on the Style settings form:

Drupal 8 Style Plugins

Since we are extending from the StylePluginBase class, there is nothing more we need to do. For the markup output we can rely on the demo_bootstrap_tabs theme which receives the relevant variables from the executed View. If we want, we can override any of the render methods and add more variables, change the theme, or whatever we need. We are good with the defaults, especially since we will implement a preprocessor to handle the variables that the template receives.

The Theme

It’s time to define the demo_bootstrap_tabs theme as we normally do (inside our .module file):


function demo_theme($existing, $type, $theme, $path) {
  return array(
    'demo_bootstrap_tabs' => array(
      'variables' => array('view' => NULL, 'rows' => NULL),
      'path' => drupal_get_path('module', 'demo') . '/templates',
    ),
  );
}

The Style plugin passes the $view object and the resulting $rows by default to the template. It is up to the preprocessor to do a bit of handling of these variables (if needed) before they are sent to the template:


function template_preprocess_demo_bootstrap_tabs(&$variables) {
  $view = $variables['view'];
  $rows = $variables['rows'];
  $variables['nav'] = array();

   $field = $view->style_plugin->options['tab_nav_field'];
  if (!$field || !isset($view->field[$field])) {
    template_preprocess_views_view_unformatted($variables);
    return;
  }

  $nav = array();
  foreach ($rows as $id => $row) {
    $nav[$id] = array(
      '#theme' => 'views_view_field',
      '#view' => $view,
      '#field' => $view->field[$field],
      '#row' => $row['#row'],
    );
  }

  template_preprocess_views_view_unformatted($variables);
  $variables['nav'] = $nav;
}

So what’s happening here? First, we check the Style plugin options for the field name to be used (the one that was selected when configuring the View). If one is not there, we return, but not before doing a bit of default preprocessing that the template_preprocess_views_view_unformatted function already does well. So we delegate to it. Then, we loop through the Views results and build an array of content for our tab navigation. For this, we use the default Views views_view_field theme function to render the selected field. Finally, we pass this array to the template and also run the default preprocessor of the unformatted list style.

The Template

In Drupal 8 there are no more theme functions, everything is now handled in Twig templates. So let’s see how the demo-bootstrap-tabs.html.twig file looks like in our module’s templates folder:

<div>
    
    <ul class="nav nav-tabs" role="tablist">
        {% for tab in nav %}
            {% set active = '' %}
            {% if loop.index0 == 0 %}
                {% set active = 'active' %}
            {% endif %}
            <li role="presentation" class="{{ active }}"><a href="#tab-{{ loop.index0 }}" aria-controls="profile" role="tab" data-toggle="tab">{{ tab }}</a></li>
        {% endfor %}
    </ul>

    
    <div class="tab-content">
        {% for row in rows %}
            {% set active = '' %}
            {% if loop.index0 == 0 %}
                {% set active = 'active' %}
            {% endif %}
            <div role="tabpanel" class="tab-pane {{ active }}" id="tab-{{ loop.index0 }}">{{ row.content }}</div>
        {% endfor %}
    </div>
</div>

As you can see, this is the necessary markup for the Bootstrap tabs. It won’t work, of course, without making sure the relevant Bootstrap styles and script are loaded in your theme first.

The first thing we render are the tab navigation items (from our nav variable). While looping through this array, we also make use of the loop index value in order to default the first item as active and be able to target the tab content panes below using unique IDs. For the actual value of the items, we just print the render array we created in our preprocessor and Drupal takes care of rendering that. That being said, it is probably a good idea to make sure that the field you use here is relatively short, without a link and plain markup. Titles would probably work just fine. But this is a matter of configuring the View accordingly.

Below the navigation, we print the actual view rows, using the same loop index to default the first row as the active tab pane and identify them uniquely so the navigation above can control their visibility. As for the content, we print the entire row.content variable (which is prepared inside template_preprocess_views_view_unformatted) and which contains all the fields in our View. And if we want to not include the field we used for the navigation, we can just exclude that one from display in the View configuration. It will still appear in the navigation (because we explicitly print it there) but not in the main tab pane.

Conclusion

And there we have it. A Views Style plugin to output the View results as Bootstrap tabs. All we need now is to make sure the Bootstrap assets are loaded and simply configure our View to use the new Style plugin. Do keep in mind that this is not meant for Views with lots of results and it only serves as an example to demonstrate how to create Style plugins.

If you have questions, comments, or suggestions, please leave them below!

Daniel Sipos

Meet the author

Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.
Sep 04 2011
Sep 04

The AWS SDK for PHP Drupal integration project, that I maintain, provides releases that correspond to each release made by Amazon. Having releases that correspond to an upstream source is something that you see in Linux packaging systems and offers some interesting bonuses. In Linux distribution model individuals do not monitor upstream for changes, and packages are rebuilt and increment version regardless of changes to the packaging scripts themselves. Given that Drupal distributions mimic Linux distributions in many ways and even individual site builds I think the reasons behind making corresponding releases are interesting and worth consideration.

To clarify what releases corresponding to upstream releases means consider the following from the AWS SDK for PHP project page.

Since Drupal.org does not allow for three-part version numbers this project follows the AWS SDK for PHP 1.x development line and the release version mapping is as follows. The mapping shows the Drupal release to the Amazon release. Please ensure you are using a Drupal module and Amazon SDK pair that match the version number mapping. For example, use the 4.1 Drupal module with the 1.4.1 Amazon SDK.

  • 4.x -> 1.4.x
  • 3.x -> 1.3.x
  • 2.x -> 1.2.x

Making a release whenever upstream makes a release means that some releases may end up with little or no changes to the actual Drupal module. The AWS SDK module provides a Drush Make file that is updated each release to point to the matching upstream release, but that is the only change for some of the releases.

Making arbitrary releases may sound foolish at first, but consider some of the advantages.

  • Drupal site maintainers and distributions developers can receive update notifications from the standard Drupal core update system instead of having to monitor upstream for releases. Making it easier to watch for updates encourages keeping up-to-date with upstream changes which has implied benefits.
  • Any incompatibilities with a specific version of a third-party system or upstream library do not need to be kept track of since the integration project is always used with a specific upstream release.
  • New features or configuration options from an upstream source may be added immediately to the integration module without worry about incompatibility with older releases of the upstream source. Meaning if a configuration variable changes name or a new option is added it can be exposed through a Drupal UI without worry if people are using the appropriate upstream version (since they are always intended to work with a matching release).
  • When building a site or distribution using Drush Make specific versions of upstream libraries can be easily included using the corresponding Drupal project with a proper make file. If one wants AWS SDK 2.6 simply add projects[awssdk] = 2.6 instead of having to include the Drupal project and override any third party generic make script.
  • Handling of updates, when necessary, is much simplier since you always know the library version that was used with the previous release.

The general pattern that seems to emmerge is that upstream sources integrate better into Drupal workflow, tools, and infrastructure when matching releases are made.

It does not seem that any other (or few if they exist) third-party integrations or library integrations on drupal.org follow this workflow. Making corresponding releases may not be appropriate in all situations, but I think it warrants consideration and I am interested to hear thoughts on the subject.

Nov 20 2008
Nov 20

The front-end database is powered by Drupal. Includes a video demonstration.

[link]

Oct 15 2007
Oct 15

Schemes to add functionality to the web OPAC fall into four categories: web OPAC enhancements, web OPAC wrappers, web OPAC replacements, and integrated library system replacements. I’m outlining these four techniques in a report I’m editing for an OhioLINK strategic task force and a bit of a reality check on this categorization is desired, so if I’m missing anything big (conceptually or announcements of projects/products that fall into these categories), please let me know in the comments. Generally speaking, this list is ordered by cost/complexity to implement — from lowest to highest — as well as the ability to offer the described enhanced services from least likely to most likely.

Web OPAC enhancements are functions that are added to the existing web OPAC system. This most often entails additional product purchases from the automation vendor, such as the optional enhancements in WebPAC Pro for Millennium OPACs or content solutions in SirsiDynix. Enhancement can also be added through creative use of an existing web OPAC’s template functions, such as the method by which LibraryThing for Libraries can be added to OPAC displays.

Web OPAC wrappers use the existing web OPAC provided by the integrated library system as a source of information, but hide that information behind a completely new interface. The intervening system get that information from the integrated library system through a variety of mechanism. In some cases, it may be possible to use established protocols (such as Z39.50) or programming interfaces (such as an XML content server). In cases where such functionality is not available from the underlying integrated library system, a “screen-scraping HTML” technique may be required. 1

One example of such a wrapper is the work at Ann Arbor Public Library on SOPAC. Short for “Social OPAC,” SOPAC is “a set of social networking tools integrated into the AADL catalog [that] gives users the ability to rate, review, comment-on, and tag items.”2 It uses an open source content management system called Drupal as a structure through which the added functionality is provided. For example, when a user seeks the bibliographic information page for a catalog record, that request is made from the user’s browser to the Drupal software. The Drupal software in turn makes a request to the integrated library system for the bibliographic information it holds. The response from the ILS is parsed by the Drupal software for key information such as title, author, subjects, holdings, etc. This information is mixed with information stored in the Drupal database (ratings, tags, reviews, cover images, etc.) and a new web page is created and returned to the user’s browser.

Another example of a web OPAC wrapper is Scriblio (formerly called WPopac). Using the underlying framework of WordPress, Scriblio offers faceted browsing, tagging, and syndication feeds for the underlying Millennium WebOPAC. Scriblio is a project of Plymouth State University, supported in part by the Andrew W. Mellon Foundation. Both SOPAC and Scriblio are available under open source licenses.

Web OPAC replacements are new systems that completely replace the existing web OPAC. Unlike wrappers (which get their bibliographic data in real-time from the underlying web OPAC), these replacements operate on sets of records that are extracted from the ILS or come from another source. (In some cases, these replacements still rely on the underlying web OPAC as a source of item status information such as checked out status and due date.) The first notable OPAC replacement was at North Carolina State University when its library installed and configured the Endeca software to provide a faceted browse to the library catalog. By itself, an Endeca OPAC display does not enable tagging, annotation, or user aggregation services such as recommendation engines.  Other similar web OPAC replacements are Encore from Innovative Interfaces3, Primo from Ex Libris and Aquabrowser from Medialab Solutions. Miami University’s experiments with the open source Apache SOLR and the exported records from their Millennium system also fall into this category. Worldcat Local is also a form of web OPAC replacement noting that the source of bibliographic records is the OCLC Worldcat database rather than the local ILS.

ILS replacements offer the biggest opportunity for enhanced user services, particularly by adopting one of the open source solutions now available. At this time, neither of the open source solutions (Evergreen and Koha) offers more than faceted search and browsing. Unlike the commercial systems, however, the source code of the system can be modified to add these functions, and the modifications shared with other users of the same system.

[Update 20071015T1624 : Corrections made — and the text improved! — based on Betsy Graham’s comment. Thanks, Betsy!]

The text was modified to update a link from http://www.iii.com/mill/webopac.shtml to http://web.archive.org/web/20071015145500/http://iii.com/mill/webopac.shtml on January 20th, 2011.

The text was modified to update a link from http://www.iii.com/encore/main_index2.html to http://web.archive.org/web/20080328163000/http://www.iii.com/encore/main... on January 20th, 2011.

The text was modified to update a link from http://www.sirsidynix.com/Solutions/Products/portalsearch.php#content to http://web.archive.org/web/20071109170053/http://www.sirsidynix.com/Solu... on January 28th, 2011.

Footnotes

  1. Such a technique gets the information from the ILS using the existing web OPAC. Such schemes are generally fragile because changes to the underlying web OPAC can have detrimental affects on the content scraping process. []
  2. Blyberg, J. (2007). AADL.org Goes Social. blyberg.net. Retrieved October 12, 2007, from http://www.blyberg.net/2007/01/21/aadlorg-goes-social/ []
  3. As Betsy Graham, Vice President of Product Management at Innovative Interfaces, notes in the comments, the Encore will perform real-time queries to a Millennium ILS for bibliographic data, and in such cases the data extract is not needed. []
(This post was updated on 27-Jan-2011.)

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web