A/B Testing For Mobile-First Experiences — Smashing Magazine


About The Author

Suzanne Scacca is a former WordPress implementer, trainer and agency manager who now works as a freelance copywriter. She specializes in crafting marketing, web …
More about Suzanne

In this mobile-first world, there is no such thing as designing a website near-perfectly the first time around. While we know that more experiences with websites begin on mobile, converting those users continues to be problematic. With proper A/B testing for mobile-first experiences, however, you can change that outcome for your clients.

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.

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 mobile halo effect
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 mobile navigation
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:


SensesLab contact shortcut
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:


SensesLab contact form
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:


SensesLab contact form fields
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 'Book Tour' button
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 books tours
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 Map icon
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 regions
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 mobile search
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 home page
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 navigation calendar
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 mobile scheduling
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
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
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 Save function
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 Save folder
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 free demo button
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.

Smashing Editorial(ra, yk, il)



Source link

New JavaScript Features That Will Change How You Write Regex — Smashing Magazine


About The Author

Faraz is a professional frontend developer, consultant, and writer who is passionate about moving the web forward and promoting patterns and ideas that will …
More about Faraz

If you have ever done any sort of sophisticated text processing and manipulation in JavaScript, you’ll appreciate the new features introduced in ES2018. In this article, we take a good look at how the ninth edition of the standard improves the text processing capability of JavaScript.

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 →

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(/(ww)1/.test('abab'));    // → true

