Jump to Navigation

Feed aggregator

How A Screen Reader User Accesses The Web: A Smashing Video

Smashing Magazine - 1 hour 16 min ago
How A Screen Reader User Accesses The Web: A Smashing Video How A Screen Reader User Accesses The Web: A Smashing Video Bruce Lawson 2019-02-18T14:00:32+01:00 2019-02-18T13:24:09+00:00

Two weeks ago, I had the pleasure of hosting a Smashing TV webinar with Léonie Watson on how a screen reader user accesses the web. In the talk, Léonie showed some big-name sites, such as BBC, sites nominated by Members (including my own!), Smashing Magazine itself, and the popular third-party service Typeform, because so many of us (including us at Smashing) just assume that the popular services have been checked for accessibility. Throughout, Léonie explained how the sites’ HTML was helping (or hindering) her use of the sites.

We felt that the webinar was so valuable that we would open it up so that it’s free for everybody to use. Hopefully, it will serve as a resource for the whole web development community to understand how — and why — semantic markup matters.

What We Learned

I was pleased that my personal site’s use of HTML5 landmark regions (main, nav, header, footer, etc) helped Léonie form a mental model of the structure of the page. Although I’ve always been scrupulous to avoid link text like “click here” because WCAG guidelines require “The purpose of each link can be determined from the link text alone”, it hadn’t occurred to me before that because I have hundreds of weekly “Reading List” articles, it’s impossible for a screen reader user to tell one from the other when navigating by headings. Since the webinar, I’ve made each new reading list’s heading unique by including its number in the heading (“Reading List 222”).

We also learned that being technically accessible is good, but even better is to be usably accessible. The Smashing Team learned that before Léonie can read her own article on our site, there’s loads of preamble (author bio, email sign-up form) that she can’t easily skip over. We’re correcting this at the moment. There’s also an issue with our quick summaries; Léonie gets no indication when the summary has finished and the article proper has begun. Sighted users get a dividing line, but what can we do for non-sighted users?

After the webinar, Léonie suggested using a semantic HTML element and a sprinkling of ARIA:

<section aria-label="Summary"> </section>

This is announced as “Summary region start” and “Summary region end”, and can be skipped over if desired.

Thank You!

We’d like to thank Léonie for giving the webinar, and also our magnificant Smashing Magazine members whose support allows us to commission such content, pay our contributors fairly, and reduce advertising on the site.

Shameless plug: if you enjoyed this webinar, why not consider becoming a Member yourself? There are around three webinars a month free, Smashing eBooks and discounts galore. It begins at $5 a month, and you can cancel anytime.

(ra, il)
Categories: Around The Web

Monthly Web Development Update 2/2019: Web Authentication And The Problem With UX

Smashing Magazine - Fri, 02/15/2019 - 5:42am
Monthly Web Development Update 2/2019: Web Authentication And The Problem With UX Monthly Web Development Update 2/2019: Web Authentication And The Problem With UX Anselm Hannemann 2019-02-15T11:42:15+01:00 2019-02-18T13:24:09+00:00

The only constant in life is change, they say. And it’s true, even if we think nothing changes at all. Whether you notice change or not is only a question of how you perceive and how you observe things. In the tech industry, it’s easy to see how fast things evolve — read a summary article like this one, and you’ll suddenly become aware of how much has happened in just one month. Since I took up meditation again, I gained a new perspective, and it helps me to deliberately appreciate such change and find personal value and gratefulness even in things that didn’t seem particularly positive at first.

Like this week, for example. I was reminded of a fact we usually forget: how the Internet is structured. If you browse the web, most traffic is directed through Amazon at some point, so if you block their servers, — or Google’s or Apple’s, or all of them —, there’s not much left of the Internet. I have used a Pi-Hole DNS blocker in my network for three years now, but never really appreciated it, until I learned about its real value this week — the security and privacy it provides considering our dependency on tech giants. Isn’t it remarkable how a big part of my perceived online security relies on one piece of open-source software that the authors spent so much time and efforts on to provide it for free in the end?

News
  • Firefox 65 was released. The new version dispatches events on disabled HTML elements and comes with support for the referrerpolicy attribute on script elements, CSS environment variables (the env() function), Intl.RelativeTimeFormat for JavaScript, and WebP images.
  • Safari Tech Preview 74 brings abortable fetch, support for U2F HID Authenticators on macOS, and new Web Authentication API features.
  • With Chrome 72, Chrome introduced the User Activation API. The new version also disallows popups on pageunload.
  • The Chrome 72 update for Android shipped the long-awaited Trusted Web Activity feature, which means we can now distribute PWAs in the Google Play Store.
  • Safari 12.1 release notes are up (iOS 12.2, macOS 10.14.4). What’s new? Dark mode for the web, intelligent tracking prevention, the push notification prompt for Safari on macOS now requires a user gesture, motion and orientation settings on iOS to enable DeviceMotionEvent and DeviceOrientationEvent (this means it’s disabled by default now). Also new are the Intersection Observer API, Web Share API, and the <datalist> element.

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬ General
  • Max Böck shares his thoughts on why simplicity is the most valuable and important thing in projects.
  • Ian Littman on Twitter: “Moving 50% of servers to PHP 7 from PHP 5 would save $2.5 (edited to 2.0) billion in energy costs per year, and avoid billions of kilograms of CO2 emissions. Upgrade to PHP 7. Save the planet.”
  • How did you start to learn web development? I guess most of us relied on our browsers’ “view source” functionality and still do. But with JavaScript SPAs and more tooling that mangles, minifies and uglifies sources, we block this road of self-education for countless people out there. Let’s move to a more open approach and at least provide source maps on production servers so that people can access the actual sources via Developer Tools.
UI/UX To create stellar user experiences we need to see our users as humans. (Image credit) HTML & SVG
  • Sara Soueidan wrote a 101 course on SVG filters to help you understand what they are and how to use them to create your own visual effects.
Accessibility Privacy
  • Google is one of those companies which always find new, clever ways to expose user location data and sell it to third parties. Now Google wants to sell the exact location data of users to improve planning for urban planners, for example. Useful on the one hand, but still worrying for all users of Google products who might not be aware of what happens with their data.
  • I was wrong about Google and Facebook: there’s nothing wrong with them (so say we all),” says Aral Balkan. This piece explains how even the most honorable open-source projects struggle to make ethical choices and the fallacies of offering the best UX instead of promoting ethically correct solutions.
Web Performance
  • Jens Oliver Meiert shares his research on how the way you write HTML influences performance. Leaving out optional tags and quotes can make a difference, even though we’re able to use gzip or other techniques to optimize the document response in the browser.
JavaScript The Guide to Web Authentication is a handy introduction to securing sensitive information online. (Image credit) CSS Explore the solar system in Fabricius Seifert’s fantastic CSS experiment. (Image credit) Work & Life
  • Paul Greenberg is in search of lost screen time and explores what our lives could look like and how much more time we’d have if we escaped the screens. There are some revealing numbers in the article: The average American spends $14,000 per decade on smartphones. That’s $70,000 over the course of an average working life. More than 29% of Americans would rather give up sex for three months than give up their smartphone for a single week. Or you could plant 150 trees and buy half an acre of land for the amount of money you’d spent on your smartphone and apps per year.
  • Are you a patient person? Regardless of if you are or not, the experiment that Jason Fried wants to try is certainly a challenge: Try to pick the longest line at the supermarket, cancel Amazon Prime so that delivery takes longer, and take the chance to wait whenever possible. Embrace slowness.
  • In Praise of Extreme Moderation” shares an interesting perspective on why the culture of over-committing, over-working, and over-delivering in all areas of life isn’t healthy, and how we can shift towards a more moderate, calmer path.
Going Beyond…
  • It must be free.” On services we obviously don’t need but want to have. My essay about the importance of seeing value in the things we really need and why less is more.
  • How can we make our lives better? By maintaining essential relationships, avoiding technology, and embracing values instead of lifehacks, says Eric Barker.
  • Watch this talk of Greta Thunberg, a sixteen-year-old woman who tells all the well-known and influential people out there that she doesn’t care about money and why we need to view climate change from a perspective like hers — her life is in danger and no money will be able to save it. We need more people like her who aren’t led by corporate or financial rules.
(cm)
Categories: Around The Web

Managing Image Breakpoints With Angular

Smashing Magazine - Thu, 02/14/2019 - 7:00am
Managing Image Breakpoints With Angular Managing Image Breakpoints With Angular Tamas Piros 2019-02-14T13:00:08+01:00 2019-02-18T13:24:09+00:00

As web developers, we are often required to create applications that are responsive as well as media-rich. Having such requirements in place means that we need to work with image breakpoints, as well as media queries since we want to provide the best experience to the end users. Adding to the list of requirements we may need to use a front-end framework such as Angular which is great for creating SPAs and other application types.

In this article, we’ll take a look at image breakpoints, their use-cases and throughout a hands-on example; we’ll implement them in an Angular application using Angular’s own BreakPoint Observer. While using this approach, we’ll also highlight why this popular framework helps us work with the aforementioned techniques in a seamless way.

Image Breakpoints And Responsive Images

In the era of responsive layouts (where we capture breakpoints based on the viewport size and based on the breakpoint we change the layout of the page), we also need to make sure that images can be displayed with the right dimensions — even after a layout change. Selecting the right image is quite challenging for modern responsive websites.

Let’s discuss two options that developers can utilize at the moment.

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬ srcset

srcset lets us define a list of images that the browser switches between based on the rendered <img> size and the density of the display.

Let’s take a look at an example:

<img srcset="tuscany-sm.jpg 600w, tuscany-md.jpg 900w, tuscany-lg.jpg 1440w" sizes="100vw" src="tuscany.jpg" />

In the above, we specify 3 images, with the w indicating the pixel width for the image. When using the above with srcset we also need to specify the sizes attribute (this is required because the spec mandates that if we use srcset and w we must have a sizes attribute as well). What is the purpose of this attribute? Browsers need to pick which resource to load out of a source set before they layout the page (before they know how big the image will end up being). We can think of sizes as a hint to the browser that, after layout, the image will occupy 100% of the width of the viewport (that’s what vw refers to). The browser knows the actual viewport width (as well as the DPR of the image) at load-time, so it can do the math to figure out what size resource it needs and pick one out of the source set.

The <picture> and <source media=""> element combinations let us switch out image resources in response to media queries, like the ones at layout breakpoints.

Let’s take a look at an example of this as well:

<picture> <source media="(min-width: 1440px)" srcset="../assets/images/tuscany-lg.jpg"> <source media="(min-width: 900px)" srcset="../assets/images/tuscany-md.jpg"> <source media="(min-width: 600px)" srcset="../assets/images/tuscany-sm.jpg"> <img src="../assets/images/tuscany-sm.jpg" /> </picture>

Change the code above locally with an image of your choice that has a small, medium and large size. Notice how, by resizing the browser, you get a different image.

The key takeaway from all the above is that if we want to swap out images at specific breakpoints, we can use the <picture> element to put media queries right into the markup.

Note: If you’re interested in exploring the differences between <picture> and srcset + sizes, I recommend reading Eric Portis’ great article: srcset and sizes.

So far we have discussed how to use image breakpoints along with media queries in a pure HTML environment. Wouldn’t it be a lot better to have a convenient, almost semi-automated way of generating image breakpoints as well as the corresponding images for the breakpoints even without having to specify media queries at all? Luckily for us Angular has a built-in mechanism to help us out and we’ll also take a look at generating the appropriate images dynamically based on certain conditions by using a third-party service.

Angular Layout Module

Angular comes with a Layout Module which lives in the CDK (Component Dev Kit) toolset. The Angular CDK contains well-tested tools to aid with component development. One part of the CDK is the Layout Module which contains a BreakpointObserver. This helper gives access to media-query breakpoints, meaning that components (and their contents) can adapt to changes when the browser size (screen size) is changed intuitively.

Recommended reading: Layout Module

Now that we have the theory out of the way let’s get down to business and create an application that will implement responsive image breakpoints. In this first iteration, we’ll create the shell of the application via the Angular CLI: ng new bpo and select the necessary options.

To use the BreakpointObserver we also need to install the Angular’s CDK Layout Module, which we can do via npm: npm i @angular/cdk.

After the installation, we will be able to add the necessary import statements to any component that we wish:

// app.component.ts import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';

Using the BreakpointObserver we can subscribe to changes in the viewport width and Angular gives us convenient accessors which mean that we don’t need to use media queries at all! Let’s go ahead and try this out:

// app.component.ts constructor(public breakpointObserver: BreakpointObserver) { } ngOnInit() { this.breakpointObserver.observe([ Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge ]).subscribe(result => { if (result.breakpoints[Breakpoints.XSmall]) { // handle XSmall breakpoint } if (result.breakpoints[Breakpoints.Small]) { // handle Small breakpoint } if (result.breakpoints[Breakpoints.Medium]) { // handle Medium breakpoint } if (result.breakpoints[Breakpoints.Large]) { // handle Large breakpoint } if (result.breakpoints[Breakpoints.XLarge]) { // handle XLarge breakpoint } }); }

As mentioned before the accessor properties above reflect media queries in the following way:

  • Breakpoints.XSmall: max-width = 599.99px
  • Breakpoints.Small: min-width = 600px and max-width = 959.99px
  • Breakpoints.Medium: min-width = 960px and max-width = 1279.99px
  • Breakpoints.Large: min-width = 1280px and max-width = 1919.99px
  • Breakpoints.XLarge: min-width = 1920px

We now have everything in place which means, we can start to generate the appropriate images.

Responsive Breakpoints For Images

We have a few options to generate responsive images:

  1. Responsive Image Breakpoints Generator
    Using this tool, we can upload any image, setup various options, e.g. the number of images that we wish to generate. After running the tool, we’ll have a visual representation about the generated images, and we can download them as a zip file along with some generated code which uses the previously mentioned <picture> element.
  2. Another solution would be to create a build step for our project to generate breakpoints via some packages available in the NPM repository, such as gulp-responsive or grunt-responsive-images. Both of these depend on additional libraries that we are required to install for our operating system. (Please check the appropriate repositories for additional information.)
  3. Yet another solution would be to use a service such as Cloudinary to store the images and serve them in a size and format that we need only by modifying the URL for the requested resource. This will be our approach since this gives us the most flexibility.

Recommended reading: Automating Art Direction With The Responsive Image Breakpoints Generator by Eric Portis

I have uploaded the original image to my Cloudinary account which means that I can access that image via the following URL:

https://res.cloudinary.com/tamas-demo/image/upload/breakpoints-article/tuscany.jpg

This is the full-sized, raw, original and unchanged image that we’ll work with.

We can modify the URL of the image to generate a much smaller version. For example, if we want to have an image with a width of 600 pixels, we could update the Cloudinary URL* to be the following:

https://res.cloudinary.com/tamas-demo/image/upload/w_600/breakpoints-article/tuscany.jpg

* Note the w_600 added to the URL.

Hopefully, by this point, you see where all this is going. Based on the approach above, we can very quickly start to generate the right image for the right breakpoint.

Using Cloudinary means that we don’t need to create, store and manage multiple version of the same image — it is done for us by Cloudinary on-the-fly.

Let’s update our code:

<!-- app.component.html --> <div> <h1>Current breakpoint: {{ breakpoint }}</h1> <img [src]="imagePath"> </div> // app.component.ts import { Component, OnInit } from '@angular/core'; // ... export class AppComponent implements OnInit { imagePath; constructor(public breakpointObserver: BreakpointObserver) { } ngOnInit() { this.breakpointObserver.observe([ ... } }

We can pick any number of breakpoints to observe from the list mentioned previously, and since we have an Observer we can subscribe to the changes and act on them:

this.breakpointObserver.observe([ Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge ]).subscribe(result => { if (result.breakpoints[Breakpoints.XSmall]) { // handle this case } });

To handle the options for the different images in Cloudinary, we’ll utilize an approach that will be very easy to follow. For each case, we’ll create an options variable and update the final Cloudinary URL.

Add the following at the top of the component definition:

// app.component.ts imagePath; breakpoint; cloudinaryOptions; baseURL = 'https://res.cloudinary.com/tamas-demo/image/upload/breakpoints-article/tuscany.jpg';

And add the following as well to the first if statement:

// app.component.ts let url = this.baseURL.split('/'); let insertIndex = url.indexOf('upload'); const options = 'c_thumb,g_auto,f_auto,q_auto,w_400'; url.splice(insertIndex + 1, 0, options); this.imagePath = url.join('/'); this.breakpoint = Breakpoints.XSmall;

The result is going to be an updated Cloudinary URL:

https://res.cloudinary.com/tamas-demo/image/upload/c_thumb,g_auto,f_auto,q_auto,w_400/breakpoints-article/tuscany.jpg

What are the options that we are setting here?

  • c_thumb (generates a thumbnail of the image);
  • g_auto (focuses on the most interesting part; we see the cathedral in the thumbnail);
  • f_auto (serves the most appropriate format for a given browser, i.e. WebP for Chrome);
  • q_auto (reduces the quality — and therefore the overall size — of the image without impacting the visuals);
  • w_400 (sets the width of the image to 400px).

For the sake of curiosity, let’s compare the original image size with this newly generated image: 2.28 MBs vs 29.08 KBs!

We now have a straightforward job: we need to create different options for different breakpoints. I created a sample application on StackBlitz so you can test it out immediately (you can also see a preview here).

Conclusion

The variety of desktop and mobile devices and the amount of media used in today’s web has reached an outstanding number. As web developers, we must be at the forefront of creating web applications that work on any device and doesn’t impact the visual experience.

There are a good number of methods that make sure the right image is loaded to the right device (or even when resizing a device). In this article, we reviewed an approach that utilizes a built-in Angular feature called BreakPoint Observer which gives us a powerful interface for dealing with responsive images. Furthermore, we also had a look at a service that allows us to serve, transform and manage images in the cloud. Having such compelling tools at our hands, we can still create immersive visual web experiences, without losing visitors.

(dm, il)
Categories: Around The Web

An Introduction To WebBluetooth

Smashing Magazine - Wed, 02/13/2019 - 7:00am
An Introduction To WebBluetooth An Introduction To WebBluetooth Niels Leenheer 2019-02-13T13:00:51+01:00 2019-02-18T13:24:09+00:00

With Progressive Web Apps, the web has been ever more closely moving towards native apps. However, with the added benefits that are inherent to the web such as privacy and cross-platform compatibility.

The web has traditionally been fantastic about talking to servers on the network, and to servers on the Internet specifically. Now that the web is moving towards applications, we also need the same capabilities that native apps have.

The amount of new specifications and features that have been implemented in the last few years in browsers is staggering. We’ve got specifications for dealing with 3D such as WebGL and the upcoming WebGPU. We can stream and generate audio, watch videos and use the webcam as an input device. We can also run code at almost native speeds using WebAssembly. Moreover, despite initially being a network-only medium, the web has moved towards offline support with service workers.

That is great and all, but one area has been almost the exclusive domain for native apps: communicating with devices. That is a problem we’ve been trying to solve for a long time, and it is something that everybody has probably encountered at one point. The web is excellent for talking to servers, but not for talking to devices. Think about, for example, trying to set up a router in your network. Chances are you had to enter an IP address and use a web interface over a plain HTTP connection without any security whatsoever. That is just a poor experience and bad security. On top of that, how do you know what the right IP address is?

Our new book, in which Alla Kholmatova explores how to create effective and maintainable design systems to design great digital products. Meet Design Systems, with common traps, gotchas and the lessons Alla has learned over the years.

Table of Contents →

HTTP is also the first problem we run into when we try to create a Progressive Web App that tries to talk to a device. PWAs are HTTPS only, and local devices are always just HTTP. You need a certificate for HTTPS, and in order to get a certificate, you need a publicly available server with a domain name (I’m talking about devices on our local network that is out of reach).

So for many devices, you need native apps to set the devices up and use them because native apps are not bound to the limitations of the web platform and can offer a pleasant experience for its users. However, I do not want to download a 500 MB app to do that. Maybe the device you have is already a few years old, and the app was never updated to run on your new phone. Perhaps you want to use a desktop or laptop computer, and the manufacturer only built a mobile app. Also not an ideal experience.

WebBluetooth is a new specification that has been implemented in Chrome and Samsung Internet that allows us to communicate directly to Bluetooth Low Energy devices from the browser. Progressive Web Apps in combination with WebBluetooth offer the security and convenience of a web application with the power to directly talk to devices.

Bluetooth has a pretty bad name due to limited range, bad audio quality, and pairing problems. But, pretty much all those problems are a thing of the past. Bluetooth Low Energy is a modern specification that has little to do with the old Bluetooth specifications, apart from using the same frequency spectrum. More than 10 million devices ship with Bluetooth support every single day. That includes computers and phones, but also a variety of devices like heart rate and glucose monitors, IoT devices like light bulbs and toys like remote controllable cars and drones.

Recommended reading: Understanding API-Based Platforms: A Guide For Product Managers

The Boring Theoretical Part

Since Bluetooth itself is not a web technology, it uses some vocabulary that may seem unfamiliar to us. So let’s go over how Bluetooth works and some of the terminology.

Every Bluetooth device is either a ‘Central device’ or a ‘Peripheral’. Only central devices can initiate communication and can only talk to peripherals. An example of a central device would be a computer or a mobile phone.

A peripheral cannot initiate communication and can only talk to a central device. Furthermore, a peripheral can only talk to one central device at the same time. A peripheral cannot talk to another peripheral.

A central device can talk to multiple peripherals. (Large preview)

A central device can talk to multiple peripherals at the same time and could relay messages if it wanted to. So a heart rate monitor could not talk to your lightbulbs, however, you could write a program that runs on a central device that receives your heart rate and turns the lights red if the heart rate gets above a certain threshold.

When we talk about WebBluetooth, we are talking about a specific part of the Bluetooth specification called Generic Attribute Profile, which has the very obvious abbreviation GATT. (Apparently, GAP was already taken.)

In the context of GATT, we are no longer talking about central devices and peripherals, but clients and servers. Your light bulbs are servers. That may seem counter-intuitive, but it actually makes sense if you think about it. The light bulb offers a service, i.e. light. Just like when the browser connects to a server on the Internet, your phone or computer is a client that connects to the GATT server in the light bulb.

Each server offers one or more services. Some of those services are officially part of the standard, but you can also define your own. In the case of the heart rate monitor, there is an official service defined in the specification. In case of the light bulb, there is not, and pretty much every manufacturer tries to re-invent the wheel. Every service has one or more characteristics. Each characteristic has a value that can be read or written. For now, it would be best to think of it as an array of objects, with each object having properties that have values.

A simplified hierarchy of services and characteristics. (Large preview)

Unlike properties of objects, the services and characteristics are not identified by a string. Each service and characteristic has a unique UUID which can be 16 or 128 bits long. Officially, the 16 bit UUID is reserved for official standards, but pretty much nobody follows that rule. Finally, every value is an array of bytes. There are no fancy data types in Bluetooth.

A Closer Look At A Bluetooth Light Bulb

So let’s look at an actual Bluetooth device: a Mipow Playbulb Sphere. You can use an app like BLE Scanner, or nRF Connect to connect to the device and see all the services and characteristics. In this case, I am using the BLE Scanner app for iOS.

The first thing you see when you connect to the light bulb is a list of services. There are some standardized ones like the device information service and the battery service. But there are also some custom services. I am particularly interested in the service with the 16 bit UUID of 0xff0f. If you open this service, you can see a long list of characteristics. I have no idea what most of these characteristics do, as they are only identified by a UUID and because they are unfortunately a part of a custom service; they are not standardized, and the manufacturer did not provide any documentation.

The first characteristic with the UUID of 0xfffc seems particularly interesting. It has a value of four bytes. If we change the value of these bytes from 0x00000000 to 0x00ff0000, the light bulb turns red. Changing it to 0x0000ff00 turns the light bulb green, and 0x000000ff blue. These are RGB colors and correspond exactly to the hex colors we use in HTML and CSS.

What does that first byte do? Well, if we change the value to 0xff000000, the lightbulb turns white. The lightbulb contains four different LEDs, and by changing the value of each of the four bytes, we can create every single color we want.

The WebBluetooth API

It is fantastic that we can use a native app to change the color of a light bulb, but how do we do this from the browser? It turns out that with the knowledge about Bluetooth and GATT we just learned, this is relatively simple thanks to the WebBluetooth API. It only takes a couple of lines of JavaScript to change the color of a light bulb.

Let’s go over the WebBluetooth API.

Connecting To A Device

The first thing we need to do is to connect from the browser to the device. We call the function navigator.bluetooth.requestDevice() and provide the function with a configuration object. That object contains information about which device we want to use and which services should be available to our API.

In the following example, we are filtering on the name of the device, as we only want to see devices that contain the prefix PLAYBULB in the name. We are also specifying 0xff0f as a service we want to use. Since the requestDevice() function returns a promise, we can await the result.

let device = await navigator.bluetooth.requestDevice({ filters: [ { namePrefix: 'PLAYBULB' } ], optionalServices: [ 0xff0f ] });

When we call this function, a window pops up with the list of devices that conform to the filters we’ve specified. Now we have to select the device we want to connect to manually. That is an essential step for security and privacy and gives control to the user. The user decides whether the web app is allowed to connect, and of course, to which device it is allowed to connect. The web app cannot get a list of devices or connect without the user manually selecting a device.

The user has to manually connect by selecting a device. (Large preview)

After we get access to the device, we can connect to the GATT server by calling the connect() function on the gatt property of the device and await the result.

let server = await device.gatt.connect();

Once we have the server, we can call getPrimaryService() on the server with the UUID of the service we want to use as a parameter and await the result.

let service = await server.getPrimaryService(0xff0f);

Then call getCharacteristic() on the service with the UUID of the characteristic as a parameter and again await the result.

We now have our characteristics which we can use to write and read data:

let characteristic = await service.getCharacteristic(0xfffc); Writing Data

To write data, we can call the function writeValue() on the characteristic with the value we want to write as an ArrayBuffer, which is a storage method for binary data. The reason we cannot use a regular array is that regular arrays can contain data of various types and can even have empty holes.

Since we cannot create or modify an ArrayBuffer directly, we are using a ‘typed array’ instead. Every element of a typed array is always the same type, and it does not have any holes. In our case, we are going to use a Uint8Array, which is unsigned so it cannot contain any negative numbers; an integer, so it cannot contain fractions; and it is 8 bits and can contain only values from 0 to 255. In other words: an array of bytes.

characteristic.writeValue( new Uint8Array([ 0, r, g, b ]) );

We already know how this particular light bulb works. We have to provide four bytes, one for each LED. Each byte has a value between 0 and 255, and in this case, we only want to use the red, green and blue LEDs, so we leave the white LED off, by using the value 0.

Reading Data

To read the current color of the light bulb, we can use the readValue() function and await the result.

let value = await characteristic.readValue(); let r = value.getUint8(1); let g = value.getUint8(2); let b = value.getUint8(3);

The value we get back is a DataView of an ArrayBuffer, and it offers a way to get the data out of the ArrayBuffer. In our case, we can use the getUint8() function with an index as a parameter to pull out the individual bytes from the array.

Getting Notified Of Changes

Finally, there is also a way to get notified when the value of a device changes. That isn’t really useful for a lightbulb, but for our heart rate monitor we have constantly changing values, and we don’t want to poll the current value manually every single second.

characteristic.addEventListener( 'characteristicvaluechanged', e => { let r = e.target.value.getUint8(1); let g = e.target.value.getUint8(2); let b = e.target.value.getUint8(3); } ); characteristic.startNotifications();

To get a callback whenever a value changes, we have to call the addEventListener() function on the characteristic with the parameter characteristicvaluechanged and a callback function. Whenever the value changes, the callback function will be called with an event object as a parameter, and we can get the data from the value property of the target of the event. And, finally extract the individual bytes again from the DataView of the ArrayBuffer.

Because the bandwidth on the Bluetooth network is limited, we have to manually start this notification mechanism by calling startNotifications() on the characteristic. Otherwise, the network is going to be flooded by unnecessary data. Furthermore, because these devices typically use a battery, every single byte that we do not have to send will definitively improve the battery life of the device because the internal radio does not need to be turned on as often.

Conclusion

We’ve now gone over 90% of the WebBluetooth API. With just a few function calls and sending 4 bytes, you can create a web app that controls the colors of your light bulbs. If you add a few more lines, you can even control a toy car or fly a drone. With more and more Bluetooth devices making their way on to the market, the possibilities are endless.

Further Resources (dm, ra, il)
Categories: Around The Web

Webhosting Compared: Testing The Uptime Of 32 Hosts In 2018

Smashing Magazine - Tue, 02/12/2019 - 6:00am
Webhosting Compared: Testing The Uptime Of 32 Hosts In 2018 Webhosting Compared: Testing The Uptime Of 32 Hosts In 2018 John Stevens 2019-02-12T12:00:22+01:00 2019-02-18T13:24:09+00:00

(This is a sponsored article.) Many surveys have indicated that uptime is number one factor when choosing a web host and although most, if not all, web hosting services “promise” 99.99% uptime, it’s not the case with our case-study.

According to our latest research, the average uptime of 32 shared web hosting providers is 99.59%. That’s approximately 35 hours 32 minutes of downtime per year, per website.

And downtime even happens to online giants. A Dun & Bradstreet study found that nearly 60 percent of Fortune 500 companies experience a minimum of 1.6 hours of downtime every week.

As a rule of thumb, if you are experiencing an uptime of 99.90% or below, you should switch your web host. A good web host should provide you with an uptime of at least 99.94%.

To run this series of tests, we have signed up for all of the 32 web hosting providers as a regular user, using the cheapest plan available. After that, we set up a basic WordPress website and start monitoring them with Pingdom.com. (Tools like Pingdom or Appoptics APM let you regularly check if a website or app is available.) Our uptime check interval was set to 1 minute, which means all of the sites are scanned every minute to get the most accurate statistics.

Please note that many hosts don’t define uptime like that, so they will often refuse to pay out on the guarantee because they say it was “planned maintenance” etc.

Let’s take a closer look.

Web Hosting Provider Average Uptime ↓ Total Outages Total Downtime Per Year 1. MidPhase 99.991% 19 outages 45 minutes 2. Bluehost 99.991% 7 outages 52 minutes 3. DigitalOcean* 99.989% 11 outages 58 minutes 4. SiteGround* 99.988% 26 outages 73 minutes 5. Site5* 99.986% 16 outages 83 minutes 6. HostGator* 99.984% 19 outages 84 minutes 7. A Small Orange* 99.978% 52 outages 125 minutes 8. iPage 99.975% 72 outages 131 minutes 9. HostPapa 99.975% 39 outages 144 minutes 10. FastComet 99.973% 44 outages 146 minutes 11. LunarPages* 99.972% 20 outages 153 minutes 12. Hostinger* 99.971% 28 outages 154 minutes 13. WebHostingBuzz 99.969% 28 outages 163 minutes 14. GreenGeeks* 99.969% 11 outages 164 minutes 15. JustHost 99.968% 28 outages 165 minutes 16. GoDaddy 99.965% 47 outages 184 minutes 17. HostRocket 99.960% 31 outages 202 minutes 18. HostMonster 99.955% 40 outages 235 minutes 19. DreamHost* 99.953% 40 outages 239 minutes 20. Hosting24 99.951% 31 outages 264 minutes 21. WestHost* 99.948% 48 outages 271 minutes 22. WebHostingHub 99.948% 76 outages 278 minutes 23. inMotion Hosting 99.935% 90 outages 341 minutes 24. A2 Hosting 99.928% 64 outages 375 minute 25. HostMetro 99.852% 247 outages 763 minutes 26. MDD Hosting* 99.833% 76 outages 874 minutes 27. FatCow 99.829% 377 outages 899 minutes 28. NameCheap* 99.826% 453 outages 917 minutes 29. HostNine 99.723% 241 outages 1448 minutes 30. One.com 99.593% 419 outages 2132 minutes 31. WebHostingPad 97.588% 1,655 outages 9 days 32. Arvixe* 91.098% 20,051 outages 1 month

* These web hosting providers offer an uptime guarantee. If they’ve failed on promised uptime, you can ask for your money back.

If you’re into each month overviews with more detailed data, take a look at these pages below:

1. MidPhase.com No uptime guarantee:

Although MidPhase doesn’t mention any uptime guarantee on their website, they are the clear winner of this case-study hitting nearly 100% uptime in 2018.

2. Bluehost.com No uptime guarantee:

Similarly to MidPhase, Bluehost doesn’t offer any uptime guarantees either (just a network/server uptime agreement). However, their servers have been working very steadily, with the exception of one bigger outage (42 minutes).

3. DigitalOcean.com Uptime guarantee available:

DigitalOcean is a cheap cloud hosting that promises 99.99% uptime which they have greatly succeeded in our test. If you see uptime less than 99.99% using DigitalOcean, you can ask your money back.

4. SiteGround.com Uptime guarantee available:

SiteGround is a hosting provider with an excellent uptime score. They also offer uptime guarantee, providing you with one month of free service if uptime falls below 99.99% and an additional month if uptime falls below 99.90%.

5. Site5.com Uptime guarantee available:

99.98% is still decent uptime. What’s even better – Site5 comes with an uptime guarantee of 99.9% – anything below that is eligible for a % of credit back. They even offer 100% credit back when uptime falls below 99.5% on their fully managed VPS

6. HostGator.com Uptime guarantee available:

HostGator comes with a decent uptime. On top of that, they even provide an uptime guarantee of 99.9% and you get credit back when it falls below. Just beware that it only applies for the actual server downtime excluding server maintenance.

7. ASmallOrange.com Uptime guarantee available:

ASmallOrange offers an uptime guarantee of 99.9%. When it’s below that, you get a refund of one day of service per every 45-min downtime. Beware of the specific clauses. Just note the high number of outages we experienced in the test period.

8. iPage.com No uptime guarantee:

iPage does not offer an uptime guarantee. Luckily, considering their ranking in overall uptime, we’re pretty sure you wouldn’t need it anyway. One thing to be aware of is the high number of outages we experienced.

9. HostPapa.com No uptime guarantee:

Even though they mention under their Terms of Service using “reasonable efforts to maintain 99.9% of uptime”, HostPapa does not exactly provide an uptime guarantee.

10. FastComet.com No uptime guarantee:

Even though their live chat agent said that they guarantee a 99% uptime, there is no official uptime guarantee on FastComet’s Terms of Service or refund/credit policy if it falls below what’s promised. The total amount of outages is somewhat concerning.

11. LunarPages.com Uptime guarantee available:

For every 15min of downtime, LunarPages credits client’s account with an equal of a full day of service. That guarantee excludes scheduled maintenance.

12. Hostinger.com Uptime guarantee available:

Hostinger’s above average uptime is backed up by their uptime guarantee of 99.9%. As in all cases, scheduled maintenance is excluded.

13. WebHostingBuzz.com No uptime guarantee:

WHB does not provide any uptime guarantee per se. Based on our experience, you most likely wouldn’t need one anyway.

14. GreenGeeks.com Uptime guarantee available:

GreenGeeks offers a 99.9% uptime guarantee. However, it only applies to their own servers and is not applicable for client errors.

15. JustHost.com No uptime guarantee:

There’s no uptime guarantee on their website, whatsoever.

16. GoDaddy.com No uptime guarantee:

Despite their big name, huge client-base and decent service, GoDaddy does not provide any uptime guarantee. You should also beware of their rather high number (compared to other top hosts) of outages.

17. HostRocket.com Limited uptime guarantee:

HostRocket has an official 99.5% uptime guarantee. Like others, it only applies for their own direct services.

18. HostMonster.com 18. HostMonster.com No uptime guarantee:

Even though you can find a statement claiming to strive for their best, there is no official uptime guarantee.

19. DreamHost.com Uptime guarantee available:

Finally, there you have it – an official 100% uptime guarantee.” DreamHost guarantees 100% uptime. A failure to provide 100% uptime will result in customer compensation pursuant to guidelines established herein.”

20. Hosting24.com Limited uptime guarantee:

They offer a service uptime guarantee of 99.9%. It’s solely determined by them and you get 5% of your monthly hosting fee as credit back. It’s only usable to purchase further service of products from Hosting24.

21. WestHost.com Uptime guarantee available:

At close investigation, we found a 99.9% service uptime guarantee. When outages happen which are directly related to WestHost, you get a credit of 5-100% of your monthly hosting fee depending on the total downtime.

22. WebHostingHub.com No uptime guarantee:

WHH claims on their front page to have a 99.9% uptime guarantee. Unfortunately, we did not find an official guarantee under their terms of service and other binding policies.

23. inMotionHosting.com Limited uptime guarantee:

Uptime guarantee only applies to Business Pro accounts. Even though it’s a really sweet one (99.999%), there is no uptime guarantee for other hosting plans.

24. A2Hosting.com No uptime guarantee:

A2 comes with a 99.9% uptime commitment. Similarly to other hosting providers, when it comes to credit, there are clauses related to server maintenance and the outage being not-their-responsibility.

25. HostMetro.com Limited uptime guarantee:

When the total uptime in a year is less than 99%, you get a 1-month service for free.

26. MDDHosting.com Uptime guarantee available:

They have an official 1000% service uptime guarantee (it’s not as cool as it sounds).

“If your server has a physical downtime of more than 1 hour, you can request for 10 times (1000%) the actual amount of downtime. This means that if your server has a physical downtime of 1 hour, you will receive 10 hours of credit.”

27. FatCow.com No uptime guarantee:

It doesn’t seem that uptime is one of the strengths of FatCow – despite our research, we did not find any uptime guarantee.

28. NameCheap.com Uptime guarantee available:

NameCheap offers one full day of service for every 1h that your server is down in a month. Beware that the first 45min are not applicable.

29. HostNine.com No uptime guarantee:

An uninspiring total uptime is even further disappointing as there is no uptime guarantee under their ToS.

30. One.com No uptime guarantee:

A day without service is a serious issue by itself. Despite our serious efforts, we could not find any uptime guarantee either that would make you eligible for at least some credit.

31. WebHostingPad.com Limited uptime guarantee:

There is a 99% official uptime guarantee. However, it’s most likely that you wouldn’t really care about the few free days you get when the service performance is this bad.

32. Arvixe.com Uptime guarantee available:

Despite their bad performance (the worst ever, by far, anywhere…), Arvix actually does have an official uptime guarantee of 99.9%. But does it really matter to get a refund or some credit when your website has lost all its traffic because of its non-existent performance?

(ms, ra, il)
Categories: Around The Web

A/B Testing For Mobile-First Experiences

Smashing Magazine - Mon, 02/11/2019 - 6:00am
A/B Testing For Mobile-First Experiences A/B Testing For Mobile-First Experiences Suzanne Scacca 2019-02-11T12:00:26+01:00 2019-02-18T13:24:09+00:00

Your client’s website is done. They’re thrilled with it. You and your team are satisfied with the results. And visitor reception looks good so far.

While I recognize that a lot of research, experimentation, analysis and review went into the creation of the website, is that all there is to building a winning website these days? I’d argue that the mobile-first web has added a layer of complexity that few are fully prepared for.

Which is why your work shouldn’t stop when you hit the “Publish” button.

If you’re not yet performing post-launch A/B testing for your website clients, that’s a big mistake. Although we have a massive amount of case studies and other research at our disposal that confirm how to design for conversion on desktop, the mobile experience is still relatively new. At least the mobile-first experience as we know it today.

The following guide includes tips for A/B testing for mobile websites and will get you thinking about conversion rate optimization in other ways than just “Buy This Now”.

A Brief Introduction To A/B Testing For Mobile

Once a website has gone live, Google Analytics and any conversion rate optimization (CRO) tools you hook up to the site will start feeding you data about your users. If you choose to do something with these valuable insights, you have two options:

  1. Identify obstacles in the experience and implement changes to the site to resolve them.
  2. Identify a single obstacle in the experience, hypothesize why it occurred and create an alternative version of the site to test the resolution.

The first option seems cut-and-dried. The data tells you there is an issue; you create a solution for it. But like I mentioned already, the chances of succeeding when shooting from the hip like that only work with tried and true desktop design techniques. Even then, it can still be risky if your audience doesn’t align with the average online user’s behavior.

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬

The second option, on the other hand, allows designers to more safely implement changes to a mobile website. Until you have a clear picture of the mobile user’s journey through your website (which, realistically, could involve them jumping from a mobile device to desktop at some point), mobile A/B testing must be an essential part of your job as a web designer.

This is how A/B testing works:

  • Identify a part of the website that you believe needs a change. (This should be based on findings in your data or direct reports from users about problematic experiences.)
  • Hypothesize why there is friction and how you think it can be resolved.
  • Choose just one element to change.
  • Using A/B testing software, set up your test variables. You should pit the control (i.e. original version of the site) against a variation of the element.
  • Run the test against equal parts of mobile visitors.
  • Let the test run for two to four weeks.
  • Monitor results to make sure you’re generating sufficient data and take note of any anomalies along the way.
  • End the test and review the results.
  • If there’s a significant margin between the control and variation results, use your mobile A/B testing tool (like VWO) to implement the winner.

It’s okay if you find that the control is the winner. Take what you’ve learned and apply it to your A/B testing efforts going forward.

Recommended reading: How To Conduct Usability Studies With Participants With Disabilities

Tips For A/B Testing For Mobile-First Experiences

You’re here because you want to know how to increase conversions on the websites you build for clients. The tips below will force you to step outside typical conversion rate optimization planning and think outside the box as you test your theories.

Tip #1: Stop Thinking About Mobile vs. Desktop A/B Testing

With traditional A/B testing, you typically have verifiable proof of what works and what doesn’t. You tweak the wording on a call-to-action and more users click to buy the product. You change the color of the shirt in a photo and sales go up by 25%. You move the placement of the CTA to the bottom of the post and more readers subscribe.

In other words, you know that a change you made will directly impact the business’s bottom line.

However, when it comes to mobile, it’s not that easy.

Qubit published a report called The Influence of Mobile Discovery in 2018.

Qubit demonstrates growth in the mobile halo effect. (Source: Qubit) (Large preview)

The above image depicts the differences in the mobile halo effect from 2016 to 2017.

The mobile halo effect is a term Qubit uses to describe how the activity that takes place on mobile directly influences what happens on desktop. Qubit’s research of over 1.2 billion customer interactions with the web found:

Analyzing the cohort of users in our dataset who logged into their accounts on more than one type of device shows that mobile activity directly influences an average of 19% of computer revenue. In some sub-verticals, this influence is much higher, with Fashion seeing an average of 24%, while some retailers receive as many as 1 in 3 of their computer transactions as a result of mobile-browsing.

What’s more, this information only accounts for mobile users who logged into a website from multiple devices. Qubit suspects that people who simply discover a website through mobile also lead to this halo effect. This, in turn, drives up the value of desktop conversions because of how helpful mobile is during the discovery phase of the customer journey.

This is why you can’t just look at mobile-only results on a mobile-first A/B test.

Instead, conduct your tests in the following manner:

  • Run your test with mobile visitors.
  • Review the results from your A/B testing tool to see if you were able to remove the obstacle from the mobile experience.
  • Then, look at your Google Analytics results from the same time period. Even if mobile traffic continued to drop off at the same point, you may find that desktop traffic and engagement increased as a result.

In sum, don’t go into mobile A/B testing thinking that everything you do must result in a greater amount of sales, subscribers or members on mobile. Instead, focus on how to improve the experience as a whole so that it improves your overall conversion rate.

Tip #2: Start with the Header

Remember that there are four micro-moments (or motivations) that drive mobile users to a website:

  1. I want to know.
  2. I want to go.
  3. I want to do.
  4. I want to buy.

With such a clear purpose driving their journey to and hopefully through your mobile site, don’t force them to wait for what they’re asking for. In terms of design, this translates to shortening their pathway — either to conversion or to completing the mobile experience before moving to desktop.

When you begin mobile-first A/B testing, look at elements that provide an answer to the micro-moments that are most relevant to your website.

Is there a way to place them in the header of the website or within the first scroll or two of the home page? Or can you at least design a one-click shortcut in the navigation to take them to it?

Here are some ideas:

1. I want to know.

Websites with lots of content would do well to test whether or not rearranging the navigation and putting emphasis on relevant and timely categories helps with conversion.

BuzzFeed takes this theory a step further:

BuzzFeed doesn’t hide its mobile navigation. (Source: BuzzFeed) (Large preview)

In addition to customizing the navigation regularly, BuzzFeed has chosen to leave the main navigation out in the open on mobile, with a fun selection of emojis to draw attention to the timeliest of categories.

Another way to answer the “I want to know” search is by providing a point of contact in as streamlined a fashion as possible as SensesLab has done:

The SensesLab includes a recognizable “mailto” icon in the header. (Source: SensesLab) (Large preview)

The “Mail” icon in the top-right corner takes mobile visitors to the Contact page. However, this is no ordinary contact page. While an introduction to their point of contact and email address is given, it’s the contact form below that really shines:

The SensesLab provides a super mobile-friendly contact form at the shortcut. (Source: SensesLab) (Large preview)

The entire form fits within an entire screen-grab on my iPhone above. There’s no wasting of time by providing instructions on how to fill out the form or anything like that. Users just need to click on the highlighted fields to personalize their responses.

Even better:

The SensesLab uses user-friendly contact form fields. (Source: SensesLab) (Large preview)

SensesLab has anticipated their responses and provided pre-populated answers along with custom keyboards to shorten the amount of time anyone has to spend filling this out.

2. I want to go.

I think the solution to test for with this one is obvious. In other words:

Where in the header or above the fold do you place the reservation buttons?

Just don’t be afraid to think outside the box with this. For example, this is The Assemblage website:

The Assemblage includes a 'Book a Tour' icon on the mobile home page. (Source: The Assemblage) (Large preview)

The Assemblage is a coworking space located in New York City. While the mobile site could’ve easily prioritized conversions up top (i.e. “Get your membership now!”), it instead provides a shortcut that makes more sense.

With the focus on booking a tour, mobile visitors can easily claim a date and time. Then, worry about learning all about and seeing the workspace in person later.

The Assemblage prioritizes booking tours of the cowork space. (Source: The Assemblage) (Large preview)

Completing the booking process is incredibly easy on mobile, too.

There are other ways to think outside the box when it comes to designing and testing for “I want to go”. This next example combines two micro-moments and does so in a really unique way, in my opinion.

This is Visit California:

Visit California uses a recognizable Map icon in its header. (Source: Visit California) (Large preview)

Among the well-chosen icons its placed in the header of the site, Visit California also includes a “Map” icon. After all, what is one of the main reasons why someone would visit this site?

“I want to go to California and need suggestions!”

Now, behind this map icon is not a reservation system, enabling users to book their trip to California. With a site promoting travel to such an expansive location, users are more likely to use this site to gather information to decide where to go. The Map icon, then, is their key to drilling down deeper into those answers:

Visit California’s Map navigation options. (Source: Visit California) (Large preview)

This is a unique and visually stimulating way to get research topics and answers into the hands of people who want it.

3. I want to do.

This question is an interesting one to design and A/B test for.

On the one hand, you’d assume that “I want to do” would be answered by articles that provide a how-to for the desired task. When that’s the case, the abundantly sized search bar from Kitchn is a good idea to test for:

Kitchn uses a search bar that’s comparable in size to the header bar. (Source: Kitchn) (Large preview)

It’s clear what Kitchn users want to do when they get here: search for recipes. And with a magazine of Kitchn’s size, that could be a difficult task to accomplish by using the traditional navigation. Instead, this search bar that’s nearly comparable in size to the entire header bar provides a faster solution.

But then you have the other kind of “I want to do” situation to design for — the one where the visitor of your mobile site wants to go out in the real world and get something done. This is similar to the “I want to go” solution from The Assemblage.

ReShape is a fitness center in Poland:

ReShape’s home page looks like your typical fitness center website. (Source: ReShape) (Large preview)

Once you open the navigation on this website, users encounter a number of options to learn about the fitness center and its services.

ReShape includes a navigational shortcut for mobile customers. (Source: ReShape) (Large preview)

What’s nice about this, however, is that the website allows current customers to cut the line and schedule a class right away through the calendar icon. There’s no need to download and use a separate mobile app. It’s all right on the mobile website and it’s easy to do, too:

ReShape makes scheduling fitness classes through mobile a breeze. (Source: ReShape) (Large preview)

When the success of the website and business is contingent upon getting customers to actually do something, don’t bury it in the mobile experience.

4. I want to buy.

Lastly, there’s the “I want to buy” scenario you have to test for.

While the hypothesis for this kind of test is going to be easy enough to figure out — “I want to get more mobile customers to make a purchase” — it’s how you use your design to compel them to do so that’s going to be difficult. Because, again, you have to remember that mobile conversion isn’t simple.

One example I really like of this comes from The Bark, a magazine for dog owners.

The Bark hello bar commands attention without being pushy. (Source: The Bark) (Large preview)

What’s nice about this design is that there are two actions competing against one another:

  1. The content of the website that allows visitors to peruse articles for free.
  2. The unobtrusive yet boldly designed sticky bar with an attractive offer to convert.
The Bark subscription page for mobile. (Source: The Bark) (Large preview)

As more and more we move away from pop-ups and with the sidebar having little to no place on mobile, we’re running out of options for ways to jump into the experience and say:

Hey! Buy this now!

You could place banners in-line with the content, but that may be too disruptive for your users. While I’d assume that a sticky bar that can easily be dismissed is the better way to compel mobile visitors to convert, this is why we have A/B testing. To let us know what exactly our specific audience will do when confronted with a Buy (Subscribe) CTA on mobile.

And if they don’t want to convert there, that’s fine. At least you’ve done your due diligence in testing alternative scenarios to see if you can improve your success rate.

Tip #3: Encourage Users to Save Instead

This last point is a good segue into what I’m going to talk about next:

There are just some websites that won’t convert well on mobile.

Although research on Generation Z as consumers is still relatively new, many suggest that they are going to be true multichannel shoppers. Most of their research will be done on mobile devices, but the preferred shopping experience will be from a computer or in person.

Whether or not that’s true for Gen Z, millennials or any other generation of consumer, I think it’s a smart idea to test for this hypothesis. Until your mobile conversion rates are consistently and significantly higher than desktop and in-person conversion, encouraging mobile users to “Save” their progress on your site might be the better design choice.

As you work on designing and redesigning websites this year, you might want to save yourself the trouble of committing solely to a conversion funnel. Instead, build in shortcuts to “Save” on the mobile experience like:

  • Sign up for an account.
  • Save products to your cart or wish list.
  • Save an article or feed for future reading.
  • Share your email address for future updates.
  • Sign up for a free demo and we’ll take care of the rest.

Then, when the site is live, test how the conversion rates are affected with or without them.

Here are some neat examples of websites that use “Save” features well on mobile.

This is Entrepreneur magazine:

Entrepreneur has a “Save” content icon in its header. (Source: Entrepreneur) (Large preview)

See that icon in the header between the search magnifying glass and account settings? This is where Entrepreneur enables regular readers to save content for future consumption:

Entrepreneur’s well-organized “Save” folder for readers. (Source: Entrepreneur) (Large preview)

As you can see, readers can save all sorts of content under this Save feature, making it easy to return to Entrepreneur articles any time, any place and from any device.

Then, there’s the example of Zendesk:

Zendesk offers a free demo and trial right away. (Source: Zendesk) (Large preview)

For those of you designing websites for service providers and SaaS companies, this is an excellent way to help your users “Save” their progress. I know it might not look that way at first glance, but let me explain:

Zendesk isn’t wasting anyone’s time with an overlong description of what it does and why people need to purchase its help desk software. Instead, it’s clearly summarized what users can expect and then provides two appealing calls-to-action. Regardless of which option the mobile user chooses, Zendesk requires them to provide contact information.

So, let’s say a mobile user fills out the form to enter the demo. They get inside it, but then realize they’re short on time or just don’t want to interact with it on mobile. Fine. Zendesk now has their information and will be in touch soon to follow up about the experience. The mobile user can then re-enter the experience from their preferred device when the inevitable follow-up email reminds them to do so.

Tip #4: A/B Test Your Page and Post Length

Another suggestion I’m going to make for mobile-first A/B testing is content length.

I actually touched on the subject of brevity in my previous article, How Web Designers Can Contribute to Mobile-First Marketing. However, I didn’t talk about how you can use A/B testing to confirm whether or not that’s the right path for your website.

There are case studies and research reports galore that discuss the subject of ideal content length for both desktop and mobile. Some are emphatic that shorter is always better, which is why I think we’ve seen such a huge push for video over written content in past years.

But then there are some who suggest that length should be determined on a case-by-case basis.

Take the Neil Patel blog, for instance. If I had to guess, I’d say that his articles are between 2,000 and 5,000 words on average — even on mobile. Considering Patel is a multi-millionaire, I don’t suspect that his lengthy posts have hurt the success of his brand in the least bit.

So, again, this is why we need A/B testing — just to confirm our suspicions and put any fears we might have about the efficacy of a site’s design or content to rest.

Unless your client comes to you as a well-known brand and they’ve already proved they can produce successful 2K-word posts like Patel, you have to test this.

Talk to your writers and marketers and ask them to create two different versions of your content for the first month or two. This includes the home page, blog posts, product pages and any other key pages in the user’s journey. Run a test to see if the length of the page on mobile affects readability as well as conversions.

You can then use these results to refine the rest of the content on your site, making sure you’re providing mobile users with the ideal reading experience wherever they go.

Wrapping Up

The goal in mobile-first A/B testing is to inspire mobile visitors to keep moving through the experience. Even if the element you’ve chosen to test doesn’t directly lead to conversion, the improvements you make should eventually trickle down to that final step, no matter which device it takes place on.

Just don’t forget to study your desktop analytics while running mobile-first A/B tests. While test results might not show you what you were hoping to see, looking at the overall picture might.

(ra, yk, il)
Categories: Around The Web

New JavaScript Features That Will Change How You Write Regex

Smashing Magazine - Fri, 02/08/2019 - 7:00am
New JavaScript Features That Will Change How You Write Regex New JavaScript Features That Will Change How You Write Regex Faraz Kelhini 2019-02-08T13:00:32+01:00 2019-02-18T13:24:09+00:00

There’s a good reason the majority of programming languages support regular expressions: they are extremely powerful tools for manipulating text. Text processing tasks that require dozens of lines of code can often be accomplished with a single line of regular expression code. While the built-in functions in most languages are usually sufficient to perform search and replace operations on strings, more complex operations — such as validating text inputs — often require the use of regular expressions.

Regular expressions have been part of the JavaScript language since the third edition of the ECMAScript standard, which was introduced in 1999. ECMAScript 2018 (or ES2018 for short) is the ninth edition of the standard and further improves the text processing capability of JavaScript by introducing four new features:

These new features are explained in detail in the subsections that follow.

Debugging JavaScript

console.log can tell you a lot about your app, but it can't truly debug your code. For that, you need a full-fledged JavaScript debugger. Read more →

Web forms are such an important part of the web, but we design them poorly all the time. The brand-new “Form Design Patterns” book is our new practical guide for people who design, prototype and build all sorts of forms for digital services, products and websites. The eBook is free for Smashing Members.

Check the table of contents ↬ Lookbehind Assertions

The ability to match a sequence of characters based on what follows or precedes it enables you to discard potentially undesired matches. This is especially important when you need to process a large string and the chance of undesired matches is high. Fortunately, most regular expression flavors provide the lookbehind and lookahead assertions for this purpose.

Prior to ES2018, only lookahead assertions were available in JavaScript. A lookahead allows you to assert that a pattern is immediately followed by another pattern.

There are two versions of lookahead assertions: positive and negative. The syntax for a positive lookahead is (?=...). For example, the regex /Item(?= 10)/ matches Item only when it is followed, with an intervening space, by number 10:

const re = /Item(?= 10)/; console.log(re.exec('Item')); // → null console.log(re.exec('Item5')); // → null console.log(re.exec('Item 5')); // → null console.log(re.exec('Item 10')); // → ["Item", index: 0, input: "Item 10", groups: undefined]

This code uses the exec() method to search for a match in a string. If a match is found, exec() returns an array whose first element is the matched string. The index property of the array holds the index of the matched string, and the input property holds the entire string that the search performed on. Finally, if named capture groups are used in the regular expression, they are placed on the groups property. In this case, groups has a value of undefined because there is no named capture group.

The construct for a negative lookahead is (?!...). A negative lookahead asserts that a pattern is not followed by a specific pattern. For example, the pattern /Red(?!head)/ matches Red only if it not followed by head:

const re = /Red(?!head)/; console.log(re.exec('Redhead')); // → null console.log(re.exec('Redberry')); // → ["Red", index: 0, input: "Redberry", groups: undefined] console.log(re.exec('Redjay')); // → ["Red", index: 0, input: "Redjay", groups: undefined] console.log(re.exec('Red')); // → ["Red", index: 0, input: "Red", groups: undefined]

ES2018 complements lookahead assertions by bringing lookbehind assertions to JavaScript. Denoted by (?<=...), a lookbehind assertion allows you to match a pattern only if it is preceded by another pattern.

Let’s suppose you need to retrieve the price of a product in euro without capturing the euro symbol. With a lookbehind, this task becomes a lot simpler:

const re = /(?<=€)\d+(\.\d*)?/; console.log(re.exec('199')); // → null console.log(re.exec('$199')); // → null console.log(re.exec('€199')); // → ["199", undefined, index: 1, input: "€199", groups: undefined]

Note: Lookahead and lookbehind assertions are often referred to as “lookarounds”.

The negative version of lookbehind is denoted by (?<!...) and enables you to match a pattern that is not preceded by the pattern specified within the lookbehind. For example, the regular expression /(?<!\d{3}) meters/ matches the word “meters” if three digits do not come before it:

const re = /(?<!\d{3}) meters/; console.log(re.exec('10 meters')); // → [" meters", index: 2, input: "10 meters", groups: undefined] console.log(re.exec('100 meters')); // → null

As with lookaheads, you can use several lookbehinds (negative or positive) in succession to create a more complex pattern. Here’s an example:

const re = /(?<=\d{2})(?<!35) meters/; console.log(re.exec('35 meters')); // → null console.log(re.exec('meters')); // → null console.log(re.exec('4 meters')); // → null console.log(re.exec('14 meters')); // → ["meters", index: 2, input: "14 meters", groups: undefined]

This regex matches a string containing meters only if it is immediately preceded by any two digits other than 35. The positive lookbehind ensures that the pattern is preceded by two digits, and then the negative lookbehind ensures that the digits are not 35.

Named Capture Groups

You can group a part of a regular expression by encapsulating the characters in parentheses. This allows you to restrict alternation to a part of the pattern or apply a quantifier on the whole group. Furthermore, you can extract the matched value by parentheses for further processing.

The following code gives an example of how to find a file name with .jpg extension in a string and then extract the file name:

const re = /(\w+)\.jpg/; const str = 'File name: cat.jpg'; const match = re.exec(str); const fileName = match[1]; // The second element in the resulting array holds the portion of the string that parentheses matched console.log(match); // → ["cat.jpg", "cat", index: 11, input: "File name: cat.jpg", groups: undefined] console.log(fileName); // → cat

In more complex patterns, referencing a group using a number just makes the already cryptic regular expression syntax more confusing. For example, suppose you want to match a date. Since the position of day and month is swapped in some regions, it’s not clear which group refers to the month and which group refers to the day:

const re = /(\d{4})-(\d{2})-(\d{2})/; const match = re.exec('2020-03-04'); console.log(match[0]); // → 2020-03-04 console.log(match[1]); // → 2020 console.log(match[2]); // → 03 console.log(match[3]); // → 04

ES2018’s solution to this problem is named capture groups, which use a more expressive syntax in the form of (?<name>...):

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2020-03-04'); console.log(match.groups); // → {year: "2020", month: "03", day: "04"} console.log(match.groups.year); // → 2020 console.log(match.groups.month); // → 03 console.log(match.groups.day); // → 04

Because the resulting object may contain a property with the same name as a named group, all named groups are defined under a separate object called groups.

A similar construct exists in many new and traditional programming languages. Python, for example, uses the (?P<name>) syntax for named groups. Not surprisingly, Perl supports named groups with syntax identical to JavaScript (JavaScript has imitated its regular expression syntax from Perl). Java also uses the same syntax as Perl.

In addition to being able to access a named group through the groups object, you can access a group using a numbered reference — similar to a regular capture group:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const match = re.exec('2020-03-04'); console.log(match[0]); // → 2020-03-04 console.log(match[1]); // → 2020 console.log(match[2]); // → 03 console.log(match[3]); // → 04

The new syntax also works well with destructuring assignment:

const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/; const [match, year, month, day] = re.exec('2020-03-04'); console.log(match); // → 2020-03-04 console.log(year); // → 2020 console.log(month); // → 03 console.log(day); // → 04

The groups object is always created, even if no named group exists in a regular expression:

const re = /\d+/; const match = re.exec('123'); console.log('groups' in match); // → true

If an optional named group does not participate in the match, the groups object will still have a property for that named group but the property will have a value of undefined:

const re = /\d+(?<ordinal>st|nd|rd|th)?/; let match = re.exec('2nd'); console.log('ordinal' in match.groups); // → true console.log(match.groups.ordinal); // → nd match = re.exec('2'); console.log('ordinal' in match.groups); // → true console.log(match.groups.ordinal); // → undefined

You can refer to a regular captured group later in the pattern with a backreference in the form of \1. For example, the following code uses a capture group that matches two letters in a row, then recalls it later in the pattern:

console.log(/(\w\w)\1/.test('abab')); // → true // if the last two letters are not the same // as the first two, the match will fail console.log(/(\w\w)\1/.test('abcd')); // → false

To recall a named capture group later in the pattern, you can use the /\k<name>/ syntax. Here is an example:

const re = /\b(?<dup>\w+)\s+\k<dup>\b/; const match = re.exec("I'm not lazy, I'm on on energy saving mode"); console.log(match.index); // → 18 console.log(match[0]); // → on on

This regular expression finds consecutive duplicate words in a sentence. If you prefer, you can also recall a named capture group using a numbered back reference:

const re = /\b(?<dup>\w+)\s+\1\b/; const match = re.exec("I'm not lazy, I'm on on energy saving mode"); console.log(match.index); // → 18 console.log(match[0]); // → on on

It’s also possible to use a numbered back reference and a named backreference at the same time:

const re = /(?<digit>\d):\1:\k<digit>/; const match = re.exec('5:5:5'); console.log(match[0]); // → 5:5:5

Similar to numbered capture groups, named capture groups can be inserted into the replacement value of the replace() method. To do that, you will need to use the $<name> construct. For example:

const str = 'War & Peace'; console.log(str.replace(/(War) & (Peace)/, '$2 & $1')); // → Peace & War console.log(str.replace(/(?<War>War) & (?<Peace>Peace)/, '$<Peace> & $<War>')); // → Peace & War

If you want to use a function to perform the replacement, you can reference the named groups the same way you would reference numbered groups. The value of the first capture group will be available as the second argument to the function, and the value of the second capture group will be available as the third argument:

const str = 'War & Peace'; const result = str.replace(/(?<War>War) & (?<Peace>Peace)/, function(match, group1, group2, offset, string) { return group2 + ' & ' + group1; }); console.log(result); // → Peace & War s (dotAll) Flag

By default, the dot (.) metacharacter in a regex pattern matches any character with the exception of line break characters, including line feed (\n) and carriage return (\r):

console.log(/./.test('\n')); // → false console.log(/./.test('\r')); // → false

Despite this shortcoming, JavaScript developers could still match all characters by using two opposite shorthand character classes like [\w\W], which instructs the regex engine to match a character that’s a word character (\w) or a non-word character (\W):

console.log(/[\w\W]/.test('\n')); // → true console.log(/[\w\W]/.test('\r')); // → true

ES2018 aims to fix this problem by introducing the s (dotAll) flag. When this flag is set, it changes the behavior of the dot (.) metacharacter to match line break characters as well:

console.log(/./s.test('\n')); // → true console.log(/./s.test('\r')); // → true

The s flag can be used on per-regex basis and thus does not break existing patterns that rely on the old behavior of the dot metacharacter. Besides JavaScript, the s flag is available in a number of other languages such as Perl and PHP.

Recommended reading: An Abridged Cartoon Introduction To WebAssembly

Unicode Property Escapes

Among the new features introduced in ES2015 was Unicode awareness. However, shorthand character classes were still unable to match Unicode characters, even if the u flag was set.

Consider the following example:

const str = '
Categories: Around The Web

How People Make Decisions

Smashing Magazine - Thu, 02/07/2019 - 7:30am
How People Make Decisions How People Make Decisions Susan Weinschenk 2019-02-07T13:30:37+01:00 2019-02-18T13:24:09+00:00

(This article is sponsored by Adobe.) Kelly’s in charge of choosing IT cloud services at her company. She has signed the company up for a chatbot service, and has had the “Pro” level service (not the “Free” or “Standard”) for two years.

It’s time for the annual renewal. Will she renew? Will she decide to renew, but switch to the free service only? Is there anything about the email notice and/or webpage for the service that will either encourage her or discourage her from renewing?

The pricing plan that is presented to Kelly. (Large preview)

There is a lot of research on human decision-making. Here are some of my favorite insights from the research.

Most Decisions Are Not Made “Logically”

We like to think that we are logical and that when we are making a decision, we carefully weigh all of our alternatives. When it’s time to buy a new car, do we read all the specs and reviews, and choose the one that is the safest and most economical? When it’s time to renew the chatbot service, does Kelly do a study to see how much use she has made of the “Pro” services and evaluate whether she should stay with that level and pay that amount each month?

These would be the logical ways to make the decision, and although we sometimes make decisions rationally and logically, there are hundreds of decisions we make every day, and we don’t do a logical think through of every one. Even the big decisions where we think we are being logical, the research shows that most of our decisions — big or small — are made unconsciously and involve emotion.

Here are some facts about decisions that may surprise you.

Most Of Our Decisions Are Made Unconsciously

By looking at brain activity while making a decision, researchers could predict what choice people would make 7-10 seconds before they themselves were even aware of having made a decision. This means that even when people think they are making a conscious, logical, decision, chances are that they aren’t aware that they’ve already made a decision and that it was unconscious. We aren’t even aware of our own process.

Do you write your messaging and content to appeal to logical thinking?

If so, it’s possible and even probable that your logical, persuasive arguments to your target audience about why they should go with the premium service, or why they should purchase a particular product may be in vain.

Be suspicious of what people say.

Another problem is that if you are diligent in your design process and ask people what factors are important to them, you might not be getting a true answer.

For example, if someone interviewed Kelly and asked her why she chooses the “Pro” level each year, it is likely that she will come up with an answer that sounds very logical (i.e. about the service, how her company uses it and so on) when the real reason she stays with “Pro” rather than the “Free” plan may be emotional (“I don’t want to have things go wrong and if I pay money things won’t go wrong”) or just habit (“It’s what we always sign up for”). What people tell you is the reason for why they do what they do may not be the actual reason.

People need to feel in order to decide.

If you can’t feel emotions, then you can’t make decisions — thanks to our ventro-medial pre-frontal cortex (or ‘vmPFC’).

The vmPFC is part of the prefrontal cortex, i.e. the front of your brain. It is important in regulating fear. Other parts of your brain (in particular the amygdala) tell you when you should be afraid and what you should be afraid of. The amygdala is where “conditioned” fear responses are born and perpetuated. The vmPFC, in contrast, has an opposite role. It mitigates conditioned fear. It stops you from continuing to be afraid in certain situations. When the vmPFC is active then you are able to let go of conditioned fears. As a result, you are then able to make a decision.

You should just assume that all decisions involve emotions. Rather than just making logical arguments to persuade, you are more likely to persuade people to take an action if you understand how they are feeling about the decision and feed their feeling. For example, if Kelly is feeling apprehensive about making a wrong decision then your messaging should be more about making her feel secure and safe than it is about product features.

People buy when they feel confident of their decision.

There is actually a neuron that fires up in the brain that triggers people to take action when the brain decides it is confident of a decision. This is subjective. It’s not necessarily based on the amount of information you’ve collected — it’s a feeling of confidence.

If you want people to take an action then you need to make them feel confident. If you want Kelly to choose the “Pro” level again, then you need to give her messaging about the “Pro” version that makes her confident of her choice. For example, feed data back to her about how much she has used the service. This will make her feel confident that she is making the correct choice.

Don’t Confuse Unconscious With Irrational Or Bad

I take exception with writers who equate unconscious decision making with making poor or irrational decisions. For example, Dan Ariely in his book, “Predictably Irrational: The Hidden Forces That Shape Our Decisions” implies that unless we work hard to prevent it, many to most of our decisions are poor and irrational.

Most of our mental processing is unconscious, and most of our decision-making is unconscious, but that doesn’t mean it’s faulty, irrational, or bad. We are faced with an overwhelming amount of data (11,000,000 pieces of data come into the brain every second according to Dr. Timothy Wilson in his book “Strangers To Ourselves: Discovering The Adaptive Unconscious”) and our conscious minds can’t process all of that.

Our unconscious has evolved to process most of the data and to make decisions for us according to guidelines and rules of thumb that are in our best interest most of the time. This is the genesis of “trusting your gut”, and most of the time it works!

People do like to think that they are being logical and thorough, however, so you may want to offer logical reasons for why a certain decision should be made so that the person making the decision has a rational reason they can give themselves and others. Go ahead and give Kelly the rational reasons she should renew for the “Pro” level, but just understand that that reason is probably not the actual reason.

Recommended reading: Grabbing Visual Attention With The Visual Cortex

Only Give More Information If People Are Making A Goal-Based Decision

There are two different types of decisions that people make. Value-based decisions are made in the orbitofrontal cortex (OFC). So, during those times when you really are comparing the Honda to the Subaru when you are shopping for a car, then you are making a value-based goal decision. If Kelly was comparing the features of the different levels for the chatbot service then she would be making a value-based goal decision.

Habit-based decisions occur in the basal ganglia (deep in the brain). When you pull your usual cereal off the shelf at the grocery store and put it in your cart, that’s a habit-based decision. If Kelly presses the ‘Renew’ button for the Chatbot software then she is making a habit-based decision.

What’s interesting is that if the OFC is quiet then the habit part of the brain takes over. This means that people are either making a goal-directed decision or a habit decision, but not both at the same time.

Structure of the human brain and location of the basal ganglia (Large preview)

If you give someone a lot of information then they will switch from habit to goal-directed. So if you want someone to make a habit decision, don’t give them too much information to review. If you want them to make a goal-directed decision then do give them information to review.

If you want Kelly to renew for the “Pro” level then don’t give her lots of data. Let her make the habit-based decision to renew. If you are hoping that she will go up a level (not down) then you may want to give her data on her options as that will kick her from a habit decision to a goal-directed decision.

Too Many Choices Means People Won’t Choose

You may have heard the idea that people can only remember, or deal with 7 plus or minus 2 things at a time (5 to 9). This actually is not true. It was a theory first mentioned by Miller in 1956 at a talk he gave at the American Psychological Association meeting. But research since then shows that 7 +- 2 is a myth. The real number is 3-4 not 5-9. Refuting research includes:

And most recently, Sheena Iyengar (author of “The Art Of Choosing”), has conducted several studies that clearly show that if you give people too many choices then they end up not choosing anything at all.

People liked having more choices to choose from but they were more satisfied with their choice when there was less to choose from.

So, if you show someone too many choices (in this case of sales/CRM services) they might not choose any and instead abandon the page.

Showing too many options can only overwhelm your users. Choose less with your goals in mind. (Large preview)

Kelly was given five choices for the Chatbot service. Three to four would have been better.

So, is there anything you can do to encourage Kelly to re-subscribe and not change her level of membership?

In this case, the decision is probably a habit-based decision. The best thing to do, then, is to not do much at all. Don’t send her an email with information on all the membership levels. Instead, give her one or two reasons why continuing with her current subscription is the way to go and leave it at that. At a different time (not when she is deciding whether to renew), you can make a pitch for a higher premium level. But if you do that pitch while she is about to renew, you may jeopardize her habit-based renewal.

Recommended reading: Don’t Let Your Brain Deceive You: Avoiding Bias In Your UX Feedback

Takeaways
  • If someone is making a habit-based decision, do not give them a lot of information.
  • Provide people with a brief, but a logical reason for their decision so they can use that to tell themselves and others why they did what they did.
  • Limit the number of choices people have to make to one, two or three. If you provide too many choices then people likely won’t choose at all.

This article is part of the UX design series sponsored by Adobe. Adobe XD tool is made for a fast and fluid UX design process, as it lets you go from idea to prototype faster. Design, prototype and share — all in one app. You can check out more inspiring projects created with Adobe XD on Behance, and also sign up for the Adobe experience design newsletter to stay updated and informed on the latest trends and insights for UX/UI design.

(cm, ms, ra, il)
Categories: Around The Web

How To Architect A Complex Web Table

Smashing Magazine - Wed, 02/06/2019 - 7:00am
How To Architect A Complex Web Table How To Architect A Complex Web Table Slava Shestopalov 2019-02-06T13:00:00+01:00 2019-02-18T13:24:09+00:00

Imagine you design a system for data researchers. Or an application for energy management. Or a dashboard for corn traders. Maybe you’re designing something like that right now. In all the mentioned cases, people will expect tables. Not those fancy ones from design inspiration sites but Excel-looking monsters with hundreds of cells and complex interaction.

In this case, a designer faces many challenges. For instance, matching design with existing frontend frameworks or struggling with “uncomfortable” data that smashes the layout. We’ll overcome these problems by means of the following steps: systematize needs, go atomic, and define interaction.

Expectation vs. Reality (Large preview) 1. Systematize Needs

So, you’ve interviewed the target audience and figured out their needs and wants. Now it’s time to piece together findings and transform them into an interface structure. For example, one user said, “I need to see how my data affects other parts of the application.” Or while watching another person work with old software you noticed he uses shortcuts and doesn’t touch a mouse at all. What does it mean?

The first user’s words are about input validation and hints. You’ll need to consider attaching alert or help information to a table. Or develop a system of meaningful colors. It depends on the domain and the mental model. The observation of the second user’s work might be a sign you need to design all actions keyboard-accessible. And you’ll probably need to think about shortcuts more profound than just “Cmd + C” and “Cmd + V”.

Here are some observation-assumption pairs.

  • I need to operate dozens of items at a time easily.”
    Allow cell multi-selecting? Add checkboxes to select many rows?
  • Now we do all the calculations this way.” [Shows Microsoft Excel]
    Is Excel effective for this purpose? What features can we borrow?
  • Can we somehow know beforehand if this name is already on the server.”
    Data validation on the fly? Error messages or auto-correction?
  • Usually I enter this information. It’s pretty generic.
    Suggest default values, defaults or templates?

Ahoy! The hunt for shiny front-end & UX treasures has begun! Meet SmashingConf San Francisco 2019

Categories: Around The Web

iOS Performance Tricks To Make Your App Feel More Performant

Smashing Magazine - Tue, 02/05/2019 - 7:00am
iOS Performance Tricks To Make Your App Feel More Performant iOS Performance Tricks To Make Your App Feel More Performant Axel Kee 2019-02-05T13:00:00+01:00 2019-02-18T13:24:09+00:00

Although modern iOS hardware is powerful enough to handle many intensive and complex tasks, the device could still feel unresponsive if you are not careful about how your app performs. In this article, we will look into five optimization tricks that will make your app feel more responsive.

1. Dequeue Reusable Cell

You’ve probably used tableView.dequeueReusableCell(withIdentifier:for:) inside tableView(_:cellForRowAt:) before. Ever wondered why you have to follow this awkward API, instead of just passing an array of cell in? Let’s go through the reasoning of this.

Say you have a table view with a thousand rows. Without using reusable cells, we would have to create a new cell for each row, like this:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { // Create a new cell whenever cellForRowAt is called. let cell = UITableViewCell() cell.textLabel?.text = "Cell \(indexPath.row)" return cell }

As you might have thought, this will add a thousand cells to the device’s memory as you scroll to the bottom. Imagine what would happen if each cell contained a UIImageView and a lot of text: Loading them all at once could cause the app to run out of memory! Apart from that, every single cell would require new memory to be allocated during scrolling. If you scroll a table view quickly, a lot of small chunks of memory will be allocated on the fly, and this process will make the UI janky!

To resolve this, Apple has provided us with the dequeueReusableCell(withIdentifier:for:) method. Cell reuse works by placing the cell that is no longer visible on the screen into a queue, and when a new cell is about to be visible on the screen (say, the subsequent cell below as the user scrolls down), the table view will retrieve a cell from this queue and modify it in the cellForRowAt indexPath: method.

How cell reuse queues work in iOS (Large preview)

By using a queue to store cells, the table view doesn’t need to create a thousand cells. Instead, it needs just enough cells to cover the area of the table view.

By using dequeueReusableCell, we can reduce the memory used by the app and make it less prone to running out of memory!

Getting workflow just right ain’t an easy task. So are proper estimates. Or alignment among different departments. That’s why we’ve set up “this-is-how-I-work”-sessions — with smart cookies sharing what works well for them. A part of the Smashing Membership, of course.

Explore Smashing Membership ↬ 2. Using A Launch Screen That Looks Like The Initial Screen

As mentioned in Apple’s Human Interface Guidelines (HIG), launch screens can be used to enhance the perception of an app’s responsiveness:

“It’s solely intended to enhance the perception of your app as quick to launch and immediately ready for use. Every app must supply a launch screen.”

It’s a common mistake to use a launch screen as a splash screen to show branding or to add a loading animation. Design the launch screen to be identical to the first screen of your app, as mentioned by Apple:

“Design a launch screen that’s nearly identical to the first screen of your app. If you include elements that look different when the app finishes launching, people can experience an unpleasant flash between the launch screen and the first screen of the app.

“The launch screen isn’t a branding opportunity. Don’t design an entry experience that looks like a splash screen or an "About" window. Don’t include logos or other branding elements unless they’re a static part of your app’s first screen.”

Using a launch screen for loading or branding purposes could slow down the time of first use and make the user feel that the app is sluggish.

When you start a new iOS project, a blank LaunchScreen.storyboard will be created. This screen will be shown to the user while the app loads the view controllers and layout.

To make your app feel faster, you can design the launch screen to be similar to the first screen (view controller) that will be shown to the user.

For example, the Safari app’s launch screen is similar to its first view :

A comparison of launch screen and first view of Safari app (Large preview)

The launch screen storyboard is like any other storyboard file, except that you can only use the standard UIKit classes, like UIViewController, UITabBarController, and UINavigationController. If you attempt to use any other custom subclasses (such as UserViewController), Xcode will notify you that using custom class names is prohibited.

Launch screen storyboard cannot contain non-UIKit standard class. (Large preview)

Another thing to note is that UIActivityIndicatorView doesn’t animate when placed on the launch screen, because iOS will generate a static image from the launch screen storyboard and displays it to the user. (This is mentioned briefly in the WWDC 2014 presentation “Platforms State of the Union”, around 01:21:56.)

Apple’s HIG also advises us not to include text on our launch screen, because the launch screen is static, and you can’t localize text to cater to different languages.

Recommended reading: Mobile App With Facial Recognition Feature: How To Make It Real

3. State Restoration For View Controllers

State preservation and restoration allow the user to return to the exact same UI state from just before they left the app. Sometimes, due to insufficient memory, the operating system might need to remove your app from memory while the app is in the background, and the app might lose track of its last UI state if it is not preserved, possibly causing users to lose their work in progress!

In the multitasking screen, we can see a list of apps that have been put in the background. We might assume that these apps are still running in the background; in reality, some of these apps might get killed and restarted by the system due to the demands of memory. The app snapshots we see in the multitasking view are actually screenshots taken by the system from right when we exited the app (i.e. to go to the home or multitasking screen).

Screenshots of apps taken by iOS when user exits the app (Large preview)

iOS uses these screenshots to give the illusion that the app is still running or is still displaying this particular view, whereas the app might have been already terminated or restarted in the background while still displaying the same screenshot.

Have you ever experienced, upon resuming an app from the multitasking screen, that the app shows a user interface different from the snapshot shown in the multitasking view? This is because the app hasn’t implemented the state-restoration mechanism, and the displayed data was lost when the app was killed in the background. This can lead to a bad experience because the user expects your app to be in the same state as when they left it.

From Apple’s article:

“They expect your app to be in the same state as when they left it. State preservation and restoration ensures that your app returns to its previous state when it launches again.”

UIKit does a lot of work to simplify state preservation and restoration for us: It handles the saving and loading of an app’s state automatically at appropriate times. All we need to do is add some configuration to tell the app to support state preservation and restoration and to tell the app what data needs to be preserved.

To enable state saving and restoring, we can implement these two methods in AppDelegate.swift:

func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool { return true } func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool { return true }

This will tell the app to save and restore the application’s state automatically.

Next, we’ll tell the app which view controllers need to be preserved. We do this by specifying the “Restoration ID” in the storyboard :

Setting restoration ID in storyboard (Large preview)

You can also check “Use Storyboard ID” to use the storyboard ID as the restoration ID.

To set the restoration ID in the code, we can use the restorationIdentifier property of the view controller.

// ViewController.swift self.restorationIdentifier = "MainVC"

During state preservation, any view controller or view that has been assigned a restoration identifier will have its state saved to disk.

Restoration identifiers can be grouped together to form a restoration path. The identifiers are grouped using the view hierarchy, from the root view controller to the current active view controller. Suppose a MyViewController is embedded in a navigation controller, which is embedded in another tab bar controller. Assuming they are using their own class names as restoration identifiers, the restoration path will look like this:

TabBarController/NavigationController/MyViewController

When the user leaves the app with the MyViewController being the active view controller, this path will be saved by the app; then the app will remember the previous view hierarchy shown (Tab Bar ControllerNavigation ControllerMy View Controller).

After assigning the restoration identifier, we will need to implement the encodeRestorableState(with coder:) and decodeRestorableState(with coder:) methods for each of the preserved view controllers. These two methods let us specify what data need to be saved or loaded and how to encode or decode them.

Let’s see the view controller:

// MyViewController.swift ​ // MARK: State restoration // UIViewController already conforms to UIStateRestoring protocol by default extension MyViewController { // will be called during state preservation override func encodeRestorableState(with coder: NSCoder) { // encode the data you want to save during state preservation coder.encode(self.username, forKey: "username") super.encodeRestorableState(with: coder) } // will be called during state restoration override func decodeRestorableState(with coder: NSCoder) { // decode the data saved and load it during state restoration if let restoredUsername = coder.decodeObject(forKey: "username") as? String { self.username = restoredUsername } super.decodeRestorableState(with: coder) } }

Remember to call the superclass implementation at the bottom of your own method. This ensures that the parent class has a chance to save and restore state.

Once the objects have finished decoding, applicationFinishedRestoringState() will be called to tell the view controller that the state has been restored. We can update the UI for the view controller in this method.

// MyViewController.swift ​ // MARK: State restoration // UIViewController already conforms to UIStateRestoring protocol by default extension MyViewController { ... override func applicationFinishedRestoringState() { // update the UI here self.usernameLabel.text = self.username } }

There you have it! These are the essential methods to implement state preservation and restoration for your app. Keep in mind that the operating system will remove the saved state when the app is being force-closed by the user, in order to avoid getting stuck in a broken state in case something goes wrong in the state preservation and restoration.

Also, don’t store any model data (i.e. data that should have been saved to UserDefaults or Core Data) to the state, even though it might seem convenient to do so. State data will be removed when the user force quits your app, and you certainly don’t want to lose model data this way.

To test whether state preservation and restoration are working well, follow the steps below:

  1. Build and launch an app using Xcode.
  2. Navigate to the screen with state preservation and restoration that you want to test.
  3. Return to the home screen (by swiping up or double-clicking home button, or pressing Shift ⇧ + Cmd ⌘ + H in the simulator) to send the app to the background.
  4. Stop the app in Xcode by pressing the ⏹ button.
  5. Launch the app again and check whether the state has been restored successfully.

Because this section only covers the basics of state preservation and restoration, I recommend the following articles by Apple Inc. for more in-depth knowledge of state restoration:

  1. Preserving And Restoring State
  2. UI Preservation Process
  3. UI Restoration Process
4. Reduce Usage Of Non-Opaque Views As Much As Possible

An opaque view is a view that has no transparency, meaning that any UI element placed behind it is not visible at all. We can set a view to be opaque in the Interface Builder:

Set UIView to opaque in storyboard (Large preview)

Or we can do it programmatically with the isOpaque property of UIView:

view.isOpaque = true

Setting a view to opaque will make the drawing system optimize some drawing performance while rendering the screen.

If a view has transparency (i.e. alpha is below 1.0), then iOS will have to do extra work to calculate what should be displayed by blending different layers of views in the view hierarchy. On the other hand, if a view is set to opaque, then the drawing system will just put this view in front and avoid the extra work of blending the multiple view layers behind it.

You can check which layers are being blended (non-opaque) in the iOS Simulator by checking DebugColor Blended Layers.

Show color blended layers in Simulator

After checking the Color Blended Layers option, you can see that some views are red and some are green. Red indicates that the view is not opaque and that its output display is a result of layers blended behind it. Green indicates that the view is opaque and no blending has been done.

Assign non-transparent background color to UILabel whenever possible to reduce color blended layers. (Large preview)

The labels shown above (“View Friends”, etc.) are highlighted in red because when a label is dragged to the storyboard, its background color is set to transparent by default. When the drawing system is compositing the display near the label area, it will ask for the layer behind the label and do some calculation.

One way you can optimize app performance is to reduce how many views are highlighted with red as much as possible.

By changing label.backgroundColor = UIColor.clear to label.backgroundColor = UIColor.white, we can reduce layer blending between the label and the view layer behind it.

Many labels are highlighted in red because their background color is transparent, causing iOS to calculate the background color by blending the view behind it. (Large preview)

You might have noticed that, even if you have set a UIImageView to opaque and assigned a background color to it, the simulator will still show red in the image view. This is probably because the image you used for the image view has an alpha channel.

To remove the alpha channel for an image, you can use the Preview app to make a duplicate of the image (Shift ⇧ + Cmd ⌘ + S), and uncheck the “Alpha” checkbox when saving.

Uncheck the ‘Alpha’ checkbox when saving an image to discard the alpha channel. (Large preview) 5. Pass Heavy Processing Functions To Background Threads (GCD)

Because UIKit only works on the main thread, performing heavy processing on the main thread will slow down the UI. The main thread is used by UIKit not only to handle and respond to user input, and also to draw the screen.

The key to making an app responsive is to move as many heavy processing tasks to background threads as possible. Avoid doing complex calculation, networking, and heavy IO operation (e.g. reading and writing to disk) on the main thread.

You might have once used an app that suddenly became unresponsive to your touch input, and it feels like the app has hung. This is most probably caused by the app running heavy computation tasks on the main thread.

The main thread usually alternates between UIKit tasks (such as handling user input) and some light tasks in small intervals. If a heavy task is running on main thread, then UIKit will need to wait until the heavy task has finished before being able to handle touch input.

Here is how the main thread handles UI tasks and why it causes the UI to hang when heavy tasks are performed. (Large preview)

By default, the code inside view controller lifecycle methods (such as viewDidLoad) and IBOutlet functions are executed on the main thread. To move heavy processing tasks to a background thread, we can use the Grand Central Dispatch queues provided by Apple.

Here’s the template for switching queues :

// Switch to background thread to perform heavy task. DispatchQueue.global(qos: .default).async { // Perform heavy task here. // Switch back to main thread to perform UI-related task. DispatchQueue.main.async { // Update UI. } }

The qos stands for “quality of service”. Different quality-of-service values indicate different priorities for the specified tasks. The operating system will allocate more CPU time and CPU power I/O throughput for tasks allocated in queues with higher QoS values, meaning that a task will finish faster in a queue with higher QoS values. A higher QoS value will also consume more energy due to it using more resources.

Here is the list of QoS values from highest to lowest priority:

Quality-of-service values of queue sorted by performance and energy efficiency (Large preview)

Apple has provided a handy table with examples of which QoS values to use for different tasks.

One thing to keep in mind is that all UIKit code should always be executed on the main thread. Modifying UIKit objects (such as UILabel and UIImageView) on the background thread could have an unintended consequence, like the UI not actually updating, a crash occurring, and so on.

From Apple’s article:

“Updating UI on a thread other than the main thread is a common mistake that can result in missed UI updates, visual defects, data corruptions, and crashes.”

I recommend watching Apple’s WWDC 2012 video on UI concurrency to better understand how to build a responsive app.

Notes

The trade-off of performance optimization is that you have to write more code or configure additional settings on top of the app’s functionality. This might make your app delivered later than expected, and you will have more code to maintain in the future, and more code means potentially more bugs.

Before spending time on optimizing your app, ask yourself whether the app is already smooth or whether it has some unresponsive part that really needs to be optimized. Spending a lot of time optimizing an already smooth app to shave off 0.01 seconds might not be worth it, as the time could be better spent developing better features or other priorities.

Further Resources (jd, ra, il)
Categories: Around The Web

A Guide To CSS Support In Browsers

Smashing Magazine - Mon, 02/04/2019 - 6:00am
A Guide To CSS Support In Browsers A Guide To CSS Support In Browsers Rachel Andrew 2019-02-04T12:00:00+01:00 2019-02-18T13:24:09+00:00

We will never live in a world where everyone viewing our sites has an identical browser and browser version, just as we will never live in a world where everyone has the same size screen and resolution. This means that dealing with old browsers — or browsers which do not support something that we want to use — is part of the job of a web developer. That said, things are far better now than in the past, and in this article, I’m going to have a look at the different types of browser support issues we might run into. I’m going to show you some ways to deal with them, and also look at things which might be coming soon which can help.

Why Do We Have These Differences?

Even in a world where the majority of browsers are Chromium-based, those browsers are not all running the same version of Chromium as Google Chrome. This means that a Chromium-based browser such as Vivaldi, might be a few versions behind Google Chrome.

And, of course, users do not always quickly update their browsers, although that situation has improved in recent years with most browsers silently upgrading themselves.

There is also the manner in which new features get into browsers in the first place. It is not the case that new features for CSS are designed by the CSS Working Group, and a complete spec handed down to browser vendors with an instruction to implement it. Quite often it is only when an experimental implementation happens, that all the finer details of the specification can be worked out. Therefore, feature development is an iterative process and requires that browsers implement these specifications in development. While implementation happens these days most often behind a flag in the browser or available only in a Nightly or preview version, once a browser has a complete feature, it is likely to switch it on for everyone even if no other browser yet has support.

All this means that — as much as we might like it — we will never exist in a world where features are magically available on every desktop and phone simultaneously. If you are a professional web developer then your job is to deal with that fact.

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬ Bugs vs. Lack Of Support

There are three issues that we face with regard to browser support:

  1. No Support Of A Feature
    The first issue (and easiest to deal with) is when a browser does not support the feature at all.
  2. Dealing With Browser “Bugs”
    The second is when the browser claims to support the feature, but does so in a way that is different to the way that other browsers support the feature. Such an issue is what we tend to refer to as a “browser bug” because the end result is inconsistent behavior.
  3. Partial Support Of CSS Properties
    This one is becoming more common; a situation in which a browser supports a feature — but only in one context.

It’s helpful to understand what you are dealing with when you see a difference between browsers, so let’s have a look at each of these issues in turn.

1. No Support Of A Feature

If you use a CSS property or value that a browser does not understand, the browser will ignore it. This is the same whether you use a feature that is unsupported, or make up a feature and try to use it. If the browser does not understand that line of CSS, it just skips it and gets on with the next thing it does understand.

This design principle of CSS means that you can cheerfully use new features, in the knowledge that nothing bad will happen to a browser that doesn’t have support. For some CSS, used purely as an enhancement, that is all you need to do. Use the feature, make sure that when that feature is not available the experience is still good, and that’s it. This approach is the basic idea behind progressive enhancement, using this feature of the platform which enables the safe use of new things in browsers which don’t understand them.

If you want to check whether a feature you are using is supported by browsers then you can look at the Can I Use website. Another good place to look for fine-grained support information is the page for each CSS property on MDN. The browser support data there tends to be very detailed.

New CSS Understands Old CSS

As new CSS features are developed, care is taken in terms of how they interact with existing CSS. For example, in the Grid and Flexbox specification, it is detailed in terms of how display: grid and display: flex deal with scenarios such as when a floated item becomes a grid item, or a multicol container is turned into a grid. This means that certain behaviors are ignored, helping you to simply overwrite the CSS for the nonsupporting browser. These overrides are detailed in the page for Progressive enhancement and Grid Layout on MDN.

Detecting Support With Feature Queries

The above method only works if the CSS you need to use does not need other properties to go along with it. You might need to add additional properties to your CSS for older browsers which would then also be interpreted by the browsers which support the feature too.

A good example of this can be found when using Grid Layout. While a floated item which becomes a grid item loses all float behavior, it is likely that if you are trying to create a fallback for a grid layout with float, you will have added percentage widths and possibly margins to the items.

.grid > .item { width: 23%; margin: 0 1%; } Using floats we can create a four column layout, widths and margins need to be set in %. (Large preview)

These widths and margins will then still apply when the floated item is a grid item. The width becomes a percentage of the grid track rather than the full width of the container; any margin will then be applied as well as a gap you may have specified.

.grid > .item { width: 23%; margin: 0 1%; } .grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; column-gap: 1%; } The width is now a percentage of the grid track — not the container. (Large preview)

Thankfully, there is a feature built into CSS and implemented into modern browsers which helps us deal with this situation. Feature Queries allow us to directly ask the browser what they support and then act on the response. Just like a Media Query — which tests for some properties of the device or screen — Feature Queries test for support of a CSS property and value.

Test For Support

Testing for support is the simplest case, we use @supports and then test for a CSS property and value. The content inside the Feature Query will only run if the browser responds with true, i.e. it does support the feature.

Test For No Support

You can ask the browser if it does not support a feature. In this case, the code inside the Feature Query will only run if the browser indicates it has no support.

@supports not (display: grid) { .item { /* CSS from browsers which do not support grid layout */ } } Test For Multiple Things

If you need more than one property to be supported, use and.

@supports (display: grid) and (shape-outside: circle()){ .item { /* CSS from browsers which support grid and CSS shapes */ } }

If you need support of one property or another, use or.

@supports (display: grid) or (display: flex){ .item { /* CSS from browsers which support grid or flexbox */ } } Picking A Property And Value To Test For

You don’t need to test for every property you want to use — just something which would indicate support for the features you are planning to use. Therefore, if you want to use Grid Layout, you might test for display: grid. In the future (and once subgrid support lands in browsers), you might need to be more specific and test for subgrid functionality. In that case, you would test for grid-template-columns: subgrid to get a true response from only those browsers which had implemented subgrid support.

If we now return to our floated fallback example, we can see how feature queries will sort it out for us. What we need to do is to query the browser to find out if it supports grid layout. If it does, we can set the width on the item back to auto and the margin to 0.

.grid > .item { width: 23%; margin: 0 1%; } @supports(display: grid) { .grid { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; column-gap: 1%; } .grid > .item { width: auto; margin: 0; } }

See the Pen Feature Queries and Grid by (Rachel Andrew) on CodePen.

Note that while I have included all of the grid code inside my feature query, I don’t need to. If a browser didn’t understand the grid properties it would ignore them so they could safely be outside of the feature query. The things that must be inside a feature query in this example are the margin and width properties, as these are needed for the old browser code but would also be applied by supporting browsers.

Embrace The Cascade

A very simple way to offer fallbacks is to utilize the fact that browsers ignore CSS that they don’t understand, and the fact that where everything else has equal specificity, source order is taken into account in terms of which CSS is applied to an element.

You first write your CSS for browsers which do not support the feature. Then test for support of property you want to use, if the browser confirms it has support overwrite the fallback code with your new code.

This is pretty much the same procedure that you might use when using media queries for responsive design, following a mobile-first approach. In that approach, you start with your layout for smaller screens, then add or overwrite things for larger ones as you move up through your breakpoints.

Can I Use CSS Feature Queries? Data on support for CSS Feature Queries across the major browsers from caniuse.com.

The above way of working means that you do not need to worry about browsers which do not support Feature Queries. As you can see from Can I Use, Feature Queries have really great support. The standout browsers that do not support them being any version of Internet Explorer.

It is likely, however, that the new feature you want to use is also not supported in IE. So, at the present time you will almost always start by writing CSS for browsers without support, then you test with a Feature Query. This Feature Query should test for support.

  1. Browsers which support Feature Queries will return true if they have support and so the code inside the query will be used, overwriting the code for older browsers.
  2. If the browser supports Feature Queries but not the feature being tested, it will return false. The code inside the feature query will be ignored.
  3. If the browser does not support Feature Queries then everything inside the Feature Query block will be ignored, which means that a browser such as IE11 will use your old browser code, which is very likely exactly what you want!
2. Dealing With Browser “Bugs”

The second browser support issue is thankfully becoming less common. If you read “What We Wished For” (published at the end of last year), you can get a little tour into some of the more baffling browser bugs of the past. That said, any software is liable to have bugs, browsers are no exception. And, if we add to that the fact that due to the circular nature of specification implementation, sometimes a browser implemented something and then the spec changed so they now need to issue an update. Until that update ships, we might be in a situation where browsers do something different to each other.

Feature Queries can’t help us if the browser reports support of something supports it badly. There is no mode by which the browser can say, “Yes, but you probably won’t like it.” When an actual interoperability bug shows up, it is in these situations where you might need to be a little more creative.

If you think you are seeing a bug then the first thing to do is confirm that. Sometimes when we think we see buggy behavior, and browsers doing different things, the fault lies with us. Perhaps we have used some invalid syntax, or are trying to style malformed HTML. In those cases, the browser will try to do something; however, because you aren’t using the languages as they were designed, each browser might cope in a different way. A quick check that your HTML and CSS is valid is an excellent first step.

At that point, I’d probably do a quick search and see if my issue was already widely understood. There are some repos of known issues, e.g. Flexbugs and Gridbugs. However, even just a well-chosen few keywords can turn up Stack Overflow posts or articles that cover the subject and may hand you a workaround.

But let’s say you don’t really know what is causing the bug, which makes it pretty hard to search for a solution. So, the next step is to create a reduced test case of your issue, i.e. stripping out anything irrelevant to help you identify exactly what triggers the bug. If you think you have a CSS bug, can you remove any JavaScript, or recreate the same styling outside of a framework? I often use CodePen to pop together a reduced test case of something I am seeing; this has the added advantage of giving me the code in a way I can easily share with someone else if I need to ask about it.

Most of the time, once you have isolated the issue, it is possible to think up an alternate way of achieving your desired result. You will find that someone else has come up with a cunning workaround, or you can post somewhere to ask for suggestions.

With that said, if you think you have a browser bug and can’t find anyone else talking about the same issue, it is quite possible you have found something new that should be reported. With all of the new CSS that has landed recently, issues can sometimes show up as people start to use things in combination with other parts of CSS.

Check out this post from Lea Verou about reporting such issues, “Help The Community! Report Browser Bugs!”. The article also has great tips for creating a reduced test case.

3. Partial Support Of CSS Properties

The third type of issue has become more common due to the way that modern CSS specifications are designed. If we think about Grid Layout and Flexbox, these specs both use the properties and values in Box Alignment Level 3, to do alignment. Therefore, properties such as align-items, justify-content, and column-gap are specified to be used in both Grid and Flexbox as well as other layout methods.

At the time of writing, however, the gap properties work in Grid Layout in all grid-supporting browsers, and column-gap works in Multicol; however, only Firefox has implemented these properties for Flexbox.

If I were to use margins to create a fallback for Flexbox, then test for column-gap and remove the margins, my boxes will have no space between them in browsers which support column-gap in Grid or multicol, so my fallback spacing will be removed.

@supports(column-gap: 20px) { .flex { margin: 0; /* almost everything supports column-gap so this will always remove the margins, even if we do not have gap support in flexbox. */ } }

This is a current limitation of Feature Queries. We don’t have a way to test for support of a feature in another feature. In the above situation, what I want to ask the browser is, “Do you have support for column-gap in Flexbox?” This way, I can get a negative response so I can use my fallback.

There is a similar issue with the CSS fragmentation properties break-before, break-after, and break-inside. As these have better support when the page is printed, browsers will often claim support. However, if you are testing for support in multicol, you get what appear to be false positives. I’ve raised an issue over at the CSS Working Group for this issue, however, it isn’t a straightforward problem to solve. If you have thoughts, please do add them there.

Testing For Selector Support

Currently, Feature Queries can only test for CSS Properties and Values. Another thing we might like to test for is the support of newer selectors, such as those in Level 4 of the Selectors specification. There is an explainer note and also an implementation behind a flag in Firefox Nightly of a new feature for Feature Queries which will achieve this.

If you visit about:config in Firefox and enable the flag layout.css.supports-selector.enabled then you can test to see if various selectors are supported. The syntax is currently very straightforward, for example to test for the :has selector:

@supports selector(:has){ .item { /* CSS for support of :has */ } }

This is a specification in development, however, you can see how features to help us manage the ever-present issues of browser support are being added as we speak.

Further Reading

It can seem frustrating when you want to use a feature and discover that it isn’t supported by one major browser, or if things seem to be behaving in different ways. I’ve rounded up some practical further reading that might help.

(il)
Categories: Around The Web

Using Vue.js To Create An Interactive Weather Dashboard With APIs

Smashing Magazine - Fri, 02/01/2019 - 7:00am
Using Vue.js To Create An Interactive Weather Dashboard With APIs Using Vue.js To Create An Interactive Weather Dashboard With APIs Souvik Sarkar 2019-02-01T13:00:18+01:00 2019-02-18T13:24:09+00:00

(This is a sponsored article.) In this tutorial, you will build a simple weather dashboard from scratch. It will be a client-end application that is neither a “Hello World” example, nor too intimidating in its size and complexity.

The entire project will be developed using tools from the Node.js + npm ecosystem. In particular, we will be heavily relying on the Dark Sky API for the data, Vue.js for all the heavy lifting, and FusionCharts for data visualization.

Prerequisites

We expect that you are familiar with the following:

  • HTML5 and CSS3 (we will also be using the basic features provided by Bootstrap;
  • JavaScript (especially ES6 way of using the language);
  • Node.js and npm (the basics of the environment and package management is just fine).

Apart from the ones mentioned above, it would be great if you have familiarity with Vue.js, or any other similar JavaScript framework. We don’t expect you to know about FusionCharts — it’s so easy to use that you will learn it on the fly!

Expected Learnings

Your key learnings from this project will be:

  1. How to plan about implementing a good dashboard
  2. How to develop applications with Vue.js
  3. How to create data-driven applications
  4. How to visualize data using FusionCharts

In particular, each of the sections take you a step closer to the learning goals:

  1. An Introduction To The Weather Dashboard
    This chapter gives you an overview of different aspects of the undertaking.
  2. Create The Project
    In this section, you learn about creating a project from scratch using the Vue command-line tool.
  3. Customize The Default Project Structure
    The default project scaffolding that you get in the previous section is not enough; here you learn the additional stuff needed for the project from a structural point of view.
  4. Data Acquisition And Processing
    This section is the meat of the project; all the critical code for acquiring and processing data from the API is showcased here. Expect to spend maximum time on this section.
  5. Data Visualization With FusionCharts
    Once we have all the data and other moving parts of the project stabilized, this section is dedicated towards visualizing the data using FusionCharts and a bit of CSS.
1. The Dashboard Workflow

Before we dive into the implementation, it is important to be clear about our plan. We break our plan into four distinct aspects:

Requirements

What are our requirements for this project? In other words, what are the things that we want to showcase through our Weather Dashboard? Keeping in mind that our intended audience are probably mere mortals with simple tastes, we would like to show them the following:

  • Details of the location for which they want to see the weather, along with some primary information about the weather. Since there are no stringent requirements, we will figure out the boring details later. However, at this stage, it is important to note that we will have to provide the audience a search box, so that they can provide input for the location of their interest.
  • Graphical information about the weather of their location of interest, such as:
    • Temperature variation for the day of query
    • Highlights of today’s weather:
      • Wind Speed and Direction
      • Visibility
      • UV Index

Note: The data obtained from the API provides information regarding many other aspects of the weather. We choose not to use all of them for the sake of keeping the code to a minimum.

Structure

Based on the requirements, we can structure our dashboard as shown below:

(Large preview) Data

Our dashboard is as good as the data we get, because there will be no pretty visualizations without proper data. There are plenty of public APIs that provide weather data — some of them are free, and some are not. For our project, we will collect data from the Dark Sky API. However, we will not be able to poll the API endpoint from the client end directly. Don’t worry, we have a workaround that will be revealed just at the right time! Once we get the data for the searched location, we will do some data processing and formatting — you know, the type of technicalities that helps us pay the bills.

Visualization

Once we get clean and formatted data, we plug it in to FusionCharts. There are very few JavaScript libraries in the world as capable as FusionCharts. Out of the vast number of offerings from FusionCharts, we will use only a few — all written in JavaScript, but works seamlessly when integrated with the Vue wrapper for FusionCharts.

Armed with the bigger picture, let’s get our hands dirty — it’s time to make things concrete! In the next section, you will create the basic Vue project, on top of which we will build further.

2. Creating The Project

To create the project, execute the following steps:

  1. Install Node.js + npm
    (If you have Node.js installed on your computer, skip this step.)
    Node.js comes with npm bundled with it, so you don’t need to install npm separately. Depending on the operating system, download and install Node.js according to the instructions given here.

    Once installed, it’s probably a good idea to verify if the software is working correctly, and what are their versions. To test that, open the command-line/terminal and execute the following commands:
    node --version npm --version
  2. Install packages with npm
    Once you have npm up and running, execute the following command to install the basic packages necessary for our project.
    npm install -g vue@2 vue-cli@2
  3. Initialize project scaffolding with vue-cli
    Assuming that the previous step has gone all well, the next step is to use the vue-cli — a command-line tool from Vue.js, to initialize the project. To do that, execute the following:
    • Initialize the scaffolding with webpack-simple template.
      vue init webpack-simple vue_weather_dashboard You will be asked a bunch of questions — accepting the defaults for all but the last question will be good enough for this project; answer N for the last one. (Large preview) Keep in mind that although webpack-simple is excellent for quick prototyping and light application like ours, it is not particularly suited for serious applications or production deployment. If you want to use any other template (although we would advise against it if you are a newbie), or would like to name your project something else, the syntax is: vue init [template-name] [project-name]
    • Navigate to the directory created by vue-cli for the project.
      cd vue_weather_dashboard
    • Install all the packages mentioned in the package.json, which has been created by the vue-cli tool for the webpack-simple template. npm install
    • Start the development server and see your default Vue project working in the browser!
      npm run dev

If you are new to Vue.js, take a moment to savor your latest achievement — you have created a small Vue application and its running at localhost:8080!

(Large preview) Brief Explanation Of The Default Project Structure

It’s time to take a look at the structure inside the directory vue_weather_dashboard, so that you have an understanding of the basics before we start modifying it.

The structure looks something like this:

vue_weather_dashboard |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src | |--- App.vue | |--- assets | | |--- logo.png | |--- main.js

Although it might be tempting to skip getting familiar with the default files and directories, if you are new to Vue, we strongly recommend at least taking a look at the contents of the files. It can be a good educational session and trigger questions that you should pursue on your own, especially the following files:

  • package.json, and just a glance at its cousin package-lock.json
  • webpack.config.js
  • index.html
  • src/main.js
  • src/App.vue

A brief explanation of each of the files and directories shown in the tree diagram are given below:

  • README.md
    No prize for guessing — it is primarily for humans to read and understand the steps necessary for creating the project scaffolding.
  • node_modules/
    This is the directory where npm downloads the packages necessary for kickstarting the project. The information about the packages necessary are available in the package.json file.
  • package.json
    This file is created by the vue-cli tool based on the requirements of the webpack-simple template, and contains information about the npm packages (including with their versions and other details) that must be installed. Take a hard look at the content of this file — this is where you should visit and perhaps edit to add/delete packages necessary for the project, and then run npm install. Read more about package.json here.
  • package-lock.json
    This file is created by npm itself, and is primarily meant for keeping a log of things that npm downloaded and installed.
  • webpack.config.js
    This a JavaScript file that contains the configuration of webpack — a tool that bundles different aspects of our project together (code, static assets, configuration, environments, mode of use, etc.), and minifies before serving it to the user. The benefit is that all things are tied together automatically, and the user experience enhances greatly because of the improvement in the application’s performance (pages are served quickly and loads faster on the browser). As you might encounter later, this is the file that needs to be inspected when something in the build system does not works the way it is intended to be. Also, when you want to deploy the application, this is one of the key files that needs to be edited (read more here).
  • index.html
    This HTML file serves as the matrix (or you can say, template) where data and code is to be embedded dynamically (that’s what Vue primarily does), and then served to the user.
  • src/main.js
    This JavaScript file contains code that primarily manages top/project level dependencies, and defines the topmost level Vue component. In short, it orchestrates the JavaScript for the entire project, and serves as the entry point of the application. Edit this file when you need to declare project-wide dependencies on certain node modules, or you want something to be changed about the topmost Vue component in the project.
  • src/App.vue
    In the previous point, when we were talking about the “topmost Vue component”, we were essentially talking about this file. Each .vue file in the project is a component, and components are hierarchically related. At the start, we have only one .vue file, i.e. App.vue, as our only component. But shortly we will add more components to our project (primarily following the structure of the dashboard), and link them in accordance to our desired hierarchy, with App.vue being the ancestor of all. These .vue files will contain code in a format that Vue wants us to write. Don’t worry, they are JavaScript code written maintaining a structure that can keep us sane and organized. You have been warned — by the end of this project, if you are new to Vue, you may get addicted to the template &mdash; script &mdash; style way of organizing code!

Now that we have created the foundation, it’s time to:

  • Modify the templates and tweak the configuration files a bit, so that the project behaves just the way we want.
  • Create new .vue files, and implement the dashboard structure with Vue code.

We will learn them in the next section, which is going to be a bit long and demands some attention. If you need caffeine or water, or want to discharge — now is the time!


3. Customizing The Default Project Structure

It’s time to tinker with the foundation that the scaffolded project has given us. Before you start, ensure that the development server provided by webpack is running. The advantage of running this server continuously is that any changes you make in the source code — one you save it and refresh the web page — it gets immediately reflected on the browser.

If you want to start the development server, just execute the following command from the terminal (assuming your current directory is the project directory):

npm run dev

In the following sections, we will modify some of the existing files, and add some new files. It will be followed by brief explanations of the content of those files, so that you have an idea of what those changes are meant to do.

Modify Existing Files index.html

Our application is literally a single page application, because there is just one webpage that gets displayed on the browser. We will talk about this later, but first let’s just make our first change — altering the text within the <title> tag.

With this small revision, the HTML file looks like the following:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- Modify the text of the title tag below --> <title>Vue Weather Dashboard</title> </head> <body> <div id="app"></div> <script src="/dist/build.js"></script> </body> </html>

Take a moment to refresh the webpage at localhost:8080, and see the change reflected on the title bar of the tab on the browser — it should say “Vue Weather Dashboard”. However, this was just to demonstrate you the process of making changes and verifying if it’s working. We have more things to do!

This simple HTML page lacks many things that we want in our project, especially the following:

  • Some meta information
  • CDN links to Bootstrap (CSS framework)
  • link to custom stylesheet (yet to be added in the project)
  • Pointers to the Google Maps Geolocation API from <script> tag

After adding those things, the final index.html has the following content:

<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="src/css/style.css"> <title>Weather Dashboard</title> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC-lCjpg1xbw-nsCc11Si8Ldg2LKYizqI4&libraries=places"></script> </head> <body> <div id="app"></div> <script src="/dist/build.js"></script> </body> </html>

Save the file, and refresh the webpage. You might have noticed a slight bump while the page was getting loaded — it is primarily due to the fact that the page style is now being controlled by Bootstrap, and the style elements like fonts, spacing, etc. are different from the default we had earlier (if you are not sure, roll back to the default and see the difference).

(Large preview)

Note: One important thing before we move on — the URL for the Google Maps API contains a key which is a property of FusionCharts. For now, you can use this key to build the project, as we don’t want you to get bogged down by these type of minute details (which can be distractions while you are new). However, we strongly urge you to generate and use your own Google Maps API key once you have made some progress and feel comfortable to pay attention to these tiny details.

package.json

At the time of writing this, we used certain versions of the npm packages for our project, and we know for sure that those things work together. However, by the time you are executing the project, it is very much possible that the latest stable versions of the packages that npm downloads for you are not the same as we used, and this might break the code (or do things that are beyond our control). Thus, it is very important to have the exact same package.json file that was used to build this project, so that our code/explanations and the results you get are consistent.

The content of the package.json file should be:

{ "name": "vue_weather_dashboard", "description": "A Vue.js project", "version": "1.0.0", "author": "FusionCharts", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "axios": "^0.18.0", "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", "fusioncharts": "^3.13.3", "moment": "^2.22.2", "moment-timezone": "^0.5.21", "vue": "^2.5.11", "vue-fusioncharts": "^2.0.4" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" } }

We encourage you to go through the new package.json, and figure out what are functions of different objects in the json. You may prefer changing the value of the “author” key to your name. Also, the packages mentioned in the dependencies will reveal themselves at the right time in the code. For the time being, it’s sufficient to know that:

  • babel-related packages are for properly handling the ES6 style code by the browser;
  • axios deals with Promise-based HTTP requests;
  • moment and moment-timezone are for date/time manipulation;
  • fusioncharts and vue-fusioncharts are responsible for rendering charts:
  • vue, for obvious reasons.
webpack.config.js

As with package.json, we suggest you to maintain a webpack.config.js file that is consistent with the one we used for building the project. However, before making any changes, we recommend you to carefully compare the default code in the webpack.config.js, and the code we have provided below. You will notice quite a few differences — google them and have a basic idea of what they mean. Since explaining webpack configurations in depth is out of the scope of this article, you are on your own in this regard.

The customized webpack.config.js file is as follows:

var path = require('path') var webpack = require('webpack') module.exports = { entry: ['babel-polyfill', './src/main.js'], output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true, host: '0.0.0.0', port: 8080 }, performance: { hints: false }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }

With changes made to the project’s webpack.config.js, it’s imperative that you stop the development server which is running (Ctrl + C), and restart it with the following command executed from the project’s directory after installing all the packages mentioned in the package.json file:

npm install npm run dev

With this, the ordeal of tweaking the configurations and ensuring that the right packages are in place ends. However, this also marks the journey of modifying and writing code, which is a bit long but also very rewarding!

src/main.js

This file is the key to top-level orchestration of the project — it is here that we define:

  • What the top level dependencies are (where to get the most important npm packages necessary);
  • How to resolve the dependencies, along with instructions to Vue on using plugins/wrappers, if any;
  • A Vue instance that manages the topmost component in the project: src/App.vue (the nodal .vue file).

In line with our goals for the src/main.js file, the code should be:

// Import the dependencies and necessary modules import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; // Resolve the dependencies Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); // Globally register the components for project-wide use Vue.use(VueFusionCharts, FusionCharts); // Instantiate the Vue instance that controls the application new Vue({ el: '#app', render: h => h(App) }) src/App.vue

This is one of the most important files in the entire project, and represents the topmost component in the hierarchy — the entire application itself, as a whole. For our project, this component will do all the heavy lifting, which we will explore later. For now, we want to get rid of the default boilerplate, and put something of our own.

If you are new to Vue’s way of organizing code, it would be better to get an idea of the general structure within the .vue files. The .vue files comprises of three sections:

  • Template
    This is where the HTML template for the page is defined. Apart from the static HTML, this section also contains Vue’s way of embedding dynamic content, using the double curly braces {{ }}.
  • Script
    JavaScript rules this section, and is responsible for generating dynamic content that goes and sits within the HTML template at appropriate places. This section is primarily an object that is exported, and consists of:
    • Data
      This is a function itself, and usually it returns some desired data encapsulated within a nice data structure.
    • Methods
      An object that consists of one or more functions/methods, each of which usually manipulates data in some way or the other, and also controls the dynamic content of the HTML template.
    • Computed
      Much like the method object discussed above with one important distinction — while all the functions within the method object are executed whenever any one of them is called, the functions within the computed object behaves much more sensibly, and executes if and only if it has been called.
  • Style
    This section is for CSS styling that applies to the HTML of the page (written within template) — put the good old CSS here to make your pages beautiful!

Keeping the above paradigm in mind, let’s minimally customize the code in App.vue:

<template> <div id="app"> <p>This component’s code is in {{ filename }}</p> </div> </template> <script> export default { data() { return { filename: 'App.vue' } }, methods: { }, computed: { }, } </script> <style> </style>

Remember that the above code snippet is simply for testing out that App.vue is working with our own code in it. It will later go on through a lot of changes, but first save the file and refresh the page on the browser.

(Large preview)

At this point, it’s probably a good idea to get some help in tooling. Check out the Vue devtools for Chrome, and if you don’t have much problems in using Google Chrome as your default browser for development, install the tool and play around with it a bit. It will come in extremely handy for further development and debugging, when things becomes more complicated.

Additional Directories And Files

The next step would be to add additional files, so that the structure of our project becomes complete. We would add the following directories and files:

Note: Save the hyperlinked .svg files in your project.

Create the directories and files mentioned above. The final project structure should like look (remember to delete folders and files from the default structure that are now unnecessary):

vue_weather_dashboard/ |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src/ | |--- App.vue | |--- css/ | | |--- style.css | |--- assets/ | | |--- calendar.svg | | |--- location.svg | | |--- location.svg | | |--- winddirection.svg | | |--- windspeed.svg | |--- main.js | |--- components/ | | |--- Content.vue | | |--- Highlights.vue | | |--- TempVarChart.vue | | |--- UVIndex.vue | | |--- Visibility.vue | | |--- WindStatus.vue

There might be some other files, like .babelrc, .gitignore, .editorconfig, etc. in the project’s root folder. You may ignore them safely for now.

In the following section, we will add minimal content to the newly added files, and test whether they are properly working.

src/css/style.css

Although it will not be of much use immediately, copy the following code to the file:

@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500"); :root { font-size: 62.5%; } body { font-family: Roboto; font-weight: 400; width: 100%; margin: 0; font-size: 1.6rem; } #sidebar { position: relative; display: flex; flex-direction: column; background-image: linear-gradient(-180deg, #80b6db 0%, #7da7e2 100%); } #search { text-align: center; height: 20vh; position: relative; } #location-input { height: 42px; width: 100%; opacity: 1; border: 0; border-radius: 2px; background-color: rgba(255, 255, 255, 0.2); margin-top: 16px; padding-left: 16px; color: #ffffff; font-size: 1.8rem; line-height: 21px; } #location-input:focus { outline: none; } ::placeholder { color: #FFFFFF; opacity: 0.6; } #current-weather { color: #ffffff; font-size: 8rem; line-height: 106px; position: relative; } #current-weather>span { color: #ffffff; font-size: 3.6rem; line-height: 42px; vertical-align: super; opacity: 0.8; top: 15px; position: absolute; } #weather-desc { font-size: 2.0rem; color: #ffffff; font-weight: 500; line-height: 24px; } #possibility { color: #ffffff; font-size: 16px; font-weight: 500; line-height: 19px; } #max-detail, #min-detail { color: #ffffff; font-size: 2.0rem; font-weight: 500; line-height: 24px; } #max-detail>i, #min-detail>i { font-style: normal; height: 13.27px; width: 16.5px; opacity: 0.4; } #max-detail>span, #min-detail>span { color: #ffffff; font-family: Roboto; font-size: 1.2rem; line-height: 10px; vertical-align: super; } #max-summary, #min-summary { opacity: 0.9; color: #ffffff; font-size: 1.4rem; line-height: 16px; margin-top: 2px; opacity: 0.7; } #search-btn { position: absolute; right: 0; top: 16px; padding: 2px; z-index: 999; height: 42px; width: 45px; background-color: rgba(255, 255, 255, 0.2); border: none; } #dashboard-content { text-align: center; height: 100vh; } #date-desc, #location-desc { color: #ffffff; font-size: 1.6rem; font-weight: 500; line-height: 19px; margin-bottom: 15px; } #date-desc>img { top: -3px; position: relative; margin-right: 10px; } #location-desc>img { top: -3px; position: relative; margin-left: 5px; margin-right: 15px; } #location-detail { opacity: 0.7; color: #ffffff; font-size: 1.4rem; line-height: 20px; margin-left: 35px; } .centered { position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); } .max-desc { width: 80px; float: left; margin-right: 28px; } .temp-max-min { margin-top: 40px } #dashboard-content { background-color: #F7F7F7; } .custom-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 20px !important; } .custom-content-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 0px !important; } .header-card { height: 50vh; } .content-card { height: 43vh; } .card-divider { margin-top: 0; } .content-header { color: #8786A4; font-size: 1.4rem; line-height: 16px; font-weight: 500; padding: 15px 10px 5px 15px; } .highlights-item { min-height: 37vh; max-height: 38vh; background-color: #FFFFFF; } .card-heading { color: rgb(33, 34, 68); font-size: 1.8rem; font-weight: 500; line-height: 21px; text-align: center; } .card-sub-heading { color: #73748C; font-size: 1.6rem; line-height: 19px; } .card-value { color: #000000; font-size: 1.8rem; line-height: 21px; } span text { font-weight: 500 !important; } hr { padding-top: 1.5px; padding-bottom: 1px; margin-bottom: 0; margin-top: 0; line-height: 0.5px; } @media only screen and (min-width: 768px) { #sidebar { height: 100vh; } #info { position: fixed; bottom: 50px; width: 100%; padding-left: 15px; } .wrapper-right { margin-top: 80px; } } @media only screen and (min-width:1440px) { #sidebar { width: 350px; max-width: 350px; flex: auto; } #dashboard-content { width: calc(100% — 350px); max-width: calc(100% — 350px); flex: auto; } } src/assets/

In this directory, download and save the .svg files mentioned below:

src/components/Content.vue

This is what we call a dumb component — a placeholder, that is there just to maintain the hierarchy, and essentially passes on data to its child components.

Remember that there is no technical bar for writing all our code in the App.vue file, but we take the approach of splitting up the code by nesting the components for two reasons:

  • To write clean code, which aids readability and maintainability;
  • To replicate the same structure that we will see on screen, i.e., the hierarchy.

Before we nest the component defined in Content.vue within the root component App.vue, let’s write some toy (but educational) code for Content.vue:

<template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> </div> </template> <script> export default { data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> </style>

In the code, carefully observe and understand the following:

  • Within the <script> tag (where we obviously write some JavaScript code), we define an object that is exported (made available to other files) by default. This object contains a function data(), that returns an array object called childComponents, with its elements being names of the component files that should be nested further.
  • Within the <template> tag (where we write some HTML template), the thing of interest is the <ul>.
    • Within the unordered list, each list item should be names of the intended child components, as defined in the array object childComponents. Moreover, the list should automatically extend till the last element of the array. Seems like we should write a for-loop, isn’t it? We do that by using the v-for directive provided by Vue.js. The v-for directive:
      • Acts as an attribute of the <li> tag, iterates through the array, renders the names of the child components where the iterator is mentioned within the {{ }} brackets (where we write the text for the list items).

The code and the explanation above forms the basis of your subsequent understanding of how the script and the template are interrelated, and how we can use the directives provided by Vue.js.

We have learnt quite a lot, but even after all these, we have one thing left to learn about seamlessly connecting components in hierarchy — passing data down from the parent component to its children. For now, we need to learn how to pass some data from src/App.vue to src/components/Content.vue, so that we can use the same techniques for the rest of the component nesting in this project.

Data trickling down from the parent to the child components might sound simple, but the devil is in the details! As briefly explained below, there are multiple steps involved in making it work:

  • Defining and the data
    For now, we want some static data to play with — an object containing hard-coded values about different aspects of weather will just be fine! We create an object called weather_data and return it from the data() function of App.vue. The weather_data object is given in the snippet below:
weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "N-E", }, visibility: "12 km", }, },
  • Passing the data from the parent
    To pass the data, we need a destination where we want to send the data! In this case, the destination is the Content.vue component, and the way to implement it is to:
    • Assign the weather_data object to a custom attribute of the <Content> tag
    • Bind the attribute with the data using the v-bind: directive provided by Vue.js, which makes the attribute value dynamic (responsive to changes made in the original data). <Content v-bind:weather_data=“weather_data”></Content>

Defining and passing the data is handled at the source side of the handshake, which in our case is the App.vue file.

The code for the App.vue file, at its current status, is given below:

<template> <div id="app"> <p>This component’s code is in {{ filename }}</p> <Content v-bind:weather_data="weather_data"></Content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'Content': Content }, data () { return { filename: 'App.vue', weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "N-E", }, visibility: "12 km", }, }, } }, methods: { }, computed: { }, } </script> <style> </style> (Large preview)

