Collective #508

Front-end Developer Handbook 2019 Cody Lindley wrote this guide that outlines and discusses the practice of front-end engineering, how to learn it and what tools are used when practicing it in 2019. Check it out Our Sponsor Visual Composer founder had an AMA Read more

Collective #500

dreamt up by webguru in Uncategorized | Comments Off on Collective #500

C500_nested

CSS Nesting Module

The draft of a new CSS module that introduces the ability to nest one style rule inside another.

Read it




C500_scroll

lax.js

A light-weight JavaScript library to create smooth and beautiful animations when you scroll.

Check it out




C500_core

Core

A responsive front-end feature kit in React. By the folks of Mason.

Get it
















Collective #500 was written by Pedro Botelho and published on Codrops.


Source: Codrops, Collective #500

A Detailed Comparison Between WordPress And October CMS

dreamt up by webguru in Uncategorized | Comments Off on A Detailed Comparison Between WordPress And October CMS

A Detailed Comparison Between WordPress And October CMS

A Detailed Comparison Between WordPress And October CMS

Leonardo Losoviz



Three months ago, WordPress finally released React-powered Gutenberg to power its default content editing experience, triggering many people who are not happy with this change to look for alternatives. Some folks decided to fork and release pre-Gutenberg WordPress, however, for me this doesn’t make much sense since it still carries 15 years worth of technical debt. If I were to find an alternative to WordPress, I would try to avoid being stuck in the past, and aim for a clean cut through some mature platform built on modern foundations.

This article compares WordPress to the arguably similar yet more modern October CMS on a wide arrange of both technical and non-technical topics. The goal of the article is not to convince people to stick to WordPress or to switch to October CMS, but simply to demonstrate what aspects must be taken into account before concluding the move to a different platform. The same comparison could (and should) also be done with other platforms before making a sensible decision.

Why October CMS

I found out about October CMS when it won an award, after which I went into research mode and spent a good deal of time digging deep into this CMS — from the perspective of both a user and a developer. As I gained knowledge on this CMS, I felt confident that I could provide an objective evaluation of its features as contrasted to WordPress. I chose this CMS for the comparison over alternative options such as Grav, Statamic, ButterCMS, Joomla, Drupal, Jekyll, Hugo, and others, for the following reasons:

  • I know how this CMS works (unlike Grav);
  • It is free and open source (unlike Statamic and ButterCMS);
  • At five years, it is “relatively” new (unlike Joomla and Drupal);
  • It is a dynamic (not static) content generator and based in PHP (unlike Jekyll and Hugo).

I believe that October CMS is a good candidate because it is based on Laravel which is a framework used for building modern applications. After seven years of existence, it has received positive approval from developers (as evidenced by its sizeable community and ecosystem), and marks a distinct contrast over coding in WordPress, i.e. WordPress is mostly procedural programming while Laravel is decidedly object-oriented programming.

What’s The Difference Between The Two?

Below I will compare WordPress and October CMS on different categories and highlight what, I believe, is good and not so good about them. However, I will not pick a winner, since that’s not the objective of the article and, in any case, there is no “best” or even “better” CMS: each CMS has its own set of strengths and weaknesses that will make it more or less suitable for each task, project, company, team, and anything else. Moreover, a project may benefit from using more than one CMS, such as using some CMS to manage and provide data, and another CMS to render the view. To decide which of the dozens of CMSs out there is most suitable for your own needs is entirely up to you.

In addition, this article could never draw definitive conclusions since it is only concerned with a subset of all possibilities. For instance, we can also find online comparisons such as “WordPress vs Drupal vs Joomla”, “WordPress vs Static Site Generators” and even “WordPress vs Medium”. Because none of these articles sees the full picture, then none of these comparisons can ever be conclusive, and should not be treated as such.

Let’s start with the comparison.

Philosophy And Target Group

It is no coincidence that WordPress powers nearly 1 in 3 websites. Ever since its inception, it has strived to be extremely user-friendly and has done so successfully, removing friction for technical and non-technical users alike as well as for people from all backgrounds — irrespective of their education and economic levels. WordPress’ founder Matt Mullenweg expressed that WordPress’ motto of “Democratize Publishing” for the current era meant the following:

“People of all backgrounds, interests, and abilities should be able to access Free-as-in-speech software that empowers them to express themselves on the open web and to own their content.”

WordPress is easy to use for everyone and its inclusivity is evidenced on the development side too: It’s not uncommon to find people without a programming background (such as marketers, designers, bloggers, sales people, and others) tinkering with their WordPress installations, designing their own themes and successfully launching their own websites. WordPress is user-centric, and the needs of the users trump those of the developers. In WordPress, the user is king (or queen).

In contrast, October CMS is more geared towards the developer, as explicity established from its very first release:

“October makes one bold but obvious assumption: clients don’t build websites, developers do. The role of a client is to manage the website and convey their business requirements. The web developer, and the industry itself, revolves around mediating these factors.”

In the words of its founders, the CMS’ mission is to “prove that making websites is not rocket science.” Being based on Laravel, October CMS can claim to have strong foundations of reusable, modular code that can produce properly-architected applications, maintainable in the long term and fully customizable without requiring hacks — the type which attracts serious programmers. October CMS can also provide a great user experience, however, it is not as simple or frictionless as that provided by WordPress. Users may need to be explained how to use certain functionality before being able to use it. For instance, embedding a form from some plugin has a lengthy explanation on how to do it, which is more cumbersome than the self-evident, drag-and-drop functionality provided by several form plugins in WordPress.

Installation

WordPress is famous for its 5-minute installation, even though many people point out that (taking into consideration all the plugins that must be installed) a typical installation requires 15 minutes or more. In addition, WordPress also offers the Multisite feature, which allows us to create a network of multiple virtual sites under a single installation. This feature makes it easy for an agency to administer the sites of multiple clients — among other user cases.

Installing October CMS is also very smooth: The Wizard installation itself takes even less than five minutes, and if you install it through the Console installation, it is even faster. You can do the latter by simply navigating to the target directory and then executing curl -s https://octobercms.com/api/installer | php (after which we need to input the database configuration, otherwise it behaves as a flat-file CMS). Once the installation has been completed, we will have a fully functioning website, but still quite bare (if you add the time needed to install and configure the required plugins, you can expect it to take at least 15 minutes).


October CMS Wizard installation
Installing October CMS with the Wizard is a breeze. (Large preview)

Security

WordPress has been accused of being insecure due to the high amount of vulnerabilities that are constantly found. This forces users to have the software for the CMS and all installed plugins always up to date to avoid security exploits. Among the main issues is WordPress’ support for older versions of PHP which are not supported by the PHP development community anymore (WordPress currently supports PHP 5.2.4, while the latest fully supported PHP version is 5.6). However, this problem should be resolved in April 2019 when WordPress will officially start supporting PHP versions 5.6 and upwards.

Otherwise, WordPress is not necessarily insecure because of itself, but because of its high popularity, which makes it a primal target for hackers. However, this plays both ways: WordPress ubiquity means that its security team must really take their job seriously by constantly looking for exploits and fixing them as soon as possible, otherwise up to a third of the web is at risk. The stakes are just too high.

October CMS, on the other hand, doesn’t have a reputation of being insecure. However, since there are roughly 27,000 live sites that use October as compared with WordPress’ millions, we can’t judge the two of them on the same terms. Nevertheless, the team behind October CMS does take security seriously, as evidenced by the Wizard installation’s prompt to input the CMS backend URL, set as /backend by default but changeable to anything else, as to make it more difficult for hackers to target the site. In contrast, changing WordPress’ login and backend URLs from /wp-login.php and /wp-admin respectively to something else must be done through a plugin. In addition, October CMS can function as a flat-file CMS (i.e. without a database) and avoid database-related vulnerabilities such as SQL injection.

Technology Stack

Both WordPress and October CMS run on the traditional LAMP stack: Linux, Apache, MySQL, and PHP. (However, only PHP is fixed: we can also use Windows, Nginx, MariaDB, and others.) October CMS can also behave as a flat-file CMS, meaning that it can do without a database, however, at the cost of forgoing many functionalities (such as blog posts and users) the only functionality that is guaranteed is pages, which is considered to be the basic unit for the creation and publishing of content and shipped as a core feature.

Concerning the language stack, sites built with both WordPress and October CMS are based on HTML, CSS, and JavaScript (note that PHP is used to generate the HTML). October CMS also makes it easy to use LESS and SASS files.

Programming Paradigm

WordPress follows a functional programming paradigm, based on calculating computations by calling functions devoid of application state. Even though WordPress developers do not need to stick to functional programming (for instance, for coding their themes and plugins), the WordPress core code inherits this paradigm from 15 years of preserving backwards compatibility, which has been one of the pillars to WordPress’ success but which has the unintended consequence of accumulating technical debt.

On the other side, October CMS follows an imperative programming paradigm, based on calculating computations by manipulating objects’ state. October CMS sits on top of Laravel, a web framework fully founded on Object-Oriented Programming principles that enable the production of modular applications based on concepts such as the Model-View-Controller to decouple the user interface from the application data, Dependency Injection to configure class dependencies, and the Interface Segregation Principle to define the core services provided by the framework, among many others.

Hooks/Events

Programming in WordPress could be characterized as HDD which stands for “Hook-Driven Development”. A hook is a mechanism that allows changing a default behavior or value and allowing other code to execute related functionality. Hooks are triggered through “actions” which allow executing extra functionality, and “filters” that allow modifying values.

Hooks, which are widespread across the WordPress codebase, are one of the concepts that I most like from coding in WordPress. They allow plugins to interact with other plugins (or with a core or theme) in a clean way, providing some basic support of Aspect-Oriented Programming.

Good news is that Laravel (and in consequence October CMS) also supports the concept of hooks, which is called “events”. Events provide a simple observer implementation, enabling code to subscribe and listen for events that occur in the application and react as needed. Events make it possible to split a complex functionality into components, which can be installed independently yet collaborate with each other, thus enabling the creation of modular applications.

Dependence on JavaScript Libraries

The latest version of WordPress incorporates React-powered Gutenberg for its default content creation experience. Hence, WordPress development now relies by and large on JavaScript (predominantly through React), even though it is also possible to use other frameworks or libraries (as evidenced by Elementor Blocks for Gutenberg which is based on Marionette). In addition, WordPress still relies on Backbone.js (for the Media Manager) and jQuery (legacy code), however, we can expect the dependence on these libraries to wither away as Gutenberg is consolidated as the new norm.

October CMS depends on jQuery, which it uses to implement its optional AJAX framework to load data from the server without a browser page refresh.

Pages, Themes and Plugins

Both WordPress and October CMS treat a page as the basic unit for creating and publishing content (in WordPress case, in addition to the post), support changing the site’s look and feel through themes, and allow to install and extend the site’s functionalities through plugins. Even though the concepts are the same in both CMSs, there are a few differences in implementation that produce somewhat different behavior.

In WordPress, pages are defined as content and stored in the database. As a result, page content can be created through the CMS only (e.g. in the dashboard area), and switching from one theme to another doesn’t make an existing page become unavailable. This produces an overall frictionless experience.

In October CMS, on the other hand, pages are static files stored under the theme directory. On the positive side from this architectural decision, page content can be created from an external application, such as text editors like Sublime or Visual Studio Code. On the negative side, when switching from one theme to another, it is required to manually recreate or copy the pages from the current to the new theme, or otherwise, they will disappear.

Significantly, October CMS resolves routing through pages, hence pages are used not just as containers for content but also for functionality. For instance, a plugin for blogging depends on a page for displaying the list of blog posts under a chosen URL, another page to display a single blog post under another chosen URL, and so on. If any of these pages disappear, the associated functionality from the plugin becomes unavailable, and that URL will produce a 404. Hence, in October CMS themes and plugins are not thoroughly decoupled, and switching themes must be done carefully.


Editing a file from inside or outsite October CMS
October CMS enables the creation of content from external applications. (Large preview)

Core vs Plugin Functionality

WordPress attempts to deliver a minimal core functionality which is enhanced through plugins. WordPress relies on the 8020 rule” to decide if to include some functionality in its core experience or not. If it benefits 80% of the users it goes in, otherwise, it belongs to plugin-land. When adding plugins to a site, they can lead to bloat if too many plugins are installed. Plugins may also not work well with one another, or execute similar code or load similar assets, resulting in suboptimal performance. Hence, whereas launching a WordPress site is relatively easy, a bigger challenge is its general maintenance and being able to preserve an optimal and performant state when adding new features.


WordPress plugin directory
The WordPress plugin directory claims to have almost 55,000 plugins. (Large preview)

Likewise, October CMS also attempts to deliver a minimal core functionality, but on steroids: the only guaranteed functionality is the creation and publication of pages, and for everything else we will need to install one plugin or another, which is expressed as:

“Everything you need, and nothing you don’t.”

The objective is clear: most simple sites are only composed of pages, with possibly no blog posts, users or login area. So why should the application load resources for these when they are not needed? As a consequence, functionalities for blogging, user management, translation and several others are released through the plugin directory.


October CMS plugins directory
Searching for ‘Rainlab’ in October’s plugins directory displays plugins created by October CMS’ team. (Large preview)

October CMS also includes certain features in its core which (even though they are not always needed) can enhance the application significantly. For instance, it provides out-of-the-box support to upload media files to Amazon S3 and accesses them through the Rackspace CDN. It also includes a Media Manager which is mostly used through plugins, e.g. for adding images into a blog post. (Pages can also use the Media Manager to embed media files, however, the CMS also ships with an Assets section to upload media files for these which seems more suitable.)

I believe that October’s opinionatedness can perfectly enable us to produce an application that is as lean as possible — mostly concerning simple sites. However, it can also backfire and encourage bloat, because the line of what is needed and what is not is an arbitrary one, and it’s difficult to be set in advance by the CMS. This difficulty can be appreciated when considering the concept of a “user”: In WordPress, website users and website admins belong to the same user entity (and through roles and privileges we can make a user become an admin). In October CMS, these two are implemented separately, shipping in core the implementation for the website administrator which can log in to the backend area and modify the settings, and through a plugin the implementation of the website user. These two types of users have a different login process and a different database table for storing their data, thus arguably breaching the DRY (Don’t Repeat Yourself) principle.

This problem arises not only concerning the behavior of an entity but also what data fields it must contain. For instance, should the website user data fields be predefined? Is a telephone field required? What about an Instagram URL field, considering that Instagram got kind of cool only recently? But then, when building a professional website shouldn’t we use a LinkedIn URL field instead? These decisions clearly depend on the application and can’t be decided by either CMS or plugin.

The October CMS plugin called User implements users but without any user field, on top of which plugin User Plus adds several arbitrary user fields, which are possibly not enough, so plugin User Plus+ adds yet other user fields. When, where and how do we stop this process?

Another problem is when there is no room to add new capabilities to an entity, which leads to the creation of another, extremely similar entity, just to support those required capabilities. For instance, October CMS ships with pages, and allows to create “static pages” through a plugin. Their nature is the same: both pages and static pages are saved as static files. The only difference between them (as far as I can tell) is that static pages are edited with a visual editor instead of the HTML editor, and can be added to menus. In my opinion, only structural differences, such as having one entity saved as a static file and the other one stored in the database, could justify creating a second entity for a page (there is a pull request to do this), but for simple features, as is the case currently, it constitutes development bloat.

In summary, a well implemented October CMS application can be very lean and efficient (e.g. by removing the database when not needed), but on the contrary it can also become unnecessarily bloated, forcing developers to implement several solutions for similar entities, and which can be very confusing to use (“Should I use a page or a static page?”). Because neither WordPress or October CMS has found a perfect solution for removing bloat, we must design either application architecture with care to avoid down-the-road pain.

Content Creation

Gutenberg makes two important contributions to WordPress: It uses components as the unit for building sites (which offers several advantages over coding blobs of HTML), and it introduces an entity called a “block” which, once Gutenberg Phase 2 is completed (presumably in 2019), will provide a unified way to incorporate content into the site, thus enabling a simpler user experience as opposed to the more chaotic process of adding content through shortcodes, TinyMCE buttons, menus, widgets, and others.


WordPress Gutenberg
Since WordPress 5.0 Gutenberg is the default content creation experience. (Large preview)

Because Gutenberg blocks can produce and save static HTML as part of the blog post, then installing many Gutenberg blocks doesn’t necessarily translate into bloat on the website on the user side, but can be kept restricted to the admin side. Hence, Gutenberg can arguably be considered a good approach to produce websites in a modular way, with a simple yet powerful user experience for creating content. Possibly the biggest drawback is the (unavoidable, but not easily so) requirement to learn React, whose learning curve is rather steep.

If React components are the basic unit for creating content in WordPress, October CMS is based on the premise that knowing good old HTML is enough for building sites. Indeed, when creating a page, we are simply presented an HTML (Markup) editor:


October CMS page creation
Creating a page in October CMS. (Large preview)

If the page were solely static HTML, then there would be no need for a CMS. Instead, October CMS pages are written using Twig templates which are compiled to plain optimized PHP code. They can select a layout to include the scaffolding of the page (i.e. repetitive elements, such as the header, footer, and so on), can implement placeholders, which are defined on the layout to allow the page to customize content, and can include partials, which are reusable chunks of code. In addition, pages can include content blocks, which are either text, HTML or Markdown files that can be edited on their own and can attach components which are functionalities implemented through plugins. And finally, for whenever HTML is not enough and we need to produce dynamic code, we can add PHP functions.

The editor is all about HTML. There is no TinyMCE textarea for adding content in a visual manner — at least not through the default experience (this functionality belongs to plugin-land). Hence, having knowledge of HTML could be considered a must for using October CMS. In addition, the several different inputs for creating content (pages, layouts, placeholders, partials, content blocks, components, and PHP functions) may be very effective, however, it is certainly not as simple as through the unified block interface from WordPress. It can even get more complex since other elements can also be added (such as static pages and menus, and snippets), and some of them, such as pages and static pages, seemingly provide the same functionality, making it confusing to decide when to use one or the other.

As a result, I dare say that while pretty much anyone can use a WordPress site from the admin side, October CMS is more developer-friendly than non-technical user-friendly, so programmers may find it a joy to use, but certain other roles (marketers, sales people, and the like) may find it non-intuitive.

Media Manager

Both WordPress and October CMS are shipped with a Media Manager which allows adding media files to the site effortlessly, supporting the addition of multiple files simultaneously through a drag-and-drop interface and displaying the images within the content area. They look and behave similarly; the only notable differences I found are that WordPress’ Media Manager allows to embed image galleries, and October’s Media Manager allows to manually create a folder structure where to place the uploaded files.


October CMS Media Manager
October CMS ships with a powerful Media Manager. (Large preview)

Since the introduction of Gutenberg, though, WordPress’ media capabilities have been enhanced greatly, enabling to embed videos, pictures and photo galleries in place as compared to within a TinyMCE textarea (which only provides a non-accurate version of how it will look like in the site), and unlocking powerful, yet easy-to-use features as shown in this video.

Internationalization

WordPress core uses gettext to enable the translation of themes and plugins. Starting from a .pot file containing all strings to translate, we need to create a .po file containing their translation to the corresponding language/locale, and this file is then compiled to a binary .mo file suitable for fast translation extraction. Tools to perform these tasks include GlotPress (online) and Poedit (downloadable application). Conveniently, this mechanism also works for client-side localization for Gutenberg.


Poedit
Poedit allows to translate strings for themes and plugins for WordPress. (Large preview)

WordPress currently doesn’t ship any solution in core to translate content, and will not do so until Phase 4 of Gutenberg (targeted for year 2020+). Until then, this functionality is provided by plugins which offer different strategies for storing and managing the translated content. For example, while plugins such as Polylang and WPML store each translation on its own row from a custom database table (which is clean since it doesn’t mix content together, but slower since it requires an additional INNER JOIN of two tables when querying the database), plugin qTranslate X stores all translations on the same field from the original database table (faster for querying the data, but content mixed all together can produce wreckage on the site if disabling the plugin). Hence, we can shop around and decide the most suitable strategy for our needs.

October CMS doesn’t ship the multilingual functionality through its core, but as a plugin created by the October CMS team that guarantees a faultless integration into the system. From a functional point of view, this plugin delivers what it promises. From a development point of view, it is not quite ideal how this plugin actually works. In WordPress, a page is simply a post with post type “page” and there is a single translation mechanism for them, but in October CMS, there are entities “page”, “static page” and “blog post” and, even though quite similar, they require three different implementations for their translations! Then, the content from a “page” can include message codes (e.g. codes called nav.content, header.title, and so on), each of which contains its translations for all locales as a serialized JSON object in database table rainlab_translate_messages. The content from a “static page” is created into a new static file per locale, however, all translated URLs for all locales are stored not in their corresponding file but instead on the default language’s file. The content for the “blog post” is stored as a serialized JSON object with one row per locale in database table rainlab_translate_attributes and the translated URL is stored with one row per locale in database table rainlab_translate_indexes. I don’t know if this complexity is due to how the plugin was implemented or whether it is due to October CMS’ architecture. Whichever the case, this is another instance of undesired bloat on the development side.

