> All in One 586

Ads

Wednesday, October 27, 2021

Partly Cloudy/Wind today!



With a high of F and a low of 36F. Currently, it's 43F and Partly Cloudy outside.

Current wind speeds: 14 from the Northwest

Pollen: 2

Sunrise: October 27, 2021 at 07:14PM

Sunset: October 28, 2021 at 05:56AM

UV index: 0

Humidity: 43%

via https://ift.tt/2livfew

October 28, 2021 at 10:05AM

How to Implement and Style the Dialog Element

A look from Christian Kozalla on the <dialog> HTML element and using it to create a nice-looking and accessible modal.

I’m attracted to the <dialog> element as it’s one of those “you get a lot for free” elements (even more so than the beloved <details> element) and it’s so easy to get modal accessibility wrong (e.g. focus trapping) that having this sort of thing provided by a native element seems… great. ::backdrop is especially cool.

But is it too good to be true? Solid support isn’t there yet, with Safari not picking it up.

Christian mentions the polyfill from Google, which definitely helps bring basic functionality to otherwise non-supporting browsers.

The main problem is actual testing on a screen reader. Scott O’Hara has an article, “Having an open dialog,” which has been updated as recently as this very month (October 2021), in which he ultimately says, “[…] the dialog element and its polyfill are not suitable for use in production.” I don’t doubt Scott’s testing, but because most people just roll-their-own modal experiences paying little mind to accessibility at all, I wonder if the web would be better off if more people just used <dialog> (and the polyfill) anyway. Higher usage would likely trigger more browser attention and improvements.


The post How to Implement and Style the Dialog Element appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3vT7k4V
via IFTTT

Testing Vue Components With Cypress

Cypress is an automated test runner for browser-based applications and pages. I’ve used it for years to write end-to-end tests for web projects, and was happy to see recently that individual component testing had come to Cypress. I work on a large enterprise Vue application, and we already use Cypress for end-to-end tests. Most of our unit and component tests are written with Jest and Vue Test Utils.

Once component testing arrived in Cypress, my team was all in favor of upgrading and trying it out. You can learn a lot about how component testing works directly from the Cypress docs, so I’m going skip over some of the setup steps and focus on what it is like to work with component tests — what do they look like, how are we using them, and some Vue-specific gotchas and helpers we found.

Disclosure! At the time I wrote the first draft of this article, I was the front-end team lead at a large fleet management company where we used Cypress for testing. Since the time of writing, I’ve started working at Cypress, where I get to contribute to the open source test runner.

The Cypress component test runner is open, using Chrome to test a “Privacy Policy” component. Three columns are visible in the browser. The first contains a searchable list of component test spec files; the second shows the tests for the currently-spec; the last shows the component itself mounted in the browser. The middle column shows that two tests are passing.

All the examples mentioned here are valid at the time of writing using Cypress 8. It’s a new feature that’s still in alpha, and I wouldn’t be surprised if some of these details change in future updates.

If you already have a background in testing and component tests, you can skip right to our team’s experiences.

What a component test file looks like

For a simplified example, I’ve created a project that contains a “Privacy Policy” component. It has a title, body, and an acknowledgment button.

The Privacy Policy component has three areas. The title reads “Privacy Policy”; the body text reads “Information about privacy that you should read in detail,” and a blue button at the bottom reads “OK, I read it, sheesh.”

When the button is clicked, an event is emitted to let the parent component know that this has been acknowledged. Here it is deployed on Netlify.

Now here’s the general shape of a component test in Cypress that uses some of the feature’s we are going to talk about:

import { mount } from '@cypress/vue'; // import the vue-test-utils mount function
import PrivacyPolicyNotice from './PrivacyPolicyNotice.vue'; // import the component to test