With the data defined and passed from the source (parent component), it is now the child’s responsibility to receive the data and render it appropriately, as explained in the next two steps.

  • Receiving the data by the child
    The child component, in this case Content.vue, must receive the weather_data object send to it by the parent component App.vue. Vue.js provides a mechanism to do so — all you need is an array object called props, defined in the default object exported by Content.vue. Each element of the array props is a name of the data objects it wants to receive from its parent. For now, the only data object that it is supposed to receive is weather_data from App.vue. Thus, the props array looks like:
<template> // HTML template code here </template> <script> export default { props: ["weather_data"], data () { return { // data here } }, } </script> <style> // component specific CSS here </style>
  • Rendering the data in the page
    Now that we have ensured receiving the data, the last task we need to complete is to render the data. For this example, we will directly dump the received data on the web page, just to illustrate the technique. However, in real applications (like the one we are about to build), data normally goes through lots of processing, and only the relevant parts of it are displayed in ways that suits the purpose. For example, in this project we will eventually get raw data from the weather API, clean and format it, feed the data to the data structures necessary for the charts, and then visualize it. Anyway, to display the raw data dump, we will just use the {{ }} brackets that Vue understands, as shown in the snippet below:
<template> <div id="pagecontent"> // other template code here {{ weather_data }} </div> </template>

