Tag: Settings

Saving Settings for a Custom WordPress Block in the Block Editor

We’ve accomplished a bunch of stuff in this series! We created a custom WordPress block that fetches data from an external API and renders it on the front end. Then we took that work and extended it so the data also renders directly in the WordPress block editor. After that, we created a settings UI for the block using components from the WordPress InspectorControls package.

There’s one last bit for us to cover and that’s saving the settings options. If we recall from the last article, we’re technically able to “save” our selections in the block settings UI, but those aren’t actually stored anywhere. If we make a few selections, save them, then return to the post, the settings are completely reset.

Let’s close the loop and save those settings so they persist the next time we edit a post that contains our custom block!

Working With External APIs in WordPress Blocks

Saving settings attributes

We’re working with an API that provides us with soccer football team ranking and we’re using it to fetch for displaying rankings based on country, league, and season. We can create new attributes for each of those like this:

// index.js  attributes: {   data: {     type: "object",   },   settings: {     type: "object",     default: {       country: {         type: "string",       },       league: {         type: "string",       },       season: {         type: "string",       },     },   }, },

Next, we need to set the attributes from LeagueSettings.js. Whenever a ComboboxControl is updated in our settings UI, we need to set the attributes using the setAttributes() method. This was more straightfoward when we were only working with one data endpoint. But now that we have multiple inputs, it’s a little more involved.

This is how I am going to organize it. I am going to create a new object in LeagueSettings.js that follows the structure of the settings attributes and their values.

// LeagueSettings.js  let localSettings = {   country: attributes.settings.country,   league: attributes.settings.league,   season: attributes.settings.season, };

I am also going to change the initial state variables from null to the respective settings variables.

// LeagueSettings.js  const [country, setCountry] = useState(attributes.settings.country); const [league, setLeague] = useState(attributes.settings.league); const [season, setSeason] = useState(attributes.settings.season);

In each of the handle______Change(), I am going to create a setLocalAttributes() that has an argument that clones and overwrites the previous localSettings object with the new country, league, and season values. This is done using the help of the spread operator.

// LeagueSettings.js  function handleCountryChange(value) {   // Initial code   setLocalAttributes({ ...localSettings, country: value });   // Rest of the code }  function handleLeagueChange(value) {   // Initial code   setLocalAttributes({ ...localSettings, league: value });   // Rest of the code }  function handleSeasonChange(value) {   // Initial code   setLocalAttributes({ ...localSettings, season: value });   // Rest of the code }

We can define the setLocalAttributes() like this:

// LeagueSettings.js  function setLocalAttributes(value) {   let newSettings = Object.assign(localSettings, value);   localSettings = { ...newSettings };   setAttributes({ settings: localSettings }); }

So, we’re using Object.assign() to merge the two objects. Then we can clone the newSettings object back to localSettings because we also need to account for each settings attribute when there a new selection is made and a change occurs.

Finally, we can use the setAttributes() as we do normally to set the final object. You can confirm if the above attributes are changing by updating the selections in the UI.

Another way to confirm is to do a console.log() in DevTools to find the attributes.

The block added to a post in the block editor with DevTools open showing the saved attributes.

Look closer at that screenshot. The values are stored in attributes.settings. We are able to see it happen live because React re-renders every time we make a change in the settings, thanks to the useState() hook.

Displaying the values in the blocks settings UI

It isn’t very useful to store the setting values in the control options themselves since each one is dependent on the other setting value (e.g. rankings by league depends on which season is selected). But it is very useful in situations where the settings values are static and where settings are independent of each other.

Without making the current settings complicated, we can create another section inside the settings panel that shows the current attributes. You can choose your way to display the settings values but I am going to import a Tip component from the @wordpress/components package:

// LeagueSettings.js import { Tip } from "@wordpress/components";

While I’m here, I am going to do a conditional check for the values before displaying them inside the Tip component:

<Tip>   {country && league && season && (     <>       <h2>Current Settings: </h2>       <div className="current-settings">         <div className="country">           Country: {attributes.settings.country}         </div>         <div className="league">           League: {attributes.settings.league}         </div>         <div className="season">           Season: {attributes.settings.season}         </div>       </div>     </>   )} </Tip>

Here’s how that winds up working in the block editor:

API data is more powerful when live data can be shown without having to manually update them each and every time. We will look into that in the next installment of this series.


Saving Settings for a Custom WordPress Block in the Block Editor originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,

Creating a Settings UI for a Custom WordPress Block

So far, we’ve covered how to work with data from an external API in a custom WordPress block. We walked through the process of fetching that data for use on the front end of a WordPress site, and how to render it directly in the WordPress Block Editor when placing the block in content. This time, we’re going to bridge those two articles by hooking into the block editor’s control panel to create a settings UI for the block we made.

Working With External APIs in WordPress Blocks

You know the control panel I’m referring to, right? It’s that panel on the right that contains post and block settings in the block editor.

WordPress block editor with the right control panel open containing the settings UI for a Paragraph block.

See that red highlighted area? That’s the control panel. A Paragraph block is currently selected and the settings for it are displayed in the panel. We can change styles, color, typography… a number of things!

Well, that’s exactly what we’re doing this time around. We’re going to create the controls for the settings of the Football Rankings block we worked on in the last two articles. Last time, we made a button in our block that fetches the external data for the football rankings. We already knew the URL and endpoints we needed. But what if we want to fetch ranking for a different country? Or maybe a different league? How about data from a different season?

We need form controls to do that. We could make use of interactive React components — like React-Select — to browse through the various API options that are available to parse that data. But there’s no need for that since WordPress ships with a bunch of core components that we hook right into!

The documentation for these components — called InspectorControls — is getting better in the WordPress Block Editor Handbook. That’ll get even better over time, but meanwhile, we also have the WordPress Gutenberg Storybook and WordPress Gutenberg Components sites for additional help.

The API architecture

Before we hook into anything, it’s a good idea to map out what it is we need in the first place. I’ve mapped out the structure of the RapidAPI data we’re fetching so we know what’s available to us:

Flow chart connecting the API endpoints for the custom WordPress block data that is fetched.
Credit: API-Football

Seasons and countries are two top-level endpoints that map to a leagues endpoint. From there, we have the rest of the data we’re already using to populate the rankings table. So, what we want to do is create settings in the WordPress Block Editor that filter the data by Season, Country, and League, then pass that filtered data into the rankings table. That gives us the ability to drop the block in any WordPress page or post and display variations of the data in the block.

In order to get the standings, we need to first get the leagues. And in order to get the leagues, we first need to get the countries and/or the seasons. You can view the various endpoints in the RapidAPI dashboard.

Full screen for the Rapid API dashboard that visualizes the API data.

There are different combinations of data that we can use to populate the rankings, and you might have a preference for which data you want. For the sake of this article, we are going to create the following options in the block settings panel:

  • Choose Country
  • Choose League
  • Choose Season

Then we’ll have a button to submit those selections and fetch the relevant data and pass them into the rankings table.

Load and store a list of countries

We can’t select which country we want data for if we don’t have a list of countries to choose from. So, our first task is to grab a list of countries from RapidAPI.

The ideal thing is to fetch the list of countries when the block is actually used in the page or post content. There’s no need to fetch anything if the block isn’t in use. The approach is very similar to what we did in the first article, the difference being that we are using a different API endpoint and different attributes to store the list of returned countries. There are other WordPress ways to fetch data, like api-fetch, but that‘s outside the scope of what we’re doing here.

We can either include the country list manually after copying it from the API data, or we could use a separate API or library to populate the countries. But the API we’re using already has a list of countries, so I would just use one of its endpoints. Let’s make sure the initial country list loads when the block is inserted into the page or post content in the block editor:

// edit.js const [countriesList, setCountriesList] = useState(null);  useEffect(() => {   let countryOptions = {     method: "GET",     headers: {       "X-RapidAPI-Key": "Your Rapid API key",       "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com",     },   };   fetch("https://api-football-v1.p.rapidapi.com/v3/countries", countryOptions)     .then( (response) => response.json() )     .then( (response) => {       let countriesArray = { ...response };       console.log("Countries list", countriesArray.response);       setCountriesList(countriesArray.response);     })   .catch((err) => console.error(err)); }, []);

We have a state variable to store the list of countries. Next, we are going to import a component from the @wordpress/block-editor package called InspectorControls which is where all of the components we need to create our settings controls are located.

import { InspectorControls } from "@wordpress/block-editor";

The package’s GitHub repo does a good job explaining InspectorControls. In our example, we can use it to control the API data settings like Country, League, and Season. Here’s a preview so that you get an idea of the UI we’re making:

The custom settings UI for the WordPress block showing the three settings options for the custom block and a blue button to fetch the data.

And once those selections are made in the block settings, we use them in the block’s Edit function:

<InspectorControls>   { countriesList && (     <LeagueSettings       props={props}       countriesList={ countriesList }       setApiData={ setApiData }     ></LeagueSettings>   )} </InspectorControls>

Here, I am making sure that we are using conditional rendering so that the function only loads the component after the list of countries is loaded. If you’re wondering about that LeagueSettings component, it is a custom component I created in a separate components subfolder in the block so we can have a cleaner and more organized Edit function instead of hundreds of lines of country data to deal with in a single file.

File structure for the block directory showing the current file.

We can import it into the edit.js file like this:

import { LeagueSettings } from "./components/LeagueSettings";

Next, we’re passing the required props to the LeagueSettings component from the parent Edit component so that we can access the state variables and attributes from the LeagueSettings child component. We can also do that with other methods like the Context API to avoid prop drilling, but what we have right now is perfectly suitable for what we’re doing.

The other parts of the Edit function can also be converted into components. For example, the league standings code can be put inside a separate component — like maybe LeagueTable.js — and then imported just like we imported LeagueSettings into the Edit function.

Inside the LeagueSettings.js file

LeagueSettings is just like another React component from which we can destructure the props from the parent component. I am going to use three state variables and an additional leagueID state because we are going to extract the ID from the league object:

const [country, setCountry] = useState(null); const [league, setLeague] = useState(null); const [season, setSeason] = useState(null); const [leagueID, setLeagueID] = useState(null);

The first thing we’re going to do is import the PanelBody component from the @wordpress/block-editor package:

import { PanelBody } from "@wordpress/block-editor";

…and include it in our return function:

<PanelBody title="Data settings" initialOpen={false}></PanelBody>

There are other panel tags and attributes — it’s just my personal preference to use these ones. None of the others are required… but look at all the components we have available to make a settings panel! I like the simplicity of the PanelBody for our use case. It expands and collapses to reveal the dropdown settings for the block and that’s it.

Speaking of which, we have a choice to make for those selections. We could use the SelectControl component or a ComboBoxControl, which the docs describe as “an enhanced version of a SelectControl, with the addition of being able to search for options using a search input.” That’s nice for us because the list of countries could get pretty long and users will be able to either do a search query or select from a list.

Here’s an example of how a ComboboxControl could work for our country list:

<ComboboxControl   label="Choose country"   value={country}   options={ filteredCountryOptions }   onChange={ (value) => handleCountryChange(value) }   onInputChange={ (inputValue) => {     setFilteredCountryOptions(       setupCountrySelect.filter((option) =>         option.label           .toLowerCase()           .startsWith(inputValue.toLowerCase())       )     );   }} />

The ComboboxControl is configurable in the sense that we can apply different sizing for the control’s label and values:

{   value: 'small',   label: 'Small', },

But our API data is not in this syntax, so we can convert the countriesList array that comes from the parent component when the block is included:

let setupCountrySelect;  setupCountrySelect = countriesList.map((country) => {   return {     label: country.name,     value: country.name,   }; });