describe('PrivacyPolicyNotice', () => {
 
 it('renders the title', () => {
    // mount the component by itself in the browser 🏗
    mount(PrivacyPolicyNotice); 
    
    // assert some text is present in the correct heading level đŸ•ĩ️ 
    cy.contains('h1', 'Privacy Policy').should('be.visible'); 
  });

  it('emits a "confirm" event once when confirm button is clicked', () => {
    // mount the component by itself in the browser 🏗
    mount(PrivacyPolicyNotice);

    // this time let's chain some commands together
    cy.contains('button', '/^OK/') // find a button element starting with text 'OK' đŸ•ĩ️
    .click() // click the button 🤞
    .vue() // use a custom command to go get the vue-test-utils wrapper 🧐
    .then((wrapper) => {
      // verify the component emitted a confirm event after the click đŸ¤¯
      expect(wrapper.emitted('confirm')).to.have.length(1) 
      // `emitted` is a helper from vue-test-utils to simplify accessing
      // events that have been emitted
    });
  });

});

This test makes some assertions about the user interface, and some about the developer interface (shoutout to Alex Reviere for expressing this division in the way that clicked for me). For the UI, we are targeting specific elements with their expected text content. For developers, we are testing what events are emitted. We are also implicitly testing that the component is a correctly formed Vue component; otherwise it would not mount successfully and all the other steps would fail. And by asserting specific kinds of elements for specific purposes, we are testing the accessibility of the component — if that accessible button ever becomes a non-focusable div, we’ll know about it.

Here’s how our test looks when I swap out the button for a div. This helps us maintain the expected keyboard behavior and assistive technology hints that come for free with a button element by letting us know if we accidentally swap it out:

The Cypress component test runner shows that one test is passing and one is failing. The failure warning is titled 'Assertion Error' and reads 'Timed out retrying after 4000ms: Expected to find content: '/^OK/' within the selector: 'button' but never did.'

A little groundwork

Now that we’ve seen what a component test looks like, let’s back up a little bit and talk about how this fits in to our overall testing strategy. There are many definitions for these things, so real quick, for me, in our codebase:

  • Unit tests confirm single functions behave as expected when used by a developer.
  • Component tests mount single UI components in isolation and confirm they behave as expected when used by an end-user and a developer.
  • End-to-end tests visit the application and perform actions and confirm the app as whole behaves correctly when used by an end-user only.

Finally, integration testing is a little more of a squishy term for me and can happen at any level — a unit that imports other functions, a component that imports other components, or indeed, an “end-to-end” test that mocks API responses and doesn’t reach the database, might all be considered integration tests. They test more than one part of an application working together, but not the entire thing. I’m not sure about the real usefulness of that as a category, since it seems very broad, but different people and organizations use these terms in other ways, so I wanted to touch on it.

For a longer overview of the different kinds of testing and how they relate to front-end work, you can check out “Front-End Testing is For Everyone” by Evgeny Klimenchenko.

Component tests

In the definitions above, the different testing layers are defined by who will be using a piece of code and what the contract is with that person. So as a developer, a function that formats the time should always return the correct result when I provide it a valid Date object, and should throw clear errors if I provide it something different as well. These are things we can test by calling the function on its own and verifying it responds correctly to various conditions, independent of any UI. The “developer interface” (or API) of a function is all about code talking to other code.

Now, let’s zoom in on component tests. The “contract” of a component is really two contracts:

  • To the developer using a component, the component is behaving correctly if the expected events are emitted based on user input or other activity. It’s also fair to include things like prop types and validation rules in our idea of “correct developer-facing behavior,” though those things can also be tested at a unit level. What I really want from a component test as a developer is to know it mounts, and sends the signals it is supposed to based on interactions.
  • To the user interacting with a component, it is behaving correctly if the UI reflects the state of the component at all times. This includes more than just the visual aspect. The HTML generated by the component is the foundation for its accessibility tree, and the accessibility tree provides the API for tools like screen readers to announce the content correctly, so for me the component is not “behaving correctly” if it does not render the correct HTML for the contents.

At this point it’s clear that component testing requires two kinds of assertions — sometimes we check Vue-specific things, like “how many events got emitted of a certain type?”, and sometimes we check user-facing things, like “did a visible success message actually end up on the screen though?”

