The Process of Implementing A UI Design From Scratch

This is a fantastic post by Ahmad Shadeed. It digs into the practical construction of a header on a website — the kind of work that many of us regularly do. It looks like it’s going to be fairly easy to create the header at first, but it starts to get complicated as considerations for screen sizes, edge cases, interactions, and CSS layout possibilities enter the mix. This doesn’t even get into cross-browser concerns, which have been somewhat less of a struggle lately, but is always there.

It’s sometimes hard to explain the interesting and tricky situations that define our work in front-end development, and this article does a good job of showcasing that.

These two illustrations set the scene really well:

That’s not to dunk on designers. I’ve done this to myself plenty of times. Plus, any designer worth their salt thinks about these things right off the bat. The reality is that the implementation weirdness largely falls to the front-end developer and the designer can help when big choices need to be made or the solutions need to be clarified.

Direct Link to ArticlePermalink

The post The Process of Implementing A UI Design From Scratch appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

Designing An Aspect Ratio Unit For CSS

Rachel Andrew says that the CSS Working Group designed an aspect ration unit at a recent meeting. The idea is to find an elegant solution to those times when we want the height of an element to be calculated in response to the width of the element, or vice versa.

Say, for example, we have a grid layout with a row of elements that should maintain a square (1:1) ratio. There are a few existing, but less-than-ideal ways we can go:

  • Define one explicit dimension. If we define the height of the items in the grid but use an intrinsic value on the template columns (e.g. auto-fill), then we no longer know the width of the element as the container responds to the browser viewport. The elements will grow and squish but never maintain that perfect square.
  • Define explicit dimensions. Sure, we can tell an element to be 200px wide and 200px tall, but what happens when more content is added to an item than there is space? Now we have an overflow problem.
  • Use the padding hack. The block-level (top-to-bottom) padding percentage of an element is calculated by the element’s inline width and we can use that to calculate height. But, as Rachel suggests, it’s totally un-intuitive and tough to maintain.

Chris wrote up a full rundown of possible ways to maintain aspect ratio, but none of them are as elegant as CSS Working Group’s proposed solution:

.box {   width: 400px;   height: auto;   aspect-ratio: 1/1; }

See that? An aspect-ratio property with an aspect ratio unit would calculate the height of the element based on the value of the width property.

Or, in the case of a grid layout where the columns are set to auto-fill:

.grid {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }  .item {   aspect-ratio: 1/1; }

Very cool! But where does the proposal go from here? The aspect-ratio is included in the rough draft of the CSS Sizing 4 specification. Create an issue in the CSSWG GitHub repo if you have ideas or suggestions. Or, as Rachel suggests, comment on her Smashing Magazine post or even write up your own post and send it along. This is a great chance to add your voice to a possible new feature.

Direct Link to ArticlePermalink

The post Designing An Aspect Ratio Unit For CSS appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Planning for Responsive Images

The first time I made an image responsive, it was as simple as coding these four lines:

img {   max-width: 100%;   height auto; /* default */ }

Though that worked for me as a developer, it wasn’t the best for the audience. What happens if the the image in the src attribute is heavy? On high-end developer devices (like mine with 16GB RAM), few or no performance problems occur. But on low-end devices? It’s another story.

image at multiple screen sizes

The above illustration isn’t detailed enough. I’m from Nigeria and, if your product works in Africa, then you shouldn’t be looking at that. Look at this graph instead:

Nowadays, the lowest-priced iPhone sells for an average of $ 300. The average African can’t afford it even though iPhone is a threshold for measuring fast devices.

That’s all the business analysis you need to understand that CSS width doesn’t cut it for responsive images. What would, you ask? Let me first explain what images are about.

Nuances of images

Images are appealing to users but are a painstaking challenge for us developers who must consider the following factors:

  • Format
  • Disk size
  • Render dimension (layout width and height in the browser)
  • Original dimension (original width and height)
  • Aspect ratio

So, how do we pick the right parameters and deftly mix and match them to deliver an optimal experience for your audience? The answer, in turn, depends on the answers to these questions:

  • Are the images created dynamically by the user or statically by a design team?
  • If the width and height of the image are changed disproportionately, would that affect the quality?
  • Are all the images rendered at the same width and height? When rendered, must they have a specific aspect ratio or one that’s entirely different?
  • What must be considered when presenting the images on different viewports?

Jot down your answers. They will not only help you understand your images — their sources, technical requirements and such — but also enable you to make the right choices in delivery.

