FLOSS Project Planets

Mario Hernandez: Managing image embeds with Drupal media

Planet Drupal - Sat, 2024-06-15 22:24

Allowing your content creators to embed images in text fields is a big risk if you don't have the right measures in place to get properly rendered images without jeopardizing your site's performance. We faced this issue first-hand with embedded images due to not using the right configuration and this lead to extremely large images being rendered. In this post I'll go over the techniques I took for addressing those issues and set up a system for image embeds that is solid and performant.

I started by writing a seven-part guide on how to setup responsive images. In this post I'll focus on image embeds. If you followed or read the responsive images guide, you should be able to take advantage of some of the work we did there in this post. The guidelines covered here include:

  • Defining requirements
  • Image styles
  • Media view modes
  • Text format configuration
Defining requirements

Before you write the first line of code or set the first drupal configuration for this issue, you need to have a clear understanding of your requirements. Here is a summary of my requirements:

  • Only certain user roles can embed images

    This means we need to figure out if the text formats used in our site will allow us to set the restrictions we need. Otherwise we may need to create or edit a text format for our target user roles.

  • Users need to be able to choose the image size and aspect ratio when embedding images

    We defined the image sizes and aspect ratios and assigned names that were user-friendly for non-technical users. We came up with name options we think our users will find easy to work with such as:

    • Small square, Small portrait, Small rectangular
    • Medium square, Medium portrait, Medium rectangular, Medium rectangular wide
    • Large square, Large rectangular, Large rectangular wide
    • Extra large square, Extra large rectangular, Extra large rectangular wide
  • If no option is selected by users, set a default image size

    For the default option when no selection is made by the user, we decided to use the Medium rectangular option. This has an aspect ratio of 3:2 and it measures about 720x480.

  • Existing Media items need to be available for embedding

    This was a tricky one because my original inclination was to create a new Media type so we can isolate all configuration for its view modes and not overpopulate our default Media type. However, this ended up not working for us because when you limit your image embeds to only use a new Media type, you don't get access to any of the media items (images), that have already been uploaded to the Media library using other media types. Ultimately we ended up using Drupal core's Media type, Image, and our dev team had to compromise on having a very busy list of view modes for this media type.

  • Images need the ability to be cropped wihin the Media page

    Since most of our images already provide the ability to be cropped at different aspect ratios, using the core Media type in the previous bullet point made this an easy solution.

Image styles

It all starts with image styles. I'm not going to go over how to create image styles, you can read my post Image styles in Drupal. The one thing I am going to repeat however is the importance of creating reusable image styles. Reusable image styles can help you reduce the number of image styles you create while providing the flexibility you need with each use case.

Image styles are key as each of the size options we defined above translate into image styles. So Small square for example, is an image style that is defined as 1:1 (250px). Medium rectangular would be something like 3:2 (720x480), etc. You may be wondering, how do you plan on using fiendly names for your content editors when your image styles names are not very friendly? Great question. Since we are using Drupal's Media, content editors do not interact directly with image styles, they do with Media view modes and this is where we will use more friendly names.

Media view modes

View modes are one of Drupal's powerful features. Being able to display content is different ways with little effort can turn a simple website into a dynamic content hub. The example I always give when someone asks me what view modes are or how do they work is the Amazon website. When you are viewing a product in amazon.com, or most retail websites for that matter, you will notice that the same product or similar ones appear all over the page but in slightly different ways, with different fields or styles. See the page below for an example.

The image above shows many ways in which a product can be displayed. I've numbered each display.

In Drupal, every entity such as content types, media types, blocks, etc., offer the ability to create view modes. For the purpose of image embeds, we will create a Media type view mode for each image style we plan on using. The view modes is what content editors will interact with when choosing an image size or aspect ratio during the image embed process. This is where we will use the user-friendly names we defined earlier. Let's go over how this relationship between view modes and image styles works for image embeds.

Configure view modes for the Image media type
  1. In your Drupal site, create an image style for each image size option you wish to provide to users when embedding images.

  2. Next, create a Media view mode for each image style (/admin/structure/display-modes/view). Very iimportant: Remember the view mode's label (name) is where we are going to use the friendly name (i.e. Medium rectangular (720x480)). I like to keep the machine name similar to the label so it's easier to debug or identify in code (i.e. medium_rectangular_720x480).

  3. Now, let's tie 1 & 2 together:

    • Go to the media type you plan on using for media embeds (/admin/structure/media/manage/image/display). I am using Drupal core's Image media type.
    • Scroll down and expand the Custom display settings fieldset.
    • Check each of the view modes you created in step 2 and click Save.
  4. Now click each of the view modes and update the image field to use the respective/matching image style.

Configure the text format

View modes and image styles are all configured. Now let's configure the Text format that authorized users will use to embed images.

  1. Go to the Text formats and editors page (/admin/config/content/formats)
  2. Click Configure next to the text format you plan on using (i.e. Full HTML)
  3. Ensure the right user roles are selected
  4. Within the Toolbar configuration section, drag the Drupal media button from the Available buttons options to the Active toolbar section. You could probably remove the original insert image button since you won't be using it.
  5. Scroll to the Enabled filters section and check the Embed media checkbox
  6. Scroll to the Filter settings section and set the following:
    • Default view mode: This is the default display that will be used if content editors don't pick an option when embedding images. Select any of the view modes that represents the image size you want to use as default.

    • Media types selectable in the Media Library: Select the Media type you plan on using. In my case is Image.

    • View modes selectable in the 'Edit media' dialog: Finally, select each of the view modes you created in the previous section. FYI: View modes will be sorted in alpha order by their machine name. In my case I had to prefix some of the machine names with either "a" or "b" so the list of options for the users to choose from would be nicely organized by their label name. See screnshot below.

    • Click Save configuration

Testing your configuration

Now that we've completed all the configuration we should be able to take it for test drive.

  • Go to any page where there is a text field with a WYSIWYG editor
  • Make sure you are using the right text format by selecting it at the bottom of the text field where you want to embed an image
  • Click the Insert media button from the editor's toolbar
  • Select or upload the image you'd like to embed
  • When the image has been inserted, click on it and you should see several options of actions you can do with the image. Things like align the image, add a caption, link it, and you should also see a selection box listing all the view modes you created.
  • After making your selection you should immediately see the image size/aspect ratio change to the one you selected. When you are happy with your selection, click the Save button to save your page.

Important: Depending on your site's configuration, the options for changing your image size may look different than mine. In my case, I am only using Drupal's core modules and this is how the options look for me:

In closing

Putting a system like this for your image embeds will give you the piece of mind that content editors have options to choose how big or small they would like images to be displayed, and from a performance point of view, if your image styles are done properly, you can rest assurred that bloated images will never be rendered because you have put the guard rails in place to avoid this from happening.

I hope you found this article useful and can put these techniques to use in your own Drupal project. Happy New Year! 🎉 🎊 🎆 👋

Categories: FLOSS Project Planets

Mario Hernandez: Responsive images, wrapping up

Planet Drupal - Sat, 2024-06-15 22:24

As far as image resolution switching, all the work we need to do is done. We will proceed to creating a quick Drupal view to display a list of news articles each of which will have a responsive image. When we finish with that, we will do the last pending thing, configuring responsive image styles using the <picture> element. You didn't forget about the picture element, did you?

Creating a new Drupal view for news articles

Out of the box Drupal already comes with a view that shows a list of content, but in the Olivero theme this view is not displayed the way we need it, so we will create our own view.

  1. From the admin toolbar, click, Structure | Views
  2. Click Add view
  3. Give the new view any name you wish (i.e. News, Latest News, etc.)
  4. In the View settings select to show Content of type Article
  5. In Page settings check Create a page
  6. Page title can be anything you wish
  7. Type news (lower case) as the path
  8. Under Page display settings chose Unformatted list of teasers
  9. Scroll and click Save and edit
  10. Scroll down and click Save