// if the last two letters are not the same 
// as the first two, the match will fail
console.log(/(ww)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+1b/;

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 [wW], which instructs the regex engine to match a character that’s a word character (w) or a non-word character (W):

console.log(/[wW]/.test('n'));    // → true
console.log(/[wW]/.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 = '';

console.log(/d/.test(str));     // → false
console.log(/d/u.test(str));    // → false

is considered a digit, but d can only match ASCII [0-9], so the test() method returns false. Because changing the behavior of shorthand character classes would break existing regular expression patterns, it was decided to introduce a new type of escape sequence.

In ES2018, Unicode property escapes, denoted by p{...}, are available in regular expressions when the u flag is set. Now to match any Unicode number, you can simply use p{Number}, as shown below:

const str = '';
console.log(/p{Number}/u.test(str));     // → true

And to match any Unicode alphabetic character, you can use p{Alphabetic}:

const str = '漢';

console.log(/p{Alphabetic}/u.test(str));     // → true

// the w shorthand cannot match 漢
console.log(/w/u.test(str));    // → false

P{...} is the negated version of p{...} and matches any character that p{...} does not:

console.log(/P{Number}/u.test(''));    // → false
console.log(/P{Number}/u.test('漢'));    // → true

console.log(/P{Alphabetic}/u.test(''));    // → true
console.log(/P{Alphabetic}/u.test('漢'));    // → false

A full list of supported properties is available on the current specification proposal.

Note that using an unsupported property causes a SyntaxError:

console.log(/p{undefined}/u.test('漢'));    // → SyntaxError

Compatibility Table

Desktop Browsers

Chrome Firefox Safari Edge
Lookbehind Assertions 62 X X X
Named Capture Groups 64 X 11.1 X
s (dotAll) Flag 62 X 11.1 X
Unicode Property Escapes 64 X 11.1 X

Mobile Browsers

ChromeFor Android FirefoxFor Android iOS Safari Edge Mobile Samsung Internet Android Webview
Lookbehind Assertions 62 X X X 8.2 62
Named Capture Groups 64 X 11.3 X X 64
s (dotAll) Flag 62 X 11.3 X 8.2 62
Unicode Property Escapes 64 X 11.3 X X 64

Node.js

  • 8.3.0 (requires --harmony runtime flag)
  • 8.10.0 (support for s (dotAll) flag and lookbehind assertions)
  • 10.0.0 (full support)

Wrapping Up

ES2018 continues the work of previous editions of ECMAScript by making regular expressions more useful. New features include lookbehind assertion, named capture groups, s (dotAll) flag, and Unicode property escapes. Lookbehind assertion allows you to match a pattern only if it is preceded by another pattern. Named capture groups use a more expressive syntax compared to regular capture groups. The s (dotAll) flag changes the behavior of the dot (.) metacharacter to match line break characters. Finally, Unicode property escapes provide a new type of escape sequence in regular expressions.

When building complicated patterns, it’s often helpful to use a regular-expressions tester. A good tester provides an interface to test a regular expression against a string and displays every step taken by the engine, which can be especially useful when trying to understand patterns written by others. It can also detect syntax errors that may occur within your regex pattern. Regex101 and RegexBuddy are two popular regex testers worth checking out.

Do you have some other tools to recommend? Share them in the comments!

Smashing Editorial(dm, il)



Source link

How People Make Decisions — Smashing Magazine


About The Author

Susan has a Ph.D.in Psychology and over 30 years of experience as a behavioral scientist. She speaks, consults, teaches, and writes about applying behavioral …
More about Susan

It’s important to understand that all decisions involve emotions. In this article, Susan Weinschenk explains how you can make your users feel confident of their decisions and why it’s a bad idea to provide more than four options to choose from.

(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?


A pricing plan that is presented to Kelly
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.


An illustration showing the parts of the human brain
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.


An example of a ‘customer success platform’ with 12 options to choose from
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.

Smashing Editorial(cm, ms, ra, il)



Source link

How To Architect A Complex Web Table — Smashing Magazine


About The Author

Slava is a designer from Ukraine. He works as a Lead Designer at ELEKS, a software consultancy, and is a journalist by education. Slava gives talks and conducts …
More about Slava

Tables frequently appear on the web but aren’t easy to design and code. This illustrated guide explains the table anatomy and how to build a table, keeping in mind its future elaboration.

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.


A trendy-looking table with little data versus a busy complex table
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?

As a result, you’ll have a list of people’s needs and wants. Open questions are helpful for figuring out real needs and filtering out whims:

“What helps you to work faster? What can make your choice easier? How is this feature affecting the efficiency of your work? What will change if you aren’t able to do X?”

So, what’s next? Now it’s time to build a logical skeleton for your table. A scheme of what it contains and is able to do. If you go directly to wireframing or prototyping, you step on the evil path of endless re-drawing and fighting with legacy.

Below is an example of what you might start with. It’s the tree of features. And the basic building block of any table is a cell. Cells unite into rows and columns, which can have specific features different from those of separate cells. And finally, we go to such important supplements of a table as a top bar with buttons, keyboard commands, and handling errors.


Tree-like hierarchical structure of table components
(Large preview)

The tree of features prevents you from making extra work and helps to focus on what is important. A well-organized feature tree is also helpful for the development team. They can match planned features with available frontend libraries and find the best way to turn designs into code.

On one of my projects, we used Angular Material framework. Unfortunately, Angular tables were too simple. We found an ag-Grid library that supported our functionality but had one limitation. It had no ability to expand a row and put children rows inside. We had revealed this issue before we put any effort into it and adjusted the design.

In A Nutshell

  • Start building a complex table with collecting and prioritizing user needs. Consider a non-table solution, for example, a chart.
  • Draw a tree diagram that systematizes all the needed features. Use it as a plan for producing visuals.

Recommended reading: Table Design Patterns On The Web by Chen Hui Jing

2. Go Atomic

So, the needs and functionality are defined, and you know technical limitations. It’s time to mockup your table. Basically, the atomic approach is designing small UI components first and then assembling bigger ones. We’ll gradually move from elementary particles like fonts and colors to such large modules like a header or column. I deliberately chose the strict brutalist style for mockups so that we can concentrate on function, not appearance.

Fonts, Colors, Icons

These parts can be already defined by the design system or UI framework you use. If you create a table for an existing product, check whether its color palette, fonts, and icons meet the table needs. On the picture below, I showed some of the shades of gray needed for table frames, lines, fills and text. Red and blue tints stand for warning-error-destructive and active-enabled-selected stuff. Text styles are to distinguish between primary and secondary information, titles and body text.


An example of a color palette, icons, and font styles sufficient for a table
(Large preview)

Cells And Accessories

When the table atoms are ready, we can proceed with molecules — different types of cells. In the first place, it’s important to think about normal, hover and active states of each element beforehand. Then go clicked, disabled and other states.

On one of my projects, we had eight types of cells with their own interaction. The simplest ones are text and numeric cells. In our case, it was allowed to populate numeric cells with non-numeric content, such as “N/A” (not applied) and “N/C” (no control). It was the peculiarity of the domain. Dropdowns and date pickers are more complex and have child elements. Finally, we had table cells that represented in-row commands.


A sample set of nine table cell types in normal, active, and clicked states
(Large preview)

Cells can have such accessories as tooltips, input hints, error messages, placeholders, etc. At this stage, they are static, but a designer should later specify the logic of how they show up (animation, delay, etc.).


Examples of table cell accessories: hints, error messages, and tooltips
(Large preview)

Rows And Headers

When cells are designed, you can make rows and see if various combinations work well together. Once I designed a table that had complex editing logic. Some of the properties were provided by users, whereas others were automatically calculated or populated with default values. Below is a mix of read-only and editable cells within one row.


Normal, hover, and active states of a read-only table row
(Large preview)

Note that the cursor is different when hovering over read-only and editable cells. Clicking on them triggers either selection of a row or going to the editing mode of the editable cell.

In the next image, you can see that people can select one or multiple rows:


Normal, hover, and multi-selection states of an editable table row
(Large preview)

Now it’s time to think about the table header. From my experience, it’s often impossible to control column title length and stick to one line. Even with a good writer on a team, you won’t keep all the texts short. Some of the tables require long technical titles or localization. Phrases, which were one-line in English, might become two- or three-line in Greek, German or Hungarian. Therefore, I showed different variants:


Examples of simple table headers with different amount of text
(Large preview)

Users of data-based software often need sorting and filtering. It helps them to find valuable information in the large chunks of data. The challenge with sorting and filtering is to combine sorting controls and filtering boxes with other header elements — column titles, measurement units, etc.


Different states of a table header: normal, hover, sorted ascendingly and descendingly
(Large preview)

Unlike table cells, filter boxes usually have “reset” icon on the right so that users can explicitly disable them and see unfiltered content.


Examples of table headers with filtering functionality
(Large preview)

In my example, there are three types of filter boxes. The alphanumeric filter enables search by letters and numbers. It supports wildcards — unknown number of unknown characters. For instance, if I type 45*A1, it might result in showing the rows with such values as 45A1, 45982A1B, 45A109B and 096445-A1.

Wildcards are a tricky feature since they depend on people’s habits. When I designed tables for technical specialists, we assigned the asterisk sign (*) to the unknown number of unknown symbols. For insurance analysts, I chose traditional SQL symbol — the percentage sign (%) — because they were used to it. As for the dropdown filter, it switches between a certain number of mutually exclusive text options, numbers or numeric ranges.


A sample set of three table filter types
(Large preview)

The date picker filter has a calendar and works like its cell equivalent. It’s good to allow users both enter the date manually and pick from the calendar. If they know what they are searching for, it’s much easier to type than click.

One more important thing is to format any meaningful input automatically and not to bother people with “invalid format” errors. On one of my projects, we allowed entering such dates as 01/25/2017, 6.12.17 and September 4 2016, and also filter by a month or year only.


Examples of a horizontal scroll in large tables: with fixed columns and without them
(Large preview)

Columns

One of the frequent features of complex tables is pinned columns. Usually, columns that contain key information, for instance, element names or statuses, are not scrollable.


Examples of text truncation in narrow columns and manual column resizing
(Large preview)

Although table columns should smartly adjust to content size, it happens when text is truncated. In this case, column resizing is helpful. Users can drag the column edge and see long content. They might also need to squeeze an unimportant column or the one with a short text.


Two ways to process long text in tales: truncation versus multiple lines
(Large preview)

Another way to handle long text strings is either to stretch a column by the longest content or wrap it and put on multiple lines. The first approach works better for more or less similar text strings. The second one works better if seeing the full content is more important for people than keeping the table vertically compact.


An example of the rules that define minimal column width for different cell types
(Large preview)

On one of my projects, we defined the minimal widths of columns to prevent tables from ungraceful resizing. We disabled squeezing columns behind a certain width depending on the content type.

Top Bar

What constitutes a table? Cells, columns, rows. In addition, complex tables often have a top bar. Like the rest of the components, the top bar is built of smaller elements — a title and commands.

Below, I collected the list of commands with all the variety of states, which we used in one of the products. We had icon commands for obvious metaphors like plus = add / create, trash bin = remove, arrow = move. Non-generic commands (e.g., assign, archive, balance) needed explicit textual naming. Moreover, some of the commands were accompanied by a dropdown menu.


A sample set of table-related commands of different types: icon, text, and dropdown
(Large preview)

Now we can try to combine different elements and see if it works. Here are some examples.


Three examples of table headers: a title only, title with icon commands, and title with text commands
(Large preview)

Of course, this is not the ultimate list of features and elements. It differs from one project to another and may include other things, for example:

  • Sorting by more than one column;
  • Customizable set of columns (ability to toggle them);
  • Expandable rows (a parent row can have child rows);
  • Logical operators for filtering and search (“and”, “or”, “else”, etc.).

If you hesitate which features to design and which not, here is a good principle. It’s Occam’s razor, or the law of parsimony. A designer should not create new instances if the existing ones satisfy the needs. You should “cut” the geeky features, which users might theoretically need in undefined future. The same story for the features that ideally fit one of a hundred situations but are useless in the remaining ninety-nine cases.

Whole Table

When all building blocks are ready, you can assemble a couple of tables for various purposes. This is a chance to spot inconsistencies. Most frequently I dealt with the following three types.

Read-Only Table

The simplest table type to build since it only shows as-is data. There are no filtering or editing options. Sorting or row hierarchy might help to analyze large chunks of data. Such a table is used for showing data, informing people about something.


An example of a read-only table with realistic data
(Large preview)
Search Table

The cells are not editable, the header has filter boxes and sorting controls, it’s possible to select rows. From my practice, such tables help to find, compare and select an item or several items out of a large range. For instance, filter out five of six thousand irrelevant tools from a catalog and then choose one needed tool.


An example of a search table with realistic data
(Large preview)
Editable Table

All or some cells are editable. Usually, there is no filtering because the order of rows might be customized. Such tables typically are accompanied by a toolbar and allow performing actions with rows.


An example of an editable table with realistic data
(Large preview)

In A Nutshell

  • Start with the smallest components, then gradually move towards bigger ones . Finally, mock up the whole thing.
  • Think about all of the possible states for each component beforehand.
  • Use Occam’s razor principle to keep the number of elements minimal yet sufficient.

Recommended reading: Design Systems by Alla Kholmatova

3. Define Interaction

Building blocks aren’t enough for such a complex interface piece as a table. A designer should think about “the rules of the game” and design logical principles and conventions behind the visual part. I’ll describe some typical things you’ll need to consider.

Numeric Data

How many decimals should numbers have in your table? One, two, five? What is the optimal precision level? I decide based on the accuracy users need for making the right decision. In some professions, fluctuation between 10932.01 and 10932.23 matters, whereas in other areas numbers 14 and 15 don’t really make a difference.

This is an example of numeric data rules my team used in a sophisticated engineering product.

  • Length
    Two decimals (57.53 m, 3.16 km); spaces are used as thousand separators (403 456.56 m).
  • Weight
    Two decimals (225.08 kg, 108.75 t); spaces are used as thousand separators (12 032.17 kg).
  • Money
    Two decimals ($9.45); commas are used as thousand separators ($16,408,989.00).
  • Diameter
    Three decimals (10.375 cm); no separators needed.
  • Latitude and longitude
    Eight decimals (26.4321121); minus sign used for the western longitude and southern longitude (-78.05640132).
  • Default
    For units not listed above — two decimals (32.05 g/m³, 86.13 C°).

One more thing we considered was the difference between the “true” data saved on servers and “approximated” data in the interface. The system used extremely accurate numbers with dozens of decimals in all calculations but people didn’t need to see it all the time. So we decided to show the number of decimals described above and expose the full number only when a table cell is active. For example, an engineer could enter 134432.97662301, and once he pressed Enter, the table showed 134 432.98. After clicking one more time, the engineer would see 134432.97662301 again.

Input Validation

Unlike the previous item about numbers, validation is important only for editable tables. It has two aspects. Firstly, the rules that qualify the entered data as valid or invalid. Secondly, either the messages that help to correct invalid data or mechanisms that fix it automatically. Usually, validation rules are too complex to reflect them in mockups or prototypes. So, designers can document them textually or in the format of flowcharts.

This is an example of message templates I once used. Text in angle brackets is dynamic and comes from the calculation engine or database.

  • Should be greater than number measurement unit. Optional explanation.
  • Should be less than number measurement unit. Optional explanation.
  • Should be between number 1 and number 2 measurement unit. Optional explanation.
  • Min value should be less than max value.
  • Max value should be greater than min value.
  • Min and max values should not be equal.

Commands

Editable tables with toolbars usually need a set of rules when toolbar commands are enabled and when disabled. These states can depend on the fact that a row is selected, on the number of selected rows, on the position or content of the selected row or rows, and other conditions. Below is one of the numerous ways to document such logical rules.

So, we have a table with some chemicals. It has such commands as “Add a row”, “Move up”, “Move down”, Delete”, “Recalculate”, and “Settings”.


An example of an editable table with a set of commands
(Large preview)

And here is the description of command states. It turns out their availability depends on one or several conditions.


A sample of logical rules that describe the behavior of table commands
(Large preview)

The next step is defining the result of each command. For example, what happens when I select two remote rows and click “Move up”? Or what is the result of clicking “Recalculate”? All these questions should be answered or at least considered beforehand.

Container And Responsiveness

How will the table be placed in the interface? For example, will it occupy some space within an existing container or be a separate module? The answers to these questions totally depend on a product and it’s better to foresee possible issues and thoroughly define the principles.


Three popular options of tale environment: as the key element of a page, as a dashboard module, and in a dialog
(Large preview)

When I design web applications, I usually think about at least three types of containers for tables. The most typical case is when a large table is the center of a screen and occupies as much space as possible. Such a table might not have its own title since the whole screen is devoted to working with the table. Small and medium tables can become autonomous modules of a dashboard as well as other items like graphs, diagrams, schemes. In this case, the top bar of a table plays the role of the card header. And finally, in large enterprise applications, tables often exist inside of popup dialogs. There should be wise guidelines so that dialogs don’t blow up because of too much content.

Another aspect of placing a table in the UI environment is the available screen area. The majority of enterprise applications are meant to be used primarily on the desktop. Table responsiveness is limited to simple stretching and squeezing behavior. Typically, tables with lots of rows and few columns occupy 100% of the available width. As a result, cells are evenly distributed on the screen, and more text can be shown without truncation of wrapping. On the other hand, huge gaps usually appear between columns, which contradicts the design law of proximity. That’s why some applications use lines between the rows or white-end-gray zebra coloring to make information better readable.


Examples of a table stretching and workarounds to make data clear: lines and “zebra” coloring
(Large preview)

A better way is to define rational default widths and allow manual resizing if needed. For reading a table, it’s better to have some empty space on the right than gaps between the columns.

If a table contains both many rows and columns, horizontal and vertical scrolls are unavoidable.

The key essence of a complex table is being large, thus giving the bird-eye view of data. Unfortunately, I cannot name a really good method of using large tables on smartphone screens. Excel and Google spreadsheets lose their power on small screens, although there are effective ways to handle small tables. For instance, converting a table to a set of cards.


An example of the table transition into cards on mobile
(Large preview)

Accessibility

Even an exceptionally smooth and nice table might become a nightmare for users. Therefore, it’s so important to follow accessibility principles. Web Content Accessibility Guidelines (WCAG 2.0) has a chapter about tables. Most of the material is about proper coding; however, a designer has a lot to think about too.

Here are major design considerations in terms of accessibility.

  • Give a title and prepare a concise summary.
    A visually impaired user should be able to get the idea of a table without voice-processing all its cells.
  • Mind the font size.
    Although there is no official minimal size for the web, 16 px (12 pt) is considered to be optimal. Additionally, a user should be able to increase it up to 200% without breaking the whole layout.
  • Test colors for people with color blindness.
    Text and controls should have enough contrast with their background. Color ratio 3:1 is minimally required (the more, the better). Also, color shouldn’t be the only way of marking things. For example, error messages shouldn’t rely on red text only, a warning icon will give additional clues to color-blind users.
  • Avoid small and ambiguous controls.
    Clickable components are considered to be touch-friendly if they are at least 40×40 px. Commands represented by icons should either be labeled or have tooltips and alternative text. Designers shouldn’t overuse icons because users might not understand complex metaphors correctly.

You can also utilize online tools for checking accessibility, for instance, Wave. Not only does it find accessibility issues and features, but also highlights them directly on a page and explains how to fix them.

In A Nutshell

  • Content unification and formatting is the designer’s work too.
  • Think beyond “things”, elements of your interface, and consider use cases and frequent patterns.
  • When everything inside is consistent and compatible, it’s time to think about how it fits the rest of the interface.

Conclusion

We’ve just walked through the process of constructing a complex table. Different projects require different approaches, but there is one universal principle. A designer should make all elements work together in any combination. That’s why it’s good to start with collecting needs and making small blocks first. And, of course, testing with users as soon as you have something clickable and realistic.

Further Reading

Smashing Editorial(dm, ra, il)



Source link

iOS Performance Tricks To Make Your App Feel More Performant — Smashing Magazine


About The Author

Axel is a software developer who aspires to be indie, based in Kuala Lumpur, Malaysia.
More about Axel

Good performance is critical to delivering a good user experience, and iOS users often have high expectations of their apps. A slow and unresponsive app might make users give up on using your app or, worse, leave a bad rating.

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.


Cell reuse queue mechanism
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!

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 :


Launch screen and first view look similar
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.


Xcode shows error when a custom class is used
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).


iOS fabricates the illusion of apps running in the background by taking a screenshot of the most recent view
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
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:


This will inform the drawing system to skip drawing whatever is behind this view
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.


Green is non-color blended, red is blended layer
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.


With an opaque color background, the layer doesn’t need to blend with another layer
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.


Using a transparent background color will cause layer blending
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.
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.


Avoid running performance-intensive or time-consuming task on the main thread
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
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

Smashing Editorial(jd, ra, il)



Source link

A Guide To CSS Support In Browsers — Smashing Magazine


About The Author

Rachel Andrew is not only editor-in-chief of Smashing Magazine, but also a web developer, writer and speaker. She is the author of a number of books, including …
More about Rachel

It can be frustrating when you want to use a feature and discover that it is not supported or behaves differently across browsers. In this article, Rachel Andrew details the different types of browser support issues, and shows how CSS is evolving to make it easier to deal with them.

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.

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%;
}

A four column layout
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%;
}

