Feeds

Author

Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Dec 16 2020
Dec 16

As a developer I tend to be lazy. I'm always searching for tools or shortcuts to make my live more comfortable.

Recently I stumbled across a giving an example on how to use migrate instead of a custom update script to update existing entities. Having no use for it at this time I saved it in my "maybe useful later"-part of my brain ... and forgot about it.

However, last week I had to add an existing field to the form display of all Paragraphs types in a project. Additionally a new field should be added and configured. Because I didn't want to configure the fields manually (remember? I'm a lazy developer!) for all Paragraphs types (there are about 80 of them in this project) I normally would have written an update hook for this task. But then I remembered the tweet and thought "wouldn't it be also possible to update the configuration using migrate?".

What do we need?

The first part of the task is easy: to display an existing field in the form of an entity you simply drag it from the "hidden" section to the content section.

Paragraphs form display

After moving the field "Published" into the content section, I exported the configuration changes to see what happened and got the following result for core.entity_form_display.paragraph.text.default.yml:

Configuration changes in Paragraphs form display

So in my migration I have to replicate exactly this configuration change for all Paragraphs types.

Migrating form display settings

In the migration I need a source plugin for all available Paragraphs types first. Because I already made the necessary changes to the form display of Paragraphs type "Text" the source plugin also needs the possibility to exclude certain items (well, I eventually could have reverted the previous configuration changes and start over with a recent database backup, but ...).

  1. namespace Drupal\up_migrate\Plugin\migrate\source;

  2. use ArrayObject;

  3. use Drupal\Core\StringTranslation\StringTranslationTrait;

  4. use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;

  5. use Drupal\migrate\Plugin\MigrationInterface;

  6. use Drupal\paragraphs\Entity\ParagraphsType;

  7. /**

  8.  * Source plugin for ParagraphsType.

  9.  *

  10.  * @MigrateSource(

  11.  *   id = "up_paragraphs_type",

  12.  *   source_module = "up_migrate"

  13.  * )

  14.  */

  15. class UpParagraphsType extends SourcePluginBase {

  16.   use StringTranslationTrait {

  17.     t as t_original;

  18.   }

  19.   /**

  20.    * List of paragraphs types to exclude.

  21.    *

  22.    * @var array

  23.    */

  24.   protected $exclude = [];

  25.   /**

  26.    * List of paragraph types.

  27.    *

  28.    * @var array

  29.    */

  30.   protected $items = [];

  31.   /**

  32.    * {@inheritdoc}

  33.    */

  34.   protected function t($string, array $args = [], array $options = []) {
  35.     if (empty($options['context'])) {
  36.       $options['context'] = 'up_migrate';

  37.     }

  38.     return $this->t_original($string, $args, $options);

  39.   }

  40.   /**

  41.    * {@inheritdoc}

  42.    */

  43.   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
  44.     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);

  45.     if (isset($configuration['exclude'])) {
  46.       $this->exclude = $configuration['exclude'];

  47.     }

  48.   }

  49.   /**

  50.    * {@inheritdoc}

  51.    */

  52.   public function fields() {

  53.     return [

  54.       'id' => $this->t('ID'),

  55.       'label' => $this->t('Label'),

  56.     ];

  57.   }

  58.   /**

  59.    * {@inheritdoc}

  60.    */

  61.   public function getIds() {

  62.     $ids['id']['type'] = 'string';

  63.     return $ids;

  64.   }

  65.   /**

  66.    * Return a comma-separated list of paragraph type ids.

  67.    */

  68.   public function __toString() {

  69.     return implode(', ', array_column($this->items, 'id'));
  70.   }

  71.   /**

  72.    * {@inheritdoc}

  73.    */

  74.   protected function initializeIterator() {

  75.     $this->items = [];

  76.     $paragraphs_types = ParagraphsType::loadMultiple();

  77.     /** @var \Drupal\paragraphs\ParagraphsTypeInterface $paragraphs_type */

  78.     foreach ($paragraphs_types as $paragraphs_type) {

  79.       $this->items[$paragraphs_type->id()] = [

  80.         'id' => $paragraphs_type->id(),

  81.         'label' => $paragraphs_type->label(),

  82.       ];

  83.     }

  84.     if (!empty($this->exclude)) {
  85.     }

  86.     return (new ArrayObject($this->items))->getIterator();

  87.   }

  88.   /**

  89.    * {@inheritdoc}

  90.    */

  91.   public function count($refresh = FALSE) {
  92.     parent::count($this->items);
  93.   }

  94. }