Plugin Management

Both WordPress and October CMS offer a sophisticated plugin manager which allows to search for plugins, install new plugins, and update currently-installed plugins to their latest version — all from within the backend.


October CMS software update
October CMS enables to keep all plugins up-to-date effortlessly. (Large preview)

Dependency Management

October CMS uses Composer as the package manager of choice, enabling plugins to download and install their dependencies when being installed, thus delivering a painless experience.

WordPress, on the opposite side, hasn’t officially adopted Composer (or any PHP dependency manager) because the community can’t agree if WordPress is a site or a site dependency. Hence, if they require Composer for their projects, developers must add it on their own. With the switch to Gutenberg, npm has become the preferred JavaScript dependency manager, with a popular developer toolkit depending on it, and the client-side libraries being steadily released as autonomous packages in the npm registry.

Interaction With The Database

WordPress provides functions to retrieve database data (such as get_posts) and store it (such as wp_insert_post and wp_update_post). When retrieving data, we can pass parameters to filter, limit and order the results, in order to indicate if the result must be passed as an instance of a class or as an array of properties and others. When the function doesn’t fully satisfy our requirements (e.g. when we need to do an INNER JOIN with a custom table) then we can query the database directly through global variable $wpdb. When creating a plugin with a custom post type, the code will most likely be executing custom SQL queries to retrieve and/or save data into custom tables. In summary, WordPress attempts to provide access to the database through generic functions in the first stage, and through low-level access to the database in the second stage.

October CMS employs a different approach: Instead of connecting to the database straight away, the application can use Laravel’s Eloquent ORM to access and manipulate database data through instances of classes called Models, making the interaction with the database also be based on Object-Oriented Programming. It is high-level access; just by following the rules on how to create tables and set-up relationships among entities, a plugin can retrieve and/or save data without writing a line of SQL. For instance, the code below retrieves an object from the database through model Flight, modifies a property, and stores it again:

$flight = Flight::find(1);
$flight->name = 'Darwin to Adelaide';
$flight->save();

Upgrading The Data Model

Another reason for WordPress’ success (in addition to not breaking backward compatibility) has been its database architecture, which was engineered to enable applications to grow over time. This objective is accomplished through “meta” properties, i.e. properties that can be loosely added to a database object at any moment. These properties are not stored in a column from the corresponding entity table (either wp_posts, wp_users, wp_comments or wp_terms), but instead as a row in the corresponding “meta” table (wp_postmeta, wp_usermeta, wp_commentmeta or wp_termmeta) and retrieved doing an INNER JOIN. Hence, even though retrieving these meta values is slower, they provide unlimited flexibility, and the application’s data model rarely needs to be re-architected from scratch in order to implement some new functionality.


WordPress database architecture
WordPress provides unlimited flexibility for upgrading the application’s data model. (Large preview)

October CMS doesn’t use meta properties but instead can store several arbitrary values, which are not directly mapped as columns in the database tables, as a serialized JSON object. Otherwise, when an object needs some new property, we need to add a new column on the corresponding table (which is the reason behind plugins User Plus and User Plus+, mentioned earlier on). To update the application’s database schema, October CMS relies on Laravel’s Migrations, which are sets of instructions to execute against the schema (such as add or drop a column, rename an index, etc) and which are executed when upgrading the software (e.g. when installing a plugin’s new version).

Headless Capabilities

Both WordPress and October CMS can be used as headless, i.e. treating the CMS as a content management system that makes content accessible through APIs, which allows to render the website on the client-side and can power other applications (such as mobile apps). Indeed, WordPress is steadily heading towards headless, since the Gutenberg content editor itself treats WordPress as a headless CMS (and, as a consequence, Gutenberg can also work with any other CMS too, as Drupal Gutenberg demonstrates).

A headless system needs to implement some API to return the data, such as REST and GraphQL. WordPress supports REST through WP REST API (merged in core), exposing endpoints under some predefined route /wp-json/wp/v2/...; October CMS supports REST through plugins RESTful and API Generator, which allow to create custom endpoints and, as a consequence, support versioning as part of the endpoint URL and can offer a better security against bots. Concerning GraphQL, WordPress supports it through WPGraphQL, while October CMS currently has no implementations for it.

Quite importantly, a headless system needs to offer powerful content management capabilities. As mentioned earlier on, WordPress has a very solid database architecture, offering a plethora of data entities (users, posts and custom posts, pages, categories, tags and custom taxonomies, comments) over which the application can be reasonably well modelled, meta properties to extend these data entities (enabling the application to upgrade its data model accordingly and without major changes), and with plugin Advanced Custom Fields filling the gap to construct relationships among the data entities. In addition, plugin VersionPress allows to version control the database content using Git. Hence, WordPress is undoubtedly a good fit for managing content, as demonstrated in several projects in the wild.

On its part, and as mentioned earlier on, October CMS can omit the database and behave as a flat-file system, or it can have a database and behave as a hybrid, storing the content from pages as static files and blog posts (and others) on the database. As a consequence, content is not centralized, and its management involves a different approach. For instance, while we can use Git to version control pages, there is no support to version control the database per se; the solution to this is to populate data into the database through Seeders which, being code, can be put under version control and executed upon deployment. In addition, October CMS doesn’t offer a baked-in database model featuring predefined data entities that can support the needs of most applications. Hence, more likely than not the application will need custom development to implement its data model, which means more work, but also means that it can be more efficient (e.g. accessing a property from a column is faster than from a row in another table through an INNER JOIN, which is the case with WordPress’ meta properties).

CLI Support

Both WordPress and October CMS can be interacted with from the console through a Command Line Interface (CLI): WordPress through WP-CLI and October CMS through Laravel’s Artisan. In addition to Laravel’s commands, October CMS implements several custom commands for updating the system, migrating the database, and others. These tools make it very convenient to access the site from outside a browser, for instance for testing purposes.

Managed Hosting

It is not a problem finding a managed hosting provider for a WordPress site: given WordPress’ market share, there are dozens (if not hundreds) of providers out there vying with each other for the business, constituting a very dynamic market. The only problem is finding the most suitable provider for our specific sites based on all of their offerings, which can vary based on price, quality, type (shared or dedicated services), bandwidth and storage size, customer support, location, frequency of renewal of equipment, and other variables which we can navigate mainly through reviews comparing them (such as this one, this one or this one).

Even though nothing near as many as WordPress, October CMS still enjoys the offering from several hosting providers, which allows for some consideration and selection. Many of them are listed as October Partners, and several others are found DuckDuckGoing, but since I haven’t found any independent review of them or article comparing them, the task of finding out the most suitable one will take some effort.

Marketplace, Ecosystem And Cost

WordPress’ commercial ecosystem is estimated to be USD $10 billion/year, evidencing how many people and companies have managed to make a living by offering WordPress products and services, such as the creation of sites, hosting, theme and plugin development, support, security, and others. Indeed, its size is so big it is even bloated, meaning that it is very common to find different plugins solving the same problem, plugins that underdeliver, underperform or have not been updated for years, and themes which seem to look-alike each other. However, when creating a new site, the size and variety of the ecosystem also means that we will most likely find at least one plugin implementing each of the required functionalities, enabling us to save money by not having to develop the functionality ourselves, and the availability of customizable themes enables to produce a reasonably distinctive-looking site with minimal effort. As a consequence, we can easily create and launch a WordPress site for less than USD $100, making WordPress a sensible option for projects of any budget.

Being relatively new (only five years so far), OctoberCMS certainly doesn’t enjoy anything near WordPress’ marketplace and ecosystem sizes, however, it has been growing steadily so its size is bound to become bigger. Currently, its marketplace boasts 600+ plugins, and only a handful of themes. Concerning plugins, the October CMS team is requesting the community to put their effort into the creation of original plugins, delivering functionality not yet provided by any other plugin.

Hence, even though 600+ plugins doesn’t sound like much, at least these translate into 600+ different functionalities. This way, even though it is not possible to choose among several vendors, at least we can expect to have those basic website features (such as blogging, comments, forum, integration with social media, e-commerce, and others) to be covered. Also, since October’s founders are personally reviewing all submitted plugins and judging them according to quality guidelines, we can expect these plugins to perform as expected. As another plus, October plugins can incorporate elements from Laravel packages (even though not all of them are compatible with October, at least not without some hacks). Concerning themes, the low number of offerings implies we will most likely need to develop our own theme by hiring a developer for the task. In fact, I dare say that the theme in October CMS will most likely be a custom development, since themes and plugins are not thoroughly decoupled (as explained earlier), with the consequence that a market for easily-swappable themes is more difficult to arise. (This is a temporary problem though: once this pull request is resolved, pages will be able to be stored in the database, and swapping themes should not disrupt functionality.)

In my opinion, because of the smaller offerings of themes and plugins, creating a simple site with OctoberCMS will be more expensive than creating a simple WordPress site. For complex sites, however, October’s better architecture (Object-Oriented Programming and Model-View-Controller paradigms) makes the software more maintainable and, as a consequence, potentially cheaper.

Community

Being a part of and having access, WordPress’ community represents one of the most compelling reasons for using WordPress. This is not simply as a matter of size (powering nearly one third of all websites in the world, there are so many stakeholders involved with WordPress, and its community is representatively big) but also as a matter of diversity. The WordPress community involves people from many different professions (developers, marketers, designers, bloggers, sales people, and so on), from all continents and countries, speaking countless languages, from different social, educational and economic backgrounds, with or without disabilities, from corporate, not-for-profit and governmental organizations, and others. Hence, it is quite likely that, for whatever problem we encounter, somebody will be able to help on any of the support forums. And contributing to WordPress is pretty straightforward too: The Make WordPress group congregates stakeholders interested in supporting different projects (accessibility, design, internationalization, and many others) and organizes how and how regularly they communicate — mostly through some dedicated channel on its Slack workspace.

Furthermore, the WordPress community is real and tangible: it doesn’t exist just online, but it gathers offline in WordCamps and meetups all over the world; in 2018, there were a total of 145 WordCamps in 48 countries with over 45,000 tickets sold, and a total of 5,400 meetup events from 687 meetup groups. Hence, it is likely that there is a local chapter nearby which anyone can join to ask for help, learn how to use the platform, keep learning on a regular basis, and teach others as well. In this sense, WordPress is not just a CMS but, more importantly, it’s also people, and considering to leave WordPress should never be done only on its technical merits but on the power of its community, too.


Attendees at WordCamp Kuala Lumpur 2017
WordCamp Kuala Lumpur 2017 drew more than 200 attendees, coming from several countries. (Large preview)

October CMS’ community is nothing near in size or diversity as WordPress’, even though it has been growing steadily following the increasing popularity of the software. October provides a support forum to ask for help, however, it is not very active. A Slack workspace exists which is pretty active and where, quite importantly, October’s founders participate regularly, helping make sure that all enquiries are properly addressed. This channel is a great source for learning low-level tips and tricks about the software, however, it is geared towards developers mainly: There are no channels concerning accessibility, design, internationalization, and other topics as in the WordPress community, at least not yet. Currently, there are no conferences concerning October CMS, but there is Laracon, the conference for the Laravel community.

Maintainers And Governance

Can we trust that the software will be maintained in the long term, so that if we decide to start a project today, we will not need to migrate to some other platform down the road? How many people are taking care of developing the software? And who is deciding in what direction the software moves towards?

Powering one-third of all sites in the world, WordPress is not short of stakeholders contributing to the software; hence we need not fear that the software will fall into decay. However, WordPress is going through internal deliberations concerning its governance model, with many members of the community expressing that decisions concerning WordPress’s direction are being taken unilaterally by Automattic, the company running WordPress.com. Center stage of this perception was the decision to launch Gutenberg, which many members disagreed with, and which suffered a lack of proper communication by the project leads during its development and release. As a consequence, many community members are questioning the role of “benign dictator”, which has been historically granted to WordPress’ founder and Automattic’s CEO Matt Mullenweg, and researching different governance models to find a more suitable one for the future of WordPress. It is yet to be seen if this quest produces any result, or if the status quo perseveres.

Decisions concerning October CMS’ direction are mainly taken by founders Alexey Bobkov and Samuel Georges and developer and community manager Luke Towers, which keep the project going strong. October CMS doesn’t have the luxury of having a governance problem yet: Its current concern is how to make the project sustainable by generating income for the core software’s maintainers.

Documentation

WordPress documentation in its own site is not extremely comprehensive, but it does the job reasonably well. However, when taking all of the documentation about WordPress into account from all sources, such as general sites (Smashing Magazine, CSS tricks, and many others), specialized sites (WPShout, WPBeginner, and many others), personal blogs, online courses, and so on, there is practically no aspect of dealing with WordPress that hasn’t already been covered.

October CMS doesn’t enjoy anything near the many third-party courses, tutorials or blog posts about it as much as WordPress does, however, the documentation on its site is reasonably comprehensive and certainly enough to start coding. October founders also regularly add new documentation through tutorials. One aspect that I personally enjoyed is the duplication of Laravel’s documentation into October’s documentation for everything of relevance, so the reader must not fill the gaps by him/herself and having to guess what is October’s domain and what is Laravel’s. However, this is not 100% perfect. October’s documentation uses terms originating from Laravel, such as middleware, service containers, facades and contracts, without adequately explaining what these are. Then, reading Laravel’s documentation in advance can be helpful (luckily, Laravel’s documentation is decidedly comprehensive, and Laravel’s screencasts, Laracasts, are another great source of learning, not just concerning Laravel but web development in general).

Conclusion

I set out to discover what features may be enticing for developers looking for alternatives to WordPress by comparing WordPress to a similar CMS, which I defined as being free and open source, based in PHP and producing dynamic content, and enjoying the support from some community. From the CMSs fulfilling these conditions, I chose October CMS for the comparison because of the knowledge I got about it, and because I appreciated its clean and modular coding approach as provided by Laravel, which could offer a fresh and modern perspective for building sites.

This article did not intend to pick a winner, but simply analyze when it makes sense to choose one or the other CMS, highlighting their strengths and weaknesses. There is no “best” CMS: only the most suitable CMS for a specific situation. Furthermore, anyone looking for a CMS to use on a particular project with a specific team and given a certain budget, should do some research and compare all the offerings out there to find out which one is most suitable for the particular context. It’s important not to limit to a few CMSs as I’ve done here in this article, but instead give a chance to all of them.

On a personal note, as a developer, what I found in October CMS is really appealing to me, mostly its ability to build modular applications as provided through Laravel. I would certainly consider this CMS for a new website. However, in the process of writing this article I also “rediscovered” WordPress. Being so popular, WordPress receives more than its fair share of criticisms, mostly concerning its old codebase and, since recently, the introduction of Gutenberg; however, WordPress also has certain excellent features (such as its super-scalable database model) which are seldom praised but should be taken into account too. And most importantly, WordPress should not be considered on its technical aspects alone: in particular, the size of its community and ecosystem places it a level or two above its alternatives. In a nutshell, some projects may benefit from sticking to WordPress, while others may better rely on October CMS or another platform.

As a final note, I would like to remark that exploring how another CMS works is a very rewarding activity on its own, independent of the decision reached concerning whether to use that particular CMS or not. In my case, I had been working for years on WordPress alone, and delving into October CMS was very refreshing since it taught me many things (such as the existence of PHP Standards Recommendations) which I had not been exposed to through WordPress. I may now decide to switch CMSs, or stick to WordPress knowing how to produce better code.

Further Reading on SmashingMag:

Smashing Editorial
(rb, ra, yk, il)

Source: Smashing Magazine, A Detailed Comparison Between WordPress And October CMS

Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

dreamt up by webguru in Uncategorized | Comments Off on Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

Anselm Hannemann



Do you sometimes feel like there’s so much to read and learn that your brain can’t take it anymore? It’s something most of us experience from time to time when we have too much to do and then overload our brains with even more. I’m aware that my reading lists aren’t helpful in that regard, as they contain even more things to learn. But it’s the very reason why I try to compile a diverse, open-minded set of articles that aren’t entirely frontend- or tech-related. And in weeks like this one where there aren’t too many articles for me to summarize, I realize how relieving this can be. Let’s give our brains the chance to wind down a bit when it tells us to and use the opportunity to reconsider how we do work.

Think about how you approach tasks, for example. Do you ask for more details when you’re given a specific task? Do you figure out how to do it yourself? Or are you just following the task’s details? Only doing the latter will get things done, of course. But it’ll also increase the risk that you forget about necessary details, as a study on storing passwords reveals now. If there’s nothing in the task description about hashing a password, for example, many people will not apply it, even if they know it’s the better solution. Or take the process of building a website: If we forget to add the correct caching, server costs will be unnecessarily high, and performance will suffer. It’s these little extra steps of thinking that make the difference between good, solid work and “just getting stuff done”.

News

  • Chrome 74 brings some new features to DevTools: It now highlights all elements that are affected by a CSS property, Lighthouse 4 is integrated into the Audits panel, and a WebSocket binary message viewer has been added, too.
  • Intersection Observer is still quite new and yet Chrome developers are currently introducing version 2 to tackle some common problems and implement learnings from the first version. Here’s what’s going to change in Intersection Observer v2.

General

  • It’s easy to forget about it, but even today we often build non-diverse solutions in many areas of life. This article shows how that happens with car crash test dummies that neglect women.
  • Voice is becoming more and more important in our lives, mainly because we use more devices without real display interfaces today — Homepod, Alexa, Siri, Google Assistant, or Amazon Echo. Mozilla teamed up with institutes from around the world to create an open-source pool of high-quality voices that helps teach machines how real people speak.
  • “In our modern world, it’s easy to junk things up. Simple is hard. We’re quick to add more questions to research surveys, more buttons to a digital interface, more burdens to people”. Kate Clayton explores how to be an elegant simplifier.
  • “People think that data is in the cloud, but it’s not. It’s in the ocean.” Let’s dive deep into how communication works and how it came to be that Microsoft, Google, Facebook, and Amazon own more than half of the undersea bandwidth. The article shows how the Internet depends on these big four companies nowadays and that we’d face massive struggles and performance impacts if we avoided them.
  • Jason Miller wrote an introduction to rendering on the web, summarizing what happens when a user accesses a website through a modern browser. There’s a lot to learn in here.

Map of the world showing undersea internet cables in 2021
Data is not in the cloud. It’s in the ocean. And more than half of the undersea bandwidth is owned by Amazon, Facebook, Google, and Microsoft. (Image credit)

UI/UX

  • Anand Satyan explains why it’s important to start designing without color first. It helps you understand the structure of data and layout better and often results in cleaner, more consistent designs.
  • Brad Frost wrote about the importance of providing forms that are simple, not clever, especially if you want users to log in.
  • Nikita Prokopov tried to analyze and redesign Github’s repository page. While I don’t like the final result too much, there are a lot of great takeaways from improving existing design patterns and the user experience with simple methods.

JavaScript

CSS

  • Constructable Stylesheets is a new way of initializing an external stylesheet or set of styles in a non-blocking way. This new approach allows us to dynamically construct stylesheets via JavaScript which is especially useful when we use it for Web Components in a ShadowDOM. The feature is available in Chrome Preview builds currently.
  • Rachel Andrew explains how we’re going to break boxes with the new CSS Fragmentation specification. With CSS Fragmentation, we can do things we used to do with float, but it’s more flexible and helps us control page breaks and other things relevant for print or eBooks.
  • This CSS-only experiment is mind-blowing. I’m seriously impressed and wouldn’t have imagined that we can do something like this with CSS today.

Security

Web Performance

Accessibility

  • Ben Robertson shares five tools we can use for automated accessibility audits. This is great because it allows us to use these tools in CIs, in regression testing (e.g., via Selenium or Chrome/Firefox headless browsers), or directly in our browsers.
  • Alex Carpenter summarized takeaways from WebAIM’s recent accessibility analysis of the top one million sites: 59% of form inputs are unlabeled and, thus, not accessible. Making them accessible for everyone wouldn’t be hard at all. It’s as easy as wrapping the input and describing it, for example like this: <label>Name<input name="name"></label> Of course, there are even better labeling practices out there, but this would already be enough to make a difference for all users of a website, not only those who rely on assistive technologies.
  • Accessibility Insights is a new platform service that provides developers with tools to analyze the accessibility of their web projects.

Cartoon cat and a laptop which is running the Accessibility Insights extension
The Accessibility Insights extension helps you spot accessibility errors and shows how to fix them. (Image credit)