It’s now time to assimilate all the bits and pieces. The code for Content.vue — at its current status — is given below:

<template> <div id="pagecontent"> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} </div> </template> <script> export default { props: ["weather_data"], data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> #pagecontent { border: 1px solid black; padding: 2px; } </style> (Large preview)

After making the changes discussed above, refresh the webpage on the browser and see how it looks. Take a moment to appreciate the complexity that Vue handles — if you modify the weather_data object in App.vue, it gets silently conveyed to Content.vue, and eventually to the browser displaying the webpage! Try by changing the value for the key location.

Although we have learned about props and data binding using static data, we will be using dynamic data collected using web APIs in the application, and will change the code accordingly.

Summary

Before we move on to the rest of the .vue files, let’s summarize what we have learnt while we wrote the code for App.vue and components/Content.vue:

  • The App.vue file is what we call the root component — the one that sits at the top of the component hierarchy. The rest of the .vue files represents components that are its direct child, grandchild, and so on.
  • The Content.vue file is a dummy component — its responsibility is to pass on the data to levels below and maintain the structural hierarchy, so that our code remains consistent with the philosophy “*what we see is what we implement*”.
  • The parent-child relationship of component does not happen out of thin air — you must register a component (either globally or locally, depending on the intended usage of the component), and then nest it using custom HTML tags (whose spellings are the exact same as that of the names with which the components has been registered).
  • Once registered and nested, data is passed on from parent to child components, and the flow is never reverse (bad things will happen if the project architecture allows backflow). The parent component is the relative source of the data, and it passes down relevant data to its children using the v-bind directive for the attributes of the custom HTML elements. The child receives the data intended for it using props, and then decides on its own what to do with the data.

For the rest of the components, we will not indulge in detailed explanation — we will just write the code based on the learnings from the above summary. The code will be self-evident, and if you get confused about the hierarchy, refer to the diagram below:

(Large preview)

The diagram says that TempVarChart.vue and Highlights.vue are the direct child of Content.vue. Thus, it might be a good idea to prepare Content.vue for sending data to those components, which we do using the code below:

<template> <div id="pagecontent"> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue' import Highlights from './Highlights.vue' export default { props: ["weather_data"], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'], tempVar: this.weather_data.temperature, highlights: this.weather_data.highlights, } }, methods: { }, computed: { }, } </script> <style> </style>