As you can see in the gist above, the source plugin is very simple. It grabs a list of all available Paragraphs types and removes the types you would like to exclude.

The next step is to write a migration that updates the configuration for the form display.

  1. id: paragraphtypes_form_display__status

  2. label: Add status field to paragraph form display.

  3. source:

  4.   plugin: up_paragraphs_type

  5.   exclude:

  6.    - text

  7.   constants:

  8.     entity_type: paragraph

  9.     field_name: status

  10.     form_mode: default

  11.     options:

  12.       region: content

  13.       settings:

  14.         display_label: true

  15.       third_party_settings: {  }

  16.       type: boolean_checkbox

  17.       weight: 5

  18. process:

  19.   bundle: id

  20.   entity_type: constants/entity_type

  21.   field_name: constants/field_name

  22.   form_mode: constants/form_mode

  23.   options: constants/options

  24. destination:

  25.   plugin: component_entity_form_display

  26. migration_tags:

  27.  - up_paragraphstype

The migration uses the new source plugin "up_paragraphs_type" and excludes the Paragraphs type "text" from the list to process. In line 11..17 we set exactly the same display settings as in the screenshot showing the configuration changes made for the "Text" Paragraphs type.

In the "process" section the migration loops over the results from the source plugin, using only the returned ID from each Paragraphs type and otherwise the constants defined earlier. Since we would like to update the form display configuration, we choose the "component_entity_form_display" plugin as destination, which is kindly provided directly by Drupal Core.

After running the migration all Paragraphs types available on the site are configured to display the "Published" checkbox. Yeah!

What about new fields?

But what about the new field I needed to create? Basically the migration doesn't really differ from the one above. The only thing we need to add is an additional migration creating the field configuration for each Paragraphs type.

Let's say, we would like to create a text field named "Comment" for all Paragraphs types. Then you will need to create the field storage for this field using something like this:

Note: if you have created the field manually (like me for Paragraphs type "Text") you can skip this migration because the field storage is already existent.

To add the newly created field we need to create a field instance for each Paragraphs type. This can be done by using the migration destination "entity:field_config":

Simple, isn't it?

What's up next?

After seeing how simple it is to create and update field configuration some new ideas came to our mind. It should also be possible to create entity types containing all required fields and display configuration using migrate. This could be a great option to enable some additional features on a site simply by clicking on a button (disclaimer: of course you need to export the configuration and eventually do some more stuff).

But this is stuff for another blogpost ...

Nov 10 2020
Nov 10

What the craftsmen use for their workshops is the local development environment for our development team. And we want it to be nice and simple and cosy. After all, we have enough other things to think and puzzle about.

From MAMP/LAMP and Docker to DDEV

In the past, each of our developers had an own MAMP/LAMP setup on his machine. This was easy to set up, worked rather uncomplicated and was also fast. However, over time, more and more problems arose. Among other things, some projects required newer php versions or special server settings that were difficult to reproduce on the local computers. In addition, it became more and more difficult to switch quickly between projects, because you always had to adjust the local configuration of your system.

For this reason we switched to a Docker-based setup for local development. This made it quite easy for us to reproduce the specifics of the servers on which the websites we were responsible for were located. This way we were better protected against unpleasant surprises after deployments - after all, the local development environment was now almost identical to the live server.

Over time, however, disadvantages of our docker setup became more apparent. It was still quite complicated to set up a new project (you had to copy various files to the right places, overwrite settings, ...). There was also a big weakness compared to the old MAMP/LAMP setup: It was not possible to run multiple projects locally at the same time; at least not without manually overwriting some settings. This finally made us rethink our setup and look for better alternatives. This is how we finally came up with DDEV.

What is DDEV?

DDEV-Local - so the complete name - is an open source based tool for software development. It builds up a local web server with the corresponding software packages, through which code can be easily built, tested and deployed. DDEV is based on Docker, but simplifies the work for the development team enormously and offers additional extensions.

Everything for a simple working environment

With DDEV, the initial setup is easy and done in no time. With only three commands in the command line (git clone, composer install, ddev start) a project is now usually set up locally. Two arguments in particular have convinced us to switch to DDEV:

  1. Setting up a local project is wonderfully simple and fast.
  2. We can easily run multiple instances in parallel in the local environment.