Two important things in the view above:

  • The path is the url where the content list will be available (i.e. https://my-site.com/news).
  • Using Teaser allows us to make sure of all the previous configuration we've done for responsive images.

With the view now in place, go to the new page we created, /news. This should show a list of news articles. Don't worry if the list does not look like the screenshot we showed earlier. The main thing is that you get a list of articles with images. The images in each article should reflect the configuration we setup for responsive images. You can check this by inspecting the images and you should see many of the attributes we configured. See example below.

  • First, the image is rendering using the img tag and not <picture>
  • The img tag uses the srcset and sizes attributes
  • We can see the diferent image styles we created

That's it! We have completed all the steps for managing responsive images using the resolution switching approach. Now we will complete this series by going back to creating a new responsive image style but this time using the <picture> element.

Responsive image style using art direction

I opted to leave this approach for last because it requires a couple of extra tools that are not part of Drupal core. As previously mentioned, art direction requires that each device gets an image cropped differently to ensure better user experience. Although Drupal provides a cropping "effect" within image styles, it is an automatic cropping process and cannot be customized for each image. This may not always work because we don't know what the focal point for each image we need cropped may need to be when trying to achieve art direction. For example, I may upload an image that I need to crop so the focal point is on the top-left corner, but next time I may upload an image where the focal point should be the bottom-right corner. Focal point refers to picking the area that is the most important to display specially on small devices.

What we need is to be able to manually crop each image we intend to do art direction with as we add them to the site. The good news is that within the Drupal echosystem we have many tools to choose from. In the interest of time, I'm going to simply discuss the tools you need to be able to manually crop your images on demand, but will not go into detail about their configuration or settings as those modules/tools have good documentation you can follow for proper configuration.

Here are the tools we will be working with:

  • Crop API: On its own it does nothing, but in combination with other tools like Image widget crop, it gives you the ability to crop images on demand.
  • Image widget crop: Provides the UI for content creators to be able to crop images on demand. It has a hard requirement of the Crop API module.
  • Cropper: A simple jQuery image cropping plugin for added cropping functionality.

Note: With the exception of the Crop API module, the other two tools above are optional or can be replaced by other similar tools/modules. Often times the modules you pick for a job depend on your unique requirements. So don't feel you need to use the tools above if you know of other equivalent ones that may do a better job.

Next key steps to complete

With your tools inplace, you will probably need to complete the following steps:

  • Creating crop types
  • Create image styles that match your images rendering requirements (aspect ratio, dimentions, crop settings, etc)
  • Adding a new effect to image styles that use "Manual crop"
  • Create new responsive image style using the <picture> element

Crop types: They are in principle similar to image styles. They allow us to setup parameters for how to crop images. In particular, they help us predefine aspect ratios for cropping images. We can create a Square crop type, 3:2 crop type, 16:9 crop type, etc. Important to note, crop types's main job is to define cropping parameters in images, they don't restrict you from manually picking the image focal point.

Image styles: Since we are going to be doing art direction, we will need image styles that allow for custom/manual cropping. We already know how to create image styles, the ones for art direction will follow the behavior of the hero image on this page. Notice how the main images are wide when viewed in a large screen, but if you resize your browser or view the site on a mobile device the images become narrow and their aspect ratio changes. See both examples below.

The examples above shows an image cropped differently for different device sizes. This is Art Direction and is achieved using the <picture> element.

Responsive image styles using <picture>

The biggest difference between creating responsive image styles for resolution switching and art direction is the "Breakpoint group" we select. Let's go through the process now:

  1. If you haven't already, enabled the Responsive image module
  2. Head over to /admin/config/media/responsive-image-style to begin creating a responsive image style
  3. Click Add responsive image style
  4. Type Banner as the label for the responsive image style. This name is solely based on the image examples above. Feel free to assign any name that makes sense to you.
  5. Select Olivero from the Breakpoint group dropdown. If you are using a different theme, choose that theme from the dropdown. We will discuss breakpoints in a bit.
  6. Scroll down and select a Fallback image style. Pick an image style that could be used on desktop and mobile if all failed with our responsive image style.
Breakpoints

Breakpoints are defined in your theme's <theme-name>.breakpoints.yml which is located in the root of your theme. For each device you wish to target you create a breakpoint. Here's an example (I modified the breakpoints slightly for better read):

olivero.sm: label: Small mediaQuery: 'all and (min-width: 500px)' weight: 0 multipliers: - 1x - 2x olivero.md: label: Medium mediaQuery: 'all and (min-width: 960px)' weight: 1 multipliers: - 1x olivero.lg: label: Large mediaQuery: 'all and (min-width: 1440px)' weight: 2 multipliers: - 1x

Based on the breakpoints found in *.breakpoints.yml, you should see an image similar to the one below which should reflect the same breakpoints.

  • Expand any of the breakpoints fieldset and you should see a familiar set of settings. Think of each fieldset you expand here as the <source> element inside the <picture> element. This is where we choose a list of image styles. Then we repeat the process for each breakpoint. This is where we are telling the browser which images to use for each device size and therefore setting up art direction.
  • The sizes field works the same as what we did in resolution switching.
  • Once you have completed all yoru configuration, save your changes.
What's next

Once the responsive image style above is ready to go, You can repeat the process of creating a new view mode for your Media image and configuring it to use the new responsive image style you created above (Banner).

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Responsive images and Media

Planet Drupal - Sat, 2024-06-15 22:24

Drupal's Media is a powerful tool and we will use it to improve the way we manage responsive images by using media types and view modes to our advantage when managing responsive images. The following quote comes straight from Drupal's Media module:

In Drupal, the core Media module manages the creation, editing, deletion, settings, and display of media entities. Media items are typically images, documents, slideshows, YouTube videos, tweets, Instagram photos, etc. Media entities are standard Drupal content entities. And are grouped by Media type bundles. Like content types they can have fields added, and their display can be managed via view modes.

Media in Drupal

Out of the box, Drupal ships with the following media types: Audio, Document, Image, Remote video, and Video. You can create new media types if needed. For example, you could create a new media type that is of type "Image". Why would you do this? Well, in some cases, you may have specific requirements to handle images of a particular entity and rather than modify the existing Image media type, you could create a new one so you can manage specific settings on the new type and don't run the risk of breaking something on your site by updating the default Image media type. Creating new media types is outside the scope of this post, but wanted to let you know what's available to you.

  1. Let's start by enableing the Media and Media Library modules as these are not enabled by default
  2. Navigate to /admin/structure/media
  3. Since we'll be working with images, click the Edit link to the right of the Image media type
  4. We won't get into all the wonders of Media types, instead let's jump into Manage display

Like any other Drupal entity bundle, Manage displays, also known as View modes, are a pretty powerful feature. So what are view modes? View modes is how we display content in different ways. Take a look at the image below. Each of the numbered marks is a view mode. Same content, different ways and different fields.

Media view modes

Now that we understand how view modes work, we will use them to manage/display our responsive images. Let's start by creating a new Media view mode.

  1. Within the Manage display tab for the Image media type, scroll down and expand the Custom display settings fieldset
  2. Click Manage view modes
  3. In the View modes screen scroll down to the Media section and click Add new Media view mode
  4. Type News listing as the view mode name.
  5. Click Save
  6. Go back to the Image media type Manage display screen (/admin/structure/media/manage/image/display)
  7. Scroll down and expand the Custom display settings fieldset
  8. Check News listing and click Save. This enables the newly created view mode. You should now see it under the Manage display tab for the Image media type.
Configure the new view mode

It's time to link the new view mode for the Image media type with the responsive image style we created.

  1. Withing the Manage display screen, click the News listing view mode we just created
  2. Change the Format of the image field to Responsive image. This allows us to pick a responsive image style of our choice.
  3. Click the gear icon to the right of the image field
  4. Under Responsive image style select Content grid. This is the responsive image style we created in the previous post.
  5. Link image to nothing
  6. Click Update
  7. Scroll to the bottom of the page and click Save
What did we just do?

We just put all the pieces together which will result in well-managed responsive images. The order we followed is:

  1. Image styles: These are several image templates that share similar attributes (i.e. aspect ratio).
  2. Responsive image style: This is a collection of image styles that fullfill a specific use case.
  3. Media type and view mode: We have specific requirements for news and event article images. View modes allow us to fullfill these requirements without affecting other image displays.
Where do we use the responsive images?

So image styles, responsive image styles and image view mode are all connected and working great (you'll have to take my word for it 😃). One thing though, what content in our site will take advantage of all the beutiful work we've done? How about news articles? Take another look at the screenshot below. That's what we will do with all the work we've done.

Configure the Article content type

Since we opted to use news article images as the content to work with, there is a little clean up we need to do first in the Article content type.

The image field that ships with the Article content type does not use Drupal's Media, it's a basic image field. This does not mean we can't use responsive images with this type of image field, we can, but we get more out of the Media type field (view modes for starters). In addition, with the media type image field, we can take advantage of media library, add more fields to the image media type, reuse images, and much more.

Remove the image field from Article content type

Warning: Removing the image field will result in loosing any images you may have uploaded using this field. Only do this if you are sure you don't need the images. I never tested deleting the image field on a site that already had images so I am not sure if Drupal will even let you delete it.

  1. From the admin toolbar, click Structure, Content types
  2. Click Manage fields to the right of the Article content type
  3. For the image field, click the dropdown under Operations and select Delete
  4. Click Delete again to confirm you want to delete the image field. The image field is now gone
Adding a new media reference field to the Article content type
  1. Still within the Manage fields for the Article content type
  2. Click Create a new field
  3. In the Add a new field dropdown, select Media which is located under the Reference section. Notice there is also an image field, but this is the same kind we just deleted. Media is what we want.
  4. Type Image as the label for the new field
  5. Click Save and configure
  6. Keep the Allowed number of values as Limited to 1 and click Save field settings
  7. Optional but always a good practice is to add Help text. (i.e. Upload or select an image for this article)
  8. Check the box if you want to make this a required field
  9. Reference method should be Default
  10. Check the Create referenced entities if they don't already exist checkbox
  11. For Media type check Image (extremely important)
  12. Click Save settings. A new Image field is now available but this time it's a Media reference field of type Image.
Arranging the field for content entry and content display

By default the new image field was added at the bottom of the list of fields. Let's move the field up. We will follow the same steps for the Manage form display (for when content is created), and Manage display (for when content is displayed on the page).

  1. Within the Manage form display tab scroll down until you find the new Image field
  2. Drag it up so it displays right under the Title field
  3. Click Save
  4. Repeat for Manage display
Configure responsive images for the new image field
  1. Still within the Article content type page, click Manage display
  2. Drupal by default provides 3 view modes for the Article content type: Default, RSS, Teaser, and Full content (not enabled by default). We can create as many new view modes as we want/need, but for this excersice we will use Teaser.
  3. Click Teaser from the list of view modes
  4. For the image field, make sure its Format is set to Rendered entity. Since the new Image field we added is of Media type, the image is an entity and we want to render it as such.
  5. Click the gear icon to the far right of the Image field
  6. Under View mode select News listing. This is the view mode we created for the Media image type field.
  7. Click Update then scroll down and click Save. That's it.
Displaying responsive images in news articles

Before we can see the responsive images, let's create a couple of news articles so we have content to work with. Go ahead and create a few news articles using the Article content type. Upload large images if you wish so we can see the work we've done in action.

In the next post, we will complete the configuration and display of responsive images.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Responsive image styles

Planet Drupal - Sat, 2024-06-15 22:24

In a nutshell, responsive image styles are a collection of image styles. It can be confusing because the similarities in their names, but responsive image styles are a bundle that holds one or more image styles.

What's the use of responsive image styles?

If you read the posts about the <picture> element as well as the one about srcset and sizes attributes, we discussed that whether you are doing art direction or resolution switching, we need to provide the browser with a collection of images to serve to different devices. In Drupal the way we provide the collection of images is by using responsive image styles.

Naming responsive image styles

In the previous post we went in detail about best practices for naming image styles. Properly naming responsive image styles is just as important but there are some differences in guidelines. While naming image styles is mostly based on the characteristics of the images (aspec ratio, orientation, dimensions), naming responsive image styles is typically based on their use case. Let's take a look at some examples.

Let's say we are building a photo gallery where we will use a series of images to display as a slider or photos grid. We may not be as concerned about the images aspect ratio or dimentions because we already have image styles in place to handle that. We're only interested on how the images will be used. In this example the use case is a Gallery. So a name for the responsive image style that makes sense to me would be Gallery or Photo gallery. Another example would be creating a responsive image style for your website's hero component. Regardless of what the images dimensions are, the responsive image style can be called Hero. Both of these examples are for very unique use cases, but there are also cases for more common type of responsive images styles such as a listing of news articles or events, a featured news article or a page for team member photos. In these cases, we can use names that we can reuse elsewhere. Something like Person listing, News full, Content grid, or 16:9 (Max 460px).

Back to hands-on exercises

We are going to create a new responsive image style in which we will make use of the image styles we created in the previous post. Let's pretend the images we are targeting will be used for displaying News and Events listings (Similar to this, and example below).

Most websites use patterns that they repeat across their sections or pages. The news listing example above is a pattern that can be used for not only displying news articles, but maybe also events, social media posts, and more. This is great because identifying these patterns can help us create better image styles and responsive image styles that are reusable. This is what I meant in the previous post when I said that if you are fortunate enough to have designs for your project, you will be able to identify these patterns right away. Let's keep this in mind as we get back into exercise mode.

Resolution switching

Here's where all of our knowledge about <picture> (art direction) and srcset and sizes (resolution switching) comes in handy. We'll start with resolution switching because art direction requires additional tooling and configuration.

In Drupal 8 we used the Picture and Breakpoints contrib modules to handle responsive images. Starting with Drupal 9, Drupal provides the "Responsive image" core module which means we don't need to install any contrib modules. Responsive image is not enabled by default.

  1. Enable the Responsive image core module (/admin/modules)
  2. Once enabled, head over to /admin/config/media/responsive-image-style to begin creating our first responsive image style
  3. Click Add responsive image style
  4. Type Content grid as the label for the responsive image style
  5. Select *Responsive image from the Breakpoint group dropdown
  6. Scroll down and select a Fallback image style (16:9 (Max 320px))
  7. Expand the 1x Viewport Sizing [] fieldset
  8. Under Type, chose Select multiple image styles and use the sizes attribute
  9. Under Sizes type the following: (max-width:640px) 100vw, 30vw (I'll explain shortly)
  10. Under Image styles select the image styles we created before
  11. Scroll down and click Save

Let's go over everything we just did

Since we are doing resolution switching and not art direction, we chose Responsive image from the Breakpoint group dropdown. Doing so presents to us the 1x Vieport Sizing [] screen with the following options:

  • Type: Again, since we are doing resolution switching, the obvious choice here is Select multiple image styles and use the sizes attribute. The other two options are irrelevant in this particular example.
  • Sizes: The Sizes option is where we tell the browser how big/small our images should be rendered in relation to the viewport being used. Depending on our goal, this field accepts a single value or a media query with some conditions. Let's say we wanted our images to always render at full width regardless of the device being used (like a Hero image for example), then the value for the Sizes field would be 100vw (100% the viewport width). In our case however, we want the image to display full width, but only if the viewport/device is not larger than 640px, otherwise, meaning if the viewport/device is larger than 640px, we want the image to display at 30% the viewport width. We could had also used a pixel-based value such as 400px for example.
  • Image styles: This is where we choose the image styles we want to make available for the browser to pick from. Think of this as the srcset attribute in the <img> tag.
  • Fallback image: We pick a fallback image in case all the above fails.

Very important: Remember in the Image resolution switching using srcset and sizes attributes post, our claim was that it's better to let the browser pick the best image possible as the browser is smarter than us? This is exactly what we are doing here. We are providing the browser with a collection of images to pick from. Each image provides its dimensions. Then we tell the browser how big/small we want the images to be rendered. One thing we are not doing is telling the browser which image to use, we let the browser make that decision. This is the complete opposite of what we do when we use <picture>. As a reminder, the method used above is what you would do in most of your images. This is like the default configuration for your responsive images. Only when you need to crop your images differently for each device size is when you would use the art direction approach.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Image styles in Drupal

Planet Drupal - Sat, 2024-06-15 22:24

Now that we've gone over some very important concepts of responsive images (art direction and resolution switching), it's time to transfer all that knowledge and put it all in practice in Drupal. One of the key pieces for achieving responsive images in Drupal is by using image styles. Image styles are how Drupal manages the way we crop and size images.

What are image styles?

Before we get to building image styles, let's go over what they are. Think of image styles as templates for cropping, scaling, converting, and sizing images. You can use these templates with any image on your site and as many times as you'd like. Thanks to image styles images will always render within the parameters we define.

A more real-world way of looking at image styles may be if you could imagine for a moment you have a couple of picture frames you'd like to use to hang some pictures in your house or office. One frame is 5x7, another is 4x6 and one last one is 8x10. The picture frames are Drupal's image styles.

So we have some picture frames and we have ordered several pictures from our favorite online photo printing service. There is one picture in particular I really love and I want to frame it using the 3 picture frames. So, although it is the same picture, I ordered different sizes of it (one for each frame size), and this will allow me to hang the 3 pictures in different sizes, aspect ratios and orientation. That in a nutshell are image styles.

Image styles best practices

Image styles are actually pretty easy to create but unfortunately because of this they can be misused or mismanaged. If not done properly, you may end up with a lot more image styles than you really need or image styles that are not well define and do no provide the outcome you are looking for. To avoid this, let's go over best practices for creating image styles which will result in less image styles to manage. Mind you, these are my best practices but I have to admit, they have worked very well for me.

Naming image styles

Have you heard the phrase "naming things is hard"? It's true. Unfortunately when it comes to image styles, if not named properly you can get yourself in a lot of trouble. Quick example, let's say I want to create an image style that I'd like to use on images of news articles that are displayed on the homepage. One might think a good name for the image style may be something like "Homepage news article images". It doesn't seem so bad but let me point out a few issues with this name:

  • The image style is limited to the homepage
  • It is limited to news article images
  • It lacks information about the image dimensions or aspect ratio

One objective with image styles is to create them in a way that they are reusable. The more reusable an image style is the less image styles you will need to create which in turn becomes easier to manage. The main issue with the image style above ("Homepage news article images"), besides the 3 bullet points we called out, is that is not reusable. The name of it limits us to only use it on the homepage and only for news article images. If we want to display similar images elsewhere, we would need to create another image style maybe with the same parameters as the first one. You may be asking yourself, wait, why can't we use the same image style elsewhere? Technically you can, but think about how confusing it will be to use an image style called "Homepage news article images", not on the homepage and not on news article images.

Creating reusable image styles

One very efficient way for creating reusable image styles is to name them based on the image aspect ratio or dimensions, or a combination of both. For example: "16:9 (Max 320px)", or "Box 1:1 (500px)". Here are some reasons why this is a great way to name image styles:

  • They are not specific to any page or type of image (articles, events, etc.)
  • They provide key information about the image aspect ratio and their dimensions
  • I can use these image styles hundreds of times on any image that fits the requirements as well as on any page
  • By creating/naming image styles this way, I may have just saved myself from creating many other image styles
Identifying the images patterns

I have found one of the most effective ways for identifyiing the image styles you need to create is by looking at your website mockups (if you are fortunate enough to have them). This may not always be possible, but if you do have designs for your website, this will tell you exactly which images you will need and how they need to be rendered. Having this information upfront will help you tremendously when creating image styles because you can plan ahead of time how to create reusable image styles that share commom attributes.

Image styles use cases

When naming image styles it helps me to think of the characteristics of the images I am creating image styles for. For example, I have an image that should be rendered in 16:9 aspect ratio and it should not exceed a width of 320px. This is how I arrived at the name 16:9 (Max 320px). This also makes it possible to know which image style to use if I have other images that need to be rendered similarly. By the way, it is perfectly okay to use an image style that is slightly off from what an image needs to be rendered at. For example, Let's say I have an image that should be rendered at 16:9 aspect ratio, but its size should not exceed 250px. for this image, I can still use the 16:9 (Max 320px) image style.

A 100px or even 200px difference between the image style dimensions and the image you need to use it on it's an acceptable thing to do for a couple of reasons:

  • 100 or 200px in most cases will not make a big of an impact in performance, however, if you are rendering 50 of these images in a single page, then this could certainly present performance issues. So my rule is as long as this is a oneoff type of situation, I'm okay with doing this.
  • Keep in mind that just because your image may be resized larger than it actually needs to be rendered, your image will still visually rendered at the right size as I would suppose it is inside a container that will be sized to the right rendering size, via CSS.
  • Being able to reuse an image style that may be slightly larger than needed saves me from creating more image styles.

I hope you see the impact good names for image styles have on your site. When you are working on an enterprise level website, using the best practices above can really help you with the maintenance of your image styles.

Image styles effects

Effects are the rules you set on each image style. Rules such as cropping, sizing, converting, saturating, rotating, and scaling of images is how we determine how to render the images in our site. In most cases, you want to let content creators of your site upload images that are relatively big. Doing so will allow you to use the images in your library in any use case. It is perfectly okay to scale your images down thorugh the use of image styles, but it is not recommended to scale images up. Doing so will result in blurry or pixelated images. This is why is better to upload large images. But you may be thinking, if I upload super large images, this will affect the performance of my site. It will if you are rendering the original images, but since we are using image styles, Drupal uses the original image to make copies at the size and aspect ratio you defined in your image styles. This is why by uploading a single image you are able to use it in many use cases at different sizes or aspect ratios.

Image styles effects can vary from image style to image style. For example, some image styles will require images to be scaled down, then cropped. Others will require images to be resized then cropped and others may just need for images to be resized to specific size. All these actions are called "Effects" in image styles. The effects you apply to your image styles will depend on the end goal for rendering the images. Let's do a quick demo of creating one image styles then applying effects to it.

Hands-on excercise

All the principles covered in this series apply to Drupal 8, 9, and 10. You can follow along or simply watch me do it. To get started, spin up a vanilla Drupal site of the version of your choice. At the begining of this series I mentioned we will stick with only core/out of the box functionality to keep things simple.

Creating a new image style
  1. Login to Drupal as administrator
  2. In your Drupal site navigate to /admin/config/media/image-styles
  3. Click Add image style
  4. For Image style name type: 16:9 (Max 320px)
  5. To keep things nifty, edit the image style machine name so it reads 16_9_max_320px. (Remove the trailing underscore generated by the ending parenthesis in the image style name. Clean machine names are important to me 😃. It actually makes a difference when you are debugging issues and you find your machine name has an extra underscore you didn't catch).
  6. Click Create new style

The image style above follows the best practices for name we covered earlier. This makes this image style reusable on any image that meets the aspect ratio and dimension requirements.

Adding effects to the image style

For the purpose of this exercise, we are going to use the Scale and crop effect. This is probably the effect I use the most because it does exactly what I want, scale the image down to the size I want and crop it in the aspect ratio I need.

  1. While in the page where the new image style was created (/admin/config/media/image-styles/manage/16_9_max_320px), scroll down and you should see the Effect dropdown
  2. Select Scale and crop from the dropdown
  3. Click Add. The Add Scale and Crop effect screen will come up
  4. Type 320 for width and 180 for height. Note: These two values are required when you select the scale and crop effect. In other effects, these values may not always be required. It is important to define fixed dimensions on your image styles. This ensures your images will be sized/cropped at exactly the size you expect them to. How did I figure out the height for a 16:9 image with a width of 320px is 180px? I used this online aspect ratio calculator.
  5. Notice how you can change the focal point of the cropping by clicking any of the circles under Anchor. For this example we'll keep it in the middle circle.
  6. Click Update effect. This will bring you back to the image style page.
  7. We're done!

Now we have one custom image style with specific effects. If you noticed, Drupal comes with a couple of predefined image styles. If they work for your images you should make use of them. In this series we will be creating custom image styles.

As I mentioned earlier, names of image styles should be descriptive but not limiting. In the case of some of the image styles that come out of the box with Drupal, the names Large, Medium, and Wide do not seem like good choices for names because those names are all relative. Large in relation to what? Medium in relation to what? ...just sayin'.

Image multipliers

One thing we have not discussed but it is very important in responsive images, is the use of "Image Multipliers". What are image multipliers? In responsive images, you often think of image sizes in natural dimensions. If I think of an image that needs to be rendered at 720px, I will most likely resize or crop that image at 720px which makes total sense. With the evolution of high density or high resolution (retina) screens on mobile and larger devices, because they contain thousands or millions more pixels than traditional resolution screens, images need to actually be bigger than the intended size so they are rendered at their highest resolution/quality. This is what image multipliers are.

If we go back to the example above of the 720px image. For this image to be rendered as sharp and high-quality as possible in retina screen devices, we should add a 2x or 3x multiplier to it. Meaning, we should create an image styles for this image at twice and three times the intended size (1440px, 2160px). When we do this, the image will still be rendered at 720px (visually), but because we are providing larger images, these images will have twice or three times the number of pixels within them and when viewed in high resolution screens, the image quality will be superior than if we are only providing a regular 720px image. Note: I typically only create a 2x multiplier image styles for most of my images, but there may be situation when there are specific requirements for creating a 3x multiplier image styles.

Create a 2x multiplier image style

The same way you created the original image style above for 16:9 (Max 320px), go ahead and repeat the process but this time create a 2x multiplier image style, 16:9 (Max 640px). Remember, the dimensions of this image style should be 640 x 360px.

So what's next?

With our custom image styles in place, we can now make use of them, but before we do, let's go over another very important concept within Drupal, Responsive image styles ...whaaaaatttt?
We'll comeback to Drupal in a bit but first, we'll talk about responsive image styles in the next post.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Image resolution switching using srcset and sizes attributes

Planet Drupal - Sat, 2024-06-15 22:24

In the previous article we defined what art direction is and how to address it using the <picture> element. In this post, the focus will be how to address responsive images when the requirement is image resolution switching. Resolution switching, in the context of responsive images, is rendering identical image content on all devices. Unlike art direction where each device gets a differently cropped image that may vary on aspect ratio, resolution switching uses images that are simply larger or smaller based on the device but retain the same aspect ratio and cropping settings. Resolution switching is how most images are rendered (the rule), the <picture> element approach is the exception to the rule. Take a look at an example of resolution switching below.

The image above demonstrate how multiple resolutions of the same image can be served to different devices. All the images in the example above are cropped exactly the same maintaining the same aspect ratio from large to small.

Using srcset and sizes attributes

Using the srcset and sizes image attributes is how most images are rendered in the web today. As indicated before, this is the recommended way for configuring responsive images if all you need is to switch resolution of images rather than art direction. So how does this approach work? Let's take a look at a typical configuration of the <img> tag using the image above as an example of the different image sizes we will want the browser to choose from:

<img srcset="original-image.jpg 2400w, extra-large.jpg 2000w, large.jpg 1600w, medium.jpg 1080w, small.jpg 800w, x-small.jpg 500w" sizes="100vw" src="large.jpg" alt="Image of sky shown at different resolutions" />

Let's break things down so we can understand this approach better.

  • <img>: Right off the bat we start by using a widely supported html tag.
  • srcset: The srcset attribute in the img tag serves two important roles, 1) It stores a list of images that can be used by the browser, 2) Each image provides its width value which plays a role on the browser choosing the right image.
  • sizes: The sizes attribute tells the browser the width, in relation to the viewport, the image should be rendered at. The value of 100vw shown above, means the image will be rendered at 100% the viewport width on all the devices. You could also use media queries like (max-width: 720px) 100vw, 50vw. This means that if the device does not exceed 720px in width, the image will be rendered at 100% the viewport width, otherwise (if the device is larger than 720px), the image will be rendered at 50% the viewport width.
  • src: The src attribute is used as a fallback if everything fails.
What does it all mean?

Let me explain things in more detail because it is important we understand how this approach is so much better than using the <picture> element.

The biggest difference/advantage of using srcset and sizes versus <picture>, is the fact that we let the browser decide which image is the best image to render on any device. This is possible thanks to all the information we have supplied to the browser. For example, in the srcset we are not only providing the browser with a list of images to choose from, but we are also telling the browser how big each image is. This is very important because the browser will use this information when choosing the image to render. In the <picture> element approach, the image size descriptors are not available.

The sizes value tells the browser the size the image needs to be rendered at in relation to the viewport. This too is extremely important information we are providing the browser because if the browser knows the dimensions of all the images to choose from and how big/small the image needs to be rendered, then the browser is able to pick the best image possible.

But that's not all, the browser is smarter and knows more about the web environment than we do when a page or image is rendered. For example, the browser knows the viewport width used when viewing a website, it knows how fast/slow your internet connection is, and it knows about any browser preference settings (if any), setup by the user. Using all this information the browser is able to determine which image from the srcset is the best to use. In contrast, with the <picture> element, we tell the browser which image to use solely based on the device size.

Closing the gap

Now let's see how using the srcset and sizes attributes closes the gap we identified when using the <picture> tag.

Environment conditions What the developer knows
during development What the browser knows
during image rendering Viewport dimensions No Yes Image size relative to the viewport Yes No Yes via sizes Screen density No Yes Images dimensions Yes No Yes via srcset

Pretty nice huh? Now thanks to the srcset and sizes attributes we've closed the gap and the browser has all the information it needs to ensure the best image is served to each device.

The next post of this series will focus on image styles. These are fun but can also get you in a lot of trouble if not properly done. See you there.

In closing

Time for a story: I recently did an experiment that 100% proves the use of resolution switching using srcset and sizes attributes. As most people nowadays, I use a very large second display when working on projects to fit more apps and see things better. My second display is nice but it's not a 4K display. It's double the physical size of my mac's screen, but the mac's screen resolution is higher by almost double (twice the number of pixels). When I look at an image of a project where I've implemented the practices in this guide, in the large display, and inspected the page, I see the browser has selected an image that is 720px which makes complete sense for the use case I am testing. I then unplugged the second display and viewed the page on my mac's screen (higher resolution), I reloaded the page and inspected it, I noticed the browser has now selected an image that is double the size of the first image. This is exactly the behavior I would expect because my mac screen is of higher resolution and my connection speed is very fast. So the browser was able to make the smart decision to use a different images based on my environment.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Art Direction using the picture HTML element

Planet Drupal - Sat, 2024-06-15 22:24

In the previous article of this guide we covered the concept of responsive images and some of the challenges that come with implementing an effective system for them. In this article we will go in detail about the concept of "Art Direction" and how this applies to responsive images.

What is art direction?

In the context of responsive images, art direction is the ability to display differently-cropped images based on the device size. For example, a large landscape shot of a person rowing in the middle of a lake is shown when viewed on a large desktop device. If we were to use the same image on a mobile device, that image would shrunk down, making the person in the image very small and hard to see. A better option would be to show a different version of the image that zooms in and focuses on the most important part of the image, the person rowing. See an example of this image below.

Enter the <picture> HTML element

In order to achieve art direction we need to be able to query for the size of the device being used to view the website. Once we've identified the device size we instruct the browser which image to use based on the device size. This will allow us to provide a better user experience as each device will display an image intended specifically for that device. Going back to the image above, we can see that the main image has been cropped differently to ensure the most important part of the image is displayed on each divice.

So how do we query for the device size and how do we instruct the browser which image to use? This is where the <picture> element/tag comes in. Let's take a look at the code that makes all this possible and break it down.

<picture> <source media="(min-width: 2400px)" srcset="images/rowing-2400.jpg 1x, images/rowing-4800.jpg 2x" type="image/webp"> <source media="(min-width: 1280px)" srcset="images/rowing-1400.jpg 1x, images/rowing-2800.jpg 2x" type="image/webp"> <source media="(min-width: 640px) and (max-width: 1279px)" srcset="images/rowing-1200.jpg 1x, images/rowing-2400.jpg 2x" type="images/webp"> <img src="images/rowing-1200.jpg" srcset="images/rowing-2400.jpg 2x" alt="Person rowing on a lake" width="1200" height="800"> </picture>

Note: The order in which the media queries are written within the <picture> tag matters. The browser will use the first match it finds even if it's not the intended one. Therefore, consider the media query order very carefully to ensure the right image is served.

  • <picture>: The <picture> tag is simply a wrapper. On its own it does not do anything.
  • <source>: The <picture> HTML element contains zero or more <source> elements. The browser will consider each child <source> element and choose the best match among them. If no matches are found—or the browser doesn't support the <picture> element—the URL of the <img> element's src attribute is selected. The selected image is then presented in the space occupied by the <img> element.
  • Within the <source> element, you will find some very handy attributes (media, srcset, and type):
    • media: Rembember earlier we said we need to query for the device size? Well, within the media attribute you can write media queries much like the media queries you write in CSS (media="(min-width: 600px)"). This is how we check the size of the device when a page is rendered.
    • srcset: This attribute allows us to provide a list of images the browser can use when the media query finds a match (srcset="img-768.jpg, img-1440.jpg").
    • type: The type attribute specifies a MIME type for the resource URL(s). This is optional if using common image types such as JPG, PNG, TIFF, etc. If you plan on providing images in different file formats, you can do so using the type attribute. This is handy in the event the browser does not support a specific file type (type="image/avif"), as you can then provide a supported file type.
  • <img>: The img element serves two purposes:
    • It describes the dimensions of the image and its presentation
    • It provides a fallback in case none of the offered <source> elements are able to provide a usable image.

And there you have it. The <picture> element is a great way to serve different images based on things like device size or screen density. When the <picture> element was first introduced it required a pollyfill as not all browsers supported it. Nowadays, unless you are supporting Internet Explorer 11 (sorry bro), all other major browsers provide native support for it. Take a look at the chart below for current browser support.

Great! Let's use <picture> on all our images ...NOOOOOOOO!!!!!!

Say what? If the <picture> element is so great, why can't we use it for rendering all of our images? Well, as great as the <picture> element is, it should not be the default solution for serving responsive images in your site. The only use case for the <picture> element is when you are trying to achieve "Art Direction" (cropping your images differently for each device size).

Remember at the begining of this post when I said "In order to achieve art direction we need to be able to query for the device size. Once we've identified the device size we instruct the browser which image to use..."? There lies the problem. Let me explain.

The issue with the statement above is that "we are telling the browser which image". Not only that, but we are doing so solely based on the size of the device. This may not always be the best way to determine which image a device should use. Imagine you are using a nice relatively new laptop with a super high density screen. Based on our rules established within the <picture> element code snippet above, we would end up with an image that is 4800px in size. This is a pretty large image but it's the one that meets our creteria defined in the media query above. If you're home with a decent wifi connection you will never see any issue loading an image this large, but imagine you are working out of a coffee shop, or at a conference with poor wifi connection, or worse yet, you're on the road using your phone as a hotspot and your signal is very bad, now you will really experience some performance issues because we are telling the browser to load the largest image possible because your computer screen is big (relatively speaking). With the <picture> element we can't check how fast your internet connection is, or whether there are browser preferences a user has configured to account for slow internet speeds. We are basing everything on the size of the device.

Then why use the picture element? Well, when developing a website, the developer does not have all the information they need to serve the best image. Likewise, when rendering a page and using the <picture> tag, the browser does not know everything about the environment. The table below shows this in more detail and exposes the gap between the developer and the browser.

Identifying the gap when using <picture> Environment conditions What the developer knows
during development What the browser knows
during image rendering Viewport dimensions No Yes Image size relative to the viewport Yes No Screen density No Yes Images dimensions Yes No

You may be wondering: "Why did you get us all excited about the <picture> element if we can't really use it?" well, if you are trying to achieve art direction, then you use the <picture> element. It's the recommended approach for that use case. If you are looking for resolution switching, a use case for most images in the web, you need to use the srcset and sizes attributes approach. In the next post we'll dive deep into this technique.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Responsive images in Drupal - a guide

Planet Drupal - Sat, 2024-06-15 22:24

Images are an essential part of a website. They enhance the appeal of the site and make the user experience a more pleasant one. The challenge is finding the balance between enhancing the look of your website through the use of images and not jeopardizing performance. In this guide, we'll dig deep into how to find that balance by going over knowledge, techniques and practices that will provide you with a solid understanding of the best way to serve images to your visitors using the latest technologies and taking advantage of the advances of web browsers in recent years.

Hi, I hope you are ready to dig into responsive images. This is a seven-part guide that will cover everything you need to know about responsive images and how to manage them in a Drupal site. Although the excercises in this guide are Drupal-specific, the core principles of responsive images apply to any platform you use to build your sites.

Where do we start?

Choosing Drupal as your CMS is a great place to start. Drupal has always been ahead of the game when it comes to managing images by providing features such as image compression, image styles, responsive images styles and media library to mention a few. All these features, and more, come out of the box in Drupal. In fact, most of what we will cover in this guide will be solely out of the box Drupal features. We may touch on third party or contrib techniques or tools but only to let you know what's available not as a hard requirement for managing images in Drupal.

It is important to become well-versed with the tools available in Drupal for managing images. Only then you will be able to make the most of those tools. Don't worry though, this guide will provide you with a lot of knowledge about all the pieces that take part in building a solid system for managing and serving responsive images.

Let's start by breaking down the topics this guide will cover:

  1. What are responsive images?
  2. Art Direction using the <picture> HTML element
  3. Image resolution switching using srcset and sizes attributes
  4. Image styles and Responsive image styles in Drupal
  5. Responsive images and Media
  6. Responsive images, wrapping up
What are responsive images?

A responsive image is one whose dimensions adjust to changes in screen resolutions. The concept of responsive images is one that developers and designers have been strugling with ever since Ethan Marcotte published his famous blog post, Responsive Web Design, back in 2010 followed by his book of the same title. The concept itself is pretty straight forward, serve the right image to any device type based on various factors such as screen resolution, internet speed, device orientation, viewport size, and others. The technique for achieving this concept is not as easy. I can honestly say that over 10 years after reponsive images were introduced, we are still trying to figure out the best way to render images that are responsive. Read more about responsive images.

So if the concept of responsive images is so simple, why don't we have one standard for effectively implementing it? Well, images are complicated. They bring with them all sorts of issues that can negatively impact a website if not properly handled. Some of these issues include: Resolution, file size or weight, file type, bandwidth demands, browser support, and more.

Some of these issues have been resolved by fast internet speeds available nowadays, better browser support for file tyes such as webp, as well as excellent image compression technologies. However, there are still some issues that will probably never go away and that's what makes this topic so complicated. One issue in particular is using poorly compressed images that are extremely big in file size. Unfortunately often times this is at the hands of people who lack the knowledge of creating images that are light in weight and properly compressed. So it's up to us, developers, to anticipate the problems and proactively address them.

Ways to improve image files for your website

If you are responsible for creating or working with images in an image editor such as Photoshop, Illustrator, GIMP, and others, you have great tools at your disposal to ensure your images are optimized and sized properly. You can play around with the image quality scale as you export your images and ensure they are not bigger than they need to be. There are many other tools that can help you with compression. One little tool I've been using for years is this little app called ImageOptim, which allows you to drop in your images in it and it compresses them saving you some file size and improving compression.

Depending on your requirements and environment, you could also look at using different file types for your images. One highly recommended image type is webp. With the ability to do lossless and lossy compression, webp provides significant improvements in file sizes while still maintaining your images high quality. The browser support for webp is excellent as it is supported by all major browsers, but do some research prior to start using it as there are some hosting platforms that do not support webp.

To give you an example of how good webp is, the image in the header of this blog post was originally exported from Photoshop as a .JPG, which resulted in a 317KB file size. This is not bad at all, but then I ran the image through the ImageOptim app and the file size was reduced to 120KB. That's a 62% file size reduction. Then I exported the same image from Photoshop but this time in .webp format and the file size became 93KB. That's 71% in file size reduction compared to the original JPG version.

A must have CSS rule in your project

By now it should be clear that the goal for serving images on any website is doing it by using the responsive images approach. The way you implement responsive images on your site may vary depending on your platform, available tools, and skillset. Regardless, the following CSS rule should always be available within your project base CSS styles and should apply to all images on your site:

img { display: block; max-width: 100%; }

Easy right? That's it, we're done 😃

The CSS rule above will in fact make your images responsive (images will automatically adapt to the width of their containers/viewport). This rule should be added to your website's base styles so every image in your website becomes responsive by default. However, this should not be the extend of your responsive images solution. Although your images will be responsive with the CSS rule above, this does not address image compression nor optimization and this will result in performance issues if you are dealing with extremly large file sizes. Take a look at this example where the rule above is being used. Resize your browser to any width including super small to simulate a mobile device. Notice how the image automatically adapts to the width of the browser. Here's the problem though, the image in this example measures 5760x3840 pixels and it weights 6.7 MB. This means, even if your browser width is super narrow, and the image is resized to a very small visual size, you are still loading an image that is 6.7 MB in weight. No good 👎

In the next post of this series, we will begin the process of implementing a solution for handling responsive images the right way.

Navigate posts within this series

Categories: FLOSS Project Planets

Mario Hernandez: Drupal base path

Planet Drupal - Sat, 2024-06-15 22:24

Recently I was building a component that required a static image which was not stored in the database but instead needed to be stored somewhere in the file system of the site. There are several ways for serving a static image for example we could have stored the image in the sites/default/files/images/ directory. A very common approach which in many cases would work just fine, however, in my case I was building a component and I wanted for the component image to be located within the same component's directory. This makes sense because if I wanted to reuse or share this component, all component assets would be included in a single directory.

Requirements

My goal with this task was to dynamically point to the image regardless the site this component was running on. Oh yeah, we are running a multi-site architecture with hundreds of sites and a single code base. So this component needed to work in all of these hundreds of sites. Now the challenge seems a little more... "challenging".

¡Manos a la hobra'!'

I started by doing research to determine the best way possible to achieve this. I read about using a pre-process function that would generate a dynamic base path of the site but I was hoping I could keep things simple and do everything on the front-end with only Twig. This would make it a more appealing approach for front-end developers.

After some research, I came across two little gems that became game-chargers for my project. One of these gems is the {{ url('<front>') }} Twig function. This will provide the current site's homepage/base path. The other very handy Twig function is {{ active_theme_path() }} which prints the path of the current active theme (themes/custom/my_theme). While researching for this task, I also found you can use the {{ directory }} Twig variable in your theme's templates to print the active theme's path. A word of coution when using either the {{ active_theme_path() }} function of the {{ directory }} variable as these could have different results depending on your whether you are using them in a base or sub theme. Here's a drupal.org issue that discusses this in more detail.
Armed with these two little functions, and one Twig variable, we can now work in generating a dynamic path to our theme's directory where the static image for our component is located. So this may seem like a simple thing but remember, our component's image should work regardless of the site the component is used on within our multi-site architecture. Some sites even use a different sub-theme but the parent theme is always the same which is where our image is stored.

Building the dynamic path

Before we can use the first function we need to run it through the |render Twig filter. Since Twig will return an array from the {{ url() }} function, we need to convert it to a string because we need the value of the function. Let's take a look:

{{ url('<front>')|render }} # This will give us http://my-site.com/

Next let's work with the theme path function. Similarly to the function above, we will use the |render Twig filter to convert it from an array to a string.

{{ active_theme_path()|render }} # This will give us themes/custom/my-theme

Now that we have two strings we can joint them together to compose the full path to our image:

<img src="{{ url('<front>')|render }}{{ active_theme_path()|render }}/images/image.jpg" alt="alt text" />

If we want to get fancy we could actually set a variable to shorten things a bit:

{% set theme_url = url('<front>')|render ~ active_theme_path()|render %} <img src="{{ theme_url ~ '/images/image.jpg' }}" alt="alt text" />

And there you have it. A dynamic path that will work on any of our sites.

I realized some people reading this already knew all of this but I didn't. So I figured I would share it because I bet there are others out there that also do not know about the {{ url('<front>') }} or {{ active_theme_path() }} Twig functions as well as the {{ directory }} variable. As I said before, there are many ways to handle this challenge, but in my case this is exactly how I wanted to approachh it. I hope this was helpful. Cheers!

Categories: FLOSS Project Planets

Mario Hernandez: Five principles for building better components

Planet Drupal - Sat, 2024-06-15 22:24

When working on a component-based project, building components or patterns, can be a liberating experience because we are able to draft the best markup possible to build the most flexible, scalable and reusable components. If you work with third party platforms such as SharePoint, WordPress, Drupal, or others, this becomes even more rewarding as in most cases content management systems are not known for producing the best markup possible. However, this liberating experience can come a a price if you are not taking the appropriate measures and checks when building your library of patterns.
In this post I am going to discuss five critical principles I follow when building components. My goal is always to achieve first and foremost, semantic markup that works in all devices including assistive technologies, reusability, scalability, and finally, ease of use. In some cases it is hard to achieve all of these goals so it is important to understand where you draw the line and what is more important in the long term.

Disclaimer: Components and Paterns are used interchangeably.

Find patterns in your patterns

Ideally, before you begin building patterns, or components, you want to fully understand how and when these patterns will be used. Certainly there are times when we don’t have all this information when we first start working on a project, but if you do, take a detailed inventory of your patterns. Doing this will help you build better and more reusable patterns.
Failing to identify the patterns in your patterns can lead to bad decisions when building them. For example, let’s say we have a card component for displaying latest news articles, if we don’t know that there are cards for events that look similar to news articles, we may miss an opportunity to turn the card component into one we can use for multiple purposes in our website. But don’t let this get you down, building a website is an iteration process. If you later discover that the news card component can be leveraged for events, it’s perfectly okay to update the card component so it can be used with events as well. My point is that it is preferred if you can catch these patterns early on, but we all know that sometimes designs are not always available in full when a project is started and when they become available we may need to re-think our process of component-building.

Don't sacrifice simplicity over reusability

Reusability when working with components is important and should be at the forefront when building components, however, don’t paint yourself in a corner by over-engineering your components because you found a way to turn a component into a multi-level reusable machine. Trying to make your components do too much can lead to over complex development and in the long run, very hard to maintain. It is better to have components that are simple and easy to maintain even if it means you may have some components that are similar. It is hard to find the balance between simplicity and reusability and this discovery will only come as you work on your components. If you have to choose between simple and reusable, simple may be the way to go.

One set of markup for all devices

Earlier I mentioned that before you start building components you should have a full picture of how and when those components will be used by looking at the design comps. Of course this is not always possible, but in an ideally designs comps give us opportunities to identify areas of improvement or areas that could lead to potential problems. An example of a problem would be if you are provided the designs for mobile, tablet, and desktop. This is great, but let’s say the information displayed at each device type changes in such a way that you don’t know how to best write the markup so it works on all devices. In a perfect world, you should not have to alter the markup of your components per breakpoint. Doing so could lead to confusion on how content is rendered, not to mention problems with accessibility or even SEO. If you find that markup may be a challenge from breakpoint to breakpoint, it is perfectly okay to go back to the designer and propose updates to the design so a unified markup structure could serve all devices. In my experience, the designer would be okay with your recommendations as long as you are not completely changing the layout of the website but instead are simply proposing minor adjustments that don’t change the overall look and feel of the website.
In some cases a designer may not be fully aware that the designs they are proposing could lead to issue when the site is rendered in different device sizes, so it’s okay to bring these issues to their attention and you may find that they are onboard with your suggestions.

Find the right names for your patterns

Another way to paint yourself in a corner is by the name you choose for your components. Component names should be generic and yet descriptive so they can be used on different scenarios. Conversely, names that are too specific will limit when and how you can use those components. Let’s take a look at some bad component names and propose a better name:

  • News Card: If we think back of how we can reuse components, naming a component News Card will limit us on what kind of content we can use this component on. What if Events use a similar card with minor differences? A better name for this component may be Card or Content Card. This allows us to use on any type of content.

  • FAQ Accordion: We have seen this before, we want to build a collapsible list of Frequently Asked Questions so the name makes sense. However, what if we want the same functionality for other type of content such as Forums or Knowledge base content? A better name would simply be Accordion which describes the functionality of the component, not the content that it can hold.

  • Latest News List: Again, we are limiting ourselves to only showing content of type news. A list of content should be generic so we can use them for any kind of content (i.e. Blog posts, events, popular content etc.). A more appropriate name would be Content List.

  • Finally, Homepage Hero: It is not uncommon for Hero sections to be in multiple pages other than the homepage. By naming this component simply Hero, we can leverage it and use it in more than just the homepage. You may wonder, what if I have other types of heroes in non-homepage pages? Well, perhaps we can find alternative names for those, things like Hero Short, or Page Breaker, something that is descriptive and yet provides the ability to be used in multiple pages.

The above are only some examples of how a bad name can not only limit where or what type of content you can use your components with, but it can also lead to building unnecessary components.

Document your components

One of the great things about design systems like Pattern Lab, Storybook and others, is that they provide a way to document the details of your components. Documenting components can go a long way to ensure your entire team is fully aware of the why and how of your components.
I practice that I follow when building components is documenting the technical aspects of the component such as its name, where it is used, and details about any variants of the components. Just as important, I like to define the components fields, their data type and values they accept. This architectural information ensures gives your team and your stakeholders a detailed view of a components role in your project.

In closing, before you begin building components, take the time to study your design comps and take notes of how you feel you can make the most out of your components library. You will not get it right the first time, that’s okay. As long as you are giving yourself the room to iterate through the process, you can always go back and update a component if it will give you more flexibility and will turn your project into a solid component-based system.

In closing

You may not always get it righ the first time, but keeping these principles in mind will help you achieve better implementation of your website's patterns.

Categories: FLOSS Project Planets

Mario Hernandez: Demystifying components integration with Drupal

Planet Drupal - Sat, 2024-06-15 22:24

Component-based development is something I have been doing for at least five years and it is incredible that I still find myself learning something new every time I work on a new project. In addition to development, I write training curriculums on the topic and this gives me more exposure to this topic than most people. You’d think by now I should have learned all there is to learn about components and integrating them with Drupal, but that’s not the case, and I love it because I alway look forward to learning something new.

The inspiration for this post

I came across an outstanding blog series about Building with Emulsify Part 3: Component Complexity by Evan Willhite of Four Kitchens.

It's not you, it's me

I’d say I’m about 90% onboard with how Evan approaches component integration, but the part I am not a fan of is writing preprocess functions to achieve full integration, mainly because I am not well-versed in preprocess functions or PHP in general. There is absolutely nothing wrong with this, but what if you are someone like me who is not comfortable with preprocess functions or custom modules? So I decided to use most of the work Evan put together, but will perform the integration slightly differently so we only work with Twig and not PHP.

Both instances for which PHP code was required in Evan’s post were the result of using a Paragraph type to reference nodes. My approach will be to eliminate the paragraph type and work directly with the node entity.

Let’s start. I will pick up from Architecture since everything prior to this is typically the approach I take as well.

Architecture

We want administrators to create Nodes of any type and using a Display View Mode we can automatically display them using the card component. The one difference for me is that I don’t have a card variation.

Passing data between entities

Following Evan’s instructions, create a new Display view mode called Card. Here's another minor difference in that my Card uses an image field and date field in addition to title, body, and link fields. Here’s what the card looks like.

In the Card display view mode of an Article content type, ensure Body and Image are available. All other fields should be placed under Disabled. The Title and Date fields don’t display in the Manage Display screens, but they are always available in all Entity templates.

Integrating the Card component within a Node template

Just like in Evan’s post, we still need to associate a Node template with our Card. For this we will use the same template suggestion as Evan, node--card.html.twig.

{% include '@molecules/card/card.twig' with { 'image': content.field_image, 'date': date, 'title': label, 'body': content.body, 'link_url': url, 'link_text': "Read the article", } only %} Displaying a list of cards with Views

Now that the Card component has been integrated with Drupal, let’s create a simple view to display a collection of nodes displayed as Cards.

The views' settings can be seen below with description of the important configurations to note.

A few things about the view:

  • It’s called Latest Articles
  • The page built by the view can be viewed at /blog
  • Rather than using fields it uses the Card view mode we created earlier. This is the biggest advantage of integrating the Card component with the Entity, any time a node is displayed using the Card Display View Mode, the node will be displayed automatically as a Card.
  • It only pulls nodes of type Article

With some minor CSS the articles would look like this:

In closing

There will be times when writing a preprocess will be required based on the requirements, but whenever possible I’d like to avoid it.

Categories: FLOSS Project Planets

Mario Hernandez: Styling Forms Elements

Planet Drupal - Sat, 2024-06-15 22:24

Whether you are a veteran or just getting started with web development, working with web forms is not the sexiest thing but there is no avoiding them. Forms on a website are critical to the website's mission and the visitors using them. In this post I am going to show you how to properly style webforms and form elements so you can ensure they are consistent with your brand and provide a pleasant user experience.

Resources:

I have recording video tutorials on some of the concepts covered in this post. Scroll to the bottom if you are the kind of person who learns better watching tutorials.

Where to start

One thing about forms you can count on is that they are not consistent. In today's API era forms can be generated from any source and you don't always have control of their HTML. I personally work with Drupal a lot and even within it forms can originate from Views, Blocks, Nodes, Paragraphs, Modules, and more. I imagine this is the case with most Content Management Systems. So how do you address something where you can't always predict source or markup of forms?

If you can't beat them, join them

Whether is Drupal or other system, it is best to let that system dictate the markup of forms. Try to bend a system to your needs can prove challenging and not worth the trouble. This is the approach I take and so far this has worked very well for me.

Attributes are your friends

As a Front-End developer for many years, I love a good css class. They make my job a lot easier than not having a class. When it comes to form elements however, it is best to ignore classes and rely solely in attributes. What attributes you may be asking yourself? Let's take a look

From element types

The type attribute in form elements make it easy to style them in a global manner. Rather than styling form elements based on their css class or ID, the type attribute is the best approach. Each form field has a type attribute which makes it possible for browsers to render the element as expected. The type attribute can also play a factor in Javascript and form handling, but for now we are going to focus on styling.

  • Input fields: Input fields such as text fields, are probably the most common form elements in a form. But don't let its simplicity fool you because text fields come in many types; the most common being text. this is the type for a typical text box where you can type just about any kind of information. This sometimes leads to developers using it for anything including for data that should be in a different form type. Let's take a look at the various types a text field can be:

    • text: Used for most text fields and accept any string of text
    • email: Used to capture email address. Using this type is recommended for email addresses as it provides regular expressions to validate the data entered in the field meets the patterns of an email addresses. In addition, using this type of field makes it possible for your mobile devices to show a custom keyboard where the "@" sign is available without having to toggle your keyborad characters to find it.
    • password: As you can imagine, this makes it possible to hide passwords from view as you type them.
    • search: Elements of type search are text fields designed for the user to enter search queries into. These are functionally identical to text inputs, but may be styled differently by the user agent.
    • tel: Similarly to the email address, using this type allows for the keyboard on mobile devices to display numbers by default rather than alpha characters.
    • url: Similarly to email, this provides you with a custom keyboard on mobile devices that make it easier to type a common url.

As you can see from the examples above, text fields may seem simple but they come in all flavors. In most cases, the goal of form elements is to be styled the same across an entire site. So it is recommended you style these elements globally so no matter where they are used they automatically inherit the intended styles. So how do you style them globally? Let's take a look:

Here are a couple of examples of differnt input fields which we will style to ensure they all look similarly everywhere.

<input type="text" name="firstname"> <input type="password" name="password"> <input type="email" name="email"> Styling input[type='text'], input[type='password'], input[type='email'], input[type='tel'], input[type='search'], textarea { background-color: #ffffff; border: 1px solid #000000; height: 40px; padding: 10px; width: 100%; } textarea { height: initial; }

By using the element's type attribute we can collectively style them with a single set of rules. As you may have noticed I included textarea as on of the elements because I want textareas boxes (multi-row textbox), to inherit all of the styles from text boxes with the exception of the height property; thus we override the height property while keeping all other styles the same as text boxes.

Button elements

Buttons are elements you typically want to display with consistent styles across your entire website. This includes buttons you may create yourself and those provided by the content management system you may be working with. So similarly to input fields above, we can't always rely on IDs or classes to style them. However, similarly to the input fields above, we can rely on the type attribute to style the different type of buttons in our site. Different type of buttons? yes, there are different types. Let's tae a look.

Markup <a class='button'>I want to look like a button</a> <button>I'm an actual button</button> <input type='submit' value='Yo tambien'> <input type='reset' value='Me too'>
  • All elements above will have the same look and feel.
  • The first 3 elements should look identical, but the last one (type=['reset']) should look slightly different because it's not a primary button, it's more of a secondary button.
  • Sometimes this secondary button will be used to cancel or to reset someting on a page. While the top 3 will be used for important actions such as submit, save, etc.
  • Let's style them now
Styles .button, button, input[type='submit'], input[type='reset'] { background-color: rebeccapurple; border-radius: 2px; border: 2px solid rebeccapurple; color: #ffffff; cursor: pointer; display: inline-block; font-size: 18px; line-height: 1; padding: 20px 40px; text-align: center; text-decoration: none; white-space: nowrap; margin-bottom: 20px; } .button:hover, .button:focus, button:hover, button:focus, input[type='submit']:hover, input[type='submit']:focus { background-color: rgb(126, 63, 189); /* lighter purple */ color: #ffffff; text-decoration: none; }
  • We are including all the button elements in our initial set of styles. The reason for this is that we want all the buttons (regardless of their type of function), to share the same shape, font size, font-weight, border width, hover behavior, and other properties.
input[type='reset'] { background-color: #ffffff; border-color: rebeccapurple; color: rebeccapurple; } input[type='reset']:hover, input[type='reset']:focus { background-color: #ffffff; border-color: #444444; color: #444444; }
  • Then for the reset type only, we are styling this button with white background and purple text. This will ensure this button will not stand out as much as the other buttons.
Radio buttons and Check boxes

Probably the hardest elements to style due to their dynamic nature, they have a hard time fitting in with the rest of HTML elements. Rather than link you to another blog post where you can copy and paste the necessary CSS to style these elements, I am walk you through the process of styling them.

Checkout the Codepen for these elements.

Markup

The typical markup for a checkbox or radio button looks like this:

<div class="checkbox__item"> <input type="checkbox" id="checkbox1" name="checkboxname" value="Item 2"> <label for="checkbox1">Item 2</label> </div> <div class="checkbox__item"> <input type="checkbox" id="checkbox2" name="checkboxname" value="Item 2"> <label for="checkbox2">Item 2</label> </div> <div class="radio__item"> <input type="radio" id="radio1" name="radioname" value="Item 2"> <label for="radio1">Item 1</label> </div> <div class="radio__item"> <input type="radio" id="radio2" name="radioname" value="Item 2"> <label for="radio2">Item 2</label> </div> Styles

Out of the box, you can not style the native checkbox nor radio button elements. To be able to apply custom styles we need to do it in a hacky way. This is my favorite way to style these elements.

Drawing the checkbox /* visually hide the native checkbox and radio buttons. */ input[type='checkbox'], input[type='radio'] { position: absolute; opacity: 0; }
  • By setting absolute position and opacity to zero, we are hiding the native radio and checkbox elements from view. However, we need the elements to still function as expected so using visibility-hidden or display: none are not viable options.
  • Although we are visually hiding the elements, they are still available in the DOM so we can interact with them and also so they can be available to assistive technologies such as screen readers.
/* Add space to the left of the label for later use. */ label { position: relative; padding-left: 35px; }
  • We set relative position on the label as we prepare to add pseudo elements in the next steps.
  • The left padding here will allow us to draw a box or a circle for the checkbox/radio button. More on this later.
/* Draw a square box with border using a pseudo element (`::before`). */ input[type='checkbox'] + label::before { border: 2px solid #e15b00; content: ''; display: inline-block; height: 24px; left: 0; position: absolute; top: -2px; width: 24px; }
  • First we define a sibling element of the checkbox with the + sign. The sibling element being the label element. If you look at the markup we wrote above, you will notice that the checkbox and the label are siblings. This is also true for the radio button and its label.
  • Using a ::before pseudo element on the label, we are drawing a 24px square box.
  • For any pesudo element to be visible on the page, we need to assign the property of content:, even if it's value is empty as shown above.
  • We then add a 2px border on the box. This box is what will be presented as the actual checkbox for the user to interact with (check/uncheck).
  • By setting the box with absolute position, we are positioning just to the left of the label. The position of the box is in relation to the label's position (hence relative position on the label).
/* Draw checkmark using a ::after pseudo elment on the label */ input[type='checkbox'] + label::after { border-bottom: 3px solid #fff; border-left: 3px solid #fff; content: ''; display: inline-block; height: 10px; left: 4px; position: absolute; top: 2px; transform: rotate(-45deg); width: 18px; }
  • The same way we drew the box previously, now we draw a checkmark symbol using a ::after pseudo element on the label. This means we are drawing the checkmark symbol after the label element.
  • In reality, we are drawing a rectangular box with left and bottom borders of 3px. By rotating the box negative 45 degrees, this makes it look like a checkmark.
  • We then use absolute position on the checkmark so we can position it right in the middle of the box we drew before.
Drawing the radio button

Before proceeding with the styles, let's repeat the steps above but this time for the radio buttons. These styles are extremely similar with the exception of in stead of drawing a square box, we will be drawing a circle with an outline, and instead of a checkmark, we will be drawing a filled circle in the middle of the outlined circle.

/* Draw a circle and add borders to it */ input[type='radio'] + label::before { border-radius: 50%; border: 2px solid #e15b00; content: ''; display: inline-block; height: 24px; left: 2px; position: absolute; top: -4px; width: 24px; } /* Draw an inner circle */ input[type='radio'] + label::after { background-color: #e15b00; border-radius: 50%; content: ''; display: inline-block; height: 20px; left: 6px; position: absolute; top: 0; width: 20px; }
  • This time we are targeting the input type of radio and we are using border-radius to draw a perfect circle.
/* Hide checkmark and inner circle by default */ .radio input[type='radio'] + label::after, .checkbox input[type='checkbox'] + label::after { content: none; }
  • By default, we don't want either the checkboxes or radio buttons to be checked. So we set the value of content: to none. This will present the checkboxes and radio buttons unchecked.
/* Show checkmark and inner circle when input is checked */ input[type='radio']:checked + label::after, input[type='checkbox']:checked + label::after { content: ''; }
  • Using the :checked pseudo class (input[type='radio']:checked, input[type='checkbox']:checked), we then show the checkmark or inner circle by setting the content: property's value to empty ('' i.e. removing none). The :checked pseudo class becomes available when the checkbox or radio buttons are click-checked and when that happens, then we show the checkmark or inner circle.
/* When box is checked, add background color. */ input[type='checkbox']:checked + label::before { content: ''; background-color: #e15b00; }
  • Since the checkmark we drew is white, we are adding a background color inside the checkbox to display the checkmark. We are doing this only when the :checked pseudo class is present.
/* Add focus styles when elements are in focus */ input[type='radio']:focus + label::before, input[type='checkbox']:focus + label::before { outline: rgb(59, 153, 252) auto 5px; }
  • Finally, using the :focus pseudo class, we are adding basic outline styles to show the "on focus" state of the inputs. We need to do this since we are not using the native input elements. If we were, the browser would automatically add these styles to our inputs.
Resources

I have recorded tutorials on severalof these things. Check them out if you are the kind of person who learns better through video tutorials.

Categories: FLOSS Project Planets

Mario Hernandez: Building an automated DDEV-based Drupal environment

Planet Drupal - Sat, 2024-06-15 22:24

A successful training experience begins before we step foot in the training room. Or, in these days of distance learning, it begins before students login to your training platform. The challenge is having an environment that is easy for students to setup and provides all the tooling required for the training. Configuring a native development environment is no easy task. Web development tools have gotten more complex with the years and it's a huge barrier for even experienced developers. My goal in setting up this new environment was to have everything completely configured and automated so students only needed to run one command. Very ambitious.

WARNING: The codebase shared in this post is only intended for local development. DO NOT use this project in a production website.

About DDEV: The official name is DDEV-Local. For simplicity I use DDEV in this tutorial.

Here are the tools and configuration requirements for this environment:

  • Docker
  • DDEV
  • Composer
  • Drush
  • Drupal 8 and contrib modules
  • Pre-built Drupal entities (content types, paragraph types, views, view modes, taxonomy, image styles, and more)
  • Custom Drupal 8 theme
  • Twig debugging enabled by default and Drupal cache disabled by default
  • NodeJS, NPM, and NVM
  • Pattern Lab
  • Gulp, ESLint, Sass Lint, BrowserSync, Autoprefixer, and many many more node dependencies
Desired behavior

My main objective was to simplify the building and interaction with the environment, by:

  • Only require Docker and DDEV to be installed on host computer
  • Reducing the number of steps for building the environment to one command, ddev start
  • Execute most if not all commands in the containers, not the host machine (Drush, Composer, Pattern Lab, etc.)
  • Access Pattern Lab running in the web container, from the host machine

Yes, it is crazy, but let's see how this turned out

First things first. As I said before, installing Docker and DDEV are the only two things students will need to install. There is no way around this. Luckily there are a lot of resources to help you with this. The DDEV docs is a great place to start.

Setting up a Drupal site

Before we start, I'd like to clarify that this post focuses on outlining the process and steps for automating a local environment. However, before we can automate, we need to build all the pieces. If you just want to grab the final product here's the repo, otherwise, read on.

As of Drupal 8.8.0, Composer project templates are now available as part of Drupal core. These project templates are recommended for building new Drupal sites as they serve as a starting point for creating a Composer-managed Drupal site.

Two things we will be doing with our Drupal project:

  1. Drupal dependencies and modules will not be commited to the git repo. They will be downloaded with Composer when the project is being built.
  2. Composer will not be installed in the host system, instead, we will use the composer version that comes with DDEV's web container.
Let's start
  1. Create a new directory for your project. The directory name should be lowercase and alpha-numeric characters only. For this example I will name it drupaltraining and will create it in my /Sites directory. Using your command line tool create the new directory

    mkdir drupaltraining
  2. Now navigate into the newly created directory

    cd drupaltraining
  3. Run the DDEV command below to setup a new Drupal project

    ddev config --project-type=drupal8 --docroot=web --create-docroot
    • This will create a .ddev directory with basic Drupal configuration.
    • It will also create settings.php and settings.ddev.php` files inside web/sites/default.
    • Finally, it creates a Drush directory.
    • Keep in mind, Drupal is not in place yet, this simply sets the environment for it.
  4. Now start DDEV to create the containers.

    ddev start
    • After the project has been built, you will see a link to open Drupal in the browser. For this project the link should be https://drupaltraining.ddev.site (ignore for now). If you have not installed mkcert (mkcert -install), the link you see may use http instead of https. That's fine, but I'd recommend always using https by installing mkcert.
  5. Now we are going to create the codebase for Drupal by using the official Drupal community
    Composer template
    .

    ddev composer create "drupal/recommended-project:^8"

    Respond Yes when asked if it's okay to override everything in the existing directory.

    • This will setup the codebase for Drupal. This could take a while depending on your connection.
    • composer.json will be updated so Drupal is added as a project's dependency.
    • New settings.php and settings.ddev.php files are created.
    • Using ddev composer create uses the version of composer that comes as part of the DDEV's web container. This means composer is not required to be installed in the host's computer.
    • One thing I love about using the new composer template is the nice list of next steps you get after downloading Drupal's code base. So useful!
  6. Now let's grab some modules

    ddev composer require drupal/devel drupal/admin_toolbar drupal/paragraphs drupal/components drupal/viewsreference drupal/entity_reference_revisions drupal/twig_field_value
    • This will download the modules and update composer.json to set them as dependencies along with Drupal. The modules and Drupal's codebase will not be commited to our repo.
  7. Let's add Drush to the project

    ddev composer require drush/drush
    • Installing Drush as part of the project which will run in the web container will make it possible to run drush commands (ddev drush <command>), even if the host does not have drush installed.
  8. Now launch Drupal in the browser to complete the installation

    ddev launch
    • This will launch Drupal's intall page. Drupal's url will be https://drupaltraining.ddev.site. Complete the installation by using the Standard profile. Since the settings.ddev.php file already exists, the database configuration screen will be skipped from the installation.
  9. Make a copy of example.settings.local.php into web/sites/default

    cp web/sites/example.settings.local.php web/sites/default/settings.local.php
    • This is recommended to override or add new configuration to your Drupal site.
  10. Update settings.php to include settings.local.php.

    if (file_exists($app_root . '/' . $site_path . '/settings.local.php')) { include $app_root . '/' . $site_path . '/settings.local.php'; }
    • I'd suggest adding the above code right after the settings.ddev.php include already in settings.php. This will allow us to override configuration found in settings.ddev.php.
  11. Let's change Drupal's default config directory. Doing this will export any configuration outside the web directory in Drupal. This is a good security measure. Let's also ignore a couple of folders to avoid drupal errors.

    if (empty($settings['config_sync_directory'])) { $settings['config_sync_directory'] = '../config/sync'; } $settings['file_scan_ignore_directories'] = [ 'node_modules', 'bower_components', ];
    • First block changes the default sync directory to drupaltraining/config/sync. If you look inside settings.ddev.php you will see that this directory is inside web. We want to store any configuration changes outside the web directory.
    • Second block sets up Drupal to ignore node_modules and bower_components. Drupal may look for twig templates inside these directories and could cause Drupal to crash. Ignoring these directories solves these issues.
  12. Since we've made changes to DDEV's configuration, restart DDEV

    ddev restart

That's quite the process, isn't it? The good news is this entire process will be eliminated when we finish automating the environment.

Drupal 8 custom theme

The project's theme is called training_theme. This is a node-based theme, and will be built with Mediacurrent's theme generator, which will provide:

  • A best-practices Drupal 8 theme
  • Pattern Lab integration
  • Automated Front-End workflow
  • Component-based-ready environment
  • Production-ready theme

The final DDEV project will include the new Drupal 8 theme so there is no need to create it now, but if you want to see how the Theme Generator works, Watch the video tutorial I recorded.

Automating our environment

Now that Drupal has been setup let's begin the automation process.

Dockerfile

A web Docker container comes with Node and NPM installed. This will work in most cases, but the Drupal theme may use a version of node not currently available in the container. In addition, the web container does not include Node Version Manager (NVM), to manage multiple node versions. If the tools we need are not available in the web or db images/containers, there are ways to modify them to include the required tools. One of those ways is an add-on Dockerfile in your project's .ddev/web-build or .ddev/db-build, depending which container you are trying to modify.

Inside .ddev/web-build create a file called Dockerfile (case sensitive), and in it, add the following code:

ARG BASE_IMAGE FROM $BASE_IMAGE ENV NVM_DIR=/usr/local/nvm ENV NODE_DEFAULT_VERSION=v14.2.0 RUN curl -sL https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh -o install_nvm.sh RUN mkdir -p $NVM_DIR && bash install_nvm.sh RUN echo "source $NVM_DIR/nvm.sh" >>/etc/profile RUN bash -ic "nvm install $NODE_DEFAULT_VERSION && nvm use $NODE_DEFAULT_VERSION" RUN chmod -R ugo+w $NVM_DIR

The code above sets an environment variable for default node version (v14.2.0), which is the node version the theme uses at the time of this setup. It installs and configures NVM, and makes NVM executable by updating the container's bash profile. A Dockerfile runs while the image or container is being built to alter any default configuration with the code found in the Dockerfile.

Custom DDEV commands

The Drupal 8 theme for this project uses Node for most of its tasks. To compile CSS, JavaScript, and Twig we need to run commands such as npm install, npm run build, npm run watch, and others. Our goal is to be able to run these commands in the web container, not the host computer. Doing this eliminates the need for students to install any node-related tools which can get really complicated. While we could achieve this by asking students to first SSH into the web container (ddev ssh), then navigate into /web/themes/custom/training_theme/, then run the commands, I want to make it even easier for them. I want them to be able to run the commands from any directory within the project and to not have to ssh into the web container. To achieve this we need to create a couple of custom commands.

Custom commands can be created to run in containers as well as the host machine. Since we want to run these commands in the web container, we are going to create the commands inside .ddev/commands/web/. Custom commands are bash script files.

NVM custom command
  • Inside .ddev/commands/web/ create a new file called nvm
  • Add the following code in the file:
#!/bin/bash ## Description: Run any nvm command. ## Usage: nvm [flags] [args] ## Example: "nvm use or nvm install" source /etc/profile && cd /var/www/html/web/themes/custom/training_theme && nvm $@
  • Since this is a bash script, #!/bin/bash is required as the first line in the file.
  • A description of the script is a good practice to explain what the script does.
  • Pay close attention to ## Usage: nvm [flgas] [args]. This is what makes the commands work. The [flags] and [args] are ways to pass arguments to the command. For example, nvm on its own won't do much, but use or install can be passed as parameters to complete the commands nvm use or nvm install. Being able to run these commands will allow us to install new versions of Node later on if needed.
  • Next we are adding examples of potential commands that can be run.
  • Finally, you see the code or actual commands. source /etc/profile is basically resetting the container's bash profile so NVM can run. Then we navigate into the training_theme directory within the container where the nvm commands will be executed. So technically in the script above we are running 3 commands in one. Using && in between each command lets us combine or concatenate them. The $@ after nvm represent the flags or arguments we can pass (i.e. use or install).

Running the new commands: Every custom command needs to be executed by adding ddev before the command. For example: ddev nvm use. Using ddev infront of the command instructs the system to run the commands in the containers, rather than the host computer.

NPM custom commands

We will also create a custom script to run NPM commands. This will be similar to the NVM script. This new script will be executed as ddev npm install or ddev npm run build, etc. The npm commands will allow us to install node dependencies by the theme as well as execute tasks like compiling code, linting code, compressing assets, and more.

  • Create a new file inside .ddev/commands/web and call it npm
  • Add the following code in the file:
#!/bin/bash ## Description: Run npm commands inside theme. ## Usage: npm [flags] [args] ## Example: "npm install or npm rebuild node-sass or npm run build or npm run watch" cd /var/www/html/web/themes/custom/training_theme && npm $@
  • Most of the code here is similar to the previous script, except instead of nvm we will run npm.
  • Notice we are again navigating into the theme directory before running the command. This make is possible for the custom commands we are creating to be ran from any directory within our project while still being executed inside the training_theme directory in the container.
Drush custom command

Let's create one last custom command to run drush commands within the DDEV containers

  • Create a new file inside .ddev/commands/web and call it drush
  • Add the following code in the file:
#!/bin/bash ## Description: Run drush inside the web container ## Usage: drush [flags] [args] ## Example: "ddev drush uli" or "ddev drush sql-cli" or "ddev drush --version" drush $@
  • This will allow us to run drush commands in the container but using ddev drush <command> (i.e. ddev drush cr, ddev drush updb -y, etc.).

So that's it for custom commands. By having custom commands for nvm and npm, we can now successfully run any theming related tasks.

Automating Drupal's setup

We want to streamline the drupal installation process. In addition, we want to be able to import a custom database file to have access to all the infrastructure needed during training. This includes content types, paragraph types, views, view modes, image styles, and more. Enter DDEV hooks.

DDEV Hooks

Hooks are a great way to perform tasks before or after DDEV starts. There are tasks that need to happen in specific sequence and hooks allow us to do just that. So what's the differnce between custom commands and hooks? Technically hooks can be considered custom commands, but the difference is that they are executed automatically before or after DDEV starts, whereas custom commands are ran on demand at any time. DDEV needs to be running if custom commands are intended to run in containers. Back to hooks, Let's build a post-start hook.

Hooks can be ran as pre-start, post-start, and after-db-import. Also, hooks can be executed inside containers and/or the host machine. In our case all the tasks we outlined above will be ran after DDEV starts and most of them in Docker's containers.

  • Open .ddev/config.yaml and add the following code at the bottom of the file. There may already be a hooks section in your file. Be sure indentation in the file is correct.
hooks: post-start: - composer: install - exec: /var/www/html/db/import-db.sh - exec: drush updb -y - exec: drush cim -y - exec-host: cp -rf web/sites/example.development.services.yml web/sites/development.services.yml - exec-host: cp -rf web/assets/images/* web/sites/default/files/images/ - exec: drush cr - exec-host: ddev launch /user
  • post-start: indicates tasks declared will run after DDEV container's have started. We want the web and db containers available before running any of the tasks.
  • composer: install will download all dependencies found in composer.json (Drupal core, modules, drush and others).
  • exec: /var/www/html/db/import-db.sh is a custom script which imports a custom database file (provided in this project). We will go over this script shortly. Importing a custom database builds the Drupal site without having to install Drupal as long as Drupal's codebase exists.
  • exec: drush cim -y, exec: drush updb -y, and exec: drush cr, are basic drush commands.
  • exec-host: cp -rf web/sites/example.development.services.yml web/sites/development.services.yml will create a new development.services.yml. This is needed because when Drupal is setup, development.services.yml is overridden and since we are using custom configuration in that file to enable twig debugging, we need to restore the configuration by replacing the file with a copy of our own.
  • exec-host: cp -rf web/assets/images/* web/sites/default/files/images/ copies a collection of images used in the demo content added to the site.
  • Finally, exec-host: ddev launch /user will open a fully configured Drupal website in the browser.

Login to Drupal: Username: admin, password: admin

Import database script

Now let's write the script to import the database we are using in the hook above.

  • In your project's root, create a new directory called db
  • In the new directory create a new file called import-db.sh and add the following code:
#!/bin/bash # Use a table that should exist in your database. if ! mysql -e 'SELECT * FROM node__field_hero;' db 2>/dev/null; then echo 'Importing the database' # Provide path to custom database. gzip -dc /var/www/html/db/drupaltraining.sql.gz | mysql db fi
  • This is again another bash script which performs a database import but only if the database is empty or clean. We do this by checking if one of the tables we expect in the database exists (node__field_hero). If it does, the database is not imported, but if it doesn't it will import the database. This table can be any table you know it should exist.

  • Notice the script calls for a database file named drupaltraining.sql.gz. This means the database file shold exist inside the db directory alongside the import-db.sh script. This database file was created/exported after Drupal was configured with all training required infrastructure and settings.

  • Make the script executable

chmod +x db/import-db.sh
  • This will ensure the script can be executed by DDEV, otherwise we would get a permission denied error.

This does it for automation. The next few tasks are things that improve the development environment. Some of these tasks are optional.

Exposing Pattern Lab's port in host computer

Since our goal is to not have to install any Front-End tools in the host computer, running Pattern Lab has to be done in the web container. The problem is we can't open Pattern Lab in the host's browser if Pattern Lab is running in the container. For this to work we need to expose the port in which Pattern Lab runs to the host machine. In this environment, that port is 3000 (this port number may vary). We identify this port by running npm run watch inside the training_theme directory. This will provide a series of links to access Pattern Lab in the browser.

Under the hood, DDEV uses docker-compose to define and run the multiple containers that make up the local environment for a project. docker-compose supports defining multiple compose files to facilitate sharing Compose configurations between files and projects, and DDEV is designed to leverage this ability.

Creating a new docker-compose.*.yaml

A docker-compose file allows to do many things including exposing ports from the containers to the host computer.

On a typical Pattern lab project if you run npm start you will see Pattern Lab running on http://localhost:3000. In this environment the equivalent command is ddev npm run watch. Since this environment is running Pattern Lab in the web container, the only way to access Pattern Lab in the browser is by having access to the container's port 3000. Exposing the port via the http or https protocols makes it possible to access Pattern Lab's UI page from the host machine.

  1. Inside .ddev/, create a new file called docker-compose.patternlab.yaml

  2. In the new file add the following code:

    # Override the web container's standard HTTP_EXPOSE and HTTPS_EXPOSE # to access patternlab on https://drupaltraining.ddev.site:3000, or http://drupaltraining.ddev.site:3001. version: '3.6' services: web: # ports are a list of exposed *container* ports ports: - "3000" environment: - HTTP_EXPOSE=${DDEV_ROUTER_HTTP_PORT}:80,${DDEV_MAILHOG_PORT}:8025,3001:3000 - HTTPS_EXPOSE=${DDEV_ROUTER_HTTPS_PORT}:80,${DDEV_MAILHOG_HTTPS_PORT}:8025,3000:3000
    • The name of the file is completely optional. It makes sense to use a name that is related to the action, app, or service you are trying to implement. In this example the name docker-compose.patternlab.yaml made sense.
    • How did we arrive at the content above for this file? DDEV comes with two files that can be used as templates for new configuration, one of those files is called .ddev-docker-compose-base.yaml. You can find all the code we added above in this file.
  3. Restart DDEV to allow for the new changes to take effect:

    ddev restart
    • The basics of the code above is modifying the web container's port 3000. We are exposing this port through the http and https protocols on the host machine.
  4. For the above to work Pattern Lab needs to be running in the container:

    ddev npm run watch

Viewing Pattern Lab in the host machine

Using NFS (optional)

Last but not least, enabling NFS in DDEV can help with performance of your application. NFS (Network File System) is a classic, mature Unix technique to mount a filesystem from one device to another. It provides significantly improved webserver performance on macOS and Windows. This is completely optional and in most cases you may not even need to do this. The steps below are for macOS only. Learn more about NFS and how to enable it in other Operating Systems like Windows.

  • In your command line run
id
  • Make note of uid (user id) and gid (group id).
  • Open etc/exports in your code editor, and add the following code, preferably at the top of the file:
/System/Volumes/Data/Users/xxxx/Sites/Docker -alldirs -mapall=502:20 localhost
  • Replace xxxx with your username. The full path shown above is required if you are using macOS Catalina.

  • Replace Sites/Docker (This is my personal project's directory), with the directory name where your DDEV projects are created. Most people would mount the entire user's home directory, but I think only mounting the directory where your projects live is good enough (/Sites/Docker/).

  • Replace 502 and 20 with the values you got when you ran the id command above.

  • Update DDEV's config.yml to enable NFS

nfs_mount_enabled: true
  • Restart DDEV
ddev restart

You should notice an improvement in performance in your Drupal website.

In closing

I realize there is a lot here, but I am pretty happy with how this turned out. Thanks to all the work in this article, when one of the students wants to setup their training environment, all the have to do is run ddev start. I think that's pretty sweet! 🙌 Happy DDEVing!

Giving credit

Before I started on this journey, I knew very little about DDEV. Thanks to the amazing help from Randy Fray from Drud.com and Michael Anello from DrupalEasy.com, I have learned a lot in the past weeks and wanted to share with other community members. You can find both, Randy and Michael on Drupal's DDEV Slack Channel. They are extremely helpful and responsive.

Resources
Categories: FLOSS Project Planets

Mario Hernandez: Building a Drupal Theme with the Theme Generator

Planet Drupal - Sat, 2024-06-15 22:24

On one of my last training workshops I took a chance and decided to let students pick their environment of choice to use during training. As always I hosted an online call prior to training to assist anyone who needed help setting up their environment. About five of the students showed up to the call. This is a first. In the past when I've used a preconfigured training environment typically no one shows up to this prep call because the environment I've put together for them has been fully tested and any potential issues have been addressed.

Although I was able to help everyone get ready for training, and no big issues were encountered during training, I learned that perhpas having a preconfigured training environment is the best way to go. Having done this in the past I found that a preconfigured environment not only provides a consistent experience for everyone but it makes things more predictable for everyone.

I'm going to show you the latest setup I am using when training people. This is a new setup I put together using DDev with a host of other tools including Drupal

See the Theme Generator's project page on Github.

Watch the full tutorial below:

Categories: FLOSS Project Planets

Mario Hernandez: Our best training yet

Planet Drupal - Sat, 2024-06-15 22:24

For the past few years we've been working on a training curriculum around Component Based Development and every time we finish updating it for an upcoming event we feel really good about it and think we finally got it. In reality, a training curriculum for a topic so complex as Component Development is probably never finished.

After BADCamp's training workshop last year we thought we were done updating the training material and we felt it was so good that it would carry us all the way to DrupalCon Seattle 2019. We could not have been more wrong. Soon after BADCamp we made the decision to move from KSS Node to Pattern Lab for handling our pattern's workflow and living styleguide. This meant our entire training material needed to be updated in time for DrupalCon. Needless to say we spent months ensuring the material was current, relevant and effective.

DrupalCon Seattle was a total success. Our training sold out in about two weeks and end up with almost 50 people in attendance which is probably a record for us. But with more students the chances of more issues or something going wrong increase. We make a big effort to ensure we provide an automated environment for student to use. This allows us provide all tools needed during training pre-configued and tested ensuring a more predictable and consistent behavior by students' systems. However, there are always cases in which a student decides not to use our environment and either due to technical skills or strict restrictions on their systems they need to use a workflow we have not tested.
This was certainly the case at DrupalCon and we had to find ways to help those students to be able to follow along during training.

One way to help people with their local setup is by conducting what we call "Office Hours". We reserve an hour or two weeks before the training where anyone who needs assistance with their local environment can join us online and we attempt to get them up and running. We helped several individuals this way. Although it is more work for us it allows us to address any issues off hours rather than spending valuable training time fixing computer problems.

The training was engaging and we received great feedback. Our curriculum, although not perfect, is relevant and well received. Having done this a few times at small and large events gives us confidence and makes it easier next time we do it.

Our team is dedicated and passionate about training and this would not be possible without them. Especial thanks go to Eric Huffman who has done most of the heavy lifting with the local environment automation. Kelly Dassing, who as a Director of Project Management provides a unique perspective to those in attendance who may not be developers. Tobias Williams who is an all around great FE develper and Engineer, and Mark Casias, who is able to answer the hard Back-end question that the rest of the team may not be as proficient as he is.

Should you have training needs please reach out and we would be happy to put together a curriculum that fits your specific needs or that of your team.

For a more in-depth look at how we prepare for a training workshop ready my blog post on Planning for an Effective Training Workshop.

Until next time ... Cheers!.

Categories: FLOSS Project Planets

Mario Hernandez: Handling Drupal attributes in components

Planet Drupal - Sat, 2024-06-15 22:24

In Drupal's twig templates you'll often see an attributes variable being output within the template. This variable is how core and contrib modules inject their CSS classes, an ID, or data attributes onto template markup. You'll also find title_prefix and title_suffix variables. These are used by core and contrib modules to inject markup into twig templates. A good example of this is the core Contextual Links module. If you were to remove the attributes , title_prefix , and title_suffix variables from a node template, for example, then the Contextual Links module would no longer have a way to add its drop-down to the display of nodes.

In some cases this may not be an issue for you, but in general it's best to plan to accommodate those Drupal-specific variables in your component markup so that when you integrate Drupal content into your components, other features can be available too.

Since the attributes variable can include class, id, and data attributes in one variable, we need to make sure we only combine Drupal’s classes with ours, and let the other attributes render without Drupal classes. This can be accomplished on the main wrapper of the component template.

<article class="card{{ attributes ? ' ' ~ attributes.class }}" {{ attributes ? attributes|without(class) }}> {{ title_prefix }} {{ title_suffix }} {% if image %} <div class="card__image"> {{ image }} </div> {% endif %} <div class="card__content"> {% if heading %} {% include '@components/heading/heading.twig' with { "heading": { "title": heading.title, "url": heading.url, "heading_level": heading.heading_level, "classes": 'card__heading' } } only %} {% endif %} </div> </article>

Note that the without twig filter in this example is a Drupal-specific filter, so for the component we'll want to make sure we’re using one that supports Drupal’s custom filters (most design systems such as KSS node, and Pattern Lab have configuration options that support Drupal twig filters).

Now if we integrate our card component with Drupal (i.e. node--card.html.twig), we can ensure Drupal's attributes and contextual links will be available when the component is rendered. The node template, also known as presenter template, would look something like this:

{% set rendered_content = content|render %} {% set heading = { title: label, url: url, heading_level: '4', attributes: title_attributes } %} {% embed '@components/card/card.twig' with { attributes: attributes, title_prefix: title_prefix, title_suffix: title_suffix, heading: heading, image: content.field_image is not empty ? content.field_image, } only %}
  • First we're triggering a full render of the content variable.
  • Then we set up a variable for the Heading field,
  • Finally we are using an embed twig statement to integrate the Card component. In the embed we are mapping all the Card fields with Drupal's data. We also pass in Drupal-specific items such as title_prefix, title_suffix, attributes, etc.

Our card component will be rendered with all Drupal's attributes and the ability to be edited inline thanks to the contextual links.

Hope this helps you in your component development journey.

Categories: FLOSS Project Planets

Mario Hernandez: Surviving the fire

Planet Drupal - Sat, 2024-06-15 22:24

We are used to watching the news and somewhere in California there is a wildfire destroying nature and people's homes. In 30 years I have lived in the Los Angeles area I have never experienced this first hand. I've have been a victim of earthquakes but this was my first as a fire victim.

We first heard about the fires earlier in the week and the night of November 8th we knew the fires were escalating, but they were nowhere near our neighborhood. All this changed when we went to bed Thursday night and we noticed winds were pretty strong to the point that they would make loud and weird noises as they blew through trees and roof of our place.

We went to bed without a single concern. At 3:30 am. the morning of Friday November 9th, we woke up to loud screams and knocks on our door by the Fire Department. As I opened the door still half asleep, they told me we had 15 min minutes to get out and to head to the nearest shelter in place. They had to repeat this information about 3 times as I was still in disbelief and completely confused. As I looked out our door, this was the image I saw:

You probably can't tell from the picture but that fire is only feets away from our house. That is the same hills I hike on and now they are completely burned.

It gets worse

My wife and I got the kids ready and were only able to pick up passports and personal documents. We didn't have enough time to grab anything else except change cloths to something more practical for the weather.

We arrived at the shelter in place at around 4:00 am. and it was packed with people and pets. We also had a pet of our own. After registering we sit there for a while waiting to hear news but nothing was coming in. At around 5:00 am. we decided it would be best to leave to my parent's house, about 28 miles away. As we are ready to leave we see one of our neighbor who proceeds to give us the worst news of the morning. He informed us our building complex has been completely burned and our house is gone. We didn't know how to respond or react to these news. The thought that we no longer had a house was unreal. I recall telling my wife I didn't believe it but I had no way to prove it. I was probably in denial or perhaps hoping it wasn't true.

As we leave the shelter in place at around 5:30 am., my wife asked me to drive by our house to see for ourselves what it looked like. To our surprise, there was zero damage to our building or buildings in our neighborhood. I was both relieved and extremely upset at my neighbor for giving us such as bad and inaccurate information. I hope I can put this behind us, but for now I am still bothered by this.

The fire stopped short of our place and we could not be more fortunate. I was relieved and felt like we had made it through the worst when we saw our place still standing.

Then it gets better

While at the shelter in place, I notified several people at my job, Mediacurrent.com, that I was being evacuated and would not be able to work that day. The response from everyone was unbelievable. Mediacurrent's partner Paul Chason responded within seconds of my email to let me know that they were behind us and to let him know of anything we needed. This was the same message I received from many colleagues and friends. I have always felt fortunate to be part of a great team like Mediacurrent's, but they go above and beyond to support their employees. This is not the first time they have shown me this kind of support. The first time was when my wife was diagnosed with Chronic Kidney disease and Mediacurrent's response was unbelievable.

As other friends in social media learned about our situation, the flood of support was so great and it gave us strength and hope that we were going to be okay.

As we started communicating with other neighbors we began to feel more confident that we would be able to return home soon.

We came back home on Friday evening to pick up some stuff, including my wife's dialysis machine, which she uses every night as treatment for her chronic kidney disease, and also to clean up and change cloths. Things by then looked pretty normal in our neighborhood but the air quality was still pretty bad so we decided to go back to my parent one more night. On Saturday evening we returned home for good and began the cleaning process which was not as bad as it could had been.

In closing

I hope we never have to go through something like this again, and really hope our friends and family never have to go through this, but having the support of our family, friends and coworkers made this horrible experience more manageable. We want to thank our family for always being there, and for providing a place to stay during this emergency. Our Mediacurrent family for showing us that we are more than coworkers, we care for each other. Thank you to our friends who would continuously check on us and offer help. Finally, our deepest and most special thanks to the everyday heroes who risk their lives to help others; first responders, fire fighters and law enforcements who worked and continue working tirelessly to protect us.

Thank you - The Hernandezes. 🙏 ❤️

Categories: FLOSS Project Planets

Mario Hernandez: Adding Social Share Links to Gatsby

Planet Drupal - Sat, 2024-06-15 22:24
Sharing is caring.

I've been working on my personal blog (this site), for a while. I built it with Gatsby and little by little I have been adding extra functionality. Today I'm going to show you how I added social sharing links to allow visitors to share my posts with others using Twitter, Facebook, LinkedIn, and other channels.

For an example of the Sharing links, look at the icons above the hero image on this and every post on this site.

There are many ways to accomplish this but from the begining I wanted to use something that was simple and did not require too much overhead to run. There are solutions out there that require third libraries and scripts and I wanted to avoid that. A while back I was introduced to Responsible Social Share Links. The beauty of Responsible Social Links is that they do not need any Javascript to work. They use the sharing links available for most social media channels.

Let's take a look at some examples of what these links look like:

Facebook

https://www.facebook.com/sharer/sharer.php?u=URL_TO_SHARE

Twitter

<a href="https://twitter.com/intent/tweet/ ?text=Check this out &url=https://mariohernandez.io &via=imariohernandez" target="_blank">Share on Twitter</a>

Most of these links accepts several parameters. You can see these parameters in more details at the Responsible Social Share Links page for additional information. In addtion, some systems may require you to encode the links but luckily for us Reacts does this for us automatically.

Using the links in a Gatsby site (or React for that matter)

You may think, that's so easy, just modify each of the links with my personal information and done. That's true to an extend. However, the tricky part is dynamically passing the current page's URL and post title to your sharing link. So here's how I did it:

  1. Edit your blog post template. In my case my blog post template is /src/templates/blog-post.js This is based on the Gatsby starter I used. Your mileage may vary.

  2. Add the following code where you wish to display the sharing links to generate a twitter share link:

<Share> <ShareLink href={`https://twitter.com/intent/tweet/?text=${post.frontmatter.title} &url=https://mariohernandez.io${post.frontmatter.path}%2F&via=imariohernandez`}> // Optional icon <LinkLabel>Share on Twitter</LinkLabel> </ShareLink> </Share>

The example above creates a twitter share link and uses the data variables I am already using to print the blog post content. As you know, Gatsby uses GraphQL to query the posts and by doing this you have access to each of the fields in your post (i.e. title, path, tags, date, etc.).

In the example above, I am passing ${post.frontmatter.title} so when the post is shared the title of the post is included as your tweet text. In addition, I am linking to the current post by passing ${post.frontmatter.path}. Finally I am passing my twitter handle.

There are other parameters you can pass to your share links. Things like hashtags, mentions, and more. Following the same pattern you can do the same for Facebook, LinkedIn and others.

A much cleaner approach

You may have noticed that I created the sharing snippet directly in the blog-post.js template. A much cleaner approach would be to create a new React component for all yoru sharing links and include the component in your blog-post.js.

Here's the full snippet for all the social channels I am using:

<Share> <ShareLabel>Share this post</ShareLabel> <ShareSocial> <ShareItem> <ShareLink href={`https://twitter.com/intent/tweet/?text=${ post.frontmatter.title }&url=https://mariohernandez.io${post.frontmatter.path}%2F&via=imariohernandez`} > <span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-twitter" > <path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" /> </svg> </span> <LinkLabel>Share on Twitter</LinkLabel> </ShareLink> </ShareItem> <ShareItem> <ShareLink href={`https://www.facebook.com/sharer/sharer.php?u=https://mariohernandez.io${ post.frontmatter.path }`} target="_blank" > <span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-facebook" > <path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" /> </svg> </span> <LinkLabel>Share on Facebook</LinkLabel> </ShareLink> </ShareItem> <ShareItem> <ShareLink href={`https://www.linkedin.com/shareArticle?mini=true&url=https://mariohernandez.io${ post.frontmatter.path }&title=${post.frontmatter.title}&source=${post.frontmatter.title}`} target="_blank" > <span> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-linkedin" > <path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z" /> <rect x="2" y="9" width="4" height="12" /> <circle cx="4" cy="4" r="2" /> </svg> </span> <LinkLabel>Share on LinkedIn</LinkLabel> </ShareLink> </ShareItem> </ShareSocial> </Share> In Closing

If you want to have a clean and light weight way to share your content with others, the Responsible Sharing Links may just be what you need.

Categories: FLOSS Project Planets

Mario Hernandez: How to build a card component

Planet Drupal - Sat, 2024-06-15 22:24

One of the most popular components on any website I've worked on is the "Card" component. Depending on the website, the card component is used to group various pieces of content into a container which can be used throughout your website.

Read the full article on how to build a card comonent.

Categories: FLOSS Project Planets

Mario Hernandez: Flexible Headings with Twig

Planet Drupal - Sat, 2024-06-15 22:24

Proper use of headings h1-h6 in your project presents many advantages incuding semantic markup, better SEO ranking and better accesibility.

Updated April 3, 2020

Building websites using the component based approach presents all kinds of advantages over the traditional page building approach. Today I’m going to show how to create what would normally be an Atom if we use the atomic design approach for building components. We are going to take this simple component to a whole new level by providing a way to dynamically controlling how it is rendered.

The heading component

Headings are normally used for page or section titles and are a big part of making your website SEO friendly. As simple as this may sound, headings need to be carefully planned. A typical heading would look like this:

<h1>This is a Heading 1</h1>

The idea of components is that they are reusable, but how can we possibly turn what already looks like a bare bones component into one that provides options and flexibility? What if we wanted to use a h2 or h3? or what if the title field is a link to another page? Then the heading component would probably not work because we have no way of changing the heading level from h1 to any other level or add a URL. Let's improve the heading component so we make it more dynamic.

Enter Twig and JSON

Twig offers many advantages over plain HTML and today we will use some logic to transform the static heading component into a more dynamic one.

Let’s start by creating a simple JSON object which we will use as data for Twig to consume. We will build some logic around this data to make the heading component more dynamic. This is typically how I build components on projects I work on.

  1. In your project, typically within the components/patterns directory create a new folder called heading
  2. Inside the heading folder create a new file called heading.json
  3. Inside the new file paste the code snippet below
{ "heading": { "heading_level": "", "modifier": "", "title": "This is the best heading I've seen!", "url": "" } }

So we created a simple JSON object with 4 keys: heading_level, modifier, title, and url.

  • The heading_level is something we can use to change the headings from say, h1 to h2 or h3 if we need to.
  • The modifier key allows us to pass a modifier CSS class when we make use of this component. The modifier class will make it possible for us to style the heading differently than other headings, if needed.
  • The title key is the title's string of text that will become the title of a page or a component.
  • ... and finally, the url key, if present, will allow us to wrap the title in an <a> tag, to make it a link.
  1. Inside the heading folder create a new file called heading.twig
  2. Inside the new file paste the code snippet below
<h{{ heading.heading_level|default('2') }} class="heading{{ heading.modifier ? ' ' ~ heading.modifier }}"> {% if heading.url %} <a href="{{ heading.url }}" class="heading__link"> {{ heading.title }} </a> {% else %} {{ heading.title }} {% endif %} </h{{ heading.heading_level|default('2') }}>

Wow! What's all this? 😮

Let's break things down to explain what's happening here since the twig code has changed significantly:

  • First we make use of heading.heading_level to complete the number part of the heading. If a value is not provided for heading_level in the JSON file, we are setting a default of 2. This will ensue that by default we will have a <h2> as the title, much better than <h1> as we saw before. This value can be changed every time the heading isused. The same approach is taken to close the heading tag at the last line of code.
  • Also, in addition to adding a class of heading, we check whether there is a value for the modifier key in JSON. If there is, we pass it to the heading as a CSS class. If no value is provided nothing will be added.
  • In the next line line, we check whether a URL was provided in the JSON file, and if so, we wrap the Flexible Headings with Twig variable in a <a> tag to turn the title into a link. The href value for the link is ``. If no URL is provided in the JSON file, we simply print the value of Flexible Headings with Twig as plain text.
Now what?

Well, our heading component is ready but unfortunately the component on its own does not do any good. The best way to take advantage of our super smart component is to start using it within other components.

Putting the heading component to use

As previously indicated, the idea of components is so they can be reusable which eliminates code duplication. Now that we have the heading component ready, we can reuse it in other templates by taking advantage of twig’s include statements. That will look like this:

<article class="card"> {% include '@components/heading/heading.twig' with { "heading": heading } only %} </article>

The example above shows how we can reuse the heading component in the card component by using a Twig’s include statement.

NOTE: For this to work, the same data structure for the heading needs to exist in the card’s JSON file. Or, you could also alter the heading's values in twig, like this:

<article class="card"> {% include '@components/heading/heading.twig' with { "heading": { "heading_level": 3, "modifier": 'card__title', "title": "This is a super flexible and smart heading", "url": "https://mariohernandez.io" } } only %} </article>

You noticed the part @components? this is only an example of a namespace. If you are not familiar with the component libraries Drupal module, it allows you to create namespaces for your theme which you can use to nest or include components as we see above.

End result

The heading component we built above would look like this when it is rendered:

<h3 class="heading card__title"> <a href="https://mariohernandez.io" class="heading__link"> This is a super flexible and smart heading </a> </h3> In closing

The main goal of this post is to bring light on how important it is to build components that are not restricted and can be used throughout the site in a way that does not feel like you are repeating yourself.

Additional Resources:

Managing heading levels in design systems.

Categories: FLOSS Project Planets

Pages