Tag: Building

Does the Next Generation of Static Site Generators Make Building Sites Better?

Just ran across îles, a new static site generator mostly centered around Vue. The world has no particular shortage of static site generators, but it’s interesting to see what this “next generation” of SSGs seem to focus on or try to solve.

îles looks to take a heaping spoonful of inspiration from Astro. If we consider them together, along with other emerging and quickly-evolving SSGs, there is some similarities:

  • Ship zero JavaScript by default. Interactive bits are opt-in — that’s what the islands metaphor is all about. Astro and îles do it at the per-component level and SvelteKit prefers it at the page level.
  • Additional fanciness around controls for when hydration happens, like “when the browser is idle,” or “when the component is visible.”
  • Use a fast build tool, like Vite which is Go-based esbuild under the hood. Or Rust-based swc in the case of Next 12.
  • Support multiple JavaScript frameworks for componentry. Astro and îles do this out of the box, and another example is how Slinkity brings that to Eleventy.
  • File-system based routing.
  • Assumption that Markdown is used for content.

When you compare these to first-cohort SSGs, like Jekyll, I get a few feelings:

  1. These really aren’t that much different. The feature set is largely the same.
  2. The biggest change is probably that far more of them are JavaScript library based. Turns out JavaScript libraries are what really what people wanted out of HTML preprocessors, perhaps because of the strong focus on components.
  3. They are incrementally better. They are faster, the live reloading is better, the common needs have been ironed out.

The post Does the Next Generation of Static Site Generators Make Building Sites Better? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , ,

Building an Angular Data Grid With Filtering

(This is a sponsored post.)

Kendo UI makes it possible to go from a basic idea to a full-fledged app, thanks to a massive component library. We’re talking well over 100 components that are ready for you to drop into your app at will, whether it’s React, Angular or Vue you’re working in — they just work. That is because Kendo UI is actually a bundle of four JavaScript libraries, each built natively for their respective framework. But more than that, as we’ve covered before, the components are super themeable to the extent that you can make them whatever you want.

But here’s the real kicker with Kendo UI: it takes care of the heavy lifting. The styling is great and all, but what separates Kendo UI from other component frameworks is the functionality it provides right out of the box.

Case in point: data. Rather than spending all your time figuring out the best way to bind data to a component, that’s just a given which ultimately allows you to focus more of your time on theming and getting the UI just right.

Perhaps the best way to see how trivial Kendo UI makes working with data is to see it in action, so…

Let’s look at the Angular Grid component

This is the Kendo UI for Angular Data Grid component. Lots of data in there, right? We’re looking at a list of employees that displays a name, image, and other bits of information about each person.

Like all of Kendo UI’s components, it’s not like there’s one data grid component that they square-pegged to work in multiple frameworks. This data grid was built from scratch and designed specifically to work for Angular, just as their KendoReact Grid component is designed specifically for React.

Now, normally, a simple <table> element might do the trick, right? But Kendo UI for Angular’s data grid is chockfull of extras that make it a much better user experience. Notice right off the bat that it provides interactive functionality around things like exporting the data to Excel or PDF. And there are a bunch of other non-trivial features that would otherwise take a vast majority of the time and effort to make the component.

Filtering

Here’s one for you: filtering a grid of data. Say you’re looking at a list of employees like the data grid example above, but for a company that employees thousands of folks. It’d be hard to find a specific person without considering a slew of features, like search, sortable columns, and pagination — all of which Kendo UI’s data grid does.

Users can quickly parse the data bound to the Angular data grid. Filtering can be done through a dedicated filter row, or through a filter menu that pops up when clicking on a filter icon in the header of a column. 

One way to filter the data is to click on a column header, select the Filter option, and set the criteria.

Kendo UI’s documentation is great. Here’s how fast we can get this up and running.

First, import the component

No tricks here — import the data grid as you would any other component:

import { Component, OnInit, ViewChild } from '@angular/core'; import { DataBindingDirective } from '@progress/kendo-angular-grid'; import { process } from '@progress/kendo-data-query'; import { employees } from './employees'; import { images } from './images';

Next, call the component

@Component({   selector: 'my-app',   template: `     <kendo-grid>       // ...     </kendo-grid>   ` })

This is incomplete, of course, because next we have to…

Configure the component

The key feature we want to enable is filtering, but Kendo’s Angular Grid takes all kinds of feature parameters that can be enabled in one fell swoop, from sorting and grouping, to pagination and virtualization, to name a few.

Filtering? It’s a one-liner to bind it to the column headers.

@Component({   selector: 'my-app',   template: `     <kendo-grid       [kendoGridBinding]="gridView"       kendoGridSelectBy="id"       [selectedKeys]="mySelection"       [pageSize]="20"       [pageable]="true"       [sortable]="true"       [groupable]="true"       [reorderable]="true"       [resizable]="true"       [height]="500"       [columnMenu]="{ filter: true }"     >       // etc.     </kendo-grid>   ` })

Then, mark up the rest of the UI

We won’t go deep here. Kendo UI’s documentation has an excellent example of how that looks. This is a good time to work on the styling as well, which is done in a styles parameter. Again, theming a Kendo UI component is a cinch.

So far, we have a nice-looking data grid before we even plug any actual data into it!

And, finally, bind the data

You may have noticed right away when we imported the component that we imported “employee” data in the process. We need to bind that data to the component. Now, this is where someone like me would go run off in a corner and cry, but Kendo UI makes it a little too easy for that to happen.

// Active the component on init export class AppComponent implements OnInit {   // Bind the employee data to the component   @ViewChild(DataBindingDirective) dataBinding: DataBindingDirective;   // Set the grid's data source to the employee data file   public gridData: any[] = employees;   // Apply the data source to the Grid component view   public gridView: any[];    public mySelection: string[] = [];    public ngOnInit(): void {     this.gridView = this.gridData;   }   // Start processing the data   public onFilter(inputValue: string): void {     this.gridView = process(this.gridData, {       filter: {         // Set the type of logic (and/or)         logic: "or",         // Defining filters and their operators         filters: [           {             field: 'full_name',             operator: 'contains',             value: inputValue           },           {             field: 'job_title',             operator: 'contains',             value: inputValue           },           {             field: 'budget',             operator: 'contains',             value: inputValue           },           {             field: 'phone',             operator: 'contains',             value: inputValue           },           {             field: 'address',             operator: 'contains',             value: inputValue           }         ],       }     }).data;      this.dataBinding.skip = 0;   }    // ... }

Let’s see that demo again


That’s a heckuva lot of power with a minimal amount of effort. The Kendo UI APIs are extensive and turn even the most complex feature dead simple.

And we didn’t even get to all of the other wonderful goodies that we get with Kendo UI components. Take accessibility. Could you imagine all of the consideration that needs to go into making a component like this accessible? Like all of the other powerful features we get, Kendo UI tackles accessibility for us as well, taking on the heavy lifting that goes into making a keyboard-friendly UI that meets WCAG 2.0 Alice standards and is compliant with Section 508 and WAI-ARIA standards.


The post Building an Angular Data Grid With Filtering appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Building a Tennis Trivia App With Next.js and Netlify

Today we will be learning how to build a tennis trivia app using Next.js and Netlify. This technology stack has become my go-to on many projects. It allows for rapid development and easy deployment.

Without further ado let’s jump in!

What we’re using

  • Next.js
  • Netlify
  • TypeScript
  • Tailwind CSS

Why Next.js and Netlify

You may think that this is a simple app that might not require a React framework. The truth is that Next.js gives me a ton of features out of the box that allow me to just start coding the main part of my app. Things like webpack configuration, getServerSideProps, and Netlify’s automatic creation of serverless functions are a few examples.

Netlify also makes deploying a Next.js git repo super easy. More on the deployment a bit later on.

What we’re building

Basically, we are going to build a trivia game that randomly shows you the name of a tennis player and you have to guess what country they are from. It consists of five rounds and keeps a running score of how many you get correct.

The data we need for this application is a list of players along with their country. Initially, I was thinking of querying some live API, but on second thought, decided to just use a local JSON file. I took a snapshot from RapidAPI and have included it in the starter repo.

The final product looks something like this:

You can find the final deployed version on Netlify.

Starter repo tour

If you want to follow along you can clone this repository and then go to the start branch:

git clone git@github.com:brenelz/tennis-trivia.git cd tennis-trivia git checkout start

In this starter repo, I went ahead and wrote some boilerplate to get things going. I created a Next.js app using the command npx create-next-app tennis-trivia. I then proceeded to manually change a couple JavaScript files to .ts and .tsx. Surprisingly, Next.js automatically picked up that I wanted to use TypeScript. It was too easy! I also went ahead and configured Tailwind CSS using this article as a guide.

Enough talk, let’s code!

Initial setup

The first step is setting up environment variables. For local development, we do this with a .env.local file. You can copy the .env.sample from the starter repo.

cp .env.sample .env.local