As a Drupal agency we work with various customer sites and not every developer has always set up all projects on his machine. With DDEV we now have a significant time saving especially with the changing sprints and security updates. The actual killer feature for us, however, is the parallel operation. During the weekly update evenings we install the latest updates in all our (SLA) customer projects. If every project were to be processed sequentially, our team would hardly be able to manage the updates in one single evening. Thanks to DDEV, we can now easily work on these projects in parallel and don't have to start complex workarounds with Docker. This saves us an enormous amount of time and hopefully long night shifts in the future.

There are other pre-built things for DDEV that simplify the work. For example the command ddev share, which makes the local instance of the page available via a public URL. This is a great way to test local sites on other devices (like the smartphone, tablet).

At https://github.com/drud/ddev-contrib there are a lot of useful examples and templates to extend DDEV. So you can easily add Solr or Varnish to your project setup to be as close as possible to the production environment.

Our summary

DDEV is already simplifying our daily work enormously now that we are using it in the first projects. The development teams can use Docker functions quickly and easily. And another plus: DDEV is not limited to Drupal. So we can recommend it to anyone who works with php projects and also wants to set up different CMS locally.

Apr 12 2019
Apr 12

For us, the Paragraphs module is the holy grail of structured content creation.

With Paragraphs it is relatively uncomplicated to define prefabricated content elements that define the structure of the corresponding content. These can then simply be placed in the desired content by editors.

This allows editors to create clearly structured content without a lot of effort and to fall back on recurring content structures. The editors simply select the desired element from the list of available content elements, fill in the corresponding fields and the content is output in the specified structure. This is extremely helpful when creating more complicated page layouts where you want to use the same structures over and over again.

Is there a better way?

We have asked ourselves and our clients' editors how to further simplify and improve the input using Paragraphs, or what might interfere with the editorial process when using Paragraphs.

It has often been mentioned here that it is quite annoying to have to drag the content elements to the desired position after adding them. This is especially cumbersome for content with many content elements. It would be much better to be able to insert the element right at the corresponding position.

Fortunately, there is the Paragraphs Features module from the Thunder Core team. This module extends Paragraphs by inserting a button between each content element to open the modal dialog for adding more elements. 

Using these buttons, editors can insert the elements in the correct position as desired and do not have to move them first.

 

Beautiful. But is there an even better way?

Editors always add the same elements to certain content. The question arose whether these content elements could not be inserted preferentially, i.e. without first having to open the dialog.

In addition, some editors were disturbed by the large number of buttons: after each single content element a button for adding further elements appears. *sigh*

So we thought about how to make the buttons less annoying. 

Buttons you don't see don't interfere. Accordingly, with our new module Paragraphs Editor Enhancements, we have simply hidden the buttons. 

With some applications and tools (e.g. Apple Mail or Slack) the buttons for certain actions only become visible when you point the mouse over a certain area. Exactly this function has also been implemented for the buttons: only when you point the mouse over an existing content element, the buttons for adding become visible above and below the element.

In addition, the most important content elements should also be able to be inserted directly with a click. 

So we had to add two new buttons for the most important content elements to the button to open the dialog.

Editors are no longer disturbed by the large number of buttons and can simultaneously insert the most important content elements with just one click.

Great! But can you make it even much better?

Another often mentioned improvement suggestion concerned the dialog for adding content elements themselves.

If you have many elements to choose from, the dialog quickly becomes confusing. In addition, editors often find it difficult to find the right elements. On the basis of the title of a content element one cannot always immediately conclude on the actual purpose of the element and cannot imagine how the element is represented in the content.

With Paragraphs Editor Enhancements we have completely redesigned the dialog. We've added the ability to filter the list of content elements so editors can quickly find the desired elements through the title and description of the element.