Work & Life

  • How do we build trust as leaders? Claire Lew shares why business retreats and team building activities don’t matter much compared to the things that really make a difference: showing vulnerability, communicating the intent behind actions, and, finally, following through on commitments.
  • I found this article by Sahil Lavingia, the founder of Gumroad, very insightful. In it, he shares the failures, the struggles, and the bad decisions when getting Venture Capital, and why having a “normal” company is worth a thought, too, to prevent the whole thing from failing.
  • Our children are technology-focused and spend a lot of time in front of screens, playing games or watching videos. Pamela Paul advocates for letting our children get bored again.

Going Beyond…

Here’s one more thing: The periodic — yet not regular — reminder to give something back if you enjoy reading my writings and summary of articles.
— Anselm

Smashing Editorial
(cm)

Source: Smashing Magazine, Monthly Web Development Update 3/2019: React Hooks, Constructable Stylesheets, And Building Trust

Block Kit: Slack’s Contribution To Building A Better Collaboration UI

dreamt up by webguru in Uncategorized | Comments Off on Block Kit: Slack’s Contribution To Building A Better Collaboration UI

Block Kit: Slack’s Contribution To Building A Better Collaboration UI

Block Kit: Slack’s Contribution To Building A Better Collaboration UI

Suzanne Scacca



(This is a sponsored article.) Over the last few years, there’s been a major shift in terms of the way companies work. As more businesses become location independent, collaboration tools have become the standard way in which teams meet and get work done.

That said, just because we have collaboration apps that integrate our connected business processes and tools, that doesn’t mean the experience always leads to optimum efficiency or productivity. Why? Well, sometimes an unfriendly UI gets in the way.

That’s why, today, I’m going to talk about Block Kit, Slack’s contribution to building a better collaboration UI.

For those of you who have built a custom Slack app (for the app directory or for internal purposes), this will be your introduction to the new design tool. For those of you who haven’t, that’s okay. There are some valuable lessons to take away from this in terms of what makes for an engaging workspace that will improve collaboration.

Developers, Do You Know What Slack Has Been Working On?

Slack has made huge strides since its launch in 2013. What originally began as a messaging app has now blossomed into a powerful collaboration platform.

As of writing this: Slack has over 10 million active users daily — and they live all over the world (over 150 countries, to be exact).


An example of a Slack channel for Japanese speakers
Here’s an example of a Slack channel for Japanese speakers. (Image source: Slack) (Large preview)

It’s not just individuals using Slack either — nearly 585,000 teams of three persons or more collaborate within the platform. 65 of the Fortune 100 companies also happen to be on Slack.


A glimpse into real-time collaboration between Slack users
A glimpse into real-time collaboration between Slack users (Image source: Slack) (Large preview)

This is all thanks to the Slack API which has opened the door for developers to create and publish publicly available apps that extend the functionality of Slack workspaces.


The front page of the Slack App Directory
The front page of the Slack App Directory. (Image source: Slack) (Large preview)

This way, Slack users don’t have to bounce between their most commonly used business tools. Related processes may take place all from within Slack.

Sometimes, though, what’s available in the Slack App Directory just isn’t enough for what your organization needs internally. You may be able to bridge some of the divides between your business tools with what’s there, but you might also find a reason to build your own custom Slack apps.

Introducing Block Kit From Slack

Here’s the thing: while Slack has succeeded in allowing developers to craft their own apps to enhance collaboration within the platform, how are developers supposed to know how to create a good experience with it?

Until recently, Slack’s API and app directory provided limited flexibility and control. As Brian Elliott, the General Manager of the Platform, explained:

“Today, all apps are forced into a limited set of ways to display rich information. If you’ve looked at and seen and used all of the different apps in Slack, many of them end up with the same layout regardless of which functionality they’re trying to deploy. When in reality what you need is a set of components that let you build rich interactive displays that are easier for people to comprehend, digest and act on.”

So, Slack developed Block Kit.

Block Kit is a UI framework that enables developers, designers and front-end builders to display their messaging apps through a rich, interactive and intuitive UI. Further, by providing a set of stackable UI elements or blocks, Block Kit now provides developers with more control and flexibility over their app designs and layouts.

Note: If you’d like to see Block Kit in action, join the upcoming Slack session, “Building with Block Kit”, where you’ll get a live product demonstration and see how easy it is to customize the design of your app.

Block Kit comes with two key components:

1. Block Kit Builder

Notice the similarity between this builder tool and many of the other tools we use to create websites and apps for clients:


A demo of the Block Kit builder
A demo of the Block Kit builder (Image source: Block Kit) (Large preview)

The building components are on the left. Simply click on the one you want to include and watch it get added to the preview of your app in the center.

Want further customization? Check out the text editor on the right. While Block Kit provides pre-made elements that follow best practices for designing messaging apps, you have the ability to make them your own if you prefer.

2. Block Kit Templates

While you can certainly craft a messaging interface from the Builder on your own, I’d suggest exploring the Templates provided as well:


Some of the templates Block Kit offers to users
These are just some of the templates Block Kit offers to users. (Image source: Block Kit) (Large preview)

The Slack Team has already seen really useful cases of Slack apps in action. Needless to say, they know what kinds of things your organization might want to leverage for the sake of improved collaboration.

That’s why you’ll find common actions like the following already built out for you:

  • Reviewing requests for approval;
  • Taking action on new notifications;
  • Running polls and monitoring results;
  • Conducting a search.

Guru is one such tool that’s leveraged Block Kit to improve its Slack app:

Guru provides a database search function within Slack. Results are now quickly retrieved and more clearly displayed on the frontend of Slack.

The Keys To Building A Better Collaboration UI

Now that we’ve seen what’s coming down the line with Block Kit, we need to talk about how it’s going to help you create apps that lead to more productive collaboration.

Blocks

I recently spoke on the subject of Gutenberg and how designers can use it to their advantage. Although the new WordPress editor clearly has its flaws, there’s no questioning why the team at WordPress made the change:


Block builders are the future of web design.

I get that block builders tend to be the preferred tool for web designers and DIY users. Builders allow for visual frontend design and often include abundant customization options.


Some of the pre-made blocks included in Block Kit
Some of the pre-made blocks included in Block Kit (Image source: Block Kit) (Large preview)

But Block Kit does much more than that, which means both designers and developers can build custom apps with ease.

Code

The key differentiator between something like a website builder and the Block Kit builder is the coding aspect.

In most cases, designers use page builders so they don’t have to bother with code. They might add some custom CSS classes or add HTML to their text, but that’s generally it. Developers don’t work like that though.

Block Kit includes a panel with pre-written JSON that developers can copy-and-paste into their own Slack app once it’s ready to go. Rather than leave developers to write their own code, Slack provides code that utilizes best practices for speed and design.


A sample of JSON you get when you build your rich messaging experience in the builder
A sample of JSON you get when you build your rich messaging experience in the builder. (Image source: Block Kit) (Large preview)

This enables developers to focus on making customizations instead of on having to build their apps from the ground up.

Consistency

When Slack users step inside the platform, they know what to expect. Every interface is the same from workspace to workspace.

However, when an API allows developers to build apps to integrate with those spaces, there’s a risk of introducing elements that just don’t jibe well. When that happens, the unpredictability of the interface can create confusion and hesitation for the end user. Ill-fitting layout choices can also harm the experience.

Block Kit enables developers to build apps with stackable UI components that have been tried and tested. When customizing an experience within an already established platform, it can be difficult to know how far you can take it — or if it’ll even work. Slack has taken those questions out of the equation.

Spacing

This is what the traditional Slack exchange looks like:


An example of Slack users messaging one another
An example of Slack users messaging one another (Image source: Slack) (Large preview)

It tends to be a single-column, back-and-forth exchange. And this works perfectly well for Slack channels where collaboration is simple. Employees message about the status of a task. A client uploads a missing asset. The CEO shares a link to a press release mentioning the company. But not all workspaces are that simple.

Block Kit helps you maximize and enhance the space that your app’s features occupy. For instance, Block Kit enables companies like Optimizely to display pertinent information in two-column formats for better readability.


Optimizely uses Block Kit to create two-column formats
Optimizely uses Block Kit to create two-column formats. (Image source: Optimizely) (Large preview)

This is indeed a better way to share pertinent details in your team’s Slack app.

Rich Interactions

Another way to elevate your app is by turning the integration into one that’s rich with interactions.

Blocks have been specially developed to enhance the most commonly used elements in Slack collaboration. For example:

  • Use the Sectional block for better organization.
  • Use the Text block to customize how messages display.
  • Use properly sized Image blocks so you can stop worrying about whether or not they’ll display correctly.
  • Use Context blocks to show bylines or additional context about messages (like author, comments, changes, etc.)
  • Use Divider blocks to improve the look of the app.
  • Use Action blocks like menu selection, button selection and calendar dates to bring better features into your app and make them display more intuitively.
  • Use 2-section blocks for cleaner layouts.

Doodle has a beautiful example of what can be done with rich interactions using Block Kit:

As you can see, users can work together to schedule meetings just as effectively as if they were using a third-party calendar. The only difference is that they can now do all of that within their Slack workspace.

Wrapping Up

Collaboration is an essential part of any organization’s success, and it doesn’t matter if it’s a team of 3 or a team of 300. But there’s a big difference between working together and productively collaborating.

Thanks to Slack’s API, developers have created some awesome ways to integrate related processes and tools into the platform. And thanks to Block Kit, those external contributions won’t disrupt the experience if the design of the elements falls short.

With intuitive block-building capabilities, developer-friendly coding options and more, Block Kit is going to help developers bring richer experiences and better collaboration to the Slack platform.

One last thing to mention:

Slack’s Frontiers conference is coming up soon. It’s going to be in San Francisco on April 24 and 25. If you’re planning to attend, note that the Developers track will include a full day’s training on Block Kit, including workshops, new feature demos, tutorials, as well as one-on-one mentoring. If you’re thinking about Block Kit, this is an opportunity you won’t want to miss.

Smashing Editorial
(ms, ra, il)

Source: Smashing Magazine, Block Kit: Slack’s Contribution To Building A Better Collaboration UI

Collective #499

dreamt up by webguru in Uncategorized | Comments Off on Collective #499











C499_noise

Cube World

Tibix created this demo as part of the “Art of Noise” series, a collection dedicated to canvas animations using Perlin and Simplex noise.

Check it out




C499_send

Firefox Send

In case you didn’t know about it: Firefox Send lets you share files with end-to-end encryption and a link that automatically expires.

Check it out






C499_stuff

Stuff

A simple alternative to Facebook events. With distribution of invitations and collection of RSVPs.

Check it out





Collective #499 was written by Pedro Botelho and published on Codrops.


Source: Codrops, Collective #499

How To Build An Endless Runner Game In Virtual Reality (Part 2)

dreamt up by webguru in Uncategorized | Comments Off on How To Build An Endless Runner Game In Virtual Reality (Part 2)

How To Build An Endless Runner Game In Virtual Reality (Part 2)

How To Build An Endless Runner Game In Virtual Reality (Part 2)

Alvin Wan



In Part 1 of this series, we’ve seen how a virtual reality model with lighting and animation effects can be created. In this part, we will implement the game’s core logic and utilize more advanced A-Frame environment manipulations to build the “game” part of this application. By the end, you will have a functioning virtual reality game with a real challenge.

This tutorial involves a number of steps, including (but not limited to) collision detection and more A-Frame concepts such as mixins.

Prerequisites