Notice it currently has one value, which is the path of our application. We will use this on the front end of our app, so we must prefix it with NEXT_PUBLIC_.

Finally, let’s use the following commands to install the dependencies and start the dev server: 

npm install npm run dev

Now we access our application at http://localhost:3000. We should see a fairly empty page with just a headline:

Creating the UI markup

In pages/index.tsx, let’s add the following markup to the existing Home() function:

export default function Home() {   return (     <div className="bg-blue-500">     <div className="max-w-2xl mx-auto text-center py-16 px-4 sm:py-20 sm:px-6 lg:px-8">       <h2 className="text-3xl font-extrabold text-white sm:text-4xl">         <span className="block">Tennis Trivia - Next.js Netlify</span>       </h2>       <div>         <p className="mt-4 text-lg leading-6 text-blue-200">           What country is the following tennis player from?         </p>         <h2 className="text-lg font-extrabold text-white my-5">           Roger Federer         </h2>          <form>           <input             list="countries"             type="text"             className="p-2 outline-none"             placeholder="Choose Country"           />           <datalist id="countries">             <option>Switzerland</option>            </datalist>            <p>              <button                className="mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"                type="submit"              >                Guess             </button>           </p>         </form>          <p className="mt-4 text-lg leading-6 text-white">           <strong>Current score:</strong> 0         </p>       </div>     </div>     </div>   );

This forms the scaffold for our UI. As you can see, we are using lots of utility classes from Tailwind CSS to make things look a little prettier. We also have a simple autocomplete input and a submit button. This is where you will select the country you think the player is from and then hit the button. Lastly, at the bottom, there is a score that changes based on correct or incorrect answers.

Setting up our data

If you take a look at the data folder, there should be a tennisPlayers.json with all the data we will need for this application. Create a lib folder at the root and, inside of it, create a players.ts file. Remember, the .ts extension is required since is a TypeScript file. Let’s define a type that matches our JSON data..

export type Player = {   id: number,   first_name: string,   last_name: string,   full_name: string,   country: string,   ranking: number,   movement: string,   ranking_points: number, };

This is how we create a type in TypeScript. We have the name of the property on the left, and the type it is on the right. They can be basic types, or even other types themselves.

From here, let’s create specific variables that represent our data:

export const playerData: Player[] = require("../data/tennisPlayers.json"); export const top100Players = playerData.slice(0, 100);  const allCountries = playerData.map((player) => player.country).sort(); export const uniqueCountries = [...Array.from(new Set(allCountries))];

A couple things to note is that we are saying our playerData is an array of Player types. This is denoted by the colon followed by the type. In fact, if we hover over the playerData we can see its type:

In that last line we are getting a unique list of countries to use in our country dropdown. We pass our countries into a JavaScript Set, which gets rid of the duplicate values. We then create an array from it, and spread it into a new array. It may seem unnecessary but this was done to make TypeScript happy.

Believe it or not, that is really all the data we need for our application!

Let’s make our UI dynamic!

All our values are hardcoded currently, but let’s change that. The dynamic pieces are the tennis player’s name, the list of countries, and the score.

Back in pages/index.tsx, let’s modify our getServerSideProps function to create a list of five random players as well as pull in our uniqueCountries variable.

import { Player, uniqueCountries, top100Players } from "../lib/players"; ... export async function getServerSideProps() {   const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());   const players = randomizedPlayers.slice(0, 5);    return {     props: {       players,       countries: uniqueCountries,     },   }; }

Whatever is in the props object we return will be passed to our React component. Let’s use them on our page:

type HomeProps = {   players: Player[];   countries: string[]; };  export default function Home({ players, countries }: HomeProps) {   const player = players[0];   ... }  

As you can see, we define another type for our page component. Then we add the HomeProps type to the Home() function. We have again specified that players is an array of the Player type.

Now we can use these props further down in our UI. Replace “Roger Federer” with {player.full_name} (he’s my favorite tennis player by the way). You should be getting nice autocompletion on the player variable as it lists all the property names we have access to because of the types that we defined.

Further down from this, let’s now update the list of countries to this:

<datalist id="countries">   {countries.map((country, i) => (     <option key={i}>{country}</option>   ))} </datalist>

Now that we have two of the three dynamic pieces in place, we need to tackle the score. Specifically, we need to create a piece of state for the current score.

export default function Home({ players, countries }: HomeProps) {   const [score, setScore] = useState(0);   ... }

Once this is done, replace the 0 with {score} in our UI.

You can now check our progress by going to http://localhost:3000. You can see that every time the page refreshes, we get a new name; and when typing in the input field, it lists all of the available unique countries.

Adding some interactivity

We’ve come a decent way but we need to add some interactivity.

Hooking up the guess button

For this we need to have some way of knowing what country was picked. We do this by adding some more state and attaching it to our input field.

export default function Home({ players, countries }: HomeProps) {   const [score, setScore] = useState(0);   const [pickedCountry, setPickedCountry] = useState("");   ...   return (     ...     <input       list="countries"       type="text"       value={pickedCountry}       onChange={(e) => setPickedCountry(e.target.value)}       className="p-2 outline-none"       placeholder="Choose Country"     />    ...   ); }

Next, let’s add a guessCountry function and attach it to the form submission:

const guessCountry = () => {   if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {     setScore(score + 1);   } else {     alert(‘incorrect’);   } }; ... <form   onSubmit={(e) => {     e.preventDefault();     guessCountry();   }} >

All we do is basically compare the current player’s country to the guessed country. Now, when we go back to the app and guess the country right, the score increases as expected.

Adding a status indicator

To make this a bit nicer, we can render some UI depending whether the guess is correct or not.

So, let’s create another piece of state for status, and update the guess country method:

const [status, setStatus] = useState(null); ... const guessCountry = () => {   if (player.country.toLowerCase() === pickedCountry.toLowerCase()) {     setStatus({ status: "correct", country: player.country });     setScore(score + 1);   } else {     setStatus({ status: "incorrect", country: player.country });   } };

Then render this UI below the player name:

