<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Erik Erskine</title>
    <description>I am a freelance software developer and consultant based in Stockholm, Sweden. I have 20 years professional experience covering desktop, web and mobile applications.
</description>
    <link>https://www.erskine.uk/blog</link>
    <atom:link href="https://www.erskine.uk/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 02 Aug 2025 19:29:28 +0000</pubDate>
    <lastBuildDate>Sat, 02 Aug 2025 19:29:28 +0000</lastBuildDate>
    <generator>Jekyll v3.3.0</generator>
    
      <item>
        <title>July Drupal contributions round-up</title>
        <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; July: lots of debugging, three comments linking a few issues together, and a sandbox module that I hope will soon be obsolete.&lt;/p&gt;

&lt;h2 id=&quot;layout-builder&quot;&gt;Layout builder&lt;/h2&gt;

&lt;p&gt;I had a Drupal project the other day involving layout builder and inline blocks. One such block had a media field that non-admins weren’t able to set until the page itself (and the block) had been saved first.&lt;/p&gt;

&lt;p&gt;You shouldn’t &lt;em&gt;need&lt;/em&gt; permission to create blocks in order to use layout builder. Except seemingly you do if you want layout builder and media to play nice together.&lt;/p&gt;

&lt;p&gt;There’s an &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/3106315&quot;&gt;open issue&lt;/a&gt; about it. However, the suggested workaround fudges the answer to &lt;em&gt;“can I create this thing?”&lt;/em&gt; depending on the circumstances:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// paraphrased:
function hook_ENTITY_TYPE_create_access() {
  if (/* inside a media library route? */) {
    return TRUE;
  }
  else {
    return /* whatever normally happens */;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I don’t like that, it feels like a second bug trying to cancel out the first.&lt;/p&gt;

&lt;p&gt;In this project there was no actual risk in granting the editors a few extra permissions. They are trusted and there isn’t any damage they can do with it. So the pragmatic thing to do is just to grant them and forget about it.&lt;/p&gt;

&lt;h2 id=&quot;group&quot;&gt;Group&lt;/h2&gt;

&lt;p&gt;But later we came across the same issue with the group module. You can’t use a media field properly if you don’t have permission to create entities of that field’s type.&lt;/p&gt;

&lt;p&gt;There’s an &lt;a href=&quot;https://www.drupal.org/project/group/issues/3071489&quot;&gt;open issue&lt;/a&gt; about that too, with an eerily similar workaround:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// paraphrased:
function hook_ENTITY_TYPE_create_access() {
  if (/* inside a media library route? */) {
    return TRUE;
  }
  else {
    return /* whatever normally happens */;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This time there is no easy way out: we aren’t able to just grant permissions and forget about it. We don’t want people creating things outside of their group.&lt;/p&gt;

&lt;h2 id=&quot;media-library&quot;&gt;Media library&lt;/h2&gt;

&lt;p&gt;I’ve tracked the bug down to the media library module, and lo and behold there is another &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/3327106&quot;&gt;open issue&lt;/a&gt; about that too. This one identifies the root cause, so any way forward is going to be a clumsy workaround.&lt;/p&gt;

&lt;p&gt;I’ve at least been able to tie these three issues together. Hopefully that will give other people who find these issues a bit of direction and prevent them going down rabbit holes. I’ve also created a &lt;a href=&quot;https://www.drupal.org/sandbox/erikerskine/3539092&quot;&gt;sandbox module&lt;/a&gt; as a temporary measure until the main issue can be resolved properly.&lt;/p&gt;
</description>
        <pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2025/july-contribution-round-up</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2025/july-contribution-round-up</guid>
        
        <category>drupal</category>
        
        
      </item>
    
      <item>
        <title>Adding GitLab CI to one of my contrib modules</title>
        <description>&lt;p&gt;I had a few hours free at the end of January to get GitLab CI enabled for the &lt;a href=&quot;https://www.drupal.org/project/daterange_compact&quot;&gt;daterange_compact&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;When I first wrote this module, Drupal projects could opt-in to automated PHPUnit testing. This module has always had plenty of test coverage, so having tests run for every patch was so useful. Somewhere along the line something changed and the automated tests stopped running (though the tests themselves still were still fine). Nowadays, projects can have their own &lt;a href=&quot;https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal/gitlab-ci&quot;&gt;GitLab CI pipeline&lt;/a&gt;, but I’d never got around to setting that up.&lt;/p&gt;

&lt;p&gt;The first task to fail was cspell, a spell checker for comments. I had no idea such a thing existed, but I soon found out.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Drupal Core uses US English spelling for all source code, including comments and names.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🤷 Whatever:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;- * behaviour of that field formatter, and the default configuration.
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+ * behavior of that field formatter, and the default configuration.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Pick your battles.&lt;/p&gt;

&lt;p&gt;Next was the PHP code sniffer. It checks coding standards, comments, whitespace etc. I tidied up a few things here, adding some missing functions comments and fixing whitespace issues.&lt;/p&gt;

&lt;p&gt;Sometimes, it’s ok to be &lt;del&gt;a bit lazy&lt;/del&gt;pragmatic:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// phpcs:ignore
private static function isSameDay($start_timestamp, $end_timestamp, $timezone): bool
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The last error was more interesting. &lt;em&gt;An actual test failure!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What?! These tests have been working for years, and I’ve always been dilligent to run them locally whenever I work on this project. Moreover, they still &lt;em&gt;passed&lt;/em&gt; locally. But one of them failed on GitLab CI.&lt;/p&gt;

&lt;p&gt;The offender was a test that makes sure we revert to a fallback date format when something isn’t available. And that &lt;a href=&quot;https://www.drupal.org/node/3467774&quot;&gt;fallback date format has changed&lt;/a&gt; in Drupal 11.1. I was still running 11.0.&lt;/p&gt;

&lt;p&gt;So the test needed updating to match. It now expects different results depending on what version of Drupal is in use.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$expected = \Drupal::VERSION &amp;gt;= '11.1'
  ? 'Thu, 1 Jan 1970 - 10:00 - Thu, 1 Jan 1970 - 11:00'
  : 'Thu, 01/01/1970 - 10:00 - Thu, 01/01/1970 - 11:00';
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It was good to get the CI pipeline working. As a bonus, I got to write a line of code too!&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Feb 2025 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2025/adding-gitlab-ci</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2025/adding-gitlab-ci</guid>
        
        <category>drupal</category>
        
        
      </item>
    
      <item>
        <title>August Drupal contributions round-up</title>
        <description>&lt;p&gt;A new release of the &lt;strong&gt;compact date/time range formatter&lt;/strong&gt; module, with support for &lt;a href=&quot;https://www.drupal.org/project/optional_end_date&quot;&gt;optional end dates&lt;/a&gt;. We now support core’s datetime &amp;amp; timestamp fields now, not just ranges, which makes sense if you want to render “3:00pm” as “3pm”.&lt;/p&gt;

&lt;p&gt;Dived down a &lt;a href=&quot;https://www.drupal.org/node/953034&quot;&gt;rabbit hole&lt;/a&gt; about
&lt;strong&gt;regions being rendered even when they are empty&lt;/strong&gt;.
This &lt;a href=&quot;https://github.com/localgovdrupal/localgov_base/issues/597&quot;&gt;affects LocalGov Drupal too&lt;/a&gt;.
I don’t think this is something that can be “fixed”, per-se.
It’s more a case of choosing between performance and design accuracy.
I’ve started a &lt;a href=&quot;https://www.drupal.org/sandbox/ingaro/3471436&quot;&gt;sandbox module&lt;/a&gt; to expose that choice.&lt;/p&gt;
</description>
        <pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2024/august-contribution-round-up</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2024/august-contribution-round-up</guid>
        
        <category>drupal</category>
        
        
      </item>
    
      <item>
        <title>On giving Drupal development advice</title>
        <description>&lt;p&gt;In my &lt;a href=&quot;https://www.drupal.org&quot;&gt;Drupal&lt;/a&gt; development and consultancy work, I often advise development teams on how best to implement something in Drupal.
I was asked recently how I—perhaps as the sole Drupal specialist in the room—validate the advice I give.
I hadn’t really articulated that before, so I wrote down some thoughts.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;With Drupal, there are often many ways to acheive the same end.
If you’re only familiar with part of it, it’s natural to look there for a solution.
But you might miss out on something easier.&lt;/p&gt;

&lt;p&gt;So first of all I’ll strive to put possible options on the table that I think might work.
That’s good for getting the nitty gritty of what a client really needs; when you propose options, some will make sense, others won’t.
Ideally we’ll get to a point where one option just naturally stands out from the rest.&lt;/p&gt;

&lt;p&gt;Let’s take an example: Drupal has templates for different content types.
If you want a piece of content to appear differently depending on it’s context, you might be tempted to put some branching logic into the template.
That’s ok, but Drupal content types also have &lt;em&gt;view modes&lt;/em&gt; precisely for this scenario: a &lt;em&gt;full page&lt;/em&gt; event, or the &lt;em&gt;teaser&lt;/em&gt; of an article.
Each content type/view mode combination has it’s own template, so you can skip the extra logic (plus the requisite twig syntax knowledge).&lt;/p&gt;

&lt;p&gt;This kind of thing makes sense when you’re aware of it, but isn’t always obvious at first.
By revealing it, the team is empowered to make that choice themselves.&lt;/p&gt;

&lt;p&gt;Beyond that, I consider these principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Is this as simple as it can be?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m a big believer in progressive enhancement.
Start by doing the simplest thing that does the job and then enhance it according to time/budget/desire etc.
On the web, progressive enhancement is often misunderstood: “must work on IE6 and no javascript is allowed!” is a common misconception.
But, when you can acheive what you want by serving up HTML content at a given URL, why not start there?
Add the JavaScript as an when it’s needed and makes things better.
The end result is much more resilient to unexpected circumstances (and obscure browsers).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Is this familiar?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any code we produce needs to be maintained.
Depending on one’s experience and knowledge of a platform, it can be advantageous to stick to what is already known, and what’s used in the rest of the project.
Perhaps that means using a GUI-based layout manager over custom templates.
Or an atomic CSS methodology like &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; over a global one like &lt;a href=&quot;http://getbem.com/introduction&quot;&gt;BEM&lt;/a&gt;.
Or PHP over complex SQL queries.
Sometimes it’s better to eschew the most technically superior methods (or the ones we think are superior!) for familiarity and consistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is this replaceable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This might sound like a strange one, after all we’re trying to produce code that brings long term value.
Shouldn’t we be striving to build reusable code instead?
Yes, but in my experience a piece of code becomes more reliable when its broken down into small discrete pieces, boundaries and dependencies clearly defined, and the implementation ring-fenced. It’s also more testable that way. If what we’ve built does need to change, that’s a lot easier and less costly to do, or swap out for something better without the burden of much technical debt.&lt;/p&gt;

&lt;p&gt;That’s by no means an exhaustive list, but in general they can be summarised as trying to keep things as simple as possible.&lt;/p&gt;

</description>
        <pubDate>Fri, 29 Jan 2021 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2021/giving-drupal-development-advice</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2021/giving-drupal-development-advice</guid>
        
        <category>drupal</category>
        
        
      </item>
    
      <item>
        <title>ffconf 2017</title>
        <description>&lt;p&gt;&lt;strong&gt;Web components meet 8-bit video games&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recently attended &lt;a href=&quot;https://2017.ffconf.org/&quot;&gt;ffconf&lt;/a&gt; - a conference in Brighton for web developers. This is the first time I’ve been. The event is broad and covers general topics about the web as a whole, rather than covering the latest trends in web development. That’s what drew me to it in the first place; as someone who has been primarily involved in “backend” development but wants to diversify a bit, I wanted something that I was sure would be a worthwhile investment, rather than a gamble on a framework I may never use.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;It’s held once a year at the &lt;a href=&quot;https://www.picturehouses.com/cinema/Duke_Of_Yorks&quot;&gt;Duke of York’s cinema&lt;/a&gt; in Brighton, so I didn’t have far to go. We were welcomed with delicious pastries and a &lt;a href=&quot;https://twitter.com/ffconf/status/928628383810023424&quot;&gt;classic 80’s playlist&lt;/a&gt; which set the bar high for what was to come.&lt;/p&gt;

&lt;p&gt;The rest the day didn’t disappoint. I made a &lt;em&gt;lot&lt;/em&gt; of notes during the day, and it’s been helpful to go over them afterwards and write them up here.&lt;/p&gt;

&lt;p&gt;Big thanks to &lt;a href=&quot;https://remysharp.com/&quot;&gt;Remy&lt;/a&gt; and &lt;a href=&quot;http://twitter.com/julieanne&quot;&gt;Julie&lt;/a&gt; for organising such a great event - I got a lot out of it.&lt;/p&gt;

&lt;div class=&quot;aspect-ratio-container-16x9&quot;&gt;
  &lt;img src=&quot;/assets/images/blog/2017/ffconf1-768.jpg&quot; alt=&quot;View of ffconf audience&quot; /&gt;
&lt;/div&gt;

&lt;h2 id=&quot;session1&quot;&gt;1. Rethinking the Web Platform&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://thejameskyle.com/&quot;&gt;James Kyle&lt;/a&gt; spoke on the conflicts between making progress and technical correctness when learning web development. He helped a friend create an app using &lt;a href=&quot;https://github.com/facebookincubator/create-react-app&quot;&gt;React&lt;/a&gt; without gaining any experience with HTML or CSS first.&lt;/p&gt;

&lt;p&gt;He felt strongly that &lt;strong&gt;statements of the form &lt;em&gt;“you need to learn … first”&lt;/em&gt; were off-putting to new developers&lt;/strong&gt;. Instead, he saw all web sites as a set of components, and advocated for getting started with a framework early on that could provide those and get a project up and running in it’s entirety, at the expense of having parts you don’t understand.&lt;/p&gt;

&lt;p&gt;James’ argument was that we need to rethink our approach to teaching, embracing tooling early on and placing a lot more emphasis on getting a result quickly. Only then should you go back and start learning individual parts in more depth. &lt;strong&gt;To really learn and understand a topic you need an appreciation of where it fits within the wider picture&lt;/strong&gt;, and an understanding of how it relates to other things.&lt;/p&gt;

&lt;p&gt;I was intrigued by this talk. Much of it resonated with me; some things I disagreed with; overall it was thought provoking. It was a challenge to hear that as an industry it’s not easy for new developers to pick things up. But I still think we need to be careful with tooling though, particularly a large framework like React. I think that comes too soon, learning a tool before knowing what that tool does for you.&lt;/p&gt;

&lt;p&gt;I’ve taken away some things from this talk for use when I teach things. I like to learn in a thorough, methodical, bottom-up way, usually &lt;em&gt;outside of a real world project&lt;/em&gt;. It’s inevitable that’s also going to be reflected in how I teach. But not everyone learns that way. Many people are happier with a big picture that they can delve into from the top-down, gradually understanding more and more. The challenge is to fit in with them, not the other way round.&lt;/p&gt;

&lt;h2 id=&quot;session2&quot;&gt;2. If you’re going out of San Francisco, be sure to wear Web Standards in your hair&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/brucel/ffconf-brighton-9-10-november-2017&quot;&gt;Slides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.brucelawson.co.uk/&quot;&gt;Bruce Lawson&lt;/a&gt; started by looking at some of the holy wars in web development. All industries have them, and our current one seems to be CSS vs CSS-in-JS. Pure use of the fundamentals of web development (HTML, CSS &amp;amp; JavaScript) is often out of line with what happens in the real world. CSS for components is difficult - everything is global, the order matters, and the CSS is tightly coupled to the structure of your HTML. But CSS-in-JS is only a methodology, not a new technology, and similar things have existed before.&lt;sup id=&quot;fnref:netscape-jsss&quot;&gt;&lt;a href=&quot;#fn:netscape-jsss&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We risk locking out many great designers who understand CSS if we use a different model just for our own convenience.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There’s also a performance cost. Bruce spoke of some of the work he’s doing with Wix. Traditionally everything was built with JavaScript. That’s been ok–performance wise–on desktops, but now has real problems on mobile. They are working on a project called &lt;a href=&quot;https://stylable.io/&quot;&gt;Stylable&lt;/a&gt; to address this.&lt;/p&gt;

&lt;p&gt;Utimately though, the web isn’t about computers or clouds, it’s about people. Technology is only worth caring about because it facilitates the communication between people across the world. During the talk Bruce shared the story of his diagnosis of MS and his first use of the internet as a result. Whilst abroad he was able to get online in an internet cafe, finding forums and information about the condition. Some of the founders of those sites have become lifelong friends.&lt;/p&gt;

&lt;p&gt;Bruce went on to give some illustrations of the digital divide. In Africa, the lack of locally relevant content and digital skills are some of the biggest barriers to internet adoption.&lt;sup id=&quot;fnref:africa-barriers-to-adoption&quot;&gt;&lt;a href=&quot;#fn:africa-barriers-to-adoption&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;.
Cost is often prohibitive. In Nigeria, the bandwidth for a 2 minute video clip can cost more than sending a child to school for a month.&lt;sup id=&quot;fnref:nigeria-school-cost&quot;&gt;&lt;a href=&quot;#fn:nigeria-school-cost&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
Landlocked African countries typically incur an extra $232 to the cost of monthly broadband.&lt;sup id=&quot;fnref:landlocked-broadband-cost&quot;&gt;&lt;a href=&quot;#fn:landlocked-broadband-cost&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The web can provide opportunities for the poor, particularly for women, older people and disabled people, who generally benefit more from flexible online working. Progressive web apps are thriving in Asia and Africa, where native apps are too big.&lt;/p&gt;

&lt;p&gt;The web is a lifeline for people in backward and despotic regimes.
But we need to be more responsible in what we do, and ensure large companies like Google, Facebook, Twitter and Amazon are held to account in the way they make profits.&lt;/p&gt;

&lt;p&gt;As our industry professionalises, we mustn’t lose the sense of craftsmanship, and the artisan mentality of caring about the end product. This is perhaps best described by the Japanese idea of &lt;a href=&quot;https://en.wikipedia.org/wiki/Artisan#Shokunin&quot;&gt;shokunin&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
Shokunin means not only having technical skill, but also implies an attitude of social consciousness... a social obligation to work best for the general wefare of the people, an obligation both material and spiritual.
&lt;/blockquote&gt;

&lt;p&gt;If you haven’t already, I’d recommend reading Bruce Lawson’s article &lt;em&gt;&lt;a href=&quot;https://www.smashingmagazine.com/2017/03/world-wide-web-not-wealthy-western-web-part-1/&quot;&gt;World Wide Web, Not Wealthy Western Web&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;session3&quot;&gt;3. How to train your &lt;del&gt;dragon&lt;/del&gt; web standards&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://meowni.ca/&quot;&gt;Monica Dinculescu&lt;/a&gt; talked about how web standards come to be. Web standards are a compromise between browser vendors. We need them to stop browsers copying each other’s bugs.&lt;/p&gt;

&lt;p&gt;Not all standards have been sucessful - &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;input type='date'&amp;gt;&lt;/code&gt; for example. Although a popular request, it turns out date pickers are a hard thing to standardise because of the variety of ways people use them. Which day of the week to start on? How do you omit day, month or year? How do you style it?&lt;/p&gt;

&lt;p&gt;This example hasn’t worked because it was an attempt to standardise the &lt;em&gt;component&lt;/em&gt; rather than the &lt;em&gt;component model&lt;/em&gt;. The date picker is too high level. &lt;strong&gt;Web standards should focus on low level capabilities of browsers, leaving space for libraries and frameworks to build on this.&lt;/strong&gt; WebGL is a good example - it facilitates graphics on the web, and tools like &lt;a href=&quot;https://d3js.org/&quot;&gt;D3.js&lt;/a&gt; exist because of it.&lt;/p&gt;

&lt;p&gt;A standards process should have these properties:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;it should cover something that browsers already do&lt;/li&gt;
  &lt;li&gt;it should document and explain any existing browser magic&lt;/li&gt;
  &lt;li&gt;it should be extendable&lt;/li&gt;
  &lt;li&gt;people should already use and like the feature&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Monica went on to talk about one particular standard - &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Web_Components&quot;&gt;web components&lt;/a&gt;. The idea behind web components was born out of a desire to reduce the amount of copying &amp;amp; pasting JavaScript to produce common widgets, like date pickers. Web components are an attempt to standardise ideas behind many different models, rather than any one implementation. They provide the low level functionality for something like a date picker to exist - a lifecycle and integration with the DOM.&lt;/p&gt;

&lt;p&gt;Despite the &lt;a href=&quot;https://extensiblewebmanifesto.org/&quot;&gt;extensible web manifesto&lt;/a&gt; being made in 2013, progress has been slow. It’s a catch-22: developers are hesitant to use features that browsers haven’t fully implemented, and browser vendors won’t implement things people don’t use. Polyfills (named after &lt;a href=&quot;http://www.polycell.co.uk/product/polycell-multi-purpose-polyfilla-ready-mixed/&quot;&gt;Polyfilla&lt;/a&gt;!) are the answer to this. They are–quite literally–a way to plug holes in existing browsers. We shouldn’t be scared of using them!&lt;/p&gt;

&lt;h2 id=&quot;session4&quot;&gt;4. Lessons learned sciencing the web&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/addyosmani/lessons-learned-sciencing-the-web&quot;&gt;Slides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://addyosmani.com/&quot;&gt;Addy Osmani&lt;/a&gt; looked at using data science to help with performance on the web.&lt;/p&gt;

&lt;p&gt;Mobile devices are resource constrained. On a 3G connection, 600ms is taken up with the network overhead. The server response adds another 200ms, only after which can client side rendering take place. So it’s cruicial to &lt;strong&gt;load only what you need, when you need it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;JavaScript comes with a cost due to parsing and compiling. So don’t just consider download size - &lt;em&gt;100kb of JavaScript is not the same as 100kb of JPEG&lt;/em&gt;. We can do a lot by removing unused code, or deferring what we don’t need for the current request and lazy loading it later. An average phone (eg Moto G4) will parse 1Mb of JavaScript in 1-2 seconds, but this can vary from almost nothing on an iPhone 8 to over 6 seconds on a low-end device.&lt;sup id=&quot;fnref:javascript-parse-costs&quot;&gt;&lt;a href=&quot;#fn:javascript-parse-costs&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Addy explained the PRPL pattern&lt;sup id=&quot;fnref:prpl&quot;&gt;&lt;a href=&quot;#fn:prpl&quot; class=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;: &lt;em&gt;push&lt;/em&gt; the minimal code for the initial route, &lt;em&gt;render&lt;/em&gt; it and become interactive as soon as possible. Then &lt;em&gt;pre cache&lt;/em&gt; remaining resources using a service worker and &lt;em&gt;lazy load&lt;/em&gt; anything else. He went on to give examples of this from CNN and Pinterest.&lt;/p&gt;

&lt;p&gt;It’s also important to examine your caching. &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;link rel=&quot;preload&quot;&amp;gt;&lt;/code&gt; is a way to specify that resources should be fetched early on. We can use this for any type of resource, not just CSS and JS. There are also &lt;code class=&quot;highlighter-rouge&quot;&gt;prefetch&lt;/code&gt; (loads but doesn’t parse), &lt;code class=&quot;highlighter-rouge&quot;&gt;dns-prefetch&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;preconnect&lt;/code&gt; declarations.&lt;/p&gt;

&lt;p&gt;These declarative ways provide hints to the browser about ways in which performance might be improved. They are a lot easier to debug and reason about than something like HTTP2 server push. Although push can be powerful when you know what resources are needed, it isn’t cache aware, so the browser may download more than it needs to.&lt;/p&gt;

&lt;p&gt;Images qaulity can make a big difference too. &lt;code class=&quot;highlighter-rouge&quot;&gt;q=80&lt;/code&gt; is a good baseline for JPEGs.&lt;sup id=&quot;fnref:jpeg-quality&quot;&gt;&lt;a href=&quot;#fn:jpeg-quality&quot; class=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt; Tools like &lt;a href=&quot;https://imageoptim.com&quot;&gt;ImageOptim&lt;/a&gt; and &lt;a href=&quot;https://github.com/mozilla/mozjpeg&quot;&gt;mozjpeg&lt;/a&gt; can give you a comparison of quality levels and their respective file sizes. Addy has also written a &lt;a href=&quot;https://images.guide/&quot;&gt;guide to image optimisation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you’re using webfonts, have a loading strategy. Preload can bring a big improvement; you don’t have to waiting for the CSS to finish loading. But if you can’t load fonts quickly, don’t load them at all. The &lt;code class=&quot;highlighter-rouge&quot;&gt;font-display&lt;/code&gt; property dictates how long a browser should wait for a font to load–if at all–before showing a fallback.&lt;/p&gt;

&lt;p&gt;Finally, it’s important to introduce workflows that force everybody to think about load times from the beginning. Performance budgets are a useful constraint to have.&lt;/p&gt;

&lt;h2 id=&quot;session5&quot;&gt;5. My password doesn’t work&lt;/h2&gt;

&lt;p&gt;After lunch, &lt;a href=&quot;https://twitter.com/blaine&quot;&gt;Blaine Cook&lt;/a&gt; looked at passwords.&lt;/p&gt;

&lt;p&gt;Much of his talk originated out of frustration in helping a friend–who is a nomadic guide in the Jordan desert–set up email on his phone. There were many hoops to jump through, not least configuring an iTunes account and billing address. Why did Apple need this information for someone to download free apps? What data did they really need? And why do password restrictions seem so arbitrary?&lt;/p&gt;

&lt;p&gt;Email addresses are so often used as a primary means of identifying someone. And if that service also uses a password, it can usually be reset via email, thereby rendering the account only as secure as the email account.&lt;/p&gt;

&lt;p&gt;Blain gave an overview of the forgotton-password-reset-email dance that we encounter so regularly. This process is only a means of verifying that someone is who they say they are.&lt;/p&gt;

&lt;p&gt;He advocated for third party sign-in as a good way to improve your conversion, arguing that if you’re going to provide a password reset mechanism over email, you might as well just use that email provider as your authentication provider. This can improve conversion, because there’s one less password for a user to remember.&lt;/p&gt;

&lt;p&gt;I’m always skeptical about logging in with Google/Twitter though! I think there’s some more information needed on the possible privacy implications of doing this.&lt;/p&gt;

&lt;p&gt;There were some pointers for web developers too:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;don’t require an account at all if you can help it&lt;/li&gt;
  &lt;li&gt;if not, leave sign in as late on in the process&lt;/li&gt;
  &lt;li&gt;long lived sessions reduce the number of times users need to enter passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we should all take care to keep our primary email secure. So much is accessible via it.&lt;/p&gt;

&lt;h2 id=&quot;session6&quot;&gt;6. Don’t forget to take out the garbage&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.katiefenn.co.uk/&quot;&gt;Katie Fenn&lt;/a&gt; gave a overview of garbage collection in JavaScript, and illustrated how Chrome dev tools can highlight memory usage.&lt;/p&gt;

&lt;p&gt;Memory is a finite resource, and apps will find a way to consume it. Memory is particularly constrained on older devices.&lt;/p&gt;

&lt;p&gt;Languages like C require manual memory management - you ask for memory by calling the &lt;code class=&quot;highlighter-rouge&quot;&gt;malloc&lt;/code&gt; function and return it for reuse by calling &lt;code class=&quot;highlighter-rouge&quot;&gt;free&lt;/code&gt;. Unlike C, we don’t have to do this in JavaScript. The browser keeps track of what memory are in use and can release it as required.&lt;/p&gt;

&lt;p&gt;JavaScript scope captures variables declared with &lt;code class=&quot;highlighter-rouge&quot;&gt;var&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;let&lt;/code&gt; or &lt;code class=&quot;highlighter-rouge&quot;&gt;const&lt;/code&gt; and makes them available to the functions within that scope. Once the scope no longer exists, the variables are no longer reachable, and can be garbage collected.&lt;/p&gt;

&lt;p&gt;The memory heap is divided into two sections - a young generation area and an older generation area. The younger generation is small and fast, for short lived, frequently used data. The older generation is for longer lasting data.&lt;/p&gt;

&lt;p&gt;Variables start off pointing to space in the young generation segment. Once that space is full, a minor garbage collection occurs. All the unreachable variables are considered dead, and can be discarded. Anything still alive after two minor garbage collections then gets moved to the older generation. A major garbage collection marks dead objects, sweeps the memory and compacts it, freeing it up for future use.&lt;/p&gt;

&lt;p&gt;JavaScript does a good job managing memory for us, but there are some potential pitfalls. If we aren’t careful, variables can remain referenced longer than we expect, and end up not being released. This is a memory leak, for which there are a few common causes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;accidental global variables caused by a missing &lt;code class=&quot;highlighter-rouge&quot;&gt;var&lt;/code&gt; - these will have the global scope rather than the function’s scope, so they will end up living indefinitely&lt;/li&gt;
  &lt;li&gt;forgotton timers and event listeners also keep a scope alive&lt;/li&gt;
  &lt;li&gt;detached DOM nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Katie illustrated using the Chrome task manager and dev tools to highlight memory usage. These give you an overview of the memory footprint of each tab, and an indicator of what objects are in use.&lt;/p&gt;

&lt;h2 id=&quot;session7&quot;&gt;7. Abstract art in a time of minification&lt;/h2&gt;

&lt;p&gt;Software is for humans. It’s easy to forget or ignore that sometimes. &lt;strong&gt;To be a good engineer, you have to be a good member of society.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jennmoney.biz/&quot;&gt;Jenn Schiffer&lt;/a&gt; learned by viewing the source of web pages. Nowadays, that method of exploring other’s work isn’t always enough to learn how things work; a lot of what we do is hidden behind developer tools. In treating those tools as &lt;em&gt;only for developers&lt;/em&gt; have we raised barriers to entry for people? Have we unwittingly made it more difficult to learn to code?&lt;/p&gt;

&lt;p&gt;There’s a sense of ephemerality about what we do online now. Things we produce and the tools we use to do so are often at the mercy of startups who disappear at any moment without our consent, leaving a steam of 404s in their wake. Ownership is another issue - who really owns your data? Which companies can you trust with it and what will they use it for? Do we understand our rights and what licences like GPL and MIT give us? Can we educate people about this?&lt;/p&gt;

&lt;p&gt;In a capitalist society, companies have an incentive to do things &lt;em&gt;faster&lt;/em&gt;, but not always &lt;em&gt;better&lt;/em&gt;. Computers are very good at doing this fast, but not all problems are best solved with speed. Many problems require empathy, and if we’re not careful technology can often make things worse here. Jenn gave examples of how software can introduce racial bias to law enforcement.&lt;sup id=&quot;fnref:wired-law-enforcement&quot;&gt;&lt;a href=&quot;#fn:wired-law-enforcement&quot; class=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt; &lt;sup id=&quot;fnref:gizmodo-law-enforcement&quot;&gt;&lt;a href=&quot;#fn:gizmodo-law-enforcement&quot; class=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Like the art world, the tech community has a problem with diversity and representation. But we make this worse for ourselves when we consider problems like accessibility as “too hard to solve”. Why do we argue this? Ok, it may take a tiny bit of work to understand the difference between a &lt;code class=&quot;highlighter-rouge&quot;&gt;div&lt;/code&gt; and a &lt;code class=&quot;highlighter-rouge&quot;&gt;button&lt;/code&gt;, but is that not worth it to avoid leaving technical debt for those less fortunate than ourselves.&lt;/p&gt;

&lt;blockquote&gt;
  That optimistic belief that every problem can be fixed sometimes leads people making tech to think they’re the only ones who can fix things. And all of these problems are severely exacerbated by tech’s persistent failings at inclusion, which means these problems affect marginalized communities even more acutely.
  &lt;br /&gt;
  &lt;cite&gt;Anil Dash &lt;sup id=&quot;fnref:anil-dash-quote&quot;&gt;&lt;a href=&quot;#fn:anil-dash-quote&quot; class=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jenn left us with a challenge: What’s your legacy? Think! talk! Widen and diversify your network, and become role models.&lt;/p&gt;

&lt;h2 id=&quot;session8&quot;&gt;8. Alpha, beta, gamer: dev mode&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://www.joehartcomedy.com/&quot;&gt;Joe Hart&lt;/a&gt;, descibed as “a comedian who codes”, closed the conference with something very different.&lt;/p&gt;

&lt;p&gt;Regaling us with tales of his unfortunate Sims characters and their various demises, through communist Tetris to a nuclear weapon-wielding Ghandi (it happens when you represent &lt;em&gt;agressiveness&lt;/em&gt; using an unsigned int…), he then got the audience playing games of cheering controlled Flappy Birds and home drawn Super Mario.&lt;/p&gt;

&lt;p&gt;If you like get the chance to see this show, do! It was a lot of fun, and nice way to end a thoroughly useful and enjoyable day.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:netscape-jsss&quot;&gt;
      &lt;p&gt;Netscape 4 had a form of stylesheets called &lt;a href=&quot;https://en.wikipedia.org/wiki/JavaScript_Style_Sheets&quot;&gt;JSSS&lt;/a&gt;. &lt;a href=&quot;#fnref:netscape-jsss&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:africa-barriers-to-adoption&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/brucel/ffconf-brighton-9-10-november-2017?slide=107&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:africa-barriers-to-adoption&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:nigeria-school-cost&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/brucel/ffconf-brighton-9-10-november-2017?slide=119&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:nigeria-school-cost&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:landlocked-broadband-cost&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/brucel/ffconf-brighton-9-10-november-2017?slide=101&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:landlocked-broadband-cost&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:javascript-parse-costs&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/addyosmani/lessons-learned-sciencing-the-web?slide=34&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:javascript-parse-costs&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:prpl&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/addyosmani/lessons-learned-sciencing-the-web?slide=36&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:prpl&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:jpeg-quality&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://speakerdeck.com/addyosmani/lessons-learned-sciencing-the-web?slide=115&quot;&gt;Slide&lt;/a&gt; &lt;a href=&quot;#fnref:jpeg-quality&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:wired-law-enforcement&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.wired.com/2017/04/courts-using-ai-sentence-criminals-must-stop-now/&quot;&gt;Courts are using AI to sentence criminals. That must stop now&lt;/a&gt; - wired.com &lt;a href=&quot;#fnref:wired-law-enforcement&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:gizmodo-law-enforcement&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://gizmodo.com/state-governments-use-racially-biased-software-to-predi-1778191307&quot;&gt;State Governments Use Racially Biased Software to Predict Crimes&lt;/a&gt; - gizmodo.com &lt;a href=&quot;#fnref:gizmodo-law-enforcement&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:anil-dash-quote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://medium.com/make-better-software/software-matters-in-the-world-f24d25b255d7&quot;&gt;Software Matters in the World&lt;/a&gt; &lt;a href=&quot;#fnref:anil-dash-quote&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 25 Nov 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/ffconf</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/ffconf</guid>
        
        
      </item>
    
      <item>
        <title>The map is not the territory</title>
        <description>&lt;p&gt;I recently attended an excellent workshop on OpenStreetMap led by &lt;a href=&quot;https://twitter.com/jnicho02&quot;&gt;Jez Nicholson&lt;/a&gt;, and hosted by the &lt;a href=&quot;https://www.meetup.com/Data-Visualisation-Brighton/&quot;&gt;Data Visualisation Brighton meetup&lt;/a&gt;.
Entitled &lt;em&gt;&lt;a href=&quot;https://www.meetup.com/Data-Visualisation-Brighton/events/244169877/&quot;&gt;The map is not the territory&lt;/a&gt;&lt;/em&gt;, we looked at maps beyond being something that’s purely visual, and delved into the data behind them.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;www.openstreetmap.org&quot;&gt;OpenStreetMap&lt;/a&gt; was started in 2004&lt;sup id=&quot;fnref:osm-history&quot;&gt;&lt;a href=&quot;#fn:osm-history&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; and is often referred to as ‘the Wikipedia of maps’. The data is openly available, owned by all contributors and moderated by peers.&lt;/p&gt;

&lt;p&gt;I’ve used OpenStreetMap many times before, but never done any editing. During the workshop we logged in and I suddenly found myself exploring a whole new world of information!&lt;/p&gt;

&lt;h2 id=&quot;lots-more-than-meets-the-eye&quot;&gt;Lots more than meets the eye&lt;/h2&gt;

&lt;p&gt;The first thing that strikes me is the vast amout more detail available than you normally see. Of course, it isn’t possible to display everything on the browseable map, but &lt;em&gt;that doesn’t mean it isn’t there&lt;/em&gt;. What you see when browsing a slippy map like the one on &lt;a href=&quot;www.openstreetmap.org&quot;&gt;openstreetmap.org&lt;/a&gt; is just a subset deemed the most suitable for a general audience.&lt;/p&gt;

&lt;figure class=&quot;screenshot-container-1920x1200&quot;&gt;
  &lt;img sizes=&quot;(max-width: 50rem) calc(100vw - 2rem), 48rem&quot; srcset=&quot;/assets/images/blog/2017/osm-edit-288.jpg 288w,
               /assets/images/blog/2017/osm-edit-576.jpg 576w,
               /assets/images/blog/2017/osm-edit-686.jpg 686w,
               /assets/images/blog/2017/osm-edit-768.jpg 768w,
               /assets/images/blog/2017/osm-edit-984.jpg 984w,
               /assets/images/blog/2017/osm-edit-1536.jpg 1536w,
               /assets/images/blog/2017/osm-edit.jpg 1920w&quot; src=&quot;/assets/images/blog/2017/osm-edit-720.jpg&quot; alt=&quot;The OpenStreetMap website in edit mode. A tennis court in Hove Park is highlighted, with tags denoting an asphalt surface and an unknown lit status.&quot; class=&quot;screenshot&quot; /&gt;
  &lt;figcaption&gt;Editing a feature on OpenStreetMap.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;everything-is-made-up-of-shapes-and-tags&quot;&gt;Everything is made up of shapes and tags&lt;/h2&gt;

&lt;p&gt;The format of OpenStreetMap data is relatively simple. Lines, points and polygons form the basic shapes from which all physical features are recorded. Then each of these items is has one or more tags identifying it.&lt;sup id=&quot;fnref:map-features&quot;&gt;&lt;a href=&quot;#fn:map-features&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; These tags have evolved dynmaically over time, no-one sat down at the beginning and worked out what all the tags were going to be. I find this rather fitting for the web. In the same way HTML has grown and adapted over time, the vocabulary of OpenStreetMap–and it’s usefulness–grows as more and more people contribute to it.&lt;/p&gt;

&lt;p&gt;During the workshop I made a small edit, marking two bus stops as having a bench and/or shelter.&lt;sup id=&quot;fnref:edit&quot;&gt;&lt;a href=&quot;#fn:edit&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; A trivial change, but nice to come away having done something real and tangible.&lt;/p&gt;

&lt;h2 id=&quot;what-about-ordnance-survey--google&quot;&gt;What about Ordnance Survey &amp;amp; Google?&lt;/h2&gt;

&lt;p&gt;This organic growth is where OpenStreetMap differs from more “official” sources of mapping like &lt;a href=&quot;https://www.ordnancesurvey.co.uk/&quot;&gt;Ordnance Survey&lt;/a&gt;. These organisations aim for very high consistency and accuracy, which they acheive at greater cost and a slower feedback cycle. Commercial providers have their own goals–&lt;a href=&quot;https://www.google.co.uk/maps&quot;&gt;Google maps&lt;/a&gt; and street view enhance their search offering.&lt;/p&gt;

&lt;p&gt;These alternatives are fundamentally closed entities - you can’t alter the data, for good or bad. And both of these data sets come with terms and conditions dictating how you can use them.&lt;/p&gt;

&lt;p&gt;There’s certainly room for both though. There are times when you want the assurance of thorough, unbiased surveying that’s gone into something like an OS map. You’d certainly take that sort of map out hiking, for example. But the existence of a global, freely-available rich dataset offers up so much potential and it’s exciting to see what can be done.&lt;/p&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:osm-history&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;http://wiki.openstreetmap.org/wiki/History_of_OpenStreetMap&quot;&gt;History of OpenStreetMap&lt;/a&gt; - wiki.openstreetmap.org &lt;a href=&quot;#fnref:osm-history&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:map-features&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;http://wiki.openstreetmap.org/wiki/Map_Features&quot;&gt;Map Features&lt;/a&gt; - wiki.openstreetmap.org &lt;a href=&quot;#fnref:map-features&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:edit&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.openstreetmap.org/changeset/53217525&quot;&gt;Changeset 53217525&lt;/a&gt; &lt;a href=&quot;#fnref:edit&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Mon, 06 Nov 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/the-map-is-not-the-territory</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/the-map-is-not-the-territory</guid>
        
        
      </item>
    
      <item>
        <title>When your composer build breaks</title>
        <description>&lt;p&gt;Yesterday a project on github was moved, causing Drupal’s own build process, and that of many other websites, to “break”. Here’s what happened:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;coder&lt;/code&gt; library was removed from github (it’s main home is on drupal.org).&lt;/li&gt;
  &lt;li&gt;Drupal core’s &lt;code class=&quot;highlighter-rouge&quot;&gt;composer.json&lt;/code&gt; had a reference to the now non-existent repository.&lt;/li&gt;
  &lt;li&gt;Anyone attempting to obtain Drupal by downloading it’s source and running &lt;code class=&quot;highlighter-rouge&quot;&gt;composer install&lt;/code&gt; couldn’t, due to the broken link.&lt;/li&gt;
  &lt;li&gt;Many other developers who tried to build their websites were similarly left disappointed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/node/2919773&quot;&gt;This issue&lt;/a&gt; on drupal.org captures the problem in more detail.&lt;/p&gt;

&lt;h2 id=&quot;weve-been-here-before&quot;&gt;We’ve been here before&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/music1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;In March 2016 a JavaScript library called &lt;a href=&quot;https://www.npmjs.com/package/left-pad&quot;&gt;left-pad&lt;/a&gt; was removed from the npm package manager, causing the builds of many front-end projects that used it to break.&lt;sup id=&quot;fnref:the-register&quot;&gt;&lt;a href=&quot;#fn:the-register&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;This seems to be a risk that comes with dependency management, and raises the question of &lt;strong&gt;should &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt; be committed to version control?&lt;/strong&gt; I’m hoping that this post will help you answer that.&lt;/p&gt;

&lt;p&gt;Notice that I’m only talking about full applications or website codebases here, not libraries.
If you’re working on some sort of standalone component for use within a larger project (like a contrib module), you definitely don’t want to ship &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt; to your users. Nor do you want to include &lt;code class=&quot;highlighter-rouge&quot;&gt;composer.lock&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;a-lean-codebase&quot;&gt;A lean codebase&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;keep-vendor-outside-git&lt;/em&gt; argument favours a lean codebase. There’s some merit to this. After all, upgrading a module is often considered a single, atomic change, and it’s nice when a pull request of the form &lt;em&gt;“Upgrade the EVA module to 1.2”&lt;/em&gt; comes down to a single line: &lt;sup id=&quot;fnref:lockfile&quot;&gt;&lt;a href=&quot;#fn:lockfile&quot; class=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;diff --git a/composer.lock b/composer.lock
index 378e3be3fa..8f6d7d31a9 100644
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;--- a/composer.lock
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/composer.lock
&lt;/span&gt;&lt;span class=&quot;gu&quot;&gt;@@ -637,17 +637,17 @@
&lt;/span&gt;         },
         {
             &quot;name&quot;: &quot;drupal/eva&quot;,
&lt;span class=&quot;gd&quot;&gt;-            &quot;version&quot;: &quot;1.1.0&quot;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+            &quot;version&quot;: &quot;1.2.0&quot;
&lt;/span&gt;         },
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;It’s not hard to cross reference that with a changelog in the other project, should you need to. And by downloading the code each time prevents you from modifying a module without auditing it. Every patches is explicitly listed.&lt;/p&gt;

&lt;p&gt;It’s interesting that composer’s own documentation recommends this approach.&lt;sup id=&quot;fnref:composer-recommendation&quot;&gt;&lt;a href=&quot;#fn:composer-recommendation&quot; class=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;resilience&quot;&gt;Resilience&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;commit-vendor-to-git&lt;/em&gt; argument focuses on resilience. You don’t want your build process to be dependent on external services that may or may not be available. Furthermore, you want to be able to track &lt;em&gt;all&lt;/em&gt; changes to your source code in one place, both the bespoke code and any other libraries.&lt;/p&gt;

&lt;p&gt;Pascal Morin articulates this well here: &lt;a href=&quot;https://www.codeenigma.com/build/blog/do-you-really-need-composer-production&quot;&gt;Do you really need composer in production?&lt;/a&gt; The &lt;a href=&quot;https://cocoapods.org/&quot;&gt;CocoaPods&lt;/a&gt; dependency manager for iOS also leans towards this position.&lt;sup id=&quot;fnref:cocoapods-recommendation&quot;&gt;&lt;a href=&quot;#fn:cocoapods-recommendation&quot; class=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;h2 id=&quot;can-we-have-our-cake-and-eat-it&quot;&gt;Can we have our cake and eat it?&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/cake1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;We’re looking at two objectives - the resilience of not being dependent on packagist/github/drupal.org for building, plus the advantages that come with a lean codebase. By trying to achieve both, are we trying to have our cake and eat it?&lt;/p&gt;

&lt;p&gt;In an ideal world, each project would have some kind of artefact repository. One that’s under your control and from where you can obtain every library/version combination you’ve ever used in the project. This is what &lt;a href=&quot;https://maven.apache.org/&quot;&gt;Maven&lt;/a&gt;, a Java dependency management tool, suggests.&lt;sup id=&quot;fnref:maven-recommendation&quot;&gt;&lt;a href=&quot;#fn:maven-recommendation&quot; class=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Do I think the core Drupal repository should contain a &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt; directory? No, I don’t. I think it’s Drupal’s job to be a component, augmented with contributed modules and perhaps custom code. Every Drupal website is different; as soon as you add a contributed module the contents of &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt; change.&lt;/p&gt;

&lt;p&gt;But unless you have the resources to manage an artefact repository, &lt;strong&gt;your website’s repository probably should contain &lt;code class=&quot;highlighter-rouge&quot;&gt;vendor&lt;/code&gt;&lt;/strong&gt;. You can still have the benefits that composer brings, but why introduce another point of failure at build time?&lt;/p&gt;

&lt;h2 id=&quot;notes&quot;&gt;Notes&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:the-register&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/&quot;&gt;How one developer just broke Node, Babel and thousands of projects in 11 lines of JavaScript&lt;/a&gt; - &lt;em&gt;The Register&lt;/em&gt; &lt;a href=&quot;#fnref:the-register&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:lockfile&quot;&gt;
      &lt;p&gt;In reality, &lt;code class=&quot;highlighter-rouge&quot;&gt;composer.lock&lt;/code&gt; will have a few other changes, like download URLs and hashes corresponding to the version. &lt;a href=&quot;#fnref:lockfile&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:composer-recommendation&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://getcomposer.org/doc/faqs/should-i-commit-the-dependencies-in-my-vendor-directory.md&quot;&gt;Should I commit the dependencies in my vendor directory?&lt;/a&gt; - &lt;em&gt;getcomposer.org&lt;/em&gt; &lt;a href=&quot;#fnref:composer-recommendation&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:cocoapods-recommendation&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control&quot;&gt;Using CocoaPods&lt;/a&gt; - guides.cocoapods.org &lt;a href=&quot;#fnref:cocoapods-recommendation&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:maven-recommendation&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://maven.apache.org/repository-management.html&quot;&gt;Best Practice - Using a Repository Manager&lt;/a&gt; - maven.apache.org &lt;a href=&quot;#fnref:maven-recommendation&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 01 Nov 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/when-your-composer-build-breaks</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/when-your-composer-build-breaks</guid>
        
        <category>drupal</category>
        
        <category>drupal-planet</category>
        
        
      </item>
    
      <item>
        <title>DrupalCamp Bristol 2017</title>
        <description>&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/balloon1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;Drupal has a thriving community in Bristol and the south-west, and they’ve
put on some excellent events over the last few years. Last weekend they had
their third DrupalCamp Bristol, and I was fortunate to be able to attend and
speak.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The day opened with a keynote by &lt;a href=&quot;https://twitter.com/embobmaria&quot;&gt;Emma Karayiannis&lt;/a&gt;
on self care and supporting others within open source communities.&lt;/p&gt;

&lt;p&gt;Emma shared some of her contributions to Drupal, where she is part of the
Community Working Group and track chair for the &lt;em&gt;Being Human&lt;/em&gt; sessions at DrupalCon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Look after yourself.&lt;/strong&gt; Don’t feel that you can only contribute a little bit or that your opinion doesn’t matter. Just find something rewarding and start small. Getting involved can be daunting, so get to know the part of the community that deals with your interest, ask how to get involved and ask for someone to help you.&lt;/p&gt;

&lt;p&gt;Burnout is real, and happens much more when we work alone, taking on lots of
responsibility without anyone to partner with. So look for someone to
co-work or co-lead with rather than try to be a lone superhero. It gives you
the freedom to step away if necessary.
Ask yourself: &lt;em&gt;if I had to stop this tomorrow, what would happen?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s easy to become overwhelmed without realising it. You need to regularly
check you’re looking after yourself, are still motivated, and aren’t taking
on too much. Be accountable to your family, colleagues and friends,
and step back if necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Look after others.&lt;/strong&gt; It’s healthy for an open source community to have
people who think differently to you. Be respectful of other people and aware
that miscommunication is very easy online, particularly with people whose
native language is different to yours. But also accept that you’ll never be
able to make everyone happy.&lt;/p&gt;

&lt;p&gt;Make sure the people are really ok even if they appear fine.
Experienced contributors, remember that you were once a beginner,
and provide opportunities and safe spaces to include new people.
&lt;em&gt;Appreciate people for who they are and not just the work they do.&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;After a short break we split into two tracks.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/dejiakala&quot;&gt;Deji Akala&lt;/a&gt; provided an interesting look into
the technical details on what happens on each page request. Along the way
he summarised various parts of Drupal and concepts such as the autoloader,
symfony handlers, the service container and event handling.&lt;/p&gt;

&lt;p&gt;It’s an interesting exercise to unpick the &lt;code class=&quot;highlighter-rouge&quot;&gt;index.php&lt;/code&gt; file line by line and
discover what happens behind the scene in a single line of code:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$kernel&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;handleRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;I then gave a short talk about &lt;a href=&quot;https://www.erskine.uk/composer&quot;&gt;Composer and Drupal&lt;/a&gt;.
I’ve spoken to a number of people recently and it’s become clear that there’s
still a bit of confusion surrounding how to use Composer with Drupal.
I certainly found it unclear and started to look into it.&lt;/p&gt;

&lt;p&gt;I pitched this at beginner developers, and the main things I wanted people to
go away understanding were:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;what the &lt;em&gt;require&lt;/em&gt;, &lt;em&gt;update&lt;/em&gt; and &lt;em&gt;install&lt;/em&gt; commands really do&lt;/li&gt;
  &lt;li&gt;the difference between &lt;em&gt;Drupal&lt;/em&gt; itself and the various template projects available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the first time I’d given that talk.
It felt a bit raw but led to some interesting Q&amp;amp;A time, and it’s given me
valuable insight for enhancing this in future.&lt;/p&gt;

&lt;p&gt;To others contemplating public speaking - do it! Events like this are an ideal
place to start, everyone’s friendly and on your side. You’ll gain knowledge,
experience and friends from doing it. I was really glad to see that several
of the speakers here were first timers - well done!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/gratton_ross&quot;&gt;Ross Gratton&lt;/a&gt; shared some insights into
using front end task runners like Gulp with Drupal.&lt;/p&gt;

&lt;p&gt;Ross has been working on a large Drupal site utilising several themes,
in 24 languages and with over 125 custom modules. He discussed the pros and
cons of different architectural decisions, such as where to put source code
and assets, what to put in version control and how to manage conflicts on
such a large site.&lt;/p&gt;

&lt;p&gt;He then shared some of the process of separating assets out to the brand level
as opposed to a project level, treating a style guide or pattern library
as a separate deliverable.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;After lunch, &lt;a href=&quot;https://twitter.com/ibluebag&quot;&gt;George Boobyer&lt;/a&gt; spoke on web
security, a topic often overlooked in the planning and budgeting of projects.&lt;/p&gt;

&lt;p&gt;Security is perceived as complex but isn’t that hard, and any effort you make
is rewarded. Recently we’ve seen a lot of ransomware attacks, but often
these just have the same impact as a disk failure, so alongside keeping
software up to date, have backups and &lt;em&gt;test them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;George gave some examples of websites that had been attacked and were now
hosting spam content, very often not visible to the naked eye but only to
search engines. Often user data is obtained by way of database dumps that have
been left accessible to the world - don’t put these in the document root.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/anouschka42&quot;&gt;Ana Hassel&lt;/a&gt; shared some insights as a
freelancer. As a site builder, Ana has been able to use Drupal to focus on
her clients’ needs and come up with a repeatable process for estimating
and selling her work.&lt;/p&gt;

&lt;p&gt;Ana also shared how she had invested some time learning the learning the
command line and setting up scripts for everyday tasks. This had given her
a better, more repeatable workflow and more predictable deployment and
hosting.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;An interesting perspective on personal development came from &lt;a href=&quot;https://twitter.com/_johangant&quot;&gt;Johan Gant&lt;/a&gt;. I felt it complemented Emma’s keynote
well with some recurring themes, and gave the day a nice mix of technical
and human elements.&lt;/p&gt;

&lt;p&gt;Johan covered issues such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Impostor_syndrome&quot;&gt;Imposter syndrome&lt;/a&gt;, depression
and burnout. Burnout often comes from a lack of engagement, and seems to be
a particular risk for knowledge workers. If the values you have aren’t aligned
with those of your employer or project, you can burn out very quickly.&lt;/p&gt;

&lt;p&gt;Be selective about what you learn—patterns and techniques will last for a
long time, whereas frameworks come and go. You need to make time to explore
new things, but make sure you are following your interests rather than trends.
Avoid stagnation—ask yourself if what you’re doing is satisfying.
It’s healthy to seek new challenges, but means getting out of your comfort zone.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;a href=&quot;https://twitter.com/leesto&quot;&gt;Lee Stone&lt;/a&gt; finished by sharing about how his
organisation does extensive code reviews.&lt;/p&gt;

&lt;p&gt;As well as preventing bugs, code reviews aid in training.
New developers can learn the business by reading code, and junior developers
can grow by asking why something is the way it is, or by asking about things
they don’t understand. They often bring fresh ideas this way.&lt;/p&gt;

&lt;p&gt;It’s important to review the code, not the person writing it.
So don’t make these things too personal, and don’t take them personally!
Prefer terms like “we” rather than “I” and “you” to foster a sense of team,
and provide solutions rather than just stating something’s wrong.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;After the talks we headed to ZeroDegrees in Bristol for a social time.
It was great to catch up over dinner with people I hadn’t seen for a while,
and make some new friends.&lt;/p&gt;

&lt;p&gt;Thanks to everyone who helped make DrupalCamp Bristol such a great event.
See you next year!&lt;/p&gt;
</description>
        <pubDate>Fri, 07 Jul 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/drupalcamp-bristol</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/drupalcamp-bristol</guid>
        
        <category>drupal</category>
        
        <category>drupal-planet</category>
        
        
      </item>
    
      <item>
        <title>Nicer date ranges in Drupal – part 3</title>
        <description>&lt;p&gt;This is the last part of a series on improving the way date ranges are presented in Drupal,
by creating a field formatter that can omit the day, month or year where appropriate,
displaying the date ranges in a nicer, more compact form:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;24–25 January 2017&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;29 January–3 February 2017&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;9:00am–4:30pm, 1 April 2017&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&quot;/blog/2017/drupal-date-ranges-1&quot;&gt;The first post&lt;/a&gt;, looked at porting
some existing code from Drupal 7 to Drupal 8, adding an automated test along
the way. In &lt;a href=&quot;/blog/2017/drupal-date-ranges-2&quot;&gt;the second post&lt;/a&gt;, we made
the format configurable.&lt;/p&gt;

&lt;p&gt;There’s currently no administrative interface though, so site builders can’t
add and edit formats from Drupal’s UI. We’ll add that in this last post.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;routing&quot;&gt;Routing&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/route66.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;According to &lt;a href=&quot;https://www.drupal.org/docs/8/api/routing-system/routing-system-overview&quot;&gt;the routing overview on drupal.org&lt;/a&gt;,
&lt;q cite=&quot;https://www.drupal.org/docs/8/api/routing-system/routing-system-overview&quot;&gt;a route is a path which is defined for Drupal to return some sort of content on&lt;/q&gt;.&lt;/p&gt;

&lt;p&gt;For our administrative interface, we want to define a number of routes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;/admin/config/regional/date_range_format&lt;/code&gt; - show a list of the formats, with links to:&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;/admin/config/regional/date_range_format/add&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;/admin/config/regional/date_range_format/*/edit&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;/admin/config/regional/date_range_format/*/delete&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are two ways in which our module can provide routes.
We could include a &lt;code class=&quot;highlighter-rouge&quot;&gt;routing.yml&lt;/code&gt; file along with our module.
This file contains the same kind of information as would have been in
&lt;code class=&quot;highlighter-rouge&quot;&gt;hook_menu&lt;/code&gt; in Drupal 7. But it’s a static file—if we want something that’s
dynamic we can provide it at runtime using a &lt;em&gt;route provider&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For dealing with entities, it’s often much easier to use Drupal’s bundled
&lt;code class=&quot;highlighter-rouge&quot;&gt;AdminHtmlRouteProvider&lt;/code&gt; class. This examines various properties on the entity
annotation—we’ll look at those next—and provides suitable routes for us
automatically.&lt;/p&gt;

&lt;p&gt;To use this route provider, we add the following to the entity annotation:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@ConfigEntityType(
  …
  handlers = {
    &quot;route_provider&quot; = {
      &quot;html&quot; = &quot;Drupal\Core\Entity\Routing\AdminHtmlRouteProvider&quot;,
    },
  },
  …
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;At this point we need to run the &lt;code class=&quot;highlighter-rouge&quot;&gt;drupal router:rebuild&lt;/code&gt; command from Drupal
console. We must do this whenever we change a &lt;code class=&quot;highlighter-rouge&quot;&gt;routing.yml&lt;/code&gt; file or any of
the properties in the entity that affect routes.&lt;/p&gt;

&lt;h2 id=&quot;the-collection-view&quot;&gt;The collection view&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/clipboard_610363.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;An entity can define a &lt;em&gt;collection view&lt;/em&gt;—typically a page showing a list
of entities with links to edit them. Drupal provides a &lt;em&gt;list builder&lt;/em&gt; which
can be used to show a list of entities with buttons for common add/edit/delete
type tasks. We’ll create one of these for our new configuration entity:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Drupal\daterange_compact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateRangeFormatListBuilder&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ConfigEntityListBuilder&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* return an array of column headings */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;buildRow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EntityInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$entity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* return an array of column values for the given entity */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We then associate this list builder with our entity by declaring it
within the &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntityType&lt;/code&gt; annotation:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;handlers = {
  &quot;list_builder&quot; = &quot;Drupal\daterange_compact\DateRangeFormatListBuilder&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The actual list builder is quite a rich, showing examples of different ranges.
You can see the full implementation &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/tree/8.x-part3/src/DateRangeFormatListBuilder.php&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-collection-page&quot;&gt;The collection page&lt;/h3&gt;

&lt;p&gt;Once we have the list builder in place, we can add the &lt;em&gt;collection&lt;/em&gt; link
to our &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntityType&lt;/code&gt; annotation. The route provider will pick up on
this link template and provide a route for the entity collection page
automatically.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;links = {
  &quot;collection&quot; = &quot;/admin/config/regional/date_range_format&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;By defining the link, our page appears at the appropriate URL.
Note that the add/edit/delete links won’t show just yet—we still have to
define those.&lt;/p&gt;

&lt;figure class=&quot;screenshot-container-1920x1200&quot;&gt;
  &lt;img src=&quot;/assets/images/blog/2017/datetime-screen-2.png&quot; alt=&quot;The date and time range configuration page, showing a list of available formats&quot; class=&quot;screenshot&quot; /&gt;
  &lt;figcaption&gt;The screen for listing date/time range formats, provided by the entity list builder.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;updating-the-main-configuration-page&quot;&gt;Updating the main configuration page&lt;/h3&gt;

&lt;p&gt;In order to reach this new page, we’ll create a menu link on the main
configuration page, within the &lt;em&gt;regional and language&lt;/em&gt; section.
We do that by supplying a &lt;code class=&quot;highlighter-rouge&quot;&gt;daterange_compact.links.menu.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;entity.date_range_format.collection&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;formats'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;route_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;entity.date_range_format.collection&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ranges&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;are&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;displayed.'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;system.admin_config_regional&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;weight&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;That link gives us the starting point for our interface:&lt;/p&gt;

&lt;figure class=&quot;screenshot-container-1920x1200&quot;&gt;
  &lt;img src=&quot;/assets/images/blog/2017/datetime-screen-1.png&quot; alt=&quot;The system configuration navigation, showing a link to date and time range formats&quot; class=&quot;screenshot&quot; /&gt;
  &lt;figcaption&gt;Date/time range formats are accessed via the main configuration page.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We can now view all the date and time range formats from the main
administrative interface in Drupal. Next we’ll build some forms to maintan
them, after which the add/edit/delete links should start to appear on our
collection page.&lt;/p&gt;

&lt;h2 id=&quot;forms&quot;&gt;Forms&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/browser3.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;We need a form to be able to edit date range formats. The same form is used
to create new ones. Drupal provides a lot of built-in functionality via the
&lt;code class=&quot;highlighter-rouge&quot;&gt;EntityForm&lt;/code&gt; class which we can extend. Drupal will then take care of loading
and saving the entity. We just need to provide the form elements to map
values on to our entity’s properties.&lt;/p&gt;

&lt;h3 id=&quot;adding--editing&quot;&gt;Adding &amp;amp; editing&lt;/h3&gt;

&lt;p&gt;We can add any number of forms, but we only need one to edit an existing
format, and we can reuse the same form for adding a new format. This form
is defined as a class, and lives in &lt;code class=&quot;highlighter-rouge&quot;&gt;src/Form/DateRangeFormatForm.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Drupal\daterange_compact\Form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateRangeFormatForm&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EntityForm&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* implementation */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Configuration entities don’t use the field API, so we need to build the form
ourselves. Although the form looks quite complicated and has a lot of options,
it’s reasonably easy to build—each property in the configuration
entity can be populated by a single element, like this:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'label'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#type'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'textfield'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#title'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Label'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#maxlength'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#default_value'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;entity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#description'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Name of the date time range format.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;'#required'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The full implementation of the form is &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/tree/8.x-part3/src/Form/DateRangeFormatForm.php&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also need to tell Drupal about this form, which we can do by adding the
following to the &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntityType&lt;/code&gt; annotation:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;form&quot; = {
  &quot;add&quot; = &quot;Drupal\daterange_compact\Form\DateRangeFormatForm&quot;,
  &quot;edit&quot; = &quot;Drupal\daterange_compact\Form\DateRangeFormatForm&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We also add some links, to match up operations such as &lt;em&gt;add&lt;/em&gt; and &lt;em&gt;edit&lt;/em&gt;
with the new form. These are also defined in the &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntityType&lt;/code&gt;
annotation:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;links = {
  &quot;add-form&quot; = &quot;/admin/config/regional/date_range_format/add&quot;,
  &quot;edit-form&quot; = &quot;/admin/config/regional/date_range_format/{date_range_format}/edit&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;If we look at the collection view again we see that alongside each format there
is a link to edit it. That is because of the &lt;code class=&quot;highlighter-rouge&quot;&gt;edit-form&lt;/code&gt; link declared in the
annotation.&lt;/p&gt;

&lt;p&gt;We also want a link at the top of that page, to add a new format.
We can do that by providing an &lt;em&gt;action link&lt;/em&gt; that refers to the &lt;code class=&quot;highlighter-rouge&quot;&gt;add-form&lt;/code&gt; link.
This belongs in the &lt;code class=&quot;highlighter-rouge&quot;&gt;daterange_compact.links.action.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;entity.date_range_format.add_form&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;route_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;entity.date_range_format.add_form'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;format'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;appears_on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;entity.date_range_format.collection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;At this point we have a means of adding and editing formats.
Our form looks like this:&lt;/p&gt;

&lt;figure class=&quot;screenshot-container-1920x1200&quot;&gt;
  &lt;img src=&quot;/assets/images/blog/2017/datetime-screen-3.png&quot; alt=&quot;The date and time range configuration page, showing our new format for editing&quot; class=&quot;screenshot&quot; /&gt;
  &lt;figcaption&gt;The screen for editing date/time range formats.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;deletion&quot;&gt;Deletion&lt;/h3&gt;

&lt;p&gt;Deleting entities is slightly different. We want to show a confirmation page
after a before performing the actual deletion. The &lt;code class=&quot;highlighter-rouge&quot;&gt;EntityDeleteForm&lt;/code&gt; class
does just that. All we need to do is subclass it and provide the wording
for the question:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Drupal\daterange_compact\Form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateRangeFormatDeleteForm&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;EntityDeleteForm&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getQuestion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'Are you sure?'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We declare this form and link on the &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntityType&lt;/code&gt; annotation in the
same way as for add/edit:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;form&quot; = {
  &quot;delete&quot; = &quot;Drupal\foo\Form\DateRangeFormatDeleteForm&quot;
}
links = {
  &quot;delete-form&quot; = &quot;/admin/config/regional/date_range_format/{date_range_format}/delete&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;That’s it. We’ve got a field formatter to render date and time ranges
in a very flexible way. Users can define their own formats thorough the
web interface, and these are represented as configuration entities, giving
us all the benefits of the &lt;a href=&quot;http://drupal8cmi.org/&quot;&gt;configuration management initiative&lt;/a&gt;,
such as predictable deployments and multilingual support.&lt;/p&gt;

&lt;p&gt;The module is available at &lt;a href=&quot;https://www.drupal.org/project/daterange_compact&quot;&gt;https://www.drupal.org/project/daterange_compact&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you found this write-up useful.&lt;/p&gt;

&lt;h2 id=&quot;want-to-help&quot;&gt;Want to help?&lt;/h2&gt;

&lt;p&gt;I’m currently working on getting this module up to scratch in order to have
&lt;a href=&quot;https://www.drupal.org/drupal-security-team/security-advisory-process-and-permissions-policy&quot;&gt;coverage from the Drupal security team&lt;/a&gt;.
If you want to help make that happen, please review the code following &lt;a href=&quot;https://www.drupal.org/node/894256&quot;&gt;this process&lt;/a&gt; and leave a comment on &lt;a href=&quot;https://www.drupal.org/node/2875485&quot;&gt;this issue&lt;/a&gt;. Thanks :-)&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Jun 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/drupal-date-ranges-3</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/drupal-date-ranges-3</guid>
        
        <category>drupal</category>
        
        <category>drupal-planet</category>
        
        
      </item>
    
      <item>
        <title>Nicer date ranges in Drupal – part 2</title>
        <description>&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/calendar1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;This is the second part of a series on improving the way date ranges are presented in Drupal,
by creating a field formatter that can omit the day, month or year where appropriate,
displaying the date ranges in a nicer, more compact form, e.g.:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;24–25 January 2017&lt;/li&gt;
  &lt;li&gt;29 January–3 February 2017&lt;/li&gt;
  &lt;li&gt;9:00am–4:30pm, 1 April 2017&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href=&quot;/blog/2017/drupal-date-ranges-1&quot;&gt;first post&lt;/a&gt; dealt with porting some
existing code from Drupal 7 to Drupal 8, adding an automated test along the way.&lt;/p&gt;

&lt;p&gt;In this post, we’ll do some of the work to make the the format customisable:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a new config entity to store formats&lt;/li&gt;
  &lt;li&gt;moving the rendering logic into a service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a long post, and we’ll cover a lot of ground.
You may want to make a cup of tea before we start.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;configurable-formats&quot;&gt;Configurable formats&lt;/h2&gt;

&lt;p&gt;Drupal’s core date formats are stored as configuration entities. Each one
consists of a single pattern made up of
&lt;a href=&quot;http://php.net/manual/en/function.date.php&quot;&gt;PHP’s date &amp;amp; time formatting symbols&lt;/a&gt;.
We’ll create another type of configuration entity for &lt;em&gt;date range&lt;/em&gt; formats.
Each format will need several different patterns:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;a default date pattern. This is used to show the full single date if the
start and end dates are the same. It’s also used to show both dates fully
where the range spans several years.&lt;/li&gt;
  &lt;li&gt;patterns for the start and end dates, where a range spans several days within the same month&lt;/li&gt;
  &lt;li&gt;patterns for the start and end dates, where a range spans several months within the same year&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also support times in a similar fashion:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a default pattern. This is used to show the full single date &amp;amp; time if the
start and end values are the same. It’s also used to show both dates &amp;amp; times
fully if the range spans several days.&lt;/li&gt;
  &lt;li&gt;optional patterns for the start and end values, where we have a range
contained within a single day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these 8 patterns can be stored within a single configuration entity,
alongside custom separator text.&lt;/p&gt;

&lt;h2 id=&quot;config-entity&quot;&gt;Defining a configuration entity&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/settings1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;To create the configuration entity, we need some custom code.
We can use &lt;a href=&quot;https://drupalconsole.com/&quot;&gt;Drupal console&lt;/a&gt; to generate a
certain amount of boilerplate code as a starting point.
Be aware that Drupal console will produce a lot of code
all in one go, including the admin interface which we’ll look at in part 3.&lt;/p&gt;

&lt;p&gt;I find it helpful to generate this boilerplate into a temporary location,
then copy the files over one at a time, editing them as I go. It slows things
down in a good way, and forces me to understand the code I’m going to be
responsible for maintaining.&lt;/p&gt;

&lt;h3 id=&quot;entity-class-and-definition&quot;&gt;Entity class and definition&lt;/h3&gt;

&lt;p&gt;Let’s start with the entity class, which lives in &lt;code class=&quot;highlighter-rouge&quot;&gt;src/Entity/DateRangeFormat.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Drupal\daterange_compact\Entity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DateRangeFormat&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ConfigEntityBase&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateRangeFormatInterface&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* implementation */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;In order to tell Drupal that this is a configuration entity, we need to add
an &lt;a href=&quot;https://www.drupal.org/docs/8/api/plugin-api/annotations-based-plugins&quot;&gt;annotation&lt;/a&gt;
to the class. Similar to the annotation on the field formatter in part 1,
we’re telling Drupal about the existence of a new &lt;em&gt;date range format&lt;/em&gt;
configuration entity, plus some information about it.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @ConfigEntityType(
 *   id = &quot;date_range_format&quot;,
 *   label = @Translation(&quot;Date range format&quot;),
 *   config_prefix = &quot;date_range_format&quot;,
 *   entity_keys = {
 *     &quot;id&quot; = &quot;id&quot;,
 *     &quot;label&quot; = &quot;label&quot;,
 *     &quot;uuid&quot; = &quot;uuid&quot;
 *   }
 * )
 */
class DateRangeFormat...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The class adheres to a corresponding &lt;code class=&quot;highlighter-rouge&quot;&gt;DateRangeFormatInterface&lt;/code&gt;.
We’ll refer to the interface, rather than the entity class directly,
elsewhere in the code.&lt;/p&gt;

&lt;p&gt;The complete implementation is &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/tree/8.x-part2/src/Entity&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;schema&quot;&gt;Schema&lt;/h3&gt;

&lt;p&gt;End users will create instances of the configuration entity—one per format.
These can be represented as a single YAML file, and imported and exported as such.
The schema describes the structure of these
YAML files, dictating the type of data we’re storing inside the entity.&lt;/p&gt;

&lt;p&gt;The schema definition is itself a YAML file,
and lives in &lt;code class=&quot;highlighter-rouge&quot;&gt;config/schema/daterange_compact.schema.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;daterange_compact.date_range_format.*&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;config_entity&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;config'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# properties of the config entity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The complete schema implementation is &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/blob/8.x-part2/config/schema/daterange_compact.schema.yml&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;updating-entity-definitions&quot;&gt;Updating entity definitions&lt;/h3&gt;

&lt;p&gt;If we look at the status report of our site, we’ll see that there is an error:
&lt;em&gt;Mismatched entity and/or field definitions&lt;/em&gt;. Each time we add or change code
that defines entities, we need to update Drupal’s internal copy of the
definitions. We can do this with the &lt;code class=&quot;highlighter-rouge&quot;&gt;drush entup&lt;/code&gt; command, and we should
get the following output:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The following updates are pending:
date_range_format entity type :
  The Date range format entity type needs to be installed.

Do you wish to run all pending updates? (y/n): y
 [success] Cache rebuild complete.
 [success] Finished performing updates.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;That’s the bare minimum for defining a config entity.
Right now the only way to manage them is by editing YAML files by hand,
but that’s enough to start working on the improved functionality for now.
In the next post we’ll look at an administrative interface for editing these
formats.&lt;/p&gt;

&lt;h3 id=&quot;providing-a-default-entity&quot;&gt;Providing a default entity&lt;/h3&gt;

&lt;p&gt;Any Drupal module can provide configuration entities as part of their install
process. The &lt;em&gt;contact&lt;/em&gt; module is a good example—enabling the module will
create the &lt;em&gt;feedback&lt;/em&gt; and &lt;em&gt;personal contact&lt;/em&gt; forms, each configuration entities.
You can then change those forms, remove them or add new ones.&lt;/p&gt;

&lt;p&gt;Let’s make our module provide a date range format called &lt;em&gt;Medium&lt;/em&gt;,
following the same naming convention as Drupal’s standard date formats.
We do that by providing a file called
&lt;code class=&quot;highlighter-rouge&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$modulename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$config_entity_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$machine_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.yml&lt;/span&gt;&lt;/code&gt; in the &lt;code class=&quot;highlighter-rouge&quot;&gt;config/install&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;So the module will contain a &lt;code class=&quot;highlighter-rouge&quot;&gt;config/install/daterange_compact.date_range_format.medium.yml&lt;/code&gt;
file that looks like the following. You’ll see the properties follow the schema
defined earlier. The patterns are made up of &lt;a href=&quot;http://php.net/manual/en/function.date.php&quot;&gt;PHP date &amp;amp; time formatting symbols&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;langcode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;en&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;pi&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;medium&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Medium&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;date_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;default_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Y'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;separator&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_month_start_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_month_end_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Y'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_year_start_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_year_end_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Y'&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;datetime_settings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;default_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Y&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;H:i'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;separator&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_day_start_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Y&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;H:i'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;same_day_end_pattern&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;H:i'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We’ll need to re-install the module for this to take effect, but if we do,
and then export the site configuration, we should get a copy of this YAML
file along with the rest of the configuration.&lt;/p&gt;

&lt;h3 id=&quot;a-note-on-cacheability&quot;&gt;A note on cacheability&lt;/h3&gt;

&lt;p&gt;Normally, whenever we produce some sort of output in Drupal 8, we need to
provide its &lt;em&gt;cacheability metdata&lt;/em&gt;, which describes how it may be cached.
Whenever something changes, the render cache can be examined and anything
that depended on it can be cleared.&lt;/p&gt;

&lt;p&gt;Certain items, like date formats, are so widely used they a treated differently.
From &lt;a href=&quot;https://www.drupal.org/node/2241275&quot;&gt;drupal.org&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The DateFormat config entity type entity type affects rendered content all over the place: it’s used pretty much everywhere. It seems appropriate in this case to not set cache tags that bubble up, but to just clear the entire render cache. Especially because it hardly ever changes: it’s a set-and-forget thing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Invalidating the &lt;code class=&quot;highlighter-rouge&quot;&gt;rendered&lt;/code&gt; cache tag should be done sparingly. It’s a very
expensive thing to do, clearing the entire render cache. But it’s
appropriate here to follow what the core &lt;em&gt;date format&lt;/em&gt; entity does.&lt;/p&gt;

&lt;p&gt;We need to add this to the &lt;code class=&quot;highlighter-rouge&quot;&gt;@ConfigEntity&lt;/code&gt; annotation:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;list_cache_tags = { &quot;rendered&quot; }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;and this to the &lt;code class=&quot;highlighter-rouge&quot;&gt;DateRangeFormat&lt;/code&gt; class:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCacheTagsToInvalidate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'rendered'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;refactoring&quot;&gt;Refactoring&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/scissors1.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;Until now, the code for rendering date ranges has been part of the
field formatter. As it’s getting more complicated, it makes sense to move
it out of there into it’s own, distinct location. That means we’ll be able
to use it outside of the context of a field.&lt;/p&gt;

&lt;p&gt;We’ll use a Drupal &lt;em&gt;service&lt;/em&gt; for this. A service is a separate class in which
we can handle the business logic independently of field formatters.
When Drupal manages a request, it will take care of creating an instance of
the service class and making it available to other parts of the system.&lt;/p&gt;

&lt;p&gt;Our service class it quite straightforward:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php
namespace Drupal\daterange_compact;

class DateRangeFormatter implements DateRangeFormatterInterface {

  function __construct(…) {
    /* implementation */
  }

  function formatDateRange($start_timestamp, $end_timestamp, $type = 'medium', …) {
    /* implementation */
  }

  function formatDateTimeRange($start_timestamp, $end_timestamp, $type = 'medium', …) {
  /* implementation */
  }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The complete implementation of &lt;code class=&quot;highlighter-rouge&quot;&gt;DateRangeFormatter&lt;/code&gt; is &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/blob/8.x-part2/src/DateRangeFormatter.php&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We then tell Drupal about the service by including it in the module’s
&lt;code class=&quot;highlighter-rouge&quot;&gt;daterange_compact.services.yml&lt;/code&gt; file. We can also specify dependencies on
other services, and these will be passed to our object’s constructor.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;daterange_compact.date_range.formatter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Drupal\daterange_compact\DateRangeFormatter&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@entity_type.manager'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@date.formatter'&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Now our formatting functions are available to use within other parts of Drupal
via the &lt;code class=&quot;highlighter-rouge&quot;&gt;daterange_compact.date_range.formatter&lt;/code&gt; service. We’ll access it
from the field formatter next.&lt;/p&gt;

&lt;p&gt;You can find &lt;a href=&quot;https://www.drupal.org/docs/8/api/services-and-dependency-injection&quot;&gt;more documentation about Drupal 8 services&lt;/a&gt; on drupal.org.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;pulling-it-all-together&quot;&gt;Pulling it all together&lt;/h2&gt;

&lt;p&gt;We need to revisit the field formatter and make a couple of changes. First we
remove the hardcoded formatting logic that was there previously, and instead
delegate that work to the service. Second, we need a way to let the site builder
choose a particular format.&lt;/p&gt;

&lt;h3 id=&quot;dependency-injection&quot;&gt;Dependency injection&lt;/h3&gt;

&lt;p&gt;The field formatter needs to be able access to the new service.
Drupal 8 makes use of &lt;a href=&quot;https://www.drupal.org/docs/8/api/services-and-dependency-injection/services-and-dependency-injection-in-drupal-8&quot;&gt;dependency injection&lt;/a&gt; for this sort of thing—a way to access dependencies at
runtime without being tied to any particular implementation.&lt;/p&gt;

&lt;p&gt;There are a few steps involved in getting to use our service this way:&lt;/p&gt;

&lt;p&gt;First, we want it for the lifetime of the field formatter, so it needs to be
in the constructor. The constructor for &lt;code class=&quot;highlighter-rouge&quot;&gt;FieldFormatterBase&lt;/code&gt; takes quite a lot
of parameters. We need to accept them all as well, plus our formatter.
Some of the other parameters are hidden with &lt;code class=&quot;highlighter-rouge&quot;&gt;…&lt;/code&gt; here for clarity.&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DateRangeFormatterInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$date_range_formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dateRangeFormatter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$date_range_formatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Next, we need to state that this formatter makes use of dependency injection.
We do that by making the class implement &lt;code class=&quot;highlighter-rouge&quot;&gt;ContainerFactoryPluginInterface&lt;/code&gt;, which
declares a &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; function that should be used to create instances.
The &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; function is passed the &lt;em&gt;container&lt;/em&gt;, an object from which we can
get services by name. In the &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; function we get the service by name
and pass it to the constructor:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ContainerInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'daterange_compact.date_range.formatter'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Now we will always have access to a formatter via the &lt;code class=&quot;highlighter-rouge&quot;&gt;$this-dateRangeFormatter&lt;/code&gt; variable.&lt;/p&gt;

&lt;h3 id=&quot;field-formatter-settings&quot;&gt;Field formatter settings&lt;/h3&gt;

&lt;p&gt;Whenever we use this formatter, we can choose a particular date range format.
We store that choice in the field formatter settings, once for each time a
field is displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/docs/8/creating-custom-modules/create-a-custom-field-formatter&quot;&gt;Adding field formatter settings&lt;/a&gt;
is documented quite thoroughly on drupal.org, but it involves a YAML file
describing the type of data we want to store, some default settings, a form
and a summary.&lt;/p&gt;

&lt;p&gt;The YAML file is named &lt;code class=&quot;highlighter-rouge&quot;&gt;field.formatter.settings.{$formatter_name}&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;field.formatter.settings.daterange_compact&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mapping&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Date/time&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;compact&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;settings'&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;format_type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;s&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Date/time&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;format'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The extra functions we need to implement in the &lt;code class=&quot;highlighter-rouge&quot;&gt;DateRangeCompactFormatter&lt;/code&gt;
class are as follows:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;defaultSettings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* an array of default values for the settings */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;settingsForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;FormStateInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$form_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* form from which to choose from a list of formats */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;settingsSummary&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* text describing what format will be used */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Now when anyone opts to use the compact formatter to render a date range field,
they will be prompted to choose a date range format.&lt;/p&gt;

&lt;h3 id=&quot;display&quot;&gt;Display&lt;/h3&gt;

&lt;p&gt;Finally, we have everything we need to render the date range using the
chosen format. We can change the &lt;code class=&quot;highlighter-rouge&quot;&gt;viewElements&lt;/code&gt; function, removing the
hardcoded stuff we had before, and delegating to our date range formatter
service:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$format&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'format_type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$formatter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;dateRangeFormatter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$formatter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;formatDate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$start_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$end_timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;…&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;The complete implementation of the formatter is &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/tree/8.x-part2/src/Plugin/Field/FieldFormatter&quot;&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;test-it&quot;&gt;Test it!&lt;/h2&gt;

&lt;div class=&quot;flaticon&quot; style=&quot;background-image: url(/assets/images/icons/checklist_449785.svg)&quot;&gt;&lt;/div&gt;

&lt;p&gt;We’ve added substantial functionality, so we need to make sure there have
been no regressions on what we had before. We also want to test the new
configurable formats.&lt;/p&gt;

&lt;p&gt;The test should pass as before, with one small tweak. In the &lt;code class=&quot;highlighter-rouge&quot;&gt;setUp&lt;/code&gt; function,
we need to load the configuration for the &lt;code class=&quot;highlighter-rouge&quot;&gt;daterange_compact&lt;/code&gt; module, so
that the &lt;code class=&quot;highlighter-rouge&quot;&gt;medium&lt;/code&gt; format is present.&lt;/p&gt;

&lt;p&gt;We’ll also define a new &lt;code class=&quot;highlighter-rouge&quot;&gt;usa&lt;/code&gt; format, for US-style month, day, year display.
That is created in the &lt;code class=&quot;highlighter-rouge&quot;&gt;setUp&lt;/code&gt; function:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setUp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* existing set up code */&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* create a new date range format called &quot;usa&quot; */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;We’ll add another test specifically for rendering the USA format:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testUSAFormats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;$all_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'start'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-01'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-01'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'expected'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Jan 1, 2017'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'start'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-02'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-03'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'expected'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Jan 2–3, 2017'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'start'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-04'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-02-05'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'expected'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Jan 4–Feb 5, 2017'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'start'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2017-01-06'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'end'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'2018-02-07'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'expected'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Jan 6, 2017–Feb 7, 2018'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$all_data&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* 1. programmatically create an entity and populate start/end dates */&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* 2. programmatically render the entity */&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/* 3. assert that the output contains the expected text */&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Whilst the goal should be to have as much test coverage as possible, is isn’t
feasible to cover every combination of dates, formats and settings. But we
should try to test lots of variations of date and datetime ranges, edge cases,
possible formats and &lt;a href=&quot;https://www.drupal.org/node/2498619&quot;&gt;results that would vary by timezone&lt;/a&gt;.
And if something doesn’t behave as expected later, we can write a test to demonstrate it
&lt;em&gt;before changing code&lt;/em&gt;. Later we can verify any fixes via the new test,
and make sure there are no other regressions too!&lt;/p&gt;

&lt;p&gt;You can find the full test implementation &lt;a href=&quot;https://github.com/erikerskine/daterange_compact/tree/8.x-part2/src/Tests&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Phew, that was a lot!&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;/blog/2017/drupal-date-ranges-3&quot;&gt;the final post&lt;/a&gt;, we’ll provide an admin interface for editing the
date range formats and look at some implications of using this in a
multilingual environment.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 May 2017 00:00:00 +0000</pubDate>
        <link>https://www.erskine.uk/blog/2017/drupal-date-ranges-2</link>
        <guid isPermaLink="true">https://www.erskine.uk/blog/2017/drupal-date-ranges-2</guid>
        
        <category>drupal</category>
        
        <category>drupal-planet</category>
        
        
      </item>
    
  </channel>
</rss>