Provisional strategies for image delivery

Image delivery has evolved from a simple addition of URLs to the src attribute to complex scenarios. Before delving into them, let’s talk about the multiple options for presenting images so that you can devise a strategy on how and when to deliver and render yours.

First, identify the sources of the images. That way, the number of obscure edge cases can be reduced and the images can be handled as efficiently as possible.

In general, images are either:

  • Dynamic: Dynamic images are uploaded by the audience, having been generated by other events in the system.
  • Static: A photographer, designer, or you (the developer) create the images for the website.

Let’s dig into strategy for each of this types of images.

Strategy for dynamic images

Static images are fairly easy to work with. On the other hand, dynamic images are tricky and prone to problems. What can be done to mitigate their dynamic nature and make them more predictable like static images? Two things: validation and intelligent cropping.

Validation

Set out a few rules for the audience on what is acceptable and what is not. Nowadays, we can validate all the properties of an image, namely:

  • Format
  • Disk size
  • Dimension
  • Aspect ratio

Note: An image’s render size is determined during rendering, hence no validation on our part.

After validation, a predictable set of images would emerge, which are easier to consume.

Intelligent Cropping

Another strategy for handling dynamic images is to crop them intelligently to avoid deleting important content and refocus on (or re-center) the primary content. That’s hard to do. However, you can take advantage of the artificial intelligence offered by open-source tools or SaaS companies that specialize in image management. An example is in the upcoming sections.


Once a strategy has been nailed down for dynamic images, create a rule table with all the layout options for the images. Below is an example. It’s even worth looking into analytics to determine the most important devices and viewport sizes.

Browser Viewport HP Laptop PS4 Slim Camera Lens / Aspect Ratio
< 300 100 vw 100 vw 100 vw/1:2
300 – 699 100 vw 100 vw 100 vw/1:1
700 – 999 50 vw 50 vw 50 vw/1:1
> 999 33 vw 33 vw 100 vw/1:2

The bare (sub-optimal) minimum

Now set aside the complexities of responsiveness and just do what we do best — simple HTML markup with maximum-width CSS.

The following code renders a few images:

<main>   <figure>     <img src="https://res.cloudinary.com/...w700/ps4-slim.jpg" alt="PS4 Slim">   </figure>          <figure>     <img src="https://res.cloudinary.com/...w700/x-box-one-s.jpg" alt="X Box One S">   </figure>          <!-- More images -->        <figure>     <img src="https://res.cloudinary.com/...w700/tv.jpg" alt="Tv">   </figure> </main>

Note: The ellipsis (…) in the image URL specifies the folder, dimension, and cropping strategy, which are too much detail to include, hence the truncation to focus on what matters now. For the complete version, see the CodePen example down below.

This is the shortest CSS example on the Internet that makes images responsive:

/* The parent container */ main {   display: grid;   grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }  img {   max-width: 100%; }

If the images do not have a uniform width and height, replace max-width with object-fit and set the value to cover.

Jo Franchetti’s blog post on common responsive layouts with CSS Grid explains how the value of grid-template-columns makes the entire layout adaptive (responsive).

See the Pen
Grid Gallery
by Chris Nwamba (@codebeast)
on CodePen.

The above is not what we are looking for, however, because…

  • the image size and weight are the same on both high-end and low-end devices, and
  • we might want to be stricter with the image width instead of setting it to 250 and letting it grow.

Well, this section covers “the bare minimum” so that’s it.

Layout variations

The worst thing that can happen to an image layout is mismanagement of expectations. Because images might have varying dimensions (width and height), we must specify how to render the images.

Should we intelligently crop all the images to a uniform dimension? Should we retain the aspect ratio for a viewport and alter the ratio for a different one? The ball is in our court.

In case of images in a grid, such as those in the example above with different aspect ratios, we can apply the technique of art direction to render the images. Art direction can help achieve something like this:

For details on resolution switching and art direction in responsive images, read Jason Grigsby’s series. Another informative reference is Eric Portis’s Responsive Images Guide, parts 1, 2, and 3.

See the code example below.

<main>   <figure>     <picture>       <source media="(min-width: 900px)" srcset="https://res.cloudinary.com/.../c_fill,g_auto,h_1400,w_700/camera-lens.jpg">              <img src="https://res.cloudinary.com/.../c_fill,g_auto,h_700,w_700/camera-lens.jpg" alt="Camera lens">     </picture>   </figure>      <figure>     <picture>       <source media="(min-width: 700px)" srcset="https://res.cloudinary.com/.../c_fill,g_auto,h_1000,w_1000/ps4-pro.jpg"></source>     </picture>     <img src="https://res.cloudinary.com/.../c_fill,g_auto,h_700,w_700/ps4-pro.jpg" alt="PS4 Pro">   </figure> </main>

