Tag: External

Rendering External API Data in WordPress Blocks on the Back End

This is a continuation of my last article about “Rendering External API Data in WordPress Blocks on the Front End”. In that last one, we learned how to take an external API and integrate it with a block that renders the fetched data on the front end of a WordPress site.

The thing is, we accomplished this in a way that prevents us from seeing the data in the WordPress Block Editor. In other words, we can insert the block on a page but we get no preview of it. We only get to see the block when it’s published.

Let’s revisit the example block plugin we made in the last article. Only this time, we’re going to make use of the JavaScript and React ecosystem of WordPress to fetch and render that data in the back-end Block Editor as well.

Where we left off

As we kick this off, here’s a demo where we landed in the last article that you can reference. You may have noticed that I used a render_callback method in the last article so that I can make use of the attributes in the PHP file and render the content.

Well, that may be useful in situations where you might have to use some native WordPress or PHP function to create dynamic blocks. But if you want to make use of just the JavaScript and React (JSX, specifically) ecosystem of WordPress to render the static HTML along with the attributes stored in the database, you only need to focus on the Edit and Save functions of the block plugin.

  • The Edit function renders the content based on what you want to see in the Block Editor. You can have interactive React components here.
  • The Save function renders the content based on what you want to see on the front end. You cannot have the the regular React components or the hooks here. It is used to return the static HTML that is saved into your database along with the attributes.

The Save function is where we’re hanging out today. We can create interactive components on the front-end, but for that we need to manually include and access them outside the Save function in a file like we did in the last article.

So, I am going to cover the same ground we did in the last article, but this time you can see the preview in the Block Editor before you publish it to the front end.

The block props

I intentionally left out any explanations about the edit function’s props in the last article because that would have taken the focus off of the main point, the rendering.

If you are coming from a React background, you will likely understand what is that I am talking about, but if you are new to this, I would recommend checking out components and props in the React documentation.

If we log the props object to the console, it returns a list of WordPress functions and variables related to our block:

Console log of the block properties.

We only need the attributes object and the setAttributes function which I am going to destructure from the props object in my code. In the last article, I had modified RapidAPI’s code so that I can store the API data through setAttributes(). Props are only readable, so we are unable to modify them directly.

Block props are similar to state variables and setState in React, but React works on the client side and setAttributes() is used to store the attributes permanently in the WordPress database after saving the post. So, what we need to do is save them to attributes.data and then call that as the initial value for the useState() variable.

The edit function

I am going to copy-paste the HTML code that we used in football-rankings.php in the last article and edit it a little to shift to the JavaScript background. Remember how we created two additional files in the last article for the front end styling and scripts? With the way we’re approaching things today, there’s no need to create those files. Instead, we can move all of it to the Edit function.