When a country is selected from the ComboboxControl, the country value changes and we filter the data accordingly:

function handleCountryChange(value) {   // Set state of the country    setCountry(value);     // League code from RapidAPI   const options = {     method: "GET",     headers: {       "X-RapidAPI-Key": "Your RapidAPI key",       "X-RapidAPI-Host": "api-football-v1.p.rapidapi.com",     },   };    fetch(`https://api-football-v1.p.rapidapi.com/v3/leagues?country=$ {value}`, options)     .then((response) => response.json())     .then((response) => {       return response.response;     })     .then((leagueOptions) => {       // Set state of the league variable       setLeague(leagueOptions);        // Convert it as we did for Country options       setupLeagueSelect = leagueOptions.map((league) => {         return {           label: league.league.name,           value: league.league.name,         };       });       setFilteredLeagueOptions(setupLeagueSelect);     })   .catch((err) => console.error(err)); }

Note that I am using another three state variables to handle changes when the country selection changes:

const [filteredCountryOptions, setFilteredCountryOptions] = useState(setupCountrySelect); const [filteredLeagueOptions, setFilteredLeagueOptions] = useState(null); const [filteredSeasonOptions, setFilteredSeasonOptions] = useState(null);

What about the other settings options?

I will show the code that I used for the other settings but all it does is take normal cases into account while defining errors for special cases. For example, there will be errors in some countries and leagues because:

  • there are no standings for some leagues, and
  • some leagues have standings but they are not in a single table.

This isn’t a JavaScript or React tutorial, so I will let you handle the special cases for the API that you plan to use:

function handleLeagueChange(value) {   setLeague(value);   if (league) {     const selectedLeague = league.filter((el) => {       if (el.league.name === value) {         return el;       }     });      if (selectedLeague) {       setLeague(selectedLeague[0].league.name);       setLeagueID(selectedLeague[0].league.id);       setupSeasonSelect = selectedLeague[0].seasons.map((season) => {         return {           label: season.year,           value: season.year,         };       });       setFilteredSeasonOptions(setupSeasonSelect);     }   } else {     return;   } }  function handleSeasonChange(value) {   setSeason(value); }

Submitting the settings selections

In the last article, we made a button in the block editor that fetches fresh data from the API. There’s no more need for it now that we have settings. Well, we do need it — just not where it currently is. Instead of having it directly in the block that’s rendered in the block editor, we’re going to move it to our PanelBody component to submit the settings selections.

So, back in LeagueSettings.js:

// When countriesList is loaded, show the country combo box { countriesList && (   <ComboboxControl     label="Choose country"     value={country}     options={filteredCountryOptions}     onChange={(value) => handleCountryChange(value)}     onInputChange={(inputValue) => {       setFilteredCountryOptions(         setupCountrySelect.filter((option) =>           option.label             .toLowerCase()             .startsWith(inputValue.toLowerCase())         )       );     }}   /> )}  // When filteredLeagueOptions is set through handleCountryChange, show league combobox { filteredLeagueOptions && (   <ComboboxControl     label="Choose league"     value={league}     options={filteredLeagueOptions}     onChange={(value) => handleLeagueChange(value)}     onInputChange={(inputValue) => {       setFilteredLeagueOptions(         setupLeagueSelect.filter((option) =>           option.label             .toLowerCase()             .startsWith(inputValue.toLowerCase())         )       );     }}   /> )}  // When filteredSeasonOptions is set through handleLeagueChange, show season combobox { filteredSeasonOptions && (   <>     <ComboboxControl       label="Choose season"       value={season}       options={filteredSeasonOptions}       onChange={(value) => handleSeasonChange(value)}       onInputChange={           (inputValue) => {             setFilteredSeasonOptions(               setupSeasonSelect.filter((option) =>               option.label               .toLowerCase()               .startsWith(inputValue.toLowerCase()             )           );         }       }     />      // When season is set through handleSeasonChange, show the "Fetch data" button     {       season && (         <button className="fetch-data" onClick={() => getData()}>Fetch data</button>       )     }     </>   </> )}

Here’s the result!

We’re in a very good place with our block. We can render it in the block editor and the front end of the site. We can fetch data from an external API based on a selection of settings we created that filters the data. It’s pretty darn functional!

But there’s another thing we have to tackle. Right now, when we save the page or post that contains the block, the settings we selected for the block reset. In other words, those selections are not saved anywhere. There’s a little more work to make those selections persistent. That’s where we plan to go in the next article, so stay tuned.


Creating a Settings UI for a Custom WordPress Block originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , ,
[Top]

Building a Settings Component

This is a tremendous CSS-focused tutorial from Adam Argyle. I really like the “just for gap” concept here. Grid is extremely powerful, but you don’t have to use all its abilities every time you reach for it. Here, Adam reaches for it for very light reasons like using it as an in-between border alternative as well as more generic spacing. I guess he’s putting money where his mouth is in terms of gap superseding margin!

I also really like calling out Una Kravet’s awesome name for flexible grids: RAM. Perhaps you’ve seen the flexible-number-of-columns trick with CSS grid? The bonus trick here (which I first saw from Evan Minto) is to use min(). That way, not only are large layouts covered, but even the very smallest layouts have no hard-coded minimum (like if 100% is smaller than 10ch here):

.el {   display: grid;   grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch)); }