Just like in the previous tutorial, you will need the following:

  • Internet access (specifically to glitch.com);
  • A Glitch project completed from part 1. (You can continue from the finished product by navigating to https://glitch.com/edit/#!/ergo-1 and clicking “Remix to edit”;
  • A virtual reality headset (optional, recommended). (I use Google Cardboard, which is offered at $15 a piece.)

Step 1: Designing The Obstacles

In this step, you design the trees that we will use as obstacles. Then, you will add a simple animation that moves the trees towards the player, like the following:

Template trees moving towards player
Template trees moving towards player (Large preview)

These trees will serve as templates for obstacles you generate during the game. For the final part of this step, we will then remove these “template trees”.

To start, add a number of different A-Frame mixins. Mixins are commonly-used sets of component properties. In our case, all of our trees will have the same color, height, width, depth etc. In other words, all your trees will look the same and therefore will use a few shared mixins.

Note: In our tutorial, your only assets will be mixins. Visit the A-Frame Mixins page to learn more.

In your editor, navigate to index.html. Right after your sky and before your lights, add a new A-Frame entity to hold your assets:

<a-sky...></a-sky>

<!-- Mixins -->
<a-assets>
</a-assets>

<!-- Lights -->
...

In your new a-assets entity, start by adding a mixin for your foliage. This mixins defines common properties for the foliage of the template tree. In short, it is a white, flat-shaded pyramid, for a low poly effect.

<a-assets>
  <a-mixin id="foliage" geometry="
      primitive: cone;
      segments-height: 1;
      segments-radial:4;
      radius-bottom:0.3;"
     material="color:white;flat-shading: true;"></a-mixin>
</a-assets>

Just below your foliage mixin, add a mixin for the trunk. This trunk will be a small, white rectangular prism.

<a-assets>
  ...
  <a-mixin id="trunk" geometry="
      primitive: box;
      height:0.5;
      width:0.1;
      depth:0.1;"
     material="color:white;"></a-mixin>
</a-assets>

Next, add the template tree objects that will use these mixins. Still in index.html, scroll down to the platforms section. Right before the player section, add a new tree section, with three empty tree entities:

<a-entity id="tree-container" ...>

  <!-- Trees -->
  <a-entity id="template-tree-center"></a-entity>
  <a-entity id="template-tree-left"></a-entity>
  <a-entity id="template-tree-right"></a-entity>

  <!-- Player -->
  ...

Next, reposition, rescale, and add shadows to the tree entities.

<!-- Trees -->
<a-entity id="template-tree-center" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>
<a-entity id="template-tree-left" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>
<a-entity id="template-tree-right" shadow scale="0.3 0.3 0.3" position="0 0.6 0"></a-entity>

Now, populate the tree entities with a trunk and foliage, using the mixins we defined previously.

<!-- Trees -->
<a-entity id="template-tree-center" ...>
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
</a-entity>
<a-entity id="template-tree-left" ...>
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
</a-entity>
<a-entity id="template-tree-right" ...>
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
</a-entity>

Navigate to your preview, and you should now see the following template trees.


Template trees for obstacles
Template trees for obstacles (Large preview)

Now, animate the trees from a distant location on the platform towards the user. As before, use the a-animation tag:

<!-- Trees -->
<a-entity id="template-tree-center" ...>
  ...
  <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation>
</a-entity>
<a-entity id="template-tree-left" ...>
  ...
  <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation>
</a-entity>
<a-entity id="template-tree-right" ...>
  ...
  <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation>
</a-entity>

Ensure that your code matches the following.

<a-entity id="tree-container"...>

<!-- Trees -->
<a-entity id="template-tree-center" shadow scale="0.3 0.3 0.3" position="0 0.6 0">
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
  <a-animation attribute="position" ease="linear" from="0 0.6 -7" to="0 0.6 1.5" dur="5000"></a-animation>
</a-entity>

<a-entity id="template-tree-left" shadow scale="0.3 0.3 0.3" position="-0.5 0.55 0">
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
  <a-animation attribute="position" ease="linear" from="-0.5 0.55 -7" to="-0.5 0.55 1.5" dur="5000"></a-animation>
</a-entity>

<a-entity id="template-tree-right" shadow scale="0.3 0.3 0.3" position="0.5 0.55 0">
  <a-entity mixin="foliage"></a-entity>
  <a-entity mixin="trunk" position="0 -0.5 0"></a-entity>
  <a-animation attribute="position" ease="linear" from="0.5 0.55 -7" to="0.5 0.55 1.5" dur="5000"></a-animation>
</a-entity>

<!-- Player -->
...

Navigate to your preview, and you will now see the trees moving towards you.

Template trees moving towards player
Template trees moving towards playerTemplate trees moving towards player (Large preview)

Navigate back to your editor. This time, select assets/ergo.js. In the game section, setup trees after the window has loaded.

/********
 * GAME *
 ********/

...

window.onload = function() {
  setupTrees();
}

Underneath the controls but before the Game section, add a new TREES section. In this section, define a new setupTrees function.

/************
 * CONTROLS *
 ************/

...

/*********
 * TREES *
 *********/

function setupTrees() {
}

/********
 * GAME *
 ********/

...

In the new setupTrees function, obtain references to the template tree DOM objects, and make the references available globally.

/*********
 * TREES *
 *********/

var templateTreeLeft;
var templateTreeCenter;
var templateTreeRight;

function setupTrees() {
  templateTreeLeft    = document.getElementById('template-tree-left');
  templateTreeCenter  = document.getElementById('template-tree-center');
  templateTreeRight   = document.getElementById('template-tree-right');
}

Next, define a new removeTree utility. With this utility, you can then remove the template trees from the scene. Underneath the setupTrees function, define your new utility.

function setupTrees() {
    ...
}

function removeTree(tree) {
  tree.parentNode.removeChild(tree);
}

Back in setupTrees, use the new utility to remove the template trees.

function setupTrees() {
  ...

  removeTree(templateTreeLeft);
  removeTree(templateTreeRight);
  removeTree(templateTreeCenter);
}

Ensure that your tree and game sections match the following:

/*********
 * TREES *
 *********/

var templateTreeLeft;
var templateTreeCenter;
var templateTreeRight;

function setupTrees() {
  templateTreeLeft    = document.getElementById('template-tree-left');
  templateTreeCenter  = document.getElementById('template-tree-center');
  templateTreeRight   = document.getElementById('template-tree-right');

  removeTree(templateTreeLeft);
  removeTree(templateTreeRight);
  removeTree(templateTreeCenter);
}

function removeTree(tree) {
  tree.parentNode.removeChild(tree);
}

/********
 * GAME *
 ********/

setupControls();  // TODO: AFRAME.registerComponent has to occur before window.onload?

window.onload = function() {
  setupTrees();
}

Re-open your preview, and your trees should now be absent. The preview should match our game at the start of this tutorial.

Part 1 finished product
Part 1 finished product (Large preview)

This concludes the template tree design.

In this step, we covered and used A-Frame mixins, which allow us to simplify code by defining common properties. Furthermore, we leveraged A-Frame integration with the DOM to remove objects from the A-Frame VR scene.

In the next step, we will spawn multiple obstacles and design a simple algorithm to distribute trees among different lanes.

Step 2 : Spawning Obstacles

In an endless runner game, our goal is to avoid obstacles flying towards us. In this particular implementation of the game, we use three lanes as is most common.

Unlike most endless runner games, this game will only support movement left and right. This imposes a constraint on our algorithm for spawning obstacles: we can’t have three obstacles in all three lanes, at the same time, flying towards us. If that occurs, the player would have zero chance of survival. As a result, our spawning algorithm needs to accommodate this constraint.

In this step, all of our code edits will be made in assets/ergo.js. The HTML file will remain the same. Navigate to the TREES section of assets/ergo.js.

To start, we will add utilities to spawn trees. Every tree will need a unique ID, which we will naively define to be the number of trees that exist when the tree is spawned. Start by tracking the number of trees in a global variable.

/*********
 * TREES *
 *********/

...
var numberOfTrees = 0;

function setupTrees() {
...

Next, we will initialize a reference to the tree container DOM element, which our spawn function will add trees to. Still in the TREES section, add a global variable and then make the reference.

...
var treeContainer;
var numberOfTrees ...

function setupTrees() {
    ...
    templateTreeRight   = ...
    treeContainer       = document.getElementById('tree-container');
    
    removeTree(...);
    ...
}

Using both the number of trees and the tree container, write a new function that spawns trees.

function removeTree(tree) {
    ...
}

function addTree(el) {
  numberOfTrees += 1;
  el.id = 'tree-' + numberOfTrees;
  treeContainer.appendChild(el);
}

...

For ease-of-use later on, you will create a second function that adds the correct tree to the correct lane. To start, define a new templates array in the TREES section.

var templates;
var treeContainer;
...

function setupTrees() {
    ...
    templates           = [templateTreeLeft, templateTreeCenter, templateTreeRight];

    removeTree(...);
    ...
}

Using this templates array, add a utility that spawns trees in a specific lane, given an ID representing left, middle, or right.

function function addTree(el) {
    ...
}

function addTreeTo(position_index) {
  var template = templates[position_index];
  addTree(template.cloneNode(true));
}

Navigate to your preview, and open your developer console. In your developer console, invoke the global addTreeTo function.

> addTreeTo(0);  # spawns tree in left lane
Invoking addTreeTo manually
Invoke addTreeTo manually (Large preview)

Now, you will write an algorithm that spawns trees randomly:

  1. Pick a lane randomly (that hasn’t been picked yet, for this timestep);
  2. Spawn a tree with some probability;
  3. If the maximum number of trees has been spawned for this timestep, stop. Otherwise, repeat step 1.

To effect this algorithm, we will instead shuffle the list of templates and process one at a time. Start by defining a new function, addTreesRandomly that accepts a number of different keyword arguments.

function addTreeTo(position_index) {
    ...
}

/**
 * Add any number of trees across different lanes, randomly.
 **/
function addTreesRandomly(
  {
    probTreeLeft = 0.5,
    probTreeCenter = 0.5,
    probTreeRight = 0.5,
    maxNumberTrees = 2
  } = {}) {
}

In your new addTreesRandomly function, define a list of template trees, and shuffle the list.

function addTreesRandomly( ... ) {
  var trees = [
    {probability: probTreeLeft,   position_index: 0},
    {probability: probTreeCenter, position_index: 1},
    {probability: probTreeRight,  position_index: 2},
  ]
  shuffle(trees);
}

Scroll down to the bottom of the file, and create a new utilities section, along with a new shuffle utility. This utility will shuffle an array in place.

/********
 * GAME *
 ********/

...

/*************
 * UTILITIES *
 *************/

/**
 * Shuffles array in place.
 * @param {Array} a items An array containing the items.
 */
function shuffle(a) {
   var j, x, i;
   for (i = a.length - 1; i > 0; i--) {
       j = Math.floor(Math.random() * (i + 1));
       x = a[i];
       a[i] = a[j];
       a[j] = x;
   }
   return a;
}

Navigate back to the addTreesRandomly function in your Trees section. Add a new variable numberOfTreesAdded and iterate through the list of trees defined above.

function addTreesRandomly( ... ) {
  ...
  var numberOfTreesAdded = 0;
  trees.forEach(function (tree) {
  });
}

In the iteration over trees, spawn a tree only with some probability and only if the number of trees added does not exceed 2. Update the for loop as follows.

function addTreesRandomly( ... ) {
  ...
  trees.forEach(function (tree) {
    if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) {
      addTreeTo(tree.position_index);
      numberOfTreesAdded += 1;
    }
  });
}

To conclude the function, return the number of trees added.

function addTreesRandomly( ... ) {
  ...
  return numberOfTreesAdded;
}

Double check that your addTreesRandomly function matches the following.

/**
 * Add any number of trees across different lanes, randomly.
 **/
function addTreesRandomly(
  {
    probTreeLeft = 0.5,
    probTreeCenter = 0.5,
    probTreeRight = 0.5,
    maxNumberTrees = 2
  } = {}) {

  var trees = [
    {probability: probTreeLeft,   position_index: 0},
    {probability: probTreeCenter, position_index: 1},
    {probability: probTreeRight,  position_index: 2},
  ]
  shuffle(trees);

  var numberOfTreesAdded = 0;
  trees.forEach(function (tree) {
    if (Math.random() < tree.probability && numberOfTreesAdded < maxNumberTrees) {
      addTreeTo(tree.position_index);
      numberOfTreesAdded += 1;
    }
  });

  return numberOfTreesAdded;
}

Finally, to spawn trees automatically, setup a timer that runs triggers tree-spawning at regular intervals. Define the timer globally, and add a new teardown function for this timer.

/*********
 * TREES *
 *********/
...
var treeTimer;

function setupTrees() {
...
}

function teardownTrees() {
  clearInterval(treeTimer);
}

Next, define a new function that initializes the timer and saves the timer in the previously-defined global variable. The below timer is run every half a second.

function addTreesRandomlyLoop({intervalLength = 500} = {}) {
  treeTimer = setInterval(addTreesRandomly, intervalLength);
}

Finally, start the timer after the window has loaded, from the Game section.

/********
 * GAME *
 ********/
...
window.onload = function() {
  ...
  addTreesRandomlyLoop();
}

Navigate to your preview, and you’ll see trees spawning at random. Note that there are never three trees at once.

Tree spawning at random
Tree randomly spawning (Large preview)

This concludes the obstacles step. We’ve successfully taken a number of template trees and generated an infinite number of obstacles from the templates. Our spawning algorithm also respects natural constraints in the game to make it playable.

In the next step, let’s add collision testing.

Step 3: Collision Testing

In this section, we’ll implement the collision tests between the obstacles and the player. These collision tests are simpler than collision tests in most other games; however, the player only moves along the x-axis, so whenever a tree crosses the x-axis, check if the tree’s lane is the same as the player’s lane. We will implement this simple check for this game.

Navigate to index.html, down to the TREES section. Here, we will add lane information to each of the trees. For each of the trees, add data-tree-position-index=, as follows. Additionally add class="tree", so that we can easily select all trees down the line:

<a-entity data-tree-position-index="1" class="tree" id="template-tree-center" ...>
</a-entity>
<a-entity data-tree-position-index="0" class="tree" id="template-tree-left" ...>
</a-entity>
<a-entity data-tree-position-index="2" class="tree" id="template-tree-right" ...>
</a-entity>

Navigate to assets/ergo.js and invoke a new setupCollisions function in the GAME section. Additionally, define a new isGameRunning global variable that denotes whether or not an existing game is already running.

/********
 * GAME *
 ********/

var isGameRunning = false;

setupControls();
setupCollision();

window.onload = function() {
...

Define a new COLLISIONS section right after the TREES section but before the Game section. In this section, define the setupCollisions function.

/*********
 * TREES *
 *********/

...

/**************
 * COLLISIONS *
 **************/

const POSITION_Z_OUT_OF_SIGHT = 1;
const POSITION_Z_LINE_START = 0.6;
const POSITION_Z_LINE_END = 0.7;

function setupCollision() {
}

/********
 * GAME *
 ********/

As before, we will register an AFRAME component and use the tick event listener to run code at every timestep. In this case, we will register a component with player and run checks against all trees in that listener:

function setupCollisions() {
  AFRAME.registerComponent('player', {
    tick: function() {
      document.querySelectorAll('.tree').forEach(function(tree) {
      }
    }
  }
}

In the for loop, start by obtaining the tree’s relevant information:

 document.querySelectorAll('.tree').forEach(function(tree) {
  position = tree.getAttribute('position');
  tree_position_index = tree.getAttribute('data-tree-position-index');
  tree_id = tree.getAttribute('id');
}

Next, still within the for loop, remove the tree if it is out of sight, right after extracting the tree’s properties:

 document.querySelectorAll('.tree').forEach(function(tree) {
  ...
  if (position.z > POSITION_Z_OUT_OF_SIGHT) {
    removeTree(tree);
  }
}

Next, if there is no game running, do not check if there is a collision.

 document.querySelectorAll('.tree').forEach(function(tree) {
  if (!isGameRunning) return;
}

Finally (still in the for loop), check if the tree shares the same position at the same time with the player. If so, call a yet-to-be-defined gameOver function:

document.querySelectorAll('.tree').forEach(function(tree) {
  ...
  if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END
      && tree_position_index == player_position_index) {
    gameOver();
  }
}

Check that your setupCollisions function matches the following:

function setupCollisions() {
  AFRAME.registerComponent('player', {
    tick: function() {
      document.querySelectorAll('.tree').forEach(function(tree) {
        position = tree.getAttribute('position');
        tree_position_index = tree.getAttribute('data-tree-position-index');
        tree_id = tree.getAttribute('id');

        if (position.z > POSITION_Z_OUT_OF_SIGHT) {
          removeTree(tree);
        }

        if (!isGameRunning) return;

        if (POSITION_Z_LINE_START < position.z && position.z < POSITION_Z_LINE_END
            && tree_position_index == player_position_index) {
          gameOver();
        }
      })
    }
  })
}

This concludes the collision setup. Now, we will add a few niceties to abstract away the startGame and gameOver sequences. Navigate to the GAME section. Update the window.onload block to match the following, replacing addTreesRandomlyLoop with a yet-to-be-defined startGame function.

window.onload = function() {
  setupTrees();
  startGame();
}

Beneath the setup function invocations, create a new startGame function. This function will initialize the isGameRunning variable accordingly, and prevent redundant calls.

window.onload = function() {
    ...
}

function startGame() {
  if (isGameRunning) return;
  isGameRunning = true;

  addTreesRandomlyLoop();
}

Finally, define gameOver, which will alert a “Game Over!” message for now.

function startGame() {
    ...
}

function gameOver() {
  isGameRunning = false;

  alert('Game Over!');
  teardownTrees();
}

This concludes the collision testing section of the endless runner game.

In this step, we again used A-Frame components and a number of other utilities that we added previously. We additionally re-organized and properly abstracted the game functions; we will subsequently augment these game functions to achieve a more complete game experience.

Conclusion

In part 1, we added VR-headset-friendly controls: Look left to move left, and right to move right. In this second part of the series, I’ve shown you how easy it can be to build a basic, functioning virtual reality game. We added game logic, so that the endless runner matches your expectations: run forever and have an endless series of dangerous obstacles fly at the player. Thus far, you have built a functioning game with keyboard-less support for virtual reality headsets.

Here are additional resources for different VR controls and headsets:

In the next part, we will add a few finishing touches and synchronize game states, which move us one step closer to multiplayer games.

Stay tuned for Part 3!

Smashing Editorial
(rb, ra, yk, il)

Source: Smashing Magazine, How To Build An Endless Runner Game In Virtual Reality (Part 2)

Exploring The Latest Web Design Trends Together With Be Theme

dreamt up by webguru in Uncategorized | Comments Off on Exploring The Latest Web Design Trends Together With Be Theme

Exploring The Latest Web Design Trends Together With Be Theme

Exploring The Latest Web Design Trends Together With Be Theme

Nick Babich



(This is a sponsored article.) Designers have a strange relationship with trends. On the one hand, when designers follow a crowd, they might feel that they aren’t able to express enough creativity. On the other hand, trends can tell designers a lot about user preferences — what people love, what they hate — and ultimately help designers to create products with better adoption rates.

People are visual creatures, and visual design has a significant impact on the way we understand products. In this article, I want to focus on the most crucial web design trends and illustrate each trend using Be Theme, a responsive multipurpose WordPress theme.

Let’s get started.

1. Digital Illustrations

Digital illustrations have become one of the most important trends in visual design. Relevant illustrations can make your design stand out from a crowd and establish a truly emotional connection with visitors. Illustrations are quite a versatile tool; product designers can use digital illustrations for various purposes: for hero sections, for feature descriptions, or even as a subtle icon in the navigation bar.

Two types of illustrations are popular among digital designers: hand-drawn flat illustrations and three-dimensional ones. Flat hand-drawn ones give an impression of fine craftsmanship, of a hand-made design; it’s relatively easy to see the personal style of the illustrator through their work. Slack, Intercom and Dropbox are just a few companies that use flat hand-drawn illustrations.


Hand-drawn illustrations look and feel personal for users
Hand-drawn illustrations look and feel personal for users. (Image source: themes.muffingroup) (Large preview)

Three-dimensional illustrations are quite a new trend. Designers started using them to add more realism, blurring the boundary between the digital and physical worlds.


3D illustrations give users the impression that they can almost reach out and touch objects in the scene
3D illustrations give users the impression that they can almost reach out and touch objects in the scene. (Image source: themes.muffingroup) (Large preview)

2. Vibrant Colors

There is a reason why so many digital product designers strive to use vibrant colors: Vibrant colors give visual interest to a layout. User attention is a precious resource, and one of the most effective ways to grab attention is by using colors that stand out. Bright colors used for the background can capture the visitor’s attention and contribute to a truly memorable experience.


Vivid colors are an excellent way to grab the visitor’s attention
Vivid colors are an excellent way to grab the visitor’s attention. (Image source: themes.muffingroup) (Large preview)

3. Hero Video Headers

“Show, don’t tell” is a foundational principle of good product design. Imagery plays a key role in visual design because it helps the designers to deliver the main idea quickly.

For a long time, web designers have had to use static imagery to convey their main idea. But the situation has changed. High-speed connections make it much easier for web designers to turn their home pages into immersive movie-style experiences. Video engages users, and users are more willing to spend time watching clips. Video clips used in a hero section can vary from a few seconds of looped video to full-length preview clips with audio.

Designers use video to tell stories. (Image source: themes.muffingroup) (Large preview)

4. Split Screen

Split screen is a relatively simple design technique. All you need to do to create one is divide the screen into two parts (usually 50/50) and use each part to deliver a distinct message. This technique translates well on mobile; two horizontal panels of content can be collapsed into vertical content blocks on small screens. The technique works well when you need to deliver two separate messages, as shown below.


Split screen is an excellent choice for e-commerce websites that offer products for both women and men
Split screen is an excellent choice for e-commerce websites that offer products for both women and men. (Image source: themes.muffingroup) (Large preview)

It also works well when you have to pair a text message with relevant imagery:


Split screen can be used to connect a text message with relevant imagery
Split screen can be used to connect a text message with relevant imagery. (Image source: themes.muffingroup) (Large preview)

5. Geometric Patterns

Designers can use geometric shapes and patterns endlessly to create beautiful ornaments. This technique works equally well for digital products. Designers can use SVG images and high-resolution PNGs with geometric patterns as backgrounds. Such backgrounds scale well, so you won’t have to worry about how they will look on small and large displays.


With geometric patterns, you can let your creativity run wild
With geometric patterns, you can let your creativity run wild. (Image source: themes.muffingroup) (Large preview)

6. Gradients and Duotones

Gradients are the multipurpose tool that works in pretty much any type of design. Designers often use gradients to give their work a little more depth. Modern graphic design trends dictate the use of big, bold and colorful gradients, which help designers make a statement.

When it comes to gradients, designers have a lot of creative freedom. They can experiment with various colors and types, using radial gradient, linear gradients, etc. For example, this is what happens when you put a linear one-color gradient overlay on a photo:


One-color gradient overlay on a photo
One-color gradient overlay on a photo (Image source: themes.muffingroup) (Large preview)

And this is how a radial two-color gradient looks on a plain background:


Two-color gradient over a plain background
Two-color gradient over a plain background. (Image source: themes.muffingroup) (Large preview)

The duotone effect was made popular by Spotify, the online music-streaming service. The service was searching for a bold identity for its brand and decided to use duotones in its design.

In the simplest terms, duotones are filters that replace the whites and blacks in a photo with two colors. Duotones can make almost any image match your company’s branding; simply use your brand’s primary color as the duotone filter.


A duotone in the hero image
A duotone in the hero image (Image source: themes.muffingroup) (Large preview)

7. Bold Typography

Most designers know that content should always come first in the design process. A design should honor the message that the product’s creators want to deliver to their users. Bold typography helps designers to achieve that. Massive, screen-dominating text puts the written content center stage.

Bold fonts serve a functional purpose — they make it easy to read the text. Consider the following example. This template is an excellent example of how powerful a bold font can be:


Designers can use bold typography to make text the focal point in a graphic
Designers can use bold typography to make text the focal point in a graphic. (Image source: themes.muffingroup) (Large preview)

Conclusion

“Should I follow the trends?” As a designer, you have to answer that for yourself. But if you want to see how each trend works for your project, you can do it right now. All of the Be Theme examples listed above can serve as excellent starting points for your creative journey.

Smashing Editorial
(ms, ra, yk, il)

Source: Smashing Magazine, Exploring The Latest Web Design Trends Together With Be Theme

Creating A Spotify-Powered App Using Nuxt.js

dreamt up by webguru in Uncategorized | Comments Off on Creating A Spotify-Powered App Using Nuxt.js

Creating A Spotify-Powered App Using Nuxt.js

Creating A Spotify-Powered App Using Nuxt.js

Cher Scarlett



We’ve all heard of Spotify. Launched back in 2008, the app offers millions of tracks from various legendary and upcoming artists. It allows you to create a playlist, follow other people or choose a playlist based on your mood.

But let’s take the app from another perspective today. Let’s build a two-page server-side rendered web application featuring a “Now Playing on Spotify” component. I’ll walk you through all of the steps of building a client-side application, building and connecting to a server API, as well as connecting to external API services.

Our project will be built using the Node.js and npm ecosystems, Github to store our code, Heroku as our host, Heroku’s Redis for our storage, and Spotify’s web API. The application and internal API will be build entirely using Nuxt’s system. Nuxt is a server-side-rendering framework that runs on Vuejs, Expressjs, Webpack, and Babeljs.

This tutorial is moderately complex, but is broken down into very consumable sections. You’ll find a working demo at cherislistening.heroku.com.


An image depicting the final product of the tutorial. Contains the information for the song “Break up with your girlfriend, I’m bored” by Ariana Grande with a link to the song on Spotify, and a photo of the album cover
Final result created with Nuxt.js, Redis, and Spotify. (Large preview)

Requirements

This tutorial requires knowledge of HTML, CSS, Javascript (ES6), and how to use command line or terminal. We’ll be working with Node.js and Vuejs; a basic understanding of both will be helpful before starting this tutorial. You’ll also need to have Xcode Tools installed if you are on MacOS.

If you prefer to reverse-engineer, you can fork the repository.

Table Of Contents

  1. Planning Our Application
    We’ll lay out our expected functionality and a visual representation of what we plan to see when we are finished.
  2. Setting Up And Creating Our Project
    We’ll walk through how to setup an application hosted on Heroku’s server, setup auto-deployment from Github, setup Nuxt using the command line tools, and get our local server running.
  3. Building Our API Layer
    We’ll learn how to add an API layer to our Nuxt application, how to connect to Redis, and Spotify’s web API.
  4. Client-Side Storage And State Management
    We’ll look at how we can leverage the built-in Vuex store to keep what’s playing up to date. We’ll set up our initial data connections our API.
  5. Building The Pages And Components
    We’ll take a brief look into how pages and components differ in Nuxt, and build two pages and a couple of components. We’ll use our data to build our Now Playing app and some animations.
  6. Publishing Our Application
    We’ll get our app onto GitHub and built on Heroku’s server, authenticate and share with everyone what music we’re listening to.

Planning Our Application

The most important step before we start any new project is to plan our goals. This will help us establish a set of requirements for accomplishing our goals.

  • How many pages are there?
  • What do we want on our pages?
  • Do we want our Spotify “Now Playing” component present on both of our pages?
  • Do we want a progress bar to show listeners where we are in the song?
  • How do we want our pages laid out?

These are the types of questions that will help us draft our requirements.

Let’s build out two pages for our application. First, we want a landing page with our “Now Playing” component. Our second page will be our authentication area where we connect our data to Spotify. Our design is going to be very minimalistic, to keep things simple.

For our “Now Playing” component, let’s plan on showing the progress of the track as a bar, the name of the track, the artist’s name, and the album art. We’ll also want to show an alternate state showing the most recent track played, in case we aren’t currently listening to anything.

Since we are dealing with Spotify’s API, we’ll have special tokens for accessing the data from our site. For security purposes, we don’t want to expose these tokens on the browser. We also only want our data, so we’ll want to ensure that we are the only user who can login to Spotify.

The first issue we find in planning is that we have to login to Spotify. This is where our Redis cache storage comes in. Spotify’s API will allow to permanently connect your Spotify account to an application with another special token. Redis is a highly performant in-memory data structure server. Since we are dealing with a token, a simple key:value storage system works well. We want it to be fast so we can retrieve it while our application is still loading.

Heroku has its own Redis cache service built in, so by using Heroku for our server, host, and storage, we can manage everything in one place. With the added benefit of auto-deployment, we can do everything from our console with commands in terminal. Heroku will detect our application language from our push, and will build and deploy it without much configuration.

Setting Up And Creating Our Project

Install Nodejs

Grab the right package for your OS here: https://nodejs.org/en/download/

$ node --version
 v10.0.1

Install git

Follow the instructions for your OS here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git

$ git --version
 git version 2.14.3 (Apple Git-98)

Sign Up For GitHub

Follow the instructions here: https://github.com/join and https://help.github.com/articles/set-up-git/.

Create a repository: https://help.github.com/articles/create-a-repo/

Clone the repository: https://help.github.com/articles/cloning-a-repository/

I named mine “cherislistening”. Here’s what my clone looks like:

$ git clone https://github.com/cherscarlett/cherislistening.git
Cloning into `cherislistening`...
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (4/4), done.
remove: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.

$ cd cherislistening/

Install And Setup Heroku

Sign up for Heroku here: https://signup.heroku.com/

Download and install the Command Line Interface (CLI): https://devcenter.heroku.com/articles/heroku-cli#download-and-install

We’ll need to login and create our app, along with setting up some configuration variables. I named my app “cherislistening”. You can also leave off the -a command and Heroku will give you a randomly generated name. You can always change it later. The url of your app will be http://<APPLICATION_NAME>.herokuapp.com.

Nuxt requires some specific configuration to build and run properly, so we’ll add those now to get them out of the way.

$ heroku –version
 heroku/7.19.4 darwin-x64 node-v11.3.0

​$ heroku login heroku: Press any key to open up the browser to login or q to exit: Logging in… done Logged in as cher.scarlett@gmail.com

$ heroku create -a cherislistening

​$ heroku config:set CLIENT_URL=http://cherislistening.herokuapp.com API_URL=/ HOST=0.0.0.0 NODE_ENV=production NPM_CONFIG_PRODUCTION=false Setting CLIENT_URL, API_URL, HOST, NODE_ENV, NPM_CONFIG_PRODUCTION and restarting ⬢ cherislistening… done, v1 API_URL: / CLIENT_URL: http://cherislistening.herokuapp.com HOST: 0.0.0.0 NODE_ENV: production NPM_CONFIG_PRODUCTION: false

Go to the Heroku dashboard and click into your newly created app. In the ‘Deploy’ tab, connect to your Github account, select the repository you cloned, and enable auto deploys from the Master branch.


Github selected on a screenshot of the dashboard area of Heroku
Github selected in the Heroku dashboard (Large preview)

Repository selection in Github on a screenshot of the dashboard area of Heroku
Repository selection in Github (Large preview)

Automatic deployment setup with Github on a screenshot of the dashboard area of Heroku
Automatic deployment setup with Github (Large preview)

Create Nuxt App

We’ll use npx to create our Nuxt application. Npm is a great ecosystem for managing Node.js packages, but to run a package, we must install it and add it to our package.json file. That isn’t very useful if we want to execute a single package one time, and installing something isn’t really necessary. This makes npx suitable for executing packages that compose file trees, adding boilerplates, and install the packages you need during execution.

$ npx --version
 6.4.1

npx is shipped by default in npm 5.2.0+, so it is highly recommended we upgrade npm instead of globally installing npx. If you just installed a fresh version of node.js, you should have current npm and npx.

The Nuxt.js team has created a scaffolding tool which will give your application the basic structure required to run. Make sure you’re in your new project’s folder before running the command.

$ npx create-nuxt-app
 npx: installed 407 in 5.865s
 > Generating Nuxt.js project in /Users/cstewart/Projects/personal/tutorials/cherislistening
 ? Project name cherislistening
 ? Project description A Spotify Now Playing App
 ? Use a custom server framework none
 ? Choose features to install Prettier, Axios
 ? Use a custom UI framework none
 ? Use a custom test framework none
 ? Choose rendering mode Universal
 ? Author name Cher Scarlett
 ? Choose a package manager npm

npm notice created a lockfile as package-lock.json. You should commit this file.

To get started:

npm run dev

To build and start for production:

npm run build
npm start

npm notice created a lockfile as package-lock.json. You should commit this file.

To get started:

npm run dev

To build & start for production:

npm run build
npm start

Every folder within the scaffolding comes with a README file. This file will give you the basics for how the folder works, and whether or not it’s needed. We will talk about the folders we’ll use as we get to them in the tutorial.

.nuxt/
assets/
|___README.md
components/
|___Logo.vue
|___README.md
layouts/
|___default.vue
|___README.md
middleware/
|___README.md
node_modules/
pages/
|___index.vue
|___README.md
plugins/
|___README.md
static/
|___favicon.co
|___README.md
store/
|___README.md
.gitignore
.prettierrc
LICENSE
nuxt.config.js
package-lock.json
package.json
README.md

We’ll need to make a change to package.json so that when we deploy to Heroku, our build process will run. In “scripts”, we’ll add "heroku-postbuild": "npm run build". Don’t forget to add a comma after the previous line in the object.

"scripts": {
     "dev": "nuxt",
     "build": "nuxt build",
     "start": "nuxt start",
     "generate": "nuxt generate",
     "heroku-postbuild": "npm run build"
   },

package.json

If you run npm run dev, and go to http://localhost:3000 in your browser, you should see the scaffolded app running:


A screenshot of the initial Nuxt application. The initial application contains the title of the application, Nuxt’s animated logo, and links to Nuxt’s documentation
Initial state of Nuxt application after scaffolding (Large preview)

Install Redis

Open a new terminal or command line tab and change directories (cd) into your project’s parent folder. Download redis and run make. If you’re on Windows, you’ll need to check out https://github.com/MicrosoftArchive/redis/releases.

$ cd ../
$ wget http://download.redis.io/releases/redis-5.0.3.tar.gz
$ tar xzf redis-5.0.3.tar.gz
$ cd redis-5.0.3
$ sudo make install
cd src && /Library/Developer/CommandLineTools/usr/bin/make install

Hint: It is a good idea to run ‘make test’. 😉

INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install

$ redis-server --version
Redis server v=5.0.3 sha=00000000:0 malloc=libc bits=64 build=bfca7c83d5814ae0

$ redis-server --daemonize yes

That will start our redis server as a background process and we can close this tab. The local redis server will be running at http://127.0.0.1:6379/.

In our tab with our project running, type Ctrl + C to kill the server. We’ll need to install a redis package for node, and provision our Heroku Redis instance.

$ npm install async-redis --save
npm WARN eslint-config-prettier@3.6.0 requires a peer of eslint@>=3.14.1 but none is installed. You must install peer dependencies yourself.

+ async-redis@1.1.5
added 5 packages from 5 contributors and audited 14978 packages in 7.954s
found 0 vulnerabilities

$ heroku addons:create heroku-redis
Creating heroku-redis on ⬢ cherislistening... free
Your add-on should be available in a few minutes.
! WARNING: Data stored in hobby plans on Heroku Redis are not persisted.
redis-metric-84005 is being created in the background. The app will restart when complete...
Use heroku addons:info redis-metric-84005 to check creation progress
Use heroku addons:docs heroku-redis to view documentation

Because we are using a hobby account, we don’t have a backup of our data. If our instance needs rebooted, we’ll need to re-authenticate to get a new key. Our application will also sleep on the free account, so some initial visits will be a little slow, while the app is “waking up”.

Our new app will be live at https://cherislistening.herokuapp.com/, where ‘cherislistening’ is whatever you named your Heroku application.


A screenshot of the initial Heroku hosted application. The initial parked page says “Welcome to your new app!” and contains a link to Heroku’s documentation
Default parked page for our new Heroku app (Large preview)

Sign Up For A Spotify Developer Account

This requires a Spotify account. Make note that every use of Spotify’s API must adhere to their brand guidelines.

Create a Client ID at https://developer.spotify.com/dashboard/applications.

Take the Client ID and the Client Secret, which you can find if you click on the green card into your new application’s details, and export them to Heroku as configuration variables. Keep these safe and secret! If you believe your client secret has been exposed, you can get a new one, but you’ll need to update your application’s configuration as well.


A screenshot of the client ID and client secret tokens in Spotify’s dashboard - the client ID and client secret have been blurred out
Never share your client ID and client secret tokens! (Large preview)
$ heroku config:set CLIENT_ID=<CLIENT_ID> CLIENT_SECRET=<CLIENT_SECRET>
Setting CLIENT_ID, CLIENT_SECRET and restarting ⬢ cherislistening... done, v3
CLIENT_ID:             <CLIENT_ID>
CLIENT_SECRET:         <CLIENT_SECRET>

On the top right side of the application dashboard, there is a Settings button. Click that and add in two callback URLs for whitelisting. You’ll need a local callback URL and one for your production server (the Heroku URL we got during setup).


A screenshot of edit settings in Spotify’s dashboard. Under redirect URIs, two entries have been added: ‘http://localhost:3000/api/callback’ and ‘http://cherislistening.herokuapp.com/api/callback’.
Whitelisted callback URLs in Spotify’s dashboard (Large preview)

Spotify has fantastic developer documentation, including a great reference interface for testing endpoints. We’ll need to get our user ID to save to our configuration variables, so let’s do that with Get Current User’s Profile. Get an auth token from their console, selecting the user-read-private scope. Click “Try It”, and in the right-hand column look for your ID. We’ll use this identifier to make sure no one else can sign into our app.

$ heroku config:set SPOTIFY_USER_ID=<SPOTIFY_USER_ID>
 Setting SPOTIFY_USER_ID and restarting ⬢ cherislistening... done, v4
 SPOTIFY_USER_ID:             <SPOTIFY_USER_ID>

As we discussed, we will have data we wouldn’t want exposed to the public. Two of these are clientId and clientSecret we were given by Spotify, and another which Heroku exported for us to access our Redis cache on the server. We’ll need to grab those for our local development as well.

$ heroku config
=== cherislistening Config Vars
API_URL:               /
CLIENT_URL:            http://cherislistening.herokuapp.com
HOST:                  0.0.0.0
NODE_ENV:              production
NPM_CONFIG_PRODUCTION: false
REDIS_URL: <REDIS_URL>
SPOTIFY_CLIENT_ID: <SPOTIFY_CLIENT_ID>
SPOTIFY_CLIENT_SECRET: <SPOTIFY_CLIENT_SECRET>
SPOTIFY_USER_ID: <SPOTIFY_USER_ID>

$ touch .env

We’ll transfer the credentials Heroku returned in our terminal to our new file, .env, and we’ll make our client URL our local server, http://localhost:3000/. We’ll need to make our Redis URL point to our local instance, too, which by default is redis://127.0.0.1:6379. This file will be ignored by git.

CLIENT_URL=http://localhost:3000/
REDIS_URL=redis://127.0.0.1:6379
SPOTIFY_CLIENT_ID=<SPOTIFY_CLIENT_ID>
SPOTIFY_CLIENT_SECRET=<SPOTIFY_CLIENT_SECRET>
SPOTIFY_USER_ID=<SPOTIFY_USER_ID>

.env

In order to access the configuration on our local server, we’ll need to update the nuxt config. We’ll add another item to our modules array: @nuxtjs/dotenv. We’ll also need to import two of the variables we will need available on the client-side of our application. We’ll add an env object following modules.

/*
  ** Nuxt.js modules
  */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/dotenv'
  ],
  env: {
    spotifyId: process.env.SPOTIFY_CLIENT_ID,
    clientUrl: process.env.CLIENT_URL
  }

nuxt.config.js

Building Our API Layer

Middleware

Nuxt has two separate methods for executing server-side code.

In a single-file component (SFC), you have access to the middleware property, which corresponds with the middleware folder in your scaffolding. The drawback with this middleware for our use-case is that while it will execute server-side when your page is loaded or refreshed, it will execute client-side once your app is mounted, and when you navigate with nuxt’s routes.

The other option is what we’re looking for. We’ll create our own directory and add it as serverMiddleware to our config. Nuxt creates its own express instance, so we can write middleware registered to its stack that will only run on the server. This way, we can protect our private data from exploitation. Let’s add an api folder and index.js to handle our API endpoints.

$ mkdir api
 $ touch api/index.js

Next, we’ll need to add our directory to our config so it registers when we start our server. Let’s open up the file nuxt.config.js at the root of our app. This file gives us our HTML <head>, as well as connecting anything to our client at build time. You can read more about the config in the docs.

We’ll add our api directory to our config file,

  },
   serverMiddleware: ['~/api']
 }

nuxt.config.js

While we are developing, our changes will require rebuilds and server reboots. Since we don’t want to have to do this manually, nuxt installs nodemon for us, which is a “hot reload” tool. This just means it will reboot the server and rebuild our app when we save our changes.

Since we’ve added our API as serverMiddleware to Nuxt’s, we’ll need to add our directory to the config. We’ll add watch to our build object, and add the relative path from root.

  */**
   *** Build configuration*
   **/*
   build: {
     watch: ['api'],
     */**
     *** You can extend webpack config here*
     **/*
     extend(config, ctx) {},
   serverMiddleware: ['~/api']
   }

nuxt.config.js

We’ll also need to change our dev script in package.json to restart the server. We’ll need to make it nodemon --watch api --exec "nuxt":

"scripts": {
     "dev": "nodemon --watch api --exec "nuxt"",
     "build": "nuxt build",
     "start": "nuxt start",
     "generate": "nuxt generate",
     "heroku-postbuild": "npm run build"
   },

package.json

Now we don’t have to worry about rebooting and restarting our server manually every time we make a change. 🎉

Let’s start our local development server.

$ npm run dev

Data Flow, Storage, And Security

Before we start writing our API layer, we’ll want to plan how we move data from external sources to our client. We’ve set up a Redis cache server, signed up for Spotify API, and set up a structure that has a client layer and a server layer. The client has pages and a store where we can store and render our data. How do these work together to keep our authentication data safe and drive our Now Playing component?


A model drawn to show how we will move our data through our application - in the center is our internal API layer - connected to it, flowing data in two ways is Spotify’s API, Redis Storage, and the client - the client is further divided into a Vue Page and the Vuex Store
Data flow plan in a chart (Large preview)

Any information we want to keep long-term, or for new incoming connections, we’ll want to store on the server. We can’t log into Spotify when other users visit our app, so we’ll need want to ensure that new client connections can bypass authentication by accessing our special service token. We’ll want to keep track of our own Spotify login so that only our own connection is approved by the API, and we’ll want a track ready to show in case we can’t connect to Spotify’s API for some reason.

So, we’ll need to plan on storing our Spotify refresh_token, our Spotify userId, and our lastPlayedTrack in our Redis Cache.

Everything else can safely be stored in our client’s vuex store. The store and the pages (including their components) will pass data back and forth using nuxt’s architecture, and we’ll talk to the Redis cache and Spotify’s API via our own server’s API.

Writing The API

Nuxt comes with the express framework already installed, so we can import it and mount our server application on it. We’ll want to export our handler and our path, so that nuxt can handle our middleware.

import express from 'express'

 const app = express()

 module.exports = {
   path: '/api/',
   handler: app
 }

api/index.js

We’ll need a few endpoints and functions to handle the services we need:

  • POST to our Redis Cache
  • Spotify last played track
  • Name
  • Artists
  • Album Cover Asset URL
  • Spotify refresh_token
  • Spotify access_token
  • Status of Spotify Connection
  • GET from our Redis Cache
  • Same as POST
  • Callback from Spotify
  • Refresh our Spotify access_token
  • GET recently played tracks from Spotify
  • GET currently playing track from Spotify

This may seem like a lot of calls, but we will combine and add small bits of logic where it makes sense as we write.

The Basics Of Writing An Endpoint In Expressjs

We’ll use express’s get() method to define most of our endpoints. If we need to send complex data to our API, we can use the post() method.

But what if we could do both? We can accept multiple methods with all().

Let’s add the first route we’ll need, which is our connection to our Redis Cache. We’ll name it spotify/data. The reason we’re naming it based on spotify rather than redis is because we’re handling information from Spotify, and Redis is simply a service we’re using to handle the data. spotify is more descriptive here, so we know what we’re getting, even if our storage service changes at some point.

For now, we’ll add only a res.send():

import express from 'express'

 const app = express()

 app.all('/spotify/data/:key', (req, res) => {
   res.send('Success! 🎉n')
 })

 module.exports = {
   path: '/api/',
   handler: app
 }

api/index.js

Let’s test to make sure everything is working properly. Open a new tab in your terminal or command line, to ensure your nuxt server continues running and run the following cURL command:

$ curl http://localhost:3000/api/spotify/data/key
 Success! 🎉

As you can see, res.send() returned the message we included in response to our GET request. This is how we will return the data we retrieve from Spotify and Redis to the client, too.

Each one of our endpoints will have the same basic structure as our first one.


The image depicts the endpoint path as spotify/data, the param as /:key, request as req, and response as res
Breakdown of an API endpoint function in Expressjs (Large preview)

It will have a path, /spotify/data/, it may have a param, like :key, and on request, express will return a request object, req, and a response object, res. req will have the data we send with to the server, res is waiting to handle what we want to do after we complete any procedures within our function.

Connecting To The Redis Cache

We’ve already seen that we can return data back to our client with res.send(), but we may also want to send a res.status(). When we have an issue reaching Spotify (or our Redis cache), we’ll want to know so we can gracefully handle the error, instead of crashing our server, or crashing the client. We’ll also want to log it, so we can be informed about failures on applications we build and service.

Before we can continue with this endpoint, we’ll need access to our Redis Cache. During setup, we installed async-redis, which will help us easily access our cache from Heroku. We’ll also need to add our dotenv config so we can access our redis URL.

import redis from 'async-redis'

require('dotenv').config()

// Redis
function connectToRedis() {
  const redisClient = redis.createClient(process.env.REDIS_URL)
  redisClient.on('connect', () => {
    console.log('n🎉 Redis client connected 🎉n')
  })
  redisClient.on('error', err => {
    console.error(`n🚨 Redis client could not connect: ${err} 🚨n`)
  })
  return redisClient
}

api/index.js

By default, redis.createClient() will use host 127.0.0.1 and port 6379, but because our production redis instance is at a different host, we will grab the one we put in our config.

We should add some console commands on the connect and error listeners the redisClient provides us. It’s always good to add logging, especially during development, so if we get stuck and something isn’t working, we’ve got a lot of information to tell us what’s wrong.

We need to handle the following cases in our API layer:

  • POST to our Redis Cache
  • Spotify lastPlayedTrack
  • Title
  • Artist
  • Album Cover Asset URL
  • Spotify vrefresh_token
  • Spotify access_token
  • GET from our Redis Cache
  • Same as POST
async function callStorage(method, ...args) {
   const redisClient = connectToRedis()
   const response = await redisClient[method](...args)
   redisClient.quit()
   return response
 }

api/index.js

Since we are requesting data from an external resource, we’ll want to use async/await to let our program know that this endpoint contains a function that always returns a promise, and that we’ll need wait for it to be returned before continuing.

In our arguments, we pull out our required, known argument method, and assign the rest (...) of the parameters to the scoped const args.

We make a call to our redis client using bracket notation, allowing us to pass a variable as the method. We again use the spread operator, ... to expand our args Array into a list of arguments with the remaining items. A call to http://localhost:3000/api/spotify/data/test?value=1 would result in a call to the redis client of redisClient['set']('test', 1). Calling redisClient['set']() is exactly the same as calling redisClient.set().

Make a note that we must quit() to close our redis connection each time we open it.

function storageArgs(key, ...{ expires, body, ...props }) {
   const value = Boolean(body) ? JSON.stringify(body) : props.value
   return [
     Boolean(value) ? 'set' : 'get',
     key,
     value,
     Boolean(expires) ? 'EX' : null,
     expires
   ].filter(arg => Boolean(arg))
 }

api/index.js

We know that we can get two types of inputs: either a JSON body or a string value. All we really need to do is check if body exists, and we’ll assume it’s JSON and stringify it. Otherwise, we’ll use props.value. If it’s empty, it will be null. We’ll assign whichever we get back from the ternary statement to the const value. Make note that we are not destructuring value from the rest (...) of props because we need to assign body to value if it exists.

The first index of the array we are returning, position 0, will be the method we call on the redis client. We’re making a Boolean check in case something other than null is passed, like undefined. If there is a value, this will return true and our method will be set. If false, get.

Index 1 and index 2 are our key and value, respectively.

The 3rd and 4th positions are used to set an expiration date on the key. This comes in handy for our access_token, which will expire every few minutes to protect the integrity of our application.

As you may have suspected, we don’t want a null or undefined value in our array, so if there is no value, we’ll want to remove it. There are several ways to handle this, but the most readable is to use Array’s method filter(). This creates a new Array, removing any items that don’t match our condition. Using a Boolean() type coercion, we can check for a true or false. A null or undefined argument in our array will be removed, leaving us with an array of arguments we can trust to return back to the caller.

const app = express()
 app.use(express.json())
 // Express app
 app.all('/spotify/data/:key', async ({ params: { key } }, res) => {
   try {
     if (key === ('refresh_token' || 'access_token'))
       throw { error: '🔒 Cannot get protected stores. 🔒' }

     const reply = await callStorage(...storageArgs(key))

     res.send({ [key]: reply })
   } catch (err) {
     console.error(`n🚨 There was an error at /api/spotify/data: ${err} 🚨n`)
     res.send(err)
   }
 })

api/index.js

Make note of app.use(express.json()). This gives us access to body on the request object. We’ll also be wrapping our endpoint procedures in try/catch blocks so we don’t wind up with uncaught errors. There are other ways to handle errors, but this is the simplest for our application.

Note: Check out this awesome demo of different errors by Wes Bos on Error Handling in Nodejs with async/await.

We want to make sure that this endpoint doesn’t return any of the data we’re trying to hide, so after we grab our key by destructuring the request object, we’ll throw an error letting the client know they can’t get those stores. Make note that when we know the structure of an incoming Object’s structure in JavaScript ES6, we can use curly braces to pull out variable names using the Object’s keys.

const reply = await callStorage(...storageArgs(key))

api/index.js

We’re calling the function named callStorage. Because we may have 3 or 4 arguments, we’re passing in rest parameters using a spread of our args Array. In the call, above, we use ... to expand an Array into our list of arguments of an unknown size, which are built from the function StorageArgs().

res.send({ [key]: reply })
   } catch (err) {
     console.error(`n🚨 There was an error at /api/spotify/data: ${err} 🚨n`)
     res.send(err)
   }
 })

api/index.js

Now that we have our reply from the redis client, we can send it to the client via the response Object’s method send(). If we POSTed to our cache, we’ll get a 1 back from the server if it’s a new key and 0 if we replaced an existing key. (We’ll want to make a mental note of that for later.) If there’s an error, we’ll catch it, log it, and send it to the client.

We’re ready to call the redis client and begin setting and getting our data.


The image maps a test parameter in the API endpoint to the key argument in the Redis client call function, and a query of value set to 1 mapped to the second argument of value
Mapping an endpoint to a Redis server call (Large preview)

Now let’s send a few test cURLs to our API endpoint in our command line or Terminal:

$ curl --request POST http://localhost:3000/api/spotify/data/test?value=Hello
{"test": 1}

$ curl http://localhost:3000/api/spotify/data/test
{"test": "Hello"}

$ curl --request POST 
  http://localhost:3000/api/spotify/data/bestSong 
  --header 'Content-Type: application/json' 
  --data '{
"name": "Break up with ur gf, I''’m bored",
"artist": "Ariana Grande"
}'
{"bestSong": 1}