In addition, the representation of the elements in the dialog has been revised. Each element shows the title and the description as well as an image (if uploaded in the element's configuration).
As an alternative to displaying the elements as tiles, you can also display the elements as a list.

As a bonus, you can create categories for content elements and assign the individual elements to one or more categories. 

This gives editors a faster and better overview of the available content elements and allows them to find the desired element more quickly and easily.

Whew. Do you have any more?

We wouldn't be undpaul if that was all.

Some time ago, one of our clients was faced with the problem of having to add the same elements in the same order over and over again. 

As a simple example, one can imagine this as follows: an introductory text followed by an image, then a two-column text and finally a text with an image.

For the editors, this meant that the corresponding content elements had to be inserted in the correct order for each individual content item. 

Wouldn't it be great if you could add the desired elements with just one click, so that the editorial staff could concentrate on entering the actual content?

The solution to this problem was the Paragraphs Sets module.

Paragraphs Sets are used to define groups of content elements. These groups can be added to the content just like the usual elements.

This saves editorial members the hassle of searching out and inserting individual elements and allows them to start entering the corresponding content directly.

Of course, Paragraphs Editor Enhancements and Paragraphs Sets also work together. The element groups of Paragraphs Sets are displayed in a separate category in the dialog and can also be found using the filter function. 

Jul 17 2013
Jul 17

When building sites for our customers we usually create some administrative views (like for content or user administration) to make it easier for editors to work with the site. For a little more user experience we modify these views (especially the exposed form providing the filters). One of these modifications is to create a "collapsible" filter dropdown.

The Mission

Think of a content administration view with filters for content type, status, author and so on. Normally the filter for the content type allows multiple selections and would look similar to the one in the image.

But we want the filter to act as a single dropdown that could be expanded to a multi-select list if the user wants to filter for several types.

The Solution

To achieve this we need a small custom module and alter the exposed form:

  1. /**

  2.  * Implements hook_form_FORM_ID_alter().

  3.  *

  4.  * Alter views exposed forms for collapsible filters.

  5.  */

  6. function MYMODULE_form_views_exposed_form_alter(&$form, &$form_state) {

  7.   if (empty($form_state['view']) || !in_array($form_state['view']->name, array('NAME_OF_VIEW', 'NAME_OF_VIEWS_DISPLAY'))) {
  8.     // We alter the exposed form of a single views display, so return if this is

  9.     // not the expected view.

  10.     return;

  11.   }

  12.   if (isset($form['type'])) {
  13.     // Add option to select all items (equals to resetting the filter).

  14.       'All' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('') : t('- Any -'),
  15.     );

  16.     $options += $form['type']['#options'];

  17.     // Change size of field based on number of options (max: 5 items).

  18.     if (count($options) <= 2) {
  19.       // Hide filter if there is only one option available (additional

  20.       // to "All").

  21.       $form['type']['#access'] = FALSE;

  22.     }

  23.     $form['type']['#options'] = $options;

  24.   }

  25.   // Alter multi-value dropdowns.

  26.   $form_multiple_selects = array();
  27.   foreach (element_children($form) as $element_name) {

  28.     if (isset($form[$element_name]['#type']) && $form[$element_name]['#type'] == 'select' && !empty($form[$element_name]['#multiple'])) {
  29.       $form_multiple_selects[$element_name] = array(
  30.         'size' => isset($form[$element_name]['#size']) ? $form[$element_name]['#size'] : 5,
  31.       );

  32.     }

  33.   }

  34.   if (count($form_multiple_selects)) {
  35.     $form['#attached'] += array(
  36.       'js' => array(),
  37.       'css' => array(),
  38.     );

  39.     // Attach custom javascript to the form.

  40.     $form['#attached']['js'][] = drupal_get_path('module', 'MYMODULE') . '/js/MYMODULE.admin.js';

  41.     $form['#attached']['js'][] = array(
  42.       'data' => array(
  43.         'collapsibleFilter' => array(
  44.           'multiple_selects' => $form_multiple_selects,

  45.         ),

  46.       ),

  47.       'type' => 'setting',

  48.     );

  49.   }

  50. }

  51. ?>

Unfortunately we have to do some more magic to avoid errors after selecting the new option "All". Because we manually added the option in the form-alter, Views does not know about it and would throw an error after selecting it. The simplest way to avoid it, is to remove the filter value before displaying the results:

  1. /**

  2.  * Implements hook_views_pre_view().

  3.  */

  4. function MYMODULE_views_pre_view(&$view, &$display_id, &$args) {

  5.   if (!in_array($view->name, array('NAME_OF_VIEW', 'NAME_OF_VIEWS_DISPLAY'))) {
  6.     return;

  7.   }

  8.   foreach (array('type') as $filter) {
  9.     if (!empty($_GET[$filter]) && (is_array($_GET[$filter])) && reset($_GET[$filter]) == 'All') {
  10.       // Remove the filter value because it is manually added and thus

  11.       // unknown to Views.

  12.       unset($_GET[$filter]);
  13.     }

  14.   }

  15. }

  16. ?>