There is a ton more trickery in the blog post. The “color pops” with :focus-within is fun and clever. So much practical CSS in building something so practical! 🧡 more blog posts like this, please. Fortunately, we don’t have to wait, as Adam has other component-focused posts like this one on Tabs and this one on Sidenav.

Direct Link to ArticlePermalink


The post Building a Settings Component appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

Building a Settings Component

This is a tremendous CSS-focused tutorial from Adam Argyle. I really like the “just for gap” concept here. Grid is extremely powerful, but you don’t have to use all its abilities every time you reach for it. Here, Adam reaches for it for very light reasons like using it as an in-between border alternative as well as more generic spacing. I guess he’s putting money where his mouth is in terms of gap superseding margin!

I also really like calling out Una Kravet’s awesome name for flexible grids: RAM. Perhaps you’ve seen the flexible-number-of-columns trick with CSS grid? The bonus trick here (which I first saw from Evan Minto) is to use min(). That way, not only are large layouts covered, but even the very smallest layouts have no hard-coded minimum (like if 100% is smaller than 10ch here):

.el {   display: grid;   grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch)); }

There is a ton more trickery in the blog post. The “color pops” with :focus-within is fun and clever. So much practical CSS in building something so practical! 🧡 more blog posts like this, please. Fortunately, we don’t have to wait, as Adam has other component-focused posts like this one on Tabs and this one on Sidenav.

Direct Link to ArticlePermalink


The post Building a Settings Component appeared first on CSS-Tricks.

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

CSS-Tricks

, ,
[Top]

Global and Component Style Settings with CSS Variables

The title of this Sara Soueidan article speaks to me. I’m a big fan of the idea that some CSS is best applied globally, and some CSS is best applied scoped to a component. I’m less interested in how that is done and more interested in just seeing that conceptual approach used in some fashion.

Sara details an approach where components don’t have too much styling by default, but have CSS custom properties applied to them that are ready to take values should you choose to set them.

For each pattern, I’ve found myself modifying the same properties whenever I needed to use it — like the font, colors (text, background, border), box shadow, spacing, etc. So I figured it would be useful and time-saving if I created variables for those properties, define those variables in the ‘root’ of the component, and ‘pass in’ the values for these variables when I use the pattern as I need. This way I can customize or theme the component by changing the property values in one rule set, instead of having to jump between multiple ones to do so.

Direct Link to ArticlePermalink

The post Global and Component Style Settings with CSS Variables appeared first on CSS-Tricks.

CSS-Tricks

, , , ,
[Top]