A four column layout with squished columns
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;
    }
}

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.

Smashing Editorial(il)



Source link

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


About The Author

Souvik works as a Technical Writer at FusionCharts, and has a background in back-end technologies and databases. In his free time, Souvik dabbles around with …
More about Souvik

Creating a dashboard with API data is often a complex affair. Choosing your tech stack, integrating APIs, selecting the right charts and beautifying with CSS styles can become tricky. This tutorial is a step-by-step guide on how to help you create a weather dashboard in Vue.js using API data.

(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:


Dashboard structure
(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.


    A screenshot of the command-line/terminal
    (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!


A screenshot of the Vue.js website
(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="http://www.smashingmagazine.com/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="http://www.smashingmagazine.com/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).


A screenshot when you refresh the webpage with localhost:8080
(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 

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.


A screenshot of the browser with the message “This component’s code is in App.vue”
(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:

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>

A screenshot of the browser with the message “This component’s code is in App.vue. This child components of Content.vue are: TempVarChart.vue, Highlights.vue”
(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>

A screenshot of the browser with the result of the code provided
(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:


A diagram explaining the hierarchy of the code
(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:


Result of the code displayed in the browser
(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.


Result of the code displayed in 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:


Result of the code displayed in the browser
(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="http://www.smashingmagazine.com/./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="http://www.smashingmagazine.com/./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 
// To format the “possibility” (of weather) string obtained from the weather API
formatPossibility: function(str) {
     str = str.toLowerCase().split('-');
     for (var i = 0; i 
// 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[i].minVal &&
         windDir 

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_long =
         (Math.round(coordinates.long * 10000) / 10000).toString() + '°E';
     } else if (coordinates.long 
/*
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 
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="http://www.smashingmagazine.com/./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="http://www.smashingmagazine.com/./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!


The application as shown in the browser
(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

A diagram showing Hourly Temperature
(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.


UV Index
(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:


A screenshot of the Horizontal Linear Gauge representing Air Visibility (16km)
(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:


Wind Status: Wind Direction (left) and Wind Speed (right)
(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="http://www.smashingmagazine.com/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.


The result
(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.

Smashing Editorial(ms, ra, il)



Source link

Spread The Love! Inspiring Wallpapers For February 2019 — Smashing Magazine


About The Author

Cosima has been an editor at SmashingMag since 2013. Whenever she’s not writing articles for the bi-weekly Smashing Newsletter or the Quick Tips series, …
More about Cosima

Why not start the new month off with a fresh wallpaper? Designed by artists and designers from across the globe, the wallpapers in this collection are available with and without a calendar for February 2019.

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:

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.

Savannah Stroll

Love Is Worth Fighting For

Designed by Maria Keller from Mexico.

Love Is Worth Fighting For

Dark Temptation

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

Dark Temptation

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.

Feel The Love!

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.

Cold And Frost

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.

What Is Love

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.

Polar Cold

Lovely Day

Designed by Ricardo Gimenes from Sweden.

Lovely Day

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.

Let Love Speak

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.

Umbrella Day

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.

Sunset

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.

World Radio Day

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.

Dog Year Ahead

Balloons

Designed by Xenia Latii from Germany.

Balloons

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.

Febpurrary

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.

Minimalistic Love

“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.

“Greben” Icebreaker

Winter Wonderland

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

Winter Wonderland

Love Angel Vader

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

Love Angel Vader

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.

Febrewery

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.

Farewell, Winter...

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.

Principles of Good Design- Dieter Rams

I Believe I Can Fly

Designed by Elise Vanoorbeek from Belgium.

Smashing Wallpaper - february 13

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.

Out There, There’s Someone Like You

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.

The Great Beyond

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!



Source link

The Authoring Experience Compared — Smashing Magazine


About The Author

Kevin Weber is a software engineer at Airbnb. He created multiple open-source projects for WordPress and AEM. He led the frontend team at Infield Digital until …
More about Kevin

Block-based editors improve the user experience for those who create and publish content. What could future editors look like? Let’s compare the new authoring experience in WordPress with the experience from AEM.

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.

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.


Title component in AEM with an overlay that includes an edit button
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.

Fragment editor for populating a component with airport data
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.


Page template editor
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.


Screen where admins handle detailed permissions.
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.


Page editor with workflow message
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.


Visual workflow editor in Git
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. Unfortunately, AEM doesn’t offer such a tool. (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.

Page editor emulating an iPad-sized screen
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. 😉


Dropdown with layout modes
Switch the mode through a dropdown. (Image source) (Large preview)

Editor where author defines a target audience
In targeting mode, content can be personalized and tested right from the page editor. (Image source) (Large preview)

Putting development complexity and money aside, the consequence of such an effort can result in a superb authoring experience. The concept of editing modes demonstrates how a simple dropdown creates an opportunity for authors to get a range of jobs done while staying on a single page.

Visual Single-Page Editor

Looking at the screenshots in this article, you must have realized that AEM’s page editor is not just component-based but also visual: If a component gets updated, the change becomes visible immediately and the author doesn’t have to open a preview in a new window. Quite a feature. Even though page builders are omnipresent in the WordPress ecosystem, the team behind WordPress has yet to define a best practice for visual editing. Let me take this one step further and ask: What happens if you marry visual editors with single-page applications (SPAs)?

SPAs are websites where navigating from one page to another feels seamless because the browser doesn’t have to reload the whole page. Some popular websites such as Gmail and Facebook are SPAs but most of the sites on the internet are not. One reason for rather low adoption is that creating SPAs is hard, and maintaining SPAs with thousands of pages is even harder. There are currently two major ways for managing content in SPAs:

  • The content of a site gets updated by updating code. That’s obviously not authoring-friendly.
  • The content is managed in a CMS that is decoupled from the visitor-facing part of the website. The content from the CMS is consumed via an API, for example by a React app. The authoring interface looks different from the assembled site that the visitor will see.

Implementing a visual editor and an SPA each by itself is already a tough technical challenge. Having a visual editor that works with an SPA is nearly unheard. Adobe’s team is working on supporting SPAs in AEM while trying not to compromise any benefits of their existing system. Even though promising first versions have been released to the AEM community in 2018, there’s still a lot of work to be done.


Weather component with overlay
This page shows a React app. Note how AEM has added a layer on top of the weather component so authors can edit its properties. (Image source) (Large preview)

Summary

Adobe Experience Manager comes with several useful features that have already made or will make their way into popular open-source projects. AEM didn’t necessarily invent the concepts highlighted in this article, but it certainly commercializes well as one of the most authoring-friendly systems on the market.

The concept of components became mainstream with the introduction of blocks in WordPress. The concept of fragments, page templates, permissions and workflows are at least partially implemented in WordPress and are important for teams with many authors that serve content to multiple channels.

The authoring experience can be improved even more using a visual editor with edit modes and support for single-page applications. Such an editor is difficult to implement but as the efforts by Adobe indicate, the improved experience might be worth the effort and eventually make it into WordPress as well.

Further Reading

Smashing Editorial(dm, ra, il)



Source link

A Guide For Product Managers — Smashing Magazine


About The Author

Mike Sędzielewski is a co-founder of Voucherify and rspective. He lives in Katowice, Poland and writes about “headless” platforms and marketing technology.
More about Michał

API-based solutions are becoming a critical building block of modern digital products. What are they? How can they impact your design process? Finally, how to evaluate them without bothering your software team?

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.

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.

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
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
    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
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
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
    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
    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
    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
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! 🙌


Successful response from Open Weather Map API
Successful response from Open Weather Map API (Large preview)

But there’s much more you can get from the API using Postman. Ready to become a true API hacker?

API parameters: Getting Tailored Responses

Usually, API endpoints have some utility features you can use to adjust the API response, e.g. if you need a better data format or you want to get the data in a particular order. These options are often hidden behind some parameters you can find in the documentation.

Query parameters are just a structured text you add at the endpoint address with the following pattern:

  1. A question mark (?) after the path,
  2. Name of a parameter,
  3. Equals (=) symbol,
  4. Value of the parameter,
  5. Ampersand (&) and others follow with points 2-4 (in this way you can add as many parameters as you want).

Take our first request as an example:

https://openweathermap.org/data/2.5/find?q=Katowice&appid=b6907d289e10d714a6e88b30761fae22

Important note: The order of query params doesn’t matter.

?q=Katowice&appid=b6907d289e10d714a6e88b30761fae22

The above is the same as the following:

?appid=b6907d289e10d714a6e88b30761fae22&q=Katowice

As mentioned, query params are described in API docs. The following excerpt from the weather API documentation shows you how to get the temperature in different units (imperial or metric):


OpenWeatherMap API documentation excerpt
OpenWeatherMap API documentation excerpt (Large preview)

Try sending these two options with Postman to see the difference in results. Remember to add your API key at the end of the endpoint address.

Note: Always take some time to study the documentation and find parameters which can save you or your development team some serious time.

API Request Options: How To Send Data To The API

So far, we’ve been getting information from the API. What if we want to add or modify information in the database behind the API? Request methods are the answer.

Let’s take a look at Postman once again. You might have noticed an upper-case GET label next to the API endpoint address. This represents one of four request methods. GET means we want to get something from the API (thanks captain) and it’s a default option. What are the other options?

Method Name What it does with the API
GET The API looks for the data you’ve requested and sends it back to you.
POST The API creates a new entry in the database and tells you whether the creation is successful.
PUT The API updates an entry in the database and tells you whether the update is successful.
DELETE The API deletes an entry in the database and tells you whether the deletion is successful.

Still confusing? Let’s move to examples.

API POST: How To Create A Record In The API

We can’t create or update anything with Weather API (because it’s meant to be read-only), so we need to find a different one for testing purposes.

Let’s come up with some more business-oriented example. We’re going to simulate the following scenario:

If it’s rainy, create a “cheer up” discount coupon for your customers.

We’re going to use Voucherify which provides an API for creating and tracking promotions for any e-commerce system.

Disclaimer: I’m a co-founder of Voucherify. I’m happy to answer your questions about designing and implementing digital promotions, and about our API of course. 🖐

We already know how to get them from the previous example, so let’s focus on creating a voucher:

  1. As we’ve said, we should always start with the documentation.
  2. The quick start guide tells us to get our API key.
    Note: Instead of creating an account, you can use the test keys from the quick start guide — we’ll show you how in a minute.
  3. Now, let’s find how to create a discount coupon. In Voucherify, this kind of promotion is represented as “voucher”.
  4. From the docs, you’ll learn that to create voucher, you need to call a POST method to /vouchers endpoint.
  5. Create a new Request in Postman.
  6. Change method to POST.

    Postman - API methods selection
    Postman – API methods selection (Large preview)
  7. Paste the Voucherify endpoint https://api.voucherify.io/v1/vouchers/ and click Send.

    Missing credentials
    Missing credentials (Large preview)
  8. Oh snap, we’re not authorized to call this endpoint. As you might have guessed, we need to provide API keys.

    Voucherify has a slightly different way of doing so. Instead of putting them as query params, you should put them to Headers. This is a common approach because it’s easier to implement and maintain keys this way rather than append them as query params.

    Add the keys as in the picture and click Send. Notice that Voucherify requires two keys. Here the ones you can use for the purpose of this tutorial:
    X-App-Id: 8a824b12-0530-4ef4-9479-d9e9b9930176
    X-App-Token: 9e322bac-8297-49f7-94c8-07946724bcbc


    Providing API keys in Postman
    Providing API keys in Postman (Large preview)

  9. We get another error message, this time it says the payload cannot be empty.

    Voucherify API returns error code 400
    Voucherify API returns error code 400 (Large preview)

    What the heck is a payload? As in the case of GET we want to retrieve some information, with POST we need to send something and the message we send is called the payload and it’s usually a JSON file.

    Now Voucherify API is complaining that we didn’t provide one, which means that it cannot create a voucher because we didn’t tell what kind of voucher it should create. So what now? Back to the docs!

  10. Let’s find what kind of information this request needs to succeed. We can see a lot of options on the list.

    Voucherify API documentation excerpt
    Voucherify API documentation excerpt (Large preview)

    One parameter (type) is required and another optional. Let’s say it’s going to be a 20% off discount, available for the first 100 customers, expiring today. Now we need to find parameters responsible for this discount features and put them together into a format understable to Voucherify API. As you can see in the examples above, the JSON notation you should use looks like this:

    {
        "type":"DISCOUNT_VOUCHER",
        "discount":{
            "percent_off":20.0,
            "type":"PERCENT"
        },
        "expiration_date":"2018-12-03T23:59:59Z",
        "redemption":{
            "quantity":100
        }
    
  11. To set up the payload in Postman, paste the JSON message into Body tab. Select “raw” type and JSON from the list of available payload formats and confirm with Send.

    POST method in Postman
    POST method in Postman (Large preview)
  12. Voila! Voucherify has created our 20% off discount coupon successfully (as we’re working with a test account, all generated codes start with the “voucherify.io-” prefix). The marketing team can now share the code with customers and Voucherify will automatically validate it whenever they come to your shop to redeem it.

    Voucherify returns 200 OK
    Voucherify returns 200 OK (Large preview)

    But how do we know it is a successful request? First of all, we can see that Voucherify has sent us a message which, according to their docs, looks like a correct API response. Secondly, Postman displays the status 200 OK — which means our request is successful. Why 200 and what’s the status?

API Status Codes And Error Messages

Most of the APIs you’ll ever interact with will be HTTP-based. The HTTP is a protocol which standardizes communication between various client applications and servers on the Internet.

One of the key elements of HTTP is status codes. By understanding the status code you (or actually systems you implement) can immediately tell what’s happened to your request. Chances are you faced one of the most popular status codes when you typed the wrong link — 404


Voucherify 404 error code
Voucherify 404 error code (Large preview)

But there are many more and end-users usually don’t see them. They range from 100+ to 500+. In general, the numbers follow the following rules:

  • 200+ means the request has succeeded;
  • 300+ means the request is redirected to another URL;
  • 400+ means an error that originates from the client application has occurred;
  • 500+ means an error that originates from the server has occurred.

If you could go through the steps once again, you would see that Voucherify replied with 401 Unauthorized when we didn’t provide API keys. Or 400 Bad Request when there was no payload which is required for Create Voucher request. Finally, we received 200 as a token of a successful API call.

If you’re curious about HTTP status codes meaning, there’s no better place than HTTP Cats (or this article maybe). 🙃


HTTP Cats 401 Unauthorized
HTTP Cats 401 (Large preview)

Summary

The growing amount of data and the need for speed in building products pushed APIs to become the lingua franca of digital teams. To design systems based on API-first systems, make sure you understand the vendors’ offerings. This hands-on testing guide is a good starting point in doing so. It will help you explore the API capabilities even before you throw it to your teaching team, saving their energy — and yours as well.

Further Reading

Smashing Editorial(rb, ra, il)





Source link