Full code
import { useState } from "@wordpress/element"; export default function Edit(props) {   const { attributes, setAttributes } = props;   const [apiData, setApiData] = useState(null);     function fetchData() {       const options = {         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/standings?season=2021&league=39",           options       )       .then((response) => response.json())       .then((response) => {         let newData = { ...response }; // Deep clone the response data         setAttributes({ data: newData }); // Store the data in WordPress attributes         setApiData(newData); // Modify the state with the new data       })       .catch((err) => console.error(err));     }     return (       <div {...useBlockProps()}>         <button onClick={() => getData()}>Fetch data</button>         {apiData && (           <>           <div id="league-standings">             <div               className="header"               style={{                 backgroundImage: `url($ {apiData.response[0].league.logo})`,               }}             >               <div className="position">Rank</div>               <div className="team-logo">Logo</div>               <div className="team-name">Team name</div>               <div className="stats">                 <div className="games-played">GP</div>                 <div className="games-won">GW</div>                 <div className="games-drawn">GD</div>                 <div className="games-lost">GL</div>                 <div className="goals-for">GF</div>                 <div className="goals-against">GA</div>                 <div className="points">Pts</div>               </div>               <div className="form-history">Form history</div>             </div>             <div className="league-table">               {/* Usage of [0] might be weird but that is how the API structure is. */}               {apiData.response[0].league.standings[0].map((el) => {                                  {/* Destructure the required data from all */}                 const { played, win, draw, lose, goals } = el.all;                   return (                     <>                     <div className="team">                       <div class="position">{el.rank}</div>                       <div className="team-logo">                         <img src={el.team.logo} />                       </div>                       <div className="team-name">{el.team.name}</div>                       <div className="stats">                         <div className="games-played">{played}</div>                         <div className="games-won">{win}</div>                         <div className="games-drawn">{draw}</div>                         <div className="games-lost">{lose}</div>                         <div className="goals-for">{goals.for}</div>                         <div className="goals-against">{goals.against}</div>                         <div className="points">{el.points}</div>                       </div>                       <div className="form-history">                         {el.form.split("").map((result) => {                           return (                             <div className={`result-$ {result}`}>{result}</div>                           );                         })}                       </div>                     </div>                     </>                   );                 }               )}             </div>           </div>         </>       )}     </div>   ); }

I have included the React hook useState() from @wordpress/element rather than using it from the React library. That is because if I were to load the regular way, it would download React for every block that I am using. But if I am using @wordpress/element it loads from a single source, i.e., the WordPress layer on top of React.

This time, I have also not wrapped the code inside useEffect() but inside a function that is called only when clicking on a button so that we have a live preview of the fetched data. I have used a state variable called apiData to render the league table conditionally. So, once the button is clicked and the data is fetched, I am setting apiData to the new data inside the fetchData() and there is a rerender with the HTML of the football rankings table available.

You will notice that once the post is saved and the page is refreshed, the league table is gone. That is because we are using an empty state (null) for apiData‘s initial value. When the post saves, the attributes are saved to the attributes.data object and we call it as the initial value for the useState() variable like this:

const [apiData, setApiData] = useState(attributes.data);

The save function

We are going to do almost the same exact thing with the save function, but modify it a little bit. For example, there’s no need for the “Fetch data” button on the front end, and the apiData state variable is also unnecessary because we are already checking it in the edit function. But we do need a random apiData variable that checks for attributes.data to conditionally render the JSX or else it will throw undefined errors and the Block Editor UI will go blank.

Full code
export default function save(props) {   const { attributes, setAttributes } = props;   let apiData = attributes.data;   return (     <>       {/* Only render if apiData is available */}       {apiData && (         <div {...useBlockProps.save()}>         <div id="league-standings">           <div             className="header"             style={{               backgroundImage: `url($ {apiData.response[0].league.logo})`,             }}           >             <div className="position">Rank</div>             <div className="team-logo">Logo</div>             <div className="team-name">Team name</div>             <div className="stats">               <div className="games-played">GP</div>               <div className="games-won">GW</div>               <div className="games-drawn">GD</div>               <div className="games-lost">GL</div>               <div className="goals-for">GF</div>               <div className="goals-against">GA</div>               <div className="points">Pts</div>             </div>             <div className="form-history">Form history</div>           </div>           <div className="league-table">             {/* Usage of [0] might be weird but that is how the API structure is. */}             {apiData.response[0].league.standings[0].map((el) => {               const { played, win, draw, lose, goals } = el.all;                 return (                   <>                   <div className="team">                     <div className="position">{el.rank}</div>                       <div className="team-logo">                         <img src={el.team.logo} />                       </div>                       <div className="team-name">{el.team.name}</div>                       <div className="stats">                         <div className="games-played">{played}</div>                         <div className="games-won">{win}</div>                         <div className="games-drawn">{draw}</div>                         <div className="games-lost">{lose}</div>                         <div className="goals-for">{goals.for}</div>                         <div className="goals-against">{goals.against}</div>                         <div className="points">{el.points}</div>                       </div>                       <div className="form-history">                         {el.form.split("").map((result) => {                           return (                             <div className={`result-$ {result}`}>{result}</div>                           );                         })}                       </div>                     </div>                   </>                 );               })}             </div>           </div>         </div>       )}     </>   ); }

If you are modifying the save function after a block is already present in the Block Editor, it would show an error like this:

The football rankings block in the WordPress block Editor with an error message that the block contains an unexpected error.

That is because the markup in the saved content is different from the markup in our new save function. Since we are in development mode, it is easier to remove the bock from the current page and re-insert it as a new block — that way, the updated code is used instead and things are back in sync.

This situation of removing it and adding it again can be avoided if we had used the render_callback method since the output is dynamic and controlled by PHP instead of the save function. So each method has it’s own advantages and disadvantages.

Tom Nowell provides a thorough explanation on what not to do in a save function in this Stack Overflow answer.

Styling the block in the editor and the front end

Regarding the styling, it is going to be almost the same thing we looked at in the last article, but with some minor changes which I have explained in the comments. I’m merely providing the full styles here since this is only a proof of concept rather than something you want to copy-paste (unless you really do need a block for showing football rankings styled just like this). And note that I’m still using SCSS that compiles to CSS on build.

Editor styles
/* Target all the blocks with the data-title="Football Rankings" */ .block-editor-block-list__layout  .block-editor-block-list__block.wp-block[data-title="Football Rankings"] {   /* By default, the blocks are constrained within 650px max-width plus other design specific code */   max-width: unset;   background: linear-gradient(to right, #8f94fb, #4e54c8);   display: grid;   place-items: center;   padding: 60px 0;    /* Button CSS - From: https://getcssscan.com/css-buttons-examples - Some properties really not needed :) */   button.fetch-data {     align-items: center;     background-color: #ffffff;     border: 1px solid rgb(0 0 0 / 0.1);     border-radius: 0.25rem;     box-shadow: rgb(0 0 0 / 0.02) 0 1px 3px 0;     box-sizing: border-box;     color: rgb(0 0 0 / 0.85);     cursor: pointer;     display: inline-flex;     font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif;     font-size: 16px;     font-weight: 600;     justify-content: center;     line-height: 1.25;     margin: 0;     min-height: 3rem;     padding: calc(0.875rem - 1px) calc(1.5rem - 1px);     position: relative;     text-decoration: none;     transition: all 250ms;     user-select: none;     -webkit-user-select: none;     touch-action: manipulation;     vertical-align: baseline;     width: auto;     &:hover,     &:focus {       border-color: rgb(0, 0, 0, 0.15);       box-shadow: rgb(0 0 0 / 0.1) 0 4px 12px;       color: rgb(0, 0, 0, 0.65);     }     &:hover {       transform: translateY(-1px);     }     &:active {       background-color: #f0f0f1;       border-color: rgb(0 0 0 / 0.15);       box-shadow: rgb(0 0 0 / 0.06) 0 2px 4px;       color: rgb(0 0 0 / 0.65);       transform: translateY(0);     }   } }
Front-end styles
/* Front-end block styles */ .wp-block-post-content .wp-block-football-rankings-league-table {   background: linear-gradient(to right, #8f94fb, #4e54c8);   max-width: unset;   display: grid;   place-items: center; }  #league-standings {   width: 900px;   margin: 60px 0;   max-width: unset;   font-size: 16px;   .header {     display: grid;     gap: 1em;     padding: 10px;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;     color: white;     font-size: 16px;     font-weight: 600;     background-color: transparent;     background-repeat: no-repeat;     background-size: contain;     background-position: right;      .stats {       display: flex;       gap: 15px;       &amp; &gt; div {         width: 30px;       }     }   } } .league-table {   background: white;   box-shadow:     rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,     rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;   padding: 1em;   .position {     width: 20px;   }   .team {     display: grid;     gap: 1em;     padding: 10px 0;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;   }   .team:not(:last-child) {     border-bottom: 1px solid lightgray;   }   .team-logo img {     width: 30px;     top: 3px;     position: relative;   }   .stats {     display: flex;     gap: 15px;     &amp; &gt; div {       width: 30px;       text-align: center;     }   }   .last-5-games {     display: flex;     gap: 5px;     &amp; &gt; div {       width: 25px;       height: 25px;       text-align: center;       border-radius: 3px;       font-size: 15px;     &amp; .result-W {       background: #347d39;       color: white;     }     &amp; .result-D {       background: gray;       color: white;     }     &amp; .result-L {       background: lightcoral;       color: white;     }   } } 

We add this to src/style.scss which takes care of the styling in both the editor and the frontend. I will not be able to share the demo URL since it would require editor access but I have a video recorded for you to see the demo:


Pretty neat, right? Now we have a fully functioning block that not only renders on the front end, but also fetches API data and renders right there in the Block Editor — with a refresh button to boot!

But if we want to take full advantage of the WordPress Block Editor, we ought to consider mapping some of the block’s UI elements to block controls for things like setting color, typography, and spacing. That’s a nice next step in the block development learning journey.


Rendering External API Data in WordPress Blocks on the Back End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,

Rendering External API Data in WordPress Blocks on the Front End

There’ve been some new tutorials popping here on CSS-Tricks for working with WordPress blocks. One of them is an introduction to WordPress block development and it’s a good place to learn what blocks are and to register them in WordPress for use in pages and posts.

While the block basics are nicely covered in that post, I want to take it another step forward. You see, in that article, we learned the difference between rendering blocks in the back-end WordPress Block Editor and rendering them on the front-end theme. The example was a simple Pullquote Block that rendered different content and styles on each end.

Let’s go further and look at using dynamic content in a WordPress block. More specifically, let’s fetch data from an external API and render it on the front end when a particular block is dropped into the Block Editor.

We’re going to build a block that outputs data that shows soccer (er, football) rankings pulled from Api-Football.

An ordered set of football team rankings showing team logos, names, and game results.
This is what we’re working for together.

There’s more than one way to integrate an API with a WordPress block! Since the article on block basics has already walked through the process of making a block from scratch, we’re going to simplify things by using the @wordpress/create-block package to bootstrap our work and structure our project.

Initializing our block plugin

First things first: let’s spin up a new project from the command line:

npx @wordpress/create-block football-rankings

I normally would kick a project like this off by making the files from scratch, but kudos to the WordPress Core team for this handy utility!

Once the project folder has been created by the command, we technically have a fully-functional WordPress block registered as a plugin. So, let’s go ahead and drop the project folder into the wp-content/plugins directory where you have WordPress installed (probably best to be working in a local environment), then log into the WordPress admin and activate it from the Plugins screen.

Now that our block is initialized, installed, and activated, go ahead and open up the project folder from at /wp-content/plugins/football-rankings. You’re going to want to cd there from the command line as well to make sure we can continue development.

These are the only files we need to concentrate on at the moment:

  • edit.js
  • index.js
  • football-rankings.php

The other files in the project are important, of course, but are inessential at this point.

Reviewing the API source

We already know that we’re using Api-Football which comes to us courtesy of RapidAPI. Fortunately, RapidAPI has a dashboard that automatically generates the required scripts we need to fetch the API data for the 2021 Premier League Standings.

A dashboard interface with three columns showing code and data from an API source.
The RapidAPI dashboard

If you want to have a look on the JSON structure, you can generate visual representation with JSONCrack.

Fetching data from the edit.js file

I am going to wrap the RapidAPI code inside a React useEffect() hook with an empty dependency array so that it runs only once when the page is loaded. This way, we prevent WordPress from calling the API each time the Block Editor re-renders. You can check that using wp.data.subscribe() if you care to.

Here’s the code where I am importing useEffect(), then wrapping it around the fetch() code that RapidAPI provided:

/** * The edit function describes the structure of your block in the context of the * editor. This represents what the editor will render when the block is used. * * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit * * @return {WPElement} Element to render. */  import { useEffect } from "@wordpress/element";  export default function Edit(props) {   const { attributes, setAttributes } = props;    useEffect(() => {     const options = {       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/standings?season=2021&league=39", options)       .then( ( response ) => response.json() )       .then( ( response ) => {         let newData = { ...response };         setAttributes( { data: newData } );         console.log( "Attributes", attributes );       })       .catch((err) => console.error(err)); }, []);    return (     <p { ...useBlockProps() }>       { __( "Standings loaded on the front end", "external-api-gutenberg" ) }     </p>   ); }

Notice that I have left the return function pretty much intact, but have included a note that confirms the football standings are rendered on the front end. Again, we’re only going to focus on the front end in this article — we could render the data in the Block Editor as well, but we’ll leave that for another article to keep things focused.

Storing API data in WordPress

Now that we are fetching data, we need to store it somewhere in WordPress. This is where the attributes.data object comes in handy. We are defining the data.type as an object since the data is fetched and formatted as JSON. Make sure you don’t have any other type or else WordPress won’t save the data, nor does it throw any error for you to debug.

We define all this in our index.js file:

registerBlockType( metadata.name, {   edit: Edit,   attributes: {     data: {       type: "object",     },   },   save, } );

OK, so WordPress now knows that the RapidAPI data we’re fetching is an object. If we open a new post draft in the WordPress Block Editor and save the post, the data is now stored in the database. In fact, if we can see it in the wp_posts.post_content field if we open the site’s database in phpMyAdmin, Sequel Pro, Adminer, or whatever tool you use.

Showing a large string of JSON output in a database table.
API output stored in the WordPress database

Outputting JSON data in the front end

There are multiple ways to output the data on the front end. The way I’m going to show you takes the attributes that are stored in the database and passes them as a parameter through the render_callback function in our football-rankings.php file.

I like keeping a separation of concerns, so how I do this is to add two new files to the block plugin’s build folder: frontend.js and frontend.css (you can create a frontend.scss file in the src directory which compiled to CSS in the build directory). This way, the back-end and front-end codes are separate and the football-rankings.php file is a little easier to read.

/explanation Referring back to the introduction to WordPress block development, there are editor.css and style.css files for back-end and shared styles between the front and back end, respectively. By adding frontend.scss (which compiles to frontend.css, I can isolate styles that are only intended for the front end.

Before we worry about those new files, here’s how we call them in football-rankings.php:

/** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued * through the block editor in the corresponding context. * * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ function create_block_football_rankings_block_init() {   register_block_type( __DIR__ . '/build', array(     'render_callback' => 'render_frontend'   )); } add_action( 'init', 'create_block_football_rankings_block_init' );  function render_frontend($ attributes) {   if( !is_admin() ) {     wp_enqueue_script( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.js');     wp_enqueue_style( 'football_rankings', plugin_dir_url( __FILE__ ) . '/build/frontend.css' ); // HIGHLIGHT 15,16,17,18   }      ob_start(); ?>    <div class="football-rankings-frontend" id="league-standings">     <div class="data">       <pre>         <?php echo wp_json_encode( $ attributes ) ?>       </pre>     </div>     <div class="header">       <div class="position">Rank</div>       <div class="team-logo">Logo</div>       <div class="team-name">Team name</div>       <div class="stats">         <div class="games-played">GP</div>         <div class="games-won">GW</div>         <div class="games-drawn">GD</div>         <div class="games-lost">GL</div>         <div class="goals-for">GF</div>         <div class="goals-against">GA</div>         <div class="points">Pts</div>       </div>       <div class="form-history">Last 5 games</div>     </div>     <div class="league-table"></div>   </div>    <?php return ob_get_clean(); }

Since I am using the render_callback() method for the attributes, I am going to handle the enqueue manually just like the Block Editor Handbook suggests. That’s contained in the !is_admin() condition, and is enqueueing the two files so that we avoid enqueuing them while using the editor screen.

Now that we have two new files we’re calling, we’ve gotta make sure we are telling npm to compile them. So, do that in package.json, in the scripts section:

"scripts": {   "build": "wp-scripts build src/index.js src/frontend.js",   "format": "wp-scripts format",   "lint:css": "wp-scripts lint-style",   "lint:js": "wp-scripts lint-js",   "packages-update": "wp-scripts packages-update",   "plugin-zip": "wp-scripts plugin-zip",   "start": "wp-scripts start src/index.js src/frontend.js" },

Another way to include the files is to define them in the block metadata contained in our block.json file, as noted in the introduction to block development:

"viewScript": [ "file:./frontend.js", "example-shared-view-script" ], "style": [ "file:./frontend.css", "example-shared-style" ],

The only reason I’m going with the package.json method is because I am already making use of the render_callback() method.

Rendering the JSON data

In the rendering part, I am concentrating only on a single block. Generally speaking, you would want to target multiple blocks on the front end. In that case, you need to make use of document.querySelectorAll() with the block’s specific ID.

I’m basically going to wait for the window to load and grab data for a few key objects from JSON and apply them to some markup that renders them on the front end. I am also going to convert the attributes data to a JSON object so that it is easier to read through the JavaScript and set the details from JSON to HTML for things like the football league logo, team logos, and stats.

The “Last 5 games” column shows the result of a team’s last five matches. I have to manually alter the data for it since the API data is in a string format. Converting it to an array can help make use of it in HTML as a separate element for each of a team’s last five matches.

import "./frontend.scss";  // Wait for the window to load window.addEventListener( "load", () => {   // The code output   const dataEl = document.querySelector( ".data pre" ).innerHTML;   // The parent rankings element   const tableEl = document.querySelector( ".league-table" );   // The table headers   const tableHeaderEl = document.querySelector( "#league-standings .header" );   // Parse JSON for the code output   const dataJSON = JSON.parse( dataEl );   // Print a little note in the console   console.log( "Data from the front end", dataJSON );      // All the teams    let teams = dataJSON.data.response[ 0 ].league.standings[ 0 ];   // The league logo   let leagueLogoURL = dataJSON.data.response[ 0 ].league.logo;   // Apply the league logo as a background image inline style   tableHeaderEl.style.backgroundImage = `url( $ { leagueLogoURL } )`;      // Loop through the teams   teams.forEach( ( team, index ) => {     // Make a div for each team     const teamDiv = document.createElement( "div" );     // Set up the columns for match results     const { played, win, draw, lose, goals } = team.all;      // Add a class to the parent rankings element     teamDiv.classList.add( "team" );     // Insert the following markup and data in the parent element     teamDiv.innerHTML = `       <div class="position">         $ { index + 1 }       </div>       <div class="team-logo">         <img src="$ { team.team.logo }" />       </div>       <div class="team-name">$ { team.team.name }</div>       <div class="stats">         <div class="games-played">$ { played }</div>         <div class="games-won">$ { win }</div>         <div class="games-drawn">$ { draw }</div>         <div class="games-lost">$ { lose }</div>         <div class="goals-for">$ { goals.for }</div>         <div class="goals-against">$ { goals.against }</div>         <div class="points">$ { team.points }</div>       </div>       <div class="form-history"></div>     `;          // Stringify the last five match results for a team     const form = team.form.split( "" );          // Loop through the match results     form.forEach( ( result ) => {       // Make a div for each result       const resultEl = document.createElement( "div" );       // Add a class to the div       resultEl.classList.add( "result" );       // Evaluate the results       resultEl.innerText = result;       // If the result a win       if ( result === "W" ) {         resultEl.classList.add( "win" );       // If the result is a draw       } else if ( result === "D" ) {         resultEl.classList.add( "draw" );       // If the result is a loss       } else {         resultEl.classList.add( "lost" );       }       // Append the results to the column       teamDiv.querySelector( ".form-history" ).append( resultEl );     });      tableEl.append( teamDiv );   }); });

As far as styling goes, you’re free to do whatever you want! If you want something to work with, I have a full set of styles you can use as a starting point.

I styled things in SCSS since the @wordpress/create-block package supports it out of the box. Run npm run start in the command line to watch the SCSS files and compile them to CSS on save. Alternately, you can use npm run build on each save to compile the SCSS and build the rest of the plugin bundle.

View SCSS
body {   background: linear-gradient(to right, #8f94fb, #4e54c8); }  .data pre {   display: none; }  .header {   display: grid;   gap: 1em;   padding: 10px;   grid-template-columns: 1fr 1fr 3fr 4fr 3fr;   align-items: center;   color: white;   font-size: 16px;   font-weight: 600;   background-repeat: no-repeat;   background-size: contain;   background-position: right; }  .frontend#league-standings {   width: 900px;   margin: 60px 0;   max-width: unset;   font-size: 16px;    .header {     .stats {       display: flex;       gap: 15px;        &amp; &gt; div {         width: 30px;       }     }   } }  .league-table {   background: white;   box-shadow:     rgba(50, 50, 93, 0.25) 0px 2px 5px -1px,     rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;   padding: 1em;    .position {     width: 20px;   }    .team {     display: grid;     gap: 1em;     padding: 10px 0;     grid-template-columns: 1fr 1fr 3fr 4fr 3fr;     align-items: center;   }    .team:not(:last-child) {     border-bottom: 1px solid lightgray;   }    .team-logo img {     width: 30px;   }    .stats {     display: flex;     gap: 15px;   }    .stats &gt; div {     width: 30px;     text-align: center;   }    .form-history {     display: flex;     gap: 5px;   }    .form-history &gt; div {     width: 25px;     height: 25px;     text-align: center;     border-radius: 3px;     font-size: 15px;   }    .form-history .win {     background: #347d39;     color: white;   }    .form-history .draw {     background: gray;     color: white;   }    .form-history .lost {     background: lightcoral;     color: white;   } }

Here’s the demo!

Check that out — we just made a block plugin that fetches data and renders it on the front end of a WordPress site.

We found an API, fetch()-ed data from it, saved it to the WordPress database, parsed it, and applied it to some HTML markup to display on the front end. Not bad for a single tutorial, right?

Again, we can do the same sort of thing so that the rankings render in the Block Editor in addition to the theme’s front end. But hopefully keeping this focused on the front end shows you how fetching data works in a WordPress block, and how the data can be structured and rendered for display.


Rendering External API Data in WordPress Blocks on the Front End originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

CSS-Tricks

, , , , ,
[Top]

You don’t need external assets in an HTML file

A fun exercise from Terence Eden. You can send an HTML file over the wire including anything a website might need without requesting any other files. CSS and JavaScript are easy, because there are <script> and <style> tags. Images and fonts (and pretty much whatever other kind of asset) aren’t too hard because Data URLs exist. See Terence’s post for an extra-tricky final version including .zip files.

Reminds me of a couple of other tricks…

Direct Link to ArticlePermalink


The post You don’t need external assets in an HTML file appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

svg-loader: A Different Way to Work With External SVG

SVGs are awesome: they are small, look sharp on any scale, and can be customized without creating a separate file. However, there is something I feel is missing in web standards today: a way to include them as an external file that also retains the format’s customization powers.

For instance, let’s say you want to use your website’s logo stored as web-logo.svg. You can do:

<img src="/images/logo.svg" />

That’s fine if your logo is going to look the same everywhere. But in many cases, you have 2-3 variations of the same logo. Slack, for example, has two versions.

Even the colors in the main logo are slightly different.

If we had a way to customize fill color of our logo above, we could pass any arbitrary color to render all the variations.

Take the case of icons, too. You wouldn’t want to do something like this, would you?

<img src="/icons/heart-blue.svg" /> <img src="/icons/heart-red.svg" />

Load external SVGs as inline elements

To address this, I have created a library called svg-loader. Simply put, it fetches the SVG files via XHR and loads them as inline elements, allowing you to customize the properties like fill and stroke, just like inline SVGs.

For example, I have a logo on my side-project, SVGBox. Instead of creating a different file for every variation, I can have one file and customize the fill color:

I used data-src to set the URL of SVG file. The fill attribute overrides fill of the original SVG file.

To use the library, the only thing I have to ensure is that files being served have appropriate CORS headers for XHRs to succeed. The library also caches the files locally, making the subsequent much faster. Even for the first load, the performance is comparable to using <img> tags.

This concept isn’t new. svg-inject does something similar. However, svg-loader is easier to use as we only have to include the library somewhere in your code (either via a <script> tag, or in the JavaScript bundle). No extra code is needed.

Dynamically-added elements and change in attributes are also handled automatically, which ensures that it works with all web frameworks. Here’s an example in React:

But why?

This approach may feel unorthodox because it introduces a JavaScript dependency and there are already multiple ways to use SVGs, including inline and from external sources. But there’s a good case for using SVGs this way. Let’s examine them by answering the common questions.

Can we not just inline SVG ourselves?

Inlining is the simplest way to use SVGs. Just copy and paste the SVG code in the HTML. That’s what svg-loader is ultimately doing. So, why add the extra steps to load a SVG file from somewhere else? There are two major reasons:

  1. Inline SVGs make the code verbose: SVGs can be anywhere from a few lines to a few hundred. Inline SVGs can work well if what you need is just a couple of icons and they are all tiny. But it becomes a major pain if they are sizeable or many, because then, they become long strings of text in code that isn’t “business logic.” The code becomes hard to parse.

    It’s the same thing as preferring an external stylesheet over a <style> tag or using images instead of data URIs. It’s no wonder that in React codebases, the preferred approach is to use SVG as a separate component, rather than define it as a part of JSX.

  1. External SVGs are much more convenient: Copying and pasting often does the job, but external SVGs can be really convenient. Say you’re experimenting with which icon to use in your app. If you’re using inline SVGs, that means going back and forth to get the SVG code. But with external SVGs, you only have to know the name of the file.

    Take a look at this example. One of the most extensive icon repository on GitHub is Material Design Icons. With svg-loader and unpkg, we can start using any of 5,000+ icons right away.

Isn’t it inefficient to trigger an HTTP request for every SVG versus making a sprite?

Not really. With HTTP2, the cost of making an HTTP request has become less relevant. Yes, there are still benefits of bundling (e.g., better compression), but for non-blocking resources and XHRs, the pros are almost non-existent in real-world scenarios.

Here’s a Pen loading 50 icons in a similar fashion as above. (Open in incognito mode as the files are cached by default):

What about <use> tag (SVG symbols)?

SVG symbols separate the definition of the SVG file from its use. Instead of defining the SVG everywhere, we can have something like this:

<svg>   <use xlink:href="#heart-icon" /> </svg>

The problem is that none of the browsers support using symbols files hosted on a third-party domain. Therefore, we can’t do something like this:

<svg>   <use xlink:href="https://icons.com/symbols.svg#heart-icon" /> </svg>

Safari doesn’t even support symbols files hosted on the same domain.

Can we not use a build tool that inlines the SVGs?

I couldn’t find an obvious way to fetch SVGs from a URL and inline them in common bundlers, like webpack and Grunt, although they exist for inlining SVG files stored locally. Even if a plugin that does this exists, setting up bundlers isn’t exactly straightforward. In fact, I often avoid using them until the project has reached acertain level of complexity. We must also realize that a majority of the internet is alien to things like webpack and React. Simple scripts can have a much wider appeal.

What about the <object> tag?

The <object> tag is a native way to include external SVG files that work across all the browsers.:

<object data="https://unpkg.com/mdi-svg@2.2.43/svg/access-point-network.svg" width="32" height="32"></object>

However, the drawback is we’re unable to customize the SVG’s attributes unless it’s hosted on the same domain (and the <object> tag doesn’t respect CORS headers). Even if the file is hosted on the same domain, we’d require JavaScript to manipulate the fill, like this:

<object data="https://unpkg.com/mdi-svg@2.2.43/svg/access-point-network.svg" width="32" height="32" onload="this.contentDocument.querySelector('svg').fill = 'red'"></object>

In short, using external SVG files this way makes it ultra-convenient to use icons and other SVG assets. As covered earlier, with unpkg, we can use any icon on GitHub without needing extra code. We can avoid creating a pipeline in a bundler to process SVG files or a component for every icon, and just host the icons on a CDN.

Loading SVG files this way packs a lot of benefits with very little cost.


The post svg-loader: A Different Way to Work With External SVG appeared first on CSS-Tricks.

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

CSS-Tricks

, , ,
[Top]