It also feels like component level tests are a powerful documentation tool. The tests should assert all the critical features of a component — the defined behaviors that are depended on — and ignore details that aren’t critical. This means we can look to the tests to understand (or remember, six months or a year from now!) what a component’s expected behavior is. And, all going well, we can change any feature that’s not explicitly asserted by the test without having to rewrite the test. Design changes, animation changes, improving the DOM, all should be possible, and if a test does fail, it will be for a reason you care about, not because an element got moved from one part of the screen to another.

This last part takes some care when designing tests, and most especially, when choosing selectors for elements to interact with, so we’ll return to this topic later.

How Vue component tests work with and without Cypress

At a high level, a combination of Jest and the Vue Test Utils library has becomes more or less the standard approach to running component tests that I’ve seen out there.

Vue Test Utils gives us helpers to mount a component, give it its options, and mock out various things a component might depend on to run properly. It also provides a wrapper object around the mounted component to make it a little easier to make assertions about what’s going on with the component.

Jest is a great test runner and will stand up the mounted component using jsdom to simulate a browser environment.

Cypress’ component test runner itself uses Vue Test Utils to mount Vue components, so the main difference between the two approaches is context. Cypress already runs end-to-end tests in a browser, and component tests work the same way. This means we can see our tests run, pause them mid-test, interact with the app or inspect things that happened earlier in the run, and know that browser APIs that our application depends on are genuine browser behavior rather than the jsdom mocked versions of those same features.

Once the component is mounted, all the usual Cypress things that we have been doing in end-to-end tests apply, and a few pain points around selecting elements go away. Mainly, Cypress is going to handle simulating all the user interactions, and making assertions about the application’s response to those interactions. This covers the user-facing part of the component’s contract completely, but what about the developer-facing stuff, like events, props, and everything else? This is where Vue Test Utils comes back in. Within Cypress, we can access the wrapper that Vue Test Utils creates around the mounted component, and make assertions about it.

What I like about this is that we end up with Cypress and Vue Test Utils both being used for what they are really good at. We can test the component’s behavior as a user with no framework-specific code at all, and only dig into Vue Test Utils for mounting the component and checking specific framework behavior when we choose to. We’ll never have to await a Vue-specific $nextTick after doing some Vue-specific thing to update the state of a component. That was always the trickiest thing to explain to new developers on the team without Vue experience — when and why they would need to await things when writing a test for a Vue component.

Our experience of component testing

The advantages of component testing sounded great to us, but of course, in a large project very few things can be seamless out of the box, and as we got started with our tests, we ran into some issues. We run a large enterprise SPA built using Vue 2 and the Vuetify component library. Most of our work heavily uses Vuetify’s built-in components and styles. So, while the “test components by themselves” approach sounds nice, a big lesson learned was that we needed to set up some context for our components to be mounted in, and we needed to get Vuetify and some global styles happening as well, or nothing was going to work.

Cypress has a Discord where people can ask for help, and when I got stuck I asked questions there. Folks from the community —as well as Cypress team members — kindly directed me to example repos, code snippets, and ideas for solving our problems. Here’s a list of the little things we needed to understand in order to get our components to mount correctly, errors we encountered, and whatever else stands out as interesting or helpful:

Importing Vuetify

Through lurking in the Cypress Discord, I’d seen this example component test Vuetify repo by Bart Ledoux, so that was my starting point. That repo organizes the code into a fairly common pattern that includes a plugins folder, where a plugin exports an instance of Veutify. This is imported by the application itself, but it can also be imported by our test setup, and used when mounting the component being tested. In the repo a command is added to Cypress that will replace the default mount function with one that mounts a component with Vuetify.

Here is all the code needed to make that happen, assuming we did everything in commands.js and didn’t import anything from the plugins folder. We’re doing this with a custom command which means that instead of calling the Vue Test Utils mount function directly in our tests, we’ll actually call our own cy.mount command:

// the Cypress mount function, which wraps the vue-test-utils mount function
import { mount } from "@cypress/vue"; 
import Vue from 'vue';
import Vuetify from 'vuetify/lib/framework';