Instead of rendering only one 700px wide image, we render 700px x 700px only if the viewport width exceeds 700px. If the viewport is larger, then the following rendering occurs:

  • Camera lens images are rendered as a portrait image of 700px in width and 1000px. in height (700px x 1000px).
  • PS4 Pro images are rendered at 1000px x 1000px.

Art direction

By cropping images to make them responsive, we might inadvertently delete the primary content, like the face of the subject. As mentioned previously, AI open-source tools can help crop intelligently and refocus on the primary objects of images. In addition, Nadav Soferman’s post on smart cropping is a useful start guide.

Strict grid and spanning

The first example on responsive images in this post is a flexible one. At a minimum of 300px width, grid items automagically flow into place according to the viewport width. Terrific.

On the other hand, we might want to apply a stricter rule to the grid items based on the design specifications. In that case, media queries come in handy.

Alternatively, we can leverage the grid-span capability to create grid items of varied widths and lengths:

@media(min-width: 700px) {   main {     display: grid;     grid-template-columns: repeat(2, 1fr);   } }  @media(min-width: 900px) {   main {     display: grid;     grid-template-columns: repeat(3, 1fr)   }   figure:nth-child(3) {     grid-row: span 2;   }   figure:nth-child(4) {     grid-column: span 2;     grid-row: span 2;   } }

For an image that is 1000px x 1000px square on a wide viewport, we can span it to take two grid cells on both row and column. The image that changes to a portrait orientation (700px x 1000px) on a wider viewport can take two cells on a row.

See the Pen
Grid Gallery [Art Direction]
by Chris Nwamba (@codebeast)
on CodePen.

Progressive optimization

Blind optimization is as lame as no optimization. Don’t focus on optimization without predefining the appropriate measurements. And don’t optimize if the optimization is not backed by data.

Nonetheless, ample room exists for optimization in the above examples. We started with the bare minimum, showed you some cool tricks, and now we have a working, responsive grid. The next question to ask is, “If the page contains 20-100 images, how good will the user experience be?”

Here’s the answer: We must ensure that in the case of numerous images for rendering, their size fits the device that renders them. To accomplish that, we need to specify the URLs of several images instead of one. The browser would pick the right (most optimal) one according to the criteria. This technique is called resolution switching in responsive images. See this code example:

<img    srcset="https://res.cloudinary.com/.../h_300,w_300/v1548054527/ps4.jpg 300w,           https://res.cloudinary.com/.../h_700,w_700/v1548054527/ps4.jpg 700w,           https://res.cloudinary.com/.../h_1000,w_1000/v1548054527/ps4.jpg 1000w"                 sizes="(max-width: 700px) 100vw, (max-width: 900px) 50vw, 33vw"   src="https://res.cloudinary.com/.../h_700,w_700/v1548054527/ps4.jpg 700w"   alt="PS4 Slim">

Harry Roberts’s tweet intuitively explains what happens:

When I first tried resolution switching, I got confused and tweeted:

Hats off to Jason Grigsby for the clarification in his replies.

Thanks to resolution switching, if the browser is resized, then it downloads the right image for the right viewport; hence small images for small phones (good on CPU and RAM) and larger images for larger viewports.

The above table shows that the browser downloads the same image (blue rectangle) with different disk sizes (red rectangle).

See the Pen
Grid Gallery [Optimized]
by Chris Nwamba (@codebeast)
on CodePen.

Cloudinary’s open-source and free Responsive Image Breakpoints Generator is extremely useful for adapting website images to multiple screen sizes. However, in many cases, setting srcset and sizes alone would suffice.

Conclusion

This article aims at affording simple yet effective guidelines for setting up responsive images and layouts in light of the many—and potentially confusing—options available. Do familiarize yourself with CSS grid, art direction, and resolution switching and you’ll be a ninja in short order. Keep practicing!

The post Planning for Responsive Images appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

How do you involve design in life without even noticing it?

[Top]

Application Holotypes

It’s entirely too common to make broad-sweeping statements about all websites. Jason Miller:

We often make generalizations about applications we see in the wild, both anecdotal and statistical: “Single-Page Applications are slower than multipage” or “apps with low TTI loaded fast”. However, the extent to which these generalizations hold for the performance and architectural characteristics we care about varies.

Just the other morning, at breakfast an An Event Apart, I sat with a fellow who worked on a university website with a massive amount of pages. Also at the table was someone who worked at a media company with a wide swath of brands, but all largely sites with blog-like content. There was also someone who worked on a developer tool that was heavy on dashboards. We can all care about accessibility, performance, maintainability, etc., but the appropriate technology stacks and delivery processes are quite different both in what we actually do and what we probably should do.

It’s a common stab at solving for these different sites by making two buckets: web sites and web apps. Or dynamic sites and static sites. Or content sites and everything else. Jason builds us more buckets (“holotypes”): (more…)

,
[Top]

Smooth Scrolling for Screencasts

Let’s say you wanted to scroll a web page from top to bottom programmatically. For example, you’re recording a screencast and want a nice full-page scroll. You probably can’t scroll it yourself because it’ll be all uneven and jerky. Native JavaScript can do smooth scrolling. Here’s a tiny snippet that might do the trick for you:

window.scrollTo({   top: document.body.getBoundingClientRect().height,   behavior: 'smooth' });

But there is no way to control the speed or easing of that! It’s likely to be way too fast for a screencast. I found a little trick though, originally published by (I think) Jedidiah Hurt.

The trick is to use CSS transforms instead of actual scrolling. This way, both speed and easing can be controlled. Here’s the code that I cleaned up a little:

const scrollElement = (element, scrollPosition, duration) => {      // useful while testing to re-run it a bunch.   // element.removeAttribute("style");       const style = element.style;   style.transition = duration + 's';   style.transitionTimingFunction = 'ease-in-out';   style.transform = 'translate3d(0, ' + -scrollPosition + 'px, 0)'; }  scrollElement(   document.body,    (     document.body.getBoundingClientRect().height     -     document.documentElement.clientHeight     +     25   ),   5 );

The idea is to transform a negative top position for the height of the entire document, but subtract the height of what you can see so it doesn’t scroll too far. There is a little magic number in there you may need to adjust to get it just right for you.

Here’s a movie I recorded that way:

It’s still not perrrrrrfectly smooth. I partially blame the FPS of the video, but even with my eyeballs watching it record it wasn’t total butter. If I needed even higher quality, I’d probably restart my computer and have this page open as the only tab and application open, lolz.

See a Demo

Another possibility is a little good ol’ fashioned jQuery .animate(), which can be extended with some custom easing. Here’s a demo of that.

See the Pen
jQuery Smooth Scrolling with Easing
by Chris Coyier (@chriscoyier)
on CodePen.

The post Smooth Scrolling for Screencasts appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Stackbit

This is not a sponsored post. I requested a beta access for this site called Stackbit a while back, got my invite the other day, and thought it was a darn fine idea that’s relevant to us web nerds — particularly those of us who spin up a lot of JAMstack sites.

I’m a big fan of the whole idea of JAMstack sites. Take our new front-end development conferences website as one little example. That site is a custom theme built with 11ty, version controlled on GitHub, hosted on Netlify, and content-managed with Netlify CMS.

Each JAMstack site is a little selection of services (⬅ I’m rebuilding that site to be even more JAMstacky!). I think it’s clever that Stackbit helps make those choices quickly.

Pick a theme, a site generator, a CMS, a repository platform, and a deployment service… and go! Like this:

Clever!

Direct Link to ArticlePermalink

The post Stackbit appeared first on CSS-Tricks.

CSS-Tricks

[Top]

Getting into GraphQL with AWS AppSync

GraphQL is becoming increasingly popular. The problem is that if you are a front-end developer, you are only half of the way there. GraphQL is not just a client technology. The server also has to be implemented according to the specification. This means that in order to implement GraphQL into your application, you need to learn not only GraphQL on the front end, but also GraphQL best practices, server-side development, and everything that goes along with it on the back end.

There will come a time when you will also have to deal with issues like scaling your server, complex authorization scenarios, malicious queries, and more issues that require more expertise and even deeper knowledge around what is traditionally categorized as back-end development.

Thankfully, we have an array of managed back-end service providers today that allow front-end developers to only worry about implementing features on the front end without having to deal with all of the traditional back-end work.

Services like Firebase (API) / AWS AppSync (database), Cloudinary (media), Algolia (search) and Auth0 (authentication) allow us to offload our complex infrastructure to a third-party provider and instead focus on delivering value to end users in the form of new features instead.

In this tutorial, we’ll learn how to take advantage of AWS AppSync, a managed GraphQL service, to build a full-stack application without writing a single line of back-end code.

While the framework we’re working in is React, the concepts and API calls we will be using are framework-agnostic and will work the same in Angular, Vue, React Native, Ionic or any other JavaScript framework or application.

We will be building a restaurant review app. In this app, we will be able to create a restaurant, view restaurants, create a review for a restaurant, and view reviews for a restaurant.

An image showing four different screenshots of the restaurant app in mobile view.

The tools and frameworks that we will be using are React, AWS Amplify, and AWS AppSync.

AWS Amplify is a framework that allows us to create and connect to cloud services, like authentication, GraphQL APIs, and Lambda functions, among other things. AWS AppSync is a managed GraphQL service.

We’ll use Amplify to create and connect to an AppSync API, then write the client side React code to interact with the API.

View Repo

Getting started

The first thing we’ll do is create a React project and move into the new directory:

npx create-react-app ReactRestaurants  cd ReactRestaurants

Next, we’ll install the dependencies we’ll be using for this project. AWS Amplify is the JavaScript library we’ll be using to connect to the API, and we’ll use Glamor for styling.

yarn add aws-amplify glamor

The next thing we need to do to is install and configure the Amplify CLI:

npm install -g @aws-amplify/cli  amplify configure

Amplify’s configure will walk you through the steps needed to begin creating AWS services in your account. For a walkthrough of how to do this, check out this video.

Now that the app has been created and Amplify is ready to go, we can initialize a new Amplify project.

amplify init

Amplify init will walk you through the steps to initialize a new Amplify project. It will prompt you for your desired project name, environment name, and text editor of choice. The CLI will auto-detect your React environment and select smart defaults for the rest of the options.

Creating the GraphQL API

One we’ve initialized a new Amplify project, we can now add the Restaurant Review GraphQL API. To add a new service, we can run the amplify add command.

amplify add api

This will walk us through the following steps to help us set up the API:

? Please select from one of the below mentioned services GraphQL ? Provide API name bigeats ? Choose an authorization type for the API API key ? Do you have an annotated GraphQL schema? N ? Do you want a guided schema creation? Y ? What best describes your project: Single object with fields ? Do you want to edit the schema now? Y

The CLI should now open a basic schema in the text editor. This is going to be the schema for our GraphQL API.

Paste the following schema and save it.

// amplify/backend/api/bigeats/schema.graphql  type Restaurant @model {   id: ID!   city: String!   name: String!   numRatings: Int   photo: String!   reviews: [Review] @connection(name: "RestaurantReview") } type Review @model {   rating: Int!   text: String!   createdAt: String   restaurant: Restaurant! @connection(name: "RestaurantReview") }

In this schema, we’re creating two main types: Restaurant and Review. Notice that we have @model and @connection directives in our schema.

These directives are part of the GraphQL Transform tool built into the Amplify CLI. GraphQL Transform will take a base schema decorated with directives and transform our code into a fully functional API that implements the base data model.

If we were spinning up our own GraphQL API, then we’d have to do all of this manually:

  1. Define the schema
  2. Define the operations against the schema (queries, mutations, and subscriptions)
  3. Create the data sources
  4. Write resolvers that map between the schema operations and the data sources.

With the @model directive, the GraphQL Transform tool will scaffold out all schema operations, resolvers, and data sources so all we have to do is define the base schema (step 1). The @connection directive will let us model relationships between the models and scaffold out the appropriate resolvers for the relationships.

In our schema, we use @connection to define a relationship between Restaurant and Reviews. This will create a unique identifier for the restaurant ID for the review in the final generated schema.

Now that we’ve created our base schema, we can create the API in our account.

amplify push
? Are you sure you want to continue? Yes ? Do you want to generate code for your newly created GraphQL API Yes ? Choose the code generation language target javascript ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes

Because we’re creating a GraphQL application, we typically would need to write all of our local GraphQL queries, mutations and subscriptions from scratch. Instead, the CLI will be inspecting our GraphQL schema and then generating all of the definitions for us and saving them locally for us to use.

After this is complete, the back end has been created and we can begin accessing it from our React application.

If you’d like to view your AppSync API in the AWS dashboard, visit https://console.aws.amazon.com/appsync and click on your API. From the dashboard you can view the schema, data sources, and resolvers. You can also perform queries and mutations using the built-in GraphQL editor.

Building the React client

Now that the API is created and we can begin querying for and creating data in our API. There will be three operations we will be using to interact with our API:

  1. Creating a new restaurant
  2. Querying for restaurants and their reviews
  3. Creating a review for a restaurant

Before we start building the app, let’s take a look at how these operations will look and work.

Interacting with the AppSync GraphQL API

When working with a GraphQL API, there are many GraphQL clients available.

We can use any GraphQL client we’d would like to interact with an AppSync GraphQL API, but there are two that are configured specifically to work most easily. These are the Amplify client (what we will use) and the AWS AppSync JS SDK (similar API to Apollo client).

The Amplify client is similar to the fetch API in that it is promise-based and easy to reason about. The Amplify client does not support offline out of the box. The AppSync SDK is more complex but does support offline out of the box.

To call the AppSync API with Amplify, we use the API category. Here’s an example of how to call a query:

import { API, graphqlOperation } from 'aws-amplify' import * as queries from './graphql/queries'  const data = await API.graphql(graphqlOperation(queries.listRestaurants))

For a mutation, it is very similar. The only difference is we need to pass in a a second argument for the data we are sending in the mutation:

import { API, graphqlOperation } from 'aws-amplify' import * as mutations from './graphql/mutations'  const restaurant = { name: "Babalu", city: "Jackson" } const data = await API.graphql(graphqlOperation(   mutations.createRestaurant,   { input: restaurant } ))

We use the graphql method from the API category to call the operation, wrapping it in graphqlOperation, which parses GraphQL query strings into the standard GraphQL AST.

We’ll be using this API category for all of our GraphQL operation in the app.

Here is the repo containing the final code for this project.

Configuring the React app with Amplify

The first thing we need to do in our app is configure it to recognize our Amplify credentials. When we created our API, the CLI created a new file called aws-exports.js in our src folder.

This file is created and updated for us by the CLI as we create, update and delete services. This file is what we’ll be using to configure the React application to know about our services.

To configure the app, open up src/index.js and add the following code:

import Amplify from 'aws-amplify' import config from './aws-exports' Amplify.configure(config)

Next, we will create the files we will need for our components. In the src directory, create the following files:

  • Header.js
  • Restaurant.js
  • Review.js
  • CreateRestaurant.js
  • CreateReview.js

Creating the components

While the styles are referenced in the code snippets below, the style definitions have been omitted to make the snippets less verbose. For style definitions, see the final project repo.

Next, we’ll create the Header component by updating src/Header.js.

// src/Header.js  import React from 'react' import { css } from 'glamor' const Header = ({ showCreateRestaurant }) => (   <div {...css(styles.header)}>     <p {...css(styles.title)}>BigEats</p>     <div {...css(styles.iconContainer)}>       <p {...css(styles.icon)} onClick={showCreateRestaurant}>+</p>     </div>   </div> )  export default Header

Now that our Header is created, we’ll update src/App.js. This file will hold all of the interactions with the API, so it is pretty large. We’ll define the methods and pass them down as props to the components that will call them.

// src/App.js  import React, { Component } from 'react' import { API, graphqlOperation } from 'aws-amplify'  import Header from './Header' import Restaurants from './Restaurants' import CreateRestaurant from './CreateRestaurant' import CreateReview from './CreateReview' import Reviews from './Reviews' import * as queries from './graphql/queries' import * as mutations from './graphql/mutations'  class App extends Component {   state = {     restaurants: [],     selectedRestaurant: {},     showCreateRestaurant: false,     showCreateReview: false,     showReviews: false   }   async componentDidMount() {     try {       const rdata = await API.graphql(graphqlOperation(queries.listRestaurants))       const { data: { listRestaurants: { items }}} = rdata       this.setState({ restaurants: items })     } catch(err) {       console.log('error: ', err)     }   }   viewReviews = (r) => {     this.setState({ showReviews: true, selectedRestaurant: r })   }   createRestaurant = async(restaurant) => {     this.setState({       restaurants: [...this.state.restaurants, restaurant]     })     try {       await API.graphql(graphqlOperation(         mutations.createRestaurant,         {input: restaurant}       ))     } catch(err) {       console.log('error creating restaurant: ', err)     }   }   createReview = async(id, input) => {     const restaurants = this.state.restaurants     const index = restaurants.findIndex(r => r.id === id)     restaurants[index].reviews.items.push(input)     this.setState({ restaurants })     await API.graphql(graphqlOperation(mutations.createReview, {input}))   }   closeModal = () => {     this.setState({       showCreateRestaurant: false,       showCreateReview: false,       showReviews: false,       selectedRestaurant: {}     })   }   showCreateRestaurant = () => {     this.setState({ showCreateRestaurant: true })   }   showCreateReview = r => {     this.setState({ selectedRestaurant: r, showCreateReview: true })   }   render() {     return (       <div>         <Header showCreateRestaurant={this.showCreateRestaurant} />         <Restaurants           restaurants={this.state.restaurants}           showCreateReview={this.showCreateReview}           viewReviews={this.viewReviews}         />         {           this.state.showCreateRestaurant && (             <CreateRestaurant               createRestaurant={this.createRestaurant}               closeModal={this.closeModal}                />           )         }         {           this.state.showCreateReview && (             <CreateReview               createReview={this.createReview}               closeModal={this.closeModal}                  restaurant={this.state.selectedRestaurant}             />           )         }         {           this.state.showReviews && (             <Reviews               selectedRestaurant={this.state.selectedRestaurant}               closeModal={this.closeModal}               restaurant={this.state.selectedRestaurant}             />           )         }       </div>     );   } } export default App

We first create some initial state to hold the restaurants array that we will be fetching from our API. We also create Booleans to control our UI and a selectedRestaurant object.

In componentDidMount, we query for the restaurants and update the state to hold the restaurants retrieved from the API.

In createRestaurant and createReview, we send mutations to the API. Also notice that we provide an optimistic update by updating the state immediately so that the UI gets updated before the response comes back in order to make our UI snappy.

Next, we’ll create the Restaurants component (src/Restaurants.js).

// src/Restaurants.js  import React, { Component } from 'react'; import { css } from 'glamor'  class Restaurants extends Component {   render() {     const { restaurants, viewReviews } = this.props     return (       <div {...css(styles.container)}>         {           restaurants.length === Number(0) && (             <h1               {...css(styles.h1)}             >Create your first restaurant by clicking +</h1>           )         }         {           restaurants.map((r, i) => (             <div key={i}>               <img                 src={r.photo}                 {...css(styles.image)}               />               <p {...css(styles.title)}>{r.name}</p>               <p {...css(styles.subtitle)}>{r.city}</p>               <p                 onClick={() => viewReviews(r)}                 {...css(styles.viewReviews)}               >View Reviews</p>               <p                 onClick={() => this.props.showCreateReview(r)}                 {...css(styles.createReview)}               >Create Review</p>             </div>           ))         }       </div>     );   } }  export default Restaurants

This component is the main view of the app. We map over the list of restaurants and show the restaurant image, its name and location, and links that will open overlays to show reviews and create a new review.

Next, we’ll look at the Reviews component (src/Reviews.js). In this component, we map over the list of reviews for the chosen restaurant.

// src/Reviews.js  import React from 'react' import { css } from 'glamor'  class Reviews extends React.Component {   render() {     const { closeModal, restaurant } = this.props     return (       <div {...css(styles.overlay)}>         <div {...css(styles.container)}>           <h1>{restaurant.name}</h1>           {             restaurant.reviews.items.map((r, i) => (               <div {...css(styles.review)} key={i}>                 <p {...css(styles.text)}>{r.text}</p>                 <p {...css(styles.rating)}>Stars: {r.rating}</p>               </div>             ))           }           <p onClick={closeModal}>Close</p>         </div>       </div>     )   } }  export default Reviews

Next, we’ll take a look at the CreateRestaurant component (src/CreateRestaurant.js). This component holds a form that keeps up with the form state. The createRestaurant class method will call this.props.createRestaurant, passing in the form state.

// src/CreateRestaurant.js  import React from 'react' import { css } from 'glamor';  class CreateRestaurant extends React.Component {   state = {     name: '', city: '', photo: ''   }   createRestaurant = () => {     if (       this.state.city === '' || this.state.name === '' || this.state.photo === ''     ) return     this.props.createRestaurant(this.state)     this.props.closeModal()   }   onChange = ({ target }) => {     this.setState({ [target.name]: target.value })   }   render() {     const { closeModal } = this.props     return (       <div {...css(styles.overlay)}>         <div {...css(styles.form)}>           <input             placeholder='Restaurant name'             {...css(styles.input)}             name='name'             onChange={this.onChange}           />           <input             placeholder='City'             {...css(styles.input)}             name='city'             onChange={this.onChange}           />           <input             placeholder='Photo'             {...css(styles.input)}             name='photo'             onChange={this.onChange}           />           <div             onClick={this.createRestaurant}             {...css(styles.button)}           >             <p               {...css(styles.buttonText)}             >Submit</p>           </div>           <div             {...css([styles.button, { backgroundColor: '#555'}])}             onClick={closeModal}           >             <p               {...css(styles.buttonText)}             >Cancel</p>           </div>         </div>       </div>     )   } }  export default CreateRestaurant

Next, we’ll take a look at the CreateReview component (src/CreateReview.js). This component holds a form that keeps up with the form state. The createReview class method will call this.props.createReview, passing in the restaurant ID and the form state.

// src/CreateReview.js  import React from 'react' import { css } from 'glamor'; const stars = [1, 2, 3, 4, 5] class CreateReview extends React.Component {   state = {     review: '', selectedIndex: null   }   onChange = ({ target }) => {     this.setState({ [target.name]: target.value })   }   createReview = async() => {     const { restaurant } = this.props     const input = {       text: this.state.review,       rating: this.state.selectedIndex + 1,       reviewRestaurantId: restaurant.id     }     try {       this.props.createReview(restaurant.id, input)       this.props.closeModal()     } catch(err) {       console.log('error creating restaurant: ', err)     }   }   render() {     const { selectedIndex } = this.state     const { closeModal } = this.props     return (       <div {...css(styles.overlay)}>         <div {...css(styles.form)}>           <div {...css(styles.stars)}>             {               stars.map((s, i) => (                 <p                   key={i}                   onClick={() => this.setState({ selectedIndex: i })}                   {...css([styles.star, selectedIndex === i && { backgroundColor: 'gold' }])}                 >{s} star</p>               ))             }           </div>           <input             placeholder='Review'             {...css(styles.input)}             name='review'             onChange={this.onChange}           />           <div             onClick={this.createReview}             {...css(styles.button)}           >             <p               {...css(styles.buttonText)}             >Submit</p>           </div>           <div             {...css([styles.button, { backgroundColor: '#555'}])}             onClick={closeModal}           >             <p               {...css(styles.buttonText)}             >Cancel</p>           </div>         </div>       </div>     )   } }  export default CreateReview

Running the app

Now that we have built our back-end, configured the app and created our components, we’re ready to test it out:

npm start

Now, navigate to http://localhost:3000. Congratulations, you’ve just built a full-stack serverless GraphQL application!

Conclusion

The next logical step for many applications is to apply additional security features, like authentication, authorization and fine-grained access control. All of these things are baked into the service. To learn more about AWS AppSync security, check out the documentation.

If you’d like to add hosting and a Continuous Integration/Continuous Deployment pipeline for your app, check out the Amplify Console.

I also maintain a couple of repositories with additional resources around Amplify and AppSync: Awesome AWS Amplify and Awesome AWS AppSync.

If you’d like to learn more about this philosophy of building apps using managed services, check out my post titled “Full-stack Development in the Era of Serverless Computing.”

The post Getting into GraphQL with AWS AppSync appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]

Design Deals for the Week

[Top]

Get Started with Node: An Introduction to APIs, HTTP and ES6+ JavaScript

Jamie Corkhill has written this wonderful post about Node and I think it’s perhaps one of the best technical articles I’ve ever read. Not only is it jam-packed with information for folks like me who aren’t writing JavaScript everyday, it is also incredibly deliberate as Jamie slowly walks through the very basics of JavaScript (such as synchronous and asynchronous functions) all the way up to working with our very own API.

Jamie writes:

What is Node in the first place? What exactly does it mean for Node to be “asynchronous”, and how does that differ from “synchronous”? What is the meaning “event-driven” and “non-blocking” anyway, and how does Node fit into the bigger picture of applications, Internet networks, and servers?

We’ll attempt to answer all of these questions and more throughout this series as we take an in-depth look at the inner workings of Node, learn about the HyperText Transfer Protocol, APIs, and JSON, and build our very own Bookshelf API utilizing MongoDB, Express, Lodash, Mocha, and Handlebars.

I would highly recommend this post if JavaScript isn’t your day job but you’ve always wanted to learn about Node in a bit more detail.

Direct Link to ArticlePermalink

The post Get Started with Node: An Introduction to APIs, HTTP and ES6+ JavaScript appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]