$ curl http://localhost:3000/api/spotify/data/bestSong
{"bestSong":"{"name":"Break up with ur gf, I’m bored","artist":"Ariana Grande"}"}
Connecting With Spotify

Our remaining to-do list has shrunk considerably:

  • Callback from Spotify
  • Refresh our Spotify access_token
  • GET recently played track from Spotify
  • GET currently playing track from Spotify

A callback is a function that must be executed following the completion of a prior function. When we make calls to Spotify’s API, they will “call us back”, and if something isn’t quite right, Spotify’s server will deny us access to the data we requested.

import axios from 'axios'

api/index.js

Our callback will need to do a couple of things. First, it will capture a response from Spotify that will contain a code we need temporarily. Then, we’ll need to make another call to Spotify to get our refresh_token, which you may recognize from our redis storage planning. This token will give us a permanent connection to Spotify’s API as long as we are on the same application logged in as the same user. We’ll also need to check for our userId for a match before we do anything else, to prevent other users from changing our data to their own. Once we confirm we are the logged in user, we can save our refresh_token and access_token to our redis cache. Because we’re making API calls in our callback function, we’ll need to import axios to make requests, which nuxt installed when we scaffolded the app.

Note that JavaScript has a native fetch() method, but it’s very common to see axios used instead, because the syntax is more user-friendly and legible.