{status && (   <div className="mt-4 text-lg leading-6 text-white">     <p>             You are {status.status}. It is {status.country}     </p>     <p>       <button         autoFocus         className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"       >         Next Player       </button>     </p>   </div> )}

Lastly, we want to make sure our input field doesn’t show when we are in a correct or incorrect status. We achieve this by wrapping the form with the following:

{!status && (   <form>   ...   </form> )}

Now, if we go back to the app and guess the player’s country, we get a nice message with the result of the guess.

Progressing through players

Now probably comes the most challenging part: How do we go from one player to the next?

First thing we need to do is store the currentStep in state so that we can update it with a number from 0 to 4. Then, when it hits 5, we want to show a completed state since the trivia game is over.

Once again, let’s add the following state variables:

const [currentStep, setCurrentStep] = useState(0); const [playersData, setPlayersData] = useState(players);

…then replace our previous player variable with:

const player = playersData[currentStep];

Next, we create a nextStep function and hook it up to the UI:

const nextStep = () => {   setPickedCountry("");   setCurrentStep(currentStep + 1);   setStatus(null); }; ... <button   autoFocus   onClick={nextStep}   className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-blue-600 bg-white hover:bg-blue-50 sm:w-auto"  >     Next Player </button>

Now, when we make a guess and hit the next step button, we’re taken to a new tennis player. Guess again and we see the next, and so on. 

What happens when we hit next on the last player? Right now, we get an error. Let’s fix that by adding a conditional that represents that the game has been completed. This happens when the player variable is undefined.

{player ? (   <div>     <p className="mt-4 text-lg leading-6 text-blue-200">       What country is the following tennis player from?     </p>     ...     <p className="mt-4 text-lg leading-6 text-white">       <strong>Current score:</strong> {score}     </p>   </div> ) : (   <div>     <button       autoFocus       className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto"       >       Play Again     </button>   </div> )}

Now we see a nice completed state at the end of the game.

Play again button

We are almost done! For our “Play Again” button we want to reset the state all of the game. We also want to get a new list of players from the server without needing a refresh. We do it like this:

const playAgain = async () => {   setPickedCountry("");   setPlayersData([]);   const response = await fetch(     process.env.NEXT_PUBLIC_API_URL + "/api/newGame"   );   const data = await response.json();   setPlayersData(data.players);   setCurrentStep(0);   setScore(0); };  <button   autoFocus   onClick={playAgain}   className="outline-none mt-8 w-full inline-flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-indigo-600 bg-white hover:bg-indigo-50 sm:w-auto" >   Play Again </button>

Notice we are using the environment variable we set up before via the process.env object. We are also updating our playersData by overriding our server state with our client state that we just retrieved.

We haven’t filled out our newGame route yet, but this is easy with Next.js and Netlify serverless functions . We only need to edit the file in pages/api/newGame.ts.

import { NextApiRequest, NextApiResponse } from "next" import { top100Players } from "../../lib/players";  export default (req: NextApiRequest, res: NextApiResponse) => {   const randomizedPlayers = top100Players.sort((a, b) => 0.5 - Math.random());   const top5Players = randomizedPlayers.slice(0, 5);   res.status(200).json({players: top5Players}); }

This looks much the same as our getServerSideProps because we can reuse our nice helper variables.

If we go back to the app, notice the “Play Again” button works as expected.

Improving focus states

One last thing we can do to improve our user experience is set the focus on the country input field every time the step changes. That’s just a nice touch and convenient for the user. We do this using a ref and a useEffect:

const inputRef = useRef(null); ... useEffect(() => {   inputRef?.current?.focus(); }, [currentStep]);  <input   list="countries"   type="text"   value={pickedCountry}   onChange={(e) => setPickedCountry(e.target.value)}   ref={inputRef}   className="p-2 outline-none"   placeholder="Choose Country" />

Now we can navigate much easier just using the Enter key and typing a country.

Deploying to Netlify

You may be wondering how we deploy this thing. Well, using Netlify makes it so simple as it detects a Next.js application out of the box and automatically configures it.

All I did was set up a GitHub repo and connect my GitHub account to my Netlify account. From there, I simply pick a repo to deploy and use all the defaults.

The one thing to note is that you have to add the NEXT_PUBLIC_API_URL environment variable and redeploy for it to take effect.

You can find my final deployed version here.

Also note that you can just hit the “Deploy to Netlify” button on the GitHub repo.

Conclusion

Woohoo, you made it! That was a journey and I hope you learned something about React, Next.js, and Netlify along the way.

I have plans to expand this tennis trivia app to use Supabase in the near future so stay tuned!

If you have any questions/comments feel free to reach out to me on Twitter.


The post Building a Tennis Trivia App With Next.js and Netlify appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Developer Decisions For Building Flexible Components

Blog posts that get into the whole “how to think like a front-end developer” vibe are my favorite. Michelle Barker nails that in this post, and does it without sharing a line of code!

We simply can no longer design and develop only for “optimal” content or browsing conditions. Instead, we must embrace the inherent flexibility and unpredictability of the web, and build resilient components. Static mockups cannot cater to every scenario, so many design decisions fall to developers at build time. Like it or not, if you’re a UI developer, you are a designer — even if you don’t consider yourself one!

There are a lot of unknowns in front-end development. Much longer than my little list. Content of unknown size and length is certainly one of them. Then square the possibilities with every component variation while ensuring good accessibility and you’ve got, well, a heck of a job to do.

Direct Link to ArticlePermalink


The post Developer Decisions For Building Flexible Components appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Building a Form in PHP Using DOMDocument

Templating makes the web go round. The synthesis of data and structure into content. It’s our coolest superpower as developers — grab some data, then make it work for us, in whatever presentation we need. An array of objects can become a table, a list of cards, a chart, or whatever we think is most useful to the user. Whether the data is our own blog posts in Markdown files, or on-the-minute global exchange rates, the markup and resulting UX are up to us as front-end developers.

PHP is an amazing language for templating, providing many ways to merge data with markup. Let’s get into an example of using data to build out an HTML form in this post.

Want to get your hands dirty right away? Jump to the implementation.

In PHP, we can inline variables into string literals that use double quotes, so if we have a variable $ name = 'world', we can write echo "Hello, {$ name}", and it prints the expected Hello, world. For more complex templating, we can always concatenate strings, like: echo "Hello, " . $ name . ".".

For the old-schoolers, there’s printf("Hello, %s", $ name). For multiline strings, you can use Heredoc (the one that starts like <<<MYTEXT). And, last but certainly not least, we can sprinkle PHP variables inside HTML, like <p>Hello, <?= $ name ?></p>.

All of these options are great, but things can get messy when a lot of inline logic is required. If we need to build compound HTML strings, say a form or navigation, the complexity is potentially infinite, since HTML elements can nest inside each other.

What we’re trying to avoid

Before we go ahead and do the thing we want to do, it’s worth taking a minute to consider what we don’t want to do. Consider the following abridged passage from the scripture of WordPress Core, class-walker-nav-menu.php, verses 170-270:

<?php // class-walker-nav-menu.php // ... $  output .= $  indent . '<li' . $  id . $  class_names . '>'; // ... $  item_output  = $  args->before; $  item_output .= '<a' . $  attributes . '>'; $  item_output .= $  args->link_before . $  title . $  args->link_after; $  item_output .= '</a>'; $  item_output .= $  args->after; // ... $  output .= apply_filters( 'walker_nav_menu_start_el', $  item_output, $  item, $  depth, $  args ); // ... $  output .= "</li>{$  n}";

In order to build out a navigation <ul> in this function, we use a variable, $ output, which is a very long string to which we keep adding stuff. This type of code has a very specific and limited order of operations. If we wanted to add an attribute to the <a>, we must have access to $ attributes before this runs. And if we wanted to optionally nest a <span> or an <img> inside the <a>, we’d need to author a whole new block of code that would replace the middle of line 7 with about 4-10 new lines, depending on what exactly we want to add. Now imagine you need to optionally add the <span> , and then optionally add the <img>, either inside the <span> or after it. That alone is three if statements, making the code even less legible.

It’s very easy to end up with string spaghetti when concatenating like this, which is as fun to say as it is painful to maintain.

The essence of the problem is that when we try to reason about HTML elements, we’re not thinking about strings. It just so happens that strings are what the browser consumes and PHP outputs. But our mental model is more like the DOM — elements are arranged into a tree, and each node has many potential attributes, properties, and children.

Wouldn’t it be great if there were a structured, expressive way to build our tree?

Enter…

The DOMDocument class

PHP 5 added the DOM module to it’s roster of Not So Strictly Typed™ types. Its main entry point is the DOMDocument class, which is intentionally similar to the Web API’s JavaScript DOM. If you’ve ever used document.createElement or, for those of us of a certain age, jQuery’s $ ('<p>Hi there!</p>') syntax, this will probably feel quite familiar.

We start out by initializing a new DOMDocument:

$  dom = new DOMDocument();

Now we can add a DOMElement to it:

$  p = $  dom->createElement('p');

The string 'p' represents the type of element we want, so other valid strings would be 'div', 'img' , etc.

Once we have an element, we can set its attributes:

$  p->setAttribute('class', 'headline');

We can add children to it:

$  span = $  dom->createElement('span', 'This is a headline'); // The 2nd argument populates the element's textContent $  p->appendChild($  span);

And finally, get the complete HTML string in one go:

$  dom->appendChild($  p); $  htmlString = $  dom->saveHTML(); echo $  htmlString;

Notice how this style of coding keeps our code organized according to our mental model — a document has elements; elements can have any number of attributes; and elements nest inside one another without needing to know anything about each other. The whole “HTML is just a string” part comes in at the end, once our structure is in place.

The “document” here is a bit different from the actual DOM, in that it doesn’t need to represent an entire document, just a block of HTML. In fact, if you need to create two similar elements, you could save a HTML string using saveHTML(), modify the DOM “document” some more, and then save a new HTML string by calling saveHTML() again.

Getting data and setting the structure

Say we need to build a form on the server using data from a CRM provider and our own markup. The API response from the CRM looks like this:

{   "submit_button_label": "Submit now!",   "fields": [     {       "id": "first-name",       "type": "text",       "label": "First name",       "required": true,       "validation_message": "First name is required.",       "max_length": 30     },     {       "id": "category",       "type": "multiple_choice",       "label": "Choose all categories that apply",       "required": false,       "field_metadata": {         "multi_select": true,         "values": [           { "value": "travel", "label": "Travel" },           { "value": "marketing", "label": "Marketing" }         ]       }     }   ] }

This example doesn’t use the exact data structure of any specific CRM, but it’s rather representative.

And let’s suppose we want our markup to look like this:

<form>   <label class="field">     <input type="text" name="first-name" id="first-name" placeholder=" " required>     <span class="label">First name</span>     <em class="validation" hidden>First name is required.</em>   </label>   <label class="field checkbox-group">     <fieldset>       <div class="choice">         <input type="checkbox" value="travel" id="category-travel" name="category">         <label for="category-travel">Travel</label>       </div>       <div class="choice">         <input type="checkbox" value="marketing" id="category-marketing" name="category">         <label for="category-marketing">Marketing</label>       </div>     </fieldset>     <span class="label">Choose all categories that apply</span>   </label> </form>

What’s that placeholder=" "? It’s a small trick that allows us to track in CSS whether the field is empty, without needing JavaScript. As long as the input is empty, it matches input:placeholder-shown, but the user doesn’t see any visible placeholder text. Just the kind of thing you can do when we control the markup!

Now that we know what our desired result is, here’s the game plan:

  1. Get the field definitions and other content from the API
  2. Initialize a DOMDocument
  3. Iterate over the fields and build each one as required
  4. Get the HTML output

So let’s stub out our process and get some technicalities out of the way:

<?php function renderForm ($  endpoint) {   // Get the data from the API and convert it to a PHP object   $  formResult = file_get_contents($  endpoint);   $  formContent = json_decode($  formResult);   $  formFields = $  formContent->fields;    // Start building the DOM   $  dom = new DOMDocument();   $  form = $  dom->createElement('form');    // Iterate over the fields and build each one   foreach ($  formFields as $  field) {     // TODO: Do something with the field data   }    // Get the HTML output   $  dom->appendChild($  form);   $  htmlString = $  dom->saveHTML();   echo $  htmlString; }

So far, we’ve gotten the data and parsed it, initialized our DOMDocument and echoed its output. What do we want to do for each field? First off, let’s build the container element which, in our example, should be a <label>, and the labelling <span> which is common to all field types:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  label = null;    // Add a `<span>` for the label if it is set   if ($  field->label) {     $  label = $  dom->createElement('span', $  field->label);     $  label->setAttribute('class', 'label');   }    // Add the label to the `<label>`   if ($  label) $  element->appendChild($  label); }

Since we’re in a loop, and PHP doesn’t scope variables in loops, we reset the $ label element on each iteration. Then, if the field has a label, we build the element. At the end, we append it to the container element.

Notice that we set classes using the setAttribute method. Unlike the Web API, there unfortunately is no special handing of class lists. They’re just another attribute. If we had some really complex class logic, since It’s Just PHP™, we could create an array and then implode it:
$ label->setAttribute('class', implode($ labelClassList)).

Single inputs

Since we know that the API will only return specific field types, we can switch over the type and write specific code for each one:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;    // Add a `<span>` for the label if it is set   // ...    // Build the input element   switch ($  field->type) {     case 'text':     case 'email':     case 'telephone':       $  input = $  dom->createElement('input');       $  input->setAttribute('placeholder', ' ');       if ($  field->type === 'email') $  input->setAttribute('type', 'email');       if ($  field->type === 'telephone') $  input->setAttribute('type', 'tel');       break;   } }

Now let’s handle text areas, single checkboxes and hidden fields:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;    // Add a `<span>` for the label if it is set   // ...    // Build the input element   switch ($  field->type) {     //...       case 'text_area':       $  input = $  dom->createElement('textarea');       $  input->setAttribute('placeholder', ' ');       if ($  rows = $  field->field_metadata->rows) $  input->setAttribute('rows', $  rows);       break;      case 'checkbox':       $  element->setAttribute('class', 'field single-checkbox');       $  input = $  dom->createElement('input');       $  input->setAttribute('type', 'checkbox');       if ($  field->field_metadata->initially_checked === true) $  input->setAttribute('checked', 'checked');       break;      case 'hidden':       $  input = $  dom->createElement('input');       $  input->setAttribute('type', 'hidden');       $  input->setAttribute('value', $  field->field_metadata->value);       $  element->setAttribute('hidden', 'hidden');       $  element->setAttribute('style', 'display: none;');       $  label->textContent = '';       break;   } }

Notice something new we’re doing for the checkbox and hidden cases? We’re not just creating the <input> element; we’re making changes to the container <label> element! For a single checkbox field we want to modify the class of the container, so we can align the checkbox and label horizontally; a hidden <input>‘s container should also be completely hidden.

Now if we were merely concatenating strings, it would be impossible to change at this point. We would have to add a bunch of if statements regarding the type of element and its metadata in the top of the block. Or, maybe worse, we start the switch way earlier, then copy-paste a lot of common code between each branch.

And here is the real beauty of using a builder like DOMDocument — until we hit that saveHTML(), everything is still editable, and everything is still structured.

Nested looping elements

Let’s add the logic for <select> elements:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;    // Add a `<span>` for the label if it is set   // ...    // Build the input element   switch ($  field->type) {     //...       case 'select':       $  element->setAttribute('class', 'field select');       $  input = $  dom->createElement('select');       $  input->setAttribute('required', 'required');       if ($  field->field_metadata->multi_select === true)         $  input->setAttribute('multiple', 'multiple');            $  options = [];            // Track whether there's a pre-selected option       $  optionSelected = false;            foreach ($  field->field_metadata->values as $  value) {         $  option = $  dom->createElement('option', htmlspecialchars($  value->label));              // Bail if there's no value         if (!$  value->value) continue;              // Set pre-selected option         if ($  value->selected === true) {           $  option->setAttribute('selected', 'selected');           $  optionSelected = true;         }         $  option->setAttribute('value', $  value->value);         $  options[] = $  option;       }            // If there is no pre-selected option, build an empty placeholder option       if ($  optionSelected === false) {         $  emptyOption = $  dom->createElement('option');              // Set option to hidden, disabled, and selected         foreach (['hidden', 'disabled', 'selected'] as $  attribute)           $  emptyOption->setAttribute($  attribute, $  attribute);         $  input->appendChild($  emptyOption);       }            // Add options from array to `<select>`       foreach ($  options as $  option) {         $  input->appendChild($  option);       }   break;   } }

OK, so there’s a lot going on here, but the underlying logic is the same. After setting up the outer <select>, we make an array of <option>s to append inside it.

We’re also doing some <select>-specific trickery here: If there is no pre-selected option, we add an empty placeholder option that is already selected, but can’t be selected by the user. The goal is to place our <label class="label"> as a “placeholder” using CSS, but this technique can be useful for all kinds of designs. By appending it to the $ input before appending the other options, we make sure it is the first option in the markup.

Now let’s handle <fieldset>s of radio buttons and checkboxes:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;    // Add a `<span>` for the label if it is set   // ...    // Build the input element   switch ($  field->type) {     // ...       case 'multiple_choice':       $  choiceType = $  field->field_metadata->multi_select === true ? 'checkbox' : 'radio';       $  element->setAttribute('class', "field {$  choiceType}-group");       $  input = $  dom->createElement('fieldset');            // Build a choice `<input>` for each option in the fieldset       foreach ($  field->field_metadata->values as $  choiceValue) {         $  choiceField = $  dom->createElement('div');         $  choiceField->setAttribute('class', 'choice');              // Set a unique ID using the field ID + the choice ID         $  choiceID = "{$  field->id}-{$  choiceValue->value}";              // Build the `<input>` element         $  choice = $  dom->createElement('input');         $  choice->setAttribute('type', $  choiceType);         $  choice->setAttribute('value', $  choiceValue->value);         $  choice->setAttribute('id', $  choiceID);         $  choice->setAttribute('name', $  field->id);         $  choiceField->appendChild($  choice);              // Build the `<label>` element         $  choiceLabel = $  dom->createElement('label', $  choiceValue->label);         $  choiceLabel->setAttribute('for', $  choiceID);         $  choiceField->appendChild($  choiceLabel);              $  input->appendChild($  choiceField);       }   break;   } }

So, first we determine if the field set should be for checkboxes or radio button. Then we set the container class accordingly, and build the <fieldset>. After that, we iterate over the available choices and build a <div> for each one with an <input> and a <label>.

Notice we use regular PHP string interpolation to set the container class on line 21 and to create a unique ID for each choice on line 30.

Fragments

One last type we have to add is slightly more complex than it looks. Many forms include instruction fields, which aren’t inputs but just some HTML we need to print between other fields.

We’ll need to reach for another DOMDocument method, createDocumentFragment(). This allows us to add arbitrary HTML without using the DOM structuring:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // Build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;    // Add a `<span>` for the label if it is set   // ...    // Build the input element   switch ($  field->type) {     //...       case 'instruction':       $  element->setAttribute('class', 'field text');       $  fragment = $  dom->createDocumentFragment();       $  fragment->appendXML($  field->text);       $  input = $  dom->createElement('p');       $  input->appendChild($  fragment);       break;   } }

At this point you might be wondering how we found ourselves with an object called $ input, which actually represents a static <p> element. The goal is to use a common variable name for each iteration of the fields loop, so at the end we can always add it using $ element->appendChild($ input) regardless of the actual field type. So, yeah, naming things is hard.

Validation

The API we are consuming kindly provides an individual validation message for each required field. If there’s a submission error, we can show the errors inline together with the fields, rather than a generic “oops, your bad” message at the bottom.

Let’s add the validation text to each element:

<?php // ... // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // build the container `<label>`   $  element = $  dom->createElement('label');   $  element->setAttribute('class', 'field');    // Reset input values   $  input = null;   $  label = null;   $  validation = null;    // Add a `<span>` for the label if it is set   // ...    // Add a `<em>` for the validation message if it is set   if (isset($  field->validation_message)) {     $  validation = $  dom->createElement('em');     $  fragment = $  dom->createDocumentFragment();     $  fragment->appendXML($  field->validation_message);     $  validation->appendChild($  fragment);     $  validation->setAttribute('class', 'validation-message');     $  validation->setAttribute('hidden', 'hidden'); // Initially hidden, and will be unhidden with Javascript if there's an error on the field   }    // Build the input element   switch ($  field->type) {     // ...   } }

That’s all it takes! No need to fiddle with the field type logic — just conditionally build an element for each field.

Bringing it all together

So what happens after we build all the field elements? We need to add the $ input, $ label, and $ validation objects to the DOM tree we’re building. We can also use the opportunity to add common attributes, like required. Then we’ll add the submit button, which is separate from the fields in this API.

<?php function renderForm ($  endpoint) {   // Get the data from the API and convert it to a PHP object   // ...    // Start building the DOM   $  dom = new DOMDocument();   $  form = $  dom->createElement('form');    // Iterate over the fields and build each one   foreach ($  formFields as $  field) {     // Build the container `<label>`     $  element = $  dom->createElement('label');     $  element->setAttribute('class', 'field');        // Reset input values     $  input = null;     $  label = null;     $  validation = null;        // Add a `<span>` for the label if it is set     // ...        // Add a `<em>` for the validation message if it is set     // ...        // Build the input element     switch ($  field->type) {       // ...     }        // Add the input element     if ($  input) {       $  input->setAttribute('id', $  field->id);       if ($  field->required)         $  input->setAttribute('required', 'required');       if (isset($  field->max_length))         $  input->setAttribute('maxlength', $  field->max_length);       $  element->appendChild($  input);          if ($  label)         $  element->appendChild($  label);          if ($  validation)         $  element->appendChild($  validation);          $  form->appendChild($  element);     }   }      // Build the submit button   $  submitButtonLabel = $  formContent->submit_button_label;   $  submitButtonField = $  dom->createElement('div');   $  submitButtonField->setAttribute('class', 'field submit');   $  submitButton = $  dom->createElement('button', $  submitButtonLabel);   $  submitButtonField->appendChild($  submitButton);   $  form->appendChild($  submitButtonField);    // Get the HTML output   $  dom->appendChild($  form);   $  htmlString = $  dom->saveHTML();   echo $  htmlString; }

Why are we checking if $ input is truthy? Since we reset it to null at the top of the loop, and only build it if the type conforms to our expected switch cases, this ensures we don’t accidentally include unexpected elements our code can’t handle properly.

Hey presto, a custom HTML form!

Bonus points: rows and columns

As you may know, many form builders allow authors to set rows and columns for fields. For example, a row might contain both the first name and last name fields, each in a single 50% width column. So how would we go about implementing this, you ask? By exemplifying (once again) how loop-friendly DOMDocument is, of course!

Our API response includes the grid data like this:

{   "submit_button_label": "Submit now!",   "fields": [     {       "id": "first-name",       "type": "text",       "label": "First name",       "required": true,       "validation_message": "First name is required.",       "max_length": 30,       "row": 1,       "column": 1     },     {       "id": "category",       "type": "multiple_choice",       "label": "Choose all categories that apply",       "required": false,       "field_metadata": {         "multi_select": true,         "values": [           { "value": "travel", "label": "Travel" },           { "value": "marketing", "label": "Marketing" }         ]       },       "row": 2,       "column": 1     }   ] }

We’re assuming that adding a data-column attribute is enough for styling the width, but that each row needs to be it’s own element (i.e. no CSS grid).

Before we dive in, let’s think through what we need in order to add rows. The basic logic goes something like this:

  1. Track the latest row encountered.
  2. If the current row is larger, i.e. we’ve jumped to the next row, create a new row element and start adding to it instead of the previous one.

Now, how would we do this if we were concatenating strings? Probably by adding a string like '</div><div class="row">' whenever we reach a new row. This kind of “reversed HTML string” is always very confusing to me, so I can only imagine how my IDE feels. And the cherry on top is that thanks to the browser auto-closing open tags, a single typo will result in a gazillion nested <div>s. Just like fun, but the opposite.

So what’s the structured way to handle this? Thanks for asking. First let’s add row tracking before our loop and build an additional row container element. Then we’ll make sure to append each container $ element to its $ rowElement rather than directly to $ form.

<?php function renderForm ($  endpoint) {   // Get the data from the API and convert it to a PHP object   // ...    // Start building the DOM   $  dom = new DOMDocument();   $  form = $  dom->createElement('form');    // init tracking of rows   $  row = 0;   $  rowElement = $  dom->createElement('div');   $  rowElement->setAttribute('class', 'field-row');    // Iterate over the fields and build each one   foreach ($  formFields as $  field) {     // Build the container `<label>`     $  element = $  dom->createElement('label');     $  element->setAttribute('class', 'field');     $  element->setAttribute('data-row', $  field->row);     $  element->setAttribute('data-column', $  field->column);          // Add the input element to the row     if ($  input) {       // ...       $  rowElement->appendChild($  element);       $  form->appendChild($  rowElement);     }   }   // ... }

So far we’ve just added another <div> around the fields. Let’s build a new row element for each row inside the loop:

<?php // ... // Init tracking of rows $  row = 0; $  rowElement = $  dom->createElement('div'); $  rowElement->setAttribute('class', 'field-row');  // Iterate over the fields and build each one foreach ($  formFields as $  field) {   // ...   // If we've reached a new row, create a new $  rowElement   if ($  field->row > $  row) {     $  row = $  field->row;     $  rowElement = $  dom->createElement('div');     $  rowElement->setAttribute('class', 'field-row');   }    // Build the input element   switch ($  field->type) {     // ...       // Add the input element to the row       if ($  input) {         // ...         $  rowElement->appendChild($  element);          // Automatically de-duped         $  form->appendChild($  rowElement);       }   } }

All we need to do is overwrite the $ rowElement object as a new DOM element, and PHP treats it as a new unique object. So, at the end of every loop, we just append whatever the current $ rowElement is — if it’s still the same one as in the previous iteration, then the form is updated; if it’s a new element, it is appended at the end.

Where do we go from here?

Forms are a great use case for object-oriented templating. And thinking about that snippet from WordPress Core, an argument might be made that nested menus are a good use case as well. Any task where the markup follows complex logic makes for a good candidate for this approach. DOMDocument can output any XML, so you could also use it to build an RSS feed from posts data.

Here’s the entire code snippet for our form. Feel free it adapt it to any form API you find yourself dealing with. Here’s the official documentation, which is good for getting a sense of the available API.

We didn’t even mention DOMDocument can parse existing HTML and XML. You can then look up elements using the XPath API, which is kinda similar to document.querySelector, or cheerio on Node.js. There’s a bit of a learning curve, but it’s a super powerful API for handling external content.

Fun(?) fact: Microsoft Office files that end with x (e.g. .xlsx) are XML files. Don’t tell the marketing department, but it’s possible to parse Word docs and output HTML on the server.

The most important thing is to remember that templating is a superpower. Being able to build the right markup for the right situation can be the key to a great UX.


The post Building a Form in PHP Using DOMDocument appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , ,
[Top]

Building Your Own Subscription Newsletter

(This is a sponsored post.)

I did a sponsored video the other week explaining how to build a paid subscription newsletter using WordPress (we did it on WordPress.com but it could be hosted anywhere), MailPoet (a plugin to visually author the emails, as well as send them), and WooCommerce (to manage the payments and subscriptions).

I published the video here and there is a landing page for the whole concept here.

I spent a lot of time on it! I feel personally compelled by the idea because I’m pretty big on having a website you control be the home base for your business. If a paid newsletter is part of your business, awesome, might as well use your own website to do it.

If you’d like to be more slowly guided through the process, watch that video above. I literally do the entire thing from start to finish in that video. But I know some folks like a more rapid-fire explanation, so allow me to do that quick.


1. Have a WordPress site

Self-hosted works. You can also use WordPress.com so long as you’re on the Business or eCommerce plan, because you’ll need to install plugins.

2. Install WooCommerce & WooCommerce Subscriptions on it.

These are two separate plugins. WooCommerce is free. WooCommerce Subscriptions is $ 199/year.

3) Install MailPoet

MailPoet is a plugin. On WordPress.com, you might even be prompted to install it while installing the WooCommerce plugins. But if not, you can install it anytime.

4) Create a product that is a subscription

This is straightforward stuff in WooCommerce. Add a product and set the type to “Simple subscription.”

5. Craft an awesome newsletter in MailPoet

This is what MailPoet excels at. It’s a visual email builder right in your WordPress admin. It has excellent templates to start you started.

6. Send the newsletter only to people with an active subscription.

The trick is to make a “segment” of a list that is specifically created from WooCommerce customers that have an active subscription.


That’s it, really. You should know that MailPoet is an email sending service as well, and you’ll probably want to use that, since it’s free up to 1,000 subscribers anyway, is very easy to hook up, and will provide you with analytics data on the sends. But you can also wire it up to other email sending services as well.

Again, the beauty of all this is that it all happens from your website — meaning you could also be sending out a non-subscription newsletter with the same system. You could publish the newsletters on your website as an SEO play. You could sell other products and services since you have 100% of the infrastructure to do that now. You’re really in good hands here with a lot of power and flexibility.


The post Building Your Own Subscription Newsletter appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Building a Cool Front End Thing Generator

Whether you are just starting out on the front end, or you’ve been doing it for a long time, building a tool that can generate some cool front-end magic can help you learn something new, develop your skills and maybe even get you a little notoriety.

You might have run across some of these popular online generators:

I’ve had fun building a few of these myself over the years. Basically, any time you run across some cool front-end thing, there might be an opportunity to make an interactive generator for that thing.

In this case, we are going to make an Animated Background Gradient Generator.

Scaffolding the project in Next

A nice thing about these projects is that they’re all yours. Choose whatever stack you want and get going. I’m a big fan of Next.js, so for this project, I’m going to start as a basic Create Next App project.

npx create-next-app animated-gradient-background-generator 

This generates all the files we need to get started. We can edit pages/index.js to be the shell for our project.

import Head from "next/head" import Image from "next/image" export default function Home() {   return (     <>       <Head>         <title>Animated CSS Gradient Background Generator</title>         <meta name="description" content="A tool for creating animated background gradients in pure CSS." />         <link rel="icon" href="/favicon.ico" />       </Head>       <main>         <h1>           Animated CSS Gradient Background Generator         </h1>       </main>     </>   ) }

Animated gradients?

At the time I’m writing this article, if you do a search for animated CSS gradient background, the first result is this Pen by Manuel Pinto.

Let’s take a look at the CSS:

body {   background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);   background-size: 400% 400%;   animation: gradient 15s ease infinite; }  @keyframes gradient {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } } 

This is a great example that we can use as the foundation for the generated animation.

A React component to describe an animated gradient

We can break out a few possible configurable options for the generator:

  • An array of gradient colors
  • The angle of the gradient
  • The speed of the animation

To put in context, we want to provide these settings as data throughout our little app using a higher-order component, context/SettingsContext.js, along with some defaults.

import React, { useState, createContext } from "react"  const SettingsContext = createContext({ colorSelection: [] })  const SettingsProvider = ({ children }) => {   const [colorSelection, setColorSelection] = useState([     "deepskyblue",     "darkviolet",     "blue",   ])   const [angle, setAngle] = useState(300)   const [speed, setSpeed] = useState(5)      return (     <SettingsContext.Provider       value={{         colorSelection,         setColorSelection,         angle,         setAngle,         speed,         setSpeed,       }}     >       {children}     </SettingsContext.Provider>   ) }  export { SettingsContext, SettingsProvider }

For our generator’s components, we want to create:

  • a control components to adjust these settings,
  • a visual display component for generated animated gradient, and
  • a component for the CSS code output.

Let’s start with a Controls component that contains the various inputs we used to adjust the settings.

import Colors from "./Colors"  const Controls = (props) => (   <>     <Colors />   </> )  export default Controls

We can add our SettingsProvider and Controls components to pages/index.js:

import Head from "next/head" import Image from "next/image" import { SettingsProvider } from "../context/SettingsContext" import Controls from "../components/Controls" import Output from "../components/Output"  export default function Home() {   return (     <>       <Head>         ...       </Head>        <SettingsProvider>         <main style={{ textAlign: "center", padding: "64px" }}>           <h1>Animated CSS Gradient Background Generator</h1>           <Controls />           <Output />         </main>       </SettingsProvider>     </>   ) }

Our SettingsProvider begins with the three colors from our CodePen example as defaults. We can verify that we are getting the color settings via our SettingsContext in a new Colors component.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Colors = () => {   const { colorSelection } = useContext(SettingsContext)   return (     <>       {colorSelection.map((color) => (         <div>{color}</div>       ))}     </>   ) }  export default Colors

Let’s use the Colors component to display individual color swatches with a small button to delete via our SettingsContext.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Colors = () => {   const { colorSelection, setColorSelection } = useContext(SettingsContext)    const onDelete = (deleteColor) => {     setColorSelection(colorSelection.filter((color) => color !== deleteColor))   }    return (     <div>       {colorSelection.map((color) => (         <div           key={color}           style={{             background: color,             display: "inline-block",             padding: "32px",             margin: "16px",             position: "relative",             borderRadius: "4px",           }}         >           <button             onClick={() => onDelete(color)}             style={{               background: "crimson",               color: "white",               display: "inline-block",               borderRadius: "50%",               position: "absolute",               top: "-8px",               right: "-8px",               border: "none",               fontSize: "18px",               lineHeight: 1,               width: "24px",               height: "24px",               cursor: "pointer",               boxShadow: "0 0 1px #000",             }}           >             ×           </button>         </div>       ))}     </div>   ) }  export default Colors

You may notice that we have been using inline styles for CSS at this point. Who cares! We’re having fun here, so we can do whatever floats our boats.

Handling colors

Next, we create an AddColor component with a button that opens a color picker used to add more colors to the gradient.

For the color picker, we will install react-color and use the ChromePicker option.

npm install react-color

Once again, we will utilize SettingsContext to update the gradient color selection.

import React, { useState, useContext } from "react" import { ChromePicker } from "react-color" import { SettingsContext } from "../context/SettingsContext"  const AddColor = () => {   const [color, setColor] = useState("white")   const { colorSelection, setColorSelection } = useContext(SettingsContext)    return (     <>       <div style={{ display: "inline-block", paddingBottom: "32px" }}>         <ChromePicker           header="Pick Colors"           color={color}           onChange={(newColor) => {             setColor(newColor.hex)           }}         />       </div>       <div>         <button           onClick={() => {             setColorSelection([...colorSelection, color])           }}           style={{             background: "royalblue",             color: "white",             padding: "12px 16px",             borderRadius: "8px",             border: "none",             fontSize: "16px",             cursor: "pointer",             lineHeight: 1,           }}         >           + Add Color         </button>       </div>     </>   ) }  export default AddColor

Handling angle and speed

Now that our color controls are finished, let’s add some components with range inputs for setting the angle and animation speed.

Here’s the code for AngleRange, with SpeedRange being very similar.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const AngleRange = () => {   const { angle, setAngle } = useContext(SettingsContext)    return (     <div style={{ padding: "32px 0", fontSize: "18px" }}>       <label         style={{           display: "inline-block",           fontWeight: "bold",           width: "100px",           textAlign: "right",         }}         htmlFor="angle"       >         Angle       </label>       <input         type="range"         id="angle"         name="angle"         min="-180"         max="180"         value={angle}         onChange={(e) => {           setAngle(e.target.value)         }}         style={{           margin: "0 16px",           width: "180px",           position: "relative",           top: "2px",         }}       />       <span         style={{           fontSize: "14px",           padding: "0 8px",           position: "relative",           top: "-2px",           width: "120px",           display: "inline-block",         }}       >         {angle} degrees       </span>     </div>   ) }  export default AngleRange

Now for the fun part: rendering the animated background. Let’s apply this to the entire background of the page with an AnimatedBackground wrapper component.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext" const AnimatedBackground = ({ children }) => {   const { colorSelection, speed, angle } = useContext(SettingsContext) const background =   "linear-gradient(" + angle + "deg, " + colorSelection.toString() + ")" const backgroundSize =   colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%" const animation =   "gradient-animation " +   colorSelection.length * Math.abs(speed - 11) +   "s ease infinite" return (   <div style={{ background, "background-size": backgroundSize, animation, color: "white" }}>     {children}   </div>   ) } export default AnimatedBackground

We’re calling the CSS animation for the gradient gradient-animation. We need to add that to styles/globals.css to trigger the animation:

@keyframes gradient-animation {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } } 

Making it useful to users

Next, let’s add some code output so people can copy and paste the generated CSS and use in their own projects.

import React, { useContext, useState } from "react" import { SettingsContext } from "../context/SettingsContext" const Output = () => {   const [copied, setCopied] = useState(false) const { colorSelection, speed, angle } = useContext(SettingsContext) const background =   "linear-gradient(" + angle + "deg," + colorSelection.toString() + ")" const backgroundSize =   colorSelection.length * 60 + "%" + " " + colorSelection.length * 60 + "%" const animation =   "gradient-animation " +   colorSelection.length * Math.abs(speed - 11) +   "s ease infinite" const code = `.gradient-background {   background: $  {background};   background-size: $  {backgroundSize};   animation: $  {animation}; } @keyframes gradient-animation {   0% {     background-position: 0% 50%;   }   50% {     background-position: 100% 50%;   }   100% {     background-position: 0% 50%;   } }` return (     <div       style={{ position: "relative", maxWidth: "640px", margin: "64px auto" }}     >       <pre         style={{           background: "#fff",           color: "#222",           padding: "32px",           width: "100%",           borderRadius: "4px",           textAlign: "left",           whiteSpace: "pre",           boxShadow: "0 2px 8px rgba(0,0,0,.33)",           overflowX: "scroll",         }}       >         <code>{code}</code>         <button           style={{             position: "absolute",             top: "8px",             right: "8px",             background: "royalblue",             color: "white",             padding: "8px 12px",             borderRadius: "8px",             border: "none",             fontSize: "16px",             cursor: "pointer",             lineHeight: 1,           }}           onClick={() => {             setCopied(true)             navigator.clipboard.writeText(code)           }}         >           {copied ? "copied" : "copy"}         </button>       </pre>     </div>   ) } export default Output

Making it fun

It is sometimes fun (and useful) to add a button that sets random values on a generator like this. That gives people a way to quickly experiment and see what kinds of results they can get out of the tool. It is also an opportunity to look up cool stuff like how to generate random hex colors.

import React, { useContext } from "react" import { SettingsContext } from "../context/SettingsContext"  const Random = () => {   const { setColorSelection, setAngle, setSpeed } = useContext(SettingsContext)    const goRandom = () => {     const numColors = 3 + Math.round(Math.random() * 3)     const colors = [...Array(numColors)].map(() => {       return "#" + Math.floor(Math.random() * 16777215).toString(16)     })     setColorSelection(colors)     setAngle(Math.floor(Math.random() * 361))     setSpeed(Math.floor(Math.random() * 10) + 1)   }    return (     <div style={{ padding: "48px 0 16px" }}>       <button         onClick={goRandom}         style={{           fontSize: "24px",           fontWeight: 200,           background: "rgba(255,255,255,.9)",           color: "blue",           padding: "24px 48px",           borderRadius: "8px",           cursor: "pointer",           boxShadow: "0 0 4px #000",           border: "none",         }}       >         RANDOM       </button>     </div>   ) }  export default Random

Wrapping up

There will be a few final things you’ll want to do to wrap up your project for initial release:

  • Update package.json with your project information.
  • Add some links to your personal site, the project’s repository and give credit where its due.
  • Update the README.md file that was generated with default content by Create Next App.

That’s it! We’re ready to release our new cool front end thing generator and reap the rewards of fame and fortune that await us!

You can see the code for this project on GitHub and the demo is hosted on Netlify.


The post Building a Cool Front End Thing Generator appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Building a Command Line Tool with Nodejs and Fauna

Command line tools are one of the most popular applications we have today. We use command line tools every day, and they range from git, npm or yarn. Command line tools are very fast and useful for automating applications and workflows.

We will be building a command line tool with Node.js and Fauna for our database in this post. In addition, we will be creating a random quotes application using Node.js, and add permission and a keyword for our app.

Prerequisites

To take full advantage of this tutorial, make sure you have the following installed on your local development environment:

  • Node.js version >= 16.x.x installed.
  • Have access to one package manager such as npm or yarn.
  • Access to Fauna dashboard.

Getting Started with Fauna

Register a new account using email credentials or a GitHub account. You can register a new account here. Once you have created a new account or signed in, you are going to be welcomed by the dashboard screen:

Creating a New Fauna Instance

To create a new database instance using Fauna services, you have to follow some simple steps. On the dashboard screen, press the button New Database:

Next, enter the name of the database and save. Once a database instance is set up, you are ready to access the key. Use access keys to connect authorization and a connection to the database from a single-page application. To create your access key, navigate to the side menu, and go to the Security tab and click on the New Key button.

Creating a Collection

Navigate to your dashboard, click on the Collections tab from the side menu, press the New Collection, button, input your desired name for the new collection, and save.

Creating Indexes

To complete setup, create indexes for our application. Indexes are essential because searching documents are done using indexes in Fauna by matching the user input against the tern field. Create an index by navigating to the Indexes tab of our Fauna dashboard.

Now, we are ready to build our notes command-line application using Node.js and our database.

Initializing a Node.js App and Installing Dependencies

This section will initialize a Node.js application and install the dependencies we need using the NPM package. We are also going to build a simple quotes application from this link.

Getting Started

To get started, let’s create a folder for our application inside the project folder using the code block below on our terminal:

mkdir quotes_cli cd quotes_cli touch quotes_app npm init -y

In the code block above, we created a new directory, navigated into the directory, and created a new file called quotes_app, and ended by initializing the npm dependencies. Next, add a package to make requests to the quotes server using axios.

npm i axios

Add a package for coloring our texts, chalk is an NPM package that helps us add color to print on the terminal. To add chalk, use the code block below

npm i chalk Let’s also import a dotenv package using the code block:

npm i dotenv

Building the Quotes App

In our quotes_app file, let’s add the code block below

const axios = require('axios') const chalk = require('chalk'); const dotenv = require('dotenv'); const url = process.env.APP_URL axios({   method: 'get',   url: url,   headers: { 'Accept': 'application/json' }, }).then(res => {   const quote = res.data.contents.quotes[0].quote   const author = res.data.contents.quotes[0].author   const log = chalk.red(`$ {quote} - $ Fortune Ikechi`)    console.log(log) }).catch(err => {   const log = chalk.red(err)    console.log(log) })

In the code block above, we imported axios, chalk, and dotenv. We added the URL of our database, our Fauna database, and using axios, we made a GET request to the URL and added headers to enable us to get our response in json.

To log a quote, we use JavaScript promises to log the quote and its author on our console and added a catch method for catching errors.

Before we run, let’s change the permissions on our file using the code below:

chmod +x quotes_app

Next, run the application using our keyword below:

./quotes_app

We should get a result similar to the image below

Conclusion

In this article, we learned more about Fauna and Node.js command-line tools. You can extend the application to be able to add date reminders in real-time.

Here is a list of some resources that you might like after reading this post:


The post Building a Command Line Tool with Nodejs and Fauna appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

A Look at Building with Astro

Astro is a brand new framework for building websites. To me, the big thing is that it allows you to build a site like you’re using a JavaScript framework (and you are), but the output is a zero-JavaScript static site. You can opt-in to client-side JavaScript as needed, and there are clever options for doing so. Notably, the learning curve is somewhat flattened by the fact that it supports componentry you may already know: React/Preact (JSX), Svelte, Vue, or web components.

Table stakes

Starting a new project is as easy as it should be:

npm init astro npm install npm start

There is a helpful little process and output:

As expected (like you would get with Next or Nuxt or any other site builder kind of project) you get a dev server at a local port you can pop right up:

From here, I consider the table stakes to be CSS injection / Hot Module Reloading. No worries there:

A static site generator with honest-to-god real actual components

This is such a wonderful thing to me. I really like the idea of static site generators—I think they make a lot of sense in a lot of situations. Sending HTML over-the-wire is just a good move for resiliency, CDN-efficiency, SEO, accessibility, you name it. But in the past a lot of the options were either:

  • A JavaScript powered static site generator, that does generate a “static” site, but also ships a JavaScript bundle (e.g. Next or Gatsby)
  • A static site generator that is more focused on HTML and has its own templating/formats that aren’t JavaScript components (e.g. Eleventy or Jekyll)

I know there are exceptions, but this covers the vast majority of the site generator market.

But I want both!

  • I want to craft sites from JavaScript-components, because the syntax and tooling around them is just better than any other component system we have right now.
  • I want static output that is actually zero-JavaScript (unless I manually opt-in to things).

That’s what happens with Astro.

Those components?

  • They can be .jsx files
  • They can be .svelte files
  • They can be .vue files
  • These are “renderers” and you can BYO.

Astro also has it’s own format (.astro) and it’s also very compelling because:

  • It’s obviously a first-class citizen of how Astro works
  • It’s comfortably JSX-like…
  • …except better because it does stuff like makes the <head> work automatically
  • Styled scoping works out of the box, through a normal <style> tag
  • “Fenced” JavaScript runs during build. Let’s look at that next.

Astro files

I mentioned some of the cool parts about the .astro syntax right above. At a higher level, I just like how they look. So little boilerplate! Just gets right to it.

--- import SomeComponent from "../components/SomeComponent";  // This runs in Node, so you look at your command line to see it. console.log("Hi.");  // Example: <SomeComponent greeting="(Optional) Hello" name="Required Name" /> const { greeting = 'Hello', name } = Astro.props; const items = ["Dog", "Cat", "Platipus"]; --- <!-- JSX-like, but also more pleasantly HTML like, like this comment  --> <div class="module">   <h1>{greeting}, {name}!</h1>   <ul>     {items.map((item) => (       <li>{item}</li>     ))} </ul> </div>  <SomeComponent regular="props" />  <style>    /* Scoped! */   .module {     padding: 1rem;   } </style>

The “fences” (---) at the top is where the initial JavaScriptin’ goes. That’s where I yank in the props for this component if it needs any (they can be typed if you like that), do imports/exports, and set up data for the template below.

What feels a little funky, but is in-line with the Astro vibe, is that this is essentially Node JavaScript. It runs in the build process. So that console.log() statement I don’t see in my browser console, I see it in my command line.

pages-style routing

It’s tempting to say Next.js popularized this, but really the concept is as old as file systems. Think of how a classic Apache server works. If you have a file system like:

index.html /about/   index.html

In a browser, you can visit http://website.com/about and that will render that index.html page under the /about folder. That’s what the routing is like here. By virtue of me having:

/pages/   index.astro   about.astro

I’ll have a homepage as well as an /about/ page on my site. That’s just a refreshingly nice way to deal with routing—as opposed to needing to build your own routing with component-ry all to itself.

If you want to do that thing where all the content of your site lives in Markdown files right in the repo, that’s a first-class citizen.

I think this is super common for stuff like blogs and documentation, especially as those are already popular targets for static site generators. And in these early days, I think we’re going to see a lot of Astro sites along those lines while people wait to see if it’s ready for bigger undertakings.

One way to use Markdown is to make Pages in Markdown straight away. The Markdown will also have “fences” (Frontmatter) where you chuck what layout you want to use (best to use an .astro file) and pass in data if you need to. Then the entire content of the Markdown file will flow into the <slot />. Pretty darn slick:

Another incredibly satisfying way to use Markdown in Astro is using the built-in <Markdown /> component. Import it and use it:

--- import { Markdown } from 'astro/components'; ---  <main>   <Markdown>     # Hello world!          - Do thing     - Another thing in my *cool list*   </Markdown>    <div>Outside Markdown</div> </main>

You can also go snag some Markdown from elsewhere in your project and barf that into a component. That leads into fetching data, so let’s look at that next.

I suppose it’s kind of weird how Astro supports all these different frameworks out of the box.

I’ve overheard some pushback that Astro is inefficient at the npm install level since you have to bring down a bunch of stuff you likely won’t need or use. I’ve overheard some pushback on the idea that mixing-matching JavaScript frameworks is a terrible idea.

I agree it’s weird-feeling, but I’m not particularly worried about non-user-facing things. When things are happening only during the build process and all the user ever gets is HTML, use whatever feels good! If you ultimately do load the components-based frameworks to do on-page interactive things, surely it makes sense to limit it to one. And since you’re getting so much at build time, maybe it makes sense to use something designed for super light on-rendered-page interactivity.

Fetching data rules

We were just talking about Markdown so let’s close the loop there. You can “fetch” data internally in Astro by using fetchContent. Look how straightforward it is:

I fetch it the raw Markdown, then I could use the HTML it returns if I want, or slap it into a <Markdown /> component if that makes sense for whatever reason:

--- import { Markdown } from 'astro/components'; const localData = Astro.fetchContent('../content/data.md'); ---  <div class="module">   <Markdown content={localData[0].astro.source} /> </div>

But I don’t have to fetch internal data only. I’m a fan of Eleventy. During an Eleventy build, you can certainly go fetch data from an outside source, but I’d argue it’s a little finnicky. You fetch the data with code in a separate JavaScript file, pulling in your own network library, then processing and returning the data to use elsewhere in a template. Like this. In Astro, that fetching can happen right alongside the component where you need it.

Check out this real-world-ish example where I yank in data from right here from CSS-Tricks and display it as cards.

--- import Card from '../components/Card.astro'; import Header from '../components/Header';  const remoteData = await fetch('https://css-tricks.com/wp-json/wp/v2/posts?per_page=12&_embed').then(response => response.json()); ---  <!doctype html> <html lang="en">  <head>     <meta charset="UTF-8">     <meta name="viewport" content="width=device-width, initial-scale=1">     <title>CSS-Trickzz</title>     <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⭐️</text></svg>">     <link rel="stylesheet" href="/style/global.css">      <style lang="scss">       .grid {         margin: 4rem;         display: grid;         grid-template-columns: repeat(2, 1fr);         @media (max-width: 650px) {           grid-template-columns: repeat(1, 1fr);           margin: 2rem;         }         gap: 3rem;       }     </style> </head>  <body>   <main>     <Header />      <div class="grid">       {remoteData.map((post) => {         return(           <Card              title={post.title.rendered}             link={post.link}             excerpt={post.excerpt.rendered}             featured_img={post.featured_media_src_url}           />         )       })}     </div>    </main> </body>  </html>

Check it out, I can build a page from CSS-Tricks data just that easily:

What’s fascinating about that is that the data happens:

  1. in Node, not client-side, and
  2. during the build process.

So, in order to keep a website like this updated, I’d have to run the build/deploy process regularly.

Styling

Let’s say you want to use Sass to style your site. With many site generators, they punt on this, as a philosophy. Like saying “nah, we don’t want to be opinionated here, you style however you want to”. And I get that, it might be a strength as sometimes frameworks that are too opinionated lose people. But to me, it often feels unfortunate as now I’m on my own to wire up some style-processing build processes (e.g. Gulp) that I really just don’t want to deal with.

With Astro, the philosophy seems to be to support a wide swath of popular styling techniques out of the box right away.

  • Just import "./style.css"; vanilla stylesheets
  • Use a <style> block anywhere in .astro files and the CSS will be scoped to that component…
  • … which is like CSS modules, but that’s only needed if you go for a .jsx file, and if you do, it’s supported.
  • The styling capabilities of .svelte and .vue files work as expected.
  • Sass is built in, just put <style lang="scss"> on the styling blocks wherever.

The styling doc has more detail.

The fancy opt-in JavaScript tricks

Allow me to blockquote this from the README:

  • <MyComponent /> will render an HTML-only version of MyComponent (default)
  • <MyComponent:load /> will render MyComponent on page load
  • <MyComponent:idle /> will use requestIdleCallback() to render MyComponent as soon as main thread is free
  • <MyComponent:visible /> will use an IntersectionObserver to render MyComponent when the element enters the viewport

That’s some fancy dancing. HTML by default, and you opt-in to running your components client-side (JavaScript) only when you specifically want to, and even then, under efficient conditions.

I put a little Vue-based counter (from their examples) onto my demo site and used the :visible modifier to see it work. Check it out:

The Vue stuff only loads when it needs to. Like the blog post says:

Of course, sometimes client-side JavaScript is inevitable. Image carousels, shopping carts, and auto-complete search bars are just a few examples of things that require some JavaScript to run in the browser. This is where Astro really shines: When a component needs some JavaScript, Astro only loads that one component (and any dependencies). The rest of your site continues to exist as static, lightweight HTML.

The Discord is poppin’

I should point out that Astro is super duper new. As I write, they don’t even have real documentation up. It might feel a bit early to be using a framework with docs that only exist as a README. They are working on it though! I’ve seen previews of it because I happen to be in the Discord.

I think they are very smart to have a public Discord as it means there is a hot-n-fast feedback loop for them to improve the framework. I’ve found that being in it is super useful.

I believe they are hoping that Astro grows up into much more than a framework, but a complete platform, where Astro is just the open-source core. You can hear Fred talk with Jason about that on a Learn with Jason episode.


The post A Look at Building with Astro appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, ,
[Top]

Scaling Organizations Should Consider Building a Website Backed by a CRM Platform

To make some terminology clear here:

  • CMS = Content Management System
  • CRM = Customer Relationship Management

Both are essentially database-backed systems for managing data. HubSpot is both, and much more. Where a CMS might be very focused on content and the metadata around making content useful, a CRM is focused on leads and making communicating with current and potential customers easier.

They can be brothers-in-arms. We’ll get to that.

Say a CRM is set up for people. You run a Lexus dealership. There is a quote form on the website. People fill it out and enter the CRM. That lead can go to your sales team for taking care of that customer.

But a CRM could be based on other things. Say instead of people it’s based on real estate listings. Each main entry is a property, with essentially metadata like photos, address, square footage, # of bedrooms/baths, etc. Leads can be associated with properties.

That would be a nice CRM setup for a real estate agency, but the data that is in that CRM might be awfully nice for literally building a website around those property listings. Why not tap into that CRM data as literal data to build website pages from?

That’s what I mean by a CRM and CMS being brothers-in-arms. Use them both! That’s why HubSpot can be an ideal home for websites like this.

To keep that tornado of synergy going, HubSpot can also help with marketing, customer service, and integrations. So there is a lot of power packed into one platform.

And with that power, also a lot of comfort and flexibility.

  • You’re still developing locally.
  • You’re still using Git.
  • You can use whatever framework or site-building tools you want.
  • You’ve got a CLI to control things.
  • There is a VS Code Extension for super useful auto-complete of your data.
  • There is a staging environment.

And the feature just keep coming. HubSpot really has a robust set of tools to make sure you can do what you need to do.

As developer-rich as this all is, it doesn’t mean that it’s developer-only. There are loads of tools for working with the website you build that require no coding at all. Dashboard for content management, data wrangling, style control, and even literal drag-and-drop page builders.

It’s all part of a very learnable system.

Themestemplatesmodules, and fields are the objects you’ll work with most in HubSpot CMS as a developer. Using these different objects effectively lets you give content creators the freedom to work and iterate on websites independently while staying inside style and layout guardrails you set.


The post Scaling Organizations Should Consider Building a Website Backed by a CRM Platform appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , , , ,
[Top]