Tag: Data

State of Jamstack 2020: Data Deep Dive

(This is a sponsored post.)

The Jamstack, a modern approach to building websites and apps, delivers better performance, higher security, lower cost of scaling, and a better developer experience. But how popular is it among developers worldwide, and what do they love and hate about it?

We at Kentico Kontent decided to take a closer look at the current state of Jamstackʼs adoption and use. Surveying more than 500 developers in four countries, we wanted to find out how long they’ve been working with the Jamstack, what they’re using this architecture for, where they typically deploy and host their projects, and more.

Based on the findings, we created The State of Jamstack 2020 Report that provides an overview of the results and comments on the most interesting facts. Now we’ve released something just as exciting:

Our free interactive data visualization page lets you dive into the data from our survey and discover how the web developers’ answers varied according to age, gender, primary programming language, and other factors.

Do you know where the majority of US developers working for large companies store their content? Or what’s their favorite static site generator? The answers may surprise you—click below to find out:

Direct Link to ArticlePermalink


The post State of Jamstack 2020: Data Deep Dive appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,

Comparing Data in Google and Netlify Analytics

Jim Nielsen:

the datasets weren’t even close for me.

Google Analytics works by putting a client-side bit of JavaScript on your site. Netlify Analytics works by parsing server logs server-side. They are not exactly apples to apples, feature-wise. Google Analytics is, I think it’s fair to say, far more robust. You can do things like track custom events which might be very important analytics data to a site. But they both have the basics. They both want to tell you how many pageviews your homepage got, for instance.

There are two huge things that affect these numbers:

  • Client-side JavaScript is blockable and tons of people use content blockers, particularly for third-party scripts from Google. Server-side logs are not blockable.
  • Netlify doesn’t filter things out of that log, meaning bots are counted in addition to regular people visiting.

So I’d say: Netlify probably has more accurate numbers, but a bit inflated from the bots.

Also worth noting, you can do server-side Google Analytics. I’ve never seen anyone actually do it but it seems like a good idea.

One bit of advice from Jim:

Never assume too much from a single set of data. In other words, don’t draw all your data-driven insights from one basket.

Direct Link to ArticlePermalink


The post Comparing Data in Google and Netlify Analytics appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Building Custom Data Importers: What Engineers Need to Know

Importing data is a common pain-point for engineering teams. Whether its importing CRM data, inventory SKUs, or customer details, importing data into various applications and building a solution for this is a frustrating experience nearly every engineer can relate to. Data import, as a critical product experience is a huge headache. It reduces the time to value for customers, strains internal resources, and takes valuable development cycles away from developing key, differentiating product features.

Frequent error messages end-users receive when importing data. Why do we expect customers to fix this themselves?

Data importers, specifically CSV importers, haven’t been treated as key product features within the software, and customer experience. As a result, engineers tend to dedicate an exorbitant amount of effort creating less-than-ideal solutions for customers to successfully import their data.

Engineers typically create lengthy, technical documentation for customers to review when an import fails. However, this doesn’t truly solve the issue but instead offsets the burden of a great experience from the product to an end-user.

In this article, we’ll address the current problems with importing data and discuss a few key product features that are necessary to consider if you’re faced with a decision to build an in-house solution.

Importing data is typically frustrating for anyone involved at a data-led company. Simply put, there has never been a standard for importing customer data. Until now, teams have deferred to CSV templates, lengthy documentation, video tutorials, or buggy in-house solutions to allow users the ability to import spreadsheets. Companies trying to import CSV data can run into a variety of issues such as:

  • Fragmented data: With no standard way to import data, we get emails going back and forth with attached spreadsheets that are manually imported. As spreadsheets get passed around, there are obvious version control challenges. Who made this change? Why don’t these numbers add up as they did in the original spreadsheet? Why are we emailing spreadsheets containing sensitive data?
  • Improper formatting: CSV import errors frequently occur when formatting isn’t done correctly. As a result, companies often rely on internal developer resources to properly clean and format data on behalf of the client — a process that can take hours per customer, and may lead to churn anyway. This includes correcting dates or splitting fields that need to be healed prior to importing.
  • Encoding errors: There are plenty of instances where a spreadsheet can’t be imported when it’s not improperly encoded. For example, a company may need a file to be saved with UTF-8 encoding (the encoding typically preferred for email and web pages) in order to then be uploaded properly to their platform. Incorrect encoding can result in a lengthy chain of communication where the customer is burdened with correcting and re-importing their data.
  • Data normalization: A lack of data normalization results in data redundancy and a never-ending string of data quality problems that make customer onboarding particularly challenging. One example includes formatting email addresses, which are typically imported into a database, or checking value uniqueness, which can result in a heavy load on engineers to get the validation working correctly.

Remember building your first CSV importer?

When it comes down to creating a custom-built data importer, there are a few critical features that you should include to help improve the user experience. (One caveat – building a data importer can be time-consuming not only to create but also maintain – it’s easy to ensure your company has adequate engineering bandwidth when first creating a solution, but what about maintenance in 3, 6, or 12 months?)

A preview of Flatfile Portal. It integrates in minutes using a few lines of JavaScript.

Data mapping

Mapping or column-matching (they are often used interchangeably) is an essential requirement for a data importer as the file import will typically fail without it. An example is configuring your data model to accept contact-level data. If one of the required fields is “address” and the customer who is trying to import data chooses a spreadsheet where the field is labeled “mailing address,” the import will fail because “mailing address” doesn’t correlate with “address” in a back-end system. This is typically ‘solved’ by providing a pre-built CSV template for customers, who then have to manipulate their data, effectively increasing time-to-value during a product experience. Data mapping needs to be included in the custom-built product as a key feature to retain data quality and improve the customer data onboarding experience.

Auto-column matching CSV data is the bread and butter of Portal, saving massive amounts of time for customers while providing a delightful import experience.

Data validation

Data validation, which checks if the data matches an expected format or value, is another critical feature to include in a custom data importer. Data validation is all about ensuring the data is accurate and is specific to your required data model. For example, if special characters can’t be used within a certain template, error messages can appear during the import stage. Having spreadsheets with hundreds of rows containing validation errors results in a nightmare for customers, as they’ll have to fix these issues themselves, or your team, which will spend hours on end cleaning data. Automatic data validators allow for streamlining of healing incoming data without the need for a manual review.

We built Data Hooks into Portal to quickly normalize data on import. A perfect use-case would be validating email uniqueness against a database.

Data parsing

Data parsing is the process of taking an aggregation of information (in a spreadsheet) and breaking it into discrete parts. It’s the separation of data. In a custom-built data importer, a data parsing feature should not only have the ability to go from a file to an array of discrete data but also streamline the process for customers.

Data transformation

Data transformation means making changes to imported data as it’s flowing into your system to meet an expected or desired value. Rather than sending data back to users with an error message for them to fix, data transformation can make small, systematic tweaks so that the users’ data is more usable in your backend. For example, when transferring a task list, prioritization data could be transformed into a different value, such as numbers instead of labels.

Data Hooks normalize imported customer data automatically using validation rules set in the Portal JSON config. These highly adaptable hooks can be worked to auto-validate nearly any incoming customer data.

We’ve baked all of the above features into Portal, our flagship CSV importer at Flatfile. Now that we’ve reviewed some of the must-have features of a data importer, the next obvious question for an engineer building an in-house importer is typically… should they?

Engineering teams that are taking on this task typically use custom or open source solutions, which may not adhere to specific use-cases. Building a comprehensive data importer also brings UX challenges when building a UI and maintaining application code to handle data parsing, normalization, and mapping. This is prior to considering how customer data requirements may change in future months and the ramifications of maintaining a custom-built solution.

Companies facing data import challenges are now considering integrating a pre-built data importer such as Flatfile Portal. We’ve built Portal to be the elegant import button for web apps. With just a few lines of JavaScript, Portal can be implemented alongside any data model and validation ruleset, typically in a few hours. Engineers no longer need to dedicate hours cleaning up and formatting data, nor do they need to custom build a data importer (unless they want to!). With Flatfile, engineers can focus on creating product-differentiating features, rather than work on solving spreadsheet imports.

Importing data is wrought with challenges and there are several critical features necessary to include when building a data importer. The alternative to a custom-built solution is to look for a pre-built data importer such as Portal.

Flatfile’s mission is to remove barriers between humans and data. With AI-assisted data onboarding, they eliminate repetitive work and make B2B data transactions fast, intuitive, and error-free. Flatfile automatically learns how imported data should be structured and cleaned, enabling customers and teams to spend more time using their data instead of fixing it. Flatfile has transformed over 300 million rows of data for companies like ClickUp, Blackbaud, Benevity, and Toast. To learn more about Flatfile’s products, Portal and Concierge, visit flatfile.io.


The post Building Custom Data Importers: What Engineers Need to Know appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , ,
[Top]

marketstack: A Market Data API

(This is a sponsored post.)

I like the apilayer company tagline: “Automate What Should Be Automated.” They have this thick suite of products that are all APIs with clear documentation. They all have usable free tiers to develop against and prove out an idea, and then paid plans if you need to start using the APIs more aggressively. They have a brand new one: marketstack.

With this API, you have access to stock market data both current and historical. Got an idea for an app (or business!) that needs stock data? Wanna chart out how Apple is doing? Hit the REST API with your key and the AAPL stock symbol and you’ll get back just the JSON data you need. If you’ve always wanted to build a stock market app, this totally opens that door.

That’s what I love about stuff like this. I’ve seen so many people with startup ideas, but then end up being limited by the fact that they just don’t have the data they need to make it actually useful. So many business ideas are really founded on what kind of data you have access to. APIs like marketstack open those doors in affordable ways to everyone.

I think picking out technology stacks is fun, even just to think about. Like doesn’t it sound fun to build an Electron app with something like Electron Forge so you have a React+webpack setup, then hit the marketstack API for interesting market data and use nivo to build charts, chucking whatever data you need to save into Fauna?

Direct Link to ArticlePermalink


The post marketstack: A Market Data API appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

How to Make a Monthly Calendar With Real Data

Have you ever seen a calendar on a webpage and thought, how the heck did they did that? For something like that, it might be natural to reach for a plugin, or even an embedded Google Calendar, but it’s actually a lot more straightforward to make one than you might think and only requires the trifecta of HTML, CSS and JavaScript. Let’s make one together!

I’ve set up a demo over at CodeSandbox so you can see what we’re aiming for.

Let’s first identify some requirements for what the calendar should do. It should:

  • Display a month grid for a given month
  • Display dates from the previous and next months to so the grid is always full
  • Indicate current date
  • Show the name of the currently selected month
  • Navigate to the previous and next month
  • Allow the user to navigate back to current month with a single click

Oh, and we’ll build this as a single page application that fetches calendar dates from Day.js, a super light utility library.

We’re going to shy away from choosing a specific framework to keep things easy. For this setup, I’m using Parcel for package management so I can write in Babel, bundle things up, and manage the one and only dependency for the project. Check out the package.json file over at CodeSandbox for specifics.

Step 1: Start with the basic markup and styles

Let’s start with creating a basic template for our calendar. This doesn’t need to be anything fancy. But it also should be done without resorting to tables.

We can outline our markup as three layers where we have:

  • A section for the calendar header. This will show the currently selected month and the elements responsible for paginating between months.
  • A section for the calendar grid header. Again, we’re not reaching form tables, but this would be sort of like a table header that holds a list containing the days of the week, starting with Monday.
  • The calendar grid. You know, each day in the current month, represented as a square in the grid.

Let’s write this up in a file called index.js. This can go inside a src folder in the project folder. We will indeed have an index.html file in the project root that imports our work, but the primary markup will live in the JavaScript file.

<!-- index.js --> document.getElementById("app").innerHTML = ` <!-- Parent container for the calendar month --> <div class="calendar-month">   <!-- The calendar header -->   <section class="calendar-month-header">     <!-- Month name -->     <div       id="selected-month"       class="calendar-month-header-selected-month"     >       July 2020     </div> 
     <!-- Pagination -->     <div class="calendar-month-header-selectors">       <span id="previous-month-selector"><</span>       <span id="present-month-selector">Today</span>       <span id="next-month-selector">></span>     </div>   </section>      <!-- Calendar grid header -->   <ol     id="days-of-week"     class="day-of-week"   >     <li>Mon</li>     ...     <li>Sun</li>   </ol> 
   <!-- Calendar grid -->   <ol     id="calendar-days"     class="date-grid"   >     <li class="calendar-day">       <span>         1       </span>       ...       <span>         29       </span>     </li>   </ol> </div> `;

Let’s go ahead and import this file into that index.html file that lives in the root directory of the project. Nothing special happening here. It’s merely HTML boilerplate with an element that’s targeted by our app and registers our index.js file.

