<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Zoosk Engineering - Medium]]></title>
        <description><![CDATA[Anything and everything the Zoosk engineering team is up to. - Medium]]></description>
        <link>https://medium.com/zoosk-engineering?source=rss----95d50021e056---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Zoosk Engineering - Medium</title>
            <link>https://medium.com/zoosk-engineering?source=rss----95d50021e056---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 11 May 2026 16:49:41 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/zoosk-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Customizing Tooltips with the Power of Sass Mixins]]></title>
            <link>https://medium.com/zoosk-engineering/customizing-tooltips-with-the-power-of-sass-mixins-78b6afb5bd8c?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/78b6afb5bd8c</guid>
            <category><![CDATA[css]]></category>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[tooltips]]></category>
            <category><![CDATA[sass-mixin]]></category>
            <category><![CDATA[sass]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Wed, 11 Mar 2020 23:17:59 GMT</pubDate>
            <atom:updated>2020-03-11T23:17:59.815Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="A collage of different tooltip designs" src="https://cdn-images-1.medium.com/max/1024/1*3gJ2VKho0yW4vEovAMtrjg.png" /></figure><p>At Zoosk we have a UI component called a tooltip. It resembles a speech bubble and provides helpful information to our users. It appears when you either click on, tap on, or hover over another element.</p><p>We have many tooltips on our desktop and mobile websites, and they come in several variations. I discovered that each time I created a new one, I duplicated CSS properties that had been used for older tooltips. This inspired me to write some Sass mixins to speed up development and make it easier to customize our tooltip designs.</p><h3>First iteration</h3><p>Initially I came up with the following mixin:</p><pre>@mixin tooltip($bg, $border, $border-radius, $bottom, $box-shadow, $color, $font-size, $left, $padding, $right, $top) {<br>    background: $bg;<br>    border: $border;<br>    border-radius: $border-radius;<br>    bottom: $bottom;<br>    box-shadow: $box-shadow;<br>    color: $color;<br>    font-size: $font-size;<br>    left: $left;<br>    line-height: 1.5;<br>    padding: $padding;<br>    position: absolute;<br>    right: $right;<br>    top: $top;<br>}</pre><p>I wasn’t happy with this because it has too many parameters. It would be hard to keep them straight in my head, and I prefer tidier code.</p><h3>Second iteration</h3><p>I grouped similar properties into separate mixins. My hope was that mixins with fewer parameters would be easier to manage and digest. Below is the first one which handles a tooltip’s position.</p><pre>@mixin tooltip-position($top: false, $right: false, $bottom: false, $left: false) {<br>    position: absolute;</pre><pre>    @if $top != false {<br>        top: $top;<br>    }</pre><pre>    @if $right != false {<br>        right: $right;<br>    }</pre><pre>    @if $bottom != false {<br>        bottom: $bottom;<br>    }</pre><pre>    @if $left != false {<br>        left: $left;<br>    }</pre><pre>    @if $left == $right {<br>        margin-left: auto;<br>        margin-right: auto;<br>    }<br>}</pre><p>All parameters are optional so they aren’t compiled unless a value is specified.</p><p>With that done, I needed mixins to handle a tooltip’s visual aspects. For the pointer arrow I wanted to use a rotated ::after pseudo-element. <a href="https://www.tutorialspoint.com/css/css_pseudo_elements.htm">Pseudo-elements</a> allow you to style special parts of DOM elements. ::after is a great candidate here because it can be styled much like a span. The following snippet shows the mixins for creating the pointer and other tooltip styles.</p><pre>// CREATE THE POINTER<br>@mixin pointer-base-styles {<br>    &amp;::after {<br>        content: &quot;&quot;;<br>        position: absolute;<br>        transform: rotate(45deg);<br>    }<br>}</pre><pre>// BACKGROUND COLOR FOR THE TOOLTIP AND POINTER ARROW<br>@mixin tooltip-backgrounds($bg) {<br>    &amp;, &amp;::after {<br>        background: $bg;<br>    }<br>}</pre><pre>// FONT STYLES<br>@mixin tooltip-text($color: false, $font-size: false, $line-height: false) {<br>    color: $color;<br>    font-size: $font-size;<br>    line-height: $line-height;<br>}</pre><pre>// OTHER VISUAL TREATMENTS<br>@mixin tooltip-framing($padding: false, $border-radius: false, $shadow: false) {<br>    @if $padding != false {<br>        padding: $padding;<br>    } </pre><pre>    @if $border-radius != false {<br>        border-radius: $border-radius;<br>    }</pre><pre>    @if $shadow != false {<br>        box-shadow: $shadow;<br>    }<br>}</pre><p>So far so good. These mixins would be added to my tooltip’s CSS declaration block via @include.</p><h3>Borders for the tooltip and pointer</h3><p>You might have noticed that I haven’t addressed border styles. This was intentional; borders on the pointer affect how the pointer is positioned on a tooltip’s edge. Therefore, I needed a mixin that handles both the pointer’s position and any borders.</p><p>Before I get to that, I’d like to demonstrate how a pointer’s borders affect its position. Let’s say I have a tooltip design in which the pointer points left. I can use position: absolute; and left on the pointer to move its center to the tooltip’s left edge. If there are no borders, I can do some simple math in the left value to achieve this position.</p><figure><img alt="A graphic of a tooltip with the CSS code that properly positions its pointer without borders" src="https://cdn-images-1.medium.com/max/1024/1*zgiGRNFvqnILfzYlocpfDQ.png" /><figcaption>Pointer Example 1: Pointer is 50% black so you can easily see its center. It lines up with the tooltip’s left edge.</figcaption></figure><p>If, however, there are borders, the code above won’t work because left doesn’t factor in the border width.</p><figure><img alt="A graphic of a tooltip with the CSS code that unsatisfactorily positions its pointer with borders" src="https://cdn-images-1.medium.com/max/1024/1*78ZzEz58yWwFsUCM62YrHQ.png" /><figcaption>Pointer Example 2: The pointer’s borders jut into the tooltip’s body.</figcaption></figure><p>To fix this, I must include the border’s width in the left calculation. In addition it’s best to apply the same border on the pointer’s internal sides but with transparent as the color. This trick makes the new left calculation work consistently regardless of border width.</p><figure><img alt="A graphic of a tooltip with the CSS code that properly positions its pointer with borders" src="https://cdn-images-1.medium.com/max/1024/1*xxDIWTmF-77kYoL6900AwA.png" /><figcaption>Pointer Example 3</figcaption></figure><p>This is better, but while testing the code, I found a visual issue if the design required a drop shadow. Any shadow would be applied to both the tooltip and pointer. Because the pointer is above the tooltip in the DOM stacking order, the pointer’s shadow unfortunately appeared over the tooltip.</p><figure><img alt="A graphic of a tooltip with its CSS code demonstrating how a shadow on the pointer appears undesirably on the tooltip" src="https://cdn-images-1.medium.com/max/1024/1*WeqftanYKBXo6R2QVd65fQ.png" /><figcaption>Pointer Example 4</figcaption></figure><p>Because of this I decided to try the ::before pseudo-element to render the pointer’s shadow. Like its counterpart ::after, ::before can function as a separate element. I can manipulate its stacking order in the DOM with the goal of positioning it under the tooltip. With this in mind I started working on the next mixin.</p><h3>Positioning the pointer</h3><p>To position the pointer and its shadow (::after and ::before respectively), my next mixin must take into account the following factors:</p><ul><li>the tooltip side on which the pointer is aligned (left, right, top, or bottom)</li><li>the pointer’s size</li><li>the pointer’s border width</li></ul><p>After much tinkering I came up with the mixin below. It accepts an additional parameter cross-position that moves the pointer and shadow along the tooltip side. Visual examples of pointer positions follow the snippet.</p><pre>@mixin pointer-position($size, $side, $cross-position, $border-width) {<br>    $half-size: $size / 2;</pre><pre>    height: $size;<br>    width: $size;<br><br>    // MOVE PSEUDO-ELEMENTS OUTSIDE OF TOOLTIP BODY<br>    @if $border-width != 0<strong> </strong>{<br>        #{$side}: calc(#{-$half-size} - #{$border-width});<br>    } @else {<br>        #{$side}: -$half-size;<br>    }<br><br>    // MOVE PSEUDO-ELEMENTS ALONG TOOLTIP SIDE<br>    @if $side == &quot;left&quot; or $side == &quot;right&quot; {<br>        @if $cross-position == &quot;middle&quot; {<br>            top: calc(50% - (#{$half-size} + #{$border-width}));<br>        } @else {<br>            top: $cross-position;<br>        }<br>    } @elseif $side == &quot;bottom&quot; or $side == &quot;top&quot; {<br>        @if $cross-position == &quot;middle&quot; {<br>            left: 0;<br>            margin: auto;<br>            right: 0;<br>        } @else {<br>            left: $cross-position;<br>        }<br>    }<br>}</pre><figure><img alt="A graphic showing two tooltips, each with a pointer sitting on the bottom edge but with different cross positions" src="https://cdn-images-1.medium.com/max/1024/1*DhP995cGe_PS6on369_yOw.png" /><figcaption>Pointers sitting on the bottom tooltip side with different cross positions</figcaption></figure><h3>Styling the pseudo-elements</h3><p>Now I needed to apply visual treatments to the pseudo-elements. Since the pointer-position mixin takes an argument for side, I can leverage it within a new mixin to manage the pointer’s border display. How? I can rotate the ::after element differently based on the tooltip side. This will result in the pointer’s visible borders pointing in the correct direction. Below is the last mixin that does just that and also sets box-shadow.</p><pre>// ADD TO TOOLTIP&#39;S MAIN DIV DECLARATION BLOCK VIA @include<br>@mixin pointer-framing($size, $side, $cross-position, $border-width: 0, $border-style: false, $shadow: false) {<br>    &amp;::before {<br>        z-index: -1; // Puts the shadow behind the tooltip</pre><pre>        @if $shadow != false {<br>            box-shadow: $shadow;<br>        }<br>    }</pre><pre>    &amp;::after, &amp;::before {<br>        @include pointer-position($size, $side, $cross-position, $border-width);<br>    }</pre><pre>    @if $border-width != 0 {<br>        border: $border-width $border-style; // For the tooltip; including it in this mixin allows us to pass border arguments once for both tooltip and ::after.</pre><pre>        &amp;::after, &amp;::before {<br>            border: $border-width solid transparent;<br>        }</pre><pre>        &amp;::after {<br>            border-bottom: $border-width $border-style;<br>            border-left: $border-width $border-style;</pre><pre>            @if $side == &quot;right&quot; {<br>                transform: rotate(225deg);<br>            } @elseif $side == &quot;bottom&quot; {<br>                transform: rotate(-45deg);<br>            } @elseif $side == &quot;top&quot; {<br>                transform: rotate(135deg);<br>            }<br>        }<br>    }<br>}</pre><p>I noticed that $shadow is a parameter here and in the mixin tooltip-framing I made earlier. The $shadow argument would likely be the same for both the tooltip and ::before, so I removed it from pointer-framing and updated the @if $shadow block in tooltip-framing.</p><pre>@mixin tooltip-framing($padding: false, $border-radius: false, $shadow: false) {<br>    @if $padding != false {<br>        padding: $padding;<br>    } </pre><pre>    @if $border-radius != false {<br>        border-radius: $border-radius;<br>    }</pre><pre>    @if $shadow != false {<br>        &amp;, &amp;::before {<br>            box-shadow: $shadow;<br>        }<br>    }<br>}</pre><pre>@mixin pointer-framing($size, $side, $cross-position, $border-width: 0, $border-style: false, $shadow: false) {<br>    &amp;::before {<br>        z-index: -1; // Puts the shadow behind the tooltip<br>    }</pre><pre>    &amp;::after, &amp;::before {<br>        @include pointer-position($size, $side, $cross-position, $border-width);<br>    }</pre><pre>    @if $border-width != 0 {<br>        border: $border-width $border-style; // For the tooltip; including it in this mixin allows us to pass border arguments once for both tooltip and ::after.</pre><pre>        &amp;::after, &amp;::before {<br>            border: $border-width solid transparent;<br>        }</pre><pre>        &amp;::after {<br>            border-bottom: $border-width $border-style;<br>            border-left: $border-width $border-style;</pre><pre>            @if $side == &quot;right&quot; {<br>                transform: rotate(225deg);<br>            } @elseif $side == &quot;bottom&quot; {<br>                transform: rotate(-45deg);<br>            } @elseif $side == &quot;top&quot; {<br>                transform: rotate(135deg);<br>            }<br>        }<br>    }<br>}</pre><p>I also updated the pointer-base-styles mixin so it includes the ::before element.</p><pre>@mixin pointer-base-styles {<br>    &amp;::after, &amp;::before {<br>        content: &quot;&quot;;<br>        position: absolute;<br>        transform: rotate(45deg);<br>    }<br>}</pre><h3>Putting it all together</h3><p>Below is a sample snippet using the mixins plus a screenshot of the resulting UI. You can also see <a href="https://codepen.io/sueannaj/pen/EMGOZx">live tooltips</a> on CodePen. (Scroll down for an embedded demo.)</p><pre>$denim: #A5C5EB;<br>$pointer-size: 1.6rem;<br>$shadow: .2rem .2rem .2rem rgba(0, 0, 0, .2);</pre><pre>.tooltip--button-info {<br>    @include pointer-base-styles;<br>    @include pointer-framing($pointer-size, bottom, middle, .3rem, solid darken($denim, 12%));<br>    @include tooltip-backgrounds($denim);<br>    @include tooltip-framing(1em, .4rem, $shadow);<br>    @include tooltip-position(-7.5rem, 0, false, 0);<br>    @include tooltip-text(darken($denim, 50%), 1.5rem, 1.25);<br>    box-sizing: border-box;<br>    text-align: center;<br>    width: 90%;<br>}</pre><pre>&lt;div class=&quot;tooltip--button-info&quot;&gt;<br>    &lt;p&gt;Vote for the candidates by clicking a button.&lt;/p&gt;<br>&lt;/div&gt;</pre><figure><img alt="A screenshot of three buttons and a tooltip above them" src="https://cdn-images-1.medium.com/max/1024/1*vcchhQtEtQDvKjnVFWp7KA.png" /></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fsueannaj%2Fembed%2Fpreview%2FEMGOZx%3Fheight%3D600%26slug-hash%3DEMGOZx%26default-tabs%3Dcss%2Cresult%26host%3Dhttps%3A%2F%2Fcodepen.io&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fsueannaj%2Fpen%2FEMGOZx&amp;image=https%3A%2F%2Fscreenshot.codepen.io%2F95575.EMGOZx.small.92b210b0-9202-4bf4-b052-2f72dd05f692.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/69f7d5291b4b221fd8007cff77d56c63/href">https://medium.com/media/69f7d5291b4b221fd8007cff77d56c63/href</a></iframe><h3>But there’s a gotcha (and a workaround)</h3><p>If your main tooltip div has a shadow plus the transform property, the shadow on the ::before will overlap the tooltip.</p><figure><img alt="A screenshot of an alert tooltip in which its pointer’s shadow is overlapping the tooltip" src="https://cdn-images-1.medium.com/max/1024/1*Y01GPPmlvv6tMPSX7nbEOA.png" /></figure><p>This looks very much like figure “Pointer Example 4” from earlier. With the transform property, the div becomes the new stacking context for its pseudo-elements. Therefore, the ::before can’t fall behind the tooltip, even with z-index: -1. You can get around this by wrapping your tooltip’s child elements in a new div then applying the following changes:</p><ul><li>Move all tooltip and pointer mixins from the main div to the new wrapper div except for tooltip-position.</li><li>If the main div has width, move that to the new div as well.</li><li>Add position: relative; to the new div so it’s still the positioning context for the pseudo-elements.</li></ul><p>Below is an example of the workaround. My new div has the CSS class tooltip__body. Be aware that other scenarios can set your main div as a new stacking context, including position: fixed. Check out Mozilla’s <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">article</a> on stacking order for a list of these scenarios.</p><pre>$alert: #EACBE4;<br>$pointer-size: 1.6rem;<br>$shadow: .2rem .2rem .2rem rgba(0, 0, 0, .2);</pre><pre>.tooltip--alert {<br>    @include tooltip-position(-3.3rem, false, false, 0);<br>    transform: translateX(calc(-100% - #{$pointer-size}));<br>        <br>    .tooltip__body {<br>        @include pointer-base-styles;<br>        @include pointer-framing($pointer-size, right, 3.7rem);<br>        @include tooltip-backgrounds($alert);<br>        @include tooltip-framing(1em, .6rem, $shadow);<br>        @include tooltip-text(darken($alert, 50%), 1.5rem, 1.25);  <br>        position: relative;<br>        width: 25rem;<br>    }<br>}</pre><pre>&lt;div class=&quot;tooltip--alert tooltip&quot;&gt;<br>    &lt;div class=&quot;tooltip__body&quot;&gt;<br>        &lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/p&gt;<br>        &lt;p&gt;This might cause an allergic reaction.&lt;/p&gt;<br>    &lt;/div&gt;<br>&lt;/div&gt;</pre><figure><img alt="A screenshot of an alert tooltip in which its pointer’s shadow is no longer overlapping the tooltip" src="https://cdn-images-1.medium.com/max/1024/1*jhPKNPbi-UqVR6kTEb0qPQ.png" /></figure><h3>Last thoughts</h3><p>The mixins in this article allow you to customize your tooltips, but they also rely on a few assumptions:</p><ul><li>The light source for the drop shadow is the same for all your tooltips.</li><li>The pointer is square with no border-radius.</li><li>The portion of the pointer that sticks out past the tooltip’s edge is the same for all.</li></ul><p>You should, of course, make your own customizations as needed. If your tooltips share common styles (such as pointer size, padding, and rounded corners), you can declare those once under a base CSS class for all of them. If you work with designers, you should advocate for standardized designs. If it’s out of your hands, I hope these mixins will help you create manageable and tidier tooltip styles. Below is a <a href="https://gist.github.com/sueannaj/5730d2dbb57d9eb1776ca2b1ceae9bd2">GitHub gist</a> with all the code.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2980eacbf17ecbb3bb3c3cd6f8fe49d1/href">https://medium.com/media/2980eacbf17ecbb3bb3c3cd6f8fe49d1/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=78b6afb5bd8c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/customizing-tooltips-with-the-power-of-sass-mixins-78b6afb5bd8c">Customizing Tooltips with the Power of Sass Mixins</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[General guidelines for making an accessible web page]]></title>
            <link>https://medium.com/zoosk-engineering/general-guidelines-for-making-an-accessible-web-page-d12a4a718b7a?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/d12a4a718b7a</guid>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[web-accessibility]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Mon, 17 Feb 2020 07:48:09 GMT</pubDate>
            <atom:updated>2020-02-17T08:06:47.959Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="A graphic with a quote from Trenton Moss about web accessibility." src="https://cdn-images-1.medium.com/max/1024/1*Tyw-8EcQcl_8yr1kn9i6LA.jpeg" /></figure><p>I spent a few days watching <a href="https://www.udacity.com/course/web-accessibility--ud891">Udacity’s accessibility course by Google</a> to get a handle on basic accessibility techniques. It turns out that accessibility isn’t as daunting as it might sound. This article summarizes what I learned from this course. The goal is to teach web developers accessibility techniques when creating a new feature or product.</p><p>FYI Udacity’s course uses the following assistive technologies for its demos:</p><ul><li><a href="https://chrome.google.com/webstore/detail/chromevox-classic-extensi/kgejglhpjiefppelpmljglcjbhoiplfn?hl=en">ChromeVox</a></li><li>Mac OS VoiceOver</li></ul><p>Both of these tools are screen readers for users with sight impairments.</p><p>For additional reading on accessibility you can check out the following resources:</p><ul><li><a href="https://www.w3.org/WAI/standards-guidelines/wcag/">W3C’s Web Content Accessibility Guidelines (WCAG) Overview</a></li><li><a href="http://webaim.org/standards/wcag/checklist">WebAIM checklist</a></li></ul><h3>Create good page structure with correct use of elements and DOM order.</h3><p><strong>Use landmark elements.</strong></p><p>Landmark elements like header, nav, and footer are more useful to screen reader users because they define regions of a page. It’s best to use these instead of divs which have no native semantics.</p><p>Below is a graphic that labels several HTML5 landmark elements you can use to structure your pages. For more about these, check out <a href="https://classroom.udacity.com/courses/ud891/lessons/8079270944/concepts/81573813960923">Lesson 4 “Navigating Content,” Concept 10 “Landmarks”</a> in the Udacity course.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ulDN93ZlsvXwhg0e7dbUNQ.png" /></figure><p><strong>Use elements that are focusable.</strong></p><p>Right out of the box, native form elements, buttons, and anchors are focusable by keyboards, and keyboards can interact with them to provide input or perform an action. These elements are also automatically added to a page’s tab order. If you use non-focusable elements in place of these, you’ll have to add semantics and tabindex in your DOM as well as functionality so that keyboards can interact with them.</p><p><strong>Arrange your focusable elements in a logical tab order.</strong></p><p>The tab order of your elements will, and should, follow the order they appear in your DOM, so try to avoid using CSS that changes the visual order in which they appear. Otherwise, the focus styles might jump around the screen during tabbing. This will cause a confusing experience for mobility-challenged people using keyboards.</p><p>Below is an example that demonstrates this unexpected behavior. The “I’m first!” button will receive focus before the other two because it is first in the DOM.</p><figure><img alt="Code for three buttons and the resulting output in a browser, showing an unexpected focus order." src="https://cdn-images-1.medium.com/max/1024/1*xr74YHRA0AqXW7ic-IKxfg.png" /></figure><p><strong>Use heading tags in their logical order.</strong></p><p>Creating a logical page structure includes using heading tags (h1, h2, h3, h4, h5, and h6) properly. Don’t use a heading tag simply because of its style. Your heading tags should follow in their natural hierarchical order. This way a screen reader user will understand the subsections within a section. You can use CSS to style your headings as you like.</p><p><strong>Use anchor tags when appropriate and as intended.</strong></p><p>Since some assistive technologies allow users to browse through all links on a page, using anchor tags with an href attribute and a valid value will make them accessible to the user.</p><p>The following are common anti-patterns developers use to present actionable text on their web pages (They should be avoided.):</p><p>1. Using an anchor tag, or a span styled as a link, with scripting to perform an action when the element is clicked.</p><p>In this example the span and anchor are not actionable with a keyboard. Try using a button as an alternative.</p><figure><img alt="Screenshot of anti-pattern usage of span and anchor tags plus alternative solution of using a button." src="https://cdn-images-1.medium.com/max/1024/1*31Bb1V64aJ0ug4GZfWRDnw.png" /></figure><p>2. Using an anchor tag with a non-sensical href value plus scripting to perform an action.</p><p>In this example having a value of “#” for the anchor’s href attribute doesn’t make the anchor actionable by a keyboard. It’s best to replace the anchor with a button element.</p><figure><img alt="Screenshot of anti-pattern usage of an anchor tag plus alternative solution of using a button." src="https://cdn-images-1.medium.com/max/1024/1*u2G3h-JdF2dzQoAX0cDg1w.png" /></figure><p>3. Using an img element as link content.</p><figure><img alt="Screenshot of code in which a logo in a link’s content plus how to make the image accessible." src="https://cdn-images-1.medium.com/max/1024/1*uTt84_mpKg2B5IJ5LT8dZw.png" /></figure><p>The first example works for sighted users, but the image will not be available to assistive technologies. To fix it, add an alt attribute with a description of the image.</p><h3>Do not remove focus styles from focusable elements.</h3><p>A browser’s default focus styles are extremely important to users who use a keyboard to navigate a page. Focus styles visually indicate which interactive element is ready for input, and they also tell the user where he currently is in the page structure.</p><p>For many websites a browser’s default focus style doesn’t fit with the site’s design. Focus styles are easily customizable with CSS using the :focus pseudo-class. The course provides <a href="https://classroom.udacity.com/courses/ud891/lessons/8085130355/concepts/80914904050923">good tips on re-writing those styles</a>.</p><h3>Write helpful link text.</h3><p>Text that is hyperlinked should be written in a way that describes the purpose of the link. The following examples demonstrate this:</p><ul><li>This website uses cookies to ensure you get the best experience on our website. For more information, see our <a href="#">cookie policy</a>.</li><li><a href="#">Subscribe to our newsletter</a> to get weekly updates in your inbox.</li></ul><p>The following are examples of commonly used link strings that are considered an anti-pattern to accessibility (i.e. Don’t use these.):</p><ul><li>Learn more</li><li>Read more</li><li>Click here</li></ul><h3>Write helpful alt text for images.</h3><p>A screen reader will read aloud the alt text for an image, so the text needs to describe what’s going on in the image in its given context. If there is no alt attribute, the image will be handled in different ways depending on which screen reader is being using. According to <a href="https://www.oomphinc.com/notes/2019/01/images-alt-tags-out-loud-experience-oomph-inc/">Oomph’s accessibility article</a> (published January 25, 2019), JAWS will announce “blank,” and NVDA will announce nothing. VoiceOver will announce “unlabeled image.”</p><p>The screenshot below shows VoiceOver announcing and displaying the alt text associated with the teacup image.</p><figure><img alt="Sample web page showing the alt text of an image displayed on the screen." src="https://cdn-images-1.medium.com/max/1024/1*V5VSakYbF9drdPpYtD_xgQ.png" /></figure><p>If an image is deemed redundant, you can add an alt attribute to the image but with an empty value. This will remove the image from the screen reader’s accessibility DOM tree; therefore, the screen reader will not announce it.</p><h3>Make your site responsive, and use relative units.</h3><p>There are a number of reasons why developers should make their sites responsive with a mobile friendly layout. One of those reasons is that if a user zooms into a web page, the layout will at some point switch to the page’s mobile design. This is helpful because as the page content gets bigger, the mobile design will re-align the page sections for easier browsing and scrolling.</p><p>Relative units for font sizes and width, and in some cases margin and padding, are also very important. They allow the text size to increase if the user changes the browser’s font settings, and your page elements will scale accordingly. Below are examples of how two sites change with page zoom. The first one shows the W3C’s page adjusting to its mobile design where content is stacked. The next one shows that Wikipedia’s site does increase font size, but the layout doesn’t change. The welcome banner extends beyond the viewport, and the two-column layout, though still readable, is a little cramped.</p><figure><img alt="Animated gif showing how the W3C’s site responds to page zoom." src="https://cdn-images-1.medium.com/max/480/1*jqAJpcgW0qCOVCS758LV5A.gif" /></figure><figure><img alt="Animated gif showing how Wikipedia’s site responds to page zoom." src="https://cdn-images-1.medium.com/max/480/1*nFhJWuvFsoHLeWPRRxO61A.gif" /></figure><h3>Make sure your site meets the minimum contrast requirements.</h3><p>The WCAG requires that your text, images, and other components provide a minimum amount of contrast against your chosen background color. You can test your UI’s contrast at <a href="https://webaim.org/resources/contrastchecker/">WebAIM’s Contrast Checker</a>.</p><h3>Do not convey information with color alone.</h3><p>Consider the form below. It does not display error messages if the user submits an invalid value. Instead, it uses color to indicate the error.</p><figure><img alt="A sample form in which an error is only indicated with a colored line." src="https://cdn-images-1.medium.com/max/1024/1*GA78J3QKPfDTcAWv2TEaKg.png" /></figure><p>In this example the phone number is invalid because it’s missing an area code. Both colorblind and screen reader users can have an issue with this. The red line alone does not sufficiently tell those users there is a problem. To remedy this, you should add text that explains the error.</p><figure><img alt="A sample form in which an error is indicated with a colored line and error text." src="https://cdn-images-1.medium.com/max/1024/1*4w_F_gy-Hi_N8IpoQwiumg.png" /></figure><p>You can use the <a href="https://chrome.google.com/webstore/detail/nocoffee/jjeeggmbnhckmgdhmgdckeigabjfbddl?hl=en-US">NoCoffee Chrome extension</a> to simulate different visual impairments and determine how you can improve your UI.</p><h3>Use the right role and ARIA attributes on elements without native semantics.</h3><p>In some cases you might need to forgo the use of focusable elements (such as input, select, or button) and instead use divs or spans to create new stylized components. The first thing to do is assign a role to the component’s main element. The WAI-ARIA spec lists <a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions">role descriptions</a> that you can browse and determine which best suits your component.</p><p>If you click on any of the listed roles, you’ll find more information and a table of associated ARIA attributes, some of which are required. It’s best to watch <a href="https://classroom.udacity.com/courses/ud891/lessons/8311490720/concepts/83062506640923">Lesson 5 “ARIA”</a> to get a general grasp of this topic. In addition to adding role and ARIA attributes, you might need to programmatically add keyboard functionality to your component. Adding role and ARIA attributes alone does not make a component fully accessible.</p><figure><img alt="Screenshot of the WAI-ARIA’s list of role definitions." src="https://cdn-images-1.medium.com/max/1024/1*6BxqKwfZ8p3PQcKkZBEQ2Q.png" /></figure><figure><img alt="Screenshot of the WCAG’s detailed information on the checkbox role." src="https://cdn-images-1.medium.com/max/1024/1*_27L82SIry1P9IHksYROeA.png" /></figure><p><strong>Helpful ARIA resources:</strong></p><ul><li><a href="https://www.w3.org/TR/wai-aria-1.1/">ARIA 1.1 spec</a></li><li><a href="https://www.w3.org/TR/wai-aria-1.1/#roles">ARIA 1.1 roles</a></li><li><a href="https://www.w3.org/TR/wai-aria-practices-1.1/">ARIA 1.1 practices guide</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">ARIA — Accessibility | MDN</a></li></ul><h3>Wrap up</h3><p>In summary there are two overarching lessons in accessibility:</p><ul><li>You can achieve accessibility on your site by using standard web development practices that you already know.</li><li>If you can’t use natively focusable elements, you must use the role attribute plus any required ARIA attributes and extra functionality to make your custom component accessible.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d12a4a718b7a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/general-guidelines-for-making-an-accessible-web-page-d12a4a718b7a">General guidelines for making an accessible web page</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[To em or not to em? That is the media query question.]]></title>
            <link>https://medium.com/zoosk-engineering/to-em-or-not-to-em-that-is-the-media-query-question-22f4a65e9747?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/22f4a65e9747</guid>
            <category><![CDATA[media-queries]]></category>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[responsive-design]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Sat, 16 Nov 2019 02:01:03 GMT</pubDate>
            <atom:updated>2019-11-16T02:01:03.842Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kcY9BtLE1ErfaHm4hwOyCg.png" /></figure><p>There’s a lot of debate on whether to use em or px for breakpoint units in media queries. A few years ago Luke Wroblewski demoed his site at An Event Apart using em breakpoints. When he increased the browser’s font size, the page morphed into a layout that better accommodated the larger text. The audience oohhed and ahhed, and I was sold on em-based breakpoints.</p><p>In Luke’s demo the page actually switched to its mobile design. If the breakpoints had been pixel based, the layout would not have changed to its mobile design. However, the text size, which was defined with a responsive font-size unit, still would have increased. This could have resulted in cramped text if he had used pixels for size, margins, and padding values.</p><p>Recently I wondered about the applicability of em-based breakpoints — are they the right solution for all designs? Are there any disadvantages? To answer these questions for my day-to-day work I needed to understand the mechanics of em-based breakpoints as well as the direct visual impacts they have. So I dug deeper into the topic with my own testing. After the investigations, I was most intrigued by the fact that a browser’s font size setting affects the calculated value of an em breakpoint.</p><h4>em- vs. pixel-based breakpoints</h4><p>Below is an example in which .banner (let’s assume it’s a div) is full width by default then gets a fixed width on viewports at least 1024 pixels wide. (Please note that all code snippets here are written in SCSS syntax.)</p><pre>.banner {<br>    font-size: 1em; // 16px<br>    padding: 16px;</pre><pre>    @media (min-width: 1024px) {<br>        margin-left: auto;<br>        margin-right: auto;<br>        width: 900px;<br>    }<br>}</pre><p>Now look at this similar example with an em breakpoint:</p><pre>.banner {<br>    font-size: 1em;<br>    padding: 16px;</pre><pre>    @media (min-width: 64em) {<br>        margin-left: auto;<br>        margin-right: auto;<br>        width: 900px;<br>    }<br>}</pre><p>We’ll assume the browser’s default font size is 16px. (This is the case for most of them.) Just like the first example this media query will kick in at 1024-wide viewports (16px * 64em), but if a user increases his default font size in the browser settings, the calculated breakpoint value will also increase. For example, if the user changes the font size to 24px, the breakpoint will be 1536px. What this means is that the media query styles won’t apply in viewports under 1536 pixels wide. In this scenario someone on a tablet or laptop might see the mobile layout if the newly calculated breakpoint value is larger than his viewport width.</p><p>This might be desirable because when text size is increased, it sometimes needs larger containers to gracefully flow on the page. Since mobile layouts are often designed more simply, sometimes in a full-width stacked layout, this would give text more horizontal space.</p><h4>What if you don’t want mobile layouts on large screens?</h4><p>So what if you find that your mobile layout isn’t optimal on desktop screens? Consider the following mobile design:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WXSn9dBZfwRYst4WNhz85A.png" /></figure><p>Let’s say we want the white boxes to appear in a single row on screens at least 1024 pixels wide:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zYoP-bNZL-jhV8QoayV6cQ.png" /></figure><p>Here’s the initial code for this example using an em-based breakpoint:</p><pre>.product-item {<br>    font-size: 1em;<br>    padding: 16px;</pre><pre>    @media (min-width: 64em) {<br>        width: 272px;</pre><pre>        .product-list &amp; {<br>            display: flex;<br>            justify-content: center;<br>         }<br>     }<br>}</pre><p>Let’s say a user has a laptop with a screen width of 1440 pixels, and he’s using Chrome. With the em-based media query if he increased the font size to 24px, the page would look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kmkQ4J_cAGL68y38KjmQZA.png" /></figure><p>The page displays the mobile styles because the calculated breakpoint is now 1536px, and the user’s screen maxes out at 1440. This doesn’t look terrible, but there’s now a lot of empty space in the white boxes because each is full width. There are two techniques you can try in tandem to achieve balance in a responsive desktop layout:</p><ul><li>using px units for your breakpoints</li><li>using responsive units for element widths and spacing</li></ul><p>Let’s first convert the breakpoint to pixel units. With this change the following are true when the user increases his browser’s font size:</p><ul><li>The padding stays at 16px.</li><li>The banner’s width stays at 272px.</li><li>The calculated breakpoint value remains the same.</li></ul><p>You can view a <a href="https://codepen.io/sueannaj/pen/wNQaGw">live demo on CodePen</a> and try out the media queries. Below is a screenshot with the results so far.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gpFHF8ZKqO7ZbMA7gJ_v3w.png" /></figure><p>As you can see, because the product-item&#39;s width doesn’t change, any text will potentially look cramped as its size is increased. Let’s now convert the padding and width to em units:</p><pre>.product-item {<br>    font-size: 1em;<br>    padding: 1em;</pre><pre>    @media (min-width: 1024px) {<br>        width: 17em; // 272px / 16px * 1em</pre><pre>        .product-list &amp; {<br>            display: flex;<br>            justify-content: center;<br>         }<br>    }<br>}</pre><p>The padding and width will scale up as the font-size increases. The text looks more comfortable within each box.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-_xLDHwuT2WQmcjgT-J4-g.png" /></figure><h4>So is that the solution?</h4><p>No, not necessarily. The hybrid responsive layout is not the smoking gun solution for everything. In Chrome a user can set the font size as high as 72px or even larger if he uses a third-party CSS extension. Below is a screenshot of our demo set to 72px with the hybrid responsive layout. I only included a portion of the page since it was very, very tall.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KnSU0vAKMBVabh8clbiaWA.png" /></figure><p>Pretty terrible. In the end the purpose of this investigation is to understand how em-based breakpoints affect layout. For me the key lessons and considerations are:</p><ul><li>Test your layouts in a range of font sizes.</li><li>Determine if your mobile layout on bigger screens degrades the user experience.</li><li>If it does and you try a hybrid responsive approach, how does the layout stand up to very large font sizes? Does it still provide a decent experience?</li></ul><p>In my opinion most page layouts are complex enough that it’s best to be safe and use em breakpoints in your media queries in addition to responsive units for font sizes, dimensions, margins, and padding. This decision is ultimately an individual one, but be sure to test and consider your audience.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=22f4a65e9747" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/to-em-or-not-to-em-that-is-the-media-query-question-22f4a65e9747">To em or not to em? That is the media query question.</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Witchy World of Inputs]]></title>
            <link>https://medium.com/zoosk-engineering/the-witchy-world-of-inputs-329c093b5a24?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/329c093b5a24</guid>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[cross-browser-testing]]></category>
            <category><![CDATA[input-field]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Sat, 16 Nov 2019 01:59:50 GMT</pubDate>
            <atom:updated>2020-02-22T23:21:45.310Z</atom:updated>
            <content:encoded><![CDATA[<h3>Standardizing input elements across browsers</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QuGsrsCEoD2GdHAJhKH5ew.png" /></figure><p>In the last 16 years I’ve never had a project that required custom styles for disabled inputs. I know this because when I worked on such a project last week I was pulling my hair out. Most of us in the business know that browsers like to render inputs in their own ways. My cross-browser testing showed that when you throw the disabled state into the mix, you can get a really big headache.</p><p>I created a <a href="https://codepen.io/sueannaj/pen/GzXVKx">CodePen demo of plain inputs</a> to get screenshots of these cross-browser differences. The only significant custom style I added was a larger font-size to make the placeholder and filled text (value) more readable.</p><figure><img alt="Native inputs with no styles" src="https://cdn-images-1.medium.com/max/700/1*_5OLlhv6ir88xF8a3-NQ5g.png" /></figure><p>As I iterated the changes for my real-life project, I discovered some of the funny things going on with inputs, and I used some browser-specific techniques to fix them, though not all required vendor prefixes. Before I get to those solutions, it’s helpful to see a <a href="https://codepen.io/sueannaj/pen/vbVNzm">second CodePen demo with some made-up style requirements</a>:</p><ul><li>Enabled placeholder text color: cornflowerblue</li><li>Enabled input filled text color: purple</li><li>Disabled placeholder text color: cornflowerblue</li><li>Disabled filled text color: purple</li><li>Disabled input background: lightgray</li><li>All inputs: 1px solid black border</li></ul><p>For this demo I created the styles first in Chrome. I used what I consider CSS techniques that should work consistently in these browsers (but don’t). Below are the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*FLBqxArGZd_rtCfheOTCRA.png" /></figure><p>The browsers sort of rendered as expected, though at this point I purposefully didn’t include vendor-prefixed solutions for IE and Edge. Still, we have some slight variations. To provide a little insight on what’s going on, below are some observations I made when I worked on my real-life project. I also included snippets of the styles needed to meet the CodePen demo’s requirements on a per section basis.</p><h4>WebKit browsers</h4><p>In Chrome and Safari (desktop and iOS), Android Chrome, and Samsung Internet, setting color on input affects filled text for both enabled and disabled inputs.</p><h4>Chrome (desktop version 71 and mobile version 70 on Android 7.1) and Samsung Internet (version 8.2 on Android 7.1)</h4><ul><li>As expected you can change an input’s filled text color by customizing the color property on input. This will apply the color to both enabled and disabled filled text. If you want a different disabled filled text color, you have to set color on input:disabled.</li><li>As expected you can change the placeholder text color by customizing the color property on input::placeholder. This will apply the color to enabled and disabled placeholder text. If you want to a different color on disabled placeholder text, use input:disabled::placeholder.</li></ul><pre>// Styles needed to meet demo requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>}</pre><pre>input::placeholder {<br>    color: cornflowerblue;<br>}</pre><h4>Firefox (desktop version 65 and mobile version 65 on Android 7.1)</h4><ul><li>Applying a custom color value to input affects all placeholder and filled text (enabled and disabled fields). If you want a different color for your placeholder text, set the color property on input::placeholder.</li><li>Applying a custom color to input:disabled affects disabled placeholder and filled text.</li><li>FF applies 50% opacity to enabled and disabled placeholder text. To normalize those, add opacity: 1 to input::placeholder.</li></ul><pre>// Styles needed to meet demo requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>}</pre><pre>input::placeholder {<br>    color: cornflowerblue;<br>    opacity: 1;<br>}</pre><h4>Safari (desktop version 11.1)</h4><ul><li>Like Chrome, Safari will take a custom color on input and apply it to enabled and disabled filled text. Some color values, however, will render as a different hue in disabled inputs. For example, if you use purple, the disabled filled text will look more like fuchsia.</li><li>If you want the enabled and disabled filled text colors to match, assign your color to input. Then create a block with the input:disabled selector and use the -webkit-text-fill-color property with the same color. This will, however, apply the color to the disabled input’s placeholder text. If this is not desirable, you have two options: 1) use the -webkit-text-fill-color property on input::placeholder to change the color of all placeholder text or 2) use the -webkit-text-fill-color property on input:disabled::placeholder for specific targeting.</li></ul><pre>// Styles needed to meet demo requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>    -webkit-text-fill-color: purple; // Only needed if you want all filled text to have the same color and you happen to pick a color that doesn&#39;t render correctly in a disabled input. FYI This will also apply to disabled filled and placeholder text in Chrome.<br>}</pre><pre>input::placeholder {<br>    -webkit-text-fill-color: cornflowerblue; // Changes all placeholder color<br>}</pre><h4>Chrome and Safari (iOS 12)</h4><ul><li>Like their desktop counterparts these browsers will take a custom color on input and apply it to enabled and disabled filled text. They also have the same inconsistent hue issue noted above for desktop Safari. The solution is the same.</li><li>Disabled inputs have a 40% opacity. Setting opacity: 1 on input normalizes the opacity of the entire element including placeholder and filled text. You can also set opacity on just input:disabled for the same effect.</li></ul><pre>// Styles needed to meet demo requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>    -webkit-text-fill-color: purple;<br>    opacity: 1;<br>}</pre><pre>input::placeholder {<br>    -webkit-text-fill-color: cornflowerblue;<br>}</pre><h4>IE 11</h4><ul><li>Setting color on input:disabled applies the color to disabled placeholder and filled text.</li><li>Setting color on input applies the color to all filled and placeholder texts, enabled and disabled. You can use a variety of selectors to override this and customize your text colors:</li></ul><pre>input:-ms-input-placeholder {...}<br>input:disabled {...}<br>input:disabled:-ms-input-placeholder {...}<br>input:disabled:not(:-ms-input-placeholder) {...}</pre><pre>// Styles needed to meet requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>}</pre><pre>input:-ms-input-placeholder {<br>    color: cornflowerblue; <br>}</pre><h4>Edge 17</h4><p>If you want to customize placeholder text, Edge requires the input::-webkit-input-placeholder selector.</p><pre>// Styles needed to meet requirements</pre><pre>input {<br>    border: 1px solid black;<br>    color: purple;<br>}</pre><pre>input:disabled {<br>    background: lightgray;<br>}</pre><pre>input::-webkit-input-placeholder {<br>    color: cornflowerblue; <br>}</pre><h4>Closing</h4><p>I have a <a href="https://codepen.io/sueannaj/pen/aXKgEB">third CodePen demo</a> with all these styles consolidated. If you view it in the browsers mentioned here, you’ll see much more consistency. You don’t have to use all these workarounds; it depends on what your requirements are. Also, if you need to change Chrome’s pre-filled input styles, check out <a href="http://webagility.com/posts/remove-forced-yellow-input-background-in-chrome">Bobby Westberg’s solution</a>. Hopefully with all this information you’ll have an easy time customizing placeholder and filled texts for enabled and disabled inputs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=329c093b5a24" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/the-witchy-world-of-inputs-329c093b5a24">The Witchy World of Inputs</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Finally, A Responsive, Low-maintenance Interstitial]]></title>
            <link>https://medium.com/zoosk-engineering/finally-a-responsive-low-maintenance-interstitial-b9912a968bf?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/b9912a968bf</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[css]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Thu, 08 Nov 2018 17:39:09 GMT</pubDate>
            <atom:updated>2019-11-18T19:09:39.491Z</atom:updated>
            <content:encoded><![CDATA[<p>Update: As of 11/18/19 I found that Chrome 78 and Firefox 70 no longer render the padding-right: 100% on the interstitial__line pseudo elements. This caused the pseudo elements to have no width, but changing the percent unit to vw works. The code in this article and the CodePen demo have been updated.</p><p>At Zoosk we often use a pattern we call an interstitial. It’s a word or short phrase flanked on the left and right by a line, and it vertically separates two sections or options on a page. In the screenshot below, the phrase “or confirm with” separates two ways to confirm your email address on Zoosk.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Zn-lBvvgICP7Xs8kZFe1AQ.png" /></figure><p>For this pattern, the text and lines must not overlap even if the text is dynamic. (This is the case for us; Zoosk supports 27 languages.) There are a few solutions for this, and they essentially do the same thing — in a nutshell you can apply a top border to an element then position another element containing your text on top of it. These solutions require applying a background color to the text-containing element that matches its parent’s background color. This way the text element covers up the line behind it. This generally works pretty well. If, however, you want a background pattern, responsive interstitial lines, or a solution that requires a little less maintenance, we have an answer for that.</p><h3>The HTML markup</h3><p>The markup for the interstitial follows:</p><pre>&lt;div class=”interstitial”&gt;<br>    &lt;span class=”interstitial__line--left interstitial__line”&gt;&lt;/span&gt;<br>    &lt;span class=”interstitial__text”&gt;<br>        A responsive interstitial — The lines’ length will change based on the text. Also works on smaller screens.<br>    &lt;/span&gt;<br>    &lt;span class=”interstitial__line--right interstitial__line”&gt;&lt;/span&gt;<br>&lt;/div&gt;</pre><p>The main interstitial container serves to hide excess length from the lines, which will be explained later. The two interstitial__line spans render the left and right lines around the text. It’s best to use spans as the children elements within interstitial. Spans next to each other will move horizontally in response to content changes in other spans. This is key for adjusting the line widths with dynamic strings.</p><h3>The CSS (written in SCSS syntax)</h3><p>As mentioned the interstitial div hides the extra length of the lines when overflow is set to hidden. Optionally you can define max-width or width. A responsive interstitial requires a percent value. Since interstitials usually look best when centered on a page, we included auto left and right margins in our example below.</p><pre>.interstitial {<br>    overflow: hidden; // Hides the excess lines<br>    margin: 0 auto; // Optional<br>    max-width: 90%; // Optional<br>}</pre><p>The lines are rendered using :before pseudo elements on the interstitial__line spans.</p><pre>.interstitial__line {<br>    position: relative;</pre><pre>    &amp;:before {<br>        border-top: 6px double; // Customizable<br>        content: &#39;&#39;;<br>        height: 0;<br>        padding-right: 100vw;<br>        position: absolute;<br>        top: 50%; // Customizable but this is our default<br>    }</pre><pre>    &amp;--left { // Parent class of span that renders the left line<br>        &amp;:before {<br>            right: 10px; // The gap between the right end of the line and the text. Customizable.<br>        }<br>    }</pre><pre>    &amp;--right { // Parent class of span that renders the right line<br>        &amp;:before {<br>            left: 10px; // The gap between the left end of the line and the text. Customizable.<br>        }<br>    }<br>}</pre><p>Let’s first tackle the :before element styles. The right padding makes the pseudo element, and therefore the border, as long as the viewport’s width. (Left padding can be used instead of right; they both have the same effect.) This is why we set overflow: hidden on the interstitial div. Because we defined the right property on the interstitial__line--left span, any width changes will flow away from the text toward the left of the page. The same goes for the interstitial__line--right span; however, its width flows in the opposite direction because we set a left value on this element. These techniques ensure the text and lines don’t overlap. We can also use the left and right values to customize the gap between each line and the text.</p><p>The interstitial__text does not require many styles:</p><pre>.interstitial__text {<br>    display: inline-block;<br>    max-width: 60%; // Customizable. Max-width as a percent will make the text and lines responsive.<br>    vertical-align: middle;<br>}</pre><p>Responsive interstitials require max-width as a percent, and we set display to inline-block so this property will take effect on the span. A couple of warnings: Defining max-width in pixels, rems, or ems might cause the lines to disappear on small screens depending on your string length. Also, using width instead of max-width can cause unexpected large gaps around your text if your dynamic content is short. So use max-width if you want the lines to automatically and consistently adjust with your text.</p><p>If you insist on using a non-percent value for max-width, you’ll need to use media queries to change max-width at your desired breakpoints to keep the lines visible.</p><p>A <a href="https://codepen.io/sueannaj/pen/gBbpOw">working example</a> of this responsive interstitial is available on CodePen.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fsueannaj%2Fembed%2Fpreview%2FgBbpOw%3Fheight%3D600%26slug-hash%3DgBbpOw%26default-tabs%3Dcss%2Cresult%26host%3Dhttps%3A%2F%2Fcodepen.io%26embed-version%3D2&amp;url=https%3A%2F%2Fcodepen.io%2Fsueannaj%2Fpen%2FgBbpOw&amp;image=https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fi.cdpn.io%2F95575.gBbpOw.small.a7822553-de79-4db7-bde6-3adf4e3cc369.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/e97961f53c8806e20dd195a7a2e6b712/href">https://medium.com/media/e97961f53c8806e20dd195a7a2e6b712/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b9912a968bf" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/finally-a-responsive-low-maintenance-interstitial-b9912a968bf">Finally, A Responsive, Low-maintenance Interstitial</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Mixins You Need in Your SCSS Library]]></title>
            <link>https://medium.com/zoosk-engineering/mixins-you-need-in-your-scss-library-930daf85ddc6?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/930daf85ddc6</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[scss]]></category>
            <dc:creator><![CDATA[Sue Anna Joe]]></dc:creator>
            <pubDate>Tue, 11 Sep 2018 19:01:54 GMT</pubDate>
            <atom:updated>2018-09-12T17:22:48.813Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/889/1*dJgb3nWLow7cEhUyXI9N7w.png" /></figure><p>As a CSS author I’ve typed many property-value pairs over and over again in different declaration blocks, and it gets really tedious. Our CSS developers here will tell you the same thing. Luckily we use Sass (and more specifically the SCSS syntax) so we’re able to group repeated properties together and call them in any block we want. These groups are called mixins.</p><p>One simple example is setting an element’s width and centering it on the page:</p><pre>header {<br>    <em>margin-left</em>: <strong>auto</strong>;<br>    <em>margin-right</em>: <strong>auto</strong>;<br>    <em>width</em>: 500<strong>px</strong>;<br>}</pre><p>This is something we do a lot, so to make this implementation a bit quicker, we can put those properties into a mixin:</p><pre>@mixin limited-container(<em>$width</em>) {<br>    <em>margin-left</em>: <strong>auto</strong>;<br>    <em>margin-right</em>: <strong>auto</strong>;<br>    <em>width</em>: <em>$width</em>;<br>}</pre><p>Then we call the mixin in the original block, passing in a custom width value based on our needs:</p><pre>header {<br>    @include limited-container(500<strong>px</strong>);<br>}</pre><p>This will compile into the first example block shown above.</p><p>Over the years we’ve added many mixins to our in-house library. They’ve really sped up our work, and some of them are common and simple enough that we’ve put them together in a gist to share. Thanks to both Roger Flanagan and Amir Sattari for their contributions to this library and a special shoutout to Roger who is a Zoosk team lead and UI veteran responsible for many of the handy mixins we use every day.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a4d2bf1fc941804bb3b08b11766eb107/href">https://medium.com/media/a4d2bf1fc941804bb3b08b11766eb107/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=930daf85ddc6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/mixins-you-need-in-your-scss-library-930daf85ddc6">Mixins You Need in Your SCSS Library</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating Automation Lab to the Cloud (Part 2)]]></title>
            <link>https://medium.com/zoosk-engineering/migrating-automation-lab-to-the-cloud-part-2-543635cc699c?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/543635cc699c</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[zoosk]]></category>
            <category><![CDATA[engineering]]></category>
            <dc:creator><![CDATA[Sid Madipalli]]></dc:creator>
            <pubDate>Fri, 24 Aug 2018 20:57:23 GMT</pubDate>
            <atom:updated>2018-08-24T20:57:22.800Z</atom:updated>
            <content:encoded><![CDATA[<p>This is part 2 of a 2 part series, check <a href="https://medium.com/@sidm_zoosk/migrating-automation-lab-to-the-cloud-part-1-748b1e95a414">part1</a> if you haven’t already.</p><p>As part of the migration, we wanted to rewrite our test runner and architect it to improve overall speed and efficiency.</p><p>Test-Runner is the backbone of our automation cluster. It takes the list of tests and test machines available and runs them in parallel on the cluster.</p><p>We use Jenkins as our automation server. The cluster management and some part of test running are delegated to Jenkins and the test runner is always in sync with it while running Automation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZhEEbAKey3nTCDtnSnFN4Q.png" /><figcaption>Representation of Post Migration Automation Test Runner Architecture</figcaption></figure><p>We started with creating a Jenkins job to run a single test. It takes the test machine, test to run, and the TestRail id to report to as parameters and runs the test on an AWS Linux spot instance pointing to the test machine’s address. As soon as the test is done, Test Runner sends the result to TestRail server.</p><h3>Approach:</h3><p><strong>Step1 ( Test-Runner ):</strong></p><ul><li>Compiles a list of tests to run</li><li>Creates the list of test machines in the available cluster</li><li>Creates a new test plan for reporting/monitoring and get’s the plan ID</li><li>Starts the Job to create an AWS Linux spot instance cluster</li><li>Starts the poll for “Run Single Test” job availability</li></ul><p><strong>Step2 ( Test-Runner ):</strong></p><ul><li>Triggers a “Run Single Test” job with parameters as soon as poll finds availability and test machine is free to run the test</li><li>Runs several tests for each machine and pings for the status of each test</li><li>As soon as the test completes, reports back to TestRail</li><li>Does a single rerun if there are any environment issues for a test</li></ul><p><strong>Step3 ( Cleanup ):</strong></p><ul><li>Stop build if any of the tests stuck</li><li>Stop the Jenkins poll thread</li><li>Delete any files created in the process</li></ul><p>The major change with this new architecture is that the Test-Runner starts a thread to poll for “Run Single Test” job availability and starts a new test as soon as something is available.</p><p>This small change had a <strong>huge</strong> impact. Previously we used to start a bunch of jobs and recursively check for them to start and proceed with the next batch again. If there was an issue or if a single job took longer than the normal time to start, the Test-Runner was stuck waiting for it to finish.</p><p>In a similar fashion Test Runners polls for the status of the single test run and reports back to TestRail as soon as it done and closes that particular thread.</p><h3><strong>Conclusion:</strong></h3><ul><li>We gained a massive boost in the time taken to complete a test run with the new architecture, from 4.5hrs to 1.5 hrs</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*BRPXGXorxwmiyJaTrR-ZqA.png" /><figcaption>Time Taken: Automation Run before changes</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/412/1*ofzmgte3XtuCMQkY_xyQXw.png" /><figcaption>Time Taken: Automation Run after changes</figcaption></figure><ul><li>No more re-runs. Part of the 4.5hr job is because includes several re-runs to eliminate the platform issues which we no longer need.</li><li>Completely platform agnostic, we can finally run the code on Mac, Linux and Windows (We are actually using all three platforms — Linux for automatic runs, Windows and Mac for development.)</li><li>Removed the legacy code and upgraded the 3rd party libraries to the latest and greatest</li><li>Updated our <a href="https://github.com/zoosk/testrail-client">open source TestRail client library</a> and also merged a PR contribution.</li></ul><p>It was a fun project to work with and understand the underlying architecture of the Automation Infrastructure. Due to tight time constraints and an office move we had to quickly finish the project.</p><p>Now that we are done with the major milestone, here are the couple of things we want to focus on next:</p><ul><li>Some of the functional tests are still brittle, since they try to test all the possible scenarios. We are working towards making them reliable.</li><li>There are few edge cases with the new test runner we are trying to address</li><li>With this knowledge we want to experiment on building an isolated test environment with the VM ( Run the tests in a container like a service rather than on a separate VM)</li><li>Invest on building more tooling around automation (like a slack bot) and reduce the test run time to ~10 minutes to enable us to achieve continuous deployment.</li></ul><p>Are you creative and want to work on fun projects like this? If yes, come work with us at Zoosk. Check out our openings <a href="https://about.zoosk.com/en/careers/jobs/">here</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=543635cc699c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/migrating-automation-lab-to-the-cloud-part-2-543635cc699c">Migrating Automation Lab to the Cloud (Part 2)</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating Automation Lab to the cloud (Part 1)]]></title>
            <link>https://medium.com/zoosk-engineering/migrating-automation-lab-to-the-cloud-part-1-748b1e95a414?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/748b1e95a414</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[automation]]></category>
            <category><![CDATA[migration]]></category>
            <category><![CDATA[zoosk]]></category>
            <dc:creator><![CDATA[Sid Madipalli]]></dc:creator>
            <pubDate>Fri, 24 Aug 2018 20:57:11 GMT</pubDate>
            <atom:updated>2018-08-24T20:57:38.005Z</atom:updated>
            <content:encoded><![CDATA[<p>This is part 1 of a 2 part series on Migrating Automation Lab to the Cloud.</p><blockquote><strong>TL;DR:</strong> Our journey on migrating from .NET Framework to .NET Core. How we leveraged Jenkins to create an on-demand cloud cluster for automation runs and saved costs on hardware and time on maintenance.</blockquote><p>At Zoosk, we have a huge set of end-to-end tests that run a few times a day in our homegrown Automation Lab cluster. The whole infrastructure is based on windows VMs running on Hyper-V (on a monster Windows machine with 100GB RAM), with around 100 VMs running at any given point in time.</p><p>The goal was to move infrastructure to the cloud and make it cross-platform ( run on mac, linux and windows )</p><p>There were a couple of challenges associated with this:</p><ol><li>Port to .NET core</li><li>Fix the broken pieces</li><li>Build infrastructure</li></ol><h3><strong>Port to .NET core:</strong></h3><p>Microsoft has good documentation and tools to help to migrate from .NET Framework to .NET core smoothly. (check out the simple guide <a href="https://docs.microsoft.com/en-us/dotnet/core/porting/">here</a>)</p><p>Visual studio 2017 or later needed to work with .NET core cross-platform so our first step was to port our project from visual studio 2015.</p><p>Visual studio uses project files in the format .csproj and the fun part is VS2017’s format of .csproj changed from the VS2015 version. We started by using the one-way upgrade VS2017 provides but none of the project compiled and it has few hundred errors.</p><p>It’s a pain to look for the errors and fix them but the new format got us excited because it removed a lot of clutter from the project file and made it easier to read and understand</p><p>For example, all the embedded and compile resources inside a project can be removed, like the ones below:</p><pre>&lt;!-- the defaults --&gt;<br>&lt;Compile Include=&quot;**\*.cs&quot; /&gt;<br>&lt;EmbeddedResource Include=&quot;**\*.resx&quot; /&gt;</pre><p>And simplified project dependency definitions like these below:</p><pre>&lt;ProjectReference Include=&quot;..\Payment\Payment.csproj&quot;&gt;<br>  &lt;Project&gt;{1A2VA732-BN78-09OD-OPD9-5698AHD9OP02}&lt;/Project&gt;<br>  &lt;Name&gt;Payment&lt;/Name&gt;<br>&lt;/ProjectReference&gt;</pre><p>are replaced by:</p><pre>&lt;ProjectReference Include=&quot;..\Payment\Payment.csproj&quot; /&gt;</pre><h3><strong>Fix the broken pieces:</strong></h3><p>After fixing the .csproj files there were few more errors left before we can could actually compile and run on VS 2017 (i.e unsupported 3rd party libraries).</p><p>With .NET core completely written from scratch and focusing on cross-platform some of the libraries didn’t support .NET core yet. Because of this, we had to find alternatives and also rewrite the code to use the new library patterns.</p><p>Using the <a href="https://github.com/Microsoft/dotnet-apiport/">API Portability Analyzer tool</a> provided by Microsoft, we generated a report on the project ( see image below) to see how much effort was needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3d-dcNBNyBuaAJmGwFvgBw.png" /><figcaption>Report from the API Portability Analyzer tool</figcaption></figure><p>The changes were broken into two steps</p><ol><li>Replace/ find an alternative for the 3rd party libraries with .NET core support and rewrite</li><li>Strip the windows API dependency code in the automation framework and rewrite to support cross-platform</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PuRG6KyErpXD_uiEOdmHGA.png" /><figcaption>a sneak peak of 3rd party library analysis</figcaption></figure><p>Finding a replacement for the current XMPP implementation was the toughest. There isn’t any well-written documentation available for this and the only place we ended up was the RFC from Internet Engineering Task Force (IETF).</p><p>We also added our custom packets on top of the XMPP standard so re-writing that part took longer than what we budgeted for. Mail library was another piece that we had to do a heavy rewrite on.</p><p>After all the changes and rewrites were made to finally compile and run, there were about 4k lines added. But the cool part was we had a chance to remove the old and unused code as part of the migration and reduced the footprint by about 8k lines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W6bBRbsmoKEuI35zHT69Jw.png" /><figcaption>Final pull request after the code changes.</figcaption></figure><h3><strong>Build infrastructure:</strong></h3><p>And, the final piece of the puzzle… Setup Jobs to kick off automation on our nightly and master branches daily.</p><p>We use <a href="https://jenkins.io/">Jenkins</a> as our automation server and now that we could run the automation test suite on Linux, It was time to retire the Hyper-V hypervisor and use <a href="https://aws.amazon.com/ec2/spot/">AWS spot instances</a>.</p><p>Spot instances were a perfect fit for us, because they were up to 90% cheaper than the regular instances and were provisioned to use for few hours and destroy</p><p>We started by creating an AMI for the spot instances and launched them on demand using the <a href="https://wiki.jenkins.io/display/JENKINS/Amazon+EC2+Plugin">EC2 plugin</a> for Jenkins.</p><p>Installing Google Chrome for CentOS is not straightforward but I found a <a href="https://intoli.com/blog/installing-google-chrome-on-centos/">script</a> on a blog post by <a href="https://intoli.com">Intoli</a> to easily install Google Chrome on CentOS to run it headless, which we used to install while creating the AMI.</p><p>Once the configuration for the AMS details was set in Jenkins, it was a piece of cake to start instances with a pipeline script.</p><pre>parameters {<br>    string(name: &#39;count&#39;, <br>    defaultValue: 10, <br>    description: &#39;count of the instances to start&#39;)<br>}<br>node {<br>    for(i=1; i&lt;= &quot;${params.count}&quot;.toInteger() ; i++){<br>        string name = &quot;Creating cloud instance &quot;+i;<br>        stage(name) {<br>            ec2 cloud: &#39;EC2&#39;, template: &#39;your cloud template&#39;<br>        }<br>    }<br>}</pre><p>This pipeline script takes a count optional parameter and creates instances on demand on AWS, and copies the slave.jar file and launches it as a Jenkins slave computer.</p><p>By using the spot instances, we no longer needed blade runner with a cluster of VM’s.</p><p>That freed us from a lot of pressure points and improved our overall processes. A few of them are:</p><ul><li>No more occasional VMs going rogue and tests failing due to unknown config issues etc.</li><li>No more cleanup/maintenance of the VM needed</li><li>Updating the AMI reflects the changes to every instance launched after</li><li>Scalable — now that we have this infrastructure we can let developers run automation for their changes as needed.</li><li>Massive time improvements (more about this in part 2 )</li></ul><p>Shout out to <a href="https://medium.com/u/a177da51da36">Conor Callahan</a> for helping me out with a lot of dev-ops related work needed to make this possible.</p><blockquote>Part 2 goes into details of the test runner architecture.</blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=748b1e95a414" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/migrating-automation-lab-to-the-cloud-part-1-748b1e95a414">Migrating Automation Lab to the cloud (Part 1)</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refactoring at Scale with Abstract Syntax Trees]]></title>
            <link>https://medium.com/zoosk-engineering/refactoring-at-scale-with-abstract-syntax-trees-a3f989ec8524?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/a3f989ec8524</guid>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Robin Keller]]></dc:creator>
            <pubDate>Tue, 10 Jul 2018 00:08:36 GMT</pubDate>
            <atom:updated>2018-07-10T00:08:36.224Z</atom:updated>
            <content:encoded><![CDATA[<p>Working on an old codebase can be stifling at times. You have a great idea for a new pattern, but there’s just so much code that you can’t feasibly fix it all. Much of the time, the team agrees to leave the codebase today as it is and only apply the pattern to new additions. This fragments the codebase into the Way Things are Done and the Dark Land of Legacy, where senior coders groan and juniors dare not tread. The more ideas the team has, the more fragments there are, until the code is lost to darkness.</p><p>It doesn’t have to be that way, however. With the proper scripting, you can make changes to vast swaths of your codebase. That’s where abstract syntax trees, or ASTs, come in.</p><h3>What are ASTs?</h3><p>ASTs are data structures used for representing code. Specifically, they’re the way that your language’s compiler or interpreter represents it. They are regular tree data structures in which each node is a <em>kind of syntax.</em> For example, something like this:</p><pre>x = y + 2</pre><p>is represented as an AST like this:</p><pre>Assignment: &quot;=&quot;<br>  |- Identifier: &quot;x&quot;<br>  |- BinaryOperator: &quot;+&quot;<br>    |- Identifier: &quot;y&quot;<br>    |- NumericLiteral: &quot;2&quot;</pre><p>This might seem a bit verbose, but it has lots of advantages over just parsing the text yourself, including:</p><ol><li>No weird whitespace, comments, or other formatting to mess with.</li><li>Order of operations is always clear.</li><li>Symbols are searchable based on their meaning in code instead of their text makeup (“=” vs “==”, “(“ in a function call or declaration, etc).</li><li>Compiler-inferred information such as types or errors is available.</li><li>Telling people that you’re working with the compiler API makes you seem really smart.</li></ol><p>You can use the wonderful <a href="http://astexplorer.net/">ASTExplorer.net</a> to compile and inspect ASTs of your code live.</p><h3>A minimal linter</h3><p>One of the easiest things you can do with ASTs is build your own linter, which will automatically analyze your code and report errors. For this example we’ll be using Typescript and its <a href="https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API">compiler API</a>, but if Typescript isn’t your thing, try out the equivalent with <a href="https://docs.python.org/3/library/ast.html">Python</a>, <a href="https://github.com/nikic/php-ast">PHP</a>, <a href="https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis">C#</a>, or <a href="http://javaparser.org/">Java</a>. We’ll be producing a script that warns whenever a function’s name starts with an uppercase letter.</p><p>First, we provide our file to the compiler:</p><pre><strong>import </strong>* <strong>as </strong>fs <strong>from </strong>&#39;fs&#39;;<br><strong>import </strong>* <strong>as </strong>ts <strong>from </strong>&#39;typescript&#39;;</pre><pre><strong>const </strong>fileName<strong> </strong>=<strong> </strong>process.argv[2]<strong>;<br>const </strong>sourceFile = ts.createSourceFile(<br>  fileName,                             // File name<br>  fs.readFileSync(fileName).toString(), // File contents<br>  ts.ScriptTarget.ES2017,               // JS dialect<br>  true,                                 // Build out the AST<br>);</pre><p>Next, we write a small tree traversal function that will perform a depth-first walk over the whole tree, searching for errors:</p><pre><strong>function</strong> traverse(node: ts.Node) {<br>  <strong>if</strong> (ts.isFunctionDeclaration(node)) {<br>    <strong>const</strong> name = node.name.getText();<br>    <strong>if</strong> (name.charAt(0).match(/[A-Z]/)) {<br>      <strong>const</strong> pos = sourceFile.getLineAndCharacterOfPosition(<br>         node.getStart()<br>      );<br>      console.error(<br>        `You really messed up on line ${pos.line}` +<br>        ` when you called your function ${name}.`<br>      );<br>    }<br>  }<br>  node.forEachChild(traverse);<br>}</pre><pre>// Start the traversal<br>sourceFile.forEachChild(traverse);</pre><p>With that, we have a full linter in only 25 lines of code! Make sure to adjust the sassiness level of your errors to fit your company’s culture.</p><h3>Rewriting code</h3><p>As the bosses of the world have said many times, it’s one small step from complaining about an error to fixing it. Let’s modify the code that prints out the error message to write out the correct version instead:</p><pre>// Add this at the top level<br><strong>const</strong> spansToReplace = [];</pre><pre>// Add this after the console.error call<br>spansToReplace.push({<br>  start: node.getStart(),<br>  end: node.getEnd(),<br>  replacement: name.charAt(0).toLowerCase() + name.substr(1),<br>});</pre><pre>// Put this at the end<br><strong>let</strong> newText = sourceFile.getFullText();<br>spansToReplace.forEach(span =&gt; {<br>  newText = newText.substring(0, span.start)<br>            + span.replacement<br>            + newText.substring(span.end);<br>});<br>console.log(newText);</pre><p>The original AST is read-only, so we need to maintain a list of replacements ourselves and then output a modified version of the original file. We can do this by keeping track of where we found the bad nodes during the tree search and then replacing everything in one go.</p><p>Now, running this on every file in your codebase will automatically fix every function declaration, regardless of the number of files you have!</p><h3>Expand your horizons</h3><p>This is just a simple introduction to linting and code modifications. The example above can be modified to check for any number of things, even across source files. Here are some ideas to try yourself:</p><ul><li>Find var or let declarations that are never modified, and convert them to const.</li><li>Search across files for public fields that are never accessed, and convert them to private.</li><li>Replace public fields and their accesses with generated getters and setters.</li><li>Use the type checker to add explicit type declarations to variables based on the return types of function calls.</li><li>Integrate your custom linter with TSLint to get IDE highlighting.</li></ul><p>Of course, you don’t have to do it all from scratch. ASTs are the basis of popular tools like <a href="https://palantir.github.io/tslint/">TSLint</a> and <a href="https://github.com/facebook/jscodeshift">JSCodeshift</a>. There are even ease-of-use libraries for the Typescript API like <a href="https://www.npmjs.com/package/tsutils">tsutils</a> and <a href="https://dsherret.github.io/ts-simple-ast/">ts-simple-ast</a>. Armed with the knowledge you’ve gained here you’ll be able to take these and expand on them to bring the light back to your legacy codebase.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a3f989ec8524" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/refactoring-at-scale-with-abstract-syntax-trees-a3f989ec8524">Refactoring at Scale with Abstract Syntax Trees</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Translating 800K lines of Javascript to Typescript]]></title>
            <link>https://medium.com/zoosk-engineering/translating-800k-lines-of-javascript-to-typescript-4a4324928af7?source=rss----95d50021e056---4</link>
            <guid isPermaLink="false">https://medium.com/p/4a4324928af7</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Robin Keller]]></dc:creator>
            <pubDate>Fri, 23 Mar 2018 22:59:09 GMT</pubDate>
            <atom:updated>2018-03-23T22:59:08.836Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iFhuBEjKfnP4GaZ3a1UbjQ.jpeg" /><figcaption>For some reason, GitHub refused to display my diff.</figcaption></figure><p>Over the last two months, I’ve been translating <a href="https://www.zoosk.com">Zoosk</a>’s 800,000-line ES5 Javascript codebase into Typescript. The process was long and harrowing, and I crashed every single external tool I used during the process. But I persevered, and here’s how.</p><h3>Planning</h3><p>The most important part of any big project is planning. Here’s the breakdown of how our code was when I started:</p><ul><li>~800,000 lines of Javascript written from 2008 onward</li><li>No ES6 anywhere — all vars and functions</li><li>No imports or exports — everything is output to the same place</li><li>Type annotations added in JSDoc, thanks to our use of the <a href="https://developers.google.com/closure/compiler/">Closure Compiler</a></li><li>Code runs both in the browser and on the server via Node</li><li>Files are built using the version of <a href="http://plovr.com/">plovr</a> from 2015</li></ul><p>Not the most cutting-edge codebase. Here’s how I wanted it to look:</p><ul><li>~800,000 lines of Typescript!</li><li>All ES7: lets, consts, rest, spread, you name it</li><li>All files should use imports and exports</li><li>Code should still be deployable to both client and server</li></ul><h3>Swapping the syntax with gents</h3><p>I like to say “a lazy programmer is a good programmer,” mostly because it’s almost always a good idea to take an already-existing solution. Typescript is the official language of Angular, and luckily the Angular Team maintains a tool called <a href="https://github.com/angular/clutz#gents---closure-to-typescript-converter">gents</a> that translates @type-annotated JS into Typescript. I <em>highly</em> recommend starting here if you’d like to follow in my footsteps.</p><p>Don’t get too comfortable, though. Gents’s use case is so specific that the tool hasn’t had too much time to develop. It will help you greatly in that it will produce a legal-TS-syntax version of your codebase. It doesn’t guarantee that the output will work. Check the <a href="https://github.com/angular/clutz/issues">issues page</a> before starting and submit an issue if you run into any trouble.</p><p>So after running my code through gents, I had 800,000 lines of Typescript. What I <em>didn’t</em> have was compiling code.</p><h3>Automated fixes with Python</h3><pre>Robin$ tsc | wc -l  # Count the compiler errors<br>66213</pre><p>Alright, so there are a few errors.</p><p>What do you do when you have more errors than will fit in 16 bits? You start automating updates to the code. Most of the time I’d recommend an AST-based solution, but for simple fixes it’s faster (both in writing and runtime) to use 15–20-line Python scripts.</p><p><a href="https://gist.github.com/theRobinator/7dd5d37b99e03a0be01faa6d8df09709">Here’s some base code that you can use to get started.</a></p><p>One of the most common things that I did was use the replacement function option of re.sub in Python. Here’s a dumb example that just capitalizes its match:</p><pre>def repl(match):<br>    return &#39;I am &#39; + match.groups()[0].upper()</pre><pre>re.sub(&#39;I am (\w+)&#39;, repl, &#39;I am hungry!&#39;)<br># Returns &#39;I am HUNGRY!&#39;</pre><p>Of course, this is possible in JS as well. String.prototype.replace has the same functionality.</p><p>I used a <em>ton</em> of tiny scripts like the one above to make far-reaching edits to my codebase. Everything from removing duplicate imports to alphabetizing fields to correctly linking up our templating system was scripted in Python.</p><h3>Building the code</h3><p>The next big challenge was how to deploy. Our one big ask here was that we continue using the Closure Compiler for the browser code. This is due to the huge gains that advanced mode provides like dead code removal, inlining, and gzip optimizations.</p><p>The Closure Compiler is very specific about its input in advanced mode. Luckily the Angular Team came to our rescue again with <a href="https://github.com/angular/tsickle">tsickle</a>, a wrapper around tsc that outputs advanced mode-compatible JS.</p><p>For me, getting started with tsickle was a headache. I ended up diving into the source of tsickle and fixing a couple bugs. Thanks to the magic of open source®, you won’t have to deal with those issues!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/781/1*hzqYcao-BodQhWbdCkOncA.png" /><figcaption>Our new build flow</figcaption></figure><p>On the server, the extra compile step was one that we already had: linking up the output of Soy template files to code running in Node. That step had to be slightly modified to match the new style of requires that tsc outputs.</p><h3>Code quality improvements</h3><p>One of the big reasons we wanted to move to Typescript was that it would help us adhere to a higher standard of code quality. We expected this to come in time (and more will!), but the initial migration actually forced us to shape up a bit. Here are some things that Typescript was not happy with initially:</p><ul><li>Usage of fields marked with @private outside of their owning class</li><li>The same thing being imported twice in one file</li><li>Enums declared as static class members instead of by themselves</li><li>Using properties of this before calling super</li><li>Child components importing and manipulating their parents</li></ul><p>Additionally, the compiler found <strong>seven bugs</strong> that we had missed before, completely automatically!</p><h3>Join us in the sun</h3><p>If you’re thinking of migrating your large codebase over to TS, I think the benefits drastically outweigh the costs. It took me two months to do ours, but a good three weeks of that time was working out bugs in the open source ecosystem, between gents, tsickle, tsc, and the Closure Compiler. I’ve submitted PRs for everything, so you should be fine.</p><p>Additionally, I spent the first month of this project working all by myself, and gained one developer during the second. A team of three devs would have been able to do 800K lines in about three weeks.</p><p>Finally, if you’re thinking that you want to move over incrementally, you can actually <a href="https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files">use Typescript on JS files</a> with almost no effort! This will give you the ability to introduce new TS files one at a time.</p><p>That said, if you want to work on Typescript without the work, you can always <a href="https://about.zoosk.com/en/careers/jobs/">apply at Zoosk</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4a4324928af7" width="1" height="1" alt=""><hr><p><a href="https://medium.com/zoosk-engineering/translating-800k-lines-of-javascript-to-typescript-4a4324928af7">Translating 800K lines of Javascript to Typescript</a> was originally published in <a href="https://medium.com/zoosk-engineering">Zoosk Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>