Vue.use(Vuetify);

// add a new command with the name "mount" to run the Vue Test Utils 
// mount and add Vuetify
Cypress.Commands.add("mount", (MountedComponent, options) => {
  return mount(MountedComponent, {
    vuetify: new Vuetify({});, // the new Vuetify instance
    ...options, // To override/add Vue options for specific tests
  });
});

Now we will always have Vuetify along with our components when mounted, and we can still pass in all the other options we need to for that component itself. But we don’t need to manually add Veutify each time.

Adding attributes required by Vuetify

The only problem with the new mount command above is that, to work correctly, Vuetify components expect to be rendered in a certain DOM context. Apps using Vuetify wrap everything in a <v-app> component that represents the root element of the application. There are a couple of ways to handle this but the simplest is to add some setup to our command itself before it mounts a component.

Cypress.Commands.add("mount", (MountedComponent, options) => {
  // get the element that our mounted component will be injected into
  const root = document.getElementById("__cy_root");

  // add the v-application class that allows Vuetify styles to work
  if (!root.classList.contains("v-application")) {
    root.classList.add("v-application");
  }

  // add the data-attribute — Vuetify selector used for popup elements to attach to the DOM
  root.setAttribute('data-app', 'true');  

return mount(MountedComponent, {
    vuetify: new Vuetify({}), 
    ...options,
  });
});

This takes advantage of the fact that Cypress itself has to create some root element to actually mount our component to. That root element is the parent of our component, and it has the ID __cy_root. This gives us a place to easily add the correct classes and attributes that Vuetify expects to find. Now components that use Vuetify components will look and behave correctly.

One other thing we noticed after some testing is that the required class of v-application has a display property of flex. This makes sense in a full app context using Vuetify’s container system, but had some unwanted visual side effects for us when mounting single components — so we added one more line to override that style before mounting the component:

root.setAttribute('style', 'display: block');

This cleared up the occasional layout issues and then we were truly done tweaking the surrounding context for mounting components.

Getting spec files where we want them

A lot of the examples out there show a cypress.json config file like this one for component testing:

{
  "fixturesFolder": false,
  "componentFolder": "src/components",
  "testFiles": "**/*.spec.js"
}

That is actually pretty close to what we want since the testFiles property accepts a glob pattern. This one says, Look in any folder for files ending in .spec.js. In our case, and probably many others, the project’s node_modules folder contained some irrelevant spec.js files that we excluded by prefixing !(node_modules) like this:

"testFiles": "!(node_modules)**/*.spec.js"

Before settling on this solution, when experimenting, we had set this to a specific folder where component tests would live, not a glob pattern that could match them anywhere. Our tests live right alongside our components, so that could have been fine, but we actually have two independent components folders as we package up and publish a small part of our app to be used in other projects at the company. Having made that change early, I admit I sure did forget it had been a glob to start with and was starting to get off course before popping into the Discord, where I got a reminder and figured it out. Having a place to quickly check if something is the right approach was helpful many times.

Command file conflict

Following the pattern outlined above to get Vuetify working with our component tests produced a problem. We had piled all this stuff together in the same commands.js file that we used for regular end-to-end tests. So while we got a couple of component tests running, our end-to-end tests didn’t even start. There was an early error from one of the imports that was only needed for component testing.

I was recommended a couple of solutions but on the day, I chose to just extract the mounting command and its dependencies into its own file, and imported it only where needed in the component tests themselves. Since this was the only source of any problem running both sets of tests, it was a clean way to take that out of the the end-to-end context, and it works just fine as a standalone function. If we have other issues, or next time we are doing cleanup, we would probably follow the main recommendation given, to have two separate command files and share the common pieces between them.

Accessing the Vue Test Utils wrapper

In the context of a component test, the Vue Test Utils wrapper is available under Cypress.vueWrapper. When accessing this to make assertions, it helps to use cy.wrap to make the result chain-able like other commands accessed via cy. Jessica Sachs adds a short command in her example repo to do this. So, once again inside commands,js, I added the following:

Cypress.Commands.add('vue', () => {
  return cy.wrap(Cypress.vueWrapper);
});

This can be used in a test, like this:

mount(SomeComponent)
  .contains('button', 'Do the thing once')
  .click()
  .should('be.disabled')
  .vue()
  .then((wrapper) => {
    // the Vue Test Utils `wrapper` has an API specifically setup for testing: 
    // https://vue-test-utils.vuejs.org/api/wrapper/#properties
    expect(wrapper.emitted('the-thing')).to.have.length(1);
  });

This starts to read very naturally to me and clearly splits up when we are working with the UI compared to when we are inspecting details revealed through the Vue Test Utils wrapper. It also emphasizes that, like lots of Cypress, to get the most out of it, it’s important to understand the tools it leverages, not just Cypress itself. Cypress wraps Mocha, Chai, and various other libraries. In this case, it’s useful to understand that Vue Test Utils is a third-party open source solution with its own entire set of documentation, and that inside the then callback above, we are in Vue Test Utils Land — not Cypress Land — so that we go to the right place for help and documentation.

Challenges

Since this has been a recent exploration, we have not added the Cypress component tests to our CI/CD pipelines yet. Failures will not block a pull request, and we haven’t looked at adding the reporting for these tests. I don’t expect any surprises there, but it’s worth mentioning that we haven’t completed integrating these into our whole workflow. I can’t speak to it specifically.

It’s also relatively early days for the component test runner and there are a few hiccups. At first, it seemed like every second test run would show a linter error and need to be manually refreshed. I didn’t get to the bottom of that, and then it fixed itself (or was fixed by a newer Cypress release). I’d expect a new tool to have potential issues like this.

One other stumbling block about component testing in general is that, depending on how your component works, it can be difficult to mount it without a lot of work mocking other parts of your system. If the component interacts with multiple Vuex modules or uses API calls to fetch its own data, you need to simulate all of that when you mount the component. Where end-to-end tests are almost absurdly easy to get up and running on any project that runs in the browser, component tests on existing components are a lot more sensitive to your component design.

This is true of anything that mounts components in isolation, like Storybook and Jest, which we’ve also used. It’s often when you attempt to mount components in isolation that you realize just how many dependencies your components actually have, and it can seem like a lot of effort is needed just to provide the right context for mounting them. This nudges us towards better component design in the long run, with components that are easier to test and while touching fewer parts of the codebase.

For this reason, I’d suggest if you haven’t already got component tests, and so aren’t sure what you need to mock in order to mount your component, choose your first component tests carefully, to limit the number of factors you have to get right before you can see the component in the test runner. Pick a small, presentational component that renders content provided through props or slots, to see it a component test in action before getting into the weeds on dependencies.

Benefits

The component test runner has worked out well for our team. We already have extensive end-to-end tests in Cypress, so the team is familiar with how to spin up new tests and write user interactions. And we have been using Vue Test Utils for individual component testing as well. So there was not actually too much new to learn here. The initial setup issues could have been frustrating, but there are plenty of friendly people out there who can help work through issues, so I’m glad I used the “asking for help” superpower.

I would say there are two main benefits that we’ve found. One is the consistent approach to the test code itself between levels of testing. This helps because there’s no longer a mental shift to think about subtle differences between Jest and Cypress interactions, browser DOM vs jsdom and similar issues.

The other is being able to develop components in isolation and getting visual feedback as we go. By setting up all the variations of a component for development purposes, we get the outline of the UI test ready, and maybe a few assertions too. It feels like we get more value out of the testing process up front, so it’s less like a bolted-on task at the end of a ticket.

This process is not quite test-driven development for us, though we can drift into that, but it’s often “demo-driven” in that we want to showcase the states of a new piece of UI, and Cypress is a pretty good way to do that, using cy.pause() to freeze a running test after specific interactions and talk about the state of the component. Developing with this in mind, knowing that we will use the tests to walk through the components features in a demo, helps organize the tests in a meaningful way and encourages us to cover all the scenarios we can think of at development time, rather than after.

Conclusion

The mental model for what exactly Cypress as whole does was tricky for me to when I first learned about it, because it wraps so many other open source tools in the testing ecosystem. You can get up and running quickly with Cypress without having a deep knowledge of what other tools are being leveraged under the hood.

This meant that when things went wrong, I remember not being sure which layer I should think about — was something not working because of a Mocha thing? A Chai issue? A bad jQuery selector in my test code? Incorrect use of a Sinon spy? At a certain point, I needed to step back and learn about those individual puzzle pieces and what exact roles they were playing in my tests.

This is still the case with component testing, and now there is an extra layer: framework-specific libraries to mount and test components. In some ways, this is more overhead and more to learn. On the other hand, Cypress integrates these tools in a coherent way and manages their setup so we can avoid a whole unrelated testing setup just for component tests. For us, we already wanted to mount components independently for testing with Jest, and for use in Storybook, so we figured out a lot of the necessary mocking ideas ahead of time, and tended to favor well-separated components with simple props/events based interfaces for that reason.

On balance, we like working with the test runner, and I feel like I’m seeing more tests (and more readable test code!) showing up in pull requests that I review, so to me that’s a sign that we’ve moved in a good direction.


The post Testing Vue Components With Cypress appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3vRpzrF
via IFTTT

Tuesday, October 26, 2021

Mostly Cloudy/Wind today!



With a high of F and a low of 37F. Currently, it's 44F and Partly Cloudy/Wind outside.

Current wind speeds: 28 from the Northwest

Pollen: 0

Sunrise: October 26, 2021 at 07:13PM

Sunset: October 27, 2021 at 05:57AM

UV index: 0

Humidity: 82%

via https://ift.tt/2livfew

October 27, 2021 at 10:05AM

On Browser-Specific URL Schemes

We’ve covered URL schemes:

A URL Scheme is like “http://…” or “ftp://…”. Those seem like a very low-level concept that you don’t have much control over, but actually, you do!

I’d call it non-trivial, but developers can register new URL schemes in apps that users install. Back in 2017, Microsoft Edge did this:

microsoft-edge:// 

If you use that, the behavior is to open the URL in Microsoft Edge — even if you’ve chosen a different default browser. So if I, as a blogger, wanted to essentially force you to use Edge for this site, I could, by starting every single URL with this URL scheme. I won’t, but I could. And so could Microsoft.

At the time, Daniel Aleksandersen wrote a program called EdgeDefelector to circumvent that behavior and explained:

I don’t hate Microsoft Edge — maybe you do! — but I do believe users who have bothered to configure a different default web browser should be allowed to keep using that default web browser. 

This has come back into the public eye a bit as the Brave browser now supports the microsoft-edge:// URL scheme. Apparently, not only does an app need to register a URL scheme, but other apps that support clicks-on-links need to honor it too. Firefox is also thinking of adding it. I think the risk of not supporting the URL scheme is that clicks on links like that could do nothing instead of actually opening the URL.

A lot of the talk is about Windows 11. But here on my Mac, I see this URL scheme do what it intends across all these browsers.

Safari
Chrome
Firefox
Brave

Daniel goes further:

So, how did we get here? Until the release of iOS version 14 in September 2020, you couldn’t change the default web browser on iPhones and iPads. Google has many apps for iOS, including a shell for its Chrome browser. To tie all its apps together, Google introduced a googlechrome: URL scheme in February 2014. It could use these links to direct you from its Search or Mail app and over to Chrome instead of Apple’s Safari browser.

Here’s my iPhone 13 opening googlechrome://css-tricks.com with and without Google Chrome installed.

iOS Safari with Google Chrome installed
iOS Safari without Google Chrome installed

Seems like that would be Google’s sin, but it is apparently Apple that allowed it on iOS. Daniel once more:

The original sin was Apple’s, but Microsoft is gulping the juice of the apple with gusto.

I’m not as boned up on all this as I should be, but I think if I made software that was involved here, I’d be tempted to intercept these URL schemes and have them open in the browser the user is already in. The web is the web, there should be no reason any given URL has to open in any specific browser.


The post On Browser-Specific URL Schemes appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3bepBQU
via IFTTT

In-Page Filtered Search With Vanilla JavaScript

If you have a page that includes a lot of information, it’s a good idea to let users search for what they might be looking for. I’m not talking about searching a database or even searching JSON data — I’m talking about literally searching text on a single rendered web page. Users can already use the built-in browser search for this, but we can augment that by offering our own search functionality that filters down the page making matching results easier to find and read.

Here’s a live demo of what we’re going to build:

I use this same technique on my real project: https://freestuff.dev/.

Meet JavaScript!

Well, you might know JavaScript already. JavaScript is going to handle all the interactivity in this journey. It’s going to…

  • find all the content we want to search through,
  • watch what a user types in the search input,
  • filter the innerText of the searchable elements,
  • test if the text includes the search term (.includes() is the heavy lifter here!), and
  • toggle the visibility of the (parent) elements, depending on if they include the search term or not.

Alright, we have our requirements! Let’s start working.

The basic markup

Let’s assume we have a FAQ page. Each question is a “card” which has a title and content:

<h1>FAQ Section</h1>

<div class="cards">
  <h3>Who are we</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p>
</div>

<div class="cards">
  <h3>What we do</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized </p>
</div>

<div class="cards">
  <h3>Why work here</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p>
</div>

<div class="cards">
  <h3>Learn more</h3>
  <p>Want to learn more about us?</p>
</div>

Imagine there are a lot of questions on this page.

To get ready for the interactivity, we’ll have this one line of CSS. This gives us a class we can add/remove depending on the search situation when we get to the JavaScript:

.is-hidden { display: none; }

Let’s add a search input with an event that fires when it is interacted with:

<label for="searchbox">Search</label>
<input 
  type="search" 
  oninput="liveSearch()" 
  id="searchbox" 
>

The JavaScript baseline

And here’s the JavaScript that does everything else!

function liveSearch() {
  // Locate the card elements
  let cards = document.querySelectorAll('.cards')
  // Locate the search input
  let search_query = document.getElementById("searchbox").value;
  // Loop through the cards
  for (var i = 0; i < cards.length; i++) {
    // If the text is within the card...
    if(cards[i].innerText.toLowerCase()
      // ...and the text matches the search query...
      .includes(search_query.toLowerCase())) {
        // ...remove the `.is-hidden` class.
        cards[i].classList.remove("is-hidden");
    } else {
      // Otherwise, add the class.
      cards[i].classList.add("is-hidden");
    }
  }
}

You can probably go line-by-line there and reason out what it is doing. It finds all the cards and the input and saves references to them. When a search event fires, it loops through all the cards, determines if the text is within the card or not. It the text in the card matches the search query, the .is-hidden class is removed to show the card; if not, the class is there and the card remains hidden.

Here is the link to the demo again.

Adding a delay

To make sure our JavaScript doesn’t run too much (meaning it would slow down the page), we will run our liveSearch function only after waiting an “X” number of seconds.

<!-- Delete on Input event on this input -->
<label for="searchbox">Search</label>
<input type="search" id="searchbox">
// A little delay
let typingTimer;        
let typeInterval = 500; // Half a second
let searchInput = document.getElementById('searchbox');

searchInput.addEventListener('keyup', () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(liveSearch, typeInterval);
});

What about fuzzy searches?

Let’s say you want to search by text that is not visible to user. The idea is sort of like a fuzzy search, where related keywords return the same result as an exact match. This helps expand the number of cards that might “match” a search query.

There are two ways to do this. The first is using a hidden element, like a span, that contains keywords:

<div class="cards">
  <h3>Who are we</h3>
  <p>It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularized</p>
  
    <!-- Put any keywords here -->
   <span class="is-hidden">secret</span> 
</div>