Finally we add our JavaScript to append the "plus sign" to the dropdown and add the "collapse" functionality:

  1. (function($) {

  2.   /**

  3.    * Change multi-value dropdown to single-value dropdown and back (visually).

  4.    */

  5.   Drupal.behaviors.collapsibleFilterRewriteMultipleSelect = {

  6.     attach: function(context, settings) {

  7.       $.each(Drupal.settings.collapsibleFilter.multiple_selects, function(name, settings) {

  8.         $('select#edit-' + name)

  9.               .once('collapsible-filter-multiple-rewrite')

  10.               .each(function() {

  11.           var selectionCount = $('option:selected', $(this)).length;

  12.           if (selectionCount <= 1) {

  13.             // Set size of select to 1 if there is not more than 1 selected.

  14.             $(this).attr('size', 1);

  15.             // Remove attribute "multiple".

  16.             $(this).removeAttr('multiple');

  17.             // Set default option.

  18.             if (selectionCount === 0 || $(this).val() === 'All') {

  19.               $(this).val('All');

  20.             }

  21.             // Add link to expand the dropdown.

  22.             $expand = $('')
  23.                     .addClass('select-expander')

  24.                     .attr('href', '#')

  25.                     .attr('title', Drupal.t('Expand selection'))

  26.                     .html('[+]')

  27.                     .click(function() {

  28.                       // Get corresponding select element.

  29.                       $select = $(this)

  30.                               .parent('.form-type-select')

  31.                               .find('.collapsible-filter-multiple-rewrite-processed');

  32.                       // Expand element.

  33.                       $select.attr('size', settings.size)

  34.                               .attr('multiple', 'multiple');

  35.                       $(this).remove();

  36.                     })

  37.                     .appendTo($(this).parent());

  38.           }

  39.         });

  40.       });

  41.     }

  42.   };

  43. })(jQuery);

Result

After you have all this together, users will be able to choose whether to select a single type from a dropdown or multiple values from a list. If a user selected multiple values the filter automatically displays as select list. 

Mar 26 2013
Mar 26

Sometimes you would like to add a map to a node or block without the need for detailled configuration options. You simply want to display a map and be done with it.
Fortunately this is an easy task using Leaflet.

Say you have a value for the location and one for the country and would like to print this "address" in a map.
So you need to first install Leaflet and Geocoder and then use this function to generate the map:

  1. /**

  2.  * Generate a simple map with a location pointer.

  3.  *

  4.  * @param string $location

  5.  *   Location to use (for example the address).

  6.  * @param string $country

  7.  *   Name of the country to use.

  8.  *

  9.  * @return string

  10.  *   The rendered map.

  11.  */

  12. function mysimplemap_map_create($location, $country) {

  13.   $map = '';

  14.   // Join the address parts to something geocoder / google maps understands.

  15.   $address = sprintf('%s, %s', $location, $country);
  16.   // Try to create a geographic point out of the given location values.

  17.   if ($geo_point = geocoder('google', $address)) {

  18.     // Create a JSON equivalent to the point.

  19.     $geo_json = $geo_point->out('json');

  20.     // Get map implementation provided by http://drupal.org/project/leaflet_googlemaps.

  21.     $map = leaflet_map_get_info('google-maps-roadmap');

  22.     // Set initial zoom level.

  23.     $map['settings']['zoom'] = 16;

  24.    

  25.     // Decode the JSON string.

  26.     // Create settings for the map.

  27.     $map_features = array(
  28.         'type' => 'point',

  29.         'lon' => $geo_data->coordinates[0],

  30.         'lat' => $geo_data->coordinates[1],

  31.       ),

  32.     );

  33.     // Render the map with a fixed height of 250 pixels.

  34.     $map = leaflet_render_map($map, $features, '250px');

  35.   }

  36.   return $map;

  37. }

  38. ?>

Easy, isn't it?

Nov 26 2010
Nov 26

Webform is a great module to build simple to complex forms for users to fill out, but it has no built-in way to trigger events for Rules. Unfortunately the maintainer is not going to add this kind of functionality anytime in the near future.

Drupal wouldn't be Drupal if you couldn't get around this issue by writing a small module.

The following code triggers an event upon sending the webform, which can be used by Rules. This is useful if you would like to send an email or display a message when the webform is submitted or use any other kind of available action in the system. The current user as well as the webform node and the form data are delivered as a parameter to the executed rule.