const getSpotifyToken = (props = {}) =>
  axios({
    method: 'post',
    url: 'https://accounts.spotify.com/api/token',
    params: {
      client_id: process.env.SPOTIFY_CLIENT_ID,
      client_secret: process.env.SPOTIFY_CLIENT_SECRET,
      redirect_uri: `${process.env.CLIENT_URL}/api/spotify/callback`,
      ...props
    },
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  })

api/index.js

One of the benefits to using a function expression instead of an arrow function expression is that you have access to an inherit object called arguments which is mapped by index, you also get access to a contextual this object. While we don’t need access to a lexical this, since we’re only returning the response of our redisClient call, we can omit closures here and implicitly return the response of the call.

We’ll want to write a single function for getting Spotify tokens. The majority of the code for getting our refresh_token and access_token is basically the same, so we can write an axios POST boilerplate, and spread (...) a props Object. Spreading an Object expands its properties into the context parent Object at the root depth, so if we spread { grant_type: 'refresh_token' }, our params Object will be extended to contain the properties of {client_id, client_secret, redirect_url, grant_type }. Again, we forego a return with an arrow function and opt for an implicit return since this function only returns a single response.

Note that we set props in the arguments as an empty Object ({}) by default just in case this function gets called without an argument. This way, nothing should break.

const spotifyBaseUrl = 'https://api.spotify.com/v1/'

 const getUserData = access_token =>
   axios.get(`${spotifyBaseUrl}me`, {
     headers: {
       withCredentials: true,
       Authorization: `Bearer ${access_token}`
     }
   })

api/index.js

In order to check to see we are the user who logged in via Spotify, we’ll write another implicitly returned arrow function expression and call Spotify’s Get Current User’s Profile method (the one we tested earlier to get our SPOTIFY_USER_ID). We set a const here with the base API URL because we will use it again in our other calls to the library. Should this ever change in the future (like for Version 2), we’ll only have to update it in once.

We now have all of the functions we need to write our callback endpoint. Make note of the fact that this will be a client-facing endpoint.