Once you save this code, you will get errors — don’t worry, it is expected. It will be fixed once you have the rest of the component files ready. If it bothers you not to be able to see the output, comment out the lines containing the custom element tags <temp-var-chart> and <today-highlights>.

For this section, this is the final code of Content.vue. For the rest of this section, we will reference to this code, and not the previous ones that we wrote for learning.

src/components/TempVarChart.vue

With its parent component Content.vue passing on the data, TempVarChart.vue must be set up to receive and render the data, as shown in the code below:

<template> <div id="tempvarchart"> <p>Temperature Information:</p> {{ tempVar }} </div> </template> <script> export default { props: ["tempVar"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style> src/components/Highlights.vue

This component will also receive data from App.vue — its parent component. After that, it should be linked with its child components, and relevant data should be passed on to them.

Let’s first see the code for receiving data from the parent:

<template> <div id="highlights"> <p>Weather Highlights:</p> {{ highlights }} </div> </template> <script> export default { props: ["highlights"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

At this point, the web page looks like the image below:

(Large preview)

Now we need to modify the code of Highlights.vue to register and nest its child components, followed by passing the data to children. The code for it is as follows:

<template> <div id="highlights"> <p>Weather Highlights:</p> {{ highlights }} <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

Once you save the code and see the web page, you are expected to see errors in the Developer Console tool provided by the browser; they appear because although Highlights.vue is sending data, nobody is receiving them. We are yet to write the code for the children of Highlights.vue.

Observe that we have not done much of the data processing, i.e, we have not extracted the individual factors of weather data that goes under the Highlights section of the dashboard. We could have done that in the data() function, but we preferred to keep Highlights.vue a dumb component that just passes on the entire data dump it receives to each of the children, who then own their own extracts what is necessary for them. However, we encourage you to try out extracting data in the Highlights.vue, and send relevant data down to each child component — it’s a good practice exercise nonetheless!

src/components/UVIndex.vue

The code for this component receives the data dump of highlights from Highlights.vue, extracts the data for UV Index, and renders it on the page.

<template> <div id="uvindex"> <p>UV Index: {{ uvindex }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { uvindex: this.highlights.uvindex } }, methods: { }, computed: { }, } </script> <style> </style> src/components/Visibility.vue

The code for this component receives the data dump of highlights from Highlights.vue, extracts the data for Visibility, and renders it on the page.

<template> <div id="visibility"> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility, } }, methods: { }, computed: { }, } </script> <style> </style> src/components/WindStatus.vue

The code for this component receives the data dump of highlights from Highlights.vue, extracts the data for Wind Status (speed and direction), and renders it on the page.

<template> <div id="windstatus"> <p>Wind Status:</p> <p>Speed — {{ speed }}; Direction — {{ direction }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { speed: this.highlights.windstatus.speed, direction: this.highlights.windstatus.direction } }, methods: { }, computed: { }, } </script> <style> </style>

After adding the code for all the components, take a look at the web page on the browser.

(Large preview)

Not to dishearten, but all these toiling was just to link the components in hierarchy, and test out whether data flow is happening between them or not! In the next section, we will throw away most of the code we have written so far, and add a lot more pertaining to the actual project. However, we will certainly retain the structure and nesting of the components; the learnings from this section will allow us to build a decent dashboard with Vue.js.

4. Data Acquisition And Processing

Remember the weather_data object in App.vue? It had some hard-coded data that we used to test whether all the components are working correctly, and also to help you learn some basic aspects of Vue application without getting bogged down in the details of real-world data. However, it’s now time that we shed our shell, and step out into the real world, where data from the API will dominate most of our code.

Preparing Child Components To Receive And Process Real Data

In this section, you will get code dump for all the components except App.vue. The code will handle receiving real data from App.vue (unlike the code we wrote in the previous section to receive and render dummy data).

We strongly encourage to read the code of each component carefully, so that you form an idea of what data each of those components are expecting, and will eventually use in visualization.

Some of the code, and the overall structure, will be similar to the ones you have seen in the previous structure — so you will not face something drastically different. However, the devil is in the details! So examine the code carefully, and when you have understood them reasonably well, copy the code to the respective component files in your project.

Note: All the components in this section are in the src/components/ directory. So each time, the path will not be mentioned — only the .vue file name will be mentioned to identify the component.

Content.vue <template> <div style="position: relative;"> <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue'; import Highlights from './Highlights.vue'; export default { props: ['highlights', 'tempVar'], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, } </script>

The following changes have been made from the previous code:

  • In the <template>, text and data within {{ }} has been removed, since we are now just receiving data and passing down to the children, with no rendering specific this component.
  • In the export default {}:
    • The props have been changed to match the data objects that will be send by the parent: App.vue. The reason for changing the props is that App.vue itself will display some of the data it acquires from the weather API and other online resources, based on the search query of the user, and pass on the rest of the data. In the dummy code we wrote earlier, App.vue was passing on the entire dummy data dump, without any discrimination, and the props of Content.vue was set up accordingly.
    • The data() function now returns nothing, as we are not doing any data manipulation in this component.
TempVarChart.vue

This component is supposed to receive detailed temperature projections for the rest of the current day, and eventually display them using FusionCharts. But for the time being, we will display them only as text on the webpage.

<template> <div> {{ tempVar.tempToday }} </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { }; }, methods: { }, }; </script> <style> </style> Highlights.vue <template> <div> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

The changes made from the previous code are:

  • In the <template>, the text and the data within {{ }} has been removed, because this is a dumb component, just like Content.vue, whose only job is to pass on the data to children while maintaining the structural hierarchy. Remember that dumb components like Highlights.vue and Content.vue exists to maintain the parity between the visual structure of the dashboard, and the code we write.
UVIndex.vue

The changes made to the previous code are as follows:

  • In the <template> and <style>, the div id has been changed to uvIndex, which is more readable.
  • In the export default {}, the data() function now returns a string object uvIndex, whose value is extracted from the highlights object received by the component using props. This uvIndex is now temporarily used to display the value as text within the <template>. Later on, we will plug in this value to the data structure suitable for rendering a chart.
Visibility.vue <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility.toString() } }, methods: { }, computed: { }, } </script> <style> </style>

The only change made in this file (with respect to its previous code) is that the definition of the visibility object returned by the data() function now contains toString() at its end, since the value received from the parent will be a floating point number, which needs to be converted into string.

WindStatus.vue <template> <div> <p>Wind Speed — {{ windSpeed }}</p> <p>Wind Direction — {{ derivedWindDirection }}, or {{ windDirection }} degree clockwise with respect to true N as 0 degree.</p> </div> </template> <script> export default { props: ["highlights"], data () { return { windSpeed: this.highlights.windStatus.windSpeed, derivedWindDirection: this.highlights.windStatus.derivedWindDirection, windDirection: this.highlights.windStatus.windDirection } }, methods: { }, computed: { }, } </script> <style> </style>

The changes made to the previous code are as follows:

  • Throughout the file, windstatus has been renamed as windStatus, to promote readability and also to be in sync with the the highlights object that App.vue provides with actual data.
  • Similar naming changes have been made for the speed and direction — the new ones are windSpeed and windDirection.
  • A new object derivedWindDirection has come into play (also provided by App.vue in the highlights bundle).

For now, the received data is rendered as text; later, it will be plugged in to the data structure necessary for visualization.

Testing With Dummy Data

Resorting to dummy data repeatedly might be a bit frustrating for you, but there are some good reasons behind it:

  • We have made a lot of changes to the code of each component, and it’s a good idea to test whether those changes are breaking the code. In other words, we should check that whether the data flow is intact, now that we are about to move to more complex parts of the project.
  • The real data from the online weather API will need lot of massaging, and it might be overwhelming for you to juggle between the code for data acquisition and processing, and the code for smooth data flow down the components. The idea is to keep the quantum of complexity under control, so that we have a better understanding of the errors we might face.

In this section, what we do is essentially hardcode some json data in the App.vue, which will obviously be replaced with live data in the near future. There are a lot of similarity between the dummy json structure, and the json structure we will use for the actual data. So it also provides you a rough idea of what to expect from the real data, once we encounter it.

However, we admit that this is far from the ideal approach one might adopt while building such a project from scratch. In the real world, you will often start with the real data source, play around with it a bit to understand what can and should be done to tame it, and then think about the appropriate json data structure to capture the relevant information. We intentionally shielded you from all those dirty work, since it takes you farther from the objective — learning how to use Vue.js and FusionCharts to build a dashboard.

Let’s now jump into the new code for App.vue:

<template> <div id="app"> <dashboard-content :highlights="highlights" :tempVar="tempVar"></dashboard-content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'dashboard-content': Content }, data () { return { tempVar: { tempToday: [ {hour: '11.00 AM', temp: '35'}, {hour: '12.00 PM', temp: '36'}, {hour: '1.00 PM', temp: '37'}, {hour: '2.00 PM', temp: '38'}, {hour: '3.00 PM', temp: '36'}, {hour: '4.00 PM', temp: '35'}, ], }, highlights: { uvIndex: 4, visibility: 10, windStatus: { windSpeed: '30 km/h', windDirection: '30', derivedWindDirection: 'NNE', }, }, } }, methods: { }, computed: { }, } </script> <style> </style>

The changes made to the code with respect to its previous version are as follows:

  • The name of the child component has been changed to dashboard-content, and accordingly the custom HTML element in the <template> has been revised. Note that now we have two attributes — highlights and tempVar — instead of a single attribute that we used earlier with the custom element. Accordingly, the data associated with those attributes have also changed. What’s interesting here is that we can use the v-bind: directive, or its shorthand : (as we have done here), with multiple attributes of a custom HTML element!
  • The data() function now returns the filename object (that existed earlier), along with two new objects (instead of the old weather_data): tempVar and highlights. The structure of the json is appropriate for the code we have written in the child components, so that they can extract the data pieces they need from the dumps. The structures are quite self-explanatory, and you can expect them to be quite similar when we deal with live data. However, the significant change that you will encounter is the absence of hardcoding (obvious, isn’t it) — we will leave the values blank as the default state, and write code to dynamically update them based on the values we will receive from the weather API.

You have written a lot of code in this section, without seeing the actual output. Before you proceed further, take a look at the browser (restart the server with npm run dev, if necessary), and bask in the glory of your achievement. The web page that you should see at this point looks like the image below:

(Large preview) Code For Data Acquisition And Processing

This section is going to be the meat of the project, with all the code to be written in App.vue for the following:

  • Location input from the user — an input box and a call-to-action button is sufficient;
  • Utility functions for various tasks; these functions will be called later in various parts of the component code;
  • Getting detailed geolocation data from Google Maps API for JavaScript;
  • Getting detailed weather data from the Dark Sky API;
  • Formatting and processing the geolocation and weather data, which will be passed on to the child components.

The subsections that follows illustrates how we can implement the tasks laid out for us in the above points. With some exceptions, most of them will follow the sequence.

Input From The User

It’s quite obvious that the action starts when the user provides the name of the place for which the weather data needs to be displayed. For this to happen, we need to implement the following:

  • An input box for entering the location;
  • A submit button that tells our application that the user has entered the location and it’s time to do the rest. We will also implement the behavior when processing starts upon hitting Enter.

The code we show below will be restricted to the HTML template part of App.vue. We will just mention the name of the method associated with the click events, and define them later in the methods object of the <script> in App.vue.

<div id="search"> <input id="location-input" type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button id="search-btn" @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div>

Placing the above snippet in the right place is trivial — we leave it to you. However, the interesting parts of the snippet are:

  • @keyup.enter="organizeAllDetails"
  • @click="organizeAllDetails"

As you know from the earlier sections, @ is Vue’s shorthand for the directive v-on:, which is associated with some event. The new thing is “organizeAllDetails” — it’s nothing but the method that will fire once the events (pressing Enter or clicking the button) happens. We are yet to define method, and the puzzle will be complete by the end of this section.

Text Information Display Controlled By App.vue

Once the user input triggers the action and lots of data is acquired from the APIs, we encounter the inevitable question — “What to do with all these data?”. Obviously some data massaging is required, but that does not answer our question fully! We need to decide what’s the end use of the data, or more directly, which are the entities that receives different chunks of the acquired and processed data?

The child components of App.vue, based on their hierarchy and purpose, are the frontline contenders for the bulk of the data. However, we will also have some data that does not belong to any of those child components, yet are quite informative and makes the dashboard complete. We can make good use of them if we display them as text information directly controlled by App.vue, while the rest of the data are passed on to the child for getting displayed as pretty charts ultimately.

With this context in mind, let’s focus on the code for setting the stage of using text data. It’s simple HTML template at this point, on which the data will eventually come and sit.

<div id="info"> <div class="wrapper-left"> <div id="current-weather"> {{ currentWeather.temp }} <span>°C</span> </div> <div id="weather-desc">{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div id="max-detail"> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div id="max-summary">at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div id="min-detail"> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div id="min-summary">at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div id="date-desc"> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div id="location-desc"> <img src="./assets/location.svg" width="10.83" height="15.83" style="opacity: 0.9;" > {{ currentWeather.full_location }} <div id="location-detail" class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div>

In the above snippet, you should understand the following:

  • The stuff inside {{ }} — they are Vue’s way of inserting dynamic data in the HTML template, before it renders in the browser. You have encountered them before, and there is nothing new or surprising. Just keep in mind that these data objects stems from the data() method in the export default() object of App.vue. They have default values that we will set according to our requirements, and then write certain methods to populate the objects with real API data.

Don’t worry for not seeing the changes on the browser — the data is not defined yet, and it’s natural for Vue to not render things that it does not know. However, once the data is set (and for now, you can even check by hard-coding the data), the text data will be controlled by App.vue.

The data() Method

The data() method is a special construct in the .vue files — it contains and returns data objects that are so crucial for the application. Recollect the generic structure of the <script> part in any .vue file — it roughly contains the following:

<script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. // the data objects will have certain default values chosen by us. // The methods that we define below will manipulate the data. // Since the data is bounded to various attributes and directives, they // will update as and when the values of the data objects change. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. }, computed: { // computed properties here }, // other objects, as necessary } </script>

So far, you have encountered the names of some of the data objects, but are a lot more. Most of them are relevant for the child components, each of which handles a different aspect of the weather information dump. Given below is the entire data() method that we will need for this project — you will have a fair idea about what data we are expecting from the APIs, and how we are disseminating the data, based on the nomenclature of the objects.

data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; },

As you can see, in most cases the default value is empty, because that will suffice at this point. Methods will be written for manipulating the data and filling it up with appropriate values, before it is rendered or passed on to the child components.

Methods in App.vue

For .vue files, the methods are generally written as values of keys nested in the methods { } object. Their primary role is to manipulate the data objects of the component. We will write the methods in App.vue keeping the same philosophy in mind. However, based on their purpose, we can categorize the methods of App.vue into the following:

  • Utility methods
  • Action/Event oriented methods
  • Data acquisition methods
  • Data processing methods
  • High level glue methods

It’s important that you understand this — we are presenting the methods to you on a platter because we have already figured out how the APIs work, what data they give, and how we should use the data in our project. It’s not that we pulled the methods out of thin air, and wrote some arcane code to deal with the data. For the purpose of learning, it’s a good exercise to diligently read and understand the code for the methods and data. However, when faced with a new project that you have to build from scratch, you must do all the dirty work yourself, and that means experimenting a lot with the APIs — their programmatic access and their data structure, before glueing them seamlessly with the data structure that your project demands. You will not have any hand holding, and there will be frustrating moments, but that’s all part of maturing as a developer.

In the following subsections, we will explain each of the method types, and also show the implementation of the methods belonging to that category. The method names are quite self-explanatory about their purpose, and so is their implementation, which we believe you will find to be easy enough to follow. However, before that, recollect the general scheme of writing methods in .vue files:

<script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. method_1: function(arg_1) { }, method_2: function(arg_1, arg_2) { }, method_3: function(arg_1) { }, ……. }, computed: { // computed properties here }, // other objects, as necessary } </script> Utility Methods

The utility methods, as the name suggests, are methods written primarily for the purpose of modularizing repetitive code used for fringe tasks. They are called by other methods when necessary. Given below are the utility methods for App.vue:

convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, // To format the “possibility” (of weather) string obtained from the weather API formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, // To convert Unix timestamps according to our convenience unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; }, // To convert temperature from fahrenheit to celcius fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; }, // To convert the air pressure reading from millibar to kilopascal milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); }, // To convert distance readings from miles to kilometers mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); }, // To format the wind direction based on the angle deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; },

Although we haven’t implemented it, you can take out the utility methods from the .vue file, and put it in a separate JavaScript file. All you need to do is import the .js file at the start of the script part in the .vue file, and you should be good to go. Such approach works really well and keeps the code clean, especially in big applications where you might use lots of methods that are better grouped together based on their purpose. You can apply this approach to all of the method groups listed in this article, and see the effect itself. However, we suggest you do that exercise once you have followed the course presented here, so that you have the big picture understanding of all the parts working in complete sync, and also have a working piece of software which you can refer to, once something breaks while experimenting.

Action/Event Oriented Methods

These methods are generally executed when we need to take an action corresponding to an event. Depending on the case, the event might be triggered from an user interaction, or programmatically. In the App.vue file, these methods sit below the utility methods.

makeInputEmpty: function() { this.$refs.input.value = ''; }, makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; }, detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); }, locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); },

One interesting thing in some of the above code snippets is the use of $ref. In simple terms, it’s Vue’s way of associating the code statement containing it, to the HTML construct it is supposed to affect (for more information, read the official guide). For example, the methods makeInputEmpty() and detectEnterKeyPress() affects the input box, because in the HTML of the input box we have mentioned the value of the attribute ref as input.

Data Acquisition Methods

We are using the following two APIs in our project:

  • Google Maps Geocoder API
    This API is for getting the coordinates of the location that the user searches. You will need an API key for yourself, which you can get by following the documentation in the given link. For now, you can use the API key used by FusionCharts, but we request you not to abuse it and get a key of your own. We refer to the JavaScript API from the index.html of this project, and we shall use the constructors provided by it for our code in the App.vue file.
  • The Dark Sky Weather API
    This API is for getting the weather data corresponding to the coordinates. However, we won’t be using it directly; we will wrap it within an URL that redirects through one of the FusionCharts’s server. The reason is that if you send a GET request to the API from an entirely client-end application such as ours, it results in the frustrating CORS error (more information here and here).

Important Note: Since we have used Google Maps and Dark Sky APIs, Both these APIs have their own API keys which we have shared with you in this article. This will help you focus on client-side developments rather than the headache of backend implementation. However, we recommend you to create your own keys, because our APIs keys will come with limits and if these limits exceed you won't be able to try the application by yourself.

For Google Maps, go to this article to get your API key. For Dark Sky API, visit https://darksky.net/dev to create your API key and respective endpoints.

With the context in mind, let’s see the implementation of the data acquisition methods for our project.

getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); }, /* The coordinates that Google Maps Geocoder API returns are way too accurate for our requirements. We need to bring it into shape before passing the coordinates on to the weather API. Although this is a data processing method in its own right, we can’t help mentioning it right now, because the data acquisition method for the weather API has dependency on the output of this method. */ setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } }, /* This method dynamically creates the the correct weather API query URL, based on the formatted latitude and longitude. The complete URL is then fed to the method querying for weather data. Notice that the base URL used in this method (without the coordinates) points towards a FusionCharts server — we must redirect our GET request to the weather API through a server to avoid the CORS error. */ fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; }, fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } },

Through these methods, we have introduced the concept of async-await in our code. If you have been a JavaScript developer for some time now, you must be familiar with the callback hell, which is a direct consequence of the asynchronous way JavaScript is written. ES6 allows us to bypass the cumbersome nested callbacks, and our code becomes much cleaner if we write JavaScript in a synchronous way, using the async-await technique. However, there is a downside. It takes away the speed that asynchronous code gives us, especially for the portions of the code that deals with data being exchanged over the internet. Since this is not a mission-critical application with low latency requirements, and our primary aim is to learn stuff, the clean code is much more preferable over the slightly fast code.

Data Processing Methods

Now that we have the methods that will bring the data to us, we need to prepare the ground for properly receiving and processing the data. Safety nets must be cast, and there should be no spills — data is the new gold (OK, that might be an exaggeration in our context)! Enough with the fuss, let’s get to the point.

Technically, the methods we implement in this section are aimed at getting the data out of the acquisition methods and the data objects in App.vue, and sometimes setting the data objects to certain values that suits the purpose.

getTimezone: function() { return this.rawWeatherData.timezone; }, getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; }, getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; }, getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; }, getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); }, getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; }, getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; }, getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; }, getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } }, getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; }, getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); }, getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); }, High Level Glue Methods

With the utility, acquisition, and processing methods out of our way, we are now left with the task of orchestrating the entire thing. We do that by creating high level glue methods, that essentially calls the methods written above in a particular sequence, so that the entire operation is executed seamlessly.

// Top level for info section // Data in this.currentWeather organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); }, // Top level for highlights organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); }, // Top level organization and rendering organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); }, mounted

Vue provides instance lifecycle hooks — properties that are essentially methods, and gets triggered when the instance lifecycle reaches that stage. For example, created, mounted, beforeUpdate, etc., are all very useful lifecycle hooks that allows the programmer to control the instance at a much more granular level than that would have been possible otherwise.

In the code of a Vue component, these lifecycle hooks are implemented just like you would for any other prop. For example:

<template> </template> <script> // import statements export default { data() { return { // data objects here } }, methods: { // methods here }, mounted: function(){ // function body here }, } </script> <style> </style>

Armed with this new understanding, take a look at the code below for the mounted prop of App.vue:

mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); } Complete Code For App.vue

We have covered a lot of ground in this section, and the last few sections have given you things in bits and pieces. However, it’s important that you have the complete, assembled code for App.vue (subject to further modifications in subsequent sections). Here it goes:

<template> <div id="ancestor"> <div class="container-fluid" id="app"> <div class="row"> <div id="sidebar" class="col-md-3 col-sm-4 col-xs-12 sidebar"> <div id="search"> <input id="location-input" type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button id="search-btn" @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div> <div id="info"> <div class="wrapper-left"> <div id="current-weather"> {{ currentWeather.temp }} <span>°C</span> </div> <div id="weather-desc">{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div id="max-detail"> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div id="max-summary">at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div id="min-detail"> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div id="min-summary">at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div id="date-desc"> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div id="location-desc"> <img src="./assets/location.svg" width="10.83" height="15.83" style="opacity: 0.9;" > {{ currentWeather.full_location }} <div id="location-detail" class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div> </div> <dashboard-content class="col-md-9 col-sm-8 col-xs-12 content" id="dashboard-content" :highlights="highlights" :tempVar="tempVar" ></dashboard-content> </div> </div> </div> </template> <script> import Content from './components/Content.vue'; export default { name: 'app', props: [], components: { 'dashboard-content': Content }, data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; }, methods: { // Some utility functions convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; }, fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; }, milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); }, mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); }, deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; }, // Some basic action oriented functions makeInputEmpty: function() { this.$refs.input.value = ''; }, makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; }, detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); }, locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); }, getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); }, // Some basic asynchronous functions setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } }, fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; }, fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } }, // Get and set functions; often combined, because they are short // For basic info — left panel/sidebar getTimezone: function() { return this.rawWeatherData.timezone; }, getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; }, getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; }, getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; }, getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); }, getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; }, getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; }, getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; }, getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } }, // For Today Highlights getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; }, getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); }, getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); }, // top level for info section organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); }, organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); }, // topmost level orchestration organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); }, }, mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); } }; </script>

And finally, after so much of patience and hard work, you can see the data flow with its raw power! Visit the application on the browser, refresh the page, search for a location in the application’s search box, and hit Enter!

(Large preview)

Now that we are done with all the heavy lifting, take a break. The subsequent sections focus on using the data to create charts that are beautiful and informative, followed by giving our ugly looking application a much deserved grooming session using CSS.

5. Data Visualization With FusionCharts Fundamental Considerations For Charts

For the end user, the essence of a dashboard is essentially this: a collection of the filtered and carefully curated information on a particular topic, conveyed through visual/graphic instruments for quick ingestion. They don’t care about the subtleties of your data pipeline engineering, or how aesthetic your code is — all they want is a high-level view in 3 seconds. Therefore, our crude application displaying text data means nothing to them, and it’s high time we implement mechanisms to wrap the data with charts.

However, before we dive deep into the implementation of charts, let’s consider some pertinent questions and the possible answers from our perspective:

  • What type of charts are appropriate for the type of data we are dealing with?
    Well, the answer has two aspects — the context, and the purpose. By context, we mean the type of data, and it’s overall fit in the scheme of bigger things, bounded by the scope and audience of the project. And by purpose, we essentially mean “what we want to emphasize on?”. For example, we can represent today’s temperature at different times of the day by using a Column chart (vertical columns of equal width, with height proportional to the value the column represents). However, we are rarely interested in the individual values, but rather the overall variation and trend throughout the data. To suit the purpose, it is in our best interest to use a Line chart, and we will do that shortly.
  • What should be kept in mind before selecting a charting library?
    Since we are doing a project predominantly using JavaScript based technologies, it’s a no-brainer that any charting library that we choose for our project should be a native of the JavaScript world. With that basic premise in mind, we should consider the following before zeroing down on any particular library:
    • Support for the frameworks of our choice, which in this case, is Vue.js. A project can be developed in other popular JavaScript frameworks like React, or Angular — check the support of the charting library for your favorite framework. Also, support for other popular programming languages like Python, Java, C++, .Net (AS and VB), especially when the project involves some serious backend stuff, must be considered.
    • Availability of charts types and features, since it is almost impossible to know beforehand what will be final shape and purpose of the data in the project (especially if the requirements are regulated by your clients in a professional setting). In this case, you should cast your net wide, and choose a charting library that has the widest collection of charts. More importantly, to differentiate your project from others, the library should have have enough features in the form of configurable chart attributes, so that you can fine-tune and customize most aspects of the charts and the right level of granularity. Also, the default chart configurations should be sensible, and the library documentation has to be top notch, for reasons that’s obvious to professional developers.
    • Learning curve, support community, and balance must also be taken into consideration, especially when you are new to data visualization. On one end of the spectrum, you have proprietary tools like Tableau and Qlickview that costs a bomb, has smooth learning curve, but also comes with so many limitations in terms of customizability, integration, and deployment. On the other end there is d3.js — vast, free (open source), and customizable to its core, but you have to pay the price of a very steep learning curve to be able to do anything productive with the library.

What you need is the sweet spot — the right balance between productivity, coverage, customizability, learning curve, and off course, cost. We nudge you to take a look at FusionCharts — the world’s most comprehensive and enterprise-ready JavaScript charting library for the web and mobile, that we will be using in this project for creating charts.

Introduction To FusionCharts

FusionCharts is used worldwide as the go-to JavaScript charting library by millions of developers spread across hundreds of countries around the globe. Technically, it’s as loaded and configurable as it can be, with support for integrating it with almost any popular tech stack used for web based projects. Using FusionCharts commercially requires a license, and you have to pay for the license depending on your use case (please contact sales if you are curious). However, we are using FusionCharts in this projects just to try out a few things, and therefore the non-licensed version (comes with a small watermark in your charts, and a few other restrictions). Using the non-licensed version is perfectly fine when you are trying out the charts and using it in your non-commercial or personal projects. If you have plans to deploy the application commercially, please ensure that you have a license from FusionCharts.

Since this is a project involving Vue.js, we will need two modules that needs to be installed, if not done earlier:

  • The fusioncharts module, because it contains everything you will need for creating the charts
  • The vue-fusioncharts module, which is essentially a wrapper for fusioncharts, so that it can be used in a Vue.js project

If you have not installed them earlier (as instructed in the third section), install them by executing the following command from the project’s root directory:

npm install fusioncharts vue-fusioncharts --save

Next, ensure that the src/main.js file of the project has the following code (also mentioned in section 3):

import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); Vue.use(VueFusionCharts, FusionCharts); new Vue({ el: '#app', render: h => h(App) })

Perhaps the most critical line in the above snippet is the following:

Vue.use(VueFusionCharts, FusionCharts)

It instructs Vue to use the vue-fusioncharts module for making sense of many things in the project that are apparently not explicitly defined by us, but is defined in the module itself. Also, this type of statement implies global declaration, by which we mean that anywhere Vue encounters anything strange in the code of our project (things that we have not explicitly defined about using FusionCharts), it will at least look once in the vue-fusioncharts and fusioncharts node modules for their definitions, before throwing up errors. If we would have used FusionCharts in an isolated part of our project (not using it in almost all of the component files), then perhaps local declaration would have made more sense.

With that, you are all set to use FusionCharts in the project. We will be using quite a few variety of charts, the choice being dependent on the aspect of the weather data that we want to visualize. Also, we will get to see the interplay of data binding, custom components, and watchers in action.

General Scheme For Using Fusioncharts In .vue Files

In this section, we will explain the general idea of using FusionCharts for creating various charts in the .vue files. But first, let’s see the pseudocode that schematically illustrates the core idea.

<template> <div> <fusioncharts :attribute_1="data_object_1" :attribute_2="data_object_2" … … ... > </fusioncharts> </div> </template> <script> export default { props: ["data_prop_received_by_the_component"], components: {}, data() { return { data_object_1: "value_1", data_object_2: "value_2", … … }; }, methods: {}, computed: {}, watch: { data_prop_received_by_the_component: { handler: function() { // some code/logic, mainly data manipulation based }, deep: true } } }; </script> <style> // component specific special CSS code here </style>

Let’s understand different parts of the above pseudocode:

  • In the <template>, within the top level <div> (that’s pretty much mandatory for the template HTML code of every component), we have the custom component <fusioncharts>. We have the definition of the component contained in the vue-fusioncharts Node module that we have installed for this project. Internally, vue-fusioncharts relies on the fusioncharts module, which have also been installed. We imported the necessary modules and resolved their dependencies, instructed Vue to use the wrapper globally (throughout the project) in the src/main.js file, and therefore there is no lack of definition for the custom <fusioncharts> component that we have used here. Also, the custom component has custom attributes, and each of the custom attribute is bound to a data object (and in turn, their values), by the v-bind directive, for which the shorthand is the colon (:) symbol. We will learn about the attributes and their associated data objects in a greater detail, when we discuss some of the specific charts used in this project.
  • In the <script>, first you declare the props that the component is supposed to receive, and then go on defining the data objects that are bounded to the attributes of <fusioncharts>. The values assigned to the data objects are the values that the attributes of <fusioncharts> pulls in, and the charts are created on the basis of those pulled in values. Apart from these, the most interesting part of the code is the watch { } object. This is a very special object in Vue’s scheme of things — it essentially instructs Vue to watch over any changes happening to certain data, and then take actions based on how the handler function for that data has been defined. For example, we want Vue to keep a watch on the prop received, i.e., data_prop_received_by_the_component in the pseudocode. The prop becomes a key in the watch { } object, and the value of the key is another object — a handler method that describes what needs to be done whenever the prop changes. With such elegant mechanisms to handle the changes, the app maintains its reactivity. The deep: true represents a boolean flag that you can associate with watchers, so that the object being watched is watched rather deeply, i.e., even the changes made in the nested levels of the object are tracked.
    (For more information on watchers, consult the official documentation).

Now that you are equipped with an understanding of the general scheme of things, let’s dive into the specific implementations of the charts in the .vue component files. The code will be pretty self-explanatory, and you should try to understand how the specifics fit in the general scheme of things described above.

Implementation Of Charts In .vue Files

While the very specifics of the implementation varies from one chart to another, the following explanation is applicable for all of them:

  • <template>
    As explained previously, the <fusioncharts> custom component has several attributes, each of them being bound to corresponding data object defined in the data() function by using the v-bind: directive. The attribute names are quite self-explanatory for what they mean, and figuring out the corresponding data objects is also trivial.
  • <script>
    In the data() function, the data objects and their values are what makes the charts work, because of the binding done by the v-bind (:) directives used on the attributes of <fusioncharts>. Before we dive deep into the individual data objects, it’s worth mentioning some general characteristics:
    • The data objects whose values are either 0 or 1 are boolean in nature, where 0 represents something not available/turned off, and 1 represents availability/turned on state. However, be cautious that non-boolean data objects can also have 0 or 1 as their values, besides other possible values — it depends on the context. For example, containerbackgroundopacity with its default value as 0 is boolean, whereas lowerLimit with its default value as 0 simply means the number zero is its literal value.
    • Some data objects deals with CSS properties like margin, padding, font-size, etc. — the value has an implied unit of “px” or pixel. Similarly, other data objects can have implicit units associated with their values. For detailed information, please refer to the respective chart attributes page of FusionCharts Dev Center.
  • In the data() function, perhaps the most interesting and non-obvious object is the dataSource. This object has three main objects nested within it:
    • chart: This object encapsulates lots of chart attributes related to the configuration and cosmetics of the chart. It is almost a compulsory construct that you will find in all the charts you will create for this project.
    • colorrange: This object is somewhat specific to the chart under consideration, and is mainly present in charts that deals with multiple colors/shades to demarcate different sub-ranges of the scale used in chart.
    • value: This object, again, is present in charts that has a specific value that needs to be highlighted in the range of the scale.
  • The watch { } object is perhaps the most crucial thing that makes this chart, and the other charts used in this project, spring to life. The reactivity of the charts, i.e., the charts updating themselves based on the new values resulting from a new user query is controlled by the watchers defined in this object. For example, we have defined a watcher for the prop highlights received by the component, and then defined a handler function to instruct Vue about the necessary actions that it should take, when anything changes about the object being watched in the entire project. This means that whenever App.vue yields a new value for any of the object within highlights, the information trickles down all the way down to this component, and the new value is updated in the data objects of this component. The chart being bound to the values, also gets updated as a result of this mechanism.

The above explanations are quite broad strokes to help us develop an intuitive understanding of the bigger picture. Once you understand the concepts intuitively, you can always consult the documentation of Vue.js and FusionCharts, when something is not clear to you from the code itself. We leave the exercise to you, and from the next subsection onward, we will not explain stuff that we covered in this subsection.

src/components/TempVarChart.vue (Large preview) <template> <div class="custom-card header-card card"> <div class="card-body pt-0"> <fusioncharts type="spline" width="100%" height="100%" dataformat="json" dataEmptyMessage="i-https://i.postimg.cc/R0QCk9vV/Rolling-0-9s-99px.gif" dataEmptyMessageImageScale=39 :datasource="tempChartData" > </fusioncharts> </div> </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { tempChartData: { chart: { caption: "Hourly Temperature", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", baseFont: "Roboto", chartTopMargin: "30", showHoverEffect: "1", theme: "fusion", showaxislines: "1", numberSuffix: "°C", anchorBgColor: "#6297d9", paletteColors: "#6297d9", drawCrossLine: "1", plotToolText: "$label<br><hr><b>$dataValue</b>", showAxisLines: "0", showYAxisValues: "0", anchorRadius: "4", divLineAlpha: "0", labelFontSize: "13", labelAlpha: "65", labelFontBold: "0", rotateLabels: "1", slantLabels: "1", canvasPadding: "20" }, data: [], }, }; }, methods: { setChartData: function() { var data = []; for (var i = 0; i < this.tempVar.tempToday.length; i++) { var dataObject = { label: this.tempVar.tempToday[i].hour, value: this.tempVar.tempToday[i].temp }; data.push(dataObject); } this.tempChartData.data = data; }, }, mounted: function() { this.setChartData(); }, watch: { tempVar: { handler: function() { this.setChartData(); }, deep: true }, }, }; </script> <style> </style> src/components/UVIndex.vue

This component contains an extremely useful chart — the Angular Gauge.

(Large preview)

The code for the component is given below. For detailed information on the chart attributes of Angular Gauge, refer to FusionCharts Dev Center page for Angular Gauge.

<template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" ></fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return { type: "angulargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", datasource: { chart: { caption: "UV Index", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", lowerLimit: "0", upperLimit: "15", lowerLimitDisplay: "1", upperLimitDisplay: "1", showValue: "0", theme: "fusion", baseFont: "Roboto", bgAlpha: "0", canvasbgAlpha: "0", gaugeInnerRadius: "75", gaugeOuterRadius: "110", pivotRadius: "0", pivotFillAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", tickValueDistance: "3", autoAlignTickValues: "1", majorTMAlpha: "20", chartTopMargin: "30", chartBottomMargin: "40" }, colorrange: { color: [ { minvalue: "0", maxvalue: this.highlights.uvIndex.toString(), code: "#7DA9E0" }, { minvalue: this.highlights.uvIndex.toString(), maxvalue: "15", code: "#D8EDFF" } ] }, annotations: { groups: [ { items: [ { id: "val-label", type: "text", text: this.highlights.uvIndex.toString(), fontSize: "20", font: "Source Sans Pro", fontBold: "1", fillcolor: "#212529", x: "$gaugeCenterX", y: "$gaugeCenterY" } ] } ] }, dials: { dial: [ { value: this.highlights.uvIndex.toString(), baseWidth: "0", radius: "0", borderThickness: "0", baseRadius: "0" } ] } } }; }, methods: {}, computed: {}, watch: { highlights: { handler: function() { this.datasource.colorrange.color[0].maxvalue = this.highlights.uvIndex.toString(); this.datasource.colorrange.color[1].minvalue = this.highlights.uvIndex.toString(); this.datasource.annotations.groups[0].items[0].text = this.highlights.uvIndex.toString(); }, deep: true } } }; </script> src/components/Visibility.vue

In this component, we use a Horizontal Linear Gauge to represent the visibility, as shown in the image below:

(Large preview)

The code for the component is given below. For an in depth understanding of the different attributes of this chart type, please refer to FusionCharts Dev Center page for Horizontal Linear Gauge.

<template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-left border-right border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" > </fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, methods: {}, computed: {}, data() { return { type: "hlineargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", creditLabel: false, datasource: { chart: { caption: "Air Visibility", captionFontBold: "0", captionFontColor: "#000000", baseFont: "Roboto", numberSuffix: " km", lowerLimit: "0", upperLimit: "40", showPointerShadow: "1", animation: "1", transposeAnimation: "1", theme: "fusion", bgAlpha: "0", canvasBgAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", pointerBorderAlpha: "0", chartBottomMargin: "40", captionPadding: "30", chartTopMargin: "30" }, colorRange: { color: [ { minValue: "0", maxValue: "4", label: "Fog", code: "#6297d9" }, { minValue: "4", maxValue: "10", label: "Haze", code: "#7DA9E0" }, { minValue: "10", maxValue: "40", label: "Clear", code: "#D8EDFF" } ] }, pointers: { pointer: [ { value: this.highlights.visibility.toString() } ] } } }; }, watch: { highlights: { handler: function() { this.datasource.pointers.pointer[0].value = this.highlights.visibility.toString(); }, deep: true } } }; </script> src/components/WindStatus.vue

This component displays the wind speed and direction (wind velocity, if you are physics savvy), and it is very difficult to represent a vector using a chart. For such cases, we suggest representing them with the aid of some nice images and text values. Since the representation we have thought about is entirely dependent on CSS, we will implement it in the next section that deals with the CSS. However, take a look at what we are aiming to create:

(Large preview) <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <div class="card-heading pt-5">Wind Status</div> <div class="row pt-4 mt-4"> <div class="col-sm-6 col-md-6 mt-2 text-center align-middle"> <p class="card-sub-heading mt-3">Wind Direction</p> <p class="mt-4"><img src="../assets/winddirection.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.derivedWindDirection }}</p> </div> <div class="col-sm-6 col-md-6 mt-2"> <p class="card-sub-heading mt-3">Wind Speed</p> <p class="mt-4"><img src="../assets/windspeed.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.windSpeed }} km/h</p> </div> </div> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return {}; }, methods: {}, computed: {} }; </script> Wrapping Up With Highlights.vue

Recall that we have already implemented code with CSS for all the components — except Content.vue and Highlights.vue. Since Content.vue is a dumb component that just relays data, the minimal styling it needs has already been covered. Also, we have already written appropriate code for styling the sidebar and the cards containing the charts. Therefore, all we are left to do is add some stylistic bits to Highlights.vue, which primarily involves using the CSS classes:

<template> <div class="custom-content-card content-card card"> <div class="card-body pb-0"> <div class="content-header h4 text-center pt-2 pb-3">Highlights</div> <div class="row"> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </div> </div> </template> <script> import UVIndex from "./UVIndex.vue"; import Visibility from "./Visibility.vue"; import WindStatus from "./WindStatus.vue"; export default { props: ["highlights"], components: { "uv-index": UVIndex, "visibility": Visibility, "wind-status": WindStatus, }, }; </script> Deployment And Source Code

With the charts and style in order, we are done! Take a moment to appreciate the beauty of your creation.

(Large preview)

It’s now time for you to deploy your application, and share it with your peers. If you don’t have much idea about deployment and expect us to help you, look here about our take on deployment ideas. The linked article also contains suggestions on how to remove the FusionCharts watermark at the left bottom of every chart.

If you mess up somewhere and want a reference point, the source code is available on Github.

(ms, ra, il)
Categories: Around The Web

Spread The Love! Inspiring Wallpapers For February 2019

Smashing Magazine - Thu, 01/31/2019 - 6:09am
Spread The Love! Inspiring Wallpapers For February 2019 Spread The Love! Inspiring Wallpapers For February 2019 Cosima Mielke 2019-01-31T12:09:12+01:00 2019-02-18T13:24:09+00:00

As designers we usually turn to different sources of inspiration, and, well, sometimes the best inspiration lies right in front of us. With that in mind, we embarked on our wallpapers creativity mission more than nine years ago. The idea: to provide you with unique and inspiring desktop wallpapers each month anew. Wallpapers created by the community for the community.

We are very thankful to all artists and designers who have contributed and are still diligently contributing to this challenge, who tickle their creativity to keep the steady stream of wallpapers flowing. This post features their artworks for February 2019. All wallpapers come in two versions — with and without a calendar — and can be downloaded for free. At the end of this post, we also collected some wallpaper favorites from past February editions for you. After all, some things are just too good to be forgotten, right?

Please note that:

  • All images can be clicked on and lead to the preview of the wallpaper,
  • You can feature your work in our magazine by taking part in our Desktop Wallpaper Calendar series. We are regularly looking for creative designers and artists to be featured on Smashing Magazine. Are you one of them?
Further Reading on SmashingMag:

Meet Smashing Book 6 — our brand new book focused on real challenges and real front-end solutions in the real world: from design systems and accessible single-page apps to CSS Custom Properties, CSS Grid, Service Workers, performance, AR/VR and responsive art direction. With Marcy Sutton, Yoav Weiss, Lyza D. Gardner, Laura Elizabeth and many others.

Table of Contents → Savannah Stroll

“February is a month focused on romance and as a southerner, I can’t think of a more romantic way to spend a day than strolling the historic moss-draped streets of Savannah, GA. Spending time sitting on a bench on one of the many beautiful squares, holding hands and people watching as you sip a cappuccino.” — Designed by Heather Ozee Designs from the United States.

Love Is Worth Fighting For

Designed by Maria Keller from Mexico.

Dark Temptation

“A dark romantic feel, walking through the city on a dark and rainy night.” — Designed by Matthew Talebi from the United States.

Feel The Love!

“We’re celebrating Valentine’s Day with our February wallpaper. Whatever Valentine’s Day means to you we’re on board, love, relationships or the best of friends. It’s something to be celebrated!” — Designed by Focus from the United Kingdom.

Cold And Frost

“Frosts in Russia are very severe. Walking through the Park, I found these branches at the top and decided to capture the moment.” — Designed by Nikolay Belikov from Russia.

What Is Love

“We all feel it, even if we cannot define it. But, who needs a definition anyway? When you sense it in your gut and feel like standing on the top of the world. Happy. From the bottom of your heart. ‘Where there is love, there is life,’ said Mahatma Gandhi. And we couldn't agree more. May the love be with you. Always.” — Designed by PopArt Studio from Serbia.

Polar Cold

“February is the month of the bear. I would like to be in the Arctic and play with the bears.” — Designed by Verónica Valenzuela from Spain.

Lovely Day

Designed by Ricardo Gimenes from Sweden.

Let Love Speak

“As it’s February, we just thought of celebrating Valentine month. Let the love between nature and human being prosper. These days we are forgetting to take care of our nature. So it’s a kind of gentle reminder for all of us.” — Designed by Sweans Technologies from London.

Umbrella Day

“Always good to have an umbrella, on rainy or sunny days! On the 10th of February we celebrate the umbrellas.” — Designed by Melissa Bogemans from Belgium.

Sunset

“I want to emphasize February 14th because it’s Valentine’s Day. I thought it would be fun to use two cats that are in love watching the sunset in the park. I have tried to use as many warm and loving colors as possible, such as red, rose, orange and purple.” — Designed by Vince Teckmans from Belgium.

World Radio Day

“Music is an important element in our everyday lives. It connects people all over the world, regardless of culture, religion etc.” — Designed by Ilke Cauwenbergh from Belgium.

Oldies But Goodies

No matter if it’s a brave icebreaker that confronts even the most adverse weather conditions, a piece of design wisdom, or a pun — a lot of things have inspired our artists to design a February wallpaper in the past few years. Below you’ll find a little best-of. Please note that these wallpapers don’t come with a calendar.

Dog Year Ahead

Designed by PopArt Studio from Serbia.

Balloons

Designed by Xenia Latii from Germany.

Febpurrary

“I was doodling pictures of my cat one day and decided I could turn it into a fun wallpaper – because a cold, winter night in February is the perfect time for staying in and cuddling with your cat, your significant other, or both!” — Designed by Angelia DiAntonio from Ohio, USA.

Minimalistic Love

“This minimalistic love logo is designed from geometric shapes, where the heart represents the letter ‘O’ and love. The anchor represents the letter ‘V’ very delicately and stylish and it symbolizes your wanderlust. The anchor is a symbol of adventure and travels.” — Designed by Antun Hirsman from Croatia.

“Greben” Icebreaker

“Danube is Europe’s second largest river, connecting 10 different countries. In these cold days, when ice paralyzes rivers and closes waterways, a small but brave icebreaker called Greben (Serbian word for ‘reef’) seems stronger than winter. It cuts through the ice on Đerdap gorge (Iron Gate) – the longest and biggest gorge in Europe – thus helping the production of electricity in the power plant. This is our way to give thanks to Greben!” — Designed by PopArt Studio from Serbia.

Winter Wonderland

“In February, nature shows its creativity. Our artwork occurs when it is being drawn.” — Designed by Ana Masnikosa from Belgrade, Serbia.

Love Angel Vader

“Valentine’s Day is coming? Noooooooooooo!” — Designed by Ricardo Gimenes from Sweden.

Febrewery

“I live in Madison, WI USA, which is famous for its breweries. Wisconsin even named their baseball team “The Brewers.” If you like beer, brats, and lots of cheese, it’s the place for you!” — Designed by Danny Gugger from the United States.

Farewell, Winter…

“Although I love winter (mostly because of the fun winter sports), there are other great activities ahead. February, the last winter month, this year is even one day longer. But I don’t mind. Thanks, winter, and see you next year!” — Designed by Igor Izhik from Canada.

Principles Of Good Design

“The simplicity seen in the work of Dieter Rams which has ensured his designs from the 50s and 60s still hold a strong appeal.” — Designed by Vinu Chaitanya from India.

I Believe I Can Fly

Designed by Elise Vanoorbeek from Belgium.

Out There, There’s Someone Like You

“I am a true believer that out there in this world there is another person who is just like us, the problem is to find her/him.” — Designed by Maria Keller from Mexico.

The Great Beyond

“My inspiration came mostly from ‘The Greay from’. It’s about a dog and an astronaut exploring a strange new world.” — Designed by Lars Pauwels from Belgium.

Join In Next Month!

Please note that we respect and carefully consider the ideas and motivation behind each and every artist’s work. This is why we give all artists the full freedom to explore their creativity and express emotions and experience throughout their works. This is also why the themes of the wallpapers weren’t anyhow influenced by us but rather designed from scratch by the artists themselves.

Thank you to all designers for their participation. Join in next month!

Categories: Around The Web

Adobe Experience Manager vs. WordPress: The Authoring Experience Compared

Smashing Magazine - Wed, 01/30/2019 - 7:45am
Adobe Experience Manager vs. WordPress: The Authoring Experience Compared Adobe Experience Manager vs. WordPress: The Authoring Experience Compared Kevin Weber 2019-01-30T13:45:55+01:00 2019-02-18T13:24:09+00:00

Thanks, WordPress and Gutenberg, for making block-based editing the standard for authoring web pages. In this article, I’m going to compare the new authoring experience in WordPress with the experience from Adobe Experience Manager (AEM), an enterprise content management system that also embraces block-based editing. I’ve implemented both WordPress and AEM for multiple companies (such as Informatica and Twitter) and had to realize that despite the authoring experience is critical for non-technical authors, it is often neglected by developers.

Note: With the term “authoring experience” I’m referring to the user experience for those people whose goal it is to create and publish content on a website. I’m not referring to the people that are going to consume the published content. If you haven’t thought about the authoring experience before, here’s a primer by Eileen Webb who was also featured in Smashing Book 5.

Adobe Experience Manager is, compared to WordPress, a complex system with a steep learning curve, especially for developers. At the same time, AEM is easier to use than more established and more expensive content management solutions, placing AEM somewhere in between free and very costly solutions.

From a technical perspective, AEM is a conglomerate of open source technologies with several touches from Adobe, placing AEM somewhere in between open-source and proprietary software. It is those touches from Adobe that make the system shiny and usable. For example, a visual drag and drop page builder has been the standard way for creating pages in AEM — long before WordPress Gutenberg was born.

Let’s take a look at some of the features that elevate the authoring experience above average.

Our new book, in which Alla Kholmatova explores how to create effective and maintainable design systems to design great digital products. Meet Design Systems, with common traps, gotchas and the lessons Alla has learned over the years.

Table of Contents → Components (Blocks)

One of the most significant ideas for websites is the concept of a component (or block in WordPress lingo). A component represents a piece of content that follows specific rules instead of being a blob of anything. For example, you can have a video component where the author can only paste in a Youtube link and control Youtube-specific settings. Or you can have a quote component where the author adds a quote to one text field and the name of the person being quoted to another text field. You can even have a layout component that contains other components and displays them below each other on a mobile device, whereas on a large screen, those components get spread across three columns.

Authors can update components right in the visual editor. Available editing options get displayed based on the selected component. (Image source) (Large preview)

An author knows exactly what to expect from a specific component and can easily fill it with the appropriate content. Equally important are the long-term benefits and new opportunities that wouldn’t be feasible for the old school “one text field fits all content” approach that has been prevalent for the past decades:

  • If a component requires a date input, the component authoring dialog can display a date picker instead of a plain text field, making it easier for the author to pick a date with the right format.
  • If a designer wants the name of a quoted person to be displayed above the quote instead of below the quote, the developer can easily rearrange the code because the quote and the name are stored separately. If the quote and name would be stored the old-fashioned way, the developer would have to manually extract the name from the text blob and move it in front of the quote.
  • If a quote needs to be translated from English to German, the quote can be submitted to a translation service. If the translation service has translated this quote before already, it can return the saved translation. If the quote would be part of a longer paragraph instead of being standalone, the translation would be much harder and probably require a human translator.
  • If a video lacks a transcript and therefore prevents deaf users from consuming it, the component can be complemented with a summary text that makes the video more accessible to deaf users.

Component-based editing has been embraced by users of AEM for a while already, and because of the arrival of Gutenberg in WordPress 5.0, component-based editors are now the de facto standard for authoring web pages.

Note: Leonardo Losoviz dives deeper into the implications of blocks in the context of WordPress.

Fragments

Content fragments and experience fragments are new terms that have been dominating the AEM scene for the past year. I’ll summarize those two concepts simply as fragments. In essence, fragments allow authors to create neutral content that can be used across web, mobile, social media and other channels.

Fragments are created outside of a page editor and are, compared to a component, less opinionated about how their data is going to be used. Let’s imagine a fragment called “Quote of the day” that authors update once a day with a new quote. Now the quoted text of this fragment can be used in a variety of places:

  • A footer widget displays the quote of the day at the bottom of every page. As soon as an author updates the fragment, the footer updates as well. The fragment determines what is going to be displayed whereas the footer widget determines how the quote is going to be displayed.
  • A quote component allows authors to import a quote from past “Quotes of the day” and add it to the blog post.
  • A plugin adds a “Share quote of the day” button to the homepage. Whenever someone clicks on that button, the plugin takes the quote of the day and formats it to meet best practices for sharing via Facebook, Twitter, and email.
Using an editor for fragments, authors focus on related data without having to know how exactly it is going to be displayed on websites and in apps. (Image source) (Large preview)

In WordPress, widgets and menus resemble fragments: Authors create menu items in a neutral interface, then developers display those items as part of the theme in a way that makes sense for the theme. If the theme gets replaced with a new theme, those menu items persist and can be displayed in the new theme as well, even though the new theme might look very different from the previous one.

I expect fragments to become more widely used, even though the concept has different names in different systems. Indeed, Matt Mullenweg already announced that his team is currently focusing on “expanding the block interface to other aspects of content management [including the creation of] a navigation menu block [and] porting all widgets over to blocks.”

Page Templates

Pages templates can be described as higher-level components because they include several other components. In AEM, authors can create templates that lock components such as a header component into a fixed position while also defining flexible areas where components can be added on a per page basis.

The template on this screen provides a heading, image and text component by default. It prevents authors from removing the “Text Locked” text and allows authors to add more components below that text. (Image source) (Large preview)

One important aspect of this is that such a flexible area can limit which components can go into it. That way you can create page templates for different purposes:

  • Template #1: Article page template
    Header, title, content area and footer are fixed. The author can update the title component but can’t remove it. The author can drop text, image and video components into the content area.
  • Template #2: Landing page template
    Only a logo and a title component at the top of the page are fixed. The author can choose among a set of landing page-specific components that are optimized for converting visitors into customers.
Permissions And Workflows

It’s unlikely that every author in a large team should be able to modify critical templates such as the article page template. In order to prevent people from being able to accidentally and irrevocably break the site, it is important to define who can modify which part of the site. Welcome to the concept of permissions and workflows. This concept is neither new nor special, but it’s important for large teams.

Yes, AEM’s interface for handling permissions could need a facelift. Anyhow, it works. (Image source) (Large preview)

A typical AEM site includes the actual production website and at least one production-like site, aka staging. Authors can publish content to a private staging site before publishing it to the public production site. The process of publishing content to staging followed by publishing content to production can be called a workflow. Another common type of workflow is that the content must go through an approval process before it can be published to the production site, and only certain people are able to hit the “publish to production” button.

This page indicates that a workflow has been initiated and the author can either complete or delegate the “Request for Activation”. (Image source) (Large preview)

Permissions and workflows are features that are negligible on small teams. However, as a team grows, those features become critical for the productivity and success of the team. Despite AEM comes with the basics for creating workflows and developers can make AEM work for any specific need, it requires quite some code changes and isn’t implemented with the snap of a finger. This is even more true for WordPress. It would be nice to have an authoring-friendly tool to create custom workflows.

Imagine how a user-friendly tool could simplify the creation of workflows. Here is how the creation of workflows in Github will look like once Github Actions are out of beta. (Large preview) Editing Modes

In AEM, authors can quickly edit and view each page in different modes. The author switches between modes based on what job needs to get done:

  • To arrange components and edit their content, choose Edit mode.
  • To change how components should be arranged on an iPad, choose Layout mode.
  • To look at the content as if you were a visitor, choose Preview mode.
On this page responsive layout mode is active. The author can emulate different device sizes and adjust the position of components within a responsive grid. (Image source) (Large preview)

There are a few more modes that show up based on how the site is set up. One ideal scenario is that A/B testing and personalization is set up by integrating AEM with Adobe Target. Using Targeting mode, authors can define when to show certain components based on a visitor’s location, age, referral page, the time of the day, etc.

Integrations in AEM are comparable to plugins in WordPress but with the difference that AEM integrations are more complex and usually custom-tailored. Especially integrating AEM Target can be more painful than salespeople make it sound.

Categories: Around The Web

Understanding API-Based Platforms: A Guide For Product Managers

Smashing Magazine - Tue, 01/29/2019 - 7:00am
Understanding API-Based Platforms: A Guide For Product Managers Understanding API-Based Platforms: A Guide For Product Managers Michał Sędzielewski 2019-01-29T13:00:57+01:00 2019-02-18T13:24:09+00:00

To build a digital product today is to integrate the myriad of various back-office systems with customer touchpoints and devices. The cost of engaging a software team to connect them in a single working solution might sky-rocket.

This is why modern product managers, when choosing vendors, often put integration capabilities in the first place which may come down to choosing systems exposing API. What’s the API and how to test it without engaging your tech team? Read on.

Embrace The Data: Why We Need APIs At All

Customer data change how the business operates. If properly collected and shifted around, they can help companies shoot up customer acquisition and retention rates, leading eventually to a burst in income.

But data crunching is a tedious job. That’s why business tapped into computer science. In the 1990s, the databases which automated the most time-consuming data tasks became massively popular across marketing departments. This led to a massive shift in how marketing strategies were conceived — that shift was called the data-driven approach.

Databases had a major con, though. To make them something of value, a company needed to hire software engineers. They were the heroes who knew how to turn huge piles of data into working insights. They were also the guards protecting data integrity and thus making sure the system was future-proof.

But software engineers cost a lot, and their communication interface required effort.

When the number of data collection channels spanned over several departments and even external companies, databases and their operators became a bottleneck. Businesses needed to find an automated way of accessing data stores.

This is how the idea of API-first systems originated.

Getting workflow just right ain’t an easy task. So are proper estimates. Or alignment among different departments. That’s why we’ve set up “this-is-how-I-work”-sessions — with smart cookies sharing what works well for them. A part of the Smashing Membership, of course.

Explore Smashing Membership ↬ What The API Actually Is Without The Tech Lingo

API-first systems, today commonly shortened as API (Application Programmable Interface), are the applications which ensure that other systems can access their data in a unified and secure way.

Without a computer science grade, Application Programmable Interface doesn’t really ring a bell. Let’s have a look at a more tangible explanation.

One of the best analogies I’ve found in the web so far has been written by Taija:

“If you go to a restaurant as a customer, you are not allowed to enter the kitchen. You need to know what is available. For that, you have the menu. After looking at the menu, you make an order to a waiter, who passes it to the kitchen and who will then deliver what you have asked for. The waiter can only deliver what the kitchen can provide.

Best API analogy I've seen: take a restaurant. The menu is the API, your order is the API call, the food from the kitchen is the response.

— Aarthi 發財 ! (@AarthiD) 19. Dezember 2013 How does that relate to an API? The waiter is the API. You are someone who is asking for service. In other words, you are an API customer or consumer. The menu is the documentation which explains what you can ask for from the API. The kitchen is, for example, a server; a database that holds only a certain type of data  —  whatever the buyer has bought for the restaurant as ingredients and what the chef has decided they will offer and what the cooks know how to prepare.”

So again:

  • Kitchen
    The database, no customers allowed to protect data integrity.
  • Waiter
    The API, a middleman that knows how to serve data from the database without disrupting its functioning.
  • Customer
    An external system that wants to get their data
  • Menu
    The data format reference the external systems have to use to perform their operation.
  • Order
    An actual single API call.

With the current state of technology, it still takes a software developer to “make an order.” But it’s way faster (read: cheaper) because the menu, like McDonald’s, is more or less standardized across the world.

So now, we’re going to wear a software developer’s shoes and try to call an exemplary API. Don’t worry; we’re not going to go beyond school computer science classes.

How Your Weather App Gets The Data: API Basics

We’re going to find out how your weather app knows the current temperature. In this way, we’ll get the basics of how to communicate with systems over the internet.

What we need:

  • A weather database
  • A browser
  • A dash of willpower

That’s it! Today’s technology makes it easy to test the API without the need for big developer tools.

Of course, that’s different when you want to create a full-blown integration. When push comes to shove, you need to know more advanced tools and programming languages, but for testing/proof of concepts, this setup is enough.

So, let’s try to get the current temperature index for your city — or, in the parlance of coders — let’s invoke the first API call. After all, it boils down to sending some text to a server and receiving a message in exchange.

The Anatomy Of An API Request

In this article, we’ll be using the https://openweathermap.org API. Visit the site and try checking weather conditions in several locations. Hopefully, you’re feeling better than me in Katowice today:

Open Weather Map API widget (Large preview)

As you might have guessed, the website is calling the API to get the data. Developers implemented it in a way that every time you press search, behind the scenes the application knocks the API’s door and says “give me <city> temperature.”

Let’s put on a hacker hat and see the API calls this website is calling with your browser. You can use Developer Tools in your Browser to see what’s happening behind the scenes:

  1. In Chrome, go to Menu → More tools → Developer Tools;
  2. Switch to Network tab;
  3. Try checking temperature in different cities in the widget above;
  4. In the list on the bottom, you’ll notice links which have been called:
    Requests monitor in Chrome Developer Tools (Large preview)
    If you copy the link, you can see it includes the location name and a couple of other parameters.
    https://openweathermap.org/data/2.5/find?callback=jQuery19103887954878001505_1542285819413&q=Katowice&type=like&sort=population&cnt=30&appid=b6907d289e10d714a6e88b30761fae22&_=1542285819418
  5. When you paste the link to the browser’s address bar, you should see the API replies with:
    jQuery19103887954878001505_1542285819413({"message":"accurate","cod":"200","count":1,"list":[{"id":3096472,"name":"Katowice","coord":{"lat":50.2599,"lon":19.0216},"main":{"temp":281.69,"pressure":1031,"humidity":61,"temp_min":281.15,"temp_max":282.15},"dt":1542285000,"wind":{"speed":3.6,"deg":50},"sys":{"country":"PL"},"rain":null,"snow":null,"clouds":{"all":90},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}]}]})
  6. It’s a bit chaotic, but if you take out the content of parentheses and run it with a data formatter, you’ll see a structure that makes sense:
    { "message":"accurate", "cod":"200", "count":1, "list":[ { "id":3096472, "name":"Katowice", "coord":{ "lat":50.2599, "lon":19.0216 }, "main":{ "temp":281.69, "pressure":1031, "humidity":61, "temp_min":281.15, "temp_max":282.15 }, "dt":1542285000, "wind":{ "speed":3.6, "deg":50 }, "sys":{ "country":"PL" }, "rain":null, "snow":null, "clouds":{ "all":90 },
  7. The reply from the API is a data structure with information about the current weather conditions — you should easily decrypt most of the parameters. This format of data is called JSON. This is an important notation because most of modern APIs use it. This pile of idents and brackets serves one purpose — it’s easier for an application to parse a well-structured message than a randomly placed text.

A word of explanation of what we’ve just done here.

The web application behind the Open Weather Map website takes the data from the API and displays it on the website.

Every time you type the city name and press search, the website connects to a server with a specific link which includes the name of the city as a parameter.

The same sentence in the tech jargon: the application behind the website sends a request to an API endpoint providing the name of the city as an argument.

Then, the API replies (sends an API response) with a text message formatted as JSON.

Webapp <—> Database diagram (Large preview)

To create an API request you need to put together its address. Yeah, the address is a good analogy. To ship something you need to provide the courier with:

  • City,
  • Street and number,
  • Sometimes some extra information on how to get to your office.

And, to connect to the API, by analogy, you need:

  • https://openweathermap.org/ (link)
    The city or root-endpoint — a starting point, an internet address of a server you want to connect to, in our case.
  • data/2.5/find (link)
    The street number or the path — determines the resource you want to get from an API.
  • ?callback=jQuery19103887954878001505_1542285819413&q=Katowice&type=like&sort=population&cnt=30&appid=b6907d289e10d714a6e88b30761fae22&_=1542285819418 (link)
    The extra info or the query parameters — let the API server know what we want to get in particular and what structure and order the data should have.

This is how APIs are designed. The root-endpoint usually stays the same for a single vendor, then you need to figure out what path and query parameters are available and what information the API development team put behind them.

Now let’s put the hacker hat a bit tighter. In our case, not all query parameters are necessary to get the weather data. Try removing different parameters after the question mark (?) and check how the Weather API replies.

For example, you can start by removing callback from the request link:

https://openweathermap.org/data/2.5/find?callback=jQuery19103887954878001505_1542285819413&q=Katowice&type=like&sort=population&cnt=30&appid=b6907d289e10d714a6e88b30761fae22&_=1542285819418

The result:

https://openweathermap.org/data/2.5/find?q=Katowice&type=like&sort=population&cnt=30&appid=b6907d289e10d714a6e88b30761fae22&_=1542285819418

If you play around with the other ones, you can see that some of them are optional too. Actually only q and appid are mandatory:

https://openweathermap.org/data/2.5/find?q=Katowice&appid=b6907d289e10d714a6e88b30761fae22

How do you know what’s mandatory and what’s optional? How do you know where to get the root-endpoint and path in the first place?

API Documentation: A Must-Read Before You Start

You always need to check the API documentation first to learn how to construct your request the right way.

In our case, the documentation https://openweathermap.org/current shows the available endpoints. It also explains all response data fields — so you can find what information the API will reply even before you even send a request.

A good API documentation offers quick start tutorials on how to create simple requests and moves on to more advanced stuff. Fortunately, the Open Weather API has one and we’re going to use it now.

Creating An API Call From Scratch

Let’s sum up our findings. We’ve already sent a request to the API. We’ve figured out the correct link by sniffing what OpenWeatherMap does behind the scenes. This approach is called reverse-engineering and it’s often hard or not possible at all.

Moreover, most of the times, API providers ban users from over-using this option. That’s why we should learn how to “call” the API by the rules (meaning — documentation).

One way to do this is to code it. But as we’re not coders (yet!), we’re going to use tools that make this easier. So much easier that even software developers have it under their toolbelt.

As promised, we won’t leave the browser. But we need to install an extension (Chrome only) — Postman. This simple plugin turns your browser into an API connector.

Postman main view (Large preview)

OK, now that we have a tool, let’s take a look into the documentation to see how we can get current weather conditions for a specific city name https://openweathermap.org/current#name.

The docs say we should use the following endpoint: api.openweathermap.org/data/2.5/weather?q={city name}

When we break it down we get the following elements:

  • Root-endpoint: api.openweathermap.org
  • Path: data/2.5/weather
  • Query parameter: q={city name} (this notion means that we should replace the braces with a specific city name)

Let’s put it into Postman. The process boils down to three easy steps:

  1. Click on ‘Request’ in the top menu.
    Postman new request view (Large preview)
  2. Name your request and provide the catalog name in the section at the bottom as well. Postman request name view (Large preview)
  3. Paste the API endpoint you want to call, click Send, and you should see the API response in the Response section: Sending the first request with Postman (Large preview)

Congrats! You’ve just successfully called your fir… wait a second! Let’s pay attention to the API response:

Invalid response example (Large preview)

It’s not a JSON filled with weather information we’ve seen before. What do the 401 and Invalid API key mean at all? Is our documentation wrong?

Authentication

You wouldn’t let anybody access your cocktail cabinet without your permission, would you? By the same token, API providers also want to control the users of their product to protect it from malicious activity. What’s malicious activity? For example, sending many API requests at the same time, which will “overheat” the server and cause downtime for other users.

How can you control the access? The same way as you guard your drinks! By using keys — API keys.

If you visit the How to start guide from Weather API documentation, you’ll notice how you can get your key. Sign up now and check your inbox.

So now the question is how to use the key? It’s easy, according to the docs, just copy and paste the key at the end of your endpoint URL (without braces).

api.openweathermap.org/data/2.5/weather?q=Katowice&appid={your API key}

And click send again. Here you go, we can now see the API response!

Categories: Around The Web

How To Sound Like A Cloud Expert

Smashing Magazine - Mon, 01/28/2019 - 6:00am
How To Sound Like A Cloud Expert How To Sound Like A Cloud Expert Zack Grossbart & Eduardo Abe 2019-01-28T12:00:47+01:00 2019-02-18T11:03:59+00:00

Your code is written and the design looks great. The new project is almost ready to go when the client asks, “Should this run in the cloud?”

You break out in a cold sweat. The question is massive. Regions and zones, high availability, load balancing — the cloud has its own language.

Don’t worry; you’ve got this. This article will teach you how to make smart decisions about the cloud, and answer your client’s cloud questions.

Four Big Questions

Before you and your client can know what kind of cloud you want, you need to discuss four questions:

  • How complex is the software you need to run?
  • How much does it need to scale?
  • How important is it that it never goes down?
  • How fast does it need to run for users around the world?

This article gives you the background and information you need to answer these questions and sound like a cloud expert.

Let’s begin.

  1. What Is A Cloud?
  2. Why Your Client Cares About The Cloud
  3. How Is A Cloud Different From A Hosting Service?
  4. Why Virtual Machines Matter So Much
  5. Let’s Talk A Little About Networking
  6. The Different Types Of Clouds
  7. The Basic Pieces Of A Cloud
  8. Questions Your Clients Are Likely To Ask

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬ What Is A Cloud?

When we talk about cloud computing, we really mean the ability to rent a piece of a computer from someone else. That’s all there is to it.

Companies like Amazon and Google have a lot of computers and they’re willing to rent parts of them to you. Renting computers from them is cost-effective because you don’t have to build your own data centers or hire your own staff of experts to run them.

When you rent a part of a computer, you need it to look like a whole computer so you can run any software you want. That’s why providers give you a virtual machine (VM) — software that makes it look like you’re running on your own separate computer.

Why Your Client Cares About The Cloud

Before you learn more about the cloud, it’s important to understand why your client cares. Let’s not dismiss the allure of buzzwords; the cloud is really trendy right now. Your client may just be asking because all the cool kids are doing it, but there are reasons the cool kids are doing it.

Let’s start with the basics. Hosting your own data center would be a pain in the rear. You’d have to worry about power consumption, keeping your hardware up to date, hiring a team of experts to run it, and a thousand other problems that have nothing to do with your business. What would happen if the power went out, there was a flood, or the roof caved in? These are all reasons not to host your website on a server running in your living room.

Not only can you pass on all the headaches to someone else, but having them run the data center gives you three big advantages:

  1. Clouds are global.
    They exist in data centers around the world, including one near your client. That means speed. You don’t want customers in China waiting for data to load from the United States. When I go to Google.com, I get a different data center in Boston than I would in Chicago or L.A., and that’s just in the U.S. That’s a large part of what makes Google’s speed possible.
  2. Clouds grow and shrink.
    If I buy a server, I have one server; even if my app doesn’t need the whole computer, I still need to pay for that server. When my app gets really popular, I need to buy more servers fast. The cloud doesn’t work like that. Renting shares of servers means I can change how much I’m renting: I can scale up the order when I’m busy, and scale it down when I don’t need as much.
  3. Clouds never go down.
    Never say never…but almost never. Cloud providers talk about “five nines” — that means being up 99.999% of the time (with just 5.26 minutes of downtime a year). You can make that even smaller with services like load balancing and failover.

Those are all reasons clouds can be cool, but you can get some of them from a simple hosting service. If your client is asking about the cloud, you need to know the difference.

How Is A Cloud Different From A Hosting Service?

I have a personal website on a hosting service named Media Temple. My site runs WordPress, so it needs a few things:

  • A directory to put my files in
  • An HTTP server
  • A database
  • PHP

My directory runs on Linux, my HTTP server is Apache, my database is MySQL, and it all runs on PHP; that’s why they call it a LAMP stack (Linux-Apache-MySQL-PHP). That may sound like a lot of pieces, but they’re limited. For example, I can’t install new software. If I want to run my database on PostgreSQL, I’m out of luck. I can’t run other languages like Python or Go; I can’t write my own separate programs. I only get a very limited, pre-configured set of things I’m allowed to do.

My website also only runs on one server in one place. Where is that server? I have no idea. I think it’s somewhere in the United States, but other than that I don’t know, and I don’t really care. The hosting provider gives me a single server, I type in a URL, and my site comes up (most of the time).

Hosting providers keep it simple. Some of them host other stacks and some allow a little more configuration, but it’s always a set package.

The fundamental difference between a hosting service and a cloud is the virtual machine. A hosting service just gives me part of an existing operating system. A virtual machine gives me an entire operating system all to myself.

Why Virtual Machines Matter So Much

A virtual machine acts just like a real machine. It can run Linux or Windows and it can do anything a normal computer can do. Apple doesn’t allow you to run OS X on a virtual machine (although a few people have made it work, building a “Hackintosh”).

When you have a virtual machine you have total control. You can run anything you want there — databases, email servers, encryption, even searches for extraterrestrials. The virtual machine allows you to do anything you want.

Having an entire operating system all to yourself is really powerful, but before you can do anything useful you need to access the VM.

Let’s Talk A Little About Networking

Virtual machines are useless if you can’t get to them. You need networking, although networking can get a little complex.

Networking isn’t this difficult. (Large preview)

But these basics will give you what you need to get started. Let’s start with an example you’ll be familiar with. It’s probably running in your house right now.

My home network (Large preview)

I have Comcast at home. Comcast gives me a cable modem with an IP address like 10.0.0.89. While it only gives me one IP, I have two laptops, an iPad, and a phone; my wife and daughter have even more devices. To make that work, I have a wireless router that connects to my cable modem. My wireless router gives every device I have an IP address like 192.168.0.100, 192.168.0.101, and so on, but those addresses are private to my network.

The technical term for those private addresses is non-routable. There are a few addresses that are set up for private use; most start with 10. or 192.168, as a sign to Internet routers that these addresses aren’t allowed out in the wild. I’m using these special addresses because they’re reusable.

Every IP address must be unique in a given network; otherwise, the router wouldn’t know which computer I wanted to connect to. There are 4,294,967,296 possible IP addresses. Although network designers thought that was a lot when all of this started in the 1970s, we’re now running out. There are some other protocols, like IPv6, that may solve this problem in the future, but today we solve this problem with Network Address Translation (NAT). Let me show you how it works.

The devices in my house have addresses that are unique in my house, but not in the entire world. When I want to get out to the Internet, I need the wireless router to translate them for me. Every time I click a link, my laptop talks to my wireless router to make the request for me; in turn the wireless router talks to the world on my behalf, but using its own address. Then my cable modem does the same thing when it talks to Comcast, and Comcast does the same thing again on a much larger scale when it sends my request out to the general Internet. Each router is translating the IP address from the one before it. All of this means that many computers can reuse an IP address like 192.168.0.101, and it all works out without conflicts.

So what’s my real IP address on the real Internet? Right now my real IP address is 66.30.118.150 and my private IP address is 192.168.0.103.

Where did the address 66.30.118.150 come from? It’s an IP address that Comcast owns. It makes it clear that I’m in Cambridge, Massachusetts, in the northeastern United States. I don’t need the details; all I need to know is that it’s a real public address that Comcast manages for me. Comcast probably uses the same address for hundreds or thousands of other people.

All of this address translation does something else very important. It gives me a single point of access that controls everything that goes into my home network. Nobody outside can access 192.168.0.103; to them, it’s a different address. I get a lot of security by controlling what traffic can come in and out. My wireless router is a pinch point where I can control what happens.

Most clouds work just like my home network. The computers are virtual, networks are called subnets, and the wireless router is called a gateway, but it’s all the same thing. They have virtual machines with private addresses and gateways that translate them to public addresses. They also have a space of addresses that they can use just like my wireless router at home. Like this:

My cloud network (Large preview)

It might sound complicated, but it’s not too hard. A cloud is a way for me to rent computers from someone else and set them up to look like my home network.

The most important things to remember are:

  • Private IPs are only available on your private network;
  • Public IPs are available on the Internet;
  • NAT allows private IPs to look like public IPs.

That’s not everything that happens with cloud networking, but it’s more than enough to get up and running and access your cloud. Now you need to decide what kind of cloud you want.

The Different Types Of Clouds

Cloud is an amorphous term — people use it to mean a lot of different things. There are really three different categories of clouds.

Infrastructure Clouds

The virtual machine and network are the building blocks for an infrastructure cloud, also known as Infrastructure as a Service (IaaS). They provide the virtualized infrastructure, which you have full control over. You decide on the operating system and everything else that runs on top of it.

You get flexibility and control, but you’re responsible for managing and supporting everything you install.

Infrastructure clouds are either static or elastic. A static cloud works like my home network: I have a set of virtual machines running whatever I need them to. They live on a private network, with a public gateway that grants them access to the Internet. Static clouds are great for processing data, having some extra computing power, or hosting a more complex site than a hosting provider can handle. You can also replicate your static cloud to other data centers around the world.

Elastic clouds work like static clouds, but they’re dynamic. Instead of a fixed set of virtual servers, you have a set that can grow or shrink depending on your needs. Your cloud expands when you have high demand on your site or service, and shrinks back to normal size when you don’t. All the expanding and shrinking saves you money. You give back the computing power you don’t need when you don’t need it.

Netflix uses IaaS. Amazon Web Services provides the infrastructure, and Netflix implements its entire system on top; it wrote its own software to provide highly customized support for streaming high-definition content. The Weather Company is another example — it runs on top of the IBM Cloud.

Platform Clouds

A platform cloud, also known as Platform as a Service (PaaS), is a specialized cloud that provides software building blocks for your application while the cloud provider manages the infrastructure and software stack for you.

For instance, if you needed a web application, PaaS could provide you with vanilla WordPress or Drupal for you to use. If you needed a database, you could pick MySQL or PostgreSQL. If you need development tools, you might choose from Node, Java, or PHP. You don’t need to worry what operating system is running, or whether a MySQL security patch needs to be applied — the cloud provider takes care of that for you.

Heroku is a PaaS cloud. It provides the software underneath, and you just write what you want on top of that. It gives you some flexibility, but does a lot more management than an IaaS cloud.

Software Clouds

A software cloud, also known as Software as a Service (SaaS), is a very specialized type of cloud that provides you with a well-defined online service. Hosting providers are a special type of SaaS cloud under the covers; they work in a very limited way.

Wix is another example of SaaS. It provides you a complete web application hosting service with a great editor, support for user login and payment, and a wide variety of templates to choose from. Wix focuses on just one job. It’s very limited in functionality, but also easier to use.

The Basic Pieces Of A Cloud

We’ve talked about the different types of clouds and why you need them. Before you can really sound like a cloud expert, you need to know the different components that make up a cloud.

Virtual Machines

A virtual machine is a software that runs like hardware, acting like a real machine without ever needing a separate server anywhere. You can do whatever you want with the VM, and it will pass the duck test — it walks and quacks like a real server. Your software will never know the difference.

This flexibility has a price. You’re responsible for maintaining your virtual machine. The cloud provider will make sure the hardware is in top condition, but you’ll have to select your operating system and everything else that runs on top of it — security patches, software updates, configuration. It’s all on you.

And if you’re not on an elastic cloud, you’d better remember to release any virtual machines you don’t need anymore. The provider will charge you whether the VM is doing work or not.

Subnets

We covered cloud networking already. Subnets are networks that run in the cloud.

Private IPs And Public IPs

The cloud network will assign a non-routable IP address to virtual machines in the subnet. Those are known as private IPs because they’re private to my network. When a virtual machine sends requests to the internet, the public gateway will translate those requests. That’s called egress traffic by the cloud providers.

Just as I can’t access my home printer from a coffee shop, in the cloud I need a public IP to access a virtual machine.

Public IPs can be assigned to virtual machines to allow ingress traffic (traffic coming from the Internet routed to virtual machines). That’s important because cloud providers will charge you differently for ingress and egress traffic.

SSH Keys

An SSH key is a piece of private information that allows you to access your cloud. It’s made up of two files. There’s a public key that looks like this:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5b8xmtjUd1taP4svy9FM/WZc/n5gkqKVkhIsqW27hw2WuhfTVNLA6IBBOs9+br+HlqGYwgYB3DSh0Zm/3Bok1uQhinH77FmKsrPGDpvtJv16weIvGiTMVp+Mct8DVKl48KZxvQKa0Hp6MxEc7cQ9WPvzWn9BPLHERSkSNwXSUobqpFBgIPy9UBWr5DsI2Li5HeMgMgTcbuVVdO/8I/rhKoIyTqkhY4CZcyssmWhMvPmk6+9IcOr0O4SyW9TL+CZgDH1mW2dUypT+1j6HgFjr9H8NfJ4EKnWnFkQXo8HZ4oh6lSTaIfDQfnbrjVUO14N7FW9ZgXbL9cJVx5FLw3ny9 you@them.com

And there’s a private key that looks like this:

-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAuW/MZrY1HdbWj+LL8vRTP1mXP5+YJKilZISLKltu4cNlroX0 1TSwOiAQTrPfm6/h5ahmMIGAdw0odGZv9waJNbkIYpx++xZirKzxg6b7Sb9esHiL xokzFafjHLfA1SpePCmcb0CmtB6ejMRHO3EPVj781p/QTyxxEUpEjcF0lKG6qRQY CD8vVAVq+Q7CNi4uR3jIDIE3G7lVXTv/CP64SqCMk6pIWOAmXMrLJloTLz5pOvvS HDq9DuEslvUy/gmYAx9ZltnVMqU/tY+h4BY6/R/DXyeBCp1pxZEF6PB2eKIepUk2 iHw0H52641VDteDexVvWYF2y/XCVceRS8N58vQIDAQABAoIBAHU7UKW+m2X55Dui zf0SqW5rXUtDwhOq6qTZhoGIvFjOBwKGfXosjRyyGJ0o6jyqvM1L4Q7ZUDXzg5fT CwXIhAYKrFprRXvHcypnS2hHsKW27k3yZ6tkIX+XW+VT5fzdhCXUyKks3jcRBHtJ ux7BI0kLGR02e6MSHYkowp47p1Auukx1saRkFTwvy+znABgqVETvtHBxAiElXndL JfQntaQacgWWDjl2qUj+06IB/Qzd9/Mo1Vtdb8SUZxv/Qc2raSi3LL0N4aSJGLGU pq395ggv9NdhUQf+DN9uGaOC4hYeGdO8gm27yysZ4rTT5iln5wOaCAcMTMrGL4+k GoU/nKECgYEA7AP/Mh9sUi9AX/17a3A/zxNAO1ZrvM+Caj/X/t/pt3HEOhqLz7o5 z3g8/Z+H0CJLZNiP9XbMak2wvOiqRj0y+FihX/ESll6XgIEPTBUcFSirWMe4f9og FltrnelUjHG9MTDW0P4jmmp1E5V8RgnCCv2VjN40ulP5zHPXXdU2FP8CgYEAySNs /qlFL7DTB/A851y6cUzQC5kiKlr/T8aUtOHeBo626jlnHDy/VY9vIJ0ttsYyHCdM OSdqZh5wRwvshr94tpOBQNnDTI4Xv7t2couHl7q2xTOYeWViwGyZaatNYlWWFh/u YSCTd2jn6cvBZOZP3BAiWoF9nzLcsjfpNLdzAkMCgYAx8TaTOKsHSRBqP41aUspt 2zkAVW0+6vpB2Xivalpegyhu0yc6scGB8YOWd6eZl2g00s7DtnvTEtWPY/yEGHcs rjSXxL+WKjYM70J5aw4iPBTmGH0mMNYRZQ8Ev1cw0PCj9B3A48ZM6rITjtJZT79L 7BU1Vd/6fcKiTPEJ3hAvqQKBgBKOQBnmR8m0iGNtGFFHzrNxIKhRQkOiDXewnDtr su3r8Jf/H7INMKGWD+x0U6lO84SBY5jKOBifqkADq5hqxZoiVYREEq5XVX2Mr8q1 cJbg1MewkNpyLgAOhMCo2wS9XJFB9N3lAXW8qdh5waerT6a/nku3Mn2jVZTjb5I7 clK9AoGAZLuvLAJpFOf/mweajULV+oFMGzIArvbk1c+cGySeI5uZwfQ9lv2MOb0N DuFTXZt6QpKV9Nix/8KgBIP2Vac6gSAeF6kIXk2+nV6gXm5tojYrf6gG1jY8ceRD IFSeGlnBhYVrFcQ79fYwJtSQgGde4PtNF1yq9ipluAyLuy1cLUc= -----END RSA PRIVATE KEY-----

You use these two files together instead of a password.

SSH public-key authentication is a robust way to log in to a remote system, more secure than the standard username and password. (SSH stands for “secure shell.”) You’ve probably seen SSH keys before in the form of your Github key.

SSH keys rely on public-key cryptography and challenge-response authentication to prevent brute-force attacks and other threats.

These keys ensure that only you have access to your virtual machines, and they work great with scripts and other automated tasks.

Data Centers

A data center is a big building full of computers. Cloud providers rent parts of them to you.

A cloud data center from IBM (Large preview)

The computers are powerful servers that can each host many virtual machines at the same time. These servers consume a lot of power and generate a lot of heat, and they need to be physically close together so the networking is fast. So a data center is the facility in which a cloud provider houses all the physical hardware that runs a piece of its cloud. The data center requires proper cooling, redundant power supply, massive network bandwidth, controlled access and skilled staff to keep all the machines running.

A data center can handle a lot of load, but it’s in just one place. A global cloud provider needs to spread data centers across the world to ensure that clients everywhere will have acceptable network-access latency to their servers.

Regions And Zones

A cloud provider needs to organize its data centers to ensure it can keep fulfilling requests during power outages, floods, hurricanes and other disasters. Providers call this Quality of Service, and it’s all part of making sure the cloud never goes down.

A region is a geographic area with a specific round-trip network latency (from where to where?). Regions have names like Dallas, Tokyo, or Frankfurt. You’re guaranteed to get the network latency you’re paying for inside that area.

A region is made up of one or more zones. Zones are an isolated data center in a region with independent electrical, mechanical and network infrastructure designed to guarantee no shared single point of failure between other zones. That enables you to build highly available fault tolerant applications by deploying to multiple zones in a region. The region can keep going if a zone crashes, but it’s really bad when all the zones go down.

A data center hosts a zone. Multiple data centers are clustered together to create a multi-zone region. Zones have names like us-south-1, us-south-2, and us-south-3. A small region can be served by a single robust data center, while a populous region might require multiple data centers to cope with the network and computing demand.

Disaster Recovery, High Availability, And Fault Tolerance

These concepts make even the most accomplished IT architects sweat. They stay awake at night wondering how they’ll make sure the cloud never goes down.

But you’ve got this! The cloud (almost) never goes down, and it’s easy to make sure you’re covered if it does. Let’s review these concepts one by one:

Disaster recovery (DR) is a set of policies and procedures detailing what to do during and after a major incident. Design your system so that it fails gracefully (meaning that your users will understand that something is wrong but you’ve got it covered), and after an incident, you know how to bring it back as quickly as possible. You need DR when a whole region goes down, your service is under cyber attack, or it’s vulnerable and you need to take it down.

High availability (HA) is but a goal — the “five nines” we talked about earlier. You want your application to be resilient to failure. This is nearly impossible if you run a server in your living room, but it’s doable with the cloud. You achieve the goal of high availability by relying on the cloud’s fault-tolerant infrastructure — everything’s taken care of for you.

A fault-tolerant infrastructure is a design that makes sure a backup takes over if something goes down. The cloud is a fault-tolerant system. If a zone within a region fails, the other zones will ensure continuity of service. You need to take advantage of that fault tolerance by using components like load balancers.

Load Balancers

High availability comes from running multiple instances of your application at the same time. A load balancer is a piece of equipment that will route traffic from your users to one of your instances that is live and well; it takes requests and sends them to the next healthy server.

The load balancer monitors the health of your virtual machines, and it can take different parameters into account. It can detect if your virtual machine or app crashes, but it can also check for network latency, specific data in the request headers and so much more.

Questions Your Clients Are Likely To Ask

So your application is running great, but your client is thinking about embracing the cloud. Are you ready for it? Here are common questions you should be able to answer to sound like a cloud expert.

When Do I Want More Than A Hosting Provider?

Your client might be engaged full speed ahead on migrating all services to the cloud. With greater flexibility, resiliency and a geographically distributed presence, the cloud makes it easy to get excited.

But even though the cloud is faster and more flexible and dependable, it’s also costlier; it might even require additional IT support to keep your client’s system running. The cloud is not for everyone.

If your client’s system is currently running on a hosting provider, should you move to the cloud?

Consider present and future needs: If your client is looking for high availability to comply with regulations, then move to the cloud. If your client is targeting a global audience, move to the cloud. If drastic surges in demand throughout the year are expected, move to the cloud.

If the system will be mostly accessed by local users within a specific geography, or if it’s not mission-critical to your client, then don’t move to the cloud.

The important takeaway: If your client’s system runs well on a hosting provider, you should consider existing SaaS offerings that would provide the resilience and performance of the cloud, while isolating your client from unnecessary IT expenses. There are traditional hosting providers like Bluehost offering cloud-based services, and there are cloud providers offering hosting services.

What Type Of Cloud Should I Use?

Finding the right type of cloud can be overwhelming, with each provider offering a wide range of services and options. The first things to consider are the complexity of your client’s software stack and the hardware it’s currently running on.

If the whole stack is highly customized with recompiled open-source libraries, customized Linux kernels or special storage optimizations, you should look at IaaS first. Some providers will allow your client to mix and match, using IaaS for highly customized components while picking PaaS for other parts.

However, if your client’s needs are mostly built on top of off-the-shelf libraries, PaaS is most likely a better starting point; the cloud provider will make sure your client’s code will always be running on top of up-to-date dependencies.

Once the right cloud model is identified, the requirements and evaluation criteria will be unique to your client. But it’s always worth paying attention to non-functional requirements: Does your client have to comply with domain-specific regulations, certifications or standards? Do they have any dependency on or partnership with specific vendors?

Finally, think about the cloud provider’s migration support, and vendor lock-in — how hard it is for your customer to migrate to a competitor once the system is running. Consider an exit plan even if your client is not planning to move out.

How Do I Make Sure My Site Never Goes Down?

Let’s say your client is working in the U.S. healthcare industry. HIPAA requires that every organization has some sort of disaster recovery plan, and that includes your website. Your client’s business will fail if the site goes down, so you need the resiliency of the cloud.

The cloud gives you the tools you need to ensure that your site never goes down. You’ll need multiple instances of your application running at the same time, and a way to control traffic so your users will never access a bad instance.

If your client is deploying your site on an IaaS cloud, you’ll need multiple instances of your application and a load balancer to control traffic. If your client has a PaaS cloud, just ensure that you have multiple instances running, as the cloud will provide the routing part automatically.

If your site uses a database, make sure that your client’s cloud is configured to support session affinity (also called sticky sessions), a way to ensure that all user traffic is routed to the same virtual machine.

How Do I Make My Site Fast For Everyone In The World?

If your client is going global and requires your application to provide fast service to end users around the world, then you need the flexibility and geographic reach of the cloud.

Even though the cloud is global, you need instances of your application to be running close to where your users are.

Work with your client to identify which cloud regions would best serve your user base, and where your client is hosting the APIs that support your site — your instances should be close to both. You might also discuss setting up a global proxy that routes traffic to different geographical areas based on the user’s location.

Conclusion

Providing scalable and resilient service to a growing customer base across the Internet is very complex; we’ve barely scratched the surface here. That’s why we want to leave the details to Amazon, Google, and the other cloud providers. Your job is to make good decisions about how to use their services.

Remember, when your client asks you if something should run in the cloud, you need to ask four questions:

  • How complex is the software you need to run?
  • How much does it need to scale?
  • How important is it that it never goes down?
  • How fast does it need to run for users around the world?

Start with those four simple questions, and you’ll sound like a cloud expert. Work with your clients to understand what the answers to these questions mean for them, considering the different types of cloud, and you’ll be a big part of solving their needs.

(dm, ra, il)
Categories: Around The Web

How Web Designers Can Contribute To Mobile-First Marketing

Smashing Magazine - Thu, 01/24/2019 - 4:30pm
How Web Designers Can Contribute To Mobile-First Marketing How Web Designers Can Contribute To Mobile-First Marketing Suzanne Scacca 2019-01-24T22:30:22+01:00 2019-02-15T08:25:19+00:00

I recently wrote about why we should be working on retiring unnecessary design elements instead of adding more stuff to websites in this mobile-first world. And it got me thinking:

What about marketing? Is there even such a thing as mobile-first marketing?

In short, I believe so.

I’m going to examine the key areas of marketing that stem from the websites we build. Then, zero in on the ways in which designers should adjust these marketing strategies for mobile-friendly and mobile-first audiences.

How Web Designers Can Contribute To Mobile-First Marketing

There are typically three kinds of marketing strategies businesses utilize:

  1. Content marketing
  2. Email marketing
  3. Social media marketing

Similar to how we view web design through a mobile-first lens, a similar process must occur when designing marketing strategies going forward. That means not being afraid to toss out design or even text elements for the mobile experience. It also means taking tried-and-true marketing techniques and compressing them down so they’re more easily digestible for audiences on mobile.

Ahoy! The hunt for shiny front-end & UX treasures has begun! Meet SmashingConf San Francisco 2019

Categories: Around The Web

Table Design Patterns On The Web

Smashing Magazine - Thu, 01/24/2019 - 7:30am
Table Design Patterns On The Web Table Design Patterns On The Web Huijing Chen 2019-01-24T13:30:57+01:00 2019-02-14T10:05:24+00:00

Tables are a design pattern for displaying large amounts of data in rows and columns, making them efficient for doing comparative analysis on categorical objects. Tables have been used for this purpose as early as the 2nd century and when the world started to go digital, tables came along with us.

It was inevitable that the web would support the display of data in a tabular format. HTML tables present tabular data in a semantic and structurally appropriate manner. However, the default styles on HTML tables are not exactly the most aesthetically pleasing thing you’ve ever seen. Depending on the desired visual design, some effort was required on the CSS front to prettify those tables. A decade ago, an article with the “Top 10 CSS Table Designs” was published on Smashing Magazine, and it still continues to get a lot of traffic to this day.

The web has evolved a lot over the past decade, and it’s more convenient than ever to make your site or application adapt to the viewport it is viewed in. That being said, we have to continue to make considered design choices that do not compromise on accessibility. Since tables don’t seem to be falling out of favor anytime soon, let’s see how tables can be created on the web in 2019.

Front-end is messy and complicated these days. That's why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

Explore Smashing Membership ↬ CSS-Only Options

If your dataset isn’t that large, and features like pagination and sorting are not necessary, then consider a JavaScript-free option. You can get some pretty nice results that work well on a whole gamut of screen sizes without the added weight of a large library.

Unfortunately, without the help of JavaScript for some DOM manipulation on the accessibility front, we only have a handful of CSS-only options. But for small data sets, they are often sufficient.

Option 1: Do Nothing

We’re going to start off with a low-effort scenario. If your data fits in a table with only a few columns and lots of rows, then such a table is pretty much mobile-ready to begin with.

A table with a few columns and many rows displayed on narrow and wide screens (Large preview)

The issue you’d have is probably having too much room on wider screens, so it might be advisable to “cap” the maximum width of the table with a max-width while letting it shrink as necessary on a narrow screen.

See the Pen Table #1: Few columns, many rows by (Chen Hui Jing) on CodePen.

This sort of a pattern works best if your data itself isn’t lines and lines of text. If they are numeric, or short phrases, you can probably get away with not doing much.

Option 2: Style The Scroll

David Bushell wrote up his technique for responsive tables back in 2012, which involved invoking overflow and allowing users to scroll to see more data. One could argue that this isn’t exactly a responsive solution, but technically, the container is responding to the width of the viewport.

When styling tables, allow users to scroll to see more data. (Large preview)

Let’s look at the “basic” overflow first. Imagine me using air-quotes around basic, because the styling for the scrolling shadows is anything but. Still, in this instance, “basic” refers to the fact that the table does not transform in any way.

See the Pen Table #3: Style the scroll (basic) by Chen Hui Jing on CodePen.

This technique for doing scrolling shadows comes from Roma Komarov and Lea Verou riffing off each other’s ideas to create magic. It hinges on using multiple gradients (linear and radial) as background images on the containing element, and using background-attachment: local to position the background relative to its contents.

What’s nice about this technique is that for browsers that don’t support scrolling shadows, you can still scroll the table as per normal. It doesn’t break the layout at all.

Another scrolling option would be to flip the table headers from a row configuration to a column configuration, while applying a horizontal scroll onto the <tbody> element’s contents. This technique leverages Flexbox behavior to transform the table’s rows into columns.

See the Pen Table #3: Style the scroll (flipped headers) by Chen Hui Jing on CodePen.

By applying display: flex to the table, it makes the <thead> and <tbody> both flex children, which are by default laid out next to each other on the same flex line.

We also make the <tbody> element a flex container, thus making all the <tr> elements within it flex children laid out in a single flex line as well. Lastly, every table cell has to have their display set to block instead of the default table-cell.

The advantage of this technique is that the headers are always in view, like a fixed header effect, so users don’t lose context as they scroll through the columns of data. One thing to take note of is that this technique results in a discrepancy of the visual and source order, and this makes things slightly unintuitive.

Sprinkle On Some JavaScript

As mentioned earlier, layout options that involving morphing the table by modifying display values sometimes have negative implications for accessibility, which require some minor DOM manipulation to rectify.

In addition, there are a number of user experience tips when it comes to designing data tables from Andrew Coyle, including features like pagination, sorting, filtering, and so on (features that do require some JavaScript to enable).

If you’re working with a relatively simpler dataset, perhaps you would like to write your own functions for some of these features.

Rows To Blocks, With Accessibility Fix

As far as I know of, this responsive data table technique came about from the CSS-Tricks article “Responsive Data Tables” by Chris Coyier back in 2011. It has since been adapted and expanded upon by many others.

The gist of this technique is to make use of a media query to switch the display property of the table element and its children to block on a narrow viewport.

Collapsing rows into blocks (Large preview)

On a narrow screen, the table headers are visually hidden, but still exist in the accessibility tree. By applying data attributes to the table cells, we can then display labels for the data via CSS, while keeping the content of the label in the HTML. Please refer to the CodePen below for how the mark-up and styles look like:

See the Pen Table #2: Rows to blocks by Chen Hui Jing on CodePen.

The original method applies a width on the pseudo-element displaying the label text, but that means you’d need to know the amount of space your label needed to begin with. The above example uses a slightly different approach, whereby the label and data are each on opposite sides of their containing block.

We can achieve such an effect via auto-margins in a flex formatting context. If we set the display property for each <td> element to flex, because pseudo-elements generate boxes as if they were immediate children of their originating element, they become flex children of the <td>.

After that, it’s a matter of setting margin-right: auto on the pseudo-element to push the cell’s content to the far end edge of the cell.

Another approach doing the narrow viewport layout is using a combination of Grid and display: contents. Please note that display: contents in supporting browsers has issues with accessibility at the moment, and this method shouldn’t be used in production until those bugs are fixed.

But maybe you’re reading this after those bugs have been resolved, in that case, here’s an alternative layout option.

See the Pen Table #2: Rows to blocks (alt) by Chen Hui Jing on CodePen.

Each <tr> element is set to display: grid, and each <td> element is set to display: contents. Only the immediate children of a grid container participate in a grid formatting context; in this case, it’s the <td> element.

When display: contents is applied to the <td>, it gets “replaced” by its contents, in this case, the pseudo-element and the <span> within the <td> become the grid children instead.

What I like about this approach is the ability to use max-content to size the column of pseudo-elements, ensuring that the column will always be the width of the longest label, without us having to manually assign a width value for it.

In future, when the sizing values of min-content, max-content and fit-content (covered by the CSS Intrinsic & Extrinsic Sizing Module Level 3) are supported as general width and height values, we’ll have even more options for laying things out.

The downside to this approach is you do need that additional <span> or <p> around the content in your table cell if it didn’t have one already, otherwise, there’d be no way to apply styles to it.

Simple Pagination

This example shows a basic pagination implementation that was modified off this CodePen by Gjore Milevski to paginate on table rows instead of divs. It is an extension of the “style the scroll” example discussed in the previous section.

See the Pen Table #4: Simple pagination by Chen Hui Jing on CodePen.

From a layout perspective, Flexbox comes in very handy for positioning the pagination elements across various viewport sizes. Different project designs will have different requirements, but the versatility of Flexbox should allow you to cater for these differences accordingly.

In this case, the pagination is centred on the page and above the table. The controls for navigating backward and forward are placed on either side of the page indicators on wider screens, but all four appear above the page indicators on narrow screens.

We can do this by levaraging the order property. Visual reordering of content should always be done with consideration because this property does not change the source order — only how it appears on screens.

Simple Sorting

This example shows a basic sorting implementation modified off this code snippet by Peter Noble to cater for both text and numerals:

See the Pen #Table 5: Simple sorting by Chen Hui Jing on CodePen.

It would be useful to have some sort of indicator of which column is currently being sorted and in what order. We can do that with the addition of CSS classes which can then be styled however you want. In this case, the indicator symbols are pseudo-elements that are toggled when the target header is clicked.

Simple Search

This example is a basic filtering functionality that iterates through all the textual content of each table cell and applies a CSS class to hide all rows that do not match the search input field.

See the Pen Table #6: Simple filtering by Chen Hui Jing on CodePen.

Such an implementation is relatively naive, and if your use case calls for it, it might make sense to search per column instead. In that scenario, it might be a good idea to have each input field as part of the table in their respective columns.

Let A Library Handle It

The above JavaScript snippets serve to demonstrate how tables with larger amounts of data can be enhanced to make life easier for users of those tables. But with really large datasets, it might probably make sense to use an existing library to manage your large tables.

The column toggle pattern is one whereby non-essential columns are hidden on smaller screens. Normally, I’m not a fan of hiding content simply because the viewport is narrow, but this approach by Maggie Costello Wachs of Filament Group resolves that qualm of mine by providing a drop-down menu which allows users to toggle the hidden columns back into view.

The above article was published back in 2011, but Filament Group has since developed a full suite of responsive table plugins known as Tablesaw, which includes features like sorting, row selection, internationalization and so on.

The column toggle feature in TableSaw also no longer depends on jQuery, unlike the examples from the original article. Tablesaw is one of the only libraries I could find that does not have a dependency on jQuery at the moment.

Wrapping Up

There is a myriad of table design patterns out there, and which approach you pick depends heavily on the type of data you have and the target audience for that data. At the end of the day, tables are a method for the organization and presentation of data. It is important to figure out which information matters most to your users and decide on an approach that best serves their needs.

Further Reading (ra, il)
Categories: Around The Web

Designing Emotional Interfaces Of The Future

Smashing Magazine - Wed, 01/23/2019 - 7:00am
Designing Emotional Interfaces Of The Future Designing Emotional Interfaces Of The Future Gleb Kuznetsov 2019-01-23T13:00:12+01:00 2019-02-13T10:05:19+00:00

Emotions play a vital role in our decision-making process. One second of emotion can change the whole reality for people engaging with a product.

Humans are an emotionally driven species; we choose certain products not because of what makes sense, but because of how we think they will make us feel. The interfaces of the future will use the concept of emotions within the foundation of product design. The experiences that people use will be based both on intellectual quotient (IQ) and emotional quotient (EQ).

This article is my attempt to look into the future and see what interfaces we will design in the next ten years. We’ll be taking a closer look at the three mediums for interaction:

  1. Voice
  2. Augmented Reality (AR)
  3. Virtual Reality (VR)
Developing For Virtual Reality

It’s not that difficult to create content for VR nowadays. Still, if you’re looking for a way to get a better understanding of VR development, working a demo project can help. Learn more →

Our new book, in which Alla Kholmatova explores how to create effective and maintainable design systems to design great digital products. Meet Design Systems, with common traps, gotchas and the lessons Alla has learned over the years.

Table of Contents → Practical Examples Of Future Emotional Interfaces

How will interfaces look like in the future? Even though we do not have an answer to this question just yet, we can discuss what characteristics interfaces might have. In my opinion, I’m sure that we will eventually move away from interfaces full of menus, panels, buttons, and move towards more ‘natural interfaces’, i.e. interfaces that extend our bodies. The interfaces of the future will not be locked in a physical screen, but instead they will use the power of all five senses. Because of that, they will require a less learning curve — ideally, no learning curve at all.

The Importance Of EQ Emotional Intelligence In Business

Apart from making the experience more natural and reducing the learning curve, designing for emotion has another benefit for product creators: it improves user adoption of the product. It’s possible to use humans’ ability to act on emotions to create better user engagement.

Voice Interfaces That Feel Real

Products that use voice as the primary interface are becoming more and more popular. Many of us use Amazon Echo and Apple Siri for daily routine activities such as setting an alarm clock or making an appointment. But a majority of voice interaction systems available on the market today still have a natural limitation: they do not take user emotions into account. As a result, when users interact with products like Google Now, they have a strong sense of communicating with a machine — not a real human being. The system responds predictably, and their responses are scripted. It’s impossible to have a meaningful dialogue with such a system.

But there are some completely different systems available on the market today. One of them is Xiaoice, a social chatbot application. This app has an emotional computing framework at its core; the app is built on the idea that it’s essential to establish an emotional connection with the user first. Xiaoice can dynamically recognize emotion and engage the user throughout long conversations with relevant responses. As a result, when users interact with Xiaoice they feel like they’re having a conversation with a real human being.

The limitation of Xiaoice is that it’s a text-based chat app. It’s evident that you can achieve a more powerful effect by making voice-based interactions (the human voice has different characteristics such as a tone that can convey a powerful spectrum of emotions).

Many of us have seen the power of voice-based interactions in the film “Her” (2013). Theodore (the main character played by Joaquin Phoenix) fell in love with Samantha (a sophisticated OS). This also makes us believe that one of the primary purposes of voice-based systems of the future will be a virtual companion to users. The most interesting thing about this film is that Theodore did not have a visual image of the Samantha — he only had her voice. To build that kind of intimacy, it’s essential to generate responses that reflect a consistent personality. This will make the system both predictable and trustworthy.

Technology is still a long away from a system like Samantha, but I believe that voice-first multimodal interfaces will be the next chapter in the evolution of voice-enabled interfaces. Such interfaces will use voice as a primary way of interaction and provide additional information in a context that creates and builds a sense of connection.

An example of a voice interface designed for Brain.ai (Image credit: Gleb Kuznetsov) The Evolution Of AR Experience

Augmented Reality (AR) is defined as a digital overlay on top of the real world and transforms the objects around us into interactive digital experiences. Our environment becomes more ‘intelligent’ and users have an illusion of ‘tangible’ objects on the tips of their fingers, which establishes a deeper connection between a user and a product (or content).

Reimagine Existing Concepts Using AR

The unique aspect of AR is that it gives us an extraordinary ability to physically interact with digital content. It allows us to see things that we could not see before and this helps us learn more about the environment around us. This AR property helps designers to create new level experiences using familiar concepts.

For example, by using mobile AR, it’s possible to create a new level of in-flight experience that allows a passenger to see detailed information about her class or current flight progress:

AR in flight experience for Airbus A380. (Image credit: Gleb Kuznetsov)

AR helps us find our way through spaces and get the required information at a glance. For example, AR can be used to create rich contextual hints for your current location. The technology known as SLAM (Simultaneous Localization And Mapping) is perfect for this. SLAM allows real-time mapping of an environment and also makes it possible to place multimedia content into the environment.

There are massive opportunities for providing value to users. For example, users can point their devices at a building and learn more about it right there on their screens. It significantly reduces the effort and allows for an emotional experience of ease by allowing navigation and access.

Providing additional information in context (Image credit: Gleb Kuznetsov)

The environment around us (such as walls or floors) can become a scene for interactivity in ways that used to be limited to our smartphones and computers.

The concept that you see below does just that; it uses a physical object (white wall) as a canvas for the content usually delivered using a digital device:

The concept of interactive walls — a digital overlay on top of the real world. (Image credit: Gleb Kuznetsov) Avoiding Information Overload

Many of us saw the video called “HYPER-REALITY”. In this video, the physical and digital worlds have merged, and the user is overwhelmed with a vast amount of information.

Technology allows us to display several different objects at the same time. When it’s misused, it can easily cause overload.

Information overload is a serious issue that has a negative impact on user experience and avoiding it will be one of the goals of designing for AR. Well-designed apps will filter out elements that are irrelevant to users using the power of AI.

Advanced Personalization

Personalization in digital experience happens when the system curates the content or functionality to users’ needs and expectations in real time. Many modern mobile apps and websites use the concept of personalization to provide relevant content. For example, when you visit Netflix, the list of movies you see is personalized based on your interests.

AR glasses allow creating a new level of personalization, i.e. an ‘advanced’ level of personalization. Since the system ‘sees’ what the user sees, it’s possible to utilize this information to make a relevant recommendation or provide additional information in context. Just imagine that you’ll soon be wearing AR glasses, and the information that is transferred to your retina will be tailored to your needs.

Here’s a foretaste of what’s in store for us:

Moving From Augmented Reality Towards Virtual Reality To Create An Immersive Experience

AR experience has a natural limitation. As users, we have a clear line between us and content; this line separates one world (AR) with another (real world). This line causes a sense that the AR world is clearly not real.

You probably know how to solve this limitation, i.e. with virtual reality (VR), of course. VR is not exactly a new medium, but it’s only been in the last few years that technology has reached a point where it allowed designers to create immersive experiences.

Immersive VR experiences remove the barrier between the real world and digital. When you put on a VR headset, it’s difficult for your brain to process whether the information that you are receiving is real. The idea of how VR experiences can look in the nearest future is well explained in the movie “Ready Player One”:

Here is what designers need to remember when creating immersive virtual environments:

  1. Write a story
    Meaningful VR has a strong story at its core. That’s why before you even start designing for a VR environment, you need to write a narrative for the user journey. A powerful tool known as a ‘storyboard’ can help you with that. Using a storyboard, it’s possible to create a story and examine all the possible outcomes. When you examine your story, you will see when and how to use both visual and audio cues to create an immersive experience.
  2. Create a deeper connection with a character
    In order to make users believe that all things around them in VR are real, we need to create a connection with the characters played by the users. One of the most obvious solutions is to include a representation of users’ hands in the virtual scene. This representation should be of actual hands — not just a rigged replica. It’s vital to consider different factors (such as gender or skin color) because it’ll make interactions more realistic.
    A user can look at his or her hands and see themselves appear as a character. (Source: leapmotion)
    It’s also possible to bring some objects from real life to a VR environment in order to create this connection. For instance, a mirror. When the user looks at a mirror and sees their character in the reflection, it enables more realistic interactions between the user and virtual characters.
    A virtual reality user looks into a virtual mirror and sees himself as a character in a VR environment. Credits: businesswire. (Large preview)
  3. Use gestures instead of menus
    When designing immersive VR experiences, we can’t rely on traditional menus and buttons. Why? Because it is relatively easy to break a sense of immersion by showing a menu. Users will know that everything around them is not real. Instead of using traditional menus, designers need to rely on gestures. The design community is still in the process of defining a universal language for using gestures, and taking part in this activity is fun and exciting exercise. The tricky part is to make gestures familiar and predictable for users.
    Hovercast VR menu is an attempt to reuse existing concepts of interaction for VR experience. Unfortunately, this concept can break the sense of immersion. New medium requires a new model of interaction.
  4. Interact with elements in the VR environment
    To create an environment that feels real, we need to give the user the ability to interact with objects in that reality. Ideally, all objects in the environment can be designed in a way that allows users to touch and inspect them. Such objects will act as stimuli and will help you create a more immersive experience. Touch is extremely important for exploring the environment; the most important information that babies get in the first days is received through touch.
  5. Share emotion in VR
    VR has a real opportunity to become a new level of social experience. But to make it happen, we need to solve one significant problem, i.e. bring the non-verbal cues into the interaction.

    When we interact with other people, a significant part on information that we get comes from body language. Surprise, disgust, anger — all these emotions are in our facial expressions, and during face-to-face interactions, we infer information from the eye region. It’s important to provide this information when people interact in a VR environment to create more realistic interactions.

    The good news is that the head-mounted devices (HMDs) will soon cover emotion recognition. Almost any area of human-to-human interaction will benefit from facial expressions in VR.
    Sharing emotions in VR space (Source: Rachel Metz of MITReview)
  6. Design sound and music suitabke for a VR environment
    Audio is a huge component of the immersive experience. It’s impossible to create a genuinely immersive experience without designing sound for the environment. The sound can both be used as a background element (i.e., ambient sound of wind) or directional. In the latter case, the sound can be used as a cue — by playing with directionality (where the sound comes from) and distance (it’s possible to focus user attention on particular elements).

    When it comes to designing audio for VR, it’s essential to make the sound 3D. 2D sound doesn’t work for VR very well because it makes everything too flat. The 3D sound is the sound that you can hear in every direction around you — front, behind, above and beyond — all over the place. You don’t need specialized headphones to experience 3D sound; it’s possible to create it using standard stereo speakers of HMD.

    Head tracking is another critical aspect of a good sound design. It’s vital to make sounds behave in a realistic manner. That’s why when a user moves his head, the sound should change according to the head movement.
  7. Prevent motion sickness
    Motion sickness is one of the primary pain-points in VR. It’s a condition in which a disagreement exists between visually perceived movement and the vestibular system’s sense of movement. It’s vital to keep users comfortable while they experience VR.

    There are two popular theories what causes motion sickness in VR:
    • ‘Sensory Conflict’ Theory
      According to this theory, motion sickness occurs as a result of a sensory disagreement between expected motion and motion that is actually experienced.
    • ‘Eye Movement’ Theory
      In the book “The VR Book: Human-Centered Design For Virtual Reality”, Jason Jerald mentions that motion sickness occurs because of the unnatural eye motion that is required to keep the scene’s image stable on the retina.
    Here are a few tips that will help you prevent users from reaching for the sickbag:
    • Physical body movement should match with visual movement. Sometimes even a small visual jitter can have an enormously negative impact on the experience.
    • Let users rest between moving scenes (this is especially important when the VR experience is really dynamic).
    • Reduce virtual rotations.
Conclusion

When we think about the modern state of product design, it becomes evident that we are only at the tip of the iceberg because we’re pretty limited to flat screens.

We’re witnessing a fundamental shift in Human-Computer Interaction (HCI) — rethinking the whole concept of digital experience. In the next decade, designers will break the glass (the era of mobile devices as we know them today) and move to the interfaces of the future — sophisticated voice interfaces, advanced ARs, and truly immersive VRs. And when it comes to creating a new experience, it’s essential to understand that the only boundary we have are our brains telling us it’s got to be how it’s always been.

(cc, ra, yk, il)
Categories: Around The Web

The Smashing Survey: Join In!

Smashing Magazine - Tue, 01/22/2019 - 7:00am
The Smashing Survey: Join In! The Smashing Survey: Join In! Rachel Andrew 2019-01-22T13:00:09+01:00 2019-02-12T10:16:25+00:00

Our entire aim here at Smashing Magazine — and my focus as Editor-in-Chief — is to provide the most helpful and interesting content and resources for web professionals. We aim not only to introduce new tools, technologies, and best practices, but also to give you handy resources to refer back to and put on interesting live events such as our conferences and webinars.

To be able to do that well, we need to understand the people who read Smashing Magazine, come to our conferences, and sign up as members. Given that we don’t ever get to meet or interact with the majority of folks who visit the site, this can make it quite difficult for us to better understand our readers and subscribers. Many of our Smashing Members join us in Slack, and we get to chat with conference attendees. However, these two groups are small in comparison to our worldwide audience.

So today, we’ve decided to launch a Smashing Survey which will help us create even more relevant content and shape the future of Smashing. The information will be used only here at Smashing to guide our content and our work, to ensure that we are doing the best we can for those who you who give us your time and attention.

The survey contains up to 35 questions and will take you only 5–10 minutes to complete. Join in!

We look forward to learning more about your experience with Smashing Magazine, and the things that matter to you. We promise to use that information to provide the resources that you need.

(ra, vf, il)
Categories: Around The Web
Syndicate content