Lullabot News, Podcasts, and Content https://www.lullabot.com/ The latest news, articles, podcasts, and more from your friends at Lullabot. en https://www.lullabot.com/themes/custom/lullabotcom/favicon-96x96.png Lullabot News, Podcasts, and Content https://www.lullabot.com/ Pens, Paper, and Personalities https://www.lullabot.com/articles/pens-paper-and-personalities <p>We are a digital agency, but we live in the physical world. And many of us take notes. Lots and lots of notes.<a href="https://www.lullabot.com/articles/squiggles-straight-lines-sketching-during-strategy-and-ux-work" target="_blank"> And lots and lots of sketching</a>. When you write as much as we do, you start paying more attention to the quality of your tools. A good writing instrument can be the difference between drudgery and delight. If you have the right setup, writing notes can feel like strolling in a park on the perfect Autumn evening. Using a subpar setup can feel like riding a sled over gravel on a snowless Winter morning.</p> <p>Sure, the stakes are pretty low. BIC ballpoint pens and cheap college-ruled notebooks will always do in a pinch. But we have preferences that we feel make us more productive. Some of us have field-tested a lot of pens and pads of paper over the course of our professional years. We wanted to share what has worked best for us, and maybe it will help you find what works best for you.</p> <h2>Favorite pens and pencils</h2> <p><strong><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">G</a><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">reg Dunlap</a>:</strong> <a href="https://www.jetpens.com/search?q=zebra+sarasa+clip+gel+pen&amp;v=2&amp;f=cb71d968eb72ebc8_89d5a6676ad6dc20_14129dabc3d4302f3d24f95ea83b21e9_5c5bd88a52c775821efdea7dd6554eda_fc002018027f9a29" target="_blank">Zebra Sarasa clip gel pens</a>. They look cool, have a nice rubber grip, and their clip is solidly made. Historically I have used the <a href="https://www.jetpens.com/Zebra-Sarasa-Clip-Gel-Pen-0.5-mm-Black/pd/6387" target="_blank">.5 tip</a>, but I recently switched to the <a href="https://www.jetpens.com/Zebra-Sarasa-Clip-Gel-Pen-0.3-mm-Black/pd/6365" target="_blank">.3</a><a href="https://www.jetpens.com/Zebra-Sarasa-Clip-Gel-Pen-0.3-mm-Black/pd/6365" target="_blank"> tip</a>. It is slightly harder to write with, which has the effect of making me write more intentionally. The thin line is kind of delicate which I’ve also been enjoying, and it is less likely to smear. I settled on these after ordering a sampler of 18 gel pens and experimenting with them for about a month.</p> <p>Zebra Sarasa recently introduced the <a href="https://www.jetpens.com/Zebra-Sarasa-Nano-Gel-Pen-0.3-mm-Black/pd/33304" target="_blank">Clip Nano, </a>which has a spring in the end that supposedly makes it rebound a little more softly when writing. But I find the body of the pen harder to hold than the standard clip pen.</p> <p><strong><a href="https://www.lullabot.com/about/darren-petersen" target="_blank">Darren Petersen</a>:</strong> <a href="https://www.papermate.com/pencils/paper-mate-mirado-black-warrior-woodcase-pencils-hb-2-lead/SAP_2254.html" target="_blank">Mirado Black Warrior #2 pencils</a> and a <a href="https://www.pentel.com/collections/erasers/products/hi-polymer-eraser-caps-non-abrasive-erasers" target="_blank">Pentel Hi-Polymer Eraser</a>. These come from my days of writing tons of music by hand. They have a clear black stroke and erase clean without damaging the paper.</p> <p><strong><a href="https://www.lullabot.com/about/aubrey-sambor" target="_blank">Aubrey Sambor</a>:</strong> I love my fountain pens! My favorites are <a href="https://www.twsbi.com/products/twsbi-eco-t-clear-fountain-pen" target="_blank">TWSBI Ecos</a>. I also have a <a href="https://www.kaweco-pen.com/en/series/frosted-sport/" target="_blank">Kaweco Sport</a>, a <a href="https://www.jetpens.com/Pilot-Metropolitan-Retro-Pop-Fountain-Pen-Turquoise-Dots-Fine-Nib/pd/15184" target="_blank">Pilot Metropolitan</a>, and a fancy <a href="https://en.sailor.co.jp/topics/professional-gear-series/" target="_blank">Sailor Pro Gear</a>. All my pens have fine nibs. I use bottles of ink to fill them, and <a href="https://www.jetpens.com/Sailor-Fountain-Pen-Ink-Bottles/ct/1791" target="_blank">my favorite inks are also by Sailor</a>. </p> <p>For plain black ink, I use <a href="https://www.muji.us/collections/pen-pencils/color_black+nib-size_0-5mm" target="_blank">Muji pens</a> in the 0.5mm size.</p> <p><strong><a href="https://www.lullabot.com/about/marissa-epstein" target="_blank">Marissa Epstein</a>:</strong> Since I am a lefty that nitpicks mistakes, a pencil with a good eraser is my go-to. Pens are fine as long as the ink dries fast enough not to smear under my (again, left) hand. If I am sketching a wireframe, I like to start in pencil and finish in pen or even marker.  </p> <p>For years, I’ve used a mix of <a href="https://www.amazon.com/Pentel-P200-Auto-Mechanical-Pencil/dp/B003XRIYVY/" target="_blank">Pentel mechanical drafting pencils</a> (0.5-0.9mm thickness, hardness ranging from HB to 2H). I prefer the <a href="https://www.amazon.com/Pentel-P205MBP2M1-Sharp-Automatic-Pencil/dp/B002VKXAY0/?th=1" target="_blank">.7mm Pentel Sharp mechanical pencil</a> with F lead, which is a little harder than a #2 pencil. I like that these pencils are slim, metal, and won't roll away. The erasers are laughably small, so I use different<a href="https://www.amazon.com/Fred-EARASER-Ear-Shaped-Rubber-Eraser/dp/B015U5TJ88" target="_blank"> erasers</a> that last longer and can be gripped better.</p> <p>One more fun fact: my parents have been work-from-home architects for all of my life, which has a lot to do with how I found my favorites. </p> <p><strong><a href="https://www.lullabot.com/about/andy-blum" target="_blank">Andy Blum</a>:</strong> I don’t do a ton of writing, but when I do, I prefer a ballpoint pen. I don’t have a special brand or anything, but I cannot stand the scratchiness of felt-tip pens. Currently at my desk are Papermate InkJoy pens.</p> <p><strong><a href="https://www.lullabot.com/about/juampy-nr" target="_blank">Juampy</a><a href="https://www.lullabot.com/about/juampy-nr" target="_blank"> NR</a>:</strong> For pens, I usually use a <a href="https://www.amazon.co.uk/BiC-Cristal-Medium-Biro-Ballpoint/dp/B00WKKMT1Q" target="_blank">BIC Medium</a>. As for pencils, I prefer mechanical pencils (<a href="https://www.amazon.com/Bic-Atlantis-0-5Mm-Mechanical-Pencil/dp/B001JK4844" target="_blank">Bic</a><a href="https://www.amazon.com/Bic-Atlantis-0-5Mm-Mechanical-Pencil/dp/B001JK4844" target="_blank"> Atlantis .5</a>) more than regular pencils. It feels easier to write.</p> <p><strong><a href="https://www.lullabot.com/about/matt-robison" target="_blank">Matt Robison</a>:</strong> I’m partial to the Pentel R.S.V.P fine tip. They’re cheap, and they write smoothly. You never feel like you’re scratching the paper.</p> <p><strong><a href="https://www.lullabot.com/about/m-nikki-flores" target="_blank">Nikki Flores</a>:</strong> I have loved the <a href="https://www.staedtler.com/us/en/products/technical-drawing-instruments/technical-pens-drawing-ink/mars-technico-780-leadholder-m780/" target="_blank">Mars Technico Lead Holder</a> since a college introduction to landscape architecture class. The pencil is easy to use and refill, and the lead works well with the <a href="https://www.staedtler.com/us/en/products/pencils-and-accessories/sharpeners/mars-502-lead-sharpener-tub-502-bk-a6/" target="_blank">Mars 502 sharpener</a>.</p> <p><strong><a href="https://www.lullabot.com/about/matt-kleve" target="_blank">Matt Kleve</a>:</strong> I always have a pencil nearby and am a fan of a <a href="https://blackwing602.com/products/blackwing-natural-set-of-12" target="_blank">Blackwing Natural</a>. It writes buttery smooth and has a great eraser. The Musgrave Pencil Company makes some great pencils, too. I’m very partial to their <a href="https://musgravepencil.com/collections/the-heritage-collection/products/harvest-320-professional-2-wood-cased-hexagonal-pencil-musgrave-pencil-company" target="_blank">Harvest 320</a>. I’d say it’s the ideal pencil, with great color, easy to use, smooth writing, and the wood feels great in my hand. Of course, I’m also never upset if someone hands me a classic <a href="https://weareticonderoga.com/shop/pencils/graphite-pencils/black-wood-cased-pencils/" target="_blank">Black #2 Ticonderoga.</a></p> <h2>Paper and notebooks</h2> <p><strong><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">G</a><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">reg Dunlap</a>:</strong> For the last five years, I’ve been using the <a href="https://www.jetpens.com/Stalogy-Editor-s-Series-365Days-Notebook-A5-Grid-Black/pd/17561" target="_blank">Stalogy Editor's Series 365Days Notebook</a>. I got my first one at the beginning of the <a href="https://www.lullabot.com/our-work/govhub-building-georgias-digital-future">Georgia project</a> and loved it immediately. It has a very subtle gray grid, and the paper is thin, so you can get a ton into one notebook, but it still doesn’t bleed and is a pleasure to write on. I’ve gone through three since then. </p> <p>But there are two problems with it. First, it doesn’t lay flat for the first and last 50 pages or so. Second, the grid is very fine, so if you want to write text along the grid, you have to write small, and things can feel cramped. Generally, I just ignore the grid when writing, but it always bothers me. </p> <p>As an experiment, I recently bought the <a href="https://www.moo.com/us/notebooks-journals/hardcover" target="_blank">Moo hardcover notebook</a> in alpine green and dot grid. I am enamored with its cloth hardcover look. It has a unique lay-flat design which works well, but it’s still tough to write on the early pages. However, the grid is perfect, and I do love the notebook’s style. We’ll see if it sticks or not.</p> <p>One other thing I have played with is working productivity systems into my notebooks. I recently found the <a href="https://inkandvolt.com/products/ink-volt-dashboard-deskpad?_pos=1&amp;_sid=77a2aba28&amp;_ss=r" target="_blank">Ink and Volt Dashboard Deskpad</a>, which is almost exactly what I need for my weekly planning. I no longer have to waste precious notebook pages with weekly to-do lists anymore.</p> <p><strong><a href="https://www.lullabot.com/about/darren-petersen" target="_blank">Darren Petersen</a>:</strong> <a href="https://rhodiapads.com/collections_boutique_dotPad.php" target="_blank">Rhodia Dot pads</a> or any scrap of paper I can find that isn’t already covered in scribbles.</p> <p><strong><a href="https://www.lullabot.com/about/aubrey-sambor" target="_blank">Aubrey Sambor</a>:</strong> I love paper! I use a <a href="https://www.1101.com/store/techo/en/lineup/" target="_blank">Hobonichi A6</a> for a planner right now, which is made with Tomoe River Paper. I love it because my fountain pen ink doesn’t feather on this paper. I have a <a href="https://www.lochby.com/products/field-journal-refill-dot-grid?variant=32294878871588" target="_blank">Lochby notebook</a> for taking notes, but I’ve wanted to try Stalogy because I’ve heard their paper is also good for fountain pens. For my journal, I use a <a href="https://www.leuchtturm1917.us/notebook-classic.html" target="_blank">Leuchtturm 1917</a>. I’ve been using them for years, and I love the shape and how my pens write in them. All my notebooks are dot grid or blank. Since getting into fountain pens, I’ve become a paper snob — my inks look really bad on most non-fancy paper!</p> <p><strong><a href="https://www.lullabot.com/about/matt-robison" target="_blank">Matt Robison</a>: </strong>I haven’t yet found a notebook I always turn to. Any paper will do. But I do love taking notes and compiling thoughts on index cards. Real index cards. Most things sold as index cards today bear no relation to the index cards of old except their size and are really just cut-up pieces of paper. Staples still carries real index cards, though they still pale in comparison to their ancestors. </p> <p><strong><a href="https://www.lullabot.com/about/marissa-epstein" target="_blank">Marissa Epstein</a>: </strong>One of my favorite notebooks is the <a href="https://www.leuchtturm1917.us/notebook-classic.html" target="_blank">Leuchtturm 1917</a>. It provides a lot of options, but I prefer a larger, soft-cover notebook with a dot grid. I like the flexibility that each has a ribbon and a pocket. I also like the weight of the paper. Plus, they come in a million colors. My other favorite “notebook” is the <a href="https://remarkable.com/" target="_blank">ReMarkable</a> tablet. It’s light, just the right size, lets you share text, PDFs, or SVGs, and comes with a ton of templates. I use it when I want a note-taking experience closer to my laptop, without any distractions. Because of this, it takes me a <em>lot</em> longer to go through paper notebooks lately.</p> <p>That said, the best notebook is <em>the one you have</em>. I have gotten wireframes approved using a hotel’s printer paper and the low-quality pen from the hotel room. Sometimes, I write on the back of mail.</p> <p><strong><a href="https://www.lullabot.com/about/matt-kleve" target="_blank">Matt Kleve</a>: </strong>I’m low maintenance and only require a basic legal pad. Yellow is nice. White is okay too.</p> <h2>Digital tools that supplement or replace pen and paper</h2> <p><strong><a href="https://www.lullabot.com/about/andy-blum" target="_blank">Andy Blum</a>:</strong> If I’m on a computer-based meeting, I’ll tend to keep everything digital. I’ll only go to paper when I have a phone call or need to do writing that’s hard to do digitally, like math or diagrams. My general preference of digitally-written notes probably also stems from a lifetime of lead or ink-smeared hands as a left-handed writer.</p> <p><strong><a href="https://www.lullabot.com/about/darren-petersen" target="_blank">Darren Petersen</a>:</strong> I’m experimenting with a <a href="https://remarkable.com/" target="_blank">ReMarkable</a>, which is nifty, but I haven’t fully locked in on how it fits into my day-to-day. But it’s a great note-taking experience so far.</p> <p><strong><a href="https://www.lullabot.com/about/andrew-berry" target="_blank">Andrew Berry</a>:</strong> I’m 100% digital. I live by <a href="https://www.omnigroup.com/omnifocus" target="_blank">OmniFocus</a> for both my work and personal lives. I even bought an Apple Watch primarily for its OmniFocus app and not the usual features. My handwriting has always been slow and atrocious, so I’m much faster with keyboards and voice transcription. For longer form notes, I’ve been using the Apple Notes app more, though I use Dropbox Paper for anything I need to collaborate on.</p> <p>The one area I use paper: diagrams for myself. I was doing some wiring with a 4-way switch and drew things out on paper (though I also had an Excel sheet for recording the behavior of each switch configuration).</p> <p>Since I use a keyboard so much, I bought a <a href="https://codekeyboards.com" target="_blank">WASD Code</a> keyboard with Cherry MX Clear switches in 2016, and it’s still going strong. A few of the keycaps could be replaced, but otherwise, it’s such a joy to type on (and really highlighted just how <em>bad</em> the old MacBook butterfly keyboards were).</p> <p><strong><a href="https://www.lullabot.com/about/aubrey-sambor" target="_blank">Aubrey Sambor</a>:</strong> Sometimes I try to use Todoist or Onenote, but I always go back to good ol’ pen and paper. </p> <p><strong><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">G</a><a href="https://www.lullabot.com/about/greg-dunlap" target="_blank">reg Dunlap</a>:</strong> I have tried every electronic productivity system there is, and they never stick. Pen and paper just tickle a different part of my brain.</p> <p><strong><a href="https://www.lullabot.com/about/marissa-epstein" target="_blank">Marissa Epstein</a>:</strong> I use a mix of paper and digital, and I don’t think I could ever abandon one for the other. I like paper, especially when collaborating with clients in person since it’s easy to use and share. Funny enough, my digital solution is also called<a href="https://paper.dropbox.com/" target="_blank"> Paper</a>. It works for messy drafts and those frequent times I need to take notes <em>fast</em>. I can type faster than I write, even in sloppy cursive.</p> <p><strong><a href="https://www.lullabot.com/about/matthew-tift" target="_blank">Matthew Tift</a>:</strong> The only time I use pen and paper is on yoga or meditation retreats where electronic devices are not used, in which case I use various pens and notepads that I’ve been given at conferences. Otherwise, I do all of my note-taking (and programming, lists, slides, activity logs, blog posts, emails, UML, academic papers, poster presentations, etc.) in text files in Vim, synchronized with my server and other devices using <a href="https://syncthing.net/" target="_blank">Syncthing</a>. I find it easier to use Vim for everything because it’s open source and free software, nobody can take it away from me, it works everywhere, and text files are easily searchable or analyzed.</p> <p><strong><a href="https://www.lullabot.com/about/kat-shaw" target="_blank">Kat Shaw</a>:</strong> <a href="https://www.sublimetext.com/" target="_blank">SublimeText</a> is my go-to for note writing on projects, code snippets, etc. I also use Trello for project tasks, checklists, and notes at times. I use Mac Notes for tracking and organizing a lot of stuff like projects, meetings, contributions, learning, working groups, templates, and more. I made a point of going paperless, and it’s worked for me.</p> <h2>Go forth and scribble</h2> <p>We love finding ways to work that not only make us more productive but also <em>feel</em> right. Some of these tools might make your work more pleasant as well. If you want to dive further into the wonderful world of pens, <a href="https://www.jetpens.com/search?q=sampler&amp;v=2" target="_blank">JetPens has several sampler packs you can buy</a>.</p> <p>For more ways to get the most out of pen and paper, check out<a href="https://www.lullabot.com/articles/squiggles-straight-lines-sketching-during-strategy-and-ux-work" target="_blank"> how we use sketching during our strategy and UX work</a> and <a href="https://www.lullabot.com/articles/why-we-advocate-paper-prototyping-digital-content" target="_blank">how we share ideas faster with paper prototyping</a>. These are techniques that have helped us launch <a href="https://www.lullabot.com/our-work" target="_blank">some of the largest websites in the world</a>. The preferences shared above have been discovered through years of scribbling and buckets of ink.</p> Fri, 23 Sep 2022 10:38:41 -0400 Matt Robison c3b7ead3-0c58-437f-92ec-cbf7c23feba5 CSS Features We’re Thankful For and CSS Features We Need https://www.lullabot.com/articles/css-features-were-thankful-and-css-features-we-need <p>When Microsoft <a href="https://docs.microsoft.com/en-us/lifecycle/announcements/internet-explorer-11-end-of-support" target="_blank">killed off Internet Explorer</a> in June of 2022, front-end developers breathed a sigh of relief. IE's death meant that the development of lots of exciting features in CSS could now be fully supported across all major browsers (Chrome, Firefox, Safari, Edge) without worrying about IE compatibility.</p> <p>So on this joyous occasion, we collected the thoughts of Lullabot's front-end developers and compiled some of the great new-ish features in CSS that we are thankful for. We also had an <a href="https://www.youtube.com/watch?v=bR3S690EF2U" target="_blank">airing of grievances</a> about some of the (many) things the CSS spec still needs.</p> <h2>CSS features for which we are thankful</h2> <h3>Individual transform properties</h3> <p>Most front-end developers are familiar with the <code>transform</code> property - it has lots of use cases for minor little tweaks (like a diagonal ribbon of text) or things like scaling an image on hover. Until recently, though, it could get a bit cumbersome to use because multiple transformations had to be included in one line.</p> <pre> <code class="language-css">.optimus-prime { inline-size: 250px; padding: 2em; border-size: 3px; border-style: solid; background-color: orange; border-color: blue; transform: rotate(-5deg) translateX(10%) scale(1.2); }</code></pre> <p>If you wanted to change just one transformation (for instance, the <code>rotate</code> property on a <code>:hover</code> state), you had to include all of the other properties as well.</p> <pre> <code class="language-css">.optimus-prime:hover { transform: rotate(5deg) translateX(10%) scale(1.5); }</code></pre> <p>However, as of Chrome 104 (released August 2022), these three properties - <code>translate</code>, <code>rotate</code>, and <code>scale</code> can now be listed as individual properties, meaning less code and an easier life.</p> <pre> <code class="language-css">.optimus-prime:hover { rotate: -5deg; translate: 10% 0; scale: 1.5; }</code></pre> <p>Chrome was the last to adopt this, so now it can be widely used across all browsers. Here is a CodePen to demonstrate:</p> <p><iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="300" loading="lazy" scrolling="no" src="https://codepen.io/hotsauce/embed/eYMXEEq?default-tab=result" style="width: 100%;" title="CSS Individual Transform Properties">See the Pen CSS Individual Transform Properties by Adam Varn (@hotsauce) on CodePen.</iframe></p> <p>You can learn more from<a data-target-href="https://web.dev/css-individual-transform-properties/" href="https://web.dev/css-individual-transform-properties/" rel="noreferrer nofollow noopener" target="_blank"> the Chrome team's blog post</a>, but this is a welcome change for anyone working with animations.</p> <h3 data-usually-unique-id="255077394852592464555604">:is() and :where() pseudo selectors</h3> <p>Have you ever had to apply the same styles to multiple HTML elements? Has it made your code long, convoluted, and hard to read? Well, no longer, now that <code>:is()</code> and <code>:where()</code> are supported in all major browsers! </p> <p>Say you want to set the color of all heading elements within an <code>&lt;article&gt;</code>. </p> <pre> <code class="language-css">article h1, article h2, article h3 { color: rebeccapurple; }</code></pre> <p>With <code>:is()</code> and <code>:where()</code>, you can now write the same code this way:</p> <pre> <code class="language-css">article :where(h1, h2, h3) { color: rebeccapurple; }</code></pre> <p>What's the difference between <code>:is()</code> and <code>:where()</code>?  <code>:where()</code> has no specificity. <code>:is()</code> takes on the specificity of its most specific argument. In the next example <code>article h1</code> will be <code>#ccc</code>.</p> <pre> <code class="language-css">article h1 { color: #ccc; } article :where(h1, h2, h3) { color: #000; }</code></pre> <p>But in this example <code>article h1</code> will be <code>#000</code>.</p> <pre> <code class="language-css">article h1 { color: #ccc; } article :is(h1, h2, h3) { color: #000; }</code></pre> <p>MDN<a data-target-href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is" rel="noreferrer nofollow noopener" target="_blank"> dives deep</a> on <code>:is()</code> and compares it to <code>:where()</code> <a data-target-href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where" rel="noreferrer nofollow noopener" target="_blank">here </a>if you need more clarification.</p> <h3>:not() pseudo selector</h3> <p>Supported in all major browsers, <code>:not()</code> adds the ability to select elements that do <i>not</i> match a particular selector and takes on the specificity of its most specific selector. The <code>:not()</code> selector operates in the opposite way of <code>:is()</code>, which selects <i>all</i> elements of a particular selector. </p> <p>With <code>:not()</code>, it's possible to add a margin to the bottom of every list item except for the last. In the past, a developer would write:</p> <pre> <code class="language-css">li { margin-bottom: 10px; } li:last-child { margin-bottom: 0; }</code></pre> <p>Now, this same block of code can be written this way, thus shortening the amount of code written:</p> <pre> <code class="language-css">li:not(:last-child) { margin-bottom: 10px; }</code></pre> <p><code>:not()</code> has been a welcome addition to the CSS landscape. Learn more about <code>:not()</code> <a data-target-href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not" href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not" rel="noreferrer nofollow noopener" target="_blank">over at MDN</a>.</p> <h3 data-usually-unique-id="591331407283442391397538">CSS logical properties</h3> <p>While not exactly "new" (fully supported since 2017), CSS logical properties have started to become the de-facto choice for front-end developers looking to make their code flexible for all languages and layouts.</p> <p>CSS logical properties are a CSS module that can manipulate a page layout through <i>logical</i> dimension and direction rather than <i>physical</i> dimension and direction. That means we use <code>margin-inline-start</code> rather than <code>margin-left</code> when setting the start of a margin.</p> <p>CSS logical properties are centered around the block and inline axes that come from a page's writing direction. <em>Inline</em> is the direction characters naturally flow and <em>block</em> is the direction that text wraps. These properties allow developers to layout a page relative to the content it displays rather than the strict definition of left, right, top, and bottom.</p> <p>For instance, say you are building a multi-lingual site and want to have a page of content and a sidebar with useful links. With languages that read left-to-right (LTR), you would likely want the sidebar to be on the right-hand side and the content on the left-hand side of the page, as that is how LTR content should be read. You could accomplish this using non-logical properties:</p> <pre> <code class="language-css">.content { margin-right: 20px; margin-bottom: 30px; padding: 15px 20px; } .sidebar { border-left: 1px solid #000; padding-left: 10px; }</code></pre> <p>But how would you approach this for languages that read right-to-left (RTL)? You would have to duplicate your code, change the direction for the padding and margin properties, and possibly add additional classes or some other control further up the style sheet:</p> <pre> <code class="language-css">.content-rtl { margin-left: 20px; margin-bottom: 30px; padding: 15px 20px; } .sidebar-rtl { border-right: 1px solid #000; padding-right: 10px; }</code></pre> <p>However, if you use CSS logical properties, you can limit your code to just two selectors and handle both layouts simultaneously.</p> <pre> <code class="language-css">.content { margin-inline-end: 20px; margin-block-end: 30px; padding-block: 15px; padding-inline: 20px; } .sidebar { border-inline-start: 1px solid #000; padding-inline-start: 10px; }</code></pre> <p>The user's preferred language is built into browsers, so even before your CSS is rendered, the browser knows in which direction the user prefers the text to be read. By utilizing <code>border-inline-start</code> for instance, it puts the border at the start of the horizontal area of the content where the <code>.sidebar</code> class should be, regardless of the direction, so that for visitors preferring LTR text, it is on the left-hand side of the sidebar, but for RTL visitors, it would be on the right because that is where the sidebar appears to them.</p> <p>We strive to make sites accessible for as broad an audience as possible, and CSS logical properties help us do that. We even published an <a data-target-href="https://architecture.lullabot.com/adr/20220622-use-css-logical-properties/" href="https://architecture.lullabot.com/adr/20220622-use-css-logical-properties/" rel="noreferrer nofollow noopener" target="_blank">architectural design record</a> (ADR) about logical properties to help accomplish this. You can find a <a data-target-href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties" rel="noreferrer nofollow noopener" target="_blank">great reference guide about CSS logical properties over at MDN</a> if you want to implement it in your projects.</p> <h3 data-usually-unique-id="421506565365696453378860">CSS line clamp</h3> <p><code>-webkit-line-clamp</code> is a sneaky little trick that makes displaying teaser text a much more manageable task. </p> <p>Originally part of Chrome way back in 2011, this "one little trick jQuery plugin authors can't stand" will let you create a four-line, ellipse-ending paragraph that works in all modern browsers:</p> <pre> <code class="language-css">.my-text-block { display: -webkit-box; text-overflow: ellipsis; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; } </code></pre> <p>Yep, you read that right. Even Firefox will accept and render the <code>-webkit</code> prefixes on all of these properties. Though not <i>officially</i> part of the CSS spec, there was such widespread support for this feature that by 2019, it worked everywhere (except IE, of course). A push is underway to add a line-clamp property to CSS, but there will still be backward support for <code>-webkit-line-clamp</code> included if this happens. So go ahead and clamp those lines! <a data-target-href="https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp" href="https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp" rel="noreferrer nofollow noopener" target="_blank">More information can be found at MDN</a>.</p> <h2>CSS features we still need</h2> <h3>Subgrid</h3> <p>CSS grid has been part of a web developer's toolkit since 2017, but what would make grid even more powerful? <b>The ability to align elements within a grid item to the grid itself</b><b>.</b> Currently, this is not possible — without subgrid, developers resort to adding margins, padding, and other methods to align items<i> just so</i>. </p> <p>Subgrid will ease the pain of many web developers who have had to create a grid within a grid. With subgrid, everything within a grid element aligns perfectly by setting <code>grid-template-rows: subgrid</code> on whatever grid item you want to use the same grid as its parent. (This works for <code>grid-template-columns</code> as well.)</p> <p>You can see subgrid in action if you view the following Codepen in Safari 16 or Firefox. Card 2's content is longer than Card 1 and Card 3, and ideally, we'd want the learn more link to align on the same grid line on all three cards. In a browser that supports subgrid, you can see that enabling subgrid allows the contents inside the card to be aligned to the same grid as the cards themselves.</p> <p><iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="300" loading="lazy" scrolling="no" src="https://codepen.io/starshaped/embed/oNdYObd?default-tab=result" style="width: 100%;" title="Subgrid goodness">See the Pen Subgrid goodness by Aubrey (@starshaped) on CodePen.</iframe></p> <p>As of September 2022, subgrid is supported in Firefox and the newest version of Safari.</p> <h3 data-usually-unique-id="974405425198616267316449">Container queries</h3> <p>Media queries for responsive design opened many doors for new and creative website layouts. Still, developers have also wanted more fine-grained control over when the size of an element should change. Enter container queries, which will allow the developer to add breakpoints at the container level.</p> <p>To use container queries, first, define your container.</p> <pre> <code class="language-css">.my-fantastic-container { container-type: inline-size; container-name: main-container; }</code></pre> <p>Now, say you want to resize a card within the container at a certain breakpoint. To do this, add the breakpoint this way:</p> <pre> <code class="language-css">@container main-container (min-width: 768px) { .card { width: 50%; } }</code></pre> <p>The card will now display at 50% width when <code>.my-fantastic-container</code> is 768px or larger.</p> <p>Container queries will be supported in the upcoming Safari 16 release and in Chrome 106, which will be released later in 2022.</p> <h3 data-usually-unique-id="855662009124223627681480">:has() pseudo selector improvements</h3> <p>There has been a lot of progress on <code>:has()</code> as of mid-2022, with Safari and Chrome (behind a flag) supporting it. But Firefox, Edge, and Chrome (without a flag) don't support it.</p> <p>If unfamiliar, the <code>:has()</code> property allows you to target something that…well, <em>has</em> a certain other element.</p> <p>For instance, you can target all <code>&lt;p&gt;</code> elements that have an <code>img</code> within them like this:</p> <p><code>p:has(img) </code></p> <p><iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="300" loading="lazy" scrolling="no" src="https://codepen.io/hotsauce/embed/qBYRmKe?default-tab=result" style="width: 100%;" title=":has example">See the Pen :has example by Adam Varn (@hotsauce) on CodePen.</iframe></p> <p>Which is incredibly useful! However, the <code>:has()</code> attribute is currently lacking the ability to apply specificity as well. </p> <p>Say you wanted to make any unordered list with at least ten items, display it in 2 columns. Code like this would be great if it worked:</p> <pre> <code class="language-css">ul:has(li:nth-child(10)) { column-count: 2; }</code></pre> <p>This same layout should be doable with <code>:has()</code> but limitations on it in browsers prevent it from doing so. Here's a CodePen to demonstrate that will, for now, only work in Safari and Chrome.</p> <p><iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="300" loading="lazy" scrolling="no" src="https://codepen.io/andy-blum/embed/YzadWXw?default-tab=result" style="width: 100%;" title=":has() test">See the Pen :has() test by Andy Blum (@andy-blum) on CodePen.</iframe></p> <p>In Safari and Chrome, you get three neat columns of US states, but it's just a long list everywhere else. That's because the specificity of the <code>nth-child</code> is ignored within the <code>:has()</code>. This might be an edge case, but without this specificity, you can't utilize all that <code>:has()</code> can offer. We hope that once this is fully supported, <code>:has()</code> will continue to evolve to be more flexible and will someday be more useful.</p> <h3 data-usually-unique-id="566395640759436837656251">A real CSS property for screen readers</h3> <p>Accessibility is more important than ever and is a core focus for all our projects. The web is for everyone, and just as we strive to give the same experience to all users and their abilities, we should hope for the same thing from the code we write.</p> <p>Unfortunately, CSS is still lacking a lot in this department. Perhaps the most glaring example is how to hide a block of text (visually) that is still accessible to screen readers. Currently, the only solid way to do something like this is with this amalgamation of CSS properties below, which many developers often shove into a <code>.visually-hidden</code> class.</p> <pre> <code class="language-css">.hidden-text-block { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; clip-path: inset(50%); border: 0; }</code></pre> <p>There has to be a better way to do this. Why couldn't we leverage the idea of <code>.visually-hidden</code> and turn it into its own property?</p> <pre> <code class="language-css">.hidden-text-block { visually-hidden: true; }</code></pre> <p>There doesn't seem to be a lot of progress within the CSS Working Group on this. Still, we hope that as accessibility continues to move to the forefront of working with CSS, there will be more significant movement on this issue.</p> <h3>The color-contrast() property</h3> <p>Continuing along the accessibility track, the <code>color-contrast</code> property takes a specified <code>color</code> property and then compares it against two sets of colors, selecting the ones with the highest contrast.</p> <p>For instance, this compares <code>#404040</code> and <code>#5a5a5a</code> against a color of <code>#111</code> and then picks the higher contrast one.</p> <pre> <code class="language-css">.header-color { color-contrast: (#404040 vs #5a5a5a, #111); }</code></pre> <p>You could see how this could work really well with a set of <code>:root</code> color variables versus a variety of background colors, giving the ability to keep colors accessible without any rewritten styles or other JS plugins.</p> <pre> <code class="language-css">--primary-color: blue; --secondary-color: green; --main-bg: #4a4a4a; --sidebar-bg: #32a3fa; .text-color { color-contrast: (--main-bg vs --sidebar-bg, --primary-color); }</code></pre> <p>Unfortunately, this property is still experimental, and as of 2022, no browser supports it. Let's hope for some progress on this requested property from the CSS WG. You can learn more about it <a data-target-href="https://drafts.csswg.org/css-color-6/#colorcontrast" href="https://drafts.csswg.org/css-color-6/#colorcontrast" rel="noreferrer nofollow noopener" target="_blank">from their draft spec.</a></p> <h3 data-usually-unique-id="835348996942810447191129">Change non-animatable properties at the beginning/end of an animation</h3> <p>CSS animations are amazing. A quick browse of CodePen reveals some of the absolute jaw-dropping things that brilliant front-end developers can do with CSS alone. But that doesn't mean there isn't room for improvement.</p> <p>For instance, you can use <code>@keyframes</code> to control the steps of an animation very easily, and a common method is to fade something from visible to invisible (or vice versa) over a series of frames. This transition works great as a visual presentation, but the effect is not the same for screen readers.</p> <p>The problem is that a normal show/hide CSS property, such as <code>display: none</code> or <code>visibility: hidden</code> doesn't get considered at the start/end of an animation. </p> <pre> <code class="language-css">.ghost-kitty { inline-size: 2rem; block-size: 4rem; background: url("../images/ghost_cat.jpg") no-repeat center; animation: ghost-kitty 5s infinite; } @keyframe ghost-kitty { 0% { opacity: 100%; } 25% { opacity: 75%; } 50% { opacity: 50%; } 75% { opacity: 25%; } 100% { opacity: 0%; display: none; } }</code></pre> <p>Using the example above, our <code>.ghost-kitty</code> will disappear (for good) once it hits the <code>display: none</code> portion of the keyframe step, but it would still be visible to a screen reader because it just gets rendered at the first usage as it works down the document, which defeats the purpose of an animation. And because <code>opacity</code>, which is commonly used in animations, does not affect screen readers, you are stuck without a solution in CSS to truly hide/show something for all audiences.</p> <p>It's possible that as <code>:has()</code> and <code>:is()</code> and <code>:where()</code> grow in usage, there might be some weird hack to make this work (like <code>line-clamp</code>), but having a useful way to truly control non-animatable properties within an animation would be nice if only to put less JavaScript out there in the world!</p> <h3 data-usually-unique-id="185395652709124115699718">text-wrap: balance</h3> <p>Discussed as <a data-target-href="https://www.w3.org/TR/css-text-4/#text-wrapping" href="https://www.w3.org/TR/css-text-4/#text-wrapping" rel="noreferrer nofollow noopener" target="_blank">part of the Text 4 spec</a> for CSS, this would work the same way as <code>wrap</code> for an inline box, where it would try to eliminate as much empty space as possible - without increasing the number of lines - by considering the amount of remaining space within the block container.</p> <p>In practice, it would basically wrap text so that each line has (approximately) the same length. So with <code>text-wrap: balance</code>, this:</p> <pre> <code>Checkout this really long line of text, boy does it go on for a while huh? I wonder when it will end? Who knows, but it sure is long!</code></pre> <p>would become</p> <pre> <code>Checkout this really long line of text, boy does it go on for a while huh? I wonder when it will end? Who knows, but it sure is long!</code></pre> <p>The best part is that this would work with fluid columns when the length of a column is unknown, so this would work wonders with grid and other responsive design elements.</p> <p><code>text-wrap: balance</code> is just a requested spec for now, but let's keep our fingers crossed that it makes it in sooner rather than later!</p> <h3 data-usually-unique-id="240014412090268084375909">Break up box-shadow so its styles can be set separately</h3> <p>This feature is more of a want to have than a need, but it would be nice to declare each box-shadow value independently. Much like being able to declare a border either by shorthand, such as <code>border: 1px solid rebeccapurple </code>or individually, like:</p> <pre> <code class="language-css">border-width: 1px; border-style: solid; border-color: rebeccapurple</code></pre> <p>The ability to declare box-shadow values like this would be beneficial:</p> <pre> <code class="language-css">box-shadow-color: rebeccapurple; box-shadow-offset-x: 2px;</code></pre> <h3 data-usually-unique-id="710070633732869482483918">// Comments like this</h3> <p>Seriously, why are we stuck with <code>/* */</code> as our only option?</p> <h2>Conclusion</h2> <p>We may never get every little feature we want in CSS. Still, the continuing evolution of it as a language and the new features from the last few years are enough to excite almost any front-end developer. </p> <p> </p> <p>To keep up with the latest and greatest and what to get excited about next, check out these resources:</p> <ul><li>The <a data-target-href="https://shoptalkshow.com/" href="https://shoptalkshow.com/" rel="noreferrer nofollow noopener" target="_blank">ShopTalk Show</a> podcast: <a data-target-href="https://shoptalkshow.com/529/" href="https://shoptalkshow.com/529/" rel="noreferrer nofollow noopener" target="_blank">Hear Jen Simmons talk about what's coming to CSS</a>, including <code>:has()</code>, container queries, and more! (Note that this episode is more Safari-centric. Still, we can get excited about these things coming to all browsers!)</li> <li>The <a data-target-href="https://syntax.fm/" href="https://syntax.fm/" rel="noreferrer nofollow noopener" target="_blank">Syntax</a> podcast: There's a recent (as of August 2022) episode about <a data-target-href="https://syntax.fm/show/497/css-proposals-when-css-masonry-carets" href="https://syntax.fm/show/497/css-proposals-when-css-masonry-carets" rel="noreferrer nofollow noopener" target="_blank">upcoming CSS proposals</a> that may or may not see the light of day, such as <code>@when</code> and carets. Intrigued? Give this episode a listen! They also have an <a data-target-href="https://syntax.fm/show/470/i-can-has-new-css-selector" href="https://syntax.fm/show/470/i-can-has-new-css-selector" rel="noreferrer nofollow noopener" target="_blank">episode on </a><code><a data-target-href="https://syntax.fm/show/470/i-can-has-new-css-selector" href="https://syntax.fm/show/470/i-can-has-new-css-selector" rel="noreferrer nofollow noopener" target="_blank">:has()</a></code> and about new viewport units, which is worth your time.</li> <li><a data-target-href="https://css-weekly.com/" href="https://css-weekly.com/" rel="noreferrer nofollow noopener" target="_blank">The CSS Weekly newsletter</a>: A compilation of some of the best articles written by many experts in the community, including Sara Soueidan, Michelle Barker, Jen Simmons, Una Kravets, Jhey Tompkins, Adam Argyle, and more! Subscribe to keep up with the latest and greatest in the CSS world, plus information about potential new CSS changes and features coming down the pipeline.</li> <li><a data-target-href="https://www.w3.org/Style/CSS/members" href="https://www.w3.org/Style/CSS/members" rel="noreferrer nofollow noopener" target="_blank">The CSS Working Group</a>: If you want to get into the weeds with what's coming down the road for CSS, check out the CSS working group's <a data-target-href="https://www.w3.org/blog/CSS/" href="https://www.w3.org/blog/CSS/" rel="noreferrer nofollow noopener" target="_blank">blog</a> or the <a data-target-href="https://github.com/w3c/csswg-drafts/issues" href="https://github.com/w3c/csswg-drafts/issues" rel="noreferrer nofollow noopener" target="_blank">GitHub issue list</a>. </li> </ul> Wed, 21 Sep 2022 15:31:43 -0400 Aubrey Sambor, Adam Varn 8472a639-c9fa-4947-9f30-cee324b04a0d JavaScript and Events - The Fundamentals https://www.lullabot.com/articles/javascript-and-events-fundamentals <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-remote_video"> <div class="video video--remote video--wide"> <div class="field--field-media-oembed-video"> <iframe src="/media/oembed?url=https%3A//www.youtube.com/watch%3Fv%3DQO8X9QiyIbA&amp;max_width=0&amp;max_height=0&amp;hash=rNGBMSOsG4qUrY7uq_COgGee_pUpex16T2INk-t5pGM" frameborder="0" allowtransparency="" width="200" height="113" class="media-oembed-content" title="JavaScript Fundamentals: Events"></iframe> </div> </div> </div> <p>Understanding how the language and the browser interact is important if you want to get the most out of JavaScript and save yourself a bunch of future headaches. We'll cover all the basics about browser events and point you toward other resources so you can dive deeper.</p> <h2>JavaScript vs. the browser</h2> <p>JavaScript is a compact programming language that is <strong>single-threaded</strong>. That means all the action happens in the same pipeline. In operating systems and some other programming languages, you can do multiple things at once, but JavaScript can only do one thing at a time.</p> <p>Because of that, JavaScript code can be described as <strong>blocking</strong>. Only one thing can happen, and the next thing JavaScript wants to do can't start until the first thing ends.</p> <p>JavaScript on our websites runs in the browser's JavaScript engine and will do things like arithmetic and logical flow (if blocks, for loops, while loops), and it has some built-in data types like strings, numbers, objects, and arrays.</p> <p>On the other hand, you have browser APIs. The browser API is an extensive and growing set of capabilities the browser has built-in. Some examples:</p> <ul><li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction">DOM</a> - the Document Object Model and all the methods that go along with it to allow manipulation of HTML.</li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model">CSSOM</a> - the CSS Object Model which allows manipulation of CSS</li> <li>Observers (like <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">intersection observer</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API">resize observer</a>)</li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch</a> - an interface for fetching resources </li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API">History</a> - allows the navigation and manipulation of the contents of a browser's history</li> </ul><p>All of these APIs are part of the browser.</p> <h2>The event loop</h2> <p>The event loop is how JavaScript and the browser interact.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 985px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image2.png?itok=x2Whdw71" width="900" height="531" alt="Chart showing the event loop, displaying the javascript call stack, web apis, and callback queue and how they interact. " /><figcaption class="image__caption">The JavaScript event loop.</figcaption></figure></div> <h3>The JavaScript call stack</h3> <p>Remember that JavaScript is blocking and single-threaded. The call stack is how functions are organized, and a second task can't begin until the first task ends. This order remains true…unless the first task calls another function.</p> <p>If a function is called during the execution of a task, it gets placed on top of the call stack. If we call another function within that function, it will also be placed on top of the stack. Since it's a stack, we can only pull from the top of it, so we can't pull things off the bottom of the stack to execute until those top pieces are resolved. This way of dealing with tasks is an approach known as last-in-first-out or <a href="https://www.geeksforgeeks.org/lifo-last-in-first-out-approach-in-programming/">LIFO</a>.</p> <p>When we describe code as "blocking," this is the source of that problem. Functions running in the call stack prevent other tasks from being completed.</p> <h3>The callback queue</h3> <p>To avoid blocking other functions, JavaScript can offload work to Web APIs. If you want to make an AJAX call or set a timeout, you can send that information to the Web API to do all that work. When the API has done its work, if it has a callback function to run, it goes into the <em>Callback Queue</em>. Once JavaScript has run everything in its call stack, it pulls the next task from the callback queue, runs that to completion, then grabs the next thing in the queue.</p> <p>If you want to dive into this deeper, <a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ">you can watch this video about the Event Loop</a>. It demos a website called <a href="http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D">Loupe</a>, a simulated event loop in the browser.</p> <p>The event loop leads us to another way JavaScript offloads work to a browser API: events.</p> <h2>Browser events</h2> <p>To reiterate, the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event">Event Interface</a> is a Web API. Events are a <em>browser</em> thing, not a javascript thing. The browser has events and kicks them off, and JavaScript is given the ability to respond to those events. Events allow the browser to kick off programmatic responses to interactions, changes, and other significant happenings.</p> <p>Events can be triggered by user interaction, browser behavior, and your code. User interaction events are triggered by things like mouse clicks, buttons pressed on the keyboard, and elements gaining or losing focus. Browser behavior events happen for things like loading and unloading resources and changes in device orientation. Finally, you can artificially set off events from your code with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent">.dispatchEvent()</a> method.</p> <p><a href="https://developer.mozilla.org/en-US/docs/Web/Events#event_listing">View the full reference for available events</a>.</p> <h2>Handling events in JavaScript</h2> <p>So events happen on the browser side. Where does JavaScript come in?</p> <p>To react to events, you’ll use<a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener"> EventTarget.addEventListener(eventtype, callback)</a>.</p> <p>An EventTarget is, for the most part, any element on your page. The class is broader than that, but for our purposes, this is enough. We can say "select my button" or "select my paragraph" and add an event listener to it.</p> <p>That short line of code is saying: "Hey browser, when 'EventTarget' has an 'eventtype' event, can you run the function 'callback'?"</p> <p>Let's walk through what happens when someone visits your page:</p> <ul><li>Server/Cache delivers a raw .html file.</li> <li>Browser parses that HTML into the DOM. <ul><li><em>This is where inline JS runs.</em></li> </ul></li> <li>Browser performs prefetches.</li> <li>Browser does layout/paint operations.</li> <li>Browser fetches remaining resources. <ul><li><em>This is where external JS runs. If you have put your code in an external file, this is when your JavaScript code is loaded and run.</em></li> </ul></li> <li>Running js calls addEventListener. Let's say you want to react to a button click.</li> <li>Browser stores callback function. <ul><li><em>An indeterminate amount of time passes. Right now, everything is sitting over in the Web API.</em></li> </ul></li> <li>The specific event happens on the specified target (the button click).</li> <li>The browser drops the callback function into the callback queue.</li> </ul><h3>What if there are multiple events?</h3> <p>When a click happens on an element, you have <code>mousedown</code>, <code>mouseup</code>, and <code>click</code> events all happening in quick succession, all on the same thing. How do we know things happen in the correct order?</p> <p>The browser fires off events in a predictable pattern that happens in up to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase">three phases</a>: capture phase, at target, and bubble phase. Think of event phases like a ball bouncing. The ball falls, makes contact with the ground, and then rebounds.</p> <p>Before going into more detail, it will help to distinguish how different types of people perceive a web page versus how the browser perceives it. Designers might see components coupled in layouts. Content strategists might see headings, articles, and footnotes. Developers might see sets of nested elements containing text and the abstracted code behind them.</p> <p>The browser doesn't care about any of that. It cares not about content or presentation. It only cares about the DOM. The browser stores its knowledge of the page's elements like a flow chart in a tree structure, relating each node in the tree to every other node. Events always start at the top of the tree and work their way down toward the deepest event target.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 785px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image1.png?itok=0HgXirNW" width="785" height="713" alt="An HTML DOM tree with a red line showing how an event traverses the tree until reaching its target." /><figcaption class="image__caption">The event will traverse the tree until it reaches the anchor element, its target.</figcaption></figure></div> <p>Imagine a click event on the anchor tag element near the bottom of the diagram. The event starts at the top and digs deeper.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 782px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image4.png?itok=fa1slWuz" width="782" height="713" alt="An HTML DOM tree with a faded red line showing how an event traverses the tree until reaching its target, with a giant red dot over the target, the anchor element " /><figcaption class="image__caption">The event has reached it target.</figcaption></figure></div> <p> Once the deepest possible element has been reached, the event fires from the "target" element. In this case, "target" means what the browser was aiming for, not necessarily the user/developer. If a user is aiming for one thing and misses, the target is whatever they actually clicked on.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 816px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image7.png?itok=5_WsMyms" width="816" height="712" alt="An HTML DOM tree with a red line showing how an event traverses, or bubbles, back up the tree." /><figcaption class="image__caption">After hitting the target, some events bubble back up the DOM tree.</figcaption></figure></div> <p>For some events, you have a bubbling phase. It retraces its path back up through the DOM toward the root. Not all events bubble. There's not a handy way that we've found to remember which events bubble and which ones do not. You'll need to check the MDN documentation.</p> <p>It's important to note the event itself "moves" rather than having a cascade of events. Instead of having a series of events fire, one per DOM node as the browser works down towards the target, the browser has a single event that is "retargeted" on each DOM node in succession. Because of this, it is important to react quickly in any event listeners if you need to alter or stop the event before the browser retargets the event to the next DOM node.</p> <p>Why does all of this matter?</p> <p>When you add an event listener, you can tell the browser which phase to invoke your callback function on. Inside your callback function, you can alter how the browser handles the event by:</p> <ul><li>Preventing default behavior</li> <li>Preventing the event from continuing its path along the DOM</li> <li>Preventing the event from invoking any additional callbacks on the current target, if you have things in the correct order</li> </ul><h3>Adding event listeners</h3> <p>Let's look at some code. If we want an event listener, we first need to get a reference to the element that is our event target. In this case, it will be the first button on the page.</p> <pre> <code class="language-javascript">const button = document.querySelector(‘button’);</code></pre> <p>We have three ways to set a callback on a click event. The first way has a named function declared elsewhere.</p> <pre> <code class="language-javascript">function myCallback(event) {   console.log(event); } button.addEventListener(‘click’, myCallback);</code></pre> <p>The second way passes an anonymous function in the older syntax.</p> <pre> <code class="language-javascript">button.addEventListener(‘click’, function(evt) { console.log(evt) });</code></pre> <p>The final way is with an arrow function.</p> <pre> <code class="language-javascript">button.addEventListener(‘click’, (e) =&gt; { console.log(e) });</code></pre> <p>While these examples all do the same thing, they will be treated as three different event listeners. If we run this code, clicking the button would result in 3 identical console log statements.</p> <p>The default behavior for events:</p> <ul><li>Event listeners will <strong><em>always</em> fire on the target phase</strong>.</li> <li>Event listeners will <strong>fire on the bubble phase by default</strong> on events that bubble. The event will go down the DOM until it hits the target, but even listeners on the target's parent will not fire until the bubble phase when the event retraces its path.</li> <li>Multiple event listeners on the same target will be <strong>triggered in the order they were attached.</strong></li> </ul><h3>Modifying default behavior</h3> <p>Event listeners can be set to run on capture. As the event passes an element, it triggers the callback function before the event gets to the target.</p> <pre> <code class="language-javascript">.addEventListener(‘click’, callback, true); .addEventListener(‘click’, callback, {useCapture: true});</code></pre> <p>Event listeners can also be set to remove themselves after firing.</p> <pre> <code class="language-javascript">.addEventListener(‘click’, callback, {once: true});</code></pre> <h3>Responding to events</h3> <p>All events have these properties/methods.</p> <pre> <code class="language-javascript">Event = {   bubbles: boolean,   cancelable: boolean,   currentTarget: EventTarget,   preventDefault: function,   stopPropagation: function,   stopImmediatePropagation: function   target: EventTarget,   type: string, }</code></pre> <p>The <code>currentTarget</code> property points to the current event target as the event moves through the DOM and will change for the event as it is retargeted. The target property is the event's intended target. The <code>type</code> will be the name of the event, like "mousedown" or "click."</p> <p>Some events that would normally trigger a default browser action can have that action prevented by calling the preventDefault() method. For example, if you have a link with a click event listener, you can prevent the browser from navigating away from the current page. While this is not a great idea for accessibility purposes, it is a common enough occurrence to serve as an example.</p> <pre> <code class="language-javascript">function myCallback(event) {   event.preventDefault(); }</code></pre> <p>Events that might trigger additional callbacks on elements further along the path can be halted with the stopPropogation() method. Calling it will stop the event from being retargeted further. Stopping propagation is a valuable tool to manage events that might otherwise conflict or overlap. For example, a click event listener on a parent element and a button inside, and you want to listen to one set of clicks but not another.</p> <pre> <code class="language-javascript">function myCallback(event) {   event.stopPropagation(); }</code></pre> <p>With stopImmediatePropagation(), you can also stop any additional callbacks on the current element that might come after the current callback. Calling this method is not common and requires careful consideration of the order in which listeners are added.</p> <pre> <code class="language-javascript">function myCallback(event) {   event.stopImmediatePropagation(); }</code></pre> <p>Certain event types have additional relevant properties. Click events get the coordinates and modifier keys. Keyboard events get keycodes. Focus events get the element that the focus moved from/to.</p> <h3>Removing event listeners</h3> <p>You can remove event listeners if you only want to listen for a limited amount of time or a specified number of times. The trick is <strong>to have a reference to the exact same function in the add and remove methods</strong>. You must have named callback functions if you want to do this. Anonymous functions will not work, even if they are character-for-character matches.</p> <p>The following code works:</p> <pre> <code class="language-javascript">function myCallback(event) {   console.log(event); } button.addEventListener(‘click’, myCallback); button.removeEventListener(‘click’, myCallback);</code></pre> <p>These examples do not work:</p> <pre> <code class="language-javascript">button.addEventListener(‘click’, function(evt) { console.log(evt) }); button.removeEventListener(‘click’, function(evt) { console.log(evt) }); button.addEventListener(‘click’, (e) =&gt; { console.log(e) }); button.removeEventListener(‘click’, (e) =&gt; { console.log(e) });</code></pre> <h3>Discovering and debugging event listeners</h3> <p>All major browsers have a way to discover event listeners in the devtools. Because the Web API depends on the browser's implementation, it can be different for each browser. The following image is what it looks like in Chrome.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 884px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image6.png?itok=43f5_mA1" width="884" height="970" alt="How Chrome organizes declared event listeners." /><figcaption class="image__caption">Chrome organizes by type of event. We can see that there are two click listeners and one keydown listener currently visible.</figcaption></figure></div> <p>With Safari, you need to look at the Node section.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image3.png?itok=CQV6ls00" width="746" height="1732" alt="Devtools in safari showing event listeners" /></figure></div> <p>Firefox puts a little event badge in the inspector, which will show you all of the event listeners on that one node.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-09/image5.png?itok=YshaAr1O" width="900" height="612" alt="devtools in firefox showing event badges on elements, and the pop up event listeners" /></figure></div> <h2>Conclusion</h2> <p>Reacting to browser events in JavaScript is one of the fundamental ways to start building more reactive websites and rich web applications. This article has been a primer on the fundamentals that should save you some headaches down the road. Here are some additional resources:</p> <ul><li><a href="https://developer.mozilla.org/en-US/docs/web/api/event">MDN Web Docs</a></li> <li><a href="http://latentflip.com/loupe/">http://latentflip.com/loupe/</a></li> <li><a href="https://domevents.dev/">https://domevents.dev/</a></li> </ul> Wed, 07 Sep 2022 15:18:48 -0400 Andy Blum 1a5375b1-2b3d-4a7a-b37b-14c8b6361e72 Lullabot Podcast: Drupal Automatic Updates—The Update https://www.lullabot.com/podcasts/lullabot-podcast/drupal-automatic-updates-update <p>Keeping a Drupal site up-to-date can be tricky and time consuming. Host Matt Kleve sits down with three people in the Drupal community who have been working to make that process easier and faster.<br /><br /> It's been in progress for awhile, but now you might be able to start using Automatic Updates on your Drupal Site.</p> Thu, 18 Aug 2022 12:51:42 -0400 Matt Kleve 972560ac-d074-47e0-842e-0d3cbbad8289 Hacking Culture: Yvette Erasmus on Building Healthy Relationships with Nonviolent Communication (NVC) https://www.lullabot.com/podcasts/hacking-culture/yvette-erasmus-building-healthy-relationships-nonviolent-communication-nvc <p>Matthew Tift talks with Yvette Erasmus about building healthy relationships at home and work with nonviolent communication (NVC), including:</p> <ul><li>An overview of NVC</li> <li>The difference between "street NVC" and classic NVC</li> <li>The ethics of NVC</li> <li>Bringing NVC into the workplace (in stealth mode)</li> <li>How accepting the present moment might needs for safety and security in the world</li> </ul> Thu, 18 Aug 2022 07:00:00 -0400 Matthew Tift 812a35d3-0f94-48f1-adb5-3c4703960edd Bitmasks in JavaScript: A Computer Science Crash Course https://www.lullabot.com/articles/bitmasks-javascript-computer-science-crash-course <p>One of the nice things about front-end web development as a career choice is that the software and coding languages are available on every modern machine. It doesn’t matter what operating system or how powerful your machine is. HTML, CSS, and JavaScript will run <em>pretty well</em> on it. This lets a lot of us in the industry bypass the need for formal education in computer science.</p> <p>Unfortunately, this also has the side effect of leaving little gaps in our knowledge here and there, especially in strategies like bitmasking, which are seldom used in web development.</p> <h2>What is a bitmask?</h2> <p>Bitmasking is a strategy that can be used to store multiple true-or-false values together as a single variable. Depending on the language, this can be more memory efficient and also opens up the doors to some special operators that we’ll look at later. This trick takes advantage of two simple facts:</p> <ul><li>humans and computers look at numbers differently.</li> <li>the way computers think about numbers is identical to how they think about true and false.</li> </ul><p>Humans typically think of numbers in a decimal, or base-10, system. We have 10 unique digits of 0-9, and when we want to count beyond 9, we create new columns as needed to symbolize how many multiples of ten, one hundred, one thousand, (the powers of ten), and so on we need. Computers, on the other hand, look at numbers in a <a href="https://code.tutsplus.com/articles/number-systems-an-introduction-to-binary-hexadecimal-and-more--active-10848" target="_blank">binary or base-2 system</a>. Computers have 2 unique digits, 0 or 1, and when they need to count beyond that, they create new columns to symbolize how many multiples of 2, 4, or 8 (the powers of two) <em>they</em> need. While we think of numbers differently, the values of integers are ultimately identical, and the computer stores all numbers as binary values. Each individual binary digit is a <em>bit </em>of information.</p> <table border="1" cellpadding="1" cellspacing="1" style="width: 500px;"><thead><tr><th scope="col">English</th> <th scope="col">Base-10</th> <th scope="col">Base-2</th> </tr></thead><tbody><tr><td>Zero</td> <td>0</td> <td>0</td> </tr><tr><td>One</td> <td>1</td> <td>1</td> </tr><tr><td>Two</td> <td>2</td> <td>10</td> </tr><tr><td>Three</td> <td>3</td> <td>11</td> </tr><tr><td>Four</td> <td>4</td> <td>100</td> </tr><tr><td>Five</td> <td>5</td> <td>101</td> </tr><tr><td>Six</td> <td>6</td> <td>110</td> </tr><tr><td>Seven</td> <td>7</td> <td>111</td> </tr><tr><td>Eight</td> <td>8</td> <td>1000</td> </tr><tr><td>Nine</td> <td>9</td> <td>1001</td> </tr><tr><td>Ten</td> <td>10</td> <td>1010</td> </tr></tbody></table><p>When we combine the fact that all our numbers are converted to binary for storage with the fact that boolean true/false values are also stored as a 1 or 0, respectively, we can see how we could easily store a group of boolean values as a single integer. All we have to do is make sure each value we care about is stored as a separate power of two.</p> <h2>Where might I see this?</h2> <p>A great example of this in front-end development is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition" target="_blank">Node.compareDocumentPosition</a> method. This method compares the relative positioning between two nodes on a page and returns a bitmask of the resulting comparison. There are six possible values of <code>a.compareDocumentPosition(b)</code></p> <ul><li>Disconnected - These nodes are not in the same document tree (for example, one node is in a web component’s <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" target="_blank">shadow DOM</a>)</li> <li>Preceding - Node a follows node b in the document tree.</li> <li>Following - Node b follows node a in the document tree.</li> <li>Contains - Node a is a descendant of node b.</li> <li>Contained By - Node a is an ancestor of node b.</li> <li>Implementation Specific - This rarely means anything to us and is an artifact of how the calculation is done within the browser.</li> </ul><p>The result of our comparison could yield any combination of those 6 being true or false, a potential 64 unique combinations! The way we make sense of this, however, is to assign each value a bit. Since we have 6 values, we’ll need six bits.</p> <table border="1" cellpadding="1" cellspacing="1" style="width: 500px;"><tbody><tr><td>Disconnected</td> <td>1</td> <td>000001</td> </tr><tr><td>Preceding</td> <td>2</td> <td>000010</td> </tr><tr><td>Following</td> <td>4</td> <td>000100</td> </tr><tr><td>Contains</td> <td>8</td> <td>001000</td> </tr><tr><td>Contained By</td> <td>16</td> <td>010000</td> </tr><tr><td>Implementation Specific</td> <td>32</td> <td>100000</td> </tr></tbody></table><p>Now our 64 possible combinations can be numbered from 0 (all are false) to 63 (all are true). Of course, not <em>all</em> combinations are actually possible, as nodes can neither precede <em>and</em> follow nor contain <em>and</em> be contained by. Nevertheless, when we examine the returned number bit-by-bit, we can tell exactly which values are true and which are false.</p> <h2>How do I use this?</h2> <p>One great use of this is in <a href="https://github.com/carbon-design-system/carbon-for-ibm-dotcom/blob/3bb5f39f7e773ff10b8ac7a35461549ade07dd45/packages/web-components/src/components/expressive-modal/expressive-modal.ts" target="_blank">the focus-trapping logic in IBM’s Carbon Design System</a>. Since we want to prevent focus from leaving the modal and instead loop it back into the element, we have a <code>focusout</code> event listener on the modal’s container element. When focus leaves an element within the modal, the <code>focusout</code> event bubbles up, and we’re able to see the event’s target element that just lost focus, as well as the event’s <code>relatedTarget</code> element that just gained focus. We can then compare the positioning of the <code>relatedTarget</code> to the modal container, and if the “contains” value is not true, we know we need to force focus back into the modal.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-08/ktprgcsa.webp?itok=KNvY44xL 325w, /sites/default/files/styles/max_650x650/public/2022-08/ktprgcsa.webp?itok=Sx11KlLv 650w, /sites/default/files/styles/max_800/public/2022-08/ktprgcsa.webp?itok=Nb_XY2pR 800w, /sites/default/files/styles/max_1300x1300/public/2022-08/ktprgcsa.webp?itok=xWIaok0o 1300w, /sites/default/files/styles/max_2600x2600/public/2022-08/ktprgcsa.webp?itok=R3uKz5Bu 2059w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-08/ktprgcsa.png?itok=KNvY44xL 325w, /sites/default/files/styles/max_650x650/public/2022-08/ktprgcsa.png?itok=Sx11KlLv 650w, /sites/default/files/styles/max_800/public/2022-08/ktprgcsa.png?itok=Nb_XY2pR 800w, /sites/default/files/styles/max_1300x1300/public/2022-08/ktprgcsa.png?itok=xWIaok0o 1300w, /sites/default/files/styles/max_2600x2600/public/2022-08/ktprgcsa.png?itok=R3uKz5Bu 2059w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-08/ktprgcsa.png?itok=KNvY44xL 325w, /sites/default/files/styles/max_650x650/public/2022-08/ktprgcsa.png?itok=Sx11KlLv 650w, /sites/default/files/styles/max_800/public/2022-08/ktprgcsa.png?itok=Nb_XY2pR 800w, /sites/default/files/styles/max_1300x1300/public/2022-08/ktprgcsa.png?itok=xWIaok0o 1300w, /sites/default/files/styles/max_2600x2600/public/2022-08/ktprgcsa.png?itok=R3uKz5Bu 2059w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:2059/1283;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-08/ktprgcsa.png?itok=xWIaok0o" alt="Diagram showing how focus is able to move between the elements within a modal, and is forced to wrap between the first and last focusable items when attempting to move focus out of the modal’s containing element." loading="lazy" /></picture></figure></div> <p>While we could split out the bits and do individual comparisons, JavaScript has <a href="https://www.w3schools.com/js/js_bitwise.asp" target="_blank"><em>bitwise operators</em></a> designed specifically to compare two bitmasks and yield a third bitmask. These operators will compare each individual bit and then yield a 0 or a 1.</p> <ul><li>&amp; will evaluate to 1 when two compared bits are <em>both</em> 1</li> <li>| will evaluate to 1 when either compared bit is 1</li> <li>^ will evaluate to 1 when one, but not both, compared bits are 1.</li> </ul><p>Try comparing 5 and 9 with each operator in this truth table to see bitwise calculations in action:</p> <p><iframe allowfullscreen="true" allowtransparency="true" frameborder="no" height="300" loading="lazy" scrolling="no" src="https://codepen.io/andy-blum/embed/ExEajpX?default-tab=html%2Cresult" style="width: 100%;" title="Bitwise Calculator">See the Pen Bitwise Calculator by Andy Blum (@andy-blum) on CodePen.</iframe></p> <p><a data-target-href="https://codepen.io/andy-blum/pen/ExEajpX" href="https://codepen.io/andy-blum/pen/ExEajpX" rel="noreferrer nofollow noopener" target="_blank">https://codepen.io/andy-blum/pen/ExEajpX</a></p> <p>Once we understand these comparison operations, we can use them within our code. We’ll start by creating the combination flags <code>PRECEDING</code> and <code>FOLLOWING</code>. These new flags combine the bitmasks provided by the <code>Node</code> object. In our use case, <code>PRECEDING</code> will indicate that the compared node’s tab order should be prior to the current node and <code>FOLLOWING</code> will indicate the opposite. We’ll also create a bitmask <code>WITHIN</code> that will be easier to read in our code later. </p> <pre> <code class="language-javascript">const PRECEDING = Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS; const FOLLOWING = Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY; const WITHIN = Node.DOCUMENT_POSITION_CONTAINED_BY;</code></pre> <p>Next, within our <code>focusout</code> event listener, we can compare the relative positions of the event’s <code>target</code>, which just lost focus and its <code>relatedTarget</code>, which just gained focus. The code below has been modified slightly from the source to make it easier to read and has comments pointing to the breakdown below.</p> <pre> <code class="language-javascript">function handleFocusOut(event) { const { target, relatedTarget } = event; // #1 const positionToModal = this.compareDocumentPosition(relatedTarget) | (this.shadowRoot?.compareDocumentPosition(relatedTarget) || 0); // #2 const positionToPrevious = target.compareDocumentPosition(relatedTarget); // #3 const relatedTargetIsContained = Boolean(positionToModal &amp; WITHIN); // #4 if (!relatedTargetIsContained &amp;&amp; !(relatedTarget === this)) { // #5a if (positionToPrevious &amp; PRECEDING) // #6a tryFocusElems(focusableElements as [HTMLElement], true, this); // #5b } else if (positionToPrevious &amp; FOLLOWING) { // #6b tryFocusElems(focusableElements as [HTMLElement], false, this); } } };</code></pre> <p>Let’s break it down:</p> <ol start="1"><li>We create a bitmask variable, <code>positionToModal</code>. This is a combination of the comparison between the modal and the <code>relatedTarget</code> as well as the modal’s shadowroot and the <code>relatedTarget</code>. The element we’ve focused to could be in either the regular document or in the Shadow DOM, so we want to compile both comparisons together.</li> <li>We create a bitmask variable, <code>positionToPrevious</code>. This is the comparison of the target and the related target.</li> <li>We create a boolean variable, <code>relatedTargetIsContained</code>. This compares <code>positionToModal</code> and <code>WITHIN</code>. If the element we focused on is in any way <i>inside our modal</i>, then this variable is true.</li> <li>We check to see if our <code>relatedTarget</code> is contained within the modal and that our <code>relatedTarget</code> is not the modal itself. If that’s true, then we know our <code>relatedTarget</code> is <i>outside</i> the modal, and we need to redirect focus.</li> <li>We compare our <code>positionToPrevious</code> bitmask with our <code>PRECEDING</code> and <code>FOLLOWING</code>  bitmasks. If they overlap, then we know which way to try to focus, and we use our <code>tryFocusElems</code> function to move focus back into the modal.</li> <li>The <code>tryFocusElems</code> function systematically attempts to place focus on each element in <code>elems</code>. It can run in forward or reverse order based on the second argument, and if none of the elements provided will hold focus, it falls back to the element provided in the third argument</li> </ol><h2 data-usually-unique-id="458284009261845567835128">Conclusion</h2> <p>Bitmasks and bitwise operations are not strategies front-end developers are likely to reach for often, but having a solid foundation of computer science principles can help us to know when they are the right tool to use. Understanding the theory behind <a data-target-href="https://code.tutsplus.com/articles/number-systems-an-introduction-to-binary-hexadecimal-and-more--active-10848" href="https://code.tutsplus.com/articles/number-systems-an-introduction-to-binary-hexadecimal-and-more--active-10848" rel="noreferrer nofollow noopener" target="_blank">how numbering systems work</a> and <a data-target-href="https://www.w3schools.com/js/js_bitwise.asp" href="https://www.w3schools.com/js/js_bitwise.asp" rel="noreferrer nofollow noopener" target="_blank">how computers can compare and manipulate masks</a> can open up new opportunities in our code.</p> Tue, 09 Aug 2022 16:05:49 -0400 Andy Blum 4f25d237-655d-4a2e-bab7-28ce97b3f94b What Equity Means to Us https://www.lullabot.com/articles/what-equity-means-us <p><span><span><span><span><span><span>Lullabot converted to an </span></span></span></span></span></span><a href="https://www.nceo.org/articles/esop-employee-stock-ownership-plan"><span><span><span><span><span><span><span><span>Employee Stock Ownership Plan</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> (ESOP)  in January 2021. Since then, at an organizational level and in individual discussions and ruminations, we have been thinking more deeply about what it truly means to be colleagues as well as <em>co-owners</em>. </span></span></span></span></span></span></p> <h2><span><span><span><span><span><span>What does equity mean to us? </span></span></span></span></span></span></h2> <p><span><span><span><span><span><span>In a cooperatively-owned endeavor, what does ownership look like and feel like, and what does it mean to have a truly equitable stake in the company's future?</span></span></span></span></span></span></p> <p><span><span><span><span><span><span>Equity means different things, depending on the context.</span></span></span></span></span></span></p> <ul><li><span><span><span><span><span><span>In the diversity, equity, and inclusion space, equity (see this graphic on </span></span></span></span></span></span><a href="https://medium.com/@CRA1G/the-evolution-of-an-accidental-meme-ddc4e139e0e4#.pqiclk8pl"><span><span><span><span><span><span><span><span>equity vs. equality</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>) typically refers to the ability to have fairness and justice in how people are treated and how policies are implemented, particularly with respect to historically marginalized populations in the United States. The majority of our Bots are based in the US, and I'm writing from my perspective as someone born and raised in North America working at a US-based company.</span></span></span></span></span></span></li> </ul><ul><li><span><span><span><span><span><span>In the capital markets, equity means an actual ownership stake in the production of outcomes in the form of equities: stocks or shares, or even rents or royalties based on ownership of the property.</span></span></span></span></span></span></li> <li><span><span><span><span><span><span>In the decision-making process, equity refers to allowing wide stakeholder contributions to the discussion and fostering a longer deliberation process and an ultimate end decision that has been collaboratively agreed upon by all.</span></span></span></span></span></span></li> </ul><p><span><span><span><span><span><span>What about at Lullabot? What does equity mean to each of us? CEO </span></span></span></span></span></span><a href="https://www.lullabot.com/about/seth-brown"><span><span><span><span><span><span><span><span>Seth Brown</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> has written that "We have always believed in equity and inclusion as central tenets of our culture. Becoming 100% employee-owned through an ESOP allows us to take these values to the next level and show the team we really mean it."</span></span></span></span></span></span></p> <p><span><span><span><span><span><span>Note that each Lullabot has a true stake in the outcome (this is also the name of a book that many of our leadership team have read and recommend, called "A Stake in the Outcome: Building a Culture of Ownership for the Long-Term Success of Your Business," by Jack Stack and Bo Burlingham). Each person on staff has an opportunity to be a beneficiary of the ESOP trust and to provide guidance and direction on how our collaboration evolves. With 65 people currently on staff, we sit with much potential and possibility, and we desire, as a whole, for our people - our colleagues and co-owners –  to feel they can bring all of their skills to the work, to know they are a valued part of the ongoing process, and to contribute to making our company a better, more inclusive, equitable place for all of us - where each of us may continue to grow, learn, and thrive.</span></span></span></span></span></span></p> <p><span><span><span><span><span><span>Some basic concepts that are important to us as a company, that have been thought-through by the co-founders and senior leadership and have percolated to all aspects of our company, include:</span></span></span></span></span></span></p> <ul><li><a href="https://www.lullabot.com/belonging-at-lullabot"><span><span><span><span><span><span><span><span>Belonging</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> as a large part of our culture: fostering the sense that, like the open source Drupal DNA from which we originated, all of us are able to do our best work by bringing our full selves. </span></span></span></span></span></span></li> <li><a href="https://www.lullabot.com/articles/employee-owners-us"><span><span><span><span><span><span><span><span>Co-ownership</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> as a guiding principle: the ESOP continues Lullabot's drive to create a sense of shared responsibility and mutual respect toward building a vehicle that helps all of us get to where we want to go as colleagues and co-creators.</span></span></span></span></span></span></li> <li><span><span><span><span><span><span>As always, our </span></span></span></span></span></span><a href="https://www.lullabot.com/values"><span><span><span><span><span><span><span><span>Core Values</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> lead all of our decisions: we continually review and refine these values to become hands-on, practical embodiments of our company. Our values help us clarify what type of environment we are co-creating and help us cross-check if we are, indeed, following through with what we stated.</span></span></span></span></span></span></li> <li><a href="https://knowyourteam.com/blog/podcast/episode-49-interview-with-matt-westgate-ceo-and-co-founder-of-lullabot/"><span><span><span><span><span><span><span><span>Psychological safety</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> as the bedrock of the organization has been a practice we think has helped us navigate the very tough problems we are called upon to solve on behalf of our enterprise clients.</span></span></span></span></span></span></li> </ul><p><span><span><span><span><span><span>Everyone wants to make a positive difference in their life and work. We believe our direction as a company helps facilitate this. We get to work with like-minded, ambitious, smart, and proactive teammates on </span></span></span></span></span></span><a href="https://www.lullabot.com/our-work"><span><span><span><span><span><span><span><span>complex projects</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> that touch </span></span></span></span></span></span><span><span><span><span><span><span>millions of users.</span></span></span></span></span></span></p> <h2>What some of our people say</h2> <p><span><span><span><span><span><span>One of our newest additions, </span></span></span></span></span></span><a href="https://www.lullabot.com/about/iesha-copeland"><span><span><span><span><span><span><span><span>Iesha Copeland</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>, explains, "</span></span></span></span></span></span><span><span><span><span><span><span><span>Aside from the literal definition, equity means action. It goes beyond the theoretical concept of being equitable and intentionally displays actions that show commitment to creating space for everyone to thrive. Before joining Lullabot, I had observed DEI being mentioned at organizations haphazardly, with equity often being completely overlooked. At Lullabot, the difference is apparent. From being an ESOP to having an extremely collaborative culture, the company's dedication to seeing each employee thrive as people (which ultimately makes them all the more committed to their jobs) is exhibited every day. Equity is a part of the foundation and reverberates through the culture."</span></span></span></span></span></span></span></p> <p><a href="https://www.lullabot.com/about/flash-gooden"><span><span><span><span><span><span><span><span>Flash Gooden</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>, who joined in April 2022, mentions, "</span></span></span></span></span></span><span><span><span><span><span><span>For me, having equity in Lullabot means I'm trusted to have an impact to help create change and bring my own expertise into the company. I think that it's important that everyone's voice can be heard. I feel like Lullabot helps set that stage by having conditions and conversations that help newer Bots feel empowered. Being an employee-owner here helps me feel like I have a real responsibility to help the company succeed. I understand that the biggest asset is myself and the effort I put into this organization."</span></span></span></span></span></span></p> <p><a href="https://www.lullabot.com/about/lacie-stiewing"><span><span><span><span><span><span><span><span>Lacie Stiewing</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>, who joined the team in October 2021, shares her reflection: "</span></span></span></span></span></span><span><span><span><span><span><span>Lullabot is unlike any company I've ever worked for, and for this, I am eternally grateful. From the job post through the hiring process to the start of my employment, I was treated like a human. An individual. I was treated professionally from my very first contact. When you join Lullabot, you are immediately trusted. Our CEO schedules a meeting with every new hire, so you can get some one-on-one time to get to know each other. Remarkable. This is also not the standard practice I've experienced elsewhere.</span></span></span></span></span></span></p> <p><span><span><span><span><span><span>"Lullabot hires people you'd want to co-own a company with. Everyone on the team recognizes the value of diversity in race, ethnicity, gender, experience, age, etc., which is key to a successful business. My co-owners are brave, humble, talented, curious, kind, fun, and everything one could want in a coworker. Technically I am not vested as an employee-owner until 1-year with the company, but I have been treated as an equal/employee-owner since the very first day I started... </span></span></span></span></span></span><span><span><span><span><span><span>Coworkers give credit for the work you do and recognize you kindly and with fervor."</span></span></span></span></span></span></p> <p><a href="https://www.lullabot.com/about/andy-blum"><span><span><span><span><span><span><span><span>Andy Blum</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> mentions, "</span></span></span></span></span></span><span><span><span><span><span><span><span>My understanding of </span></span></span></span></span></span></span><span><span><span><span><span><span>equity</span></span></span></span></span></span><span><span><span><span><span><span><span> is that it's equal access to </span></span></span></span></span></span></span><span><span><span><span><span><em><span>outcomes</span></em></span></span></span></span></span><span><span><span><span><span><span><span> as opposed to equal access to </span></span></span></span></span></span></span><span><span><span><span><span><em><span>opportunity</span></em></span></span></span></span></span><span><span><span><span><span><span><span> or equal access to </span></span></span></span></span></span></span><span><span><span><span><span><em><span>help</span></em></span></span></span></span></span><span><span><span><span><span><span><span>. If social </span></span></span></span></span></span></span><span><span><span><span><span><span>equity</span></span></span></span></span></span><span><span><span><span><span><span><span> is equal access to outcomes of social matters (quality of life, happiness, treatment under the law, etc), then financial </span></span></span></span></span></span></span><span><span><span><span><span><span>equity</span></span></span></span></span></span><span><span><span><span><span><span><span> as an employee-owner at Lullabot would be equal access to the outcomes of the company. When Lullabot enjoys financial success, then the employee-owners do as well."</span></span></span></span></span></span></span></p> <p><a href="https://www.lullabot.com/about/megh-plunkett"><span><span><span><span><span><span><span><span>Megh Plunkett</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span> responds, "</span></span></span></span></span></span><span><span><span><span><span><span>For me, overall, it means that I have a stake in the company doing well, beyond Lullabot just being my employer that pays me a salary. There is more impetus to think about ways to improve and to do things that make the company thrive and continue to be successful. Ultimately I will benefit from that success based on my shares. Now that we are an ESOP, it isn't up to only the owners to decide how to spend profits, there is an ESOP board that the leadership answers to."</span></span></span></span></span></span></p> <p><span><span><span><span><span><span>An employee-owner hire from the class of 2022, </span></span></span></span></span></span><a href="https://www.lullabot.com/about/stephanie-ganzer"><span><span><span><span><span><span><span><span>Stephanie Ganzer</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>, relates, "</span></span></span></span></span></span><span><span><span><span><span><span>Equity</span></span></span></span></span></span><span><span><span><span><span><span><span> has so many layers for me. Very few companies strive to implement </span></span></span></span></span></span></span><span><span><span><span><span><span>equity</span></span></span></span></span></span><span><span><span><span><span><span><span> from a social, professional, and financial front. From day one, Lullabot trusts and empowers their bots to make decisions based on their experience and expertise and gives them the opportunity to put their personality into the role. As a company focused on the success of each of their fellow bots, I was blown away by the support and resources the team provides, no matter the ask. The community created within Lullabot is unmatched and creates healthy (safe) spaces for everyone to be themselves. Additionally, having financial </span></span></span></span></span></span></span><span><span><span><span><span><span>equity</span></span></span></span></span></span><span><span><span><span><span><span><span> in the company fuels my excitement for Lullabot's growth and success and pushes me to bring my best to the team."</span></span></span></span></span></span></span></p> <p><span><span><span><span><span><span>Upon creating the ESOP, former CEO and co-founder </span></span></span></span></span></span><a href="https://www.lullabot.com/about/matt-westgate"><span><span><span><span><span><span><span><span>Matt Westgate</span></span></span></span></span></span></span></span></a> wrote,<span><span><span><span><span><span> "Our north star has always been that the company's goals can be aligned with the team. I know of no more significant way to demonstrate that than through employee ownership. They all have earned their stake in the outcome."</span></span></span></span></span></span><br /><br /><span><span><span><span><span><span>As a group of colleagues and co-owners, we are continuing to build as equal co-owners and sharing our knowledge along the way. We invite you to learn more about ESOPs at the National Center for Employee Ownership at </span></span></span></span></span></span><a href="https://www.nceo.org/what-is-employee-ownership"><span><span><span><span><span><span><span><span>nceo.org</span></span></span></span></span></span></span></span></a><span><span><span><span><span><span>.</span></span></span></span></span></span></p> Fri, 05 Aug 2022 15:51:17 -0400 M. Nikki Flores 30d6fc16-4510-4c79-bf86-5c0e88ec7a73 Hacking Culture: Dori Kelner on Workplace Mindfulness Programs https://www.lullabot.com/podcasts/hacking-culture/dori-kelner-workplace-mindfulness-programs <p>Matthew Tift talks with Dori Kelner, a mindfulness teacher, yoga teacher, and a long-time member of the Drupal community. She founded Insightful Culture to provide evidence-based wellness practices to those who want a proven method for calming their inner critic and moving through life with greater ease. In this episode, we discuss topics such as:</p> <ul><li>Dori's transition from developer to manager to yoga teacher to workplace mindfulness facilitator</li> <li>The differences between mindfulness, meditation, and yoga</li> <li>How to tell when an organization might be ready to incorporate mindfulness practices into their culture</li> <li>Potential goals of a workplace mindfulness program</li> <li>Dori's approach to "discovery" when she's working with clients</li> <li>The difference between training leaders and training employees</li> <li>The Mindfulness Based Stress Reduction (MBSR) method</li> <li>The role of metrics in a workplace mindfulness program</li> </ul> Thu, 04 Aug 2022 07:00:00 -0400 Matthew Tift 06d2933a-2f88-4645-91fb-2bd80da21b18 Progressive Decoupling in Drupal (and Beyond) https://www.lullabot.com/webinars/progressive-decoupling-drupal-and-beyond <p>Decoupling separates the system that stores the content from how that content is displayed on other systems. The back-end and the front-end(s) are independent of one another. Decoupling comes with many benefits like clean content APIs for mobile applications, reduction in the risk of re-platforming the back-end or redesigning the front-end, and less reliance on Drupal specialists. However, there are also some big potential downsides.</p> <p>But what if you could get some of the benefits of decoupling your website while minimizing those downsides? What if you could decouple your website without revamping your entire infrastructure? And what if you could begin doing it today?</p> <p>Enter progressive decoupling, the process of decoupling your website one small piece at a time.</p> <p>In this webinar, you will learn:</p> <ul><li>The key benefits of progressive decoupling.</li> <li>Why IBM prioritizes progressive decoupling in their website strategy.</li> <li>The tools IBM has open-sourced so you can start progressive decoupling your own website.</li> </ul> Thu, 28 Jul 2022 09:51:21 -0400 2a9c5780-ad3f-4065-a905-1ef60d8deb1a Hacking Culture: Nicole Lovald on Yoga, Meditation, Therapy, and Mental Health https://www.lullabot.com/podcasts/hacking-culture/nicole-lovald-yoga-meditation-therapy-and-mental-health <p>Matthew Tift talks with Nicole Lovald about the differences between therapy and contemplative practices, such as yoga and meditation. We also discuss topics such as:</p> <ul><li>The practice of integrative therapy</li> <li>Approaches for dealing with anxiety</li> <li>Gaining control by being a witness to our own thoughts</li> <li>What science tells us about the various approaches</li> <li>The "window of tolerance"</li> <li>The importance of belly breathing</li> <li>Why someone might choose yoga, meditation, or therapy</li> </ul> Thu, 21 Jul 2022 09:00:00 -0400 Matthew Tift 584e7331-52be-417f-acf8-4ee896bd585f The Dangers of Inline Editing Structured Content https://www.lullabot.com/articles/dangers-inline-editing-structured-content <p>In our previous article, we went over the basics of <a href="https://www.lullabot.com/articles/basics-drupal-revisions-and-content-moderation">how Drupal handles revisions and content moderation</a>. But one of Drupal's strengths is what we call "structured content" and its ability to implement complex content models, as opposed to a big blob of HTML in a WYSIWYG field. Entities can have lots of different fields. Those fields can refer to other entities that also have lots of other fields. It is easy to establish content relationships. </p> <p>Even with Drupal websites that don't have complex content requirements, there are almost always a few entity reference fields that carry a lot on their shoulders. An article might reference other related articles. An album might reference an "artist" content type. A biography might reference an "address" content type.</p> <p>And to make things easier for editors, Drupal also has tools to allow inline editing of this referenced content within the context of a parent entity. But this lays potential, unexpected traps. When using content moderation and revisions with structured content, there are some dangers involved that you should be aware of.</p> <h2>Implementation approaches for structured content</h2> <p>The more structured we have our content, the more responsibility we take on to make sure we implement that structured content responsibly.</p> <p>We won't go over details about why <a href="https://www.lullabot.com/articles/understanding-create-once-publish-everywhere-cope" target="_blank">structured content is preferable</a> in modern content-rich sites and will assume you have already decided to move away from the everything-in-a-single-field as much as possible. For our purposes, “structured content” will mean a set of relationships between “components” that constitute the pieces of your content.</p> <p>In Drupal, when we want to create entity relationships, there are several implementation options, each with its pros and cons. We will focus here on two of the most popular implementation approaches: <strong>Entity</strong><strong> </strong><strong>reference</strong> fields and <strong>Paragraphs</strong>.</p> <p>Entity reference fields are probably the most common way of relating two entities together. Drupal core does that extensively. Examples of this are: when assigning Users as authors to Nodes, in file or image media fields, when using Taxonomy Terms, etc. This means that often the “components” of your content will likely be an entity of some sort, and you will probably be using entity_reference fields to put your components together.</p> <p>Another prevalent approach to creating structured content is the <a href="http://dgo.to/paragraphs" target="_blank">Paragraphs</a> contributed module. The Paragraphs module lets you pre-define your components (called “paragraphs” under the hood), and by doing so, you ensure their appearance is consistent when rendered. Content editors can choose on-the-fly which paragraph types they want to use when creating the page, and you know the components will always look the same. We will get into more details about this option later.</p> <h2>Challenges when moderating structured content and inline editing</h2> <p>Consider one of the simplest and most common content modeling scenarios: a page (node) with an entity_reference field to another node. Let’s assume the main page is a “Bio” profile page, and the component we are interested in is called “Location.”</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/body-location-fields.webp?itok=dNnSDzA0 325w, /sites/default/files/styles/max_650x650/public/2022-07/body-location-fields.webp?itok=wyhby_rP 650w, /sites/default/files/styles/max_800/public/2022-07/body-location-fields.webp?itok=VrqxvdtV 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/body-location-fields.webp?itok=fI1MjQVp 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/body-location-fields.webp?itok=msY7Zw0T 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/body-location-fields.png?itok=dNnSDzA0 325w, /sites/default/files/styles/max_650x650/public/2022-07/body-location-fields.png?itok=wyhby_rP 650w, /sites/default/files/styles/max_800/public/2022-07/body-location-fields.png?itok=VrqxvdtV 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/body-location-fields.png?itok=fI1MjQVp 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/body-location-fields.png?itok=msY7Zw0T 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/body-location-fields.png?itok=dNnSDzA0 325w, /sites/default/files/styles/max_650x650/public/2022-07/body-location-fields.png?itok=wyhby_rP 650w, /sites/default/files/styles/max_800/public/2022-07/body-location-fields.png?itok=VrqxvdtV 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/body-location-fields.png?itok=fI1MjQVp 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/body-location-fields.png?itok=msY7Zw0T 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/596;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/body-location-fields.png?itok=fI1MjQVp" alt="body and office location fields on a bio content type" loading="lazy" /></picture></figure></div> <p><strong>Note on implementation choices for your components:</strong> Using nodes as the data storage mechanism for components that don’t have a standalone version (page) is common but requires additional contributed modules, such as <a href="https://drupal.org/project/micronode" target="_blank">micronode</a>, <a href="https://drupal.org/project/rabbithole" target="_blank">rabbit ho</a><a href="https://drupal.org/project/https://www.drupal.org/project/rabbit_hole" target="_blank">le</a>, etc. Other approaches and modules that don’t use nodes are equally valid, such as using core <a href="https://www.drupal.org/docs/core-modules-and-themes/core-modules/block-module/managing-blocks" target="_blank">Content Blocks</a>, the <a href="https://www.drupal.org/project/microcontent" target="_blank">micro-content</a> module, or even custom entities that you create in code. However, for the purposes of this example, all of these approaches are equivalent since they all use an entity-reference field to relate the host entity with the target entity (component).</p> <p>By default, Drupal core doesn’t provide a great UX for inline editing. For example, the entity reference field only comes with an autocomplete widget by default, which means that when creating a Bio node, we aren’t able to finish the page unless the Location we want to use is already created.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/create-bio.webp?itok=J_0wN6J7 325w, /sites/default/files/styles/max_650x650/public/2022-07/create-bio.webp?itok=qFy4KmdW 650w, /sites/default/files/styles/max_800/public/2022-07/create-bio.webp?itok=T1OEunBE 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/create-bio.webp?itok=DxD1Y9gC 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/create-bio.webp?itok=PE5LFt8D 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/create-bio.png?itok=J_0wN6J7 325w, /sites/default/files/styles/max_650x650/public/2022-07/create-bio.png?itok=qFy4KmdW 650w, /sites/default/files/styles/max_800/public/2022-07/create-bio.png?itok=T1OEunBE 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/create-bio.png?itok=DxD1Y9gC 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/create-bio.png?itok=PE5LFt8D 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/create-bio.png?itok=J_0wN6J7 325w, /sites/default/files/styles/max_650x650/public/2022-07/create-bio.png?itok=qFy4KmdW 650w, /sites/default/files/styles/max_800/public/2022-07/create-bio.png?itok=T1OEunBE 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/create-bio.png?itok=DxD1Y9gC 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/create-bio.png?itok=PE5LFt8D 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1114;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/create-bio.png?itok=DxD1Y9gC" alt="New bio node form, with office location field highlighted, with no inline editing enabled." loading="lazy" /></picture></figure></div> <p>We can add inline editing of referenced entities through different contributed modules, and <a href="https://www.drupal.org/project/inline_entity_form" target="_blank">Inline Entity Form</a> and <a href="https://www.drupal.org/project/entity_browser" target="_blank">Entity Browser</a> are the most popular solutions. If we configure Inline Entity Form, for example, we will get a node form similar to the one below: </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide image--captioned" style="max-width: 1600px;"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-inline-entity-form.webp?itok=ExnAMCy3 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-inline-entity-form.webp?itok=32vnG-lZ 650w, /sites/default/files/styles/max_800/public/2022-07/bio-inline-entity-form.webp?itok=i7cnbgYX 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-inline-entity-form.webp?itok=uTXpZAqO 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-inline-entity-form.webp?itok=KiHPK_v0 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-inline-entity-form.png?itok=ExnAMCy3 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-inline-entity-form.png?itok=32vnG-lZ 650w, /sites/default/files/styles/max_800/public/2022-07/bio-inline-entity-form.png?itok=i7cnbgYX 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-inline-entity-form.png?itok=uTXpZAqO 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-inline-entity-form.png?itok=KiHPK_v0 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-inline-entity-form.png?itok=ExnAMCy3 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-inline-entity-form.png?itok=32vnG-lZ 650w, /sites/default/files/styles/max_800/public/2022-07/bio-inline-entity-form.png?itok=i7cnbgYX 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-inline-entity-form.png?itok=uTXpZAqO 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-inline-entity-form.png?itok=KiHPK_v0 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1438;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/bio-inline-entity-form.png?itok=uTXpZAqO" alt="Create a bio with inline entity form used for the location field" loading="lazy" /></picture><figcaption class="image__caption">Adding a new Location with Inline Entity Form.</figcaption></figure></div> <p>The whole UX could still arguably be improved, but for the sake of this example, let’s assume this is what our editors usually work with. After creating a first published version of our page, we would have something like: </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide image--captioned" style="max-width: 1600px;"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-after-saved.webp?itok=Y7v40EOt 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-after-saved.webp?itok=6OHswf8L 650w, /sites/default/files/styles/max_800/public/2022-07/bio-after-saved.webp?itok=gItScAdK 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-after-saved.webp?itok=An45UAiD 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-after-saved.webp?itok=Qzp2kKgo 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-after-saved.png?itok=Y7v40EOt 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-after-saved.png?itok=6OHswf8L 650w, /sites/default/files/styles/max_800/public/2022-07/bio-after-saved.png?itok=gItScAdK 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-after-saved.png?itok=An45UAiD 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-after-saved.png?itok=Qzp2kKgo 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-after-saved.png?itok=Y7v40EOt 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-after-saved.png?itok=6OHswf8L 650w, /sites/default/files/styles/max_800/public/2022-07/bio-after-saved.png?itok=gItScAdK 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-after-saved.png?itok=An45UAiD 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-after-saved.png?itok=Qzp2kKgo 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1254;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/bio-after-saved.png?itok=An45UAiD" alt="Editing bio with location already saved using inline entity form" loading="lazy" /></picture><figcaption class="image__caption">What it looks like when there is already an entity referenced with Inline Entity Form.</figcaption></figure></div> <p> </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-published.webp?itok=mTtqUf8_ 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-published.webp?itok=dveM3OMp 650w, /sites/default/files/styles/max_800/public/2022-07/bio-published.webp?itok=KH0GULfc 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-published.webp?itok=XmTtjmUU 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-published.webp?itok=gtfFSsK6 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-published.png?itok=mTtqUf8_ 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-published.png?itok=dveM3OMp 650w, /sites/default/files/styles/max_800/public/2022-07/bio-published.png?itok=KH0GULfc 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-published.png?itok=XmTtjmUU 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-published.png?itok=gtfFSsK6 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/bio-published.png?itok=mTtqUf8_ 325w, /sites/default/files/styles/max_650x650/public/2022-07/bio-published.png?itok=dveM3OMp 650w, /sites/default/files/styles/max_800/public/2022-07/bio-published.png?itok=KH0GULfc 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/bio-published.png?itok=XmTtjmUU 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/bio-published.png?itok=gtfFSsK6 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1250;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/bio-published.png?itok=XmTtjmUU" alt="A bio page displaying an office location referenced with an entity reference field." loading="lazy" /></picture></figure></div> <p> Sometime after this page is published, the editor needs to perform a few modifications, which will require review and approval before going live. Content moderation to the rescue! They can just create a new draft, right?</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/new-bio-draft.webp?itok=uwkvXhh6 325w, /sites/default/files/styles/max_650x650/public/2022-07/new-bio-draft.webp?itok=LWPc5diK 650w, /sites/default/files/styles/max_800/public/2022-07/new-bio-draft.webp?itok=g3-mXuUd 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/new-bio-draft.webp?itok=4iHvEu7V 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/new-bio-draft.webp?itok=47rfbyWL 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/new-bio-draft.png?itok=uwkvXhh6 325w, /sites/default/files/styles/max_650x650/public/2022-07/new-bio-draft.png?itok=LWPc5diK 650w, /sites/default/files/styles/max_800/public/2022-07/new-bio-draft.png?itok=g3-mXuUd 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/new-bio-draft.png?itok=4iHvEu7V 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/new-bio-draft.png?itok=47rfbyWL 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/new-bio-draft.png?itok=uwkvXhh6 325w, /sites/default/files/styles/max_650x650/public/2022-07/new-bio-draft.png?itok=LWPc5diK 650w, /sites/default/files/styles/max_800/public/2022-07/new-bio-draft.png?itok=g3-mXuUd 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/new-bio-draft.png?itok=4iHvEu7V 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/new-bio-draft.png?itok=47rfbyWL 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1329;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/new-bio-draft.png?itok=4iHvEu7V" alt="Editing a bio and creating a new draft with some added text. We also need to tweak the address in the new version, so we are going to go ahead and click &quot;Edit&quot; on the entity reference field to edit the office location." loading="lazy" /></picture></figure></div> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/bio-draft-location.png?itok=VeL8fhSs" width="900" height="1233" alt="Editing the location of a bio page. Since we're changing the Draft version, we can safely click &quot;Update node&quot; and then &quot;save,&quot; right?" /></figure></div> <p> If we don’t pay close attention, everything seems to have worked as expected, since when we save the form, we see the <code>/node/123/latest</code> version of that page, which is a forward (unpublished) revision, and this indeed contains all changes we expect to get approved before they go live:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/latest-version-bio.webp?itok=h7xkR_y0 325w, /sites/default/files/styles/max_650x650/public/2022-07/latest-version-bio.webp?itok=WG4EH8Cd 650w, /sites/default/files/styles/max_800/public/2022-07/latest-version-bio.webp?itok=c6JK1rTi 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/latest-version-bio.webp?itok=T17s9SVd 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/latest-version-bio.webp?itok=KbZvVw3X 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/latest-version-bio.png?itok=h7xkR_y0 325w, /sites/default/files/styles/max_650x650/public/2022-07/latest-version-bio.png?itok=WG4EH8Cd 650w, /sites/default/files/styles/max_800/public/2022-07/latest-version-bio.png?itok=c6JK1rTi 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/latest-version-bio.png?itok=T17s9SVd 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/latest-version-bio.png?itok=KbZvVw3X 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/latest-version-bio.png?itok=h7xkR_y0 325w, /sites/default/files/styles/max_650x650/public/2022-07/latest-version-bio.png?itok=WG4EH8Cd 650w, /sites/default/files/styles/max_800/public/2022-07/latest-version-bio.png?itok=c6JK1rTi 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/latest-version-bio.png?itok=T17s9SVd 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/latest-version-bio.png?itok=KbZvVw3X 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1453;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/latest-version-bio.png?itok=T17s9SVd" alt="Latest version of bio draft before it has been published, showing changed text correctly." loading="lazy" /></picture></figure></div> <p>However, if we log out and visit the currently-published version of this page, we see that the new office location for the bio is already live. That's not what we wanted. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--wide"><picture><!--[if IE 9]><video style="display: none;"><![endif]--><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/live-office-location.webp?itok=l5F7pk1G 325w, /sites/default/files/styles/max_650x650/public/2022-07/live-office-location.webp?itok=y-Cik7C6 650w, /sites/default/files/styles/max_800/public/2022-07/live-office-location.webp?itok=WcPRt2W8 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/live-office-location.webp?itok=Rnvy4yK0 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/live-office-location.webp?itok=K73l4SkZ 1600w" type="image/webp" sizes="(min-width: 1290px) 1290px, 100vw"></source><source srcset="/sites/default/files/styles/max_325x325/public/2022-07/live-office-location.png?itok=l5F7pk1G 325w, /sites/default/files/styles/max_650x650/public/2022-07/live-office-location.png?itok=y-Cik7C6 650w, /sites/default/files/styles/max_800/public/2022-07/live-office-location.png?itok=WcPRt2W8 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/live-office-location.png?itok=Rnvy4yK0 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/live-office-location.png?itok=K73l4SkZ 1600w" type="image/png" sizes="(min-width: 1290px) 1290px, 100vw"></source><!--[if IE 9]></video><![endif]--><img srcset="/sites/default/files/styles/max_325x325/public/2022-07/live-office-location.png?itok=l5F7pk1G 325w, /sites/default/files/styles/max_650x650/public/2022-07/live-office-location.png?itok=y-Cik7C6 650w, /sites/default/files/styles/max_800/public/2022-07/live-office-location.png?itok=WcPRt2W8 800w, /sites/default/files/styles/max_1300x1300/public/2022-07/live-office-location.png?itok=Rnvy4yK0 1300w, /sites/default/files/styles/max_2600x2600/public/2022-07/live-office-location.png?itok=K73l4SkZ 1600w" sizes="(min-width: 1290px) 1290px, 100vw" style="aspect-ratio:1600/1203;display:block;width:100%;" onload="this.removeAttribute('style');" src="/sites/default/files/styles/max_1300x1300/public/2022-07/live-office-location.png?itok=Rnvy4yK0" alt="The office location update is already published in the live version of the bio." loading="lazy" /></picture></figure></div> <p>We edited a draft. So how did the changes leak to the live version?</p> <p>Well, it turns out that is indeed the expected behavior. Here is what happened.</p> <p>When the editor clicks on the “Edit” button, they are opening the edit form of that referenced node entity. Once <code>entity_reference</code> fields only store information about the entity type and entity ID, the action being performed really is “<em>let’s</em><em> modify the default revision of this referenced entity, and save that as a new default revision</em>.” This is the same as if they went to <code>/node/45/edit</code> and edited the location node there. Editing the referenced entity like this is <strong>almost never</strong> what you want to do in the context of using an inline form because it will:</p> <ul><li>Affect this (re-usable) component everywhere it may be being used</li> <li>Even if it’s not being used elsewhere, in this scenario, it will change the location entity default revision, so the published content referencing it will reflect the changes immediately.</li> </ul><h2>How to mitigate or reduce the risk of this happening on your site</h2> <p>There is no one-size-fits-all solution for these dangers, but you can minimize the risk.</p> <h3>Train your editors</h3> <p>If your editors understand how revisions and moderation workflows work, they can more easily work around CMS limitations when necessary. For example, in this case, it might be enough just to remove the reference to that particular Location component and create a new node instead. When the main page draft is published, it will display the new node instead of the old one. Admittedly, this is not always possible or desirable in all scenarios and teams.</p> <h3><strong>Avoid inline editing when moderation workflows are in place</strong></h3> <p>If editors have to go to the standalone form to modify the referenced content, this might make it more visible that the changes could affect more instances than desired.</p> <h3><strong>Use helper contributed modules to reduce confusion</strong></h3> <p>There are modules created to help editors know better the repercussions of the editorial changes. For example, the <a href="https://drupal.org/project/entity_usage" target="_blank">Entity Usage</a> module can be configured to display a warning message in the edit form when we are altering content referenced from other places. Additionally, the <a href="https://drupal.org/project/entity_reference_preview" target="_blank">Entity Reference Preview</a> module helps editors preview unpublished content that references items that also have forward revisions.</p> <h3><strong>Architect your implementation to account for the scenarios your editors will find</strong></h3> <p>Maybe none of the mitigation ideas mentioned above are enough for you, or you need a more robust way to guarantee your inline edits in moderated content will be safe regardless of the editor’s skills. In this case, you might want to consider stopping the use of entity_reference fields to point to entities as components and start using the <a href="https://drupal.org/project/paragraphs" target="_blank">Paragraphs</a> module instead.</p> <h2>What is different with Paragraphs?</h2> <p>The Paragraphs module still creates entities to store your components' data, but the difference is that it enforces that a given revision of the component (paragraph) is always tied to a specific revision of the host entity (parent). In fact, this is often referred to by developers as a “composite entity,” meaning the paragraph itself is only ever expected to exist “in the context of its parent.”</p> <p>This solves our problem of inline editing moderated content nicely since when we create a new draft of the parent content, we will also be generating new draft revisions of all components on the page, which will always travel together through the moderation workflow.</p> <p>This also has some downsides you should consider when choosing your implementation model. For example, in a paragraphs scenario, your components can’t be re-used directly. You will need to create one-off versions of a given component every time you need to place it on a page. Also, depending on your content model, if you have deep nesting levels of components inside components, the UX for inline editing might be tricky. On sites with a high volume of data, this could lead to large database tables since all revisions of all components will be created independently.</p> <h2>Conclusion</h2> <p>If you have to take one thing from this read, it should be <strong>“be</strong><strong> careful with inline editing of referenced content in entity_reference fields when using content moderation.”</strong> This is a high-risk scenario that you should discuss early in the project with all stakeholders and plan accordingly. Unfortunately, there is no one-size-fits-all solution, and you should create your Drupal architecture to best serve the use cases that matter for your site users.</p> <p>Page-building strategy is a complex subject that we haven’t explored in depth here either. Layout Builder options, embedding content in WYSIWYG areas, re-usability of components, media handling, translation workflows, theming repercussions, decoupled scenarios, etc., are all topics you should have in mind when deciding on a page-building approach. Understanding how revisions, entity_reference fields, and content moderation all play together is a good start.</p> <p>Lullabot has <a href="https://www.lullabot.com/articles/why-we-advocate-paper-prototyping-digital-content">helped plan</a> and <a href="https://www.lullabot.com/webinars/georgiagov-case-study-structured-content-and-flexible-layouts">build editorial workflows</a> for organizations of all shapes and sizes so that Drupal helps them work <em>toward</em> their content goals. If you want proven help navigating these issues, <a href="https://www.lullabot.com/contact">contact us</a>.</p> Mon, 18 Jul 2022 10:22:11 -0400 Marcos Cano 493cd912-821a-4216-b766-96ca99c47eb9 The Basics of Drupal Revisions and Content Moderation https://www.lullabot.com/articles/basics-drupal-revisions-and-content-moderation <p>Out of the box, Drupal offers some great tools for content moderation and basic editorial workflows. It is simple and flexible by design because there is no one-size-fits-all solution for every organization and editorial team. Drupal prefers to provide the tools. Your team can figure out how best to use those tools.</p> <p>Publishing workflows and strategies are complex subjects, but we'll go over the fundamental concepts of how Drupal handles these concepts.</p> <h2>The basic concepts</h2> <p>Let's start by refreshing some fundamental concepts that are the basis of Drupal's entity storage and what its data models are based on.</p> <h3>Entities</h3> <p>Most content in Drupal is stored under the hood in buckets we call "Entities," and these entities have fields (or properties.) "User" is an entity, and "User name" is a field of that entity. Nodes are a special type of entity, expected to model most of the site's standalone pages in Drupal.</p> <h3>Revisions</h3> <p>Most content stored in entity fields is revisionable. This means that these entities will have an <strong>ID</strong> and a <strong>Revision ID</strong>. This is what allows you to create different <strong>versions</strong> of the same content.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/enable-revisions.png?itok=jwEsneR5" width="900" height="483" alt="Setting a content type to create a new revision after every change." /></figure></div> <p>Currently, Drupal has the "<strong>Create</strong><strong> new revision</strong>" setting marked by default on Content Types (nodes), so if you don't configure anything else explicitly, then your site will generate a new revision each time the editor saves the node form. Because of that, after a couple of edits, a new "<strong>Revisions</strong>" tab will be available to users with the appropriate permissions, allowing them to check the history of revisions for that particular piece of content.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/revisions-tab.png?itok=OHc2tU1R" width="900" height="734" alt="An article with the Revisions tab highlighted" /></figure></div> <p> </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/revisions-page.png?itok=124lx8ET" width="900" height="408" alt="The revisions page showing 3 revisions, one of them the labeled as the current revision" /></figure></div> <p>Note: if you install the <a href="http://dgo.to/diff" target="_blank">Diff</a> module, this page will also allow you to compare two revisions, checking what the actual content differences are. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/revisions-diff.png?itok=eSno0Muo" width="900" height="484" alt="Revisions page with the Diff module enabled" /></figure></div> <p><strong>Bonus tip:</strong> <em>Use the </em><a href="http://dgo.to/revision_log_default" target="_blank"><em>Revision Log Default</em></a><em> module to ensure that revisions always have a log message</em><em>.</em> </p> <p>The "<strong>Current</strong><strong> revision</strong>" (or, more technically, "<strong>Default</strong><strong> revision</strong>") of a particular piece of content is the concept that Drupal uses to define "<em>what</em><em> version</em><em> </em><em>(revision)</em><em> of this content should I load when a specific revision is not specified</em>." This is the revision you get, for example, when:</p> <ul><li>a user visits <code>/node/123 </code> </li> <li>an editor opens the node edit form (in a standard Drupal setup)</li> <li>a field references a related node only by the node ID (more on this later),</li> <li>you load an entity in custom code with something like:</li> </ul><p><code>$node = \Drupal::entityTypeManager-&gt;getStorage('node')-&gt;load(123);</code></p> <p>The reference to which revision is the "default" one is stored in the column <code>vid</code> of the base entity table (for nodes, this means the <code>node</code> table).</p> <p>The "<strong>Revert</strong>" action (also referred to as "<strong>Roll-back</strong>") will generate a new revision and set it to be the new default revision. The contents of this new revision will be copied from the past revision you selected to revert to. Note that <em>without Content Moderation, the default revision will always be the latest revision created</em>.</p> <h3>Publishing status</h3> <p>All nodes (and most content entities) in Drupal have a <code>status</code> property that defines whether that particular piece of content should be "publicly visible" or not. Out of the box, Drupal content can have a status of "published" or "unpublished." You could modify this behavior by allowing different roles access to unpublished content, but that's the general idea.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/published-box.png?itok=WbEsRa53" width="900" height="631" alt="The published status box is checked" /></figure></div> <p>In a standard Drupal setup (i.e., without Content Moderation), once what is loaded in the edit form is the default revision, <strong>publishing</strong> or <strong>unpublishing</strong> a node will always affect the default (and also the latest) revision of that content.</p> <h2>Increase the complexity with Content Moderation</h2> <p>The functionalities described above allow some basic control over the publishing status of the content but fall short when sites need to implement more advanced editorial workflows. For example, if you need to have your content go through several unpublished (but different) states before it gets published, or when you have a published page, and you need to get modifications on it approved by stakeholders before making them public.</p> <p>The <strong>Content Moderation</strong> module from Drupal core brings some significant changes to the basic functionality we saw above, the most important ones being <strong>workflow states</strong> and <strong>forward revisions</strong>.</p> <h3>Workflow states</h3> <p>If you have experimented with the Content Moderation module, you have already discovered that it depends on the <strong>Wor</strong><strong>k</strong><strong>flows</strong> module, where the concepts of "<em>Workflow</em><em> states</em>" and "<em>Transitions</em>" are defined. With these modules, you can define arbitrary paths that describe the life cycle of the content on your site.</p> <p>For example, by default, the Content Moderation module creates the "<strong>Editorial</strong>" workflow with three states: draft, archived, and published. The workflow defines which states can flow to other states.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/workflow.png?itok=Ta0VgCuL" width="900" height="373" alt="The default workflow using the states of draft, archives, and published provided by Content Moderation" /></figure></div> <p>As you can imagine, you can modify both the states and transitions of this workflow to suit your specific needs. You can also create additional workflows with different states that could apply to a subset of your content while using the "Editorial" workflow for others. For example, your <em>Press Release</em> content type might need to go through an extra round of approval that other content types don't need. </p> <h3>Forward revisions</h3> <p>A key aspect of Content Moderation is the ability to have <strong>future</strong> (or <strong>forward</strong>) revisions. This means that the default revision is not necessarily the most recent one anymore. Additionally, you can associate the <strong>publishing status</strong> of the content with one or more <strong>workflow states</strong>.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/adding-state.png?itok=6h3RvM4d" width="900" height="456" alt="Adding a new state called &quot;in review&quot;" /></figure></div> <p><em>When using Content Moderation, Drupal will show on your site the most recent revision that is in one of the</em><em> </em><em>"published"</em><em> states.</em></p> <p>You could configure some states to trigger the default revision without being a published state, but that is a non-standard configuration that should be avoided in most scenarios.</p> <p>So after enabling Content Moderation, defining your workflow states/transitions, and assigning this workflow to your content entity (check the <a href="https://www.drupal.org/docs/8/core/modules/content-moderation/overview" target="_blank">documentation</a> for more info on how to do this), your editors now have different features when creating content:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/new-states.png?itok=YCOvwjMa" width="900" height="739" alt="New states dropdown box that replaces the single &quot;published&quot; checkbox" /></figure></div> <p>The "Revisions" tab also shows an important difference. New drafts are shown in the list above the published "default revision."</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/forward-revisions.png?itok=oDMb4OWG" width="900" height="619" alt="Revisions page showing draft &quot;forward&quot; revisions" /></figure></div> <p>Users with the appropriate permissions will be able to see a "<strong>Latest</strong><strong> version</strong>" tab on the node pages (also available under the URL: <code>/node/123/latest</code>).</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-07/lastest-version-tab.png?itok=bE_tLmZC" width="900" height="662" alt="Showing the new &quot;latest version&quot; tab" /></figure></div> <p>This is the content that will be loaded when users open the node edit form and is ultimately the content being "moderated," as opposed to the "published" content, which visitors would typically see when visiting <code>/node/123</code>. In most scenarios, content with no unpublished revisions pending approval (i.e., no forward revisions) has the default revision as its "Latest version." In those cases, this tab is not shown. </p> <p><strong>Bonus Tip:</strong><em> You can use the modules </em><a href="http://dgo.to/moderation_sidebar" target="_blank"><em>Moderation Sidebar</em></a><em> and </em><a href="http://dgo.to/moderation_note" target="_blank"><em>Moderation Note </em></a><em>to improve the user experience of your editors while transitioning content between states in the workflow.</em></p> <h2>Conclusion</h2> <p>Those are the basics of revisions and content moderation. Of course, figuring out how to use these tools for your own needs is an entire topic in itself. <a href="https://www.lullabot.com/articles/why-we-advocate-paper-prototyping-digital-content">The possibilities are almost endless</a>. </p> <p>Because Drupal is so good at complex, <a href="https://www.lullabot.com/articles/understanding-create-once-publish-everywhere-cope">structured content</a>, one of the traps you can fall into is making assumptions about how revisions work for reusable components and content that refers to other content. Most Drupal sites make use of entity reference fields for this purpose. But there are dangers in making assumptions. In the next article, we'll discuss some of those dangers, particularly when inline editing embedded content is involved.</p> Wed, 13 Jul 2022 17:28:57 -0400 Marcos Cano 592fee0a-8a29-4200-9bf5-969c955f97d9 Hacking Culture: Angie Byron on Cultivating Well-Being in Tech Communities https://www.lullabot.com/podcasts/hacking-culture/angie-byron-cultivating-well-being-tech-communities <p>In this episode Matthew Tift talks with Angie Byron, the Principal Community Manager at MongoDB and a Drupal Core Committer. They discuss a wide range of issues related to cultivating well-being in open-source communities, such as:</p> <ul><li>Angie's early experiences in the Drupal project making cultural norms explicit, creating codes of conduct, and establishing procedures for conflict resolution</li> <li>How Drupal has positively impacted the lives of its members</li> <li>Contributing to something bigger than yourself</li> <li>Setting up structures to prevent burnout within communities</li> <li>Challenges in monitoring community health</li> <li>How her participation in the Drupal community affected her personal life, both positively and negatively</li> <li>How community wellness checks might benefit an open-source community</li> <li>How Drupal agencies support the overall well-being of the Drupal community</li> <li>Angie's transition to working in the MongoDB community</li> <li>The joy she gets helping individuals find their place in a community</li> </ul><p>Since 2006, Angie has done incredible work helping to grow the Drupal community. On the subject of what all of us can do to help cultivate well-being, her advice is "Look out for your people."</p> <p>The theme music used in this episode comes from the Open Goldberg Variations.</p> Thu, 07 Jul 2022 05:00:00 -0400 Matthew Tift 12117fe2-25e8-4fe3-8949-973a5a425ea9 Making the Most of Display Modes In Drupal https://www.lullabot.com/articles/making-most-display-modes-drupal <p>One of Drupal's biggest strengths is the ability to define structured content, then display and edit it in a variety of ways without duplicating development or design work. "Display Modes" have always been a big part of that power. One piece of content can be displayed as a Teaser, as a Full Page, as a Search Result, and so on — each with its own markup and field presentation.</p> <p>Customizing the list of Display modes has always been possible in Drupal, but since Drupal 8, it's become easier. And the same flexibility has been extended to the edit forms that let site users create and modify content. A "Simple" Form Mode, for example, might hide complex optional fields to streamline the editing experience for new users or less privileged user roles.</p> <h2>Adding custom display modes</h2> <p>To create a new display mode (or customize one of the "stock" ones that come pre-installed), log in to your site as an administrator and navigate to the admin/structure/display-modes page. There, you can select either <em>View modes</em> or <em>Form modes</em>. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1600px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/displaymodes.png?itok=JP94KRhm" width="900" height="282" alt="Menu showing links for Form modes and View modes" /><figcaption class="image__caption">/admin/structure/display-modes</figcaption></figure></div> <p>Every entity type on the site (content nodes, user accounts, taxonomy terms, etc.) can have its own list of custom display modes. Once you've created one, go to the Manage Display page for any content type. For new Drupal sites, /admin/structure/types/manage/[bundle-name]/display is usually a good bet.</p> <p>We have a music demo site with the following content types: <em>album</em>, <em>artist</em>, and <em>track</em>. When viewing an <em>album</em> page, we only want the image and name of the <em>artist</em> to show up. However, when an <em>album</em> is viewed in a listing like search results, we only want the <strong>name</strong> of the <em>artist</em> to show up. To accomplish this, we are going to add a view mode named <em>Artist Image Card</em>. To start with, we visit /admin/structure/display-modes/view and create a <strong>content</strong> view mode giving it our chosen name.</p> <p>Now we need to enable the new <em>Artist Image Card </em>display mode on the artist content type by going to /admin/structure/types/manage/artist/display and toggling Custom Display Settings.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/17oohkia.png?itok=KMDhKzwQ" width="900" height="481" alt="View mode options to enable for custom settings." /></figure></div> <p>This will activate the display mode for custom treatment. After checking the <em>Artist Image Card</em> view mode and saving, we can see it as an option in the list at the top of the page. After selecting the <em>Artist Image Card,</em> you can reorder, hide, and change the format of each field for that display mode. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1600px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/brij-arc.png?itok=mmLJfiqn" width="900" height="381" alt="Display settings for the Artist Image Card view mode" /><figcaption class="image__caption">Display settings for the Artist Image Card view mode</figcaption></figure></div> <p> </p> <p>While we are focused on display modes for content types, you can also do the same thing for taxonomy terms, media, users, and comments.</p> <h2>Using your new modes</h2> <p>There are two easy ways to make use of your new display modes. Whenever a piece of content is included in an Entity Reference field, you can choose to display it as a link or as a fully-rendered entity in the display mode of your choice. Creating a unique display mode for these "embedded" views makes it easier to control how content appears when it's included inside another entity without clobbering its normal display. </p> <p>For the music demo site, an <em>album</em> references an <em>artist</em> using an Entity Reference field, so let's ensure it renders the <em>artist</em> with our new <em>Artist Image Card</em> view mode. First, we disable all fields except the Image field in the <em>Artist Image Card</em> view mode on the <em>artist</em> content type. We also set the image style and link the image back to the artist page that owns the image.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 3230px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/3xgnkmzq.png?itok=X0f0WIBD" width="900" height="365" alt="Display settings with all fields hidden except Image" /><figcaption class="image__caption">All fields hidden except for the Image field</figcaption></figure></div> <p>Next, we go to the <em>album</em> Manage Display settings. We have enabled the <em>Full content</em> mode to have custom settings because we want to be explicit about what we are changing and for what context. We set the Artist field format to "Rendered entity" and then use the <em>Artist Image Card</em> view mode, as shown below.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 3236px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/screen-shot-2022-06-16-at-5.26.56-pm.png?itok=yLR3dg7_" width="900" height="382" alt="The Artist field being rendered as an Artist Image Card" /><figcaption class="image__caption">Setting the Artist field to render as the Artist Image Card display mode</figcaption></figure></div> <p>Now, whenever we view an album's page, it will render its referenced <em>artist</em> entity using the <em>Artist Image Card</em> view mode.</p> <p>If you want more control over the rendered content, Drupal creates template suggestions for nodes based on display mode names. If we need to add special CSS classes or additional formatting to our <em>Artist Image Card</em>, we can create a template called <code>node--artist-image-card.html.twig</code> or, to get more granular, <code>node--artist--artist-image-card.html.twig</code>. Create the template in your theme, rebuild the theme registry by clearing the cache, and Drupal will pull from this template whenever it renders that view mode. It helps to copy over the code from <code>node.html.twig </code>to have a starting block.</p> <p>Drupal also creates these template suggestions for media items. If you want template suggestions for other entities, like taxonomy terms, you can implement <em>hook_theme_suggestions_HOOK() </em>in your themes or modules. Use <a href="https://api.drupal.org/api/drupal/core%21modules%21node%21node.module/function/node_theme_suggestions_node/9.0.x" target="_blank">node_theme_suggestions_node()</a> as an example to start with.</p> <p>The same approach can be used when building Views. Any display mode can be used when spitting out the contents of a View. This makes it easy to use, for example, a unique <em>Slideshow Card</em> display mode when building a rotating gallery view. Here's an example of a View using our new view mode for artists. This way, when the output of a View needs to change, you don't have to modify the View, only the view mode.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/screen-shot-2022-06-16-at-5.50.33-pm.png?itok=CwAEVz4i" width="900" height="363" alt="View settings using the Artist Image Card to display content" /></figure></div> <p>You probably wouldn't want to use the <em>Artist Image Card</em> view mode in a listing View, however, so this would be an excellent opportunity to create another view mode for this purpose, named appropriately.</p> <p>The <a href="https://www.drupal.org/project/view_mode_page" target="_blank">View Mode Page</a> module for Drupal gives some extra flexibility by adding URLs, like node/1/details, that show an entity's custom view modes. You're still responsible for adding links to those new URLs in your design, but it makes sure that they respect any Pathauto patterns and SEO-friendly URLs you've created. This is useful if you have one field with a lot of content that might deserve its own page.</p> <h2>But what about forms?</h2> <p>Although defining custom form display modes follows much the same pattern (see any content type's Manage Form Display page at /admin/structure/types/manage/[bundle-name]/form-display), actually using custom form displays requires some contrib modules.</p> <p>Using modules like <a href="https://www.drupal.org/project/inline_entity_form" target="_blank">Inline Entity Form</a> or <a href="https://www.drupal.org/project/entity_browser" target="_blank">Entity Browser</a> allows you to configure an embedded entity form to use a certain form mode. In certain contexts, you can show a more limited number of fields. This is the most straightforward use of form modes.</p> <p>In the form configuration for the <em>Album</em> content type, the Tracks field is set up to use Inline Entity Form, and you can see where the form mode has been set to use <em>Simple</em>.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/screen-shot-2022-06-27-at-5.16.12-pm.png?itok=1eWY8Atc" width="900" height="355" alt="Inline entity form settings using a form mode" /></figure></div> <p> And in an Entity Browser set up for adding new artists, you can see the form mode used is <em>Default</em>. This allows Drupal sites to simplify content creation forms depending on the context and workflow.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/screen-shot-2022-06-27-at-5.17.47-pm.png?itok=Jh0QXw_M" width="900" height="172" alt="Add New settings for Entity Browser, using a form mode" /></figure></div> <p>There are some other contrib modules you can look at, and <a href="https://www.drupal.org/project/form_mode_manager" target="_blank">Form Mode Manager</a> is the simplest: it lets you set up a custom editing page for each distinct mode and adds a tab to switch between modes on the content editing form itself. If you're using form display modes as optional alternatives to the standard edit form, it's your best bet. <a href="https://www.youtube.com/watch?v=7xFmSKNkRbo" target="_blank">View a demo of this module</a>.</p> <p><a href="https://www.drupal.org/project/form_mode_control" target="_blank">Form Mode Control</a> takes a slightly different approach. It lets you assign custom form displays to individual user roles, giving editors more flexibility without complicated access control tools. As with Form Mode Manager, modes can also be requested manually by adding a querystring to the node editing URL, like: mysite.com/node/add?display=simple.</p> <p>There are, of course, <a href="https://www.droptica.com/blog/custom-entity-forms-example-user-editing-and-registration/" target="_blank">ways to use your custom form modes in custom code</a>.</p> <h2>Integrating display modes into the design process</h2> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/unnamed-2.jpeg?itok=RK40og62" width="900" height="675" alt="Paper with post it notes showing a break down of how Content types map to Display Modes and Pages" /></figure></div> <p>Display modes provide a great opportunity to tie code to design documents and entrench a <a href="https://www.lullabot.com/articles/naming-content-types-using-ubiquitous-language" target="_blank">ubiquitous language</a>. If your content or marketing team calls a particular look and arrangement of data an <em>Article Image Card</em>, for example, guess what you should name your display mode? Yes, you should probably name it <em>Article Image Card</em>. Content strategists and architects will pick up on this usage during the discovery phase and use it in the site planning. Using a shared vocabulary from the beginning of the project helps project managers, designers, and developers to learn the same language as stakeholders, which helps improve communication.</p> <p>This becomes more useful if you have a <a href="https://www.lullabot.com/articles/component-libraries-drupal" target="_blank">component library</a>, and groups of components can be mapped to a display mode. Everyone has a better chance of knowing what everyone else is talking about. This has a greater chance of success when the front-end development team works closely with the design team to establish these connections. Our own collaborative teams of designers and developers often work in tandem to name components, establish where and how they are used and go so far as to annotate designs with information about component names, reuse, and any other display details. This process strengthens the overall design system and ensures consistency.</p> <p>Let's say we have several mockups of a <em>Case Study</em> content type. This content type needs to be displayed in different contexts, and a designer presents a work in progress, as shown below. Keep in mind that this is a simplified example.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/3ldzrqoh.png?itok=ML-Ig6Kg" width="900" height="900" alt="Case Study designs for full content, hero with quote, and promo card" /></figure></div> <p>In this example, it would be a good idea to enable the <em>Full content </em>view mode for the Case Study content type and use that for the main content. We would then want to create two other view modes for the content type: <em>Hero with quote</em> and <em>Promo card.</em></p> <p>We would then enable those view modes with Custom Display Settings and hide all fields that are now shown. We'll probably create some custom templates for each of these as well.</p> <p>Now, when the design team says something like, "We made some changes to the hero with quote on our case studies," developers will know exactly what is being talked about, and they will know where they need to go to implement the changes. Designers can also say things like, "At the bottom of the case study, we need three promo cards displayed," and it should make sense to the entire development team. Likewise, a developer can come to the design team and say, "We're having some problems implementing the image on the promo card design for case studies." Designers will immediately be on the same wavelength. </p> <p>Imagine the possible confusion (and time wasted) if the view mode had been named something like <em>Short Blurb</em>. Or worse, if it had been named something like <em>Display Mode Three.</em></p> <h2>Navigate the risk of having a gazillion display modes</h2> <p>One risk of using display modes in the way we have been describing: having the number of them balloon out of control, making site maintenance and design implementation more difficult. We have seen some installs where a single content type has four different types of <em>Promo card</em> modes because each has a slightly different field configuration. While that can be justified, know that each one adds more complexity. This is another reason to collaborate closely through the entire development process.</p> <p>Strategists, designers, and developers working together can help identify the necessary number of variations based on requirements. The group can come to a consensus, for example, that it is sufficient to use a single <em>Promo card</em> mode for both the homepage and the "Related content" section at the bottom of an article. You didn't need two different styles, after all. In some cases, maybe you do need those extras. What's important is collaborating and talking things through to land on the best solution.</p> <p>Finding the correct number of display modes for your system helps provide enough visual variation and content interest while simultaneously keeping the structural and technical implementation of the site manageable and sustainable for site admins and developers who may need to make adjustments or changes in the future.</p> <h2>Conclusion</h2> <p>Like many other parts of Drupal, display modes offer a lot of flexibility, which means they require a little more discipline and planning to use them to their full potential. We think taking the time to do this can be worth it. Using them properly can provide benefits throughout your entire development stack. They could even help streamline communication between stakeholders, content editors, and the development team.</p> <p>That's a lot of potential value for what seems like simple functionality.</p> Wed, 29 Jun 2022 10:03:03 -0400 Matt Robison 18f997a9-e835-452b-b4a3-4fe3aa7690a8 Hacking Culture: Stephanie Wagner on Cultivating Healthy Minds at Work https://www.lullabot.com/podcasts/hacking-culture/stephanie-wagner-cultivating-healthy-minds-work <p>Matthew Tift talks with Stephanie Wagner about <a href="https://hminnovations.org/">Healthy Minds Innovations</a>, their Health Minds @Work program, and "hacking the mind." They talk about tools that cultivate and measure well-being. They discuss various critiques of workplace wellness programs and how Healthy Minds might be able to support your company or organization.</p> <p> </p> Thu, 23 Jun 2022 10:20:06 -0400 Matthew Tift 904563b1-209b-4e18-9040-301d99e1f5ed Paper Prototyping for Websites and Digital Products https://www.lullabot.com/articles/why-we-advocate-paper-prototyping-digital-content <p>Have you ever spent a lot of time on something for your project, only to throw it away? Often someone else interacts with your work and uncovers something important. Perhaps the issue came from a stakeholder, real users, or the implementation team, and their input is important. But now, you have to go back to the drawing board. We can relate.</p> <p>Lullabots advocate for ways to get ideas shared faster, to learn, and ultimately get projects on track more quickly. In the past, we've written about <a href="https://www.lullabot.com/articles/squiggles-straight-lines-sketching-during-strategy-and-ux-work" target="_blank">sketching</a>, another low-fidelity paper process.<em> </em>Now, we're excited to share another way. </p> <p>Enter paper prototyping. </p> <h2>What is paper prototyping?</h2> <p>In its simplest form, paper prototyping consists of making <em>something</em> out of paper to explore and validate an idea. Carolyn Snyder literally wrote the <a href="https://www.amazon.com/exec/obidos/ASIN/1558608702" target="_blank">book on paper prototyping</a>, so here's her definition:</p> <blockquote> <p>Paper prototyping is a variation of usability testing where representative users perform realistic tasks, by interacting with a paper version of the interface that is manipulated by a person “playing computer."</p> </blockquote> <p>This is a low-cost, low-time, portable, shareable artifact. An artifact is "an object made by a human being with cultural or historical interest." A design artifact is any object specifically of historical interest as a part of a project process; it's useful documentation but does not necessarily serve as a client deliverable.</p> <p>Think of a paper prototype as a communication aid that suits different types of conversations and testing. Designers and strategists can quickly sketch and cut out visuals to walk others through their user experience (UX) ideas. And paper is a technology that will never become outdated (the book mentioned above is from 2003 and is still relevant).</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1620px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/laminated-wireframes.jpeg?itok=rCKclmW5" width="900" height="1200" alt="Paper wireframe components arranged on a laminated background" /><figcaption class="image__caption">The method pictured includes a portable whiteboard as a frame to annotate pre-made flashcards. (Be careful combining permanent and dry-erase markers!)</figcaption></figure></div> <h2>Why it's worth it</h2> <p>We paper-prototype to:</p> <ul><li>Quickly document many ideas or a single but complex idea visually while brainstorming</li> <li>Create a way to flow through a series of states in an experience to share with others </li> <li>Build modular interfaces using common components </li> <li>Test the experience or workflow of a new concept at a high-level</li> <li>Pitch concepts in a rougher state that encourages open feedback </li> <li>Encourage non-visual folks to collaborate or present their ideas without a learning curve</li> <li>Engage non-technical folks with a feature without using their content management system</li> </ul><p>We use paper prototyping in a variety of ways. This activity works solo or with a group. One person can lead the group by prototyping a summary of group consensus, or everyone can play in their own sandbox and then present to the larger group. We've led workshops in person with real paper goods and remotely with pixels; we'll come back to how to make it work whether you're on a distributed team or together in the same room. </p> <p>When deciding if this artifact is right for you, ask yourself: what assumptions do you need to test if any? What types of participants would be best to test your hypothesis? Let your hypothesis drive your prototype experiments. </p> <h2>Testing testing </h2> <p>Usability testing is an effective user research methodology. The process centers around meeting with users to measure their experience and discover pain points. Test moderators often prompt actual end-users with before and after questions and tasks to complete while taking lots of notes. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 647px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/old-testing.png?itok=U-gODC5Z" width="647" height="269" alt="Retro testing illustration showing 4 people sitting around a table, one observer, one person pretending to be a &quot;computer&quot;, a facilitator of the test, and the user doing the test" /><figcaption class="image__caption">This retro testing illustration is from a 1994 programming article, Prototyping for Tiny Fingers. Three test moderators is ideal, but you can get by with one or two people. </figcaption></figure></div> <p><a href="https://www.usability.gov/get-involved/blog/2005/06/paper-prototypes-and-software-prototypes.html" target="_blank">Five different studies</a> compared usability tests using high and low-fidelity prototypes. Researchers found two interesting patterns:</p> <ol start="1"><li>Test participants largely preferred high-fidelity, computer-based prototypes (likely because they feel more realistic).</li> <li>Yet, fidelity didn't make a difference to the observations gathered from testing. Usability findings were the same sensitivity for both approaches. </li> </ol><p>Using lower-fidelity prototypes provides efficiency not only within the test but beyond it. Projects that incorporate them are more productive than projects that don't. Ultimately, prototyping can save a lot of money. </p> <p>The Nielsen Norman Group has proven that <a href="https://www.nngroup.com/articles/paper-prototyping/" target="_blank">prototyping early reduces project costs</a>. They explain, "The biggest improvements in user experience come from gathering usability data as early as possible in a design project." In other words, </p> <blockquote> <p>Teams can save time on testing by using lower fidelity, empowering earlier and more frequent testing.</p> </blockquote> <h2>Get started</h2> <p>Thankfully, you don't need much. A trip to your local Staples or office supply cabinet should have it all. Some printer paper and a good writing utensil are enough to show almost anything. A pair of scissors and some stickers if you're fancy. </p> <p>Consider what formats you're most comfortable with to keep the barrier-to-entry low. Are you a classic Sharpie kind of person, or would you rather have a pack of colored markers? Do you like the sound of flashcards that you can shuffle and slide, or would you prefer to layer stickies on a wall? Or would you rather "frame" your scene in a cut-out browser window or mobile screen to incorporate scrolling? For example, we like to use laminated paper as our "dry-erase" frame; we can jot notes down in the margins, then wipe them clean for the next session.</p> <p>Alternately, you can always print and cut out wireframes if you are trying to test-run something already in digital format. We use the <a href="https://remarkable.com/store/remarkable-2" target="_blank">Remarkable sketching tablet</a> for an approach that is hand-to-digital-and-back-again. Consider downloading or building printable templates specific to your needs.</p> <p>Remember that your goal is to make it, use it, or share it, and move on. It's okay if this artifact is a little ugly because that's the magic of its ease and speed. It's okay to fold over or tear the paper instead of cutting everything out. Keep these in mind when deciding what level of fidelity is right for you. Be creative with how you keep testing scrappy and quick. </p> <h3>Tips by use case </h3> <p>Maximizing the value of your paper crafts depends on the context in which you use them. </p> <p>If you are working on your own, document, document, document: </p> <ul><li>Note any details you'll need in the margins to help remember specifications later. </li> <li>Take lots of pictures along the way to document changes and "finished products."</li> <li>To capture a long or complex sequence, try recording a video of yourself walking through it (this is my favorite way to share remotely). </li> </ul><p>If you are collaborating with a group onsite, plan, plan, plan: </p> <ul><li>Do everything from the last section.</li> <li>Plan your activity format (again, what is the hypothesis you are testing?).  <ul><li>Will you have one big group participate together or split up into smaller groups? If smaller groups, how many? And will they focus on a shared task, or will you set a unique task per group?</li> <li>How much time will you set aside for ideation, presentation, and feedback?</li> </ul></li> <li>Build a reusable kit beforehand, informed by your plan.</li> <li>Prepare a short pitch to walk through how to participate in the workshop.</li> <li>Bring blanks and expect to add to the toolkit ad-hoc.</li> <li>Consider a recording or transcription tool (like <a href="https://otter.ai/" target="_blank">Otter</a>) or take lots of good notes.</li> </ul><p>If you are collaborating, but the others are remote, test those tools: </p> <ul><li>Everything previously mentioned applies again.</li> <li>You can still conduct similar paper prototyping workshops online without the paper. </li> <li>Explore the many available tools for online visual collaboration, starting with ones you or your client are familiar with. <ul><li>See online whiteboards <a href="https://miro.com/" target="_blank">Miro</a>, <a href="https://www.figma.com/community/figjam" target="_blank">Figjam</a>, and <a href="https://www.invisionapp.com/freehand" target="_blank">Invision Freehand</a>.</li> <li>For interactive prototypes, see <a href="https://www.figma.com/prototyping/" target="_blank">Figma</a>, <a href="https://marvelapp.com/pop/" target="_blank">Marvel POP</a>, and even your favorite slide deck tool. </li> </ul></li> <li>Once you pick your tool, assemble your template kit and take it for a test run internally.  <ul><li>Get a sense of the time you'll need for the activity. Then pad it 5-15 minutes.</li> </ul></li> <li>Save the link or take a lot of screenshots for posterity. </li> </ul><p>If you are testing usability, plan, document, <em>and</em> test: </p> <ul><li>98% of the earlier tips apply, especially the recording and note-taking bits. </li> <li>Read our <a href="https://www.lullabot.com/articles/usability-checklist-everyone" target="_blank">usability checklist article</a> for an overview on testing.</li> <li>Draft a protocol: include context-setting, open-ended questions, and specific task prompts.</li> <li>Simplify the screens or layers wherever you can reduce confusion. <ul><li>Frame an area for the participant to focus on. </li> <li>Use placeholder text to add clarity; use squiggles to add focus.</li> <li>Cut-outs are fine for expanding or layering elements, but try to limit screens to a sheet of paper.</li> </ul></li> <li>Do a deep-dive test run again. <ul><li>Get familiar with the tool and its limitations. </li> <li>Practice responding as the neutral "computer." Don't help the user any more than the interface would. </li> <li>Make sure you have everything you want to show (and enough time to show it!).</li> </ul></li> </ul><h2>Paper prototypes in action</h2> <p>Finally, these case studies illustrate two times we used paper prototypes. While the examples are very different, both helped others understand or try a new UX. </p> <h3>Modeling page-building onsite with Georgia's state agencies </h3> <p>This case study is one of several times Lullabots used paper-prototyping with site editors to assemble web pages. We often workshop important pages or templates with clients, prioritizing content and functionality. In this case, for <a href="https://georgia.gov/" target="_blank">Georgia.gov</a>, we met with authors from several state agencies, all using a shared platform. However, this workshop had a dual purpose: we wanted to prototype a new editorial experience with their CMS, Layout Builder. This would be a big shift in the way authors create content, from filling out fields to a drag-and-drop. Our end-users were these authors, who would later use Layout Builder to add agency content and unique layouts. </p> <p>The team used paper to get feedback long before implementing any code. We made a deck of proposed components with markers on flashcards. Each card had a loose wireframe of an important feature or unique layout. We brought 11x17 paper to serve as our frame. Planning these materials ahead of time let us collaborate more efficiently.</p> <p>We began each session by walking each agency through our page-building concept at a high level, then introducing the component deck. Before handing off, we established a baseline of understanding. Then each group would pick a high-traffic page and work together to rebuild it using the tool provided. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1440px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/paper-georgia.png?itok=LNEfduJM" width="900" height="675" alt="Paper wireframe components and laminated backgrounds spread out on a table" /><figcaption class="image__caption">This flexible page-building paper prototype includes print-outs, page sheets, and component flashcards. This kit simplified what can be a confusing experience in Drupal.</figcaption></figure></div> <p>Half a dozen sessions—and a file folder full of images—later, the team had a variety of impressions from real end-users: </p> <ul><li>This new layout functionality sounded more exciting than daunting. </li> <li>Certain components generated more interest than others. </li> <li>The toolkit covered almost every use case.</li> <li>For the unmet needs we discovered, quick brainstorms captured new component ideas.</li> </ul><p>We made adjustments, we reported back to our team, and we green-lit development. The group undertook a big implementation project with confidence, thanks to our author feedback. Thankfully, the authors adopted and still use this content layout tool. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1435px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/prototyping-session.png?itok=dCb6v1kA" width="900" height="675" alt="People around a table participating in a prototyping session." /><figcaption class="image__caption">An in-process shot from testing with content editors. After a walkthrough, this agency recreates an existing landing page using existing and new components. </figcaption></figure></div> <p>Viewing different agency pages, you can get a sense of the flexibility and cohesion of our end result. Visit the departments of <a href="https://dds.georgia.gov/" target="_blank">Driver Services,</a> <a href="https://dhs.georgia.gov/" target="_blank">Human Services</a>, <a href="https://dor.georgia.gov/" target="_blank">Revenue</a>, and <a href="https://ada.georgia.gov/" target="_blank">ADA Coordination</a>. Watch Lullabot's webinar recording to learn more about this workshop: <a href="https://www.lullabot.com/webinars/georgiagov-case-study-structured-content-and-flexible-layouts" target="_blank">Structured Content and Flexible Layouts</a>.</p> <h3>Play-testing game rules within a 72-hour hackathon</h3> <p>This example is much less commonplace but still fun. A team of five developers and designers had three days to make a small, playable game for a large online hackathon known as <a href="https://ldjam.com/" target="_blank">Ludum Dare</a>. Our submission had to follow the theme, "Keep It Alive," so our concept had to be relevant, fresh, and bite-sized.  </p> <p>This was not a slow-moving, thoughtful decision like with government; we had to move <em>fast</em>. We spent the first evening brainstorming and refining our favorite game idea. We hoped to string together something rough but playable 24 hours after generating an idea, then refine a shippable game the next day. This plan gave us a half-day buffer in case something went terribly wrong.</p> <p>We came up with a game concept but wanted to validate that it was actually fun before building. Our idea was a silly but soothing solitaire-type card game played against your goldfish, Gil. We used paper to conceptualize the game board layout, card decks, and user interface. We tested feature ideas and game rules on these early prototypes, playing them through to understand the experience. It was easy to make and change different versions until the card game felt right. The first draft had a board, a scoring area, and two decks of cards. When we met again, we pitched a refinement of the original idea using the paper draft as a visual aid.</p> <p>By the end of the discussion, we had defined the game, outlined its sequence of steps, and unblocked the rest of the work. The developers made a list of feature requirements; the designers explored visual assets; even the sound guy got guidance on mood. We had all this clarity mere hours into the competition, without a single design mock-up or a line of code.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1714px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/game-prototypes.png?itok=Jb41CJKC" width="900" height="646" alt="A person play-testing a paper version of a computer game, with decks of cards and game sheet" /><figcaption class="image__caption">The Don’t Kill Gil paper prototype includes a game sheet, UI card markers, and two flashcard decks. Here, a volunteer gamer play-tests the game with paper cards and takes notes of his experience. </figcaption></figure></div> <p>This paper prototype served another fun purpose the next day. A volunteer participated first in a moderated usability test. After iteration, we tailored the deck, taught him how to act as the computer, and set him loose. He play-tested the experience over and over, looking for how easy, fun, long, and consistent the game was to play, then reported back. The paper prototype let us tinker with the cards, then model a refined deck for development.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1412px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/volunteer-testing.png?itok=iraADyMN" width="900" height="622" alt="User testing an early version of a game on a tablet" /><figcaption class="image__caption">Here, the volunteer tests a digital work-in-progress, which is a much higher fidelity and deals its own cards. We took this photo less than two days later. </figcaption></figure></div> <p> The end result? The team finished the game before the deadline. We were proud of the end product and received lots of positive feedback from other hackathon participants. Ultimately, <a href="https://webthingee.itch.io/fvf" target="_blank">Don't Kill Gil</a> rated a 3.9 out of 4! It was a satisfying experience. We got there with paper.</p> <h2>Conclusion</h2> <p>We hope these tips and examples will aid your next project. Paper prototyping takes many shapes, sizes, and fidelities, easily adapting to different phases, teams, and needs. It allows visual documentation of ideas without knowing Figma or similar design tools. It empowers concept communication up front to avoid extending timelines. Return on investment aside, it's pretty fun. </p> <p>Lullabot would love to hear from you: what ways have you used paper prototyping in the past? Is there anything you are excited to try? </p> <p>If this sounds like the right direction, but you aren't sure where to start, we can help! <a href="https://www.lullabot.com/contact" target="_blank">Contact</a> Lullabot to learn more about how our UX and strategy services might fit your needs.</p> Wed, 22 Jun 2022 12:23:48 -0400 Marissa Epstein cc2d0ed2-0a66-4026-8ad9-b49eb67d5831 Lullabot Podcast: The New Olivero Theme – Awesome to the Core https://www.lullabot.com/podcasts/lullabot-podcast/olivero-core <p>A group of Lullabots (and Former 'bot and podcast co-host Mike Herchel) get together to discuss the new Default theme in Drupal 9 and 10 that they helped build.</p> <p>The theme called "Olivero" is as beautiful as it is flexible and accessible.<br /><br /> The team talks about the immense amount of work it took for a project of such high visibility in the Drupal community.</p> Thu, 16 Jun 2022 23:30:00 -0400 Matt Kleve ff2073b7-ef65-4b1a-8dcf-dd33114a28ad How to Make Changes to kube-apiserver https://www.lullabot.com/articles/how-make-changes-kube-apiserver <p>When <a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips">preparing for the CKS exam</a>, you have to train a lot with tasks that involve making changes to <code>/etc/kubernetes/manifests/kube-apiserver.yaml</code>. This file is monitored by the Kubelet service, which runs the kube-apiserver process in a container. If the Kubelet detects a chance in that file, it restarts the container with the updated configuration.</p> <p>In successful scenarios, kube-apiserver's container stops for a few minutes and then returns. However, if there is an error, it might not come back.</p> <p>We'll show you steps you can follow to get back on track, especially if you are preparing for any of the Linux Foundation's Kubernetes certifications.</p> <h2>The testing environment</h2> <p>Let's take one of <a href="https://killercoda.com/killer-shell-cks/scenario/apiserver-node-restriction">Kim Wuestkamp's killercoda challenges</a> where we have an ephemeral cluster that we can tinker with. Here is an active environment with the printed the contents of <code>/etc/kubernetes/manifests/kube-apiserver.yaml</code>:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1220px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/kube-apiserver1.png?itok=no5km9cU" width="900" height="666" alt="An active environment with the printed the contents of /etc/kubernetes/manifests/kube-apiserver.yaml" /><figcaption class="image__caption">The contents of /var/kubernetes/manifests/kube-apiserver.yaml</figcaption></figure></div> <p>We can see that there is a command called kube-apiserver, which receives a long list of options, each in its own line. We will start by changing something that we know will work, then forge ahead from there.</p> <h2>Making a change</h2> <p>The change we are going to make is about forbidding the creation of privileged containers. Per the <a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/" target="_blank">official documentation</a>, this is about turning the option <code>--allow-privileged=true</code> into <code>--allow-privileged=false</code>.</p> <p>Before making the change, it is recommended to make a copy of the existing configuration. You can just copy it into the home directory with the following command:</p> <p><code>cp /etc/kubernetes/manifests/kube-apiserver.yaml .</code></p> <p>Now let's make the actual change in the file with an editor like VIM. Open the file, make the change described above, save, and quit.</p> <p>TIP: In a production environment, Kubernetes configuration might be managed via configuration in code. However, the steps to monitor and analyze the result of a change explained below should still be helpful.</p> <h2>Monitor the change</h2> <p>The Kubelet will detect the file change and restart the kube-apiserver container with the new configuration. We can track this by observing the running containers via the following command:</p> <p><code>watch crictl ps</code></p> <p>Here is the output, which refreshes every two seconds:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1006px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/observe-running-containers.png?itok=9F9Fb5QK" width="900" height="185" alt="Output from watch crictl ps" /><figcaption class="image__caption">Running containers. Notice that kube-apiserver has been running for 6 minutes.</figcaption></figure></div> <p>This listing shows our running containers. One of them is <code>kube-apiserver</code>, which has been running for 6 minutes. If everything goes well, it should vanish for a few seconds and reappear, like in the screenshot below:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 990px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/observe-running-containers2.png?itok=Br8JtzYJ" width="900" height="215" alt="Output from watch crictl ps after kube-apiserver restarts" /><figcaption class="image__caption">Running containers. Notice how kube-apiserver has been running for 33 seconds.</figcaption></figure></div> <p>TIP: <span>In the above command, we use <code>crictl</code> instead of docker to list running containers. <code>crictl</code> is a command-line interface for CRI-compatible container runtimes. It is common to use CRI as the container runtime in Kubernetes instead of docker. If you are using docker, though, the command would be identical, only replacing <code>crictl</code> with <code>docker</code>.</span></p> <h2>Verify the change</h2> <p>Now that the kube-apiserver container is running, we can verify that it is using our change by searching for running processes:</p> <p><code>ps aux | grep kube-apiserver | grep privileged</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1320px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/running-processes.png?itok=AX-rZWcB" width="900" height="239" alt="Output of ps aux | grep kube-apiserver | grep privileged" /><figcaption class="image__caption">The kube-apiserver process has the expected option value.</figcaption></figure></div> <p>We can see that there is a <code>kube-apiserver</code> process that is using the option -<code>-allow-privileged=false</code>, hence the change did take effect.</p> <h2>Dealing with failures</h2> <p>There are two kinds of errors:</p> <ul><li>Bad syntax errors. These are caused by introducing an error at <code>/etc/kubernetes/manifests/kube-apiserver.yaml</code> which makes its YAML invalid, and therefore the Kubelet stops the kube-apiserver container but does not start it back.</li> <li>Incorrect configuration. This causes a container startup error, so the Kubelet will try to start the kube-apiserver container a few times before it desists.</li> </ul><p>In both cases, the steps to find out are the same, but understanding these two scenarios will help you to know where to look at. Let's look at each in turn.</p> <h3>Bad syntax</h3> <p>We will introduce a typo at <code>/etc/kubernetes/manifests/kube-apiserver.yaml</code> and then see what happens. The following screenshot shows an invalid array of options that are passed to the kube-apiserver command:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 677px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/config-with-typo.png?itok=HAlC9mQL" width="677" height="402" alt="kube-apiserver.yaml with typo at allow-privileged" /><figcaption class="image__caption">Notice the missing dash at the third option (allow-privileged).</figcaption></figure></div> <p> Now let's save the file and monitor containers as we did before. We list containers with the following command:</p> <p><code>watch crictl ps</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1028px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/containers-after-type.png?itok=ch9MUFAB" width="900" height="192" alt="watch crictl ps output before typo trigger failure" /><figcaption class="image__caption">kube-apiserver is still running as it has not been stopped yet by the Kubelet</figcaption></figure></div> <p>After a couple of minutes, <code>kube-apiserver</code> is gone and does not reappear:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1028px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/container-after-gone-typo.png?itok=Nqz2ciER" width="900" height="192" alt="Output of watch crictl ps after kube-apiserver stops working" /><figcaption class="image__caption">kube-apiserver does not show up again.</figcaption></figure></div> <p>Time to look at the logs. These are under <code>/var/log/pods</code> or <code>/var/log/containers</code>. Use whatever you prefer. In this case, there is nothing since the container is not running, and it was deleted. The <code>kube-apiserver</code> logs are missing at <code>/var/log/pods</code>:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1219px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/list-of-logs.png?itok=R7Q-ij8o" width="900" height="85" alt="Output of tail -f /var/log/pods/kube-system_" /><figcaption class="image__caption">There is no directory for kube-apiserver at /var/log/pods.</figcaption></figure></div> <p>Now let's look at <code>/var/log/containers</code>:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1012px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/another-list-of-logs.png?itok=roXhjUmt" width="900" height="167" alt="Output of tail -f /var/log/containers/kube-" /><figcaption class="image__caption">There are no kube-apiserver logs either.</figcaption></figure></div> <p>Next, let's list all containers in the system using crictl, including the ones that are not running:</p> <p><code>crictl ps -a</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 991px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/no-kube-container.png?itok=3C-T8OIW" width="900" height="211" alt="Output of crictl ps -a" /><figcaption class="image__caption">There is no container for kube-apiserver. Not even a crashed one.</figcaption></figure></div> <p>If you get to this point, look for errors at <code>journalctl</code>, filtering them by <code>kube-apiserver</code> since there is usually a lot of output. You can do this with:</p> <p><code>journalctl | grep apiserver</code></p> <p>And here is the result, right at the bottom of the output:</p> <p><code>May 13 13:25:49 controlplane kubelet[25181]: E0513 13:25:49.437945   25181 file.go:187] "Could not process manifest file" err="/etc/kubernetes/manifests/kube-apiserver.yaml: couldn't parse as pod(yaml: line 18: could not find expected ':'), please check config file" path="/etc/kubernetes/manifests/kube-apiserver.yaml"</code></p> <p>We can see above that the manifest file <code>/etc/kubernetes/manifests/kube-apiserver.yaml </code>could not be processed. We even got the line number (18), which matches the line where we broke the array of options. Let's fix it and verify that the kube-apiserver gets back into life:</p> <p><code>vim /etc/kubernetes/manifests/kube-apiserver.yaml</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 687px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/kube-apiserver-config-again.png?itok=TvemQert" width="687" height="418" alt="kube-apiserver.yaml file with the missing dash restored" /><figcaption class="image__caption">Here we have restored the missing dash at the options array.</figcaption></figure></div> <p>Then we save and monitor containers again. As expected, <code>kube-apiserver</code> is now running:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1000px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/kube-apiserver-nowrunning.png?itok=pZRu2ea4" width="900" height="199" alt="Output of crictl ps" /><figcaption class="image__caption">kube-apiserver has been running for 10 seconds.</figcaption></figure></div> <p>When we have introduced bad syntax, there will be no new containers and (unless you look quickly) no logs under <code>/var/log</code>. Therefore, we can find the error at <code>journalctl</code>.</p> <p>Let's look at the other type of error that we may find.</p> <h3>Incorrect configuration</h3> <p>In this scenario, the manifest syntax is correct, but the configuration is not. This could happen when we set the wrong value to an option, such as an incorrect data type or an incorrect volume path. Let's see an example.</p> <p>We will use the same option we have been using in this article, allow-privileged, and this time we will set it to wrong. The resulting manifest is valid YAML, but when the Kubelet starts the container, it will fail and enter a loop in which it tries to start and crashes. Let's start by making the change:</p> <p><code>vim /etc/kubernetes/manifests/kube-apiserver.yaml</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1000px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/wrong-incorrect-config.png?itok=gSe62lXM" width="900" height="314" alt="kube-apiserver.yaml with wrong config" /><figcaption class="image__caption">Notice the “wrong” value at --allow-privileged</figcaption></figure></div> <p>We then save the file and monitor containers:</p> <p><code>watch crictl ps</code></p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1000px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/kube-apiserver-exited.png?itok=cRcvIksZ" width="900" height="236" alt="Output of watch crictl ps with exited state for kube-apiserver" /><figcaption class="image__caption">kube-apiserver can’t start</figcaption></figure></div> <p>Look at the listing above, in which <code>kube-apiserver</code> appears as STATE Exited, and there have been two attempts to start it, the last one 11 seconds ago. We can dig deeper by looking at the logs of the container of the previous attempt by copying the container identifier in the CONTAINER column (<code>2a5e823478bdf</code>), and then using <code>crictl logs 2a5e823478bdf</code>:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/crictl-logs.png?itok=18UFZyMH" width="900" height="59" alt="Output of crictl logs with hash, showing an error" /></figure></div> <p>There is the error. Now that we know what to fix, let's look at container logs to see if we can find this error there:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1000px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/container-logs.png?itok=7v03bnSl" width="900" height="183" alt="Output of tail -f /var/log/containers/kube-" /><figcaption class="image__caption">No kube-apiserver logs at /var/log/pods</figcaption></figure></div> <p> Nothing there. Now let's look at pod logs:</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1222px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-06/log-containers.png?itok=QqdzsVSI" width="900" height="83" alt="Output of tail -f /var/log/pods/kube-system_" /><figcaption class="image__caption">No kube-apiserver logs at /var/log/pods</figcaption></figure></div> <p>Nothing there either. Finally, let's look at the error at <code>journalctl</code>:</p> <p><code>journalctl | grep kube-apiserver</code></p> <p>The above command returns a lot of error messages since there are other services trying to reach out to the kube-apiserver and failing. None of the errors is the one that we found in the container logs via <code>crictl</code>.</p> <h2>Summary</h2> <p>Here is a suggested list of steps that sums up all the above examples:</p> <ol start="1"><li>Make a copy of <code>kube-apiserver.yaml</code> before you make changes to it.</li> <li>Make the change at <code>kube-apiserver.yaml</code> and save it. </li> <li>Monitor containers with <code>watch crictl ps</code> or <code>watch docker ps </code>depending on the container runtime that the cluster uses.</li> <li>If the <code>kube-apiserver</code> does not reappear after a couple of minutes: <ol start="1"><li>Look at container logs with <code>crictl ps -a</code>. If you see a <code>kube-apiserver</code> container, inspect its logs with <code>crictl logs [id of the container]</code>. If you identify the error, fix it at <code>kube-apiserver.yaml</code> and go back to step 3.</li> <li>If you did not find the error, search for it at <code>journalctl</code> using <code>journalctl | grep api-server</code>. Try to find the error there.</li> <li>If you did not find the error at <code>journalctl</code> , look at the container and pod logs at <code>/var/log</code>.</li> </ol></li> <li>There are cases where the Kubelet did stop the kube-apiserver container but did not start it again. You can force it to do so with <code>systemctl restart kubelet.service</code>. That should attempt to start kube-apiserver and log an error at <code>journalctl</code> if it failed.</li> </ol><h2>Conclusion</h2> <p>This guide should help you to track configuration errors at kube-apiserver. It requires patience and practice until you get fluent with these commands and the kind of output you will find. You can start practicing now by solving the challenges that involve the apiserver at <a href="https://killercoda.com" target="_blank">https://killercoda.com</a>.</p> <p> </p> Wed, 15 Jun 2022 16:47:05 -0400 Juampy NR 46d1b817-029f-4c9b-9c43-929ff6de3266 Component Libraries in Drupal https://www.lullabot.com/articles/component-libraries-drupal <p>Component libraries have become one of the first things organizations require for new engagements. There are <a href="https://www.uxpin.com/studio/blog/ui-component-library/" target="_blank">many reasons</a> for organizations to want a component library, but the reality is that Drupal, and PHP in general, have not been in a good place to deliver great solutions in this area. This is why we have created the <em>Component Libraries</em> suite of modules. This article will walk through what we mean when we say <em>component </em>and why we think these modules offer an excellent, flexible solution.</p> <h2>Drupal Components</h2> <p>Before we talk about component libraries, let's clarify what we mean by <em>Components</em>.</p> <p>In our opinion, a Drupal component is a combination of:</p> <ul><li>A Twig template.</li> <li>Metadata describing the input data the template accepts.</li> <li>Optional JavaScript.</li> <li>Optional Styles.</li> </ul><p>In this context, a component is <strong>not</strong> a type of block plugin (like <a href="https://www.drupal.org/project/component" target="_blank">Component</a> and <a href="https://www.drupal.org/project/pdb" target="_blank">Decoupled Blocks</a> modules). Those are progressive decoupling approaches. It's a different problem space, and in that space, we leverage the <a href="https://www.lullabot.com/articles/progressive-decoupling-made-easy" target="_blank">JS Widgets modules</a>.</p> <p>The <a href="http://www.drupal.org/project/components" target="_blank">Components</a> module (the plural is an entirely different module from the singular) is a very popular and useful module, but it does not help us declare or render components. Its function is to <em>simplify Twig namespaces and provide some additional Twig functions and filters for use in Drupal templates</em>. Also, a different problem space. In fact, you can leverage this module with our solution.</p> <p>We developed the <a href="https://www.drupal.org/project/cl_components" target="_blank"><em>Component Libraries: Components</em></a> (aka CL Components) as a way to way to declare and manage components in Drupal. Our understanding of components aligns with the <a href="https://www.drupal.org/project/sfc" target="_blank">Single File Components</a> module and the <a href="https://www.drupal.org/project/ui_patterns" target="_blank">UI Patterns</a> module. We only differ in how components are declared and used. CL Components takes the simplest and most familiar approach, where the developer writes the Twig, JS, and SCSS in separate files and then embeds the components in the Drupal templates (like <code>node--article--card.twig.html</code>). Check <a href="https://git.drupalcode.org/project/cl_components/-/blob/1.x/docs/writing-components.md" target="_blank">the documentation</a> if you want to learn more about how to declare components. Since CL Components is still in early development, we are researching ways to make these three compatible.</p> <h2>Moving away from Twig.js</h2> <p>All this time, we have been <strong>emulating</strong> Drupal in our component libraries. This was a solution to a very real problem: most component libraries are written in JavaScript. <a href="https://storybook.js.org/" target="_blank">Storybook</a> is a React application, <a href="https://fractal.build/" target="_blank">Fractal</a> is written in JavaScript, and so is <a href="https://patternlab.io/" target="_blank">Pattern Lab</a> (and it is <a href="https://github.com/pattern-lab/patternlab-node/issues/1316" target="_blank">no longer maintained as it used to be</a>.) In all of these cases, a JavaScript application needs to render Drupal components, which are PHP. There is no simple way for these JavaScript applications to interpret PHP code.</p> <p>The solution we've been using in the past is <a href="https://github.com/twigjs/twig.js/" target="_blank">Twig.js</a>. This is the compatibility layer between Twig, a PHP project, and JavaScript. From their GitHub page:</p> <blockquote> <p>Twig.js is a pure JavaScript implementation of the Twig PHP templating language (http://twig.sensiolabs.org/)</p> <p>The goal is to provide a library that is compatible with both browsers and server-side JavaScript environments such as node.js.</p> <p>Twig.js is currently a work in progress and supports a limited subset of the Twig templating language (with more coming).</p> </blockquote> <p>The problem with sending our Twig templates to Twig.js so they can be rendered in our component library is that they don't match Drupal. When Twig.js renders a template, it turns a <code>my-template-file.twig</code> file with some example data into <code>output.html</code>. On the surface, this seems enough. However, this workflow does not take into account:</p> <ul><li>The styles of your Drupal theme.</li> <li>The JavaScript of your Drupal theme.</li> <li>Any additional styles and JS added by custom &amp; contrib modules.</li> <li>Preprocess functions that alter the HTML or massage the data.</li> </ul><p>In summary, <strong>this workflow does not take Drupal into account</strong>. How can we confidently tell our clients that what they see in Storybook is what they will see in Drupal? The uncomfortable truth is that we cannot. How can this work if this workflow doesn't know what Drupal theme should be used? We can try to make the emulation of Drupal as realistic as possible, but as soon as you change a line of CSS, install a new contrib, or add a preprocess, the emulation is outdated.</p> <p>On top of that, the emulation of PHP will inevitably have rough edges. A consistent transformation of <code>my-template-file.twig</code> into <code>output.hml</code> is not guaranteed. Twig.js only implements part of the Twig specification and only for the most recent version of Twig, which is not Drupal's. Aside from that, Drupal may leverage custom and contrib Twig extensions. If you want to render a template with one of those using Twig.js, you will have to write the pure JavaScript implementation first.</p> <p>As you can see, there are <strong>many problems and wasted effort in emulating Drupal</strong> for your component library. Our solution is to stop emulating Drupal and start using Drupal to render your components using the <a href="https://www.drupal.org/project/cl_server" target="_blank">CL Server</a> module.</p> <h2>Make Drupal render the components</h2> <p>Storybook started working on <strong>server-rendered components</strong> <a href="https://github.com/storybookjs/storybook/commit/422bc3a177553c4a8877f0ed907efd725f903e7b" target="_blank">back in 2020</a>. Even if they are not yet, present on the documentation site, the Storybook team has <a href="https://github.com/storybookjs/storybook/tree/next/app/server#addon-compatibility=" target="_blank">pledged compatibility</a> for the <em>server framework</em>. This means that we can use Storybook as our component library and instruct it to make HTTP requests to Drupal to render the components. If you are interested, you can look at the <a href="https://git.drupalcode.org/project/cl_server/-/blob/1.x/docs/storybook.md" target="_blank">step-by-step tutorial to configure Storybook + Drupal</a>.</p> <p>The CL Server module will allow you to <strong>render a component in isolation</strong>. To do so, the module uses your theme to render a full page with your component in a way that is usable for Storybook. Then the <code><a href="https://www.github.com/lullabot/storybook-drupal-addon#readme" target="_blank">@lullabot/storybook-drupal-addon</a></code> removes all the unnecessary bits (like menus, sidebar blocks, etcetera). The result is an HTML document that contains only the component as rendered by Drupal. Storybook will use that HTML to display the component. The Storybook Drupal addon will let you select what Drupal theme you want to use because the same component renders differently depending on the theme.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-video"> <figure class="video video--wide video--captioned"><video controls="" muted="" poster=""><source src="/sites/default/files/2022-05/storybook-drupal_-rendered-in-drupal-with-your-themes-720p-hls.mp4" type="video/mp4"></source></video><figcaption class="video__caption">Components rendered in Drupal with your themes</figcaption></figure></div> <p>Using this setup, you can ensure that <code>my-template-file.twig</code> will render the same as in your Drupal pages. As soon as you change a line of CSS, install a new contrib, or add a preprocess, the component library is <strong>properly updated</strong>. No effort required.</p> <p>The result is a component library that showcases <strong>components that stay true to your Drupal themes</strong> and enables <strong>modern development techniques</strong> for Drupal developers. Techniques that, in the past, were reserved for JS devs.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-video"> <figure class="video video--wide video--captioned"><video controls="" muted="" poster=""><source src="/sites/default/files/2022-05/storybook-drupal_-hot-reload-of-server-side-changes-720p-hls.mp4" type="video/mp4"></source></video><figcaption class="video__caption">Hot reload of server-side changes</figcaption></figure></div> Wed, 01 Jun 2022 10:26:26 -0400 Mateu Aguiló Bosch 042dd2a4-4f63-47bd-b248-bf93e58f007e Content Strategy: Small Steps for Big Improvements https://www.lullabot.com/webinars/small-step-content-strategy <p>Your website is full of content. Some good, some not-so-good. Some organized, some coming out of corners you didn't know existed. You manage things well, but part of you wishes you could burn it down and start over.</p> <p>But time, money, and resources are never there when you need them. Your organization doesn't have the time or budget for a major redesign. You feel compelled to do something because, everywhere you look, you see potential improvements that could make a huge difference.</p> <p>A small-step content strategy might be exactly what you need. You don't have to start from scratch.</p> <p>In this webinar, you will learn:</p> <ul><li>What a small-step content strategy is, and why it can be so effective.</li> <li>Specific steps to take to start improving a section of your website <em>today</em>.</li> <li>How the State of Massachusetts used a small-step content strategy to realize big improvements for their users.</li> </ul> Tue, 24 May 2022 18:09:16 -0400 2e8576fc-c779-4656-b8ea-858bc0aa7835 Certified Kubernetes Security Specialist Exam Tips https://www.lullabot.com/articles/certified-kubernetes-security-specialist-exam-tips <p>Passing the <a href="https://training.linuxfoundation.org/certification/certified-kubernetes-security-specialist/" target="_blank">Certified Kubernetes Security Specialist</a> (CKS) exam is tough. You have up to two hours to solve 15 to 20 real-life scenarios, each with its own cluster. Even more intimidating: you have a proctor overseeing you and your screen.</p> <p>In this article, we’ll share the journey to prepare, take, and pass the exam.</p> <h2>Required certifications </h2> <p><a href="https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/" target="_blank">Certified Kubernetes Administrator</a> (CKA) certification is required to take the CKS exam. If you don’t have it or it has expired (it lasts three years), you can <a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank">view</a><a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank"> </a><a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank">our</a><a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank"> article with tips for t</a><a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank">he CKA</a><a href="https://www.lullabot.com/articles/kubernetes-certifications-ckad-and-cka-exam-tips" target="_blank"> exam</a>.</p> <h2>Courses</h2> <p>There are a lot of courses to choose from. Based on experience, here is what we have learned.</p> <p>You might be tempted to start with the Linux Foundation course <a href="https://training.linuxfoundation.org/training/kubernetes-security-essentials-lfs260/" target="_blank">Kubernetes Security Essentials</a>. While the course is interesting, we don’t recommend it as preparation for the CKS exam as it is too generic and does not provide tools or exercises to prepare for the exam.</p> <p>Kim Wüstkamp’s <a href="https://www.udemy.com/course/certified-kubernetes-security-specialist/" target="_blank">Kubernetes CKS 2022 Complete Course - Theory - Practice</a> at Udemy is an outstanding choice. This course is from 2021, and thus it uses Kubernetes 1.22. The CKS exam now uses Kubernetes 1.23 but there are not any considerable differences to take into account. The course is outstanding. Kim explains concepts really well and each chapter has a hands-on section that you can carry out with your own cluster.</p> <p>Kim is the creator of <a href="https://killercoda.com/killer-shell-cks" target="_blank">Killercoda</a>, a set of scenarios to practice CKS topics. You will get a chance to experience an environment with a built-in terminal in the browser where you can solve a set of challenges. We recommend solving all scenarios.</p> <p>Udemy has another CKS course from 2022: <a href="https://www.udemy.com/course/certified-kubernetes-security-specialist-certification/" target="_blank">Certified Kubernetes Security Specialist 2022</a>. This course uses Kubernetes 1.23. This course had too much theory and not much practice, and if you have completed Kim’s course, it will not add anything significant.</p> <h2>Exam simulator</h2> <p>Once you purchase the CKS exam, you have two simulator sessions available. The simulator experience is similar to what you will face on the day of the exam. Here are the main differences:</p> <ul><li>You have two takes.</li> <li>The simulator is harder than the actual exam. It’s meant to put you under a lot of pressure for you to try to complete it under two hours.</li> <li>The exam environment will be up for 36 hours, even if you close the browser. This is very useful as you can use that extra time to study and test the questions that you failed.</li> <li>Once you have figured out how to solve all questions, try the second take, with the goal of completing it under two hours. If you made any mistakes, check the answers and study further until you understand what went wrong.</li> </ul><h2>Exam tips</h2> <p>Start by reading the CKS <a href="https://docs.linuxfoundation.org/tc-docs/certification/important-instructions-cks" target="_blank">Important Instructions</a> and <a href="https://docs.linuxfoundation.org/tc-docs/certification/faq-cka-ckad-cks" target="_blank">Frequently Asked Questions</a> documents. This is a requirement for you to take the exam. </p> <p>Prior to the exam, clear as much from your desk as possible and make sure that the room is tidy. When you take the exam, the proctor will ask you to use your webcam to show your desk (above and under) and the whole room. Therefore, the less clutter that you have in the space and desk, the easier that it will be for the proctor to consider that your space is safe to take the exam.</p> <p>You can use an external monitor for the exam. The proctor will ask you to share both screens and use only one of them for the exam.</p> <p>On the day of the exam, join 15 minutes earlier so you have plenty of time to go through the security process with the proctor. It may take a bit longer than 15 minutes but this should not matter since the counter will start once you actually request the proctor to start the exam. Notice though that the exam cannot start 15 minutes later than the scheduled time.</p> <p>During the exam, speed is key. You just need to get a 67 out of 100 to pass. Therefore:</p> <ul><li>Start by solving all questions that you think you can solve quickly.</li> <li>If you find a task that seems tricky, flag it (there is a button to flag tasks) or make a note in the built-in notepad.</li> <li>Keep track of each question, its score, and whether you passed it or not. This will help you to decide which question to tackle next once you have done the first pass.</li> <li>At the beginning of each question, there is a command to switch to the cluster to use. Make sure that you are at the root environment before using such a command and not inside a cluster; otherwise, you won’t be able to switch context and therefore you will be using the wrong cluster. </li> </ul><h2>Conclusion</h2> <p>There are several resources out there to get confident enough for the exam. Please share your thoughts if you get to read this article and take the exam.</p> <p>Good luck!</p> Wed, 11 May 2022 17:32:11 -0400 Juampy NR 53cf3878-0691-44c3-ad71-95e4397a81a9 Lullabot Podcast: Small Team Skills -- Putting in the Work on a Drupal 7 Upgrade https://www.lullabot.com/podcasts/lullabot-podcast/small-team-skills-managing-drupal-7-upgrade <p>Host Matt Kleve talks with Nevin Katz who is an experienced Drupal developer on a small team who recently went through the process of upgrading an old D7 site to D9.</p> Mon, 25 Apr 2022 14:17:24 -0400 Matt Kleve c43d2d83-c3c4-4677-8d28-18a145194de3 The Present and Future of Drupal’s Administrative Interface https://www.lullabot.com/articles/present-and-future-drupals-administrative-interface <p>Claro is the new core administration theme based on the new <a href="https://www.drupal.org/docs/core-modules-and-themes/core-themes/claro-theme/design">Drupal design system</a>. It is a clone of the Seven admin theme, the default admin theme in Drupal since Drupal 7. If Seven is adequate for a certain need, then Claro will be adequate for that need, too. We didn’t have to start from scratch.</p> <p>The big-picture goals are as follows:</p> <ul><li>Refresh the administration UI with an up-to-date look and feel</li> <li>Bring in new but well-known UI patterns used on the web that improve the UX</li> <li>Focus on the UX for each user type or persona</li> </ul><p>Why do we want to do this? Why focus on the administration theme? First, let’s look back to where we came from.</p> <h2>The past</h2> <p>Below is a form the original Drupal admin theme. It was designed by developers, built by developers, and made <em>for</em> developers.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/sk90riy8.png?itok=MKlP6fGH" width="900" height="569" alt="Old administration screen from early version of Drupal" /></figure></div> <p>The Views module showed a huge step forward for site builders who were not developers. These patterns eventually flowed to other parts of Drupal, like the Field UI, and helped site builders create complex pages that would have previously required the help of a developer. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/no2ythwa.png?itok=IgjZZNGL" width="900" height="483" alt="Views 2 user interface" /></figure></div> <p>This additional focus on site builders was welcome. But there was still one user we were leaving out: the content author. Or the content editor or content manager. Drupal’s interface isn’t focused on them. Drupal’s menus do not assume that content authors/editors make up the primary audience. They have to swim through menus that are not built for them.</p> <p>But with a content management system, managing content is kind of important.</p> <h2>The present</h2> <p>Today, we have the<em> </em><em>Easy Out of the Box</em> initiative, which aims to make Drupal easier to use, thanks to Claro, Media, and Layout Builder. Claro was born as the new admin theme inside the already closed “Drupal Administration User Interface and Javascript Modernization Initiative,” and it was grouped in this new initiative because it shared the same goals. We already have a path forward. The community has already succeeded in getting <a href="https://www.lullabot.com/articles/designing-chaos-finding-order-olivero">a fresh new front-end theme as the default in Olivero</a>.</p> <p>Drupal 9 uses Bartik as its default front-end theme, but Olivero is currently available to use. In Drupal 10, Olivero will replace Bartik. </p> <p>Similarly, Drupal 9 uses Seven as its default admin theme. Claro is currently available to use. But right now, it is <em>not</em> the default theme for Drupal 10. We need to get things stable, and sooner rather than later. </p> <p><strong>UPDATE: As of April 27th, 2022, Claro is stable. And it has been set as the default theme in core for Drupal 9.4 and 10. Time to move toward the future. Thank you for all your help.</strong></p> <h2>The future</h2> <p>We needed Claro as the default admin theme for D10. We were splitting time and attention toward maintaining two themes. Now that Claro is the default (for 9.4 and above), we can focus our full efforts on pushing Claro into the future and, with it, the entire Drupal admin experience.</p> <p>Where should we go, now that Claro is in core?</p> <h3><strong>Gin as sandbox</strong></h3> <p><a href="https://www.drupal.org/project/gin">Gin</a> is a sub-theme of Claro, born when Claro showed symptoms of being too slow to adopt some of the features we needed to improve the UI. Things like a different layout, a Dashboard, a Dark mode, or more JS interactions are some things that haven’t made it to core or Claro yet but are available for Gin. It has been an excellent sandbox for things that can’t go into Claro. At least, not yet. From the project page:</p> <blockquote> <p>The Gin theme also includes things which are currently out of scope for Claro and/or some customisations we're experimenting with for the future. Built on the foundation of Claro from one of the lead designers of Claro &amp; Drupal Design System.</p> </blockquote> <p>Sometimes, Gin implements something, things don’t lead where we expected, and the team can then tell us to avoid certain roads. So there is a good back and forth.</p> <h3><strong>Improvements to components</strong></h3> <p>Most things are exactly the same thing we’ve had since Drupal 6. We want to improve how things look and how they work, like tags.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/a2n3mcsa.png?itok=2IVWWmaD" width="900" height="547" alt="New UX pattern for Drupal tags" /></figure></div> <p>Another example is the Drag and Drop UI. There are a lot of possible colors and patterns. Drupal has had drag and drop for ages, but it doesn’t look like a modern implementation. It is currently based only on tables, and we might want to change it. It also needs to be accessible.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/jz8vz4wu.png?itok=eBnw0GRN" width="900" height="498" alt="New color options for Drupal's drag and drop UI" /></figure></div> <h3><strong>Layout changes for forms</strong></h3> <p>Different forms have different needs and different audiences. Not all forms can use the same layout. But we can’t just “change all the things” again because nothing gets done. We need to be more incremental and start with small improvements.</p> <h4>Improve legibility</h4> <p>We have already started doing this since it is the lowest-hanging fruit.</p> <p>The line length of textareas can extend to 150-170 characters when the ideal line length should be 60-80 characters. We set a thinner boundary for the form area, which can help the legibility of text and other components. The first image below is what we’ve had for years, but with Claro styles applied.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/7rehjduy.png?itok=ZhDTXnSj" width="900" height="363" alt="Seven node form" /></figure></div> <p>This is what we changed.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/tfhzh14g.png?itok=6-hkXDLn" width="900" height="504" alt="Proposed Claro changes to node form, with thinner form." /></figure></div> <p> </p> <p>We initially had this region even thinner, but this caused some issues with the Paragraphs module, so we needed to compromise. This is just one example of how difficult it can be to get changes into core: we need to be sure we’re compatible with as many contrib projects as possible.</p> <h4>More complex layouts</h4> <p>We want to work with the rest of the regions of the page. We have plans for a lot of cool things, starting with the Node form, and Gin already has a lot of the features we eventually want to implement in Claro.</p> <ul><li>A bar at the top with actions</li> <li>Off-canvas dialogs</li> <li>Visually independent regions wrapped in layers, providing a better feeling of depth and organization.</li> </ul> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/e8zx_jqg.png?itok=ZjREkRaR" width="900" height="678" alt="Node form layout mockup inspired by Gin" /></figure></div> <p>But it is complicated to have flexibility for everything. You never know what other people are going to throw into their forms. Vertical tabs. Paragraphs. There are so many options, so we need to take it one step at a time.</p> <h3><strong>Improve and simplify forms</strong></h3> <p>We want to approach sidebar content and behavior sensibly. Some elements can extend the sidebar way past the bottom of the page, leading to less-than-ideal experiences. What if we could move things to another tab in a way that did not require a full page load? What if we automatically saved the node? These changes would require heavier JS usage in several places.</p> <p>We also want the ability to add two columns where two things are in the same region, side by side.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow image--captioned" style="max-width: 1600px;"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/1rdppcck.png?itok=9dcAzLss" width="900" height="712" alt="Two column region highlighted on the Drupal node form" /><figcaption class="image__caption">Screenshot from Gin using field layouts</figcaption></figure></div> <p> </p> <h3><strong>Improve knowledge given to site builders </strong></h3> <p>Most of the time, whoever builds the forms for editors are developers and developers use technology differently than site builders and content creators. Providing additional instructions can be helpful.</p> <p>For example: who decides how many options before jumping into a select box from a radio widget?</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/gyqgiauw.png?itok=7DUfmfCF" width="900" height="741" alt="Example going form lots of radio options to a single select box" /></figure></div> <p>UX people have this knowledge. Developers often don’t, even though they are building these forms. Sometimes, people don’t take the time to search on the internet for best practices, or maybe they don’t have the proper vocabulary to search effectively. Why not put some information and recommendations in the UI itself? We could also provide more information when creating a new field.</p> <p>This could be a whole new initiative, different from the existing Help Topics Initiatives. This initiative would go field type by field type:</p> <ul><li>Use existing UI.</li> <li>Independent commits per solution/item proposed, so not a big commit to core.</li> <li>Start with an initial UX and UI analysis.</li> </ul><h3><strong>More interactive UI</strong><strong> </strong><strong>(more</strong><strong> javascript)</strong></h3> <p>Not completely changing the UI with something like React, just enhancing it. If you look at JIRA and Github, there are some good patterns where you can make changes without switching pages. In Drupal, we are currently switching pages all the time.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/ea24k3_o.png?itok=aTkxXJy7" width="900" height="493" alt="JIRA ticket form with sections highlighted showing where it can be edited without reloading the page" /></figure></div> <h3><strong>Menus for content creators</strong></h3> <p>One initiative related to this is the <em>Decoupled Menus Initiative. </em>It has aims, at least, for:</p> <ul><li>A menu that is global and static</li> <li>A menu(s) that changes from page to page and might be different for different users</li> </ul><p>There is also a new <a href="https://www.drupal.org/project/ideas/issues/3203618">“content creation” menu proposal</a>. The current menu has an architecture for those building the site, like “Structure” and “Extend.” We need to group things for content creation and management and present a completely different menu for someone who is <em>not</em> a site builder.</p> <h3><strong>Theme variations</strong></h3> <p>Everything has a dark mode now. Why not Drupal? Something like below is a possibility.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/ppecx0-o.png?itok=9xFkWFW7" width="900" height="654" alt="Drupal dark mode example" /></figure></div> <p>We also want options to change the colors of the interface. If your logo uses orange, you should be able to change your primary color in the theme or define your own palettes. <a href="https://www.drupal.org/project/ideas/issues/3090894">One issue is proposing to replace the Color module with a solution that uses CSS variables</a>, which is something Olivero already does. </p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/chqmdz-e.jpeg?itok=tmR1Ti9M" width="900" height="363" alt="Drupal node form with color palette changes and examples" /></figure></div> <p>We also want more UI customization—accent colors, spacing, etc. For example, some people might want smaller table cells so more rows are on a single screen. We can also improve accessibility b letting users choose their font size and toggle animations on and off.</p> <h3><strong>Dashboard</strong></h3> <p>Drupal 7 had a customizable dashboard, but it started as empty. To have a good dashboard, you need to know your target audience. <a href="https://www.drupal.org/project/ideas/issues/3244581">What about a different dashboard per role?</a> As you may have noticed, changing things per role is a big theme for the future of the administration UI initiative. Presenting the correct information to a user in an easy-to-digest way is one of the main goals.</p> <div class="inline-entity inline-entity--medium inline-entity--type-media inline-entity--bundle-image"> <figure class="image image--narrow"><img loading="lazy" src="/sites/default/files/styles/max_900/public/2022-04/yclmx5eq.png?itok=lQUDFUUK" width="900" height="884" alt="Dashboard mockup for Claro" /></figure></div> <p> </p> <h2>What’s next?</h2> <p>Before we can get to all of this awesome stuff for the future, Claro needs to be stable. We need your help to get there. A new core theme requires a lot more coordination and work than some other types of modules. Yes, we need more developers. But we also need UX experts and designers, site builders, and more. We need a good cross-section of expertise to succeed.</p> <p>Jump into the <a href="https://www.drupal.org/project/drupal/issues/3066007">stable blocker issue</a> and see where you can help! We’d love to have you.</p> <p><strong>UPDATE April 27th, 2022: Claro is now stable and is <a href="https://www.drupal.org/project/drupal/issues/3277057">the default admin theme for Drupal 9.4 and Drupal 10</a>!</strong></p> Wed, 20 Apr 2022 14:43:29 -0400 Cristina Chumillas a5cacb48-cdd8-4135-b0f7-dad3be2d2e76 Four Ways DevOps Strengthens Teams https://www.lullabot.com/articles/fours-ways-devops-strengthens-teams <p>Development Operations (DevOps) is shorthand for discussing systems set up to plan, build, test, and deploy software. By making certain processes automated, you can catch bugs earlier, deploy to production faster (and more often), and enable more rapid feedback. All in all, a proper DevOps setup can save a lot of time and money while helping you deliver a better product.</p> <p>But DevOps is also about systems meant to make software development more streamlined and remove some of the more mundane, laborious tasks from the shoulders of developers. When creating these processes, certain cultural changes come with it that can have repercussions for your entire organization.</p> <p>Because of this, when done right, DevOps can have a positive impact on the morale of your team, from developers to project managers to stakeholders. It leads to stronger bonds and smoother teamwork.</p> <h2>DevOps makes feedback more objective and less personal</h2> <p>Most development teams have a peer review process where code is submitted for review by other developers. Questions can be asked, different approaches discussed, potential regressions noticed, and kudos given. Senior developers can coach more junior developers. It’s an opportunity for learning and improvement all around.</p> <p>But this can sometimes degrade into nitpicking when developers leave feedback on coding standards. Or at least, it can feel like nitpicking. A developer submits something they are proud of, representing hours and hours of effort, and someone comes in and complains about the one line that doesn’t have the correct number of spaces. Or about that camelcase variable name.</p> <p>Coding standards are important, so these things <em>should</em> be highlighted and fixed, but it can get exhausting and sometimes confrontational for everyone involved. Automating these types of checks with linters and other tools whenever possible eliminates this possible stressor.</p> <h2>DevOps makes the implicit explicit</h2> <p>When you set out to automate processes, standards and heuristics must be agreed upon. Decisions have to be made. This means you must have conversations and communicate with your team.</p> <p>Infrastructure in code needs hard numbers and specs, which means you need to know what they should be first. Linters for code standards need to know what the code standards are, which means your team needs to agree on what those standards are first. Automated tests need to know what to test, which means your team needs to have a good idea of what is important enough to test.</p> <p>Working toward automation helps eliminate assumptions and forces decisions to be made. These conversations alone can lead to better products. And when things go wrong, there tends to be less finger-pointing because hidden assumptions among the team have been rooted out.</p> <h2>DevOps allows for more frequent releases</h2> <p>One university used to have two releases per year for their website. Just two. Every six months, everything coalesced into an important date. All stakeholders would wrap up all of their hopes and dreams, and the weight of those expectations threatened to crush the development team. </p> <p>Bugs could not be tolerated (because it would be another six months before the next release), so testing was laborious and stressful. Deployment to production required a lot of time and attention. Each deployment was a major event where things could go spectacularly right…or horribly wrong.</p> <p>More frequent releases and bug fixes make everyone’s lives easier. It helps everyone sleep at night. Each release has far less importance. No more sitting around, biting your nails, wondering if the next deployment will force you to work over the weekend. And DevOps can help you achieve more frequent releases.</p> <p>Sure, there will still be problems that pop up. But your team will be better prepared for them. Putting out fires should be an occasional emergency and not a feared expectation. Developers shouldn’t be sitting around, sleep-deprived, looking as if they are being hunted by wolves.</p> <p>Additionally, developers are less likely to have desperate stakeholders breathing down their necks. If that new feature isn’t in the next release, it’s not that big of a deal if it’s pushed to the next one. This promotes more goodwill, and everyone has a better chance of getting along.</p> <h2>DevOps empowers product owners</h2> <p><a href="https://climbtheladder.com/product-owner/">Product owners</a> and other stakeholders can be more involved and provide continuous feedback during the development process. This is especially true if you have something that builds <a href="https://www.tugboat.qa/">pull request previews</a> as part of your DevOps setup. Transparency for every change, even for non-technical members of the team.</p> <p>When there is no mystery and every change can be viewed, product owners will be less likely to feel the urge to be more controlling. They don’t need to request constant demos because they can view a demo whenever they want. It’s built into the process.</p> <p>DevOps workflows free up mental energy for more productive research and conversations, which leads to better requirements and faster validation of those requirements. Everyone wins.</p> <h2>Conclusion</h2> <p>Besides saving time and money, DevOps can strengthen your team. It’s a benefit that is harder to measure but one that still pays dividends. The great thing about DevOps is that it can be implemented progressively. You don’t have to do everything at once.</p> <p>Start small. Get accustomed to it. Let your team see and feel the benefits. Then move on to the next improvement. Pretty soon, your organization will wonder how they ever lived without it.</p> <p>If you need help getting started,<a href="https://www.lullabot.com/contact"> contact us</a>. We’ve helped development teams large and small become more efficient and productive, all while having more fun.</p> Wed, 30 Mar 2022 10:54:58 -0400 Matt Robison cd4b7b71-f8df-4bf3-a6fa-f1660e6347e9 What to Do With Your Drupal 7 Website https://www.lullabot.com/articles/what-do-your-drupal-7-website <p><a href="https://www.drupal.org/psa-2022-02-23">Drupal 7's EOL has been extended a year and is now November 1, 2023</a>. And it might live even longer than that. By July 2023, the security team will announce whether they will extend Drupal 7's support an additional year.</p> <p>If you are still on Drupal 7, you have some more breathing room. Time to catch your breath a bit. But don't use this extra year to lounge about and twiddle your thumbs. You have some important decisions to make, and once you make them, you need enough time to execute those decisions.</p> <p>There are two questions you should start asking.</p> <h2>Am I going to stay on Drupal 7?</h2> <p>If the answer to this question is "yes," there are some things you should know.</p> <p>Drupal 7 is over ten years old. While a decade doesn't seem like a long time when you compare it to the age of the universe, ten years is a long time for software to stick around. In dog years, it's 70 years old, and in software years (something we just made up), it's even older than that.</p> <p>Drupal 7 has served us well, but like all good things, it must come to an end. It might be next year. It might be three years from now. But it <em>will</em> end.</p> <p>And in the meantime, <strong>anyone still on Drupal 7 is paying a high opportunity cost</strong>. You are missing out on the new features and performance enhancements you would get with more recent technology. Not just with Drupal, but with your entire web stack. </p> <p>For example, Drupal 7 depends upon several JQuery libraries:</p> <ul><li>jQuery</li> <li>jQuery UI</li> <li>jQuery Forms</li> </ul><p>Out of the box, Drupal 7 comes with versions of these libraries that are obsolete and unsupported. The current version of jQuery is 3.6.x. Drupal 7 comes with 1.4.2. The other libraries are in similar situations. </p> <p>You can mitigate this a little bit with the <a href="https://www.drupal.org/project/jquery_update">jQuery Update module</a>, but the highest version you will get is 2.1.x.</p> <p>Drupal 8 and above (and many other content management systems) have easy ways to enable API access to your content. In the age of <a href="https://www.lullabot.com/articles/understanding-create-once-publish-everywhere-cope">"publish</a><a href="https://www.lullabot.com/articles/understanding-create-once-publish-everywhere-cope"> everywhere</a>," this is important functionality. Drupal 7 has some simple, rudimentary API support, but if you want a full-fledged API with <em>write</em> support, you'll need to create it yourself, which adds additional technical debt and potential vulnerabilities.</p> <p><strong>So are you going to stay on Drupal 7? The answer, at some point, will need to be a resounding</strong><strong> </strong><strong>"no."</strong><strong> </strong>Drupal 7 lives, but it lives only to die another day.</p> <p>You might try to ride Drupal 7 off into the sunset, trusting your noble steed for one more adventure. The problem is that your steed is old and might collapse at any moment, and now the sun is down, and it will be much harder to secure a new one.</p> <p>Better to let Drupal 7 retire while the sun is still high and clear in the sky and you still have daylight to secure your next solution. Consider this EOL extension an extra hour of daylight to help you get your affairs in order.</p> <p>The cost of moving off of Drupal 7 might seem high, but it will be less than the cost of trying to recover from an undiscovered Drupal 7 security exploit after its final EOL. Or making sure it stays compatible with newer versions of PHP. Or making sure it stays up to date with new operating systems. Or making sure it still works with your database.</p> <p>In other words, the cost to simply "keep the lights on" goes up with every passing day.</p> <h2>What are my options?</h2> <p>You have decided you need to move off of Drupal 7. Maybe not today, and maybe not tomorrow. But someday.</p> <p>What are your options? There are three.</p> <h3><strong>Migrate to Drupal 9</strong></h3> <p>This is the first option to consider. There are a lot of good tools you can use to help you migrate to the latest version of Drupal. And there is <a href="https://www.lullabot.com/articles/preparing-your-drupal-7-site-eventual-migration">a lot you can do to prepare your Drupal 7 website for that eventual migration</a>.</p> <p>Drupal 9 and above will give you the latest and greatest that Drupal has to offer. You'll have a modern CMS that measures up to any other option you could choose.</p> <ul><li><a href="https://www.lullabot.com/articles/improve-editorial-experience-type-tray-and-page-templates">Modern content authoring</a></li> <li><a href="https://www.lullabot.com/articles/decoupled-drupal-getting-started-gatsby-and-jsonapi">JSON API out of the box</a></li> <li><a href="https://www.lullabot.com/articles/speed-drupals-front-end-webp-images">Improved </a><a href="https://www.lullabot.com/articles/speed-drupals-front-end-webp-images">front-end </a><a href="https://www.lullabot.com/articles/speed-drupals-front-end-webp-images">performance</a></li> <li>Integrated media library</li> <li><a href="https://www.lullabot.com/articles/write-better-code-typed-entity">Better methods of code organization</a></li> <li><a href="https://www.lullabot.com/articles/how-upgrade-drupal-8-drupal-9">More frequent minor releases and easier updates</a></li> <li>Robust and extensible system for handling translations</li> <li><a href="https://www.drupal.org/drupal-security-team">A global security team helping to protect your investment</a></li> <li>Easier hiring of talent</li> <li><a href="https://www.drupal.org/project/project_module">An extensive library of ready-to-use open source modules</a></li> </ul><p>And this is important to understand: You can skip Drupal 8 and jump straight to Drupal 9. No need for a bridge migration. </p> <p>However, you really are getting a completely new system. Ever since Drupal 8, new versions of Drupal have left Drupal 7 behind, and a lot of your institutional knowledge will not transfer one-to-one. </p> <p>There is <em>some</em> continuity. A lot of the vocabulary will still be the same. Entities, nodes, view modes, themes, modules, taxonomy, fields, etc. This might seem small, but it's a bigger deal than you might think. If your team is already comfortable communicating with Drupal's vocabulary, that is one less thing you have to worry about. <a href="https://www.lullabot.com/articles/naming-content-types-using-ubiquitous-language">Having a ubiquitous language makes everything easier</a>. If everyone is already well-practiced in using it, it's invaluable.</p> <p>But what if you have ten years of business logic built into Drupal 7? It represents thousands and thousands of hours and millions of dollars of total investment.</p> <p>What's more, your developer team knows Drupal 7 like the back of their collective hand. Bugs are fixed quickly, and new features are still being added. Most of your organization is happy with the way things are going.</p> <p>For Drupal 7 sites with a lot of custom functionality and institutional history, migration to Drupal 9+ is not a cheap endeavor, and it is not a fast endeavor. On top of that, your development team might have a steep learning curve to overcome.</p> <p>You can do a slower, more progressive migration one small piece at a time. This lets your team learn at their own pace while they help migrate your data and logic. But you might not have the team size to pull this off <em>and</em> maintain your current website. You also don't want to stall new development while you migrate over a long period of time.</p> <p>If this sounds similar to your situation, you might want to take a look at Backdrop.</p> <h3><strong>Upgrade</strong><strong> </strong><strong>to Backdrop</strong><strong> CMS</strong></h3> <p><a href="https://backdropcms.org/">Backdrop</a> is a fork of Drupal that started back in 2013 when a group of Drupal core developers (including Lullabot Nate Lampton) realized that Drupal 8 was going to require nearly <em>everything</em> to be rewritten. Backdrop is backward compatible with Drupal 7 while continuing to provide similar features.</p> <p>Some of the similarities between Backdrop and Drupal include:</p> <ul><li>Updated libraries and dependencies</li> <li>Support for the latest versions of PHP, MySQL, and MariaDB</li> <li>A regular feature release cycle (Backdrop releases new features three times a year, compared to Drupal's twice a year).</li> <li>Configuration management (the ability to deploy configuration separate from content)</li> <li>Views module for creating listings</li> <li>A new Layout system for assembling complex pages</li> <li>Many modules you used to install separately are now included</li> </ul><p>However, Backdrop differs from Drupal's approach in several substantial ways:</p> <ul><li>Backdrop does not use Composer and limits its external dependencies.</li> <li>Backdrop provides an <em>upgrade path</em> (not a migration) from Drupal 7.</li> <li>Most APIs are compatible with Drupal 7. Modules usually take hours to upgrade rather than needing to be rewritten.</li> <li>Backdrop has a much longer major release cycle. Backdrop 1.0 came out in 2015, and the 1.x branch expects to be supported through 2027.</li> </ul><p>Backdrop, in many ways, acts as a "Drupal Classic" that keeps the same basic approaches used in earlier versions of Drupal. And at the same time, it's updated with new features regularly. The Backdrop community estimates that the functionality of <a href="https://docs.backdropcms.org/documentation/features-added-to-core">over 70 contrib modules ha</a><a href="https://docs.backdropcms.org/documentation/features-added-to-core">s</a><a href="https://docs.backdropcms.org/documentation/features-added-to-core"> been built into the core software</a>. Its security team coordinates with Drupal's security team, and it has support from hosting providers like Pantheon and Platform.sh. Your development team will still be able to use Lando and DDEV for their local development.</p> <p>Backdrop can provide the most value to sites that have invested heavily in Drupal 7, especially those with a lot of custom development. Stanford University recently upgraded from Drupal 7 to Backdrop CMS for both their off-campus learning program (<a href="https://solo.stanford.edu/">solo.stan</a><a href="https://solo.stanford.edu/">f</a><a href="https://solo.stanford.edu/">ord.edu</a>) and internal research funding program (<a href="https://seedfunding.stanford.edu">seedfunding.stanford.edu</a>), two sites that had extensive custom code. They estimated that <a href="https://atendesigngroup.com/articles/making-case-drupal-7-backdrop-cms-upgrade">upgrading to Backdrop was one-fifth the cost of rewriting their code for Drupal 9</a> — the equivalent of $400,000 in savings for their project.</p> <p>If you've invested heavily in Drupal, but the upgrade to Drupal 9 or higher looks daunting, expensive, or complicated, Backdrop is worth adding to your bucket of options. It has some downsides. There is a smaller community of developers contributing to it, and there is no large enterprise like Acquia advocating for and supporting it. But for the right situation, it might be the puzzle piece you've been looking for.</p> <h3><strong>Choose a different ecosystem altogether</strong></h3> <p>If migration is required to move to Drupal 9+ and its underlying guts make it a different CMS anyway, why not take the opportunity to migrate to a different CMS altogether?</p> <p>If you have the budget and the wherewithal, this is a viable option. There are many enterprise-ready CMSs that can help you meet your goals. Each has its own pros and cons, which is beyond the scope of this article.</p> <p>But there are several things you need to clarify:</p> <ul><li>If you want to stick with PHP to match your team's current expertise, you don't have many options. Drupal's biggest competitors, like Sitecore and Adobe AEM, use .NET or Java. Are you ready to retrain and/or hire new developers?</li> <li>You might think of switching to a content delivery platform like <a href="https://www.lullabot.com/articles/decoupled-platforms-contentful-vs-drupal">Contentful</a>, Bloomreach, or Magnolia. This way, you can still leverage some of your team's strengths when writing your front-end consumers. But making <a href="https://www.lullabot.com/articles/the-hidden-costs-of-decoupling">the jump to a decoupled architecture is a big undertaking</a>, even in the best of times.</li> <li>Do you want to switch because of a genuine business need? Or do <a href="https://www.lullabot.com/articles/avoiding-drupal-disillusionment-drupal-long-term">you just feel disillusioned with Drupal</a>? Don't make a platform change for the wrong reasons. Often, you will still need to compromise with the new platform you move to. They might be different compromises, and you may prefer those compromises to the ones you made for Drupal, but be sure you aren't pretending that the grass is perfectly green on the other side.</li> </ul><p>Drupal 9 and above might have completely different guts than Drupal 7, but it is still a true spiritual successor. As already mentioned, it shares the same vocabulary and the same overall paradigms. The high-level mental models your team has about your CMS will still overlay nicely on top of Drupal 9, even if those models might not be accurate as you zoom in on the details.</p> <p><strong>This is no minor thing. It means much of your team's deep, institutional experience will still be useful.</strong> Instead of building a new house from scratch, you get to start with most of the foundation and framing already intact. Instead of starting the race at the beginning, you get a 30-second head start.</p> <p>For this reason, among many others, it's better to remain in the Drupal ecosystem.</p> <h2>Conclusion</h2> <p>You are going to need to get off of Drupal 7. Eventually. Remaining on software that is over ten years old has a high opportunity cost, and when it reaches EOL, the likelihood and cost of an undiscovered vulnerability begin to rise quickly.</p> <p>You have many options available and an extra year to decide and plan for one of those options. The best choice depends on your team's experience, how much business logic you have baked into Drupal 7, and your potential budget.</p> <p>We have helped plan and execute mission-critical migrations from Drupal 7 to Drupal 8 and above, and we have done it for some of the largest websites (and networks of websites) in the world. Our experience covers <a href="https://www.lullabot.com/our-work/govhub-building-georgias-digital-future">state government</a>, <a href="https://www.lullabot.com/our-work/georgia-public-broadcasting">public broadcasting</a>, <a href="https://www.lullabot.com/our-work/nyu-school-of-medicine">healthcare</a>, and <a href="https://www.lullabot.com/our-work">more</a>. </p> <p>If you need help navigating your choices with Drupal 7, <a href="https://www.lullabot.com/contact">Lullabot can help</a>.</p> Wed, 23 Mar 2022 16:46:49 -0400 Matt Robison 5b3ba4e5-c871-4118-b38f-89fc9e43d0a6