app.get('/spotify/callback', async ({ query: { code } }, res) => {
  try {
    const { data } = await getSpotifyToken({
      code,
      grant_type: 'authorization_code'
    })
    const { access_token, refresh_token, expires_in } = data
    const {
        data: { id }
    } = await getUserData(access_token)

    if (id !== process.env.SPOTIFY_USER_ID)
      throw { error: "🤖 You aren’t the droid we’re looking for. 🤖" }


    callStorage(...storageArgs({ key: 'is_connected', value: true }))
    callStorage(...storageArgs({ key: 'refresh_token', value: refresh_token }))
    callStorage(
      ...storageArgs({
        key: 'access_token',
        value: access_token,
        expires: expires_in
      })
    )

    const success = { success: '🎉 Welcome Back 🎉' }
    res.redirect(`/auth?message=${success}`)
  } catch (err) {
    console.error(
      `n🚨 There was an error at /api/spotify/callback: ${err} 🚨n`
    )
    res.redirect(`/auth?message=${err}`)
  }

api/index.js

Our callback endpoint must match the URL we added to our settings in the Spotify dashboard exactly. We used /api/spotify/callback, so we’ll get at /spotify/callback here. This is another async function, and we need to destructure code from the request object.

We call the function we wrote earlier, getSpotifyToken(), to get our first access_token, our refresh_token, and our first expires_in. We’ll want to save all three of these to our redis cache, using redis’ set method’s built-in key timeout command to expire our access_token in expires_in seconds. This will help us set up a system of refreshing our access_token when we need it. Redis will set the access_token to null after the time to live (TTL) has reached 0 milliseconds.

Now that we have an access_token, we can make sure that the user who connected is us. We call getUserData(), the function we wrote earlier, and destructure the ID to compare to the user ID we saved to our environment configuration. If it’s not a match, we’ll throw an error message.

After we’re sure that our refresh_token is trusted, we can save our tokens to our redis cache. We call callStorage again — once for each token.

Make note that redis does have methods for setting multiple keys, but because we want to expire our access_token, we need to use set().

Since this is a client-facing endpoint, we’ll redirect to a URL and append a success or error message for the client to interpret. We’ll set this path up later on the client side.

We’ll need to retrieve our access_token and refresh it if necessary before we call any other Spotify endpoints. Let’s write an async function to handle that.

async function getAccessToken() {
  const redisClient = connectToRedis()
  const accessTokenObj = { value: await redisClient.get('access_token') }
  if (!Boolean(accessTokenObj.value)) {
    const refresh_token = await redisClient.get('refresh_token')
    const {
      data: { access_token, expires_in }
    } = await getSpotifyToken({
      refresh_token,
      grant_type: 'refresh_token'
    })
    Object.assign(accessTokenObj, {
      value: access_token,
      expires: expires_in
    })
    callStorage(...storageArgs('access_token', { ...accessTokenObj }))
  }
  redisClient.quit()
  return accessTokenObj.value
}

api/index.js

We assign a const accessTokenObj to an Object with the value of our redis get('access_token'). If the value is null, we’ll know it’s expired and we need to refresh it. After getting our refresh_token from our cache, and getting a new access_token, we’ll assign our new values to accessTokenObj, set() them in redis, and return the access_token.

Let’s get write our endpoint for getting the currently-playing track. Since we’ll only want recently-played if there’s nothing currently playing, we can write a function for our endpoint to call that handles getting that data if it’s needed.

app.get('/spotify/now-playing/', async (req, res) => {
  try {
    const access_token = await getAccessToken()
    const response = await axios.get(
      `${spotifyBaseUrl}me/player/currently-playing?market=US`,
      {
        headers: {
          withCredentials: true,
          Authorization: `Bearer ${access_token}`
        }
      }
    )
    const { data } = response
    setLastPlayed(access_token, data)
    const reply = await callStorage('get', 'last_played')
    res.send({
      item: JSON.parse(reply),
      is_playing: Boolean(data.is_playing),
      progress_ms: data.progress_ms || 0
    })
  } catch (err) {
    res.send({ error: err.message })
  }
})

async function setLastPlayed(access_token, item) {
  if (!Boolean(item)) {
    const { data } = await axios.get(
      `${spotifyBaseUrl}me/player/recently-played?market=US`,
      {
        headers: {
          withCredentials: true,
          Authorization: `Bearer ${access_token}`
        }
      }
    )
    postStoredTrack(data.items[0].track)
  } else {
    postStoredTrack(item)
  }
}


function postStoredTrack(props) {
  callStorage(
    ...storageArgs({
      key: 'last_played',
      body: props
    })
  )
}

api/index.js

The endpoint gets the Get the User’s Currently Playing Track endpoint and the async function setLastPlayed() calls the Get Current User’s Recently Played Tracks if nothing is returned from currently-playing. We’ll call our last function postStoredTrack() with whichever one we have, and retrieve it from our cache to send to the client. Note the we cannot omit the else closure because we aren’t returning anything in the if closure.

Vuex: Client-Side Storage And State Management

Now that we have middleware to connect to our services by proxy, we can connect those services to our client-side application. We’ll want our users to have automatic updates when we change songs, pause, rewind, or fast-forward, and we can handle those changes with state management.

State is our application’s way of holding onto information in real-time. It is how our application remembers the data it uses, and any changes to that data. State is really a short way of saying “the state of the system’s data”. The state of a Vue application is held in a user’s browser session, and with certain patterns, we can trigger various events to mutate that state. When the state changes, our application can update without requiring storage or server calls.

The pattern we’ll use is called a store pattern. This gives us a single source of truth as a user moves about our application (even though we’ll only have two pages for this particular app).

Vue’s component lifecycle adds the necessary one-way bindings we need, and Nuxt comes with Vuex that does all of the heavy lifting when our data changes. We will want our state to be constantly updating, but we won’t to call our API every few milliseconds to keep a progress bar moving. Instead of constantly polling our API, and reaching Spotify’s rate limit, we can lean on Vuex setters to continuously update the state of our bindings.

The data we’ll be dealing with will only be bound one-way. This means that our component and page views can get the data in store, but in order to mutate that data, they will need to call an action in the store.


A model drawn to show how data flows one way in our app - in the center is our Vuex store, which sends data our view, the pages and components - the view calls actions to mutate the model, which in turn update the Vuex store
One way data flow in our application (Large preview)

As you can see, the data only moves one way. When our application starts, we’ll instantiate our models with some default data, then we will hydrate the state in a middleware function expression built into Nuxt’s implementation of Vuex called nuxtServerInit(). After the application is running, we will periodically rehydrate the store by dispatching actions in our pages and components.

Here’s the basic structure we’ll need to activate a store in store/index.js:

// instantiated defaults on state
export const state = () => {
  property: null
}

// we don’t edit the properties directly, we call a mutation method
export const mutations = {
  mutateTheProperty (state, newProperty) {
    // we can perform logical state changes to the property here
    state.property = newProperty
  }
}

// we can dispatch actions to edit a property and return its new state
export const actions = {
  updateProperty: ({ commit, state }, newProperty) => {
   commit('mutateTheProperty', newProperty)
     return state.property // will equal newProperty and trigger subscribers to re-evaluate
   }
}

Once you feel comfortable, you can set up more shallow modular stores, which Nuxt implements based on your file structure in store/. We’ll use only the index module.

$ touch store/index.js
export const state = () => ({
  isConnected: false,
  message: null,
  nowPlaying: {},
  recentlyPlayed: {},
  trackProgress: 0,
  isPlaying: false
})

store/index.js

We’re going to need a few models to instantiate the state when our app starts. Note that this must be a function that returns an Object.

  • isConnected: tells us if we’re already connected via Spotify.
  • message: tells us if there’s an error during authentication (we set these up in the API on our callback endpoint).
  • nowPlaying: the song (track) Object that is currently or recently playing.
  • recentlyPlayed: the track most recently played.
  • trackProgress: the amount of the track that has already played (a percentage).
  • isPlaying: if the nowPlaying track is currently being played.

To update these, we’ll need to add mutations for each model. You can mutate more than one model in a mutation function, but to keep things digestible, we’re going to write a flat mutations object.

export const mutations = {
  connectionChange(state, isConnected) {
    state.isConnected = isConnected
  },
  messageChange(state, message) {
    state.message = message
  },
  nowPlayingChange(state, nowPlaying) {
    state.nowPlaying = nowPlaying
  },
  isPlayingChange(state, isPlaying) {
    state.isPlaying = isPlaying
  },
  progressChange(state, { progress, duration }) {
    state.trackProgress = (progress / duration) * 100
  },
  recentlyPlayedChange(state, recentlyPlayed) {
    state.recentlyPlayed = recentlyPlayed
  }
}

store/index.js

We’re not doing much in the way of data massaging for this app, but for progress we’ll need to calculate the percentage ourselves. We’ll return an exact number from 0-100.

export const actions = {
  async nuxtServerInit({ commit }) {
      try {
          const redisUrl = `${clientUrl}/api/spotify/data/`
                    const {
                         data: { is_connected }
                    } = await axios.get(`${redisUrl}is_connected`)

                    commit('connectionChange', is_connected)
                    if (Boolean(is_connected)) {
                         const {
                             data: { item, is_playing }
                          } = await axios.get(`${clientUrl}/api/spotify/now-playing`)

            commit('nowPlayingChange', item)
                        commit('isPlayingChange', is_playing)
                    }
  } catch (err) {
                   console.error(err)
            }
    },
  updateProgress: ({ commit, state }, props) => {
    commit('progressChange', props)
    return state.trackProgress
  },
  updateTrack: ({ commit, state }, nowPlaying) => {
    commit('nowPlayingChange', nowPlaying)
    return state.nowPlaying
  },
  updateStatus: ({ commit, state }, isPlaying) => {
    commit('isPlayingChange', isPlaying)
    return state.isPlaying
  },
  updateConnection: ({ commit, state }, isConnected) => {
    commit('connectionChange', isConnected)
    return state.isConnected
  }
}

store/index.js

nuxtServerInit() will be run when our server starts automatically, and will check if we are connected to Spotify already with a query to our redis data endpoint. If it finds that the redis cache key of is_connected is true, it will call our “now-playing” end point to hydrate nowPlaying with live data from Spotify, or whatever is already in the cache.

Our other actions take our store object and destructure commit() and state with our new data, commit() the data to the store with our mutations, and return the new state to the client.

Building The Pages And Components

Now that we have our API setup to give us data from Spotify and our store, we’re ready to build our pages and components. While we’re only going to make a couple of small pieces in this tutorial for brevity, I encourage liberal creativity.

We’ll need to remove the initial pages that the Nuxt scaffolding added, and then we’ll add our components and pages.

$ rm pages/index.vue components/Logo.vue layouts/default.vue
$ touch pages/index.vue components/NowPlaying.vue components/Progress.vue

The basic structure of every layout, page, and component in a single file component is the same. In fact, every layout, page, and component in Nuxt is a Vue component.

You can read further usage outside of the scope of this tutorial on Vue’s component registration documentation. We’re just going to do everything in the file and use plain HTML and CSS.

The repository for the demo will contain some components and styles that are not in this tutorial in order to keep things a little less complex.

<template>
  // Write plain HTML here, avoid using any logic here
  
</template> // Write plain javascript here, you can import libraries, too export default { key: 'value' } <style> // Write plain global CSS here div { display: inline; } </style>

Layout

We need to start with the default layout; this is the root of the application, where Vue will be mounted. The layout is a type of view, of which every page extends. This means that the HTML found in the layout will be the basis of all the html in every page we create.

<template>
  
Login
</template>

layouts/default.vue

In the template tag, we need a single root container, and <nuxt/> is where our application will mount.

Note: In the demo code, I’ve added a <Header/> and a <Footer/>, and the footer is a functional component because all of the data is static.

In this tutorial, I’ve added a <nuxt-link/> pointed to /auth. <nuxt-link> creates navigational links for routes within your app. I’ve added a conditional aria-current attribute to nuxt-link. By adding a colon (:) in front of the attribute, I’ve indicated to Vue that the value of the attribute is bound to some data, turning the value into JavaScript that will be interpreted as a string during the component lifecycle, depending on the condition of the expression. In a computed ternary statement, if the user on the route named auth, it will set the aria-current attribute to “page”, giving screen readers context to whether or not the user is on the path the link is pointed to. For more information on Vue’s data-binding, read this documentation.


    export default {
      titleShort: 'is Listening',
      authorName: 'Cher',
      computed: {
        ariaCurrent() {
          return 'auth' === this.$route.name ? 'page' : false
        }
      },
      head() {
        return {
          title: `${this.$options.authorName} ${
            this.$options.titleShort
          } ·X· A Musical App`,
          link: [
            {
              rel: 'stylesheet',
              href: 'https://fonts.googleapis.com/css?family=Bungee+Hairline|Oswald'
            }
          ]
        }
      }
    }

layouts/default.vue

The script tag can be thought of like a single JavaScript module. You can import other modules, and you export an Object of properties and methods. Above, we set two custom properties: titleShort and authorName. These will be mounted onto this.$options, and down the component tree you can access them through $nuxt.layout. This is useful for information you use at the root level, and in deep-nested children, like for updating the document title, or using our authorName in other contexts.


There are several functions that Vue will look for and run, like head() and computed() in the above example.

head() will modify the <head> of the HTML document. Here I’ll update the document title, and add a link.

The computed() method is for reactive data that needs to be evaluated. Whenever the shape of the data changes, it triggers a re-evaluation and a subsequent re-render of the node it is bound to.

<style>
    :root {
      --colorGray: #333642;
      --colorBlue: rgba(118, 120, 224, 0.5);
      --colorBrightBlue: rgb(0, 112, 255);
    }

    html {
      background: #000000;
    }

    body {
      padding: 0;
      margin: 0;
      color: white;
      font-family: 'Bungee Hairline', monospace;
    }

    a {
      color: white;
      text-decoration: none;
      display: inline-block;
      position: relative;
    }

    a:after,
    a:before {
      content: '';
      position: absolute;
      left: 0;
      right: 0;
      height: 1em;
      z-index: -1;
      mix-blend-mode: color-burn;
    }

    a:after {
      bottom: 2px;
      background: var(--colorBlue);
      z-index: -1;
      transform: rotate(-3deg);
    }

    a:before {
      background: rgba(118, 120, 224, 0.4);
      transform: rotate(2deg);
    }

    .nuxt-progress {
      opacity: 0.3;
      height: 2px;
      bottom: 0;
      top: auto;
    }
</style>

layouts/default.vue

In the CSS, you’ll notice I’m using a non-standard font, but no @import declaration. Since these are rendered on the server, they won’t be able to reach an external resource that isn’t in the build. We can still attach external resources — we just need to do it in a different way. There are workarounds that exist for this, but we just added it to our head(). You can also add it to nuxt.config.js.

The :root selector allows us to set global CSS variables we can use throughout the application. .nuxt-progress selector is for the progress bar that Nuxt adds during build automatically. We can style it here. I’ve just moved it to the bottom of the app and made it transparent and small.

Authentication Page

Now that we have a default layout, we can work on our authentication page. Pages are another kind of view in Nuxt, which render the HTML, CSS, and JavaScript that is needed for specific routes.

Pages and routes are automatically handled for every Vue file inside of the pages directory. You can also add more complex routing.

Everything has led us to this moment! Finally, we get to render some of our API-retrieved data!

<template>
  <transition name="fade" mode="in-out">
    <section>
      <nuxt-link
        to="/"
        name="index"
      >Close</nuxt-link>
      {{ message }}
    </section>
  </transition>
</template>

pages/auth.vue

<transition> is used to add transitions between pages and components mounting and unmounting. This will add conditional class names related to the name, and the mode “in-out” will make our transition happen both on entry and exit. For further usage, check out the documentation.

We get at data in the <template> with double curly braces {{}}. this is implied, so we don’t need to include it in the <template>.


    export default {
      asyncData({ env: {spotifyId, clientUrl}, query }) {
        const spotifyUrl = `https://accounts.spotify.com/authorize?client_id=${
          spotifyId
        }&response_type=code&scope=user-read-currently-playing,user-read-recently-played&redirect_uri=${
          clientUrl
        }/api/spotify/callback`
        return {
          spotifyUrl,
          query
        }
      },
      computed: {
        isConnected() {
          return this.$store.state.isConnected
        },
        message() {
          return this.$store.state.message
        }
      },
      mounted() {
        const { success, error } = this.query
        if (
          !Boolean(success || error) &&
          !Boolean(this.isConnected)
        ) {
          window.location = this.spotifyUrl
        } else if (Boolean(Object.keys(this.query).length !== 0)) {
          window.history.replaceState({}, document.title, window.location.pathname)
          this.$store.commit(
            'updateMessage',
            success || error
          )
          if (Boolean(success)) {
            this.$store.dispatch('updateConnection', true)
          }
        }
        if (Boolean(this.isConnected)) {
          this.$store.commit('updateMessage', "⚡ We’re Connected ⚡")
        }
      }
    }

pages/auth.vue

The first thing we need to do is redirect to the authentication server, which will call us back at our callback API proxy, and we setup to redirect us back to /auth, or this file we’re in now. To build the URL, we’ll need to get the environment variables we attached to the context object under the env parameter. This can only be done in pages. To access the context object, we’ll need to add the asyncData() method to our Object.

This function will be run before initializing the component, so make note that you do not have access to a component’s lexical this (which is always in the context of the local $nuxt Object) in this method because it does not exist yet. If there is async data required in a component, you will have to pass it down through props from the parent. There are many keys available in context, but we’ll only need env and query. We’ll return spotifyUrl and query, and they will be automatically merged with the rest of the page’s data.

There are many other lifecycle methods and properties to hook onto, but we’ll really only need mounted() and computed, data(), props, components, methods, and beforeDestroy(). mounted() ensures we have access to the window Object.

In mounted(), we can add our logic to redirect the user (well, us) to login via Spotify. Because our login page is shared with our authentication status page, we’ll check for the message Object we sent back from our callback redirect. If it exists, we will bypass redirecting so we don’t end up in an infinite loop. We’ll also check to see if we’re connected. We can set window.location to our spotifyUrl and it will redirect to the login. After logging in, and grabbing the query Object, we can remove it from our URL so our users don’t see it with window.history.replaceState({}, document.title, window.location.pathname). Let’s commit and dispatch the changes to our state in message and isConnected.

In computed(), we can return our properties from the store and they will be automatically updated on the view when they change.

Note that all properties and methods will have access to the lexical this once the component has been initialized.

<style scoped>
    section {
      position: absolute;
      width: 30%;
      min-width: 300px;
      left: 0;
      right: 0;
      bottom: 50%;
      margin: auto;
      padding: 1em;
      display: flex;
      flex-direction: column;
      text-align: center;
      justify-content: center;
      mix-blend-mode: hard-light;
      z-index: 2;
    }

    section:after,
    section:before {
      content: '';
      display: block;
      position: absolute;
      top: 0;
      bottom: 0;
      right: 0;
      left: 0;
      z-index: -1;
    }
    section:after {
      transform: rotate(1deg);
      background: rgba(255, 255, 255, 0.1);
    }
    section:before {
      transform: rotate(3deg);
      background: rgba(255, 255, 255, 0.03);
    }

    .fade-enter-active,
    .fade-leave-active {
      transition: opacity 600ms ease-out;
    }

    .fade-enter,
    .fade-leave-active {
      opacity: 0;
    }
</style>

pages/auth.vue

Note the scoped attribute added to <style>. This allows us to write shallow selectors that will only affect elements in the scope of this page (or component) by adding unique data attributes to the DOM. For more information read the documentation.

All the selectors starting with fade- are the classes created for our <transition>.

Head to http://localhost:3000/auth. If everything’s working, we should be able to login with Spotify by clicking the “Login” button, and be redirected back to see this:


A screenshot of our authentication page after we’ve been logged in. Shows our status as connected in the center of the screen
⚡ We’re connected! ⚡ (Large preview)

Let’s set up our root page.

Landing Page

This is the fun part! We’ll be creating the view that users will see when they get to our app, commonly referred to as the root or index. This is just a concise way of indicating it is the home file of its directory, and in our case, the entire application.

We’ll be adding our player directly to this page.

<template>
  <section>
    <NowPlaying v-if="isConnected && track" :nowPlaying="track" :isPlaying="isPlaying"/>
    <p v-if="!isConnected">
      😭 {{ $nuxt.layout.authorName }} hasn’t connected yet. 😭
    </p>
  </section>
</template>


import NowPlaying from '~/components/NowPlaying.vue'

export default {
  components: { NowPlaying },
  computed: {
    nowPlaying() {
      if (Boolean(Object.keys(this.$store.state.nowPlaying).length !== 0)) {
        this.$store.dispatch('updateConnection', true)
        return this.$store.state.nowPlaying
      }
      return this.$store.state.recentlyPlayed
    },
    track() {
      return this.nowPlaying
    },
    isPlaying() {
      return this.$store.state.isPlaying
    },
    isConnected() {
      return this.$store.state.isConnected
    }
  }
}


<style scoped>
section {
  min-width: 300px;
  max-width: 750px;
  margin: auto;
  padding: 1em;
}
</style>

pages/index.vue

We’ll need to import our NowPlaying component (we will write it next), and we’ll want to conditionally load it with a v-if binding based on whether or not we are connected and we have track data to show. Our computed nowPlaying() method will return the nowPlaying Object if it has properties (we instantiated an empty object in the store, so it will always exist), and we’ll dispatch an action that we’re connected. We’re passing the track and isPlaying props since they are required to show the component.

We’ll need to create our components next, otherwise this page won’t build.

Components

In Nuxt, components are partial views. They cannot be rendered on their own, and instead, can only be used to encapsulate parts of a layout or page view that should be abstracted. It’s important to note that certain methods Page views have access to, like asyncData() won’t be ever be called in a component view. Only pages have access to a server-side call while the application is starting.

Knowing when to split a chunk of a layout, page, or even component view can be difficult, but my general rule of thumb is first by the length of the file, and second by complexity. If it becomes cumbersome to understand what is going on in a certain view, it’s time to start abstracting.

We’ll split our landing page in three parts, based on complexity:

  • Index component: The page we just wrote.
  • NowPlaying component: The container and track information.
  • Progress component: The animated track progress indicator.
Now Playing
<template>
  <transition name="fade">
    <section>
      <aside>
        <img v-if="image" :src="image" alt="Album Artwork">
        <Progress :class="className" :progressPercent="progress" :image="image"/>
      </aside>
      
    </section>
  </transition>
</template>

components/NowPlaying.vue

It’s important we include a link to Spotify, as it is a part of the requirements to use their API free of charge. We’re going to pass the progress and image props to our <Progress> component.



import Progress from './Progress.vue'

export default {
  components: { Progress },
  props: ['isPlaying', 'nowPlaying'],
  data() {
    return { staleTimer: '', trackTimer: '' }
  },
  computed: {
    image() {
      const { album, image } = this.nowPlaying
      if (Boolean(album)) {
        const { url } = album.images[0]
        return url
      }
      return Boolean(image)
        ? image
        : 'https://developer.spotify.com/assets/branding-guidelines/icon2@2x.png'
    },
    progress() {
      return this.$store.state.trackProgress
    },
    artistsList() {
      const { artists } = this.nowPlaying
      return artists ? artists.map(artist => artist.name).join(', ') : null
    },
    href() {
      const { external_urls } = this.nowPlaying
      return external_urls ? external_urls.spotify : null
    },
    name() {
      return this.nowPlaying.name
    },
    status() {
      return this.isPlaying
        ? `is playing this track with ${Math.round(
            this.$store.state.trackProgress
          )}% complete`
        : 'has paused this track'
    }
  },
  created() {
    this.getNowPlaying()
    this.staleTimer = setInterval(() => {
      this.getNowPlaying()
    }, 10000)
  },
  methods: {
    updateProgress(progress = 0, duration = 0) {
      this.$store.dispatch('updateProgress', { progress, duration })
    },
    async getNowPlaying() {
      const { progress_ms, is_playing, item } = await this.$axios.$get(
        `/api/spotify/now-playing/`
      )
      if (Boolean(item)) {
        const progress = progress_ms
        const duration = item.duration_ms
        this.$store.dispatch('updateStatus', is_playing)
        clearInterval(this.trackTimer)
        if (is_playing) {
          this.timeTrack(Date.now(), duration, progress)
        } else {
          this.updateProgress(progress, duration)
        }
        let id = null
        if (Boolean(this.nowPlaying)) id = this.nowPlaying.id
        if (item && (is_playing && item.id !== id)) {
          this.$store.dispatch('updateTrack', item)
        }
      }
    },
    timeTrack(now, duration, progress) {
      const remainder = duration - progress
      const until = now + remainder
      this.trackTimer = setInterval(() => {
        const newNow = Date.now()
        if (newNow 

components/NowPlaying.vue

In addition to our computed() data, we will also have another type of reactive data on the data property. This property returns an Object with reactive properties, but these do not need to be re-evaluated. We will be using them for our timing intervals, so the updates will be come from setInterval().

created() runs when our component is done being initialized, so we’ll call our function getNowPlaying(), and start one of our two interval timers, staleTimer, which will run getNowPlaying() once every 10 seconds. You can make this shorter or longer, but keep in mind that Spotify does have rate limiting, so it shouldn’t be any less than a few seconds to avoid getting undesired API failures.

It’s important we add beforeDestroy() and clear our running intervals as a best practice.

In the methods property, we’ll have three functions: getNowPlaying(), updateProgress(), and timeTrack(). updateProgress() will dispatch progress updates to the store, while getNowPlaying() and timeTrack() will do the heavy lifting of keeping our track object hydrated and the progress bar moving every 10th of a second so we have a constantly moving progress bar.

Let’s take a closer look at getNowPlaying():

async getNowPlaying() {
  const { progress_ms, is_playing, item } = await this.$axios.$get(
    `/api/spotify/now-playing/`
  )
  if (Boolean(item)) {
    const progress = progress_ms
    const duration = item.duration_ms
    this.$store.dispatch('updateStatus', is_playing)
    clearInterval(this.trackTimer)
    if (is_playing) {
      this.timeTrack(Date.now(), duration, progress)
    } else {
      this.updateProgress(progress, duration)
    }
    const { id } = this.nowPlaying
    if (item.id !== id) {
      this.$store.dispatch('updateTrack', item)
    }
 }

components/NowPlaying.vue

This is an async function because we’re calling out now-playing endpoint, and we’ll want the function to wait until it has an answer to continue. If the item is not null or undefined, we’ll dispatch an update to the status, clearInterval() of our trackTimer (which may not be running, but that’s OK). If the is_playing is true, we’ll call timeTrack(); if it’s false, we’ll call updateProgress(). Last, we’ll check if our updated track is different than the one in our store. If it is, we’ll dispatch an update to the track in store to rehydrate our data.

timeTrack(now, duration, progress) {
  const remainder = duration - progress
  const until = now + remainder
  this.trackTimer = setInterval(() => {
    const newNow = Date.now()
    if (newNow < until + 2500) {
      const newRemainder = until - newNow
      const newProgressMs = duration - newRemainder
      this.updateProgress(newProgressMs, duration)
    } else {
      this.updateProgress(1, 1)
      clearInterval(this.trackTimer)
      this.getNowPlaying()
    }
  }, 100)
}

components/NowPlaying.vue

This function takes a current time, duration, and progress in milliseconds and starts running an interval every 100 milliseconds to update the progress. until is the time calculated when the track will be finished playing if it is not paused or scrubbed forwards or backwards. When the interval starts, we grab the current time in milliseconds with JavaScript’s Date Object’s now() method. We’ll compare the current time to see if it is less than until plus a buffer of 2500 milliseconds. The buffer is to allow for Spotify to update the data between tracks.

If we determine the track is theoretically still playing, we’ll calculate a new progress in milliseconds and call out the updateProgress() function. If we determine the track is complete, we’ll update the progress to 100%, clearInterval() and call nowPlaying() to get the next track.

<style scoped>
    section {
      position: relative;
      display: grid;
      grid-template-columns: 42% 58%;
      align-items: center;
      justify-content: center;
    }
    aside {
      position: relative;
      min-width: 50px;
    }

    img {
      opacity: 0;
      position: absolute;
      height: 0;
      width: 0;
    }

    section:after,
    section:before {
      content: '';
      display: block;
      position: absolute;
      top: 0;
      bottom: 0;
      right: 0;
      left: 0;
      z-index: 0;
    }

    section:after {
      transform: rotate(1deg);
      background: rgba(255, 255, 255, 0.1);
    }

    section:before {
      transform: rotate(3deg);
      background: rgba(255, 255, 255, 0.03);
    }
    .metadata {
      padding-left: 1.4em;
      position: relative;
      z-index: 2;
    }
    h2 {
      font-family: 'Oswald', monospace;
      margin: 0;
      font-size: 3em;
    }
    p {
      margin: 0;
      text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
    }

    .fade-enter-active {
      transition: opacity 600ms ease-out;
    }
    .fade-leave-active {
      opacity: 0;
    }
    .fade-enter,
    .fade-leave-to {
      opacity: 0;
    }

    .status span {
      opacity: 0.7;
      font-size: 0.8em;
      padding: 1em 0;
      display: block;
      white-space: nowrap;
    }
    .is-playing span {
      opacity: 0;
      transition: opacity 600ms ease-out;
    }

    @media (max-width: 600px) {
      section {
        grid-template-rows: 42% 58%;
        grid-template-columns: 100%;
      }
      aside {
        max-width: 160px;
        margin: 0 auto;
      }
      .metadata {
        text-align: center;
    padding: 0;
      }
    }
</style>

components/NowPlaying.vue

section is a display-type grid that keeps the album art and song metadata in two columns, and then on viewports up to 600px wide (the layout switches to two rows).

Progress

Now let’s build our Progress component. A simple solution is a bar using the width of a

:

I wanted to do something a bit different, so I’ve built a square out in SVG:

<template>
  
</template> export default { props: ['progressPercent', 'image'] } <style scoped> div { filter: grayscale(0); transform: rotate(-2deg) scale(0.9); } .is-paused { filter: grayscale(80%); transition: all 600ms ease-out; } svg { height: 100%; width: 100%; } svg.album { filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.3)); } svg.progress { position: absolute; top: 0; left: 0; right: 0; bottom: 0; filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.2)) drop-shadow(0 0 2px var(--colorBrightBlue)) drop-shadow(0 0 3px var(--colorBrightBlue)) drop-shadow(0 0 5px var(--colorBrightBlue)) opacity(65%) contrast(150%); } .bar { stroke: url(#gradient); stroke-width: 0.03em; transform: rotate(0deg); transform-origin: center; animation: fill 2s reverse; } .image { fill: url(#image); } @keyframes fill { to { stroke-dasharray: 0 100; } } </style>

components/Progress.vue

Above, we create two rect SVGs. One has a pattern fill of our image, the other is the progress bar. It’s important that whatever shape you use has a total perimeter of 100. This allows us to use the stroke-dasharray to fill the space based on a percentage. The left value is the length of the stroke, the right value is the space between the strokes. The stroke size getting larger pushes the space out of the frame and eventually is the entire length of the perimeter. We added an animation that fills the progress bar from 0 to its current point when the component is rendered.

Head to localhost:3000 and if we did everything right (and you’re playing a song) we should see something like this:


An image depicting the final product of the tutorial - contains the information for the song “Intro” by The XX with a link to the song on Spotify, and a photo of the album cover
Spotify player component finished (Large preview)

Awesome! 🙌

Publishing Our Application

Let’s get everything up into our repository!

$ git add .
$ git commit . -m 'Adds Nuxt application 🎉'
$ git push
[master b63fb2d] Adds Nuxt application 🎉.

If you look into your Heroku dashboard and look at the activity feed on the right-hand panel, there should be a build and a deployment:


A screenshot of the Heroku dashboard activity feed showing the build triggered by our auto-deployment - the commit ID from Github from the most recent merge is shown, along with links to the code diff and the build log
Deployment success! (Large preview)

If everything looks good, open your site!

$ heroku open

Log in with Spotify on production and start sharing your jam sessions!

🎉

Conclusion

Phew! We built a universal, server-side rendered application, wrote an API proxy on our server, connected to a Redis cache, and hosted on our application on Heroku. That’s pretty awesome!

Now that we know how to build an application using Nuxt, and have an understanding of what kind of data we should handle securely on the server, the possibilities for interesting applications are endless!

Build On Your Knowledge

Spotify’s API has a medley of endpoints to add more interesting experiences to the application we built, or for composing entirely new ones! You can fork my repository to explore some other components I’ve coded, or read through the docs and apply what you’ve learned to share more musical ideas!

Further Reading on SmashingMag:

Smashing Editorial
(rb, ra, il)

Source: Smashing Magazine, Creating A Spotify-Powered App Using Nuxt.js

Designing An Aspect Ratio Unit For CSS

dreamt up by webguru in Uncategorized | Comments Off on Designing An Aspect Ratio Unit For CSS

Designing An Aspect Ratio Unit For CSS

Designing An Aspect Ratio Unit For CSS

Rachel Andrew



One of the things that comes up again and again in CSS is the fact that there is no way to size things based on their aspect ratio. In particular when working with responsive designs, you often want to be able to set the width to a percentage and have the height correspond to some aspect ratio. This is something that the body responsible for designing CSS – the CSS Working Group (CSSWG) have been discussing recently and, at the recent CSSWG meeting in San Francisco a proposed solution was agreed on.

This is a new resolution so we have no browser implementations yet, but I thought it worth writing up the proposal, in case anyone in the wider web community could see a showstopping issue with it. It also gives something of an insight into the work of the CSSWG and how issues like this are discussed, and new features designed.

What Is The Problem We Are Trying To Solve?

The issue in in regard to non-replaced elements, which do not have an intrinsic aspect ratio. Replaced elements are things like images or a video placed with the <video> element. They are different to other boxes in CSS as they have set dimensions, and their own behavior. These replaced elements are said to have an intrinsic aspect ratio, due to them having dimensions.

A div or some other HTML element which may contain your content has no aspect ratio, you have to give it a width and a height. There is no way to say that you want to maintain a 16 / 9 aspect ratio, and that whatever the width is, the height should be worked out using the given aspect ratio.

A very common situation is when you want to embed an iframe in order to display a video from a video sharing site such as YouTube. If you use the <video> element then the video has an aspect ratio, just like an image. This isn’t the case if the video is elsewhere and you are using an embed. What you want is for your video to be responsive, yet remain at the correct aspect ratio for the video. What you get however, if you set width to 100%, is the need to then set a height. Your video ends up stretched or squished.

Let’s also look at a really simple case of creating a grid layout with square cells. If we were using fixed column track sizes then it is easy to get our square cells as we can define rows to be the same size as the column tracks. We could also make our row tracks auto sized, and have items and set the height on the items.

The problem comes when we want to use auto-fill, and fill a container with as many column tracks as will fit. We now can’t simply give the items a height, as we don’t know what the width is. Our items are no longer square.

Being able to size things based on their aspect ratio would mean we could calculate the correct aspect ratio once the grid item is laid out. Thus making the grid items as tall as they are wide, so that they always maintain as a square no matter what their width.

Current Aspect Ratio Solutions

Web developers have been coping with the lack of any aspect ratio in CSS in various ways, the main one being the “padding hack”. This solution uses the fact that padding % in the block direction (so top and bottom padding in a horizontal top to bottom language) is calculated from the inline size (width).

The article Aspect Ratio Boxes on CSS Tricks has a good rundown of the current methods of creating aspect ratio boxes. The padding hack works in many cases but does require a bunch of hoops to jump through in order to get it working well. It’s also not in the slightest bit intuitive – even if you know why and how it works. It’s those sort of things that we want to try and solve in the CSS Working Group. Personally I feel that the more we get elegant solutions for layout in CSS, the more the messy hacks stand out as something we should fix.

For the video situation you can use JavaScript, and a very popular solution is to use FitVids, as also described in the CSS Tricks article. Using JavaScript is a reasonable solution but it’s more script to load, and also something else to remember to do. You can’t simply plonk a video into your content and it just work.

The Proposed Solution

What we are looking for is a generic solution that will work for regular block layout, such as a video in an iframe, or a div on the page. It should also work if the item is a grid or flex item. There is a different issue of wanting grid tracks to maintain an aspect ratio (having the row tracks match the columns), this solution would fix many cases however where you might want that. You would be working from the item out rather than the track in however.

The soluion will be part of the CSS Sizing Specification, and is being written up in the CSS Sizing 4 specification. This is the first step for new features being designed by the CSS Working Group, the idea is discussed, and then written up in a specification. An initial proposal for this feature was brought to the group by Jen Simmons, and you can see her slide deck which goes through many of the use cases discussed in this article here.

The new property introduced to the Sizing specification is be the aspect-ratio property. This property will accept a value which is an aspect ratio such as 16/9. For example, if you want a square box with the width the same as the height you would use the following:

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

For a 16 / 9 box, such as for a video:

.box {
  width: 100%;
  height: auto;
  aspect-ratio: 16/9;
}

For the example with the square items in a grid layout, we leave our grid tracks auto sized, which means they will take their size from the items, we then make our items sized with the aspect-ratio unit.

.grid {
    display: grid;
    grid-template-columns: repeat(autofill, minmax(200px, 1fr));
}

.item {
    aspect-ratio: 1/1;
}

Features often go through various iterations before browsers start to implement them. Having discussed the need for an aspect ratio unit previously, this time we were looking at one particular concern around the proposal. What happens if you specify an aspect ratio box, but then add too much content to the box? This same issue is brought up in the CSS Tricks article about the padding hack, with equally unintuitive solutions required to fix it.

Dealing With Overflow

What we are dealing with here is overflow, as is so often the case on the web. We want to have a nice neatly sized box, our design asks for a nice neatly sized box, our content is less well behaved and turns out to be bigger than we expected and breaks out of the box. In addition to specifying how we ask for an aspect ratio in one dimension, we also have to specify what happens if there is too much content, and how the web developer can tell the browser what to do about that overflow content.

There is a general design principal in CSS that we try to avoid data loss. Data loss in a CSS context is where some of your content vanishes. That might either be because it gets poked off the side of the viewport, or is cropped when it overflows. It’s generally preferable to have a messy overflow, as you will notice it and do something about it. If we cause something to vanish, you may not realise, especially if it only happens at one breakpoint.

We have a similar issue in grid layout which is nicely fixed with the minmax() function for track sizes. You can define grid tracks with a fixed height using a length unit. This will give you a lovely neat grid of boxes, however as soon as someone adds more content than you expected into one of those boxes, you will get overflow.

The fix for this in grid layout is to use minmax() for your track size, and make the max value auto. In this case auto can be thought of as “big enough to fit the content”. What you then get is a set of neat looking boxes that, if more content that expected gets in, grow to accept that content. Infinitely better than a messy overflow or cropped content. In the example below you can see the first row with the box with extra content has grown, the second row is sized at 100 pixels.

We need something similar for our aspect ratio boxes, and the suggestion turns out to be relatively straightforward. If you do nothing about overflow, then the default behavior will be that the content is allowed to grow past the height that is inferred by the aspect ratio. This will give you the same behavior as the grid tracks sized with minmax(). In the case of a height, the height will be at least the height defined by the aspect ratio, but if the content is taller, the height can grow to fit it.

If you don’t want that, then you can change the value of overflow as you would normally do. For example hiding the overflow, or allowing it to scroll.

Commenting On Proposals In Progress

I think that this proposal covers the use cases detailed in the CSS Tricks article, and the common things that web developers want to do. It gives you a way to create aspect ratio sized boxes in various layout contexts, and is robust. It will cope with the real situation of content on the web, where we don’t always know how much content we have or how big it is.

If you spot some real problem with this, or have some other use case that you think can’t be solved, you can comment on the proposal directly by raising an issue at the CSSWG GitHub repo. If you don’t want to do that you can always comment here, or post to your own blog and link to it here so I can see it. I’d be very happy to share your thoughts with the working group as this feature is discussed.

Smashing Editorial
(il)

Source: Smashing Magazine, Designing An Aspect Ratio Unit For CSS

Can You Make More Money With A Mobile App Or A PWA?

dreamt up by webguru in Uncategorized | Comments Off on Can You Make More Money With A Mobile App Or A PWA?

Can You Make More Money With A Mobile App Or A PWA?

Can You Make More Money With A Mobile App Or A PWA?

Suzanne Scacca



Let’s be honest. The idea behind building mobile apps, websites or any other branded platforms online is to make money, right? Your clients have contacted you to do this for them in order to maximize their results and, consequently, their profits. If money didn’t matter, they’d use a free website builder tool to throw something — anything — up there and you’d no longer be part of the equation.

Money does matter, and if your clients don’t see a huge return on their investment in an app, it’s going to be quite difficult to sustain a business built around designing apps.

Today, I’m going to talk about why app monetization needs to be one of the first things you think about before making a choice between designing a mobile app or PWA for your clients. And why the smartest thing you can do right now is to steer profit-driven clients to a PWA.

Your Guide To Progressive Web Apps

There are a lot of pain points that users face when browsing old non-PWA websites. Make sure you’re familiar with important technologies that make for cool PWAs, like service workers, web push notifications and IndexedDB. Read more  →

PWA vs. Mobile App Monetization: Consider This

I’ve been watching the MoviePass app closely since it came out. Part of me wanted to hop aboard and start reaping the benefits of the too-good-to-be-true movie app’s promise, but part of me just didn’t see how that kind of business model could be viable or sustainable.

For starters, the subscription service was way underpriced. I realize the makers of the app hoped that many users wouldn’t use their subscriptions to the fullest, or at all, which would then drive profits up on their end. They had a similar suspicion regarding the amount of data they’d be able to mine from users and sell to advertisers and marketers. But, in 2019, that all seems to have been faulty logic with the app in a major downward spiral of profit loss.

It just goes to show you that no matter how popular your mobile app may be, it’s really difficult to make a profit with one. Now, “difficult” does not mean “impossible”. There are certainly mobile apps that make incredible amounts of money. But just because you can make money with a mobile app, does it mean it’s the smartest option for your client? If your client’s end users are craving a convenient, fast and intuitively designed app experience, couldn’t you give them a PWA instead?

If you look at the big picture, you’ll find that there’s a greater opportunity to make money (and, not only that, make a profit) with a PWA when compared to a mobile app.

Let’s explore how we get to that point and how you can use these calculations to determine what the best option is for your client.

  1. The Cost To Build
  2. The Cost To Maintain
  3. The Cost To Acquire Users
  4. The Cost To Monetize

#1: The Cost To Build

Building an app is no easy feat, whether it be a native mobile app or PWA. According to Savvy, there are three tiers of mobile app development options:


Savvy app cost estimates
Savvy breaks down app building costs into three categories (Source: Savvy) (Large preview)

According to Savvy, small development shops may charge up to $100,000 to build an app. App development agencies can charge up to $500,000. And those targeting enterprises may bill up to $1,000,000.

That said, PWAs aren’t cheap either.

Give Otreva’s “How Much to Build an App” calculator a try. These are the estimated costs I received (top-right corner) to build an e-commerce mobile app that’s feature-rich:


Otreva calculator mobile app calculations
Otreva calculates the cost of building an ecommerce app to be $356k. (Source: Otreva Calculator) (Large preview)

Compare that to the estimated costs to build a progressive web app with the same exact features:


Otreva calculator PWA calculations
Otreva calculates the cost of building an ecommerce app to be $346k. (Source: Otreva Calculator) (Large preview)

Although the costs here aren’t too far apart, I don’t suspect that to be the case when building less robust apps for clients. As you decrease the amount of features included, you’re likely to find that the gap between the cost of mobile apps and PWAs grows.

Even so, let’s say what you plan to build is comparable in pricing regardless of which app you choose. Keep in mind that these calculators don’t take into consideration the cost of building out the backend server environment (which is something a PWA doesn’t need). Plus, when you compare the timeline of developing a mobile app against a PWA, mobile apps will almost always take longer as you have to build an app for each of the stores you want it to appear in.

So, when you consider the upfront costs of building an app, be sure to look a bit more closely at everything involved. At some point, the revenue you generate is going to have to make up for that investment (i.e. loss).

#2: The Cost To Maintain

Software of any kind must be updated regularly — as does anything you build with it. That’s because designs go stale, security bugs require patches and performance can always be improved. But the way you manage and maintain mobile apps vs. PWAs is incredibly different.

BuildFire has a great roundup of the hidden costs that come with having a mobile app. In it, author Ian Blair shares the most expensive maintenance costs associated with apps:


Hidden mobile app costs
BuildFire estimates the most expensive mobile app hidden costs. (Source: BuildFire) (Large preview)

Some of these will certainly overlap with PWAs. However, take a look at these three that are specific to mobile apps:

  • App update submissions = $2,400
  • iOS and Android updates = $10,000
  • Servers = $12,000

That’s why you’ll find that most estimates put the cost of annual mobile app maintenance at about 20% of the original upfront cost to build it.

One thing that’s missing from this breakdown is the time-cost of maintaining a mobile app. Because not only are updates costly to manage, but they take a while to happen, too, as app stores have to review any changes to the software or content you’re attempting to push through.

PWAs are significantly easier and cheaper to maintain as they’re web-based applications. So, it’s not all that different from what you would do to keep a website up-to-date.

Yes, the surrounding web hosting architecture, SSL certificate, payment gateways and other integrated technology will require monitoring and maintenance. However, a lot of that stuff is managed by the provider itself. Most of what you have to concern yourself with in terms of maintaining a PWA is the update piece.

When the underlying software has an update available or you simply want to make a change to the content of the PWA, you can push it through to your site (after testing on a staging server first, of course). There’s no app store process you have to follow or to wait for approval from. Changes immediately go live.

Recommended reading: Native And PWA: Choices, Not Challengers!

#3: The Cost To Acquire Users

Once you have a handle on how much the app itself costs, it’s time to wrap your head around the cost of customer acquisition. This is where we’ll start to see PWAs pull far ahead of mobile apps.

For example, here are all the things you have to do in order to acquire users for a mobile app:

Get An App Store Membership

Pay the $99/year Apple Developer Program membership fee or pay the $25 one-time fee to create a Google Play Developer account. You can’t publish to the stores without them.

In-Depth Market Testing

Because a mobile app is such an expensive investment, you can’t afford to throw something into the app store without first doing in-depth audience research and beta testing.

This means looking at the current app market to see if there’s even a need or room for your mobile app. Then, study the target audience and how they’re likely to stumble upon it and convert. Once you have a good hypothesis in place, beta testing will be key to ensure you have a viable strategy in place. (It’ll also be quite expensive, too.)

Decide On A Customer Acquisition Model

Getting someone to install your app from an app store is one thing. Getting users to become an actual customer is another. If you haven’t done so already, figure out what sort of action you’ll require of them before you’re willing to call them a “customer”.

Statista’s 2017–2018 data on the average mobile app user acquisition costs might have you reconsidering your original choice though:


Mobile app customer acquisition costs
Statista presents estimates for the cost of mobile app customer acquisition. (Source: Statista) (Large preview)

Not only is there a great discrepancy between acquiring a user who’s willing to install your app and someone who’s willing to pay for a subscription, but there’s also a large discrepancy between the cost of converting Android vs. iOS users.

You might find that the monetization model you had hoped to use just won’t pay off in the end. (More on that down below.)

App Store Optimization

Publishing a mobile app to an app store isn’t enough to guarantee users will want to install it. You have to drive traffic to it within each app store.

If you don’t have a tool that’ll help you write descriptions and metadata for the listing, you’ll need to hire a copywriter or agency who can help (and they’re not cheap). Plus, don’t forget about the screenshots of the app in action. There’s still a bit of work to do before you can get that app out to the app stores for review and approval.

Build A Website

Yep, that’s right. Even though your client has spent all this money to build a mobile app, they’re still going to need a website when all is said and done. It’s not going to be a duplicate of the app store though. All they really need is a high-converting landing page that’ll rank in search, bring attention to the app and help drive engaged leads to it.

That said, websites cost money. You’ll need a domain name, web hosting, SSL certificate and perhaps a premium theme or plugin to design it.

Get Good Press

Because you can’t leverage regular ol’ search marketing to drive traffic to your app (since there’s no link to share), you have to rely on online publications and influencers to talk it up on your behalf. You should also be doing this on your own through social media. The only thing is, organic social media marketing takes time.

If you want good press for your mobile app, you’ll have to use paid social ads, search ads and affiliate relationships to help you spread the word quickly.

Retention Rate Optimization

One final customer acquisition cost to factor in is retention rate optimization. As we’ve seen before, all it takes is 30 days for a mobile app to lose up to 90% of its user base. If you’re not continually evaluating the quality of your app and refining it to create a better experience, you might as well double the cost of customer acquisition now.

Consumers, in general, aren’t as eager to spend money with new brands and definitely don’t spend as much as long-time customers do. If you don’t have a plan to develop ways to breed loyalty with current ones, your mobile app is going to bleed a lot of money along the way.

On the other hand, there’s a lot less you must do to acquire users for a progressive web app:

Search Engine Optimization

A PWA is already on the web, so there’s no need to build an additional website to promote it. All you need to worry about now is optimizing it for search. You could do this on your own with free keyword tools and SEO plugins.

However, it’s probably worth investing in an SEO pro or agency if you’re trying to get the app to the top of search ASAP.

Paid Promotions

There’s no need to go to the extent of a mobile app with press pitches, affiliate links or influencer marketing. Instead, you can use paid ads on social media and Google (all within a reasonable budget) to increase the presence of your PWA in search.

Leverage The “Add To Homescreen” Button

Unlike mobile apps which need users to find them within app stores, PWAs are searchable. However, if you’re trying to retain these users and convert them into customers, your best bet is to put the “Add to Homescreen” button in front of them like The Weather Channel does.


Weather Channel PWA add to homescreen
The Weather Channel asks visitors to add the PWA to the home screen. (Source: The Weather Channel) (Large preview)

All it takes it one click and they’ll have instant access to your PWA right from their mobile homescreen.

#4: The Cost To Monetize

That doesn’t make sense, does it? The “cost” to monetize? Sadly, it does.

Before I explain the costs, let’s discuss the kinds of monetization that are available for each.

Mobile App Monetization

Paid apps are ones that are completely gated off unless a user pays for subscription access. The New York Times does this, though it gives users a handful of articles to read for free to give them a taste of what they’re missing.


New York Times app subscription
The New York Times app is subscription only. (Source: The New York Times) (Large preview)

Freemium apps are ones that are mostly free, but ask for payment to access certain parts of the app. An example of this is Jackpot Magic Slots, which allows users to create competitive clubs like this one which requires “member” funding:


Jackpot Magic Slots club funding
Jackpot Magic Slots enables users to create clubs that receive funding. (Source: Jackpot Magic Slots) (Large preview)

The catch is that users will inevitably need to purchase coins or spend a lot of time gambling in the app in order to afford those funding fees. So, Jackpot Magic Slots is indirectly making money off of its users.

In-app purchase apps are ones that allow unfettered access to the app. However, they ask for payment from users that want to upgrade their experience with in-app currency, premium features and more. Words with Friends sells Power Ups, Premiums and Coins to customers who want to get more out of their gameplay.


Words with Friends upgrades
Words with Friends charges for in-app upgrades. (Source: Words with Friends) (Large preview)

Sponsored content apps are ones that publish sponsored ads and content to generate revenue. Facebook, of course, is a master of this seeing as how it’s nearly impossible for businesses to get in front of users otherwise:


Facebook business ads
Facebook is basically a pay-to-play platform for businesses. (Source: Facebook) (Large preview)

Ad-free apps are ones that accept payment to remove intrusive ads from getting in the way of the app interface.

eCommerce apps are ones that sell goods through their own payment gateways as Fashion Nova does:


Fashion Nova ecommerce
Fashion Nova has a mobile app store, too. (Source: Fashion Nova) (Large preview)

Free apps are just what they sound like. However, they aren’t typically available to the public at large. Instead, loyalty users, enterprise customers and others who pay for a premium service in person or online gain access for free.

There’s another way free apps make money and that’s to reward users for referring others to it as is the case with Wordscapes:


Wordscapes referral rewards
Wordscapes rewards users for inviting others to join the app. (Source: Wordscapes) (Large preview)

It might not lead directly to cash in the bank for the app, but it does increase the amount of word-of-mouth referrals which tend to be more valuable in the long run anyway.

The Cost…
As great as all these monetization methods are, there are two big things to note here in terms of what mobile app monetization is going to cost you:

Mobile app stores take a portion of money earned through your app. More specifically, app stores take 30% of your earnings.

This becomes obvious when you compare app store revenues:


Mobile app store revenue
Statista tracks mobile app store revenue trends from 2015 to 2020. (Source: Statista) (Large preview)

Against mobile app revenues:


Mobile app revenue
Statista tracks mobile app revenue trends from 2015 to 2020. (Source: Statista) (Large preview)

Note that the app store revenues shown above are about a third of total mobile app revenues. So, your earnings with a mobile app are more like 70% of your projected total earnings.

Another monetization “cost” you have to think about is the fact that app stores don’t pay you out right away.

According to Apple:

Payments are made within 45 days of the last day of the month in which book purchases were made. To receive payment, you must have provided all required banking and tax information and documentation, as well as meeting the minimum payment threshold.

Not only that, but you have to meet a certain minimum threshold. If your app doesn’t generate over a certain limit based on which country you operate out of, you might have to wait longer.

According to Google:

In many cases, Google will initiate a payment on the 15th day of each month or on the next business day, if your bank account has been verified and you’ve reached a minimum balance, which varies by region.

Google’s minimum threshold is much higher than Apple’s, so you could end up waiting even longer to get paid your app earnings.

In sum, not only are you paying the app stores a membership fee and letting them take a good chunk of your earnings, but you’re paying with your time as well.

PWA Monetization

Subscriptions: Just like mobile apps, PWAs can sell premium access. The Financial Times is an online newspaper that sells premium access to its stories through its PWA:


Financial Times PWA subscription
Financial Times has a PWA that’s subscription-only. (Source: Financial Times) (Large preview)

Freemium access: Since you’re not apt to find a lot of gaming apps as PWAs, freemium access won’t come in the form of things like in-app upgrades. Instead, you’ll see examples like The Billings Gazette which offer subscriptions for a more streamlined news-reading experience:


Billing Gazette monetizes a streamlined experience
The Billings Gazette offers survey-free articles for a subscription. (Source: The Billings Gazette) (Large preview)

Advertising: Ads have been a part of the web’s monetization model for a long time now, so it would be odd for PWAs to ignore this obvious choice. Forbes is one such example that uses a lot of advertising on its PWA:


Forbes PWA ads
Forbes makes the most of its ad space on its PWA. (Source: Forbes) (Large preview)

Affiliate marketing is another way to collect ad revenue with PWAs.

eCommerce: Traditional ecommerce sales can take place on PWAs, especially since an SSL certificate is required in order to have one. Debenhams is a nice example of a PWA that sells products online through a PWA to generate revenue.


Debenhams ecommerce PWA
Debenhams attracts mobile shoppers with its ecommerce PWA. (Source: Debenhams) (Large preview)

But that’s not all. Any kind of business can easily convert its website into a PWA and continue selling its products, services, and downloadables. eCommerce monetization is available to everyone.

The Cost…
Compared to how many ways you can earn money with a mobile app, this might seem like a tawdry list. But here’s the thing:

When you make money with a PWA, it’s yours to keep. That is, aside from any affiliate commissions or e-commerce gateway fees you may owe. But neither of those come close to the 30% take the app stores claim.

Additionally, if you’re helping your client make the move from website to PWA (which is much more seamless than website to native app), you can expect a major leap in revenue generation almost right away.

Think with Google interviewed Mobify CEO Igor Faletski to see what sort of monetization trends his company has noticed when it comes to PWAs. Here’s what he said:

Not only can a PWA provide your customers with a richer mobile experience sooner, it can deliver a faster return on investment. And that ROI can potentially offset the cost and risk of the larger digital transformation project.
Our customers typically see a 20% revenue boost with a PWA, so every minute you don’t have a PWA is a minute spent with 20% less revenue on your busiest customer touchpoint. As an example, a retailer with $20 million in annual e-commerce revenue could lose $1.4 million by waiting a month to offer a PWA and another $6.8 million by waiting for six months.


PWA possible earnings timeline
Think with Google shows how much money you can earn if you launch a PWA today. (Source: Think with Google) (Large preview)

Want to see a real-life example of how businesses are earning more money by creating a progressive web app? Check out this story about JM Bullion.

Thanks to the major increase in speed with its PWA:

JMBullions.com’s smartphone conversion rate is 28% higher this month compared with the month prior to switching over.

Wrapping Up

Before you go rushing out to build a mobile app for your clients, think about the kind of ROI they can realistically expect compared to a PWA.

The upfront costs and ongoing maintenance of a native mobile app are huge. If your client isn’t generating huge sums of money right away (or even before launch), the app itself might not even be a sustainable venture.

However, if you look at the PWA counterpart, not only is it less expensive to build and maintain, but the turnaround times ensure that cash will start to flow in sooner rather than later. Plus, since PWAs are web-based, there’s really no secret to how much work is involved in optimizing them for search or marketing them to the world.

With a shorter learning curve and lower costs, it seems odd to opt for a mobile app when a PWA can provide nearly as good of an experience.

Further Reading on SmashingMag:

Smashing Editorial
(ra, yk, il)

Source: Smashing Magazine, Can You Make More Money With A Mobile App Or A PWA?