Contents of webform_rules.module: Contents of webform_rules.rules.inc: array( 'label' => t('A webform has been submitted'), 'module' => 'Webform', 'arguments' => array( 'user' => array('type' => 'user', 'label' => t('User, who submitted the webform.')), 'node' => array('type' => 'node', 'label' => t('The webform node.')), 'data' => array('type' => 'data', 'label' => t('The submitted webform data.')), ), ), ); } ?>

After installing the module you will see a new entry in the list of available events when creating a new rule and you'll be able to use the webform data.

Download and test the module

If you found any errors while using the module or have support questions or think a new feature would be cool to be integrated use the issue queue of Webform Rules. This will reach a wider audience and increases the chance of an reaction ;).

Oct 30 2010
Oct 30

This tutorial shows a quick and easy way of creating an inline image gallery.

Our goal is to add an arbitrary number of images to content (say of the content type "page"). We want to be able to freely choose the position in the text for the images. On click we would like to display a magnified version of the image.

Required modules

Step 1: create Imagecache presets

First we create two ImageCache presets that define the thumbnail (which is visible in the text later) and a bigger version for the magnification effect.

The settings shown here are only sample values of course, and can be changed to your liking later.

Step 2: Adding a field to the content type

After we have defined the ImageCache presets, we can extent the desired content type. In our case I am using the content type page as an example.

Those are the example settings: every image uses its own directory inside sites/default/files and you can also give a seperate title to every image.

Now the settings for inserting the image into the content. In the image settings, there is the area "Insert" for this task, in which we can choose how to insert the image into the content. Since we are working with Colorbox, we choose the option "Colorbox" with our "Thumbnail" ImageCache preset.

To complete this, a few general settings for the new field are created. Among other things we can define here how many images can be inserted.

As a small addition you should also define to not render the values of the imagefield (Display Settings of the content type). Otherwise the images would be visible below the content for a second time.

Create content

After finishing the field settings, we can now create content and insert images into it. To do so you choose the desired image, optionally give it a title and then click the button "Insert" next to the image. In this way the HTML code for the image is inserted into the body text at the current cursor position.

As we have chosen in the image settings to use Colorbox, a link around the image is created in the same step as well as the classes that Colorbox needs.

Before saving be sure to choose an input format which does not remove the HTML elements and attributes. As a quick solution I have chosen "Full HTML", but you should not allow this format for every user on a live page.
It is more secure to define an additional input format for this purpose.

Result

If we save the content with the inserted images as now, the images are shown in the middle of the text. Clicking on the preview images opens a bigger version of the image with Colorbox and one can also step through all the images that are inserted into the text.

Aug 01 2010
Aug 01

With CSS3 the W3C has introduced a new goodie for design on the web: Media Queries. Until then stylesheets could only be called conditionally by using media types like "print" or "screen". Media Queries extends this to being able to use certain properties as a controlling mechanism if to load a style or not.

You could specify that a stylesheet is only loaded if the browser window has a maximum width of 800px. To do so, you simply extend the value of the media-attribute by the desired attribute and value when inserting the stylesheet. To get our example to work we need to use the following HTML-Code:

As an alternative you could also write it into the stylesheet directly: @media screen and (max-width: 800px) { ... } If you're working with Drupal (which you should), you can use the regular .info file of the theme you're using: stylesheets[screen and (max-width: 800px)][] = style.css

Writing the above HTML-Code () directly into page.tpl.php would be also possible, but this is not a clean way and thus not "the Drupal way". Stylesheets should be called from the .info file and most likely your regular stylesheet is already being called from there. Media Queries in Drupal can also be called in the function drupal_add_css().

Of course this is not limited to using the width of the browser window as an attribute. E.g. there are height, width, orientation, color and resolution. All allowed attributes and the correct way of using them can be found at Media Queries (Media Features.

To get a glimpse of how it works check out this very site, undpaul.de. As soon as the browser window gets resized to a width of less than 980px, the right sidebar jumps below the main content and the header and logo get smaller. When getting below 820px width, the header gets even smaller.

Another positive side effect comes for free: when viewing the site on mobile devices, it looks much more structured, because the right sidebar blocks are no longer taking up space in the main content.

I should mention one drawback: users of browsers that do not understand CSS3 (which are Internet Explorer up to version 8, Firefox < 3.x) cannot benefit from this feature. There is a script (css3-mediaqueries.js), which enables media queries for those browsers, but we haven't tested it yet. I also do not see it as best practice to load more JavaScript for every little feature.

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