Then we need to update our liveSearch function. Instead of using .innerText we will use .textContent to includes hidden elements. See more detail about the difference between innerText and textContent here

 for (var i = 0; i < cards.length; i++) {
        if(cards[i].textContent.toLowerCase()
                .includes(search_query.toLowerCase())) {
            cards[i].classList.remove("is-hidden");
        } else {
            cards[i].classList.add("is-hidden");
        }
    }

Try typing “secret” on a search box, it should reveal this card, even though “secret” isn’t a displayed on the page.

A second approach is searching through an attribute. Let’s say we have a gallery of images. We can put the keywords directly on the alt attribute of the image. Try typing “kitten” or “human” in the next demo. Those queries are matching what’s contained in the image alt text.

For this to work, we need to change innerText to getAttribute('alt') since we want to look through alt attributes in addition to what’s actually visible on the page.

for (var i = 0; i < cards.length; i++) {
  if(cards[i].getAttribute('alt').toLowerCase()
    .includes(search_query.toLowerCase())) {
      cards[i].classList.remove("is-hidden");
  } else {
    cards[i].classList.add("is-hidden");
  }
}

Depending on your needs, you could put your keywords in another attribute, or perhaps a custom one.

Caveat

Again, this isn’t a search technology that works by querying a database or other data source. It works only if you have all the searchable content in the DOM on that page, already rendered.

So, yeah, there’s that. Just something to keep in mind.

Wrapping up

Obviously, I really like this technique, enough to use it on a production site. But how else might you use something like this? An FAQ page is a clear candidate, as we saw, but any situation that calls for filtering any sort of content is fit for this sort of thing. Even a gallery of images could work, using the hidden input trick to search through the alt tag content of the images.

Whatever the case, I hope you find this helpful. I was surprised that we can get a decently robust search solution with a few lines of vanilla JavaScript.

Have you used this technique before, or something like it? What was your use case?


The post In-Page Filtered Search With Vanilla JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3Gm2Okl
via IFTTT

Userwell is for Managing Customer Feedback

(This is a sponsored post.)

Building products for users is a balance between your vision of what your product can do for them, and refining that vision based on their feedback. If you’re really a customer-focused company, you’re listening. And more, you’re making it easy for customers to talk to you.

A good user feedback pipeline takes software — but you aren’t building user feedback software too, that would mean lost focus on your real product. That’s where Userwell comes in. Userwell is already good user feedback software. You focus on building your own product and let Userwell be your project feedback management software.

Gather feedback from all channels

Being picky about how you receive feedback doesn’t exactly send the right signal to customers. Just like the ol’ Robustness Principle: you should be liberal in what you accept. Userwell can accept customer feedback from anything.

They offer customizable Feedback Pages (which can be as public or private as you need) where feedback can be collected and viewed. They also offer a widget that can be easily integrated into any page for collecting feedback.

But it’s not limited to that! There are loads of direct integrations from other apps, like Slack and Salesforce. Imagine you have a #feedback channel in a Slack with your customers that can be fed into Userwell from there.

Plus they have an API, meaning literally anything can push feedback data into Userwell.

Analyze the feedback to make good product decisions

Now that you have all your customer feedback funneled into Userwell all together, you can triage it, respond to it, and even open it up to voting.

This takes guesswork the guesswork out of prioritizing work on user facing features. Nothing justifies a feature sprint like clearly looking at a dashboard showing you the top requests of active customers.

You can add internal-only information as well. Because of course, not all feature work is equal. For example, you can rate the effort level of features and then cross-reference it with the impact it will have. Low effort and high impact features are no brainers! Or cross references the features with how valuable the customer is providing the feedback.

Doing the work

Userwell can also be used to show those users (or any stakeholder) the progress and plan for all these features.

That might a faithful representation of your high-level project management overview. But say you use other software for project management. No problem! You can push tasks from Userwell into that software too, as Userwell’s API and direct integrations open things up to loads of other apps.


Go forth and build products that users love!


The post Userwell is for Managing Customer Feedback appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3GmRkNE
via IFTTT

Thunderstorms/Wind Early today!

With a high of F and a low of 47F. Currently, it's 60F and Showers in the Vicinity outside. Current wind speeds: 22 from the South ...