<!DOCTYPE html> <html>   <head>     <title>Parcel Sandbox</title>     <meta charset="UTF-8" />   </head>    <body>     <div id="app"></div>      <script src="src/index.js"></script>   </body> </html>

Now that we have some markup to work with, let’s style it up a bit so we have a good visual to start with. Specifically, we’re going to:

  • Position the elements using flexbox
  • Create a calendar frame using CSS grid
  • Position the labels within the cells

First up, let’s create a new styles.css file in the same src folder where we have index.js and drop this in:

body {   --grey-100: #e4e9f0;   --grey-200: #cfd7e3;   --grey-300: #b5c0cd;   --grey-800: #3e4e63;   --grid-gap: 1px;   --day-label-size: 20px; }  .calendar-month {   position: relative;   /* Color of the day cell borders */   background-color: var(--grey-200);   border: solid 1px var(--grey-200); } 
 /* Month indicator and selectors positioning */ .calendar-month-header {   display: flex;   justify-content: space-between;   background-color: #fff;   padding: 10px; } 
 /* Month indicator */ .calendar-month-header-selected-month {   font-size: 24px;   font-weight: 600; } 
 /* Month selectors positioning */ .calendar-month-header-selectors {   display: flex;   align-items: center;   justify-content: space-between;   width: 80px; } 
 .calendar-month-header-selectors > * {   cursor: pointer; } 
 /* | Mon | Tue | Wed | Thu | Fri | Sat | Sun | */ .day-of-week {   color: var(--grey-800);   font-size: 18px;   background-color: #fff;   padding-bottom: 5px;   padding-top: 10px; } 
 .day-of-week, .days-grid {   /* 7 equal columns for weekdays and days cells */   display: grid;   grid-template-columns: repeat(7, 1fr); } 
 .day-of-week > * {   /* Position the weekday label within the cell */   text-align: right;   padding-right: 5px; } 
 .days-grid {   height: 100%;   position: relative;   /* Show border between the days */   grid-column-gap: var(--grid-gap);   grid-row-gap: var(--grid-gap);   border-top: solid 1px var(--grey-200); } 
 .calendar-day {   position: relative;   min-height: 100px;   font-size: 16px;   background-color: #fff;   color: var(--grey-800);   padding: 5px; } 
 /* Position the day label within the day cell */ .calendar-day > span {   display: flex;   justify-content: center;   align-items: center;   position: absolute;   right: 2px;   width: var(--day-label-size);   height: var(--day-label-size); }

The key part that sets up our grid is this:

.day-of-week, .days-grid {   /* 7 equal columns for weekdays and days cells */   display: grid;   grid-template-columns: repeat(7, 1fr); }

Notice that both the calendar grid header and the calendar grid itself are using CSS grid to lay things out. We know there will always be seven days in a week, so that allows us to use the repeat() function to create seven columns that are proportional to one another. We’re also declaring a min-height of 100px on each date of the calendar to make sure the rows are consistent.

We need to hook these styles up with the markup, so let’s add this to the top of our index.js file:

import "./styles.css";

This is a good spot to stop and see what we have so far.

Step 2: Setting up current month calendar

As you probably noticed, the template only contains static data at the moment. The month is hardcoded as July and the day numbers are hardcoded as well. That’s where Day.js comes into play. It provides all the data we need to properly place dates on the correct days of the week for a given month using real calendar data. It allows us to get and set anything from the start date of a month to all the date formatting options we need to display the data.

We will:

  • Get the current month
  • Calculate where the days should be placed (weekdays)
  • Calculate the days for displaying dates from the previous and next months
  • Put all of the days together in a single array

First, we need to import Day.js and remove all static HTML (selected month, weekdays and days). We’ll do that by adding this to our index.js file right above where we imported the styles:

import dayjs from "dayjs";

We’re also going to lean on a couple of Day.js plugins for help. WeekDay helps us set the first day of the week. Some prefer Sunday as the first day of the week. Other prefer Monday. Heck, in some cases, it makes sense to start with Friday. We’re going to start with Monday.

The weekOfYear plugin returns the numeric value for the current week out of all weeks in the year. There are 52 weeks in a year, so we’d say that the week starting January 1 is the the first week of the year, and so on.

So here what we put into index.js right after our import statements:

const weekday = require("dayjs/plugin/weekday"); const weekOfYear = require("dayjs/plugin/weekOfYear"); 
 dayjs.extend(weekday); dayjs.extend(weekOfYear);

Once we strip out the hardocded calendar values, Here’s what we have in index.js so far:

import dayjs from "dayjs"; import "./styles.css"; const weekday = require("dayjs/plugin/weekday"); const weekOfYear = require("dayjs/plugin/weekOfYear"); 
 dayjs.extend(weekday); dayjs.extend(weekOfYear); 
 document.getElementById("app").innerHTML = ` <div class="calendar-month">   <section class="calendar-month-header">     <div       id="selected-month"       class="calendar-month-header-selected-month"     >     </div>     <div class="calendar-month-header-selectors">       <span id="previous-month-selector"><</span>       <span id="present-month-selector">Today</span>       <span id="next-month-selector">></span>     </div>   </section>      <ul     id="days-of-week"     class="day-of-week"   >   </ul>   <ul     id="calendar-days"     class="days-grid"   >   </ul> </div> `;

Now let’s set few constants. Specifically, we want to construct an array of days of the weeks (i.e. Monday, Tuesday, Wednesday, etc.):

const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

Then, we want to fetch the current year and set in in YYYY format:

const INITIAL_YEAR = dayjs().format("YYYY");

And we want to set the current month as the starting point when initially loading the calendar, where M formats the month as a numeric value (e.g. January equals 1):

const INITIAL_MONTH = dayjs().format("M");

Let’s go and populate our calendar grid header with the days of the week. First we grab the proper element (#days-of-week), then we iterate through our WEEKDAYS array, creating a list item element for each item in the array while setting the name for each one:

// Select the calendar grid header element const daysOfWeekElement = document.getElementById("days-of-week"); 
 // Loop through the array of weekdays WEEKDAYS.forEach(weekday => {   // For each item in the array, make a list item element   const weekDayElement = document.createElement("li");   // Append a child element inside the list item...   daysOfWeekElement.appendChild(weekDayElement);   /// ...that contains the value in the array   weekDayElement.innerText = weekday; });

Step 3: Creating the calendar grid

That was pretty straightforward but now the real fun starts as we will now play with the calendar grid. Let’s stop for a second a think what we really need to do to get that right.

First, we want the date numbers to fall in the correct weekday columns. For example, July 1, 2020 is on a Wednesday. That’s where the date numbering should start.

If the first of the month falls on Wednesday, then that means we’ll have empty grid items for Monday and Tuesday in the first week. The last day of the month is July 31, which falls on a Friday. That means Saturday and Sunday will be empty in the last week of the grid. We went to fill those with the trailing and leading dates of the previous and next months, respectively, so that the calendar grid is always full.

Create days for the current month

To add the days for the current month to the grid, we need to know how many days exist in the current month. We can get that using the daysInMonth method provided by Day.js. Let’s create a helper method for that.

function getNumberOfDaysInMonth(year, month) {   return dayjs(`$ {year}-$ {month}-01`).daysInMonth() }

When we know that, we create an empty array with a length that’s equal to number of days in the current month. Then we map() that array and create a day object for each one. The object we create has an arbitrary structure, so you can add other properties if you need them.

In this example, though, we need a date property that will be used to check if a particular date is the current day. We’ll also return a dayOfMonth property that acts as the label (e.g. 1, 2, 3 and so on). isCurrentMonth checks whether the date is in the current month or outside of it. If it is outside the current month, we will style those so folks know they are outside the range of the current month.

function createDaysForCurrentMonth(year, month) {   return [...Array(getNumberOfDaysInMonth(year, month))].map((day, index) => {     return {       date: dayjs(`$ {year}-$ {month}-$ {index + 1}`).format("YYYY-MM-DD"),       dayOfMonth: index + 1,       isCurrentMonth: true     };   }); }

Add dates from the previous month to the calendar grid

To get dates from the previous month to display in the current month, we need to check what is the weekday of the first day in selected month. That’s where we can use the WeekDay plugin for Day.js. Let’s create a helper method for that.

function getWeekday(date) {   return dayjs(date).weekday() }

Then, based on that, we need to check which day was the last Monday in the previous month. We need that value to know how many days from the previous month should be visible in the current month view. We can get that by subtracting the weekday value from the first day of the current month. For example, if first day of the month is Wednesday, we need to subtract 3 days to get last Monday of the previous month. Having that value allows us to create an array of day objects starting from the last Monday of the previous month through the end of that month.

function createDaysForPreviousMonth(year, month) {   const firstDayOfTheMonthWeekday = getWeekday(currentMonthDays[0].date); 
   const previousMonth = dayjs(`$ {year}-$ {month}-01`).subtract(1, "month");      const previousMonthLastMondayDayOfMonth = dayjs(     currentMonthDays[0].date   ).subtract(firstDayOfTheMonthWeekday - 1, "day").date(); 
   // Account for first day of the month on  a Sunday (firstDayOfTheMonthWeekday === 0)   const visibleNumberOfDaysFromPreviousMonth = firstDayOfTheMonthWeekday ? firstDayOfTheMonthWeekday - 1 : 6 
   return [...Array(visibleNumberOfDaysFromPreviousMonth)].map((day, index) => {         return {       date: dayjs(         `$ {previousMonth.year()}-$ {previousMonth.month() + 1}-$ {previousMonthLastMondayDayOfMonth + index}`       ).format("YYYY-MM-DD"),       dayOfMonth: previousMonthLastMondayDayOfMonth + index,       isCurrentMonth: false     };   }); }

Add dates from the next month to the calendar grid

Now, let’s do the reverse and calculate which days we need from the next month to fill in the grid for the current month. Fortunately, we can use the same helper we just created for the previous month calculation. The difference is that we will calculate how many days from the next month should be visible by subtracting that weekday numeric value from 7.

So, for example, if the last day of the month is Saturday, we need to subtract 1 day from 7 to construct an array of dates needed from next month (Sunday).

function createDaysForNextMonth(year, month) {   const lastDayOfTheMonthWeekday = getWeekday(`$ {year}-$ {month}-$ {currentMonthDays.length}`) 
   const visibleNumberOfDaysFromNextMonth = lastDayOfTheMonthWeekday ? 7 - lastDayOfTheMonthWeekday : lastDayOfTheMonthWeekday 
   return [...Array(visibleNumberOfDaysFromNextMonth)].map((day, index) => {     return {       date: dayjs(`$ {year}-$ {Number(month) + 1}-$ {index + 1}`).format("YYYY-MM-DD"),       dayOfMonth: index + 1,       isCurrentMonth: false     }   }) }

OK, we know how to create all days we need, let’s use the methods we just created and then merge all days into a single array of all the days we want to show in the current month, including filler dates from the previous and next months.

let currentMonthDays = createDaysForCurrentMonth(INITIAL_YEAR, INITIAL_MONTH) let previousMonthDays = createDaysForPreviousMonth(INITIAL_YEAR, INITIAL_MONTH, currentMonthDays[0]) let nextMonthDays = createDaysForNextMonth(INITIAL_YEAR, INITIAL_MONTH) 
 let days = [...this.previousMonthDays, ...this.currentMonthDays, ...this.nextMonthDays]

Here’s everything we just covered put together in index.js:

// Same as before ... 
 const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; const INITIAL_YEAR = dayjs().format("YYYY"); const INITIAL_MONTH = dayjs().format("M"); const daysOfWeekElement = document.getElementById("days-of-week"); 
 // Add weekdays to calendar header WEEKDAYS.forEach(weekday => {   const weekDayElement = document.createElement("li");   daysOfWeekElement.appendChild(weekDayElement);   weekDayElement.innerText = weekday; }); 
 let currentMonthDays = createDaysForCurrentMonth(INITIAL_YEAR, INITIAL_MONTH); let previousMonthDays = createDaysForPreviousMonth(INITIAL_YEAR, INITIAL_MONTH); let nextMonthDays = createDaysForNextMonth(INITIAL_YEAR, INITIAL_MONTH); let days = [...previousMonthDays, ...currentMonthDays, ...nextMonthDays]; 
 console.log(days); 
 function getNumberOfDaysInMonth(year, month) {   return dayjs(`$ {year}-$ {month}-01`).daysInMonth(); } 
 function createDaysForCurrentMonth(year, month) {   return [...Array(getNumberOfDaysInMonth(year, month))].map((day, index) => {     return {       date: dayjs(`$ {year}-$ {month}-$ {index + 1}`).format("YYYY-MM-DD"),       dayOfMonth: index + 1,       isCurrentMonth: true     };   }); } 
 function createDaysForPreviousMonth(year, month) {   const firstDayOfTheMonthWeekday = getWeekday(currentMonthDays[0].date);   const previousMonth = dayjs(`$ {year}-$ {month}-01`).subtract(1, "month");   const previousMonthLastMondayDayOfMonth = dayjs(currentMonthDays[0].date)     .subtract(firstDayOfTheMonthWeekday - 1, "day")     .date();   // Cover first day of the month being sunday (firstDayOfTheMonthWeekday === 0)   const visibleNumberOfDaysFromPreviousMonth = firstDayOfTheMonthWeekday     ? firstDayOfTheMonthWeekday - 1     : 6; 
   return [...Array(visibleNumberOfDaysFromPreviousMonth)].map((day, index) => {     return {       date: dayjs(         `$ {previousMonth.year()}-$ {previousMonth.month() +           1}-$ {previousMonthLastMondayDayOfMonth + index}`       ).format("YYYY-MM-DD"),       dayOfMonth: previousMonthLastMondayDayOfMonth + index,       isCurrentMonth: false     };   }); }  function createDaysForNextMonth(year, month) {   const lastDayOfTheMonthWeekday = getWeekday(     `$ {year}-$ {month}-$ {currentMonthDays.length}`   );   const nextMonth = dayjs(`$ {year}-$ {month}-01`).add(1, "month");   const visibleNumberOfDaysFromNextMonth = lastDayOfTheMonthWeekday     ? 7 - lastDayOfTheMonthWeekday     : lastDayOfTheMonthWeekday;   return [...Array(visibleNumberOfDaysFromNextMonth)].map((day, index) => {     return {       date: dayjs(         `$ {nextMonth.year()}-$ {nextMonth.month() + 1}-$ {index + 1}`       ).format("YYYY-MM-DD"),       dayOfMonth: index + 1,       isCurrentMonth: false     };   }); }  function getWeekday(date) {   return dayjs(date).weekday(); }

Step 4: Show calendar dates

OK, so we have the basic markup for our calendar, the data we need to display dates from the current month, plus dates from the previous and next month to fill in empty grid items. Now we need to append the dates to the calendar!

We already have a container for the calendar grid, #calendar-days. Let’s grab that element.

const calendarDaysElement = document.getElementById("calendar-days");

Now, let’s create a function that will append a day to our calendar view.

function appendDay(day, calendarDaysElement) {   const dayElement = document.createElement("li");   const dayElementClassList = dayElement.classList; 
   // Generic calendar day class   dayElementClassList.add("calendar-day"); 
   // Container for day of month number   const dayOfMonthElement = document.createElement("span"); 
   // Content   dayOfMonthElement.innerText = day.dayOfMonth; 
   // Add an extra class to differentiate current month days from prev/next month days   if (!day.isCurrentMonth) {     dayElementClassList.add("calendar-day--not-current");   } 
   // Append the element to the container element   dayElement.appendChild(dayOfMonthElement);   calendarDaysElement.appendChild(dayElement); }

Notice that we’re tossing in a check for the dates that  are coming from the previous and next months so that we can add a class to style them differently from the dates in the current month:

.calendar-day--not-current {   background-color: var(--grey-100);   color: var(--grey-300); }

That’s it! Our calendar should now look as we wanted.

Step 5: Select current month

What we have so far is pretty nice, but we want the user to be able to paginate from month-to-month forwards and backwards in time, starting from the current month. We have most of the logic in place, so all we really need to do is to add a click listener to the pagination buttons that re-runs the days calculation and re-draws the calendar with updated data.

Before we begin, let’s define variables for dates that are in the current month, previous month, and next month so we can reference them throughout the code.

let currentMonthDays; let previousMonthDays; let nextMonthDays;

Now, let’s create a method that will be responsible for re-calculating the calendar days and re-rendering the calendar when paginating to another month. We will call that function createCalendar. This method will accept two attributes — year and month  — and based on that, the calendar will re-render with new data and without a new page load.

The method will replace the header content to always show the selected month label.

function createCalendar(year = INITIAL_YEAR, month = INITIAL_MONTH) {   document.getElementById("selected-month").innerText = dayjs(     new Date(year, month - 1)   ).format("MMMM YYYY"); 
   // ...

Then it will grab the calendar days container and remove all existing days.

// ... 
   const calendarDaysElement = document.getElementById("calendar-days");   removeAllDayElements(calendarDaysElement); 
   // ...

When the calendar is cleared, it will calculate new days that should be displayed using the methods we created before.

//... 
 currentMonthDays = createDaysForCurrentMonth(   year,   month,   dayjs(`$ {year}-$ {month}-01`).daysInMonth() ); 
 previousMonthDays = createDaysForPreviousMonth(year, month); 
 nextMonthDays = createDaysForNextMonth(year, month); 
 const days = [...previousMonthDays, ...currentMonthDays, ...nextMonthDays]; 
 // ...

And, finally, it will append a day element for each day.

// ... days.forEach(day => {   appendDay(day, calendarDaysElement); });

There is one piece of logic still missing: a removeAllDayElements method that clears the existing calendar. This method takes the first calendar day element, removes it, and replaces it with another one. From there, it will run the logic in a loop until all of the elements are removed.

function removeAllDayElements(calendarDaysElement) {   let first = calendarDaysElement.firstElementChild; 
   while (first) {     first.remove();     first = calendarDaysElement.firstElementChild;   } }

Now we can reuse that logic when we want to change the month. Recall the first step when we created a static template for our component. We added these elements:

<div class="calendar-month-header-selectors">   <span id="previous-month-selector"><</span>   <span id="present-month-selector">Today</span>   <span id="next-month-selector">></span> </div>

These are the controls for paginating between months. To change it, we need to store the currently selected month. Let’s create a variable to keep track of what that is and set its initial value to the present month.

let selectedMonth = dayjs(new Date(INITIAL_YEAR, INITIAL_MONTH - 1, 1));

Now, to make the selectors work, we need a bit of JavaScript. To make it more readable, we will create another method called initMonthSelectors and we will keep the logic there. This method will add event listeners to the selector elements. It will listen for click events and update the value of selectedMonth to the name of the newly selected month before running the createCalendar method with proper year and month values.

function initMonthSelectors() {   document   .getElementById("previous-month-selector")   .addEventListener("click", function() {     selectedMonth = dayjs(selectedMonth).subtract(1, "month");     createCalendar(selectedMonth.format("YYYY"), selectedMonth.format("M"));   }); 
   document   .getElementById("present-month-selector")   .addEventListener("click", function() {     selectedMonth = dayjs(new Date(INITIAL_YEAR, INITIAL_MONTH - 1, 1));     createCalendar(selectedMonth.format("YYYY"), selectedMonth.format("M"));   }); 
   document   .getElementById("next-month-selector")   .addEventListener("click", function() {     selectedMonth = dayjs(selectedMonth).add(1, "month");     createCalendar(selectedMonth.format("YYYY"), selectedMonth.format("M"));   }); }

That’s it! Our calendar is ready. While that’s great and all, it would be even nicer if we could mark the current date so it stands out from the rest. That shouldn’t be very hard. We are already styling days that are not in the selected month, so let’s do similar thing to that.

We’ll create a variable that’s set for today:

const TODAY = dayjs().format("YYYY-MM-DD");

Then, in the appendDay method where we apply a class for dates outside the current month, we have to add another check to see if the element is today’s date. If it is, we’ll add a class to that element:

function appendDay(day, calendarDaysElement) {   // ...   if (day.date === TODAY) {     dayElementClassList.add("calendar-day--today");   } }

Now we can style it!

.calendar-day--today {   padding-top: 4px; } 
 .calendar-day--today > div {   color: #fff;   border-radius: 9999px;   background-color: var(--grey-800); }

Voilà, there we have it! Check out the final demo to see everything put together.


The post How to Make a Monthly Calendar With Real Data appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Creating a Gatsby Site with WordPress Data

In my previous article last week, I mentioned creating a partially ported WordPress-Gatsby site. This article is a continuation with a step-by-step walkthrough under the hood.

Gatsby, a React-based framework for static sites, is attracting attention not only from JavaScript developers but also from the WordPress developers and users. Many WordPress users find its features appealing, like ultra fast image handling and improved security protection from hackers, but would like to use them while continuing to use the WordPress admin and editor to manage content.

Chris has covered the idea of combining Gatsby and WordPress before here on CSS-Tricks. As a WordPress enthusiast, I decided to give it a try. This article is based on what I learned and documented along the way.

Please note that WPGraphQL and gatsby-cli are in constant development with breaking changes in later versions. This project was done using WPGraphQL 0.8.3, gatsby-source-wpgraphql 2.5.1 and gatsby-cli 2.12.21. Unlike WordPress, newer WPGraphQL releases do not support backward compatibility. Please consult the official WPGraphQL & Gatsby doc for the latest changes and proceed with caution before using.

There are ready-to-use projects in the Gatsby starters library. Two great examples are Alexandra Spalato’s gatsby-wordpress-theme-blog and the twenty-nineteen-gatsby-theme by Zac Gordon and Muhammad Muhsin.

Prerequisites

If you want to follow along, here’s what you’ll need:

Assets and resources

Because I had already done a few Gatsby learning projects in the past, I had some assets like typography, layouts, and other reusable components that I could apply here. I had also gone through the following recent tutorial guides, which helped me to prepare for this project.

Henrick Wirth’s guide is super comprehension and thorough. Jason’s step-by-step article is a great resource and even includes super helpful videos that help see the process take place. Muhammad’s article helps explain how static pages are created with Gatsby’s createPages API and breaks down various functions, template files and React components along the way.

I largely followed Henrik’s guide and divided this article into similar sections. Henrik’s guide includes image handling and adding PageBuilder with ACF Flexible Content features which we don’t get into here.

Section 1: Setting up WordPress and Gatsby

First, let’s set up a WordPress site for a data source. This could be an already existing site or a fresh one. Even a local WordPress installation is fine. I decided to start with a new test WordPress site for this project using the Twenty Twenty theme that ships with WordPress.

Install the WPGraphQL and WPGraphiQL plugins

Let’s start by installing a couple of plugins in WordPress. We’ll use WPGraphQL to enable GraphQL API in WordPress and open up WordPress as a data source. We’ll also use WPGraphiQL (note the “i” in the name). This one is actually optional, but it creates an interface for testing GraphQL queries directly in the WordPress dashboard, which is super handy. You may notice that I’m linking to the GitHub repos for the plugins instead of the WordPress Plugin Directory and that’s intentional — neither plugin is available in the directory at the time of this writing. As such, you’ll download the ZIP files and manually install them in WordPress via the /wp-content/plugins directory.

Once activated, the GraphiQL API is displayed in the WordPress dashboard.

The GraphiQL API provides a playground to test GraphQL queries from WordPress site.

Screenshot of the GraphQL playground interface in the WordPress dashboard.
The GraphiQL screen provides three panels: one to navigate between different objects (left), one to query data (center),and one to visualize the returned data (right).

Setting up a local Gatsby site

We will setup a local Gatsby site by installing Gatsby’s starter default in the wordpress-gatsby directory of the project with this in the command line:

#! create a new Gatsby site using the default starter gatsby new wordpress-gatsby https://github.com/gatsbyjs/gatsby-starter-default

Restart the server with gatsby develop, then let’s navigate to localhost:8000 in a new browser tab. We should get a starter page in the browser.

A link to how to create a gatsby site locally is available from the Gatsby documentation.

Next, we’re going to install and configure the gatsby-source-graphql plugin. Just as we did when setting up WordPress, we have to install and configure WPGraphQL in the Gatsby site.

#! install wpgraphql plugin #! add with yarn yarn add gatsby-source-graphql #! install with npm npm install --save gatsby-source-graphql

OK, now it’s time to configure the gatsby-source-graphql plugin. Open up the gatsby-config.js file and let’s use these settings:

// plugin configuration module.exports = {   plugins: [     {       resolve: "gatsby-source-graphql",       options: {         typeName: "WPGraphQL",         fieldName: "wpcontent",         // GraphQL endpoint, relative to your WordPress home URL.         url: "https://tinjurewp.com/wp-gatsby/graphql",         // GraphQL endpoint using env variable        // url: "$ {process.env.WORDPRESS_URL}/graphql",       },     },   ], }

How did I come up with this exact configuration? I strictly followed what’s described in the Gatsby docs. The plugin was added to the Gatsby instance by specifying the URL of the GraphQL endpoint (highlighted above) and two configuration options: typeName, a remote schema query type, and fieldName, which is available in the Gatsby query. Please note, the latest WPGraphQL doc suggest using fieldName: "wpcontent" instead of "wpgraphql"as described in the guide.

Alternative setup: Use the dotenv module

Optionally, we could have set things up  using the dotenv npm module to define environment variables that are used to customize the development environment. Henrik uses this method in his guide as well.

If you’re using this method, a variable in the .env.production plugin configuration file, like WORDPRESS_URL, can be defined and used instead of exposing the WordPress URL.

# .env.production # Don't put any sensible data here!!! WORDPRESS_URL=https://tinjurewp.com/wp-gatsby/

My test environment equally exposes the WordPress instance and data to WPGraphQL.

Colby Fayock has a helpful step-by-step guide on using environmental variables with Gatsby and Netlify.

After re-starting the development server, the WPGraphQL API is available with Gatsby to query and retrieve the specific data that’s queried from the WordPress site and display it on a Gatsby site through the localhost GraphQL URL at https//localhost:8000/___graphql/.

Screenshot showing the GraphQL query interface with the explorer on the left, the query in the center, and the returned data on the right.
Note that, unlike in WordPress site itself, the data here is exposed to WPGraphQL. We can query against the WPGraphQL API to display any field from the WordPress site.

Section 2: Porting posts and pages from WordPress

In Gatsby, posts and pages can be created at build time by querying data with GraphQL and mapping the query results to posts or page templates. The process is described in a Gatsby tutorial on programmatically creating pages from data. Gatsby make use of two APIs, onCreateNode and createPages, and tutorial contains a detailed explanation on how they are implemented.

The code snippets here come from Henrik’s guide. Because of the way WordPress stores data in its database under different data types and categories, porting all the contents turns out to be less than straightforward. However, with prior knowledge of creating pages and posts with Gatsby createPages API and Node API, I was able to follow along. There’s also a lot of real-world starter sites that can be referenced as examples.

Step 1: Add posts and pages content in a WordPress site

Add some posts and pages in WordPress site if you don’t have any already. Before creating page for that content, we need to delete index.js and page-2.js from the pages folder of the Gatsby site. These two files seem to interfere with the ported WordPress data.

Step 2: Create page and post template

We’re going to create two template files  for our content, one for posts (/src/templates/posts/index.js) and one for pages (/src/templates/pages/index.js).

Here’s our post template. Basically, we’re using the post title twice (one as the SEO page title and one as the post heading) and the post content as a Post component.

// src/templates/post/index.js import React  from "react" import Layout from "../../components/layout" import SEO from "../../components/SEO" 
 const Post = ({ pageContext }) => {   const post = pageContext.post 
   return (     <Layout>       <SEO title={post.title} /> 
       <h1> {post.title} </h1>       <div dangerouslySetInnerHTML={{__html: post.content}} /> 
     </Layout>   ) } 
 export default Post

We’ll do nearly the same thing for the page template:

//src/templates/pages/index.js  import React  from "react" import Layout from "../../components/layout" import SEO from "../../components/seo" 
 const Page = ({ pageContext }) => {   const page = pageContext.page 
   return (     <Layout>       <SEO title={page.title} /> 
       <h1>{page.title}</h1>       <div dangerouslySetInnerHTML={{__html: page.content}} /> 
     </Layout>   ) } 
 export default Page

Step 3: Create static posts and pages with the createPages API

Note that the entire code we’re covering here can be written in the node.js file. However, for readability purposes, posts and pages are separated in a folder named create in the project’s root directory following Henrik’s Guide.

We’re going to get our hands dirty with the GraphQL createPages API! We’ll start by adding the following to gatsby-node.js.

// gatsby-node.js const createPages = require("./create/createPages") const createPosts = require("./create/createPosts") 
  exports.createPagesStatefully = async ({ graphql, actions, reporter }, options) => {   await createPages({ actions, graphql, reporter }, options)   await createPosts({ actions, graphql, reporter }, options)  }

Muhammad’s post makes a good point that’s worth calling out here: 

The createPages API is part of the Node APIs that Gatsby exposes. It essentially instructs Gatsby to add pages. Within this we are calling some methods using async/await (a feature of ECMAScript 2017).

In other words: both functions create relevant static pages. With that in mind, let’s define what data we want to use and fetch that data in the create/createPages.js file. Sorry for the big code dump, but Henrik’s comments help explain what’s happening.

//create/createPages.js const pageTemplate = require.resolve('../src/templates/page/index.js'); 
 const GET_PAGES = `   query GET_PAGES($ first:Int $ after:String) {     wpgraphql {       pages(         first: $ first         after: $ after         # This will make sure to only get the parent nodes and no children         where: {           parent: null          }       ) {         pageInfo {           hasNextPage           endCursor         }         nodes {           id           title           pageId           content           uri           isFrontPage         }       }     }   } ` 
 const allPages = [] let pageNumber = 0 const itemsPerPage = 10 
 /** This is the export which Gatbsy will use to process.  * @param { actions, graphql }  * @returns {Promise<void>} */ module.exports = async ({ actions, graphql, reporter }, options) => { 
   /** This is the method from Gatsby that we're going    * to use to create pages in our static site. */   const { createPage } = actions   /** Fetch pages method. This accepts variables to alter    * the query. The variable `first` controls how many items to    * request per fetch and the `after` controls where to start in    * the dataset.    * @param variables    * @returns {Promise<*>} */   const fetchPages = async (variables) =>     /** Fetch pages using the GET_PAGES query and the variables passed in. */     await graphql(GET_PAGES, variables).then(({ data }) => {       /** Extract the data from the GraphQL query results */       const {         wpgraphql: {           pages: {             nodes,             pageInfo: { hasNextPage, endCursor },           },         },       } = data 
       /** Map over the pages for later creation */       nodes       && nodes.map((pages) => {         allPages.push(pages)       }) 
       /** If there's another page, fetch more        * so we can have all the data we need. */       if (hasNextPage) {         pageNumber++         reporter.info(`fetch page $ {pageNumber} of pages...`)         return fetchPages({ first: itemsPerPage, after: endCursor })       } 
       /** Once we're done, return all the pages        * so we can create the necessary pages with        * all the data on hand. */       return allPages     }) 
   /** Kick off our `fetchPages` method which will get us all    * the pages we need to create individual pages. */   await fetchPages({ first: itemsPerPage, after: null }).then((wpPages) => { 
     wpPages && wpPages.map((page) => {       let pagePath = `$ {page.uri}` 
       /** If the page is the front page, the page path should not be the uri,        * but the root path '/'. */       if(page.isFrontPage) {         pagePath = '/'       } 
       createPage({         path: pagePath,         component: pageTemplate,         context: {           page: page,         },       }) 
       reporter.info(`page created: $ {page.uri}`)     }) 
     reporter.info(`# -----> PAGES TOTAL: $ {wpPages.length}`)   }) }

Again, Muhammad’s post is excellent help because it breaks down what the createPages.js and createPosts.js functions can do. Henrik’s guide also provides helpful comments for each step. 

Step 4: Creating posts

The createPosts.js file is almost identical to createPages.js. The sole difference is prefixing the path with blog/ and replacing the “page” with “posts” throughout the code.

If we stop here and restart the development server with gatsby develop in the terminal, the develop log displays the page buildup.

Now, if we open up localhost:8000 in a browser, we get a 404 error. 

That might be off-putting, but it’s all good. Clicking any of the links on the 404 page displays the correct page or post from the WordPress data source. For example, if the sample-page link is clicked, it displays sample page content from WordPress in the browser.


Section 3: Working with navigation

Let’s move on to the navigation menu for our site. WordPress has a navigation management feature that allows us to construct menus using links to pages, posts, archives, taxonomies, and even custom links. We want to create navigation for a main menu in WordPress and send it to GraphQL where we can query it for our own site.

Navigation links — including page and post links — are created in Gatsby using the Gatsby Link API, which uses both the built-in <Link> component and navigate function. The <Link> component is used for linking to internal pages, but not to external links.

Porting navigation menu from the WordPress into Gatsby site turns out to be a tricky little task that requires creating <Menu> and <MenuItems> components and refactoring the <Layout> component accordingly. Here’s how that works.

Code snippets used in this section are taken directly from Henrik’s guide for completeness, however these code snippets appear to be pretty standard used in other Gatsby WordPress starters with little variation.

Step 1: Create a WordPress menu

As described in the guide, it is important to set up a menu called “PRIMARY” which is defined in the Twenty Twenty theme. We’re going to toss three links in there:

  • Home: A link to our homepage, which will be a custom link pointing to our site’s index.
  • Sample Page: The default page that WordPress creates on a new WordPress installation.
  • Front Page: This is typically the name given for the homepage in WordPress. You’ll need to create this page in the editor.

Step 2: Query Menu Items with GraphiQL Explorer

Next up, we’ll write a query for the menu items from the GraphiQL interface. Notice that we can use the explorer to practically write it for us by checking a few boxes.

query MyQuery {   menuItems(where: {location: PRIMARY}) {     nodes {       label       url       title       target     }   } }

Step 3: Create menu and link utility components in Gatsby

See how the URLs in the data are absolute, displaying the full address? We’ll need a utility function to translate those to relative URLs, again, because that’s what the <Link> component supports.

Henrik’s guide provides the following utility function for converting absolute WordPress URLs to relative URLs that are required for Gatsby:

// src/utils/index.js /** Parses a menu item object and returns Gatsby-field URI.  * @param {object} menuItem a single menu item  * @param wordPressUrl  * @param blogURI */ export const CreateLocalLink = (menuItem, wordPressUrl, blogURI='blog/') => {   const { url, connectedObject } = menuItem; 
   if (url === '#') {     return null;   }   /** Always want to pull of our API URL */   let newUri = url.replace(wordPressUrl, ''); 
   /** If it's a blog link, respect the users blogURI setting */   if (connectedObject && connectedObject.__typename === 'WPGraphQL_Post') {     newUri = blogURI + newUri;   } 
   return newUri; };

Step 4: Create a menu item component

The next step is to create a <MenuItem> component which utilizes the utility function created in the previous step. The result is a fully formed link that gets consumed by the Gatsby site menu.

// src/components/MenuItem.js import React from "react" import { CreateLocalLink } from "../utils" import { Link } from "gatsby" 
 const MenuItem = ({ menuItem, wordPressUrl }) => {   return (     <Link style={{marginRight: '20px' }}      to={CreateLocalLink(menuItem, wordPressUrl)}>      {menuItem.label}      </Link>   ) } 
 export default MenuItem

Step 5: Creating a menu component 

OK, we created URLs and a functional <MenuItem> component. Let’s create a new <Menu> component where our <MenuItem> components can go. The Gatsby StaticQuery API is used to query all primary menu items with GraphQL.

// src/components/Menu.js import React from "react" import { StaticQuery, graphql } from "gatsby" import MenuItem from "./MenuItem" 
 /** Define MenuItem fragment and get all primary menu items */ const MENU_QUERY = graphql`   fragment MenuItem on WPGraphQL_MenuItem {     id     label     url     title     target   } 
   query GETMAINMENU {     wpgraphql {       menuItems(where: {location: PRIMARY}) {         nodes {           ...MenuItem         }       }       generalSettings {         url       }     }   } ` 
 const Menu = () => {   return (     <StaticQuery       query={MENU_QUERY}       render={(data) => {         if (data.wpgraphql.menuItems) {           const menuItems = data.wpgraphql.menuItems.nodes           const wordPressUrl = data.wpgraphql.generalSettings.url 
        return (          <div style={{ marginBottom: "20px" }}>            {              menuItems &&              menuItems.map((menuItem) => (                <MenuItem key={menuItem.id}                menuItem={menuItem} wordPressUrl={wordPressUrl}/>              )            )}          </div>        )       }       return null    }}   />   ) } 
 export default Menu

Step 6: Adding the menu to the layout component

At this point, we have everything we need to construct a Gatsby site menu using WordPress data. We just need to drop the <Menu> component into our <Layout> component:

// src/components/layout.js import React from "react" import PropTypes from "prop-types" import useSiteMetadata from '../components/siteMetadata'; import Header from "./Header" import Footer from "./Footer" import Menu from "./Menu" import "./layout.css" 
 const Layout = ({ children }) => {   const { title, description } = useSiteMetadata(); 
   return (     <section>       <Header siteTitle=Creating a Gatsby Site with WordPress Data description={description} />       <div       style={{ margin: `0 auto`, maxWidth: 960,                padding: `0 1.0875rem 1.45rem`,}}>         <Menu />         <main>{children}</main>         <Footer />       </div>     </section>   ) } 
 Layout.propTypes = {   children: PropTypes.node.isRequired, } 
 export default Layout

Step 7: Adding Support for External Link path

Gatsby’s documentation on the <Link> component explains that data coming from an external CMS, like WordPress, should ideally be inspected by the <Link> component and renders either with Gatsby’s <Link> or with a regular <a> tag accordingly. This ensures that any truly external links on the WordPress side remain absolute without conflicting the <Link> component.

This requires — you guessed it — another component that does exactly that. In Gatsby Docs it’s referred as <UniversalLink> which returns either a Gatsby-compatible <Link> component or a traditional <a> element:

//src/components/UniversalLink.js import React from "react" import { Link as GatsbyLink } from "gatsby" 
 const UniversalLink = ({ children, to, activeClassName, partiallyActive, ...other }) => {   const internal = /^/(?!/)/.test(to)   // Use Gatsby Link for internal links, and <a> for others   if (internal) {     return (       <GatsbyLink         to={to}         activeClassName={activeClassName}         partiallyActive={partiallyActive}         {...other}       >         {children}       </GatsbyLink>     )   }   return (     <a href={to} {...other}>       {children}     </a>   ) } export default UniversalLink

Now, let’s go back to our <MenuItem> component and update it to use the <UniversalLink>:

/ src/components/MenuItem.js import React from "react" import { CreateLocalLink } from "../utils" import UniversalLink from "./UniversalLink" 
 const MenuItem = ({ menuItem, wordPressUrl }) => {   return (     <UniversalLink style={{marginRight: '20px' }}       to={CreateLocalLink(menuItem, wordPressUrl)}>       {menuItem.label}     </UniversalLink>   ) } 
 export default MenuItem

Are you ready to check things out? Restart the local server with gatsby develop and the browser should display a navigation menu with items that contain links to relative page paths.

Screenshot showing the Same Page title and content under the site navigation, which includes the site title, and menu items.
Created in WordPress, displayed in Gatsby.

Section 4: Displaying blog posts in Gatsby

We’re in pretty good shape at this point, but there’s a big piece we’ve gotta tackle: displaying pages on the site. We’ll walk through the steps to make that happen in this section, specifically by  creating blog post templates as well as a couple of new components for post images before tying everything together in createPages.js and createPosts.js.

Have you already created your pages and posts in WordPress? If not, this is a good time to jump in there and do it.

Step 1: Add a globals variable file at the root of the project directory

// global variable const Globals = {   blogURI: '' } module.exports = Globals

The blogURI = ' ' URL path is used when the homepage setting in the WordPress admin (SettingsReading) is set to the “Your latest posts” option.

If you’re planning to use the “static page” option instead, then blogURI= 'blog' should be used in the global variables file.

Step 2: Create a blog template inside the templates folder

This template will handle displaying all published posts. Take note that two components — PostEntry and Pagination, both of which don’t exist yet) —are used here. We’ll get to those in just a moment.

// src/templates/post/blog.js import React from "react" import Layout from "../../components/Layout" import PostEntry from "../../components/PostEntry" import Pagination from "../../components/Pagination" import SEO from "../../components/SEO" 
 const Blog = ({ pageContext }) => {   const { nodes, pageNumber, hasNextPage, itemsPerPage, allPosts }   = pageContext 
   return (     <Layout>       <SEO         title="Blog"         description="Blog posts"         keywords={[`blog`]}       />       {nodes && nodes.map(post => <PostEntry key={post.postId}         post={post}/>)}       <Pagination         pageNumber={pageNumber}         hasNextPage={hasNextPage}         allPosts={allPosts}         itemsPerPage={itemsPerPage}       />     </Layout>   ) } 
 export default Blog

Step 3. Create a post entry component

This component is used within archive.js and other components to iterate through posts, displaying the post entry title, featured image (if any), excerpt and URL (aka “slug” in WordPress parlance).

// src/components/PostEntry.js import React from "react" import { Link } from "gatsby" import Image from "./Image" import { blogURI } from "../../globals" 
 const PostEntry = ({ post }) => {   const { uri, title, featuredImage, excerpt } = post
   return (     <div style={{ marginBottom: "30px" }}>       <header>         <Link to={`$ {blogURI}/$ {uri}/`}>           <h2 style={{ marginBottom: "5px" }}>Creating a Gatsby Site with WordPress Data</h2>           <Image image={featuredImage} style={{ margin: 0 }}/>         </Link>       </header> 
       <div dangerouslySetInnerHTML={{ __html: excerpt }}/>     </div>   ) }  export default PostEntry

Step 4: Create an (optional) image component

The Gatsby default starter comes with an Image component and that works just fine in most case. In this example, we’re fetching the image file used as the post’s featured image in WordPress and assign it a fallback image in case there is no featured image as described in Henrik’s guide.

// src/components/Image.js import React from "react" import { useStaticQuery, graphql } from "gatsby" 
 const Image = ({ image, withFallback = false, ...props }) => {   const data = useStaticQuery(graphql`     query {       fallBackImage: file(relativePath: { eq: "fallback.svg" }) {         publicURL       }     }   `) 
   /* Fallback image */   if (!image) {     return withFallback ? <img src={data.fallBackImage.publicURL}       alt={"Fallback"} {...props}/> : null   } 
   return <img src={image.sourceUrl} alt={image.altText} {...props}/> } 
 export default Image

If withFallback is set to false (like it is in the default Gatsby component file), then it will simply not render a DOM element.

Step 5: Create a Pagination component

The Pagination component allows us to display specified number of posts per page in the post index. WordPress has two types of pagination one that returns Next and Previous links to navigate between pages one at a time, and one that provides linked page numbers. We’re working with the former in this component:

// src/components/Pagination.js import React from "react" import { Link } from "gatsby" import { blogURI } from "../../globals" 
 const Pagination = ({ pageNumber, hasNextPage }) => {   if (pageNumber === 1 && !hasNextPage) return null 
   return (     <div style={{ margin: "60px auto 20px", textAlign: "center" }}>       <div className="nav-links">         {           pageNumber > 1 && (             <Link               className="prev page-numbers"               style={{                 padding: "8px 8px 5px 4px",               }}            to={pageNumber > 2 ? `$ {blogURI}/page/$ {pageNumber - 1}`: `$ {blogURI}/`}             >               ← <span> Previous</span>             </Link>           )         }           <span className="meta-nav screen-reader-text"></span>           {pageNumber}         </span> 
         {           hasNextPage && (             <Link               style={{                 padding: "4px 8px 5px 8px",               }}               className="next page-numbers"               to={`$ {blogURI}/page/$ {pageNumber + 1}`               }             >               <span>Next </span> →             </Link>           )         }       </div>     </div>   ) } 
 export default Pagination

There is a conditional statement on Line 7 that returns null if pageNumber === 1 && !hasNextPage. In other words, if the current page’s hasPageNumber is greater than 1, the Previous button (Lines 13-24) will display. Similarly, when the current page’s hasNextPage is at least 1, then the Next button (Lines 30-42) will display.

Step 6: Refactoring createPages

We need to clean up the createPages.js file to reflect all the work we’ve done since creating the file. The file simply becomes too big with everything it’s tracking. To keep our code organized and structured, we can use GraphQL fragments, which allow us “to split up complex queries into smaller, easier to understand components,” according to the documentation.

GraphQL fragments are reusable units which allows to construct sets of fields, and then include them in queries wherever needed.

If we follow Henrik’s guide, the GraphQL query fields for the post template and post preview are stored in the data.js file:

// src/templates/posts/data.js const PostTemplateFragment = `   fragment PostTemplateFragment on WPGraphQL_Post {     id     postId     title     content     link     featuredImage {       sourceUrl     }     categories {       nodes {         name         slug         id       }     }     tags {       nodes {         slug         name         id       }     }     author {       name       slug     }   } ` 
 const BlogPreviewFragment = `   fragment BlogPreviewFragment on WPGraphQL_Post {     id     postId     title     uri     date     slug     excerpt     content     featuredImage {       sourceUrl     }     author {       name       slug     }   } ` 
 module.exports.PostTemplateFragment = PostTemplateFragment module.exports.BlogPreviewFragment = BlogPreviewFragment

Next, refactoring the create/createPosts.js file as described in the guide requires adding the following code at the top section of createPosts.js (Lines 2-10), just above the const = GET_POSTS=` query statement on Line 4.

// create/createPosts.js const {   PostTemplateFragment,   BlogPreviewFragment, } = require("../src/templates/posts/data.js") 
 const { blogURI } = require("../globals") 
 const postTemplate = require.resolve("../src/templates/posts/index.js") const blogTemplate = require.resolve("../src/templates/posts/blog.js") 
 const GET_POSTS = `   # Here we make use of the imported fragments which are referenced above   $ {PostTemplateFragment}   $ {BlogPreviewFragment}   query GET_POSTS($ first:Int $ after:String) {     wpgraphql {       posts(        first: $ first        after: $ after        # This will make sure to only get the parent nodes and no children        where: {          parent: null        }       ) {          pageInfo {            hasNextPage            endCursor          }          nodes {            uri 
            # This is the fragment used for the Post Template            ...PostTemplateFragment 
            #This is the fragment used for the blog preview on archive pages           ...BlogPreviewFragment         }       }     }  } `

Here, the fragment strings created in the previous steps (Lines 9-10) are imported and registered outside the GET_POSTS query (Line 12) and used as fragments (Lines 34 and 37 ) inside the GET_POSTS($ first:Int $ after:String) query.

At the bottom of the createPosts.js file, the blogPage path is defined with global blogURI variable (Lines 36-41) and we’ve added the code to create paginated blog pages (Lines 99-111).

// create/createPosts.js // Previous code excluded 
 const allPosts = [] const blogPages = []; let pageNumber = 0; const itemsPerPage = 10; 
 /** This is the export which Gatbsy will use to process.  * @param { actions, graphql }  * @returns {Promise<void>} */ module.exports = async ({ actions, graphql, reporter }, options) => { 
   /** This is the method from Gatsby that we're going    * to use to create posts in our static site */   const { createPage } = actions 
   /** Fetch posts method. This accepts variables to alter    * the query. The variable `first` controls how many items to    * request per fetch and the `after` controls where to start in    * the dataset.    * @param variables    * @returns {Promise<*>} */   const fetchPosts = async (variables) =>     /** Fetch posts using the GET_POSTS query and the variables passed in */     await graphql(GET_POSTS, variables).then(({ data }) => {       /** Extract the data from the GraphQL query results */       const {         wpgraphql: {           posts: {             nodes,             pageInfo: { hasNextPage, endCursor },           },         },       } = data 
       /** Define the path for the paginated blog page.        * This is the url the page will live at        * @type {string} */       const blogPagePath = !variables.after         ? `$ {blogURI}/`         : `$ {blogURI}/page/$ {pageNumber + 1}` 
       /** Add config for the blogPage to the blogPage array for creating later        * @type {{        *   path: string,        *   component: string,        *   context: {nodes: *, pageNumber: number, hasNextPage: *} }} */       blogPages[pageNumber] = {         path: blogPagePath,         component: blogTemplate,         context: {           nodes,           pageNumber: pageNumber + 1,           hasNextPage,           itemsPerPage,           allPosts,         },       } 
       /** Map over the posts for later creation */       nodes       && nodes.map((posts) => {         allPosts.push(posts)       }) 
      /** If there's another post, fetch more so we can have all the data we need */       if (hasNextPage) {         pageNumber++         reporter.info(`fetch post $ {pageNumber} of posts...`)         return fetchPosts({ first: itemsPerPage, after: endCursor })       } 
       /** Once we're done, return all the posts so we can        * create the necessary posts with all the data on hand */       return allPosts     }) 
   /** Kick off our `fetchPosts` method which will get us all    * the posts we need to create individual posts */   await fetchPosts({ first: itemsPerPage, after: null }).then((wpPosts) => { 
     wpPosts && wpPosts.map((post) => {       /** Build post path based of theme blogURI setting */       const path = `$ {blogURI}$ {post.uri}` 
       createPage({         path: path,         component: postTemplate,         context: {           post: post,         },       }) 
       reporter.info(`post created:  $ {path}`)     }) 
     reporter.info(`# -----> POSTS TOTAL: $ {wpPosts.length}`) 
     /** Map over the `blogPages` array to create the      * paginated blog pages */     blogPages     && blogPages.map((blogPage) => {       if (blogPage.context.pageNumber === 1) {         blogPage.context.publisher = true;         blogPage.context.label = blogPage.path.replace('/', '');       }       createPage(blogPage);       reporter.info(`created blog archive page $ {blogPage.context.pageNumber}`);     });   }) }

The final updated create/createPosts.js and create/createPage.js files are available in this GitHub repository.

In his Twenty Nineteen porting tutorial post, Muhammad describes in great detail how static pages created with Gatsby’s createPage use nearly the same code and file structure used in this example. Nice to see some consistency forming between our references.

After re-starting local server with gatsby develop, we should display a screen in our browser now showing a loop of our published posts, containing the post title and excerpt.


Section 5: Styling and deployment

While styling, typography and deployment are all beyond the scope of what we’re covering here, we can touch on them a bit. The Gatsby’s documentation provides excellent resources on both styling and deployment/hosting options.

Basic site styling

Gatsby’s documentation is grouped by global CSS files, modular stylesheets and CSS-in-JS. There are other styling options available, including Typograpgy.js, Sass, JSS, Stylus and PostCSS.

While porting the Twenty Nineteen WordPress theme to Gatsby, Muhammad includes the theme’s styles so they can be used over on the Gatsby site. He cautions that some adjustments are needed since some units and values are incompatible with Gatsby. For example, he had to adjust vw units in CSS to use them with flexbox for some components. Similarly, porting Twenty Twenty theme to Gatsby, Henrik followed a similar process in his Gatsby starter -Twenty Twenty by porting over the Twenty Twenty stylesheet as well as fonts.

I decided to use Sass in my project. That requires installing gatsby-plugin-sass and its required node-sass dependency:

#! install node-sass & gatsby-sass yarn add node-sass gatsby-plugin-sass #! or with npm npm install --save node-sass gatsby-plugin-sass

Then the plugin can be added to gatsby-config.js and configured as shown here.

// gatsby-config.js module.exports = {   siteMetadata: {     plugins: [       `gatsby-plugin-sass`     ],   } }

Now we can write styles in .scss files and import them as we normally would in any other Sass project.

// using import in a component file import("./src/styles/global.scss") 
 // using require in the gatsby-browser.js file require('./src/styles/global.scss')

The .scss stylesheet can be imported by the global <Layout> component or added in gatsby-browser.js with a require statement. For this demo project, I’m using Gatsby’s default styling for the main page and I simply left post content as is. I refactored the Header.js file a bit with some very basic styling.

//src/components/Header.js import { Link } from "gatsby" import PropTypes from "prop-types" import React from "react" import useSiteMetadata from '../components/siteMetadata'; import Menu from "./Menu" import "../styles/header.css" 
 const Header = () =>{   const { title } = useSiteMetadata(); 
   return (     <header className="header">       <div className="nav-container brand">         <Link  to="/"> Creating a Gatsby Site with WordPress Data </Link>         {/* Menu here */}         <Menu />       </div>     </header>   ) } 
 Header.propTypes = {   siteTitle: PropTypes.string,   description: PropTypes.string, } 
 Header.defaultProps = {   siteTitle: ``,   description: ``, } 
 export default Header

This should give us the site header when we restart the server with gatsby develop.

Supporting WordPress block styles

I’m assuming you’re well familiar with the WordPress block editor if you’ve made it this far and know how blocks generally work. Since releasing the block editor, WordPress has maintained a separate set of styles specifically for block content.

That means we need an extra step to port those over to Gatsby with the theme styles. Jason Lengstorf demonstrates in his tutorial guide. First, the WordPress blocks package is installed:

# install wordpress/block-library npm install @wordpress/block-library # with yarn add yarn add @wordpress/block-library

Then we can import those styles into a Gatsby component. Let’s go with the <Layout> component:

// src/components/layout.js import React from "react"   import { Link } from "gatsby" 
 import "@wordpress/block-library/build-style/style.css"   import "../styles/layout.css" 
 const Layout = ({ children }) => {   return (     <section>       <header>         <Link to="/" className="home">           Gatsby + WP         </Link>       </header>       <main>{children}</main>     </section>   ) } 
 export default Layout

The block editor is still very much in active development, which means things are prone to change, perhaps unexpectedly. So, definitely proceed with caution if you’re planning to use them.

Site deployment

We’ve talked a bit about deployment when I explained why I chose Netlify, I chose it because it hooks into the project’s GitHub repo and deploys automatically when pushing to a specific branch, thanks to Netlify Functions.

Netlify has a nice steop-by-step guide that covers how to connect a Gatsby site to Netlify. The Gatsby Doc also describes deploying to Netlify.

Finally, link to my own Netlify deployed demo site.

Again, this gives us continuous deployment where the site rebuilds automatically when changes are pushed to the repo. If we want a similar process whenever changes are made in WordPress — like publishing a post or editing a page — then the JAMstack Deployments plugin can be used as described in Jason’s guide.


This is still a work in progress!

While what I’ve learned in the process of porting a WordPress theme to Gatsby is great for constructing the basic building blocks of a blog, I’ve realized that there is still a lot of work to cover. I mean, WordPress stores so much data, including authors, categories, tags, post statuses, custom post types, and so much more, that all take extra consideration.

But there’s a growing list of decoupled Gatsby WordPress site examples, some of which I’ll list below for reference. Henrik’s an awesome list of WordPress-Gatsby resources is super helpful to learn more about the WordPress-Gatsby decoupling.

Credits 

I know I mentioned it throughout this post, but a big shout out to Henrick Wirth, Jason Lengstorf and Muhammad Muhsin for all the work they’ve done to document and share what it takes to port WordPress to Gatsby. Everything I’ve covered here is merely the accumulation of their fine work and I appreciate each of them for creating such helpful guides suitable even for beginners like myself. I owe a special thank you to our own Geoff Graham from CSS-Tricks for editing this article.


The post Creating a Gatsby Site with WordPress Data appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Using Structured Data to Enhance Search Engine Optimization

SEO is often considered the snake oil of the web. How many times have you scrolled through attention-grabbing headlines on know how to improve your SEO? Everyone and their uncle seems to have some “magic” cure to land high in search results and turn impressions into conversions. Sifting through so much noise on the topic can cause us to miss true gems that might be right under our nose.

We’re going to look at one such gem in this article: structured data.

There’s a checklist of SEO must-haves that we know are needed when working on a site. It includes things like a strong <title>, a long list of <meta> tags, and using descriptive alt tags on images (which is a double win for accessibility). Running a cursory check on any site using Lighthouse will flag up turn up even more tips and best practices to squeeze the most SEO out of the content.

Search engines are getting smarter, however, and starting to move past the algorithmic scraping techniques of yesteryear. Google, Amazon, and Microsoft are all known to be investing a considerable amount in machine learning, and with that, they need clean data to feed their search AI. 

That’s where the the concept of schemas comes into play. In fact, it’s funding from Google and Microsoft — along with Yahoo and Yandex — that led to the establishment of schema.org, a website and community to push their format — more commonly referred to as structured data —forward so that they and other search engines can help surface content in more useful and engaging ways.

So, what is structured data?

Structured data describes the content of digital documents (i.e. websites, emails, etc). It’s used all over the web and, much like <meta> tags, is an invisible layer of information that search engines use to read the content.

Structured data comes in three flavors: Microdata, RDFa and JSON-LD. Microdata and RDF are both injected directly into the HTML elements of a document, peppering each relevant element of a page with machine readable pointers. For example, an example of using Microdata attributes on a product, taken straight from the schema.org docs:

<div itemscope itemtype="http://schema.org/Product">   <span itemprop="name">Kenmore White 17" Microwave</span>   <img itemprop="image" src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />   <div itemprop="aggregateRating"     itemscope itemtype="http://schema.org/AggregateRating">    Rated <span itemprop="ratingValue">3.5</span>/5    based on <span itemprop="reviewCount">11</span> customer reviews   </div>   <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">     <!--price is 1000, a number, with locale-specific thousands separator     and decimal mark, and the $  character is marked up with the     machine-readable code "USD" -->     <span itemprop="priceCurrency" content="USD">$ </span><span           itemprop="price" content="1000.00">1,000.00</span>     <link itemprop="availability" href="http://schema.org/InStock" />In stock   </div>   Product description:   <span itemprop="description">0.7 cubic feet countertop microwave.   Has six preset cooking categories and convenience features like   Add-A-Minute and Child Lock.</span>   Customer reviews:   <div itemprop="review" itemscope itemtype="http://schema.org/Review">     <span itemprop="name">Not a happy camper</span> -     by <span itemprop="author">Ellie</span>,     <meta itemprop="datePublished" content="2011-04-01">April 1, 2011     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">       <meta itemprop="worstRating" content = "1">       <span itemprop="ratingValue">1</span>/       <span itemprop="bestRating">5</span>stars     </div>     <span itemprop="description">The lamp burned out and now I have to replace     it. </span>   </div>   <div itemprop="review" itemscope itemtype="http://schema.org/Review">     <span itemprop="name">Value purchase</span> -     by <span itemprop="author">Lucas</span>,     <meta itemprop="datePublished" content="2011-03-25">March 25, 2011     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">       <meta itemprop="worstRating" content = "1"/>       <span itemprop="ratingValue">4</span>/       <span itemprop="bestRating">5</span>stars     </div>     <span itemprop="description">Great microwave for the price. It is small and     fits in my apartment.</span>   </div>   <!-- etc. --> </div>

If that seems like bloated markup, it kinda is. But it’s certainly beneficial if you prefer to consolidate all of your data in one place.

JSON-LD, on the other hand, usually sits in a <script> tag and describes the same properties in a single block of data. Again, from the docs:

<script type="application/ld+json"> {   "@context": "http://schema.org",   "@type": "Product",   "aggregateRating": {     "@type": "AggregateRating",     "ratingValue": "3.5",     "reviewCount": "11"   },   "description": "0.7 cubic feet countertop microwave. Has six preset cooking categories and convenience features like Add-A-Minute and Child Lock.",   "name": "Kenmore White 17" Microwave",   "image": "kenmore-microwave-17in.jpg",   "offers": {     "@type": "Offer",     "availability": "http://schema.org/InStock",     "price": "55.00",     "priceCurrency": "USD"   },   "review": [     {       "@type": "Review",       "author": "Ellie",       "datePublished": "2011-04-01",       "description": "The lamp burned out and now I have to replace it.",       "name": "Not a happy camper",       "reviewRating": {         "@type": "Rating",         "bestRating": "5",         "ratingValue": "1",         "worstRating": "1"       }     },     {       "@type": "Review",       "author": "Lucas",       "datePublished": "2011-03-25",       "description": "Great microwave for the price. It is small and fits in my apartment.",       "name": "Value purchase",       "reviewRating": {         "@type": "Rating",         "bestRating": "5",         "ratingValue": "4",         "worstRating": "1"       }     }   ] } </script>

This is my personal preference, as it is treated as a little external instruction manual for your content, much like JavaScript for scripts, and CSS for your styles, all happily self-contained. JSON-LD can become essential for certain types of schema, where the content of the page is different from the content of the structured data (for example, check out the speakable property, currently in beta).

A welcome introduction to the implementation of JSON-LD on the web is Google’s allowance of fetching structured data from an external source, rather than forcing inline scripting, which was previously frustratingly impossible. This can be done either by the developer, or in Google Tag Manager.

What structured data means to you

Beyond making life easier for search engine crawlers to read your pages? Two words: Rich snippets. Rich snippets are highly visual modules that tend to sit at the top of the search engine, in what is sometimes termed as “Position 0” in the results — displayed above the first search result. Here’s an example of a simple search for “blueberry pie” in Google as an example:

Google search results showing three recipes displayed as cards at the top, a card of nutritional facts in the right sidebar, a first result showing user reviews, and finally, the search results.
Check out those three recipes up top — and that content in the right column — showing up before the list of results using details from structured data.

Even the first result is a rich snippet! As you can see, using structured data is your ticket to get into a rich snippet on a search results page. And, not to spur FOMO or anything, but any site not showing up in a rich snippet is already at risk of dropping into “below the fold” territory. Notice how the second organic result barely makes the cut.

Fear not, dear developers! Adding and testing structured data to a website is aq simple and relatively painless process. Once you get the hang of it, you’ll be adding it to every possible location you can imagine, even emails.

It is worth noting that structured data is not the only way to get into rich snippets. Search engines can sometimes determine enough from your HTML to display some snippets, but utilizing it will push the odds in your favor. Plus, using structured data puts the power of how your content is displayed in your hands, rather than letting Google or the like determine it for you.

Types of structured data

Structured data is more than recipes. Here’s a full list of the types of structured data Google supports. (Spoiler alert: it’s almost any kind of content.)

  • Article
  • Book (limited support)
  • Breadcrumb
  • Carousel
  • Course
  • COVID-19 announcements (beta)
  • Critic review (limited support)
  • Dataset
  • Employer aggregate rating
  • Estimated salary
  • Event
  • Fact check
  • FAQ
  • How-to
  • Image license metadata (beta)
  • Job posting
  • Local business
  • Logo
  • Movie
  • Product
  • Q&A
  • Recipe
  • Review snippet
  • Sitelinks searchbox
  • Software app
  • Speakable (beta)
  • Subscription and paywalled content
  • Video

Yep, lots of options here! But with those come lots of opportunity to enhance a site’s content and leverage these search engine features.

Using structured data

The easiest way to find the right structured data for your project is to look through Google’s search catalogue. Advanced users may like to browse what’s on schema.org, but I’ll warn you that it is a scary rabbit hole to crawl through.

Let’s start with a fairly simple example: the Logo logo data type. It’s simple because all we really need is a website URL and the source URL for an image, along with some basic details to help search engine’s know they are looking at a logo. Here’s our JSON-LD:

<script type="application/ld+json">   {     "@context": "https://schema.org",     "@type": "Organization",     "name": "Example",     "url": "http://www.example.com",     "logo": "http://www.example.com/images/logo.png"   } </script>

First off, we have the <script> tag itself, telling search engines that it’s about to consume some JSON-LD.

From there, we have five properties:

  • @context: This is included on all structured data objects, no matter what type it is. It’s what tells search engines that the JSON-LD contains data that is defined by schema.org specifications.
  • @type: This is the reference type for the object. It’s used to identify what type of content we’re working with. In this case, it’s “Organization” which has a whole bunch of sub-properties that follow.
  • name: This is the sub-property that contains the organization’s name.
  • url: This is the sub-property that contains the organization’s web address.
  • logo: This is the sub-property that contains the file path for the organization’s logo image file. For Google to consider this, it must be at least 112⨉112px and in JPG, PNG, or GIF format. Sorry, no SVG at the moment.

A page can have multiple structured data types. That means it’s possible to mix and match content.

Testing structured data

See, dropping structured data into a page isn’t that tough, right? Once we have it, though, we should probably check to see if it actually works.

Google, Bing, and Yandex (login required) all have testing tools available. Google even has one specifically for validating structured data in email. In most cases, simply drop in the website URL and the tool will spin up a test and show which object it recognizes, the properties it sees, and any errors or warning to look into.

Showing Google's testing results where the JSON-LD is displayed on the left of the screen and the details of it on the right.
Google’s structured data testing tool fetches the markup and displays the information it recognizes.

The next step is to confirm that the structured data is accessible on your live site through Google Search Console. You may need to set up an account and verify your site in order to use a particular search engine’s console, but checking data is — yet again — as simple as dropping in a site URL and using the inspection tools to check that the site is indeed live and sending data when it is accessed by the search engine.

If the structured data is implemented correctly, it will display. In Google’s case, it’s located in the “Enhancements” section with a big ol’ checkmark next to it.

Google Search Console screenshot showing Google can find the site and that it recognizes search enhancements below that. In this case, it is showing that the Logo structured data type was found and is supported.
Notice the “Logo” that is detected at the end — it works!

But wait! I did all that and nothing’s happening… what gives?

As with all search engine optimizations, there are no guarantees or time scales, when it comes to how or when structured data is used. It might take a good while before rich snippets take hold for your content — days, weeks, or even months! I know, it stinks to be left in the dark like that. It is unfortunately a waiting game.


Hopefully, this gives you a good idea of what structured data is and how it can be used to leverage features that search engines have made to spotlight content has it.

There’s absolutely no shortage of advice, tips, and tricks for helping optimize a site for search engines. While so much of it is concerned with what’s contained in the <head> or how content is written, there are practical things that developers can do to make an impact. Structured data is definitely one of those things and worth exploring to get the most value from content.

The world is your oyster with structured data. And, sure, while search engine only support a selection of the schema.org vocabulary, they are constantly evolving and extending that support. Why not start small by adding structured data to an email link in a newsletter? Or perhaps you’re into trying something different, like defining a sitelinks search box (which is very meta but very cool). Or, hey, add a recipe for Pinterest. Blueberry pie, anyone? 

The post Using Structured Data to Enhance Search Engine Optimization appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

A Complete Guide to Data Attributes

Introduction

HTML elements can have attributes on them that are used for anything from accessibility information to stylistic control.

<!-- We can use the `class` for styling in CSS, and we've also make this into a landmark region --> <div class="names" role="region" aria-label="Names"></div>

What is discouraged is making up your own attributes, or repurposing existing attributes for unrelated functionality.

<!-- `highlight` is not an HTML attribute --> <div highlight="true"></div>  <!-- `large` is not a valid value of `width` --> <div width="large">

There are a variety of reasons this is bad. Your HTML becomes invalid, which may not have any actual negative consequences, but robs you of that warm fuzzy valid HTML feeling. The most compelling reason is that HTML is a living language and just because attributes and values that don’t do anything today doesn’t mean they never will.

Good news though: you can make up your own attributes. You just need to prefix them with data-* and then you’re free to do what you please!

Syntax

It can be awfully handy to be able to make up your own HTML attributes and put your own information inside them. Fortunately, you can! That’s exactly what data attributes are. They are like this:

<!-- They don't need a value --> <div data-foo></div>  <!-- ...but they can have a value --> <div data-size="large"></div>  <!-- You're in HTML here, so careful to escape code if you need to do something like put more HTML inside --> <li data-prefix="Careful with HTML in here."><li>  <!-- You can keep dashing if you like --> <aside data-some-long-attribute-name><aside>

Data attributes are often referred to as data-* attributes, as they are always formatted like that. The word data, then a dash -, then other text you can make up.

Can you use the data attribute alone?

<div data=""></div>

It’s probably not going to hurt anything, but you won’t get the JavaScript API we’ll cover later in this guide. You’re essentially making up an attribute for yourself, which as I mentioned in the intro, is discouraged.

What not to do with data attributes

Store content that should be accessible. If the content should be seen or read on a page, don’t only put them in data attributes, but make sure that content is in the HTML content somewhere.

<!-- This isn't accessible content --> <div data-name="Chris Coyier"></div>  <!-- If you need programmatic access to it but shouldn't be seen, there are other ways... --> <div>   <span class="visually-hidden">Chris Coyier</span> </div>

Here’s more about hiding things.

Styling with data attributes

CSS can select HTML elements based on attributes and their values.

/* Select any element with this data attribute and value */ [data-size="large"] {   padding: 2rem;   font-size: 125%; }  /* You can scope it to an element or class or anything else */ button[data-type="download"] { } .card[data-pad="extra"] { }

This can be compelling. The predominant styling hooks in HTML/CSS are classes, and while classes are great (they have medium specificity and nice JavaScript methods via classList) an element either has it or it doesn’t (essentially on or off). With data-* attributes, you get that on/off ability plus the ability to select based on the value it has at the same specificity level.

/* Selects if the attribute is present at all */ [data-size] { }  /* Selects if the attribute has a particular value */ [data-state="open"], [aria-expanded="true"] { }  /* "Starts with" selector, meaning this would match "3" or anything starting with 3, like "3.14" */ [data-version^="3"] { }  /* "Contains" meaning if the value has the string anywhere inside it */ [data-company*="google"] { }

The specificity of attribute selectors

It’s the exact same as a class. We often think of specificity as a four-part value:

inline style, IDs, classes/attributes, tags

So a single attribute selector alone is 0, 0, 1, 0. A selector like this:

div.card[data-foo="bar"] { }

…would be 0, 0, 2, 1. The 2 is because there is one class (.card) and one attribute ([data-foo="bar"]), and the 1 is because there is one tag (div).

Attribute selectors have less specificity than an ID, more than an element/tag, and the same as a class.

Case-insensitive attribute values

In case you’re needing to correct for possible capitalization inconsistencies in your data attributes, the attribute selector has a case-insensitive variant for that.

/* Will match <div data-state="open"></div> <div data-state="Open"></div> <div data-state="OPEN"></div> <div data-state="oPeN"></div> */ [data-state="open" i] { }

It’s the little i within the bracketed selector.

Using data attributes visually

CSS allows you to yank out the data attribute value and display it if you need to.

/* <div data-emoji="✅"> */  [data-emoji]::before {   content: attr(data-emoji); /* Returns '✅' */   margin-right: 5px; }

Example styling use-case

You could use data attributes to specify how many columns you want a grid container to have.

<div data-columns="2"></div> <div data-columns="3"></div> <div data-columns="4"></div>

Accessing data attributes in JavaScript

Like any other attribute, you can access the value with the generic method getAttribute.

let value = el.getAttribute("data-state");  // You can set the value as well. // Returns data-state="collapsed" el.setAttribute("data-state", "collapsed");

But data attributes have their own special API as well. Say you have an element with multiple data attributes (which is totally fine):

<span    data-info="123"    data-index="2"    data-prefix="Dr. "   data-emoji-icon="🏌️‍♀️" ></span>

If you have a reference to that element, you can set and get the attributes like:

// Get span.dataset.info; // 123 span.dataset.index; // 2  // Set span.dataset.prefix = "Mr. "; span.dataset.emojiIcon = "🎪";

Note the camelCase usage on the last line there. It automatically converts kebab-style attributes in HTML, like data-this-little-piggy, to camelCase style in JavaScript, like dataThisLittlePiggy.

This API is arguably not quite as nice as classList with the clear add, remove, toggle, and replace methods, but it’s better than nothing.

You have access to inline datasets as well:

<img src="spaceship.png"   data-ship-id="324" data-shields="72%"   onclick="pewpew(this.dataset.shipId)"> </img>

JSON data inside data attributes

<ul>   <li data-person='     {       "name": "Chris Coyier",       "job": "Web Person"     }   '></li> </ul>

Hey, why not? It’s just a string and it’s possible to format it as valid JSON (mind the quotes and such). You can yank that data and parse it as needed.

const el = document.querySelector("li");  let json = el.dataset.person; let data = JSON.parse(json);  console.log(data.name); // Chris Coyier console.log(data.job); // Web Person

JavaScript use-cases

The concept is that you can use data attributes to put information in HTML that JavaScript may need access to do certain things.

A common one would have to do with database functionality. Say you have a “Like” button:

<button data-id="435432343">♡</button>

That button could have a click handler on it which performs an Ajax request to the server to increment the number of likes in a database on click. It knows which record to update because it gets it from the data attribute.

Specifications

Browser support

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
7 6 11 12 5.1

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
79 68 3 5.0-5.1

The post A Complete Guide to Data Attributes appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Making a Chart? Try Using Mobx State Tree to Power the Data

Who loves charts? Everyone, right? There are lots of ways to create them, including a number of libraries. There’s D3.js, Chart.js, amCharts, Highcharts, and Chartist, to name only a few of many, many options.

But we don’t necessary need a chart library to create charts. Take Mobx-state-tree (MST), an intuitive alternative to Redux for managing state in React. We can build an interactive custom chart with simple SVG elements, using MST to manage and manipulate data for the chart. If you’ve attempted to build charts using something like D3.js in the past, I think you’ll find this approach more intuitive. Even if you’re an experienced D3.js developer, I still think you’ll be interested to see how powerful MST can be as a data architecture for visualizations.

Here’s an example of MST being used to power a chart:

This example uses D3’s scale functions but the chart itself is rendered simply using SVG elements within JSX. I don’t know of any chart library that has an option for flashing hamster points so this is a great example of why it’s great to build your own charts — and it’s not as hard as you might think!

I’ve been building charts with D3 for over 10 years and, while I love how powerful it is, I’ve always found that my code can end up being unwieldy and hard to maintain, especially when working with complex visualizations. MST has changed all that completely by providing an elegant way to separate the data handling from the rendering. My hope for this article is that it will encourage you to give it a spin.

Getting familiar with MST model

First of all, let’s cover a quick overview of what a MST model looks like. This isn’t an in-depth tutorial on all things MST. I only want to show the basics because, really, that’s all you need about 90% of the time.

Below is a Sandbox with the code for a simple to-do list built in MST. Take a quick look and then I’ve explain what each section does.

First of all, the shape of the object is defined with typed definitions of the attribute of the model. In plain English, this means an instance of the to-do model must have a title, which must be a string and will default to having a “done” attribute of false.

.model("Todo", {   title: types.string,   done: false //this is equivalent to types.boolean that defaults to false })

Next, we have the view and action functions. View functions are ways to access calculated values based on data within the model without making any changes to the data held by the model. You can think of them as read-only functions.

.views(self => ({   outstandingTodoCount() {     return self.todos.length - self.todos.filter(t => t.done).length;   } }))

Action functions, on the other hand, allow us to safely update the data. This is always done in the background in a non-mutable way.

.actions(self => ({   addTodo(title) {     self.todos.push({       id: Math.random(),       title     });   } }));

Finally, we create a new instance of the store:

const todoStore = TodoStore.create({   todos: [     {       title: "foo",       done: false     }   ] });

To show the store in action, I’ve added a couple of console logs to show the output of outStandingTodoCount() before and after triggering the toggle function of the first instance of a Todo.

console.log(todoStore.outstandingTodoCount()); // outputs: 1 todoStore.todos[0].toggle(); console.log(todoStore.outstandingTodoCount()); // outputs: 0

As you can see, MST gives us a data structure that allows us to easily access and manipulate data. More importantly, it’s structure is very intuitive and the code is easy to read at a glance — not a reducer in sight!

Let’s make a React chart component

OK, so now that we have a bit of background on what MST looks like, let’s use it to create a store that manages data for a chart. We’ll will start with the chart JSX, though, because it’s much easier to build the store once you know what data is needed.

Let’s look at the JSX which renders the chart.

The first thing to note is that we are using styled-components to organize our CSS. If that’s new to you, Cliff Hall has a great post that shows it in use with a React app.

First of all, we are rendering the dropdown that will change the chart axes. This is a fairly simple HTML dropdown wrapped in a styled component. The thing to note is that this is a controlled input, with the state set using the selectedAxes value from our model (we’ll look at this later).

<select   onChange={e =>     model.setSelectedAxes(parseInt(e.target.value, 10))   }   defaultValue={model.selectedAxes} >

Next, we have the chart itself. I’ve split up the axes and points in to their own components, which live in a separate file. This really helps keep the code maintainable by keeping each file nice and small. Additionally, it means we can reuse the axes if we want to, say, have a line chart instead of points. This really pays off when working on large projects with multiple types of chart. It also makes it easy to test the components in isolation, both programmatically and manually within a living style guide.

{model.ready ? (   <div>     <Axes       yTicks={model.getYAxis()}       xTicks={model.getXAxis()}       xLabel={xAxisLabels[model.selectedAxes]}       yLabel={yAxisLabels[model.selectedAxes]}     ></Axes>     <Points points={model.getPoints()}></Points>   </div> ) : (   <Loading></Loading> )}

Try commenting out the axes and points components in the Sandbox above to see how they work independently of each other.

Lastly, we’ll wrap the component with an observer function. This means that any changes in the model will trigger a re-render.

export default observer(HeartrateChart);

Let’s take a look at the Axes component:

As you can see, we have an XAxis and a YAxis. Each has a label and a set of tick marks. We go into how the marks are created later, but here you should note that each axis is made up of a set of ticks, generated by mapping over an array of objects with a label and either an x or y value, depending on which axis we are rendering.

Try changing some of the attribute values for the elements and see what happens… or breaks! For example, change the line element in the YAxis to the following:

<line x1={30} x2="95%" y1={0} y2={y} />

The best way to learn how to build visuals with SVG is simply to experiment and break things. 🙂

OK, that’s half of the chart. Now we’ll look at the Points component.

Each point on the chart is composed of two things: an SVG image and a circle element. The image is the animal icon and the circle provides the pulse animation that is visible when mousing over the icon.

Try commenting out the image element and then the circle element to see what happens.

This time the model has to provide an array of point objects which gives us four properties: x and y values used to position the point on the graph, a label for the point (the name of the animal) and pulse, which is the duration of the pulse animation for each animal icon. Hopefully this all seems intuitive and logical.

Again, try fiddling with attribute values to see what changes and breaks. You can try setting the y attribute of the image to 0. Trust me, this is a much less intimidating way to learn than reading the W3C specification for an SVG image element!

Hopefully this gives you an understanding and feel for how we are rendering the chart in React. Now, it’s just a case of creating a model with the appropriate actions to generate the points and ticks data we need to loop over in JSX.

Creating our store

Here is the complete code for the store:

I’ll break down the code into the three parts mentioned earlier:

  1. Defining the attributes of the model
  2. Defining the actions
  3. Defining the views

Defining the attributes of the model

Everything we define here is accessible externally as a property of the instance of the model and — if using an observable wrapped component — any changes to these properties will trigger a re-render.

.model('ChartModel', {   animals: types.array(AnimalModel),   paddingAndMargins: types.frozen({     paddingX: 30,     paddingRight: 0,     marginX: 30,     marginY: 30,     marginTop: 30,     chartHeight: 500   }),   ready: false, // means a types.boolean that defaults to false   selectedAxes: 0 // means a types.number that defaults to 0 })

Each animal has four data points: name (Creature), longevity (Longevity__Years_), weight (Mass__grams_), and resting heart rate (Resting_Heart_Rate__BPM_).

const AnimalModel = types.model('AnimalModel', {   Creature: types.string,   Longevity__Years_: types.number,   Mass__grams_: types.number,   Resting_Heart_Rate__BPM_: types.number });

Defining the actions

We only have two actions. The first (setSelectedAxes ) is called when changing the dropdown menu, which updates the selectedAxes attribute which, in turn, dictates what data gets used to render the axes.

setSelectedAxes(val) {   self.selectedAxes = val; },

The setUpScales action requires a bit more explanation. This function is called just after the chart component mounts, within a useEffect hook function, or after the window is resized. It accepts an object with the width of the DOM that contains the element. This allows us to set up the scale functions for each axis to fill the full available width. I will explain the scale functions shortly.

In order to set up scale functions, we need to calculate the maximum value for each data type, so the first thing we do is loop over the animals to calculate these maximum and minimum values. We can use zero as the minimum value for any scale we want to start at zero.

// ... self.animals.forEach(   ({     Creature,     Longevity__Years_,     Mass__grams_,     Resting_Heart_Rate__BPM_,     ...rest   }) => {     maxHeartrate = Math.max(       maxHeartrate,       parseInt(Resting_Heart_Rate__BPM_, 10)     );     maxLongevity = Math.max(       maxLongevity,       parseInt(Longevity__Years_, 10)     );     maxWeight = Math.max(maxWeight, parseInt(Mass__grams_, 10));     minWeight =       minWeight === 0         ? parseInt(Mass__grams_, 10)         : Math.min(minWeight, parseInt(Mass__grams_, 10));   } ); // ...

Now to set up the scale functions! Here, we’ll be using the scaleLinear and scaleLog functions from D3.js. When setting these up, we specify the domain, which is the minimum and maximum input the functions can expect, and the range, which is the maximum and minimum output.

For example, when I call self.heartScaleY with the maxHeartrate value, the output will be equal to marginTop. That makes sense because this will be at the very top of the chart. For the longevity attribute, we need to have two scale functions since this data will appear on either the x- or the y-axis, depending on which dropdown option is chosen.

self.heartScaleY = scaleLinear()   .domain([maxHeartrate, minHeartrate])   .range([marginTop, chartHeight - marginY - marginTop]); self.longevityScaleX = scaleLinear()   .domain([minLongevity, maxLongevity])   .range([paddingX + marginY, width - marginX - paddingX - paddingRight]); self.longevityScaleY = scaleLinear()   .domain([maxLongevity, minLongevity])   .range([marginTop, chartHeight - marginY - marginTop]); self.weightScaleX = scaleLog()   .base(2)   .domain([minWeight, maxWeight])   .range([paddingX + marginY, width - marginX - paddingX - paddingRight]);

Finally, we set self.ready to be true since the chart is ready to render.

Defining the views

We have two sets of functions for the views. The first set outputs the data needed to render the axis ticks (I said we’d get there!) and the second set outputs the data needed to render the points. We’ll take a look at the tick functions first.

There are only two tick functions that are called from the React app: getXAxis and getYAxis. These simply return the output of other view functions depending on the value of self.selectedAxes.

getXAxis() {   switch (self.selectedAxes) {     case 0:       return self.longevityXAxis;       break;     case 1:     case 2:       return self.weightXAxis;       break;   } }, getYAxis() {   switch (self.selectedAxes) {     case 0:     case 1:       return self.heartYAxis;       break;     case 2:       return self.longevityYAxis;       break;   } },

If we take a look at the Axis functions themselves we can see they use a ticks method of the scale function. This returns an array of numbers suitable for an axis. We then map over the values to return the data we need for our axis component.

heartYAxis() {   return self.heartScaleY.ticks(10).map(val => ({     label: val,     y: self.heartScaleY(val)   })); } // ...

Try changing the value of the parameter for the ticks function to 5 and see how it affects the chart: self.heartScaleY.ticks(5).

Now we have the view functions to return the data needed for the Points component.

If we take a look at longevityHeartratePoints (which returns the point data for the “Longevity vs. Heart” rate chart), we can see that we are looping over the array of animals and using the appropriate scale functions to get the x and y positions for the point. For the pulse attribute, we use some maths to convert the beats per minute value of the heart rate into a value representing the duration of a single heartbeat in milliseconds.

longevityHeartratePoints() {   return self.animals.map(     ({ Creature, Longevity__Years_, Resting_Heart_Rate__BPM_ }) => ({       y: self.heartScaleY(Resting_Heart_Rate__BPM_),       x: self.longevityScaleX(Longevity__Years_),       pulse: Math.round(1000 / (Resting_Heart_Rate__BPM_ / 60)),       label: Creature     })   ); },

At the end of the store.js file, we need to create a Store model and then instantiate it with the raw data for the animal objects. It is a common pattern to attach all models to a parent Store model which can then be accessed through a provider at top level if needed.

const Store = types.model('Store', {   chartModel: ChartModel }); const store = Store.create({   chartModel: { animals: data } }); export default store;

And that is it! Here’s our demo once again:


This is by no means the only way to organize data to build charts in JSX, but I have found it to be incredibly effective. I’ve have used this structure and stack in the wild to build a library of custom charts for a big corporate client and was blown away with how nicely MST worked for this purpose. I hope you have the same experience!

The post Making a Chart? Try Using Mobx State Tree to Power the Data appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , ,
[Top]

Filtering Data Client-Side: Comparing CSS, jQuery, and React

Say you have a list of 100 names:

<ul>   <li>Randy Hilpert</li>   <li>Peggie Jacobi</li>   <li>Ethelyn Nolan Sr.</li>    <!-- and then some --> </ul>

…or file names, or phone numbers, or whatever. And you want to filter them client-side, meaning you aren’t making a server-side request to search through data and return results. You just want to type “rand” and have it filter the list to include “Randy Hilpert” and “Danika Randall” because they both have that string of characters in them. Everything else isn’t included in the results.

Let’s look at how we might do that with different technologies.

CSS can sorta do it, with a little help.

CSS can’t select things based on the content they contain, but it can select on attributes and the values of those attributes. So let’s move the names into attributes as well.

<ul>   <li data-name="Randy Hilpert">Randy Hilpert</li>   <li data-name="Peggie Jacobi">Peggie Jacobi</li>   <li data-name="Ethelyn Nolan Sr.">Ethelyn Nolan Sr.</li>    ... </ul>

Now to filter that list for names that contain “rand”, it’s very easy:

li {   display: none; } li[data-name*="rand" i] {   display: list-item; }

Note the i on Line 4. That means “case insensitive” which is very useful here.

To make this work dynamically with a filter <input>, we’ll need to get JavaScript involved to not only react to the filter being typed in, but generate CSS that matches what is being searched.

Say we have a <style> block sitting on the page:

<style id="cssFilter">   /* dynamically generated CSS will be put in here */ </style>

We can watch for changes on our filter input and generate that CSS:

filterElement.addEventListener("input", e => {   let filter = e.target.value;   let css = filter ? `     li {       display: none;     }     li[data-name*="$  {filter}" i] {       display: list-item;     }   ` : ``;   window.cssFilter.innerHTML = css; });

Note that we’re emptying out the style block when the filter is empty, so all results show.

See the Pen
Filtering Technique: CSS
by Chris Coyier (@chriscoyier)
on CodePen.

I’ll admit it’s a smidge weird to leverage CSS for this, but Tim Carry once took it way further if you’re interested in the concept.

jQuery makes it even easier.

Since we need JavaScript anyway, perhaps jQuery is an acceptable tool. There are two notable changes here:

  • jQuery can select items based on the content they contain. It has a selector API just for this. We don’t need the extra attribute anymore.
  • This keeps all the filtering to a single technology.

We still watch the input for typing, then if we have a filter term, we hide all the list items and reveal the ones that contain our filter term. Otherwise, we reveal them all again:

const listItems = $  ("li");  $  ("#filter").on("input", function() {   let filter = $  (this).val();   if (filter) {     listItems.hide();     $  (`li:contains('$  {filter}')`).show();   } else {     listItems.show();   } });

It’s takes more fiddling to make the filter case-insensitive than CSS does, but we can do it by overriding the default method:

jQuery.expr[':'].contains = function(a, i, m) {   return jQuery(a).text().toUpperCase()       .indexOf(m[3].toUpperCase()) >= 0; };

See the Pen
Filtering Technique: jQuery
by Chris Coyier (@chriscoyier)
on CodePen.

React can do it with state and rendering only what it needs.

There is no one-true-way to do this in React, but I would think it’s React-y to keep the list of names as data (like an Array), map over them, and only render what you need. Changes in the input filter the data itself and React re-renders as necessary.

If we have an names = [array, of, names], we can filter it pretty easily:

filteredNames = names.filter(name => {   return name.includes(filter); });

This time, case sensitivity can be done like this:

filteredNames = names.filter(name => {   return name.toUpperCase().includes(filter.toUpperCase()); });

Then we’d do the typical .map() thing in JSX to loop over our array and output the names.

See the Pen
Filtering Technique: React
by Chris Coyier (@chriscoyier)
on CodePen.

I don’t have any particular preference

This isn’t the kind of thing you choose a technology for. You do it in whatever technology you already have. I also don’t think any one approach is particularly heavier than the rest in terms of technical debt.

The post Filtering Data Client-Side: Comparing CSS, jQuery, and React appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]