Modern Web Development PDF
Modern Web Development PDF
Modern Web Development PDF
DEVELOPMENT
BY FLAVIO COPES
HTTPS://FLAVIOCOPES.COM
JavaScript
Introduction to JavaScript
ECMAScript
Lexical Structure
Variables
Types
Expressions
Arrays
Promises
Template Literals
The Set Data Structure
The Map Data Structure
Loops and Scope
Async and Await
Functional Programming
Web Platform
Progressive Web Apps
Service Workers
Fetch API
Channel Messaging API
Cache API
Push API
Notifications API
IndexedDB
Selectors API
GraphQL
GraphQL
Apollo
CSS
CSS Grid
Flexbox
CSS Variables
PostCSS
How to center things in modern CSS
The CSS margin property
Deployment
Netlify
Firebase Hosting
Tutorials
Make a CMS-based website work offline
Create a Spreadsheet using React
JAVASCRIPT
INTRODUCTION TO THE JAVASCRIPT
PROGRAMMING LANGUAGE
JavaScript is one of the most popular programming languages in the
world.
Introduction
Basic definition of JavaScript
JavaScript versions
Introduction
JavaScript is one of the most popular programming languages in the world.
Created in 20 years ago, it’s gone a very long way since its humble
beginnings.
Being the first - and the only - scripting language that was supported
natively by web browsers, it simply sticked.
With the growing needs that the web platform demands, JavaScript had the
responsibility to grow as well, to accomodate the needs of one of the most
widely used ecosystem of the world.
Many things were introduced in the platform, with browser APIs, but the
language grew quite a lot as well.
JavaScript is now widely used also outside of the browser. The rise of
Node.js in the last few years unlocked backend development, once the
domain of Java, Ruby, Python and PHP and more traditional server-side
languages.
JavaScript is now also the language powering databases and many more
applications, and it’s even possible to develop embedded applications,
mobile apps, TV sets apps and much more. What started as a tiny language
inside the browser is now the most popular language in the world.
JavaScript versions
Let me introduce the term ECMAScript here. We have a complete guide
dedicated to ECMAScript where you can dive into it more, but to start
with, you just need to know that ECMAScript (also called ES) is the name
of the JavaScript standard.
For a very long time, the version of JavaScript that all browser ran was
ECMAScript 3. Version 4 was cancelled due to feature creep (they were
trying to add too many things at once), while ES5 was a huge version for
JS.
Since then, the ones in charge decided to release one version per year, to
avoid having too much time idle between releases, and have a faster
feedback loop.
Introduction
Current ECMAScript version
When is the next version coming out?
What is TC39
ES Versions
ES Next
ES2015 aka ES6
Arrow Functions
A new this scope
Promises
Generators
let and const
Classes
Constructor
Super
Getters and setters
Modules
Importing modules
Exporting modules
Multiline strings
Template Literals
Default parameters
Destructuring assignments
Enhanced Object Literals
Simpler syntax to include variables
Prototype
super()
Dynamic properties
For-of loop
Map and Set
ES2016 aka ES7
Array.prototype.includes()
Exponentiation Operator
ES2017 aka ES8
String padding
Object.values()
Object.entries()
getOwnPropertyDescriptors()
In what way is this useful?
Trailing commas
Async functions
Why they are useful
A quick example
Multiple async functions in series
Shared Memory and Atomics
Introduction
Whenever you read about JavaScript you’ll inevitably see one of these
terms:
ES3
ES5
ES6
ES7
ES8
ES2015
ES2016
ES2017
ECMAScript 2017
ECMAScript 2016
ECMAScript 2015
ECMAScript is the standard upon which JavaScript is based, and it’s often
abbreviated to ES.
Beside JavaScript, other languages implement(ed) ECMAScript, including:
What is TC39
Every standard version proposal must go through various stages, which are
explained here (https://tc39.github.io/process-document/) .
ES Versions
Why does this happen? During the process that led to ES2015, the name was
changed from ES6 to ES2015, but since this was done late, people still
referenced it as ES6, and the community has not left the edition naming
behind - the world is still calling ES releases by edition number.
ES Next
So at the time of writing, ES8 has been released, and ES.Next is ES9
Since this long time passed, the release is full of important new features
and major changes in suggested best practices in developing JavaScript
programs. To understand how fundamental ES2015 is, just keep in mind that
with this version, the specification document went from 250 pages to ~600.
Arrow functions
Promises
Generators
let and const
Classes
Modules
Multiline strings
Template literals
Default parameters
Destructuring assignment
Enhanced object literals
The for..of loop
Map and Set
Arrow Functions
Arrow functions since their introductions changed how most JavaScript code
looks (and works).
to
The this scope with arrow functions is inherited from the context.
Promises
Promises have been used by JavaScript developers well before ES2015, with
many different libraries implementations (e.g. jQuery, q, deferred.js,
vow…), and the standard put a common ground across differences.
setTimeout(function() {
console.log('I promised to run after 1s')
setTimeout(function() {
console.log('I promised to run after 2s')
}, 1000)
}, 1000)
as
wait().then(() => {
console.log('I promised to run after 1s')
return wait()
})
.then(() => console.log('I promised to run after 2s'))
Generators
The code decides that it has to wait, so it lets other code “in the queue”
to run, and keeps the right to resume its operations “when the thing it’s
waiting for” is done.
All this is done with a single, simple keyword: yield . When a generator
contains that keyword, the execution is halted.
function *calculator(input) {
var doubleThat = 2 * (yield (input / 2))
var another = yield (doubleThat)
return (input * doubleThat * another)
}
We initialize it with
calc.next()
This first iteration starts the iterator. The code returns this object:
{
done: false
value: 5
}
What happens is: the code runs the function, with input = 10 as it was
passed in the generator constructor. It runs until it reaches the yield ,
and returns the content of yield : input / 2 = 5 . So we got a value of
5, and the indication that the iteration is not done (the function is just
paused).
calc.next(7)
{
done: false
value: 14
}
We then reach the second yeld, and that returns doubleThat , so the
returned value is 14 .
calc.next(100)
As the iteration is done (no more yield keywords found) and we just return
(input * doubleThat * another) which amounts to 10 * 14 * 100 .
Classes
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
Classes do not have explicit class variable declarations, but you must
initialize any variable in the constructor.
Constructor
Super
class Person {
get fullName() {
return `${this.firstName} ${this.lastName}`
}
}
class Person {
set age(years) {
this.theAge = years
}
}
Modules
AMD
RequireJS
CommonJS
Importing modules
Exporting modules
You can write modules and export anything to other modules using the
export keyword:
Multiline strings
Template Literals
string
is awesome!`
Default parameters
Destructuring assignments
Given an object, you can extract just some values and put them into named
variables:
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const a = [1,2,3,4,5]
[first, second, , , fifth] = a
Instead of doing
you can do
Prototype
super()
Dynamic properties
const x = {
['a' + '_' + 'b']: 'z'
}
x.a_b //z
For-of loop
ES5 back in 2009 introduced forEach() loops. While nice, they offered
no way to break, like for loops always did.
Map and Set (and their respective garbage collected WeakMap and WeakSet)
are the official implementations of two very popular data structures.
Compared to ES6, ES7 is a tiny release for JavaScript, containing just two
features:
Array.prototype.includes
Exponentiation Operator
Array.prototype.includes()
With ES6 and lower, to check if an array contained an element you had to
use indexOf , which checks the index in the array, and returns -1 if
the element is not there.
if (![1,2].indexOf(3)) {
console.log('Not found')
}
if (![1,2].includes(3)) {
console.log('Not found')
}
Exponentiation Operator
Math.pow(4, 2) == 4 ** 2
String padding
Object.values
Object.entries
Object.getOwnPropertyDescriptors()
Trailing commas in function parameter lists and calls
Async functions
Shared memory and atomics
String padding
padStart(targetLength [, padString])
padEnd(targetLength [, padString])
Sample usage:
padStart()
‘test’.padStart(4) ‘test’
‘test’.padStart(5) ’ test’
‘test’.padStart(8) ’ test’
padEnd()
‘test’.padEnd(4) ‘test’
‘test’.padEnd(5) ‘test ‘
‘test’.padEnd(8) ‘test ‘
Object.values()
This method returns an array containing all the object own property
values.
Usage:
Object.entries()
This method returns an array containing all the object own properties, as
an array of [key, value] pairs.
Usage:
const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]
getOwnPropertyDescriptors()
However there is a problem with that, because it does not correctly copies
properties with non-default attributes.
If an object for example has just a setter, it’s not correctly copied to a
new object, using Object.assign() .
const person1 = {
set name(newName) {
console.log(newName)
}
}
const person2 = {}
Object.assign(person2, person1)
const person3 = {}
Object.defineProperties(person3,
Object.getOwnPropertyDescriptors(person1))
person1.name = 'x'
"x"
person2.name = 'x'
person3.name = 'x'
"x"
The same limitation goes for shallow cloning objects with Object.create().
Trailing commas
doSomething('test2', 'test2',)
This change will encourage developers to stop the ugly “comma at the start
of the line” habit.
Async functions
ES2017 introduced the concept of async functions, and it’s the most
important change introduced in this ECMAScript edition.
A quick example
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000)
})
}
console.log('Before')
doSomething()
console.log('After')
The above code will print the following to the browser console:
Before
After
I did something //after 3s
Async functions can be chained very easily, and the syntax is much more
readable than with plain promises:
function promiseToDoSomething() {
return new Promise((resolve)=>{
setTimeout(() => resolve('I did something'), 10000)
})
}
watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => {
console.log(res)
})
They offer a messaging protocol via events. Since ES2017, you can create a
shared memory array between web workers and their creator, using a
SharedArrayBuffer .
Since it’s unknown how much time writing to a shared memory portion takes
to propagate, Atomics are a way to enforce that when reading a value, any
kind of writing operation is completed.
Unicode
Semicolons
White space
Case sensitive
Comments
Literals and Identifiers
Reserved words
Unicode
JavaScript is written in Unicode. This means you can use Emojis as
variable names, but more importantly, you can write identifiers in any
language, for example Japanese or Chinese.
Semicolons
JavaScript has a very C-like syntax, and you might see lots of code
samples that feature semicolons at the end of each line.
Semicolons are’t mandatory, and JavaScript does not have any problem in
code that does not use them, and lately many developers, especially those
coming from languages that do not have semicolons, started avoiding using
them.
You just need to avoid doing strange things like typing statements on
multiple lines
return
variable
or starting a line with parentheses ( [ or ( ) and you’ll be safe 99.9% of
the times (and your linter will warn you).
White space
JavaScript does not consider white space meaningful. Spaces and line
breaks can be added in any fashion you might like, even though this is in
theory.
In practice, you will most likely keep a well defined style and adhere to
what people commonly use, and enforce this using a linter or a style tool
such as Prettier.
Case sensitive
JavaScript is case sensitive. A varible named something is different
from Something .
Comments
You can use two kind of comments in JavaScript:
/* */
//
The first can span over multiple lines and needs to be closed.
The second comments everything that’s on its right, on the current line.
Literals and Identifiers
We define as literal a value that is written in the source code, for
example a number, a string, a boolean or also more advanced constructs,
like Object Literals or Array Literals:
5
'Test'
true
['a', 'b']
{color: 'red', shape: 'Rectangle'}
Test
test
TEST
_test
Test1
$test
Reserved words
You can’t use as identifiers any of the following words:
break
do
instanceof
typeof
case
else
new
var
catch
finally
return
void
continue
for
switch
while
debugger
function
this
with
default
if
throw
delete
in
try
class
enum
extends
super
const
export
import
implements
let
private
public
interface
package
protected
static
yield
Variables in JavaScript do not have any type attached. Once you assign a
specific literal type to a variable, you can later reassign the variable
to host any other type, without type errors or any issue.
A variable must be declared before you can use it. There are 3 ways to do
it, using var , let or const , and those 3 ways differ in how you can
interact with the variable later on.
Using var
Until ES2015, var was the only construct available for defining
variables.
var a = 0
If you forget to add var you will be assigning a value to an undeclared
variable, and the results might vary.
In modern environments, with strict mode enabled, you will get an error.
In older environments (or with strict mode disabled) this will simply
initialize the variable and assign it to the global object.
If you don’t initialize the variable when you declare it, it will have the
undefined value until you assign a value to it.
var a = 1
var a = 2
You can also declare multiple variables at once in the same statement:
var a = 1, b = 2
Any variable defined into a function with the same name of a global
variable takes precedence over the global variable, shadowing it.
Using let
let is a new feature introduced in ES2015 and it’s essentially a block
scoped version of var . Its scope is limited to the block, statement or
expression where it’s defined, and all the contained inner blocks.
Modern JavaScript developers might choose to only use let and completely
discard the use of var .
Using const
Variables declared with var or let can be changed later on in the
program, and reassigned. A once a const is initialized, its value can
never be changed again, and it can’t be reassigned to a different value.
const a = 'test'
const does not provide immutability, just makes sure that the reference
can’t be changed.
const has block scope, same as let .
Primitive types
Numbers
Strings
Template strings
Booleans
null
undefined
Object types
Primitive types
Primitive types are
Numbers
Strings
Booleans
null
undefined
Numbers
Internally, JavaScript has just one type for numbers: every number is a
float.
A numeric literal is a number represented in the source code, amd
depending on how it’s written, it can be an integer literal or a floating
point literal.
Integers:
10
5354576767321
0xCC //hex
Floats:
3.14
.1234
5.2e4 //5.2 * 10^4
Strings
A string type is a sequence of characters. It’s defined in the source code
as a string literal, which is enclosed in quotes or double quotes
'A string'
"Another string"
"A \
string"
A string can contain escape sequences that can be interpreted when the
string is printed, like \n to create a new line. The backslash is also
useful when you need to enter for example a quote in a string enclosed in
quotes, to prevent the char to be interpreted as a closing quote:
'I\'m a developer'
Template strings
`a string`
`a string
with
${something}`
Booleans
JavaScript defines two reserved words for booleans: true and false. Many
comparision operations == === < > (and so on) return either one or the
other.
They don’t just accept true or false, but also accept truthy and falsy
values.
null
null is a special value that indicates the absence of a value.
undefined
undefined indicates that a variable has not been initialized and the
value is absent.
Object types
Anything that’s not a primitive type is an object type.
Functions, arrays and what we call objects are object types. They are
special on their own, but they inherit many properties of objects, like
having properties and also having methods that can act on those
properties.
JAVASCRIPT EXPRESSIONS
Expressions are units of code that can be evaluated and resolve to a
value. Expressions in JS can be divided in categories.
Arithmetic expressions
String expressions
Primary expressions
Array and object initializers expressions
Logical expressions
Left-hand-side expressions
Property access expressions
Object creation expressions
Function definition expressions
Invocation expressions
Arithmetic expressions
Under this category go all expressions that evaluate to a number:
1 / 2
i++
i -= 2
i * 2
String expressions
Expressions that evaluate to a string:
Primary expressions
Under this category go variable references, literals and constants:
2
0.02
'something'
true
false
this //the current object
undefined
i //where i is a variable or a constant
function
class
function* //the generator function
yield //the generator pauser/resumer
yield* //delegate to another generator or iterator
async function* //async function expression
await //async function pause/resume/wait for completion
/pattern/i //regex
() // grouping
Logical expressions
Logical expressions make use of logical operators and resolve to a boolean
value:
a && b
a || b
!a
Left-hand-side expressions
new //create an instance of a constructor
super //calls the parent constructor
...obj //expression using the spread operator
Property access expressions
object.property //reference a property (or method) of an object
object[property]
object['property']
Invocation expressions
The syntax for calling a function or method
a.x(2)
window.resize()
JAVASCRIPT ARRAYS
Common array operations using every feature available up to ES7
Initialize array
Get length of the array
Iterating the array
Every
Some
Iterate the array and return a new one with the returned result
of a function
Filter an array
Reduce
forEach
for..of
for
@@iterator
Adding to an array
Add a the end
Add at the beginning
Removing an item from an array
From the end
From the beginning
At a random position
Remove and insert in place
Join multiple arrays
Lookup the array for a specific element
Get a portion of an array
Sort the array
Get a string representation of an array
Copy an existing array by value
Copy just some values from an existing array
Copy portions of an array into the array itself, in other positions
JavaScript arrays over time got more and more features, sometimes it’s
tricky to know when to use some construct vs another. This post aims to
explain what you should use, as of 2017.
Initialize array
const a = []
const a = [1,2,3]
const a = Array.of(1,2,3)
const a = Array(6).fill(1); //init an array of 6 items of value 1
Don’t use the old syntax (just use it for typed arrays)
Every
a.every(f)
Some
a.some(f)
Iterate the array and return a new one with the returned result
of a function
const b = a.map(f)
Iterates a and builds a new array with the result of executing f() on
each a element
Filter an array
const b = a.filter(f)
Iterates a and builds a new array with elements of a that returned true
when executing f() on each a element
Reduce
reduce() executes a callback function on all the items of the array and
allows to progressively compute a result. If initialValue is specified,
accumulator in the first iteration will equial to that value.
Example:
// return value is 24
forEach
ES6
a.forEach(f)
Example:
a.forEach(v => {
console.log(v)
})
for..of
ES6
for (let v of a) {
console.log(v);
}
for
@@iterator
ES6
const a = [1, 2, 3]
let it = a[Symbol.iterator]();
console.log(it.next().value); //1
console.log(it.next().value); //2
console.log(it.next().value); //3
let it = a.entries();
console.log(it.next().value); //[0, 1]
console.log(it.next().value); //[1, 2]
console.log(it.next().value); //[2, 3]
let it = a.keys();
console.log(it.next().value); //0
console.log(it.next().value); //1
console.log(it.next().value); //2
.next() returns undefined when the array ends. You can also detect if
the iteration ended by looking at it.next() which returns a value,
done pair. done is always false until the last element, which returns
true .
Adding to an array
a.push(4)
a.unshift(0)
a.unshift(-2, -1)
a.pop()
a.shift()
At a random position
Returns the index of the first matching item found, or -1 if not found
a.lastIndexOf()
Returns the index of the last matching item found, or -1 if not found
ES6
Returns the first item that returns true. Returns undefined if not found.
Returns the index of the first item that returns true. Returns undefined
if not found.
ES7
a.includes(value)
a.includes(value, i)
const b = [1,'a','Z',3,2,11]
b = a.sort() //1, 11, 2, 3, Z, a
const a = [1,10,3,2,11]
a.sort((a, b) => a - b) //1, 2, 3, 10, 11
a.reverse()
a.join()
a.join(', ')
Introduction to promises
How promises work, in brief
Which JS API use promises?
Creating a promise
Consuming a promise
Chaining promises
Example of chaining promises
Handling errors
Cascading errors
Orchestrating promises
Promise.all()
Promise.race()
Introduction to promises
A promise is commonly defined as a proxy for a value that will eventually
become available.
Promises are one way to deal with asynchronous code, without writing too
many callbacks in your code.
Although being around since years, they have been standardized and
introduced in ES2015, and now they have been superseded in ES2017 by async
functions.
Once a promise has been called, it will start in pending state. This means
that the caller function continues the execution, while it waits for the
promise to do its own processing, and give the caller function some
feedback.
At this point the caller function waits for it to either return the
promise in a resolved state, or in a rejected state, but as you know
JavaScript is asynchronous, so the function continues its execution while
the promise does it work.
In addition to your own code, and libraries code, promises are used by
standard modern Web APIs such as:
It’s unlikely that in modern JavaScript you’ll find yourself not using
promises, so let’s start diving right into them.
Creating a promise
The Promise API exposes a Promise constructor, which you initialize using
new Promise() :
As you can see the promise checks the done global constant, and if
that’s true, we return a resolved promise, otherwise a rejected promise.
Using resolve and reject we can communicate back a value, in the above
case we just return a string, but it could be an object as well.
Consuming a promise
In the last section we introduced how a promise is created.
Chaining promises
A promise can be returned to another promise, creating a chain of
promises.
fetch('/todos.json')
.then(status)
.then(json)
.then((data) => { console.log('Request succeeded with JSON response', data) })
.catch((error) => { console.log('Request failed', error) })
In this example, we call fetch() to get a list of TODO items from the
todos.json file found in the domain root, and we create a chain of
promises.
response also has a json() method, which returns a promise that will
resolve with the content of the body processed and transformed as JSON.
So given those premises, this is what happens: the first promise in the
chain is a function that we defined, called status() , that checks the
response status and if it’s not a success response (between 200 and 299),
it rejects the promise.
This operation will cause the promise chain to skip all the chained
promises listed and will skip directly to the catch() statement at the
bottom, logging the Request failed text along with the error message.
In this case we return the data JSON processed, so the third promise
receives the JSON directly:
.then((data) => {
console.log('Request succeeded with JSON response', data)
})
Handling errors
In the example in the previous section we had a catch that was appended
to the chain of promises.
// or
new Promise((resolve, reject) => {
reject('Error')
})
.catch((err) => { console.error(err) })
Cascading errors
If inside the catch() you raise an error, you can append a second
catch() to handle it, and so on.
Orchestrating promises
Promise.all()
Example:
const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
You are not limited to using fetch of course, any promise is good to go.
Promise.race()
Example:
const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
The syntax at a first glance is very simple, just use backticks instead of
single or double quotes:
They are unique because they provide a lot of features that normal strings
built with quotes, in particular:
Multiline strings
Pre-ES6, to create a string spanned over two lines you had to use the \
character at the end of a line:
const string = 'first part \
second part'
This allows to create a string on 2 lines, but it’s rendered on just one
line:
To render the string on multiple lines as well, you explicitly need to add
\n at the end of each line, like this:
or
Once a template literal is opened with the backtick, you just press enter
to create a new line, with no special characters, and it’s rendered as-is:
string
is awesome!`
First
Second
an easy way to fix this problem is by having an empty first line, and
appending the trim() method right after the closing backtick, which will
eliminate any space before the first character:
const string = `
First
Second`.trim()
Interpolation
Template literals provide an easy way to interpolate variables and
expressions into strings.
Template tags
Tagged templates is one features that might sound less useful at first for
you, but it’s actually used by lots of popular libraries around, like
Styled Components or Apollo, the GraphQL client/server lib, so it’s
essential to understand how it works.
this function returns a string, which can be the result of any kind of
computation.
`something
another `
`
new line `
`
test`
The function that is passed those values can do anything with them, and
this is the power of this kind feature.
The most simple example is replicating what the string interpolation does,
by simply joining literals and expressions :
What is a Set
Initialize a Set
Add items to a Set
Check if an item is in the set
Delete an item from a Set by key
Determine the number of items in a Set
Delete all items from a Set
Iterate the items in a Set
Initialize a Set with values
Convert to array
Convert the Set keys into an array
A WeakSet
What is a Set
A Set data structure allows to add data to a container.
ECMAScript 6 (also called ES2015) introduced the Set data structure to the
JavaScript world, along with Map
Initialize a Set
A Set is initialized by calling:
You can add items to the Set by using the add method:
s.add('one')
s.add('two')
Once an element is in the set, we can check if the set contains it:
s.has('one') //true
s.has('three') //false
s.delete('one')
s.size
s.clear()
Iterate the items in a Set
The entries() method returns an iterator, which you can use like this:
const i = s.entries()
console.log(i.next())
for (const k of s) {
console.log(k)
}
Convert to array
Convert the Set keys into an array
const a = [...s.keys()]
// or
const a = [...s.values()]
A WeakSet
A WeakSet is a special kind of Set.
In a Set, items are never garbage collected. A WeakSet instead lets all
its items be freely garbage collected. Every key of a WeakSet is an
object. When the reference to this object is lost, the value can be
garbage collected.
add()
has()
delete()
THE MAP JAVASCRIPT DATA STRUCTURE
Discover the Map data structure introduced in ES6
What is a Map
Before ES6
Enter Map
Add items to a Map
Get an item from a map by key
Delete an item from a map by key
Delete all items from a map
Check if a map contains an item by key
Find the number of items in a map
Initialize a map with values
Map keys
Weird situations you’ll almost never find in real life
Iterating over a map
Iterate over map keys
Iterate over map values
Iterate over map key, value pairs
Convert to array
Convert the map keys into an array
Convert the map values into an array
WeakMap
What is a Map
A Map data structure allows to associate data to a key.
Before ES6
ECMAScript 6 (also called ES2015) introduced the Map data structure to the
JavaScript world, along with Set
Before its introduction, people generally (ab)used objects as maps, by
associating some object or value to a specific key value:
const car = {}
car['color'] = 'red'
car.owner = 'Flavio'
console.log(car['color']) //red
console.log(car.color) //red
console.log(car.owner) //Flavio
console.log(car['owner']) //Flavio
Enter Map
ES6 introduced the Map data structure, providing us a proper tool to
handle this kind of data organization.
You can add items to the map by using the set method:
m.set('color', 'red')
m.set('age', 2)
m.clear()
Map keys
Just like any value (object, array, string, number) can be used as the
value of the key-value entry of a map item, any value can be used as the
key, even objects.
If you try to get a non-existing key using get() out of a map, it will
return undefined .
Map offers the keys() method we can use to iterate on all the keys:
Map offers the values() method we can use to iterate on all the values:
Map offers the values() method we can use to iterate on all the values:
for (const [k, v] of m.entries()) {
console.log(k, v)
}
Convert to array
const a = [...m.keys()]
const a = [...m.values()]
WeakMap
A WeakMap is a special kind of map.
In a Map, items are never garbage collected. A WeakMap instead lets all
its items be freely garbage collected. Every key of a WeakMap is an
object. When the reference to this object is lost, the value can be
garbage collected.
get(k)
set(k, v)
has(k)
delete(k)
The use cases of a WeakMap are less evident than the ones of a Map, and
you might never find the need for them, but essentially it can be used to
build a memory-sensitive cache that is not going to interfere with garbage
collection, or for careful encapsualtion and information hiding.
JAVASCRIPT LOOPS AND SCOPE
Learn some tricks about JavaScript loops and scoping with var and let
const operations = []
0
1
2
3
4
5
5
5
5
5
Why is this the case? Because of the use of var .
var i;
const operations = []
so, in the for-of loop, i is still visible, it’s equal to 5 and every
reference to i in the function is going to use this value.
Simply changing var to let in the loop variable is going to work fine:
const operations = []
0
1
2
3
4
How is this possible? This works because on every loop iteration i is
created as a new variable each time, and every function added to the
operations array gets its own copy of i .
Keep in mind you cannot use const in this case, because there would be
an error as for tries to assign a new value in the second iteration.
Another way to solve this problem was very common in pre-ES6 code, and it
is called Immediately Invoked Function Expression (IIFE).
In this case you can wrap the entire function and bind i to it. Since in
this way you’re creating a function that immediately executes, you return
a new function from it, so we can execute it later:
const operations = []
Introduction
Why was async/await introduced?
How it works
A quick example
Promise all the things
The code is much simpler to read
Multiple async functions in series
Introduction
JavaScript evolved in a very short time from callbacks to promises
(ES2015), and since ES2017 asynchronous JavaScript is even simpler with
the async/await syntax.
They were good primitives around which a better syntax could be exposed to
the developers, so when the time was right we got async functions.
They make the code look like it’s syncronous, but it’s asynchronous and
non-blocking behind the scenes.
How it works
An async function returns a promise, like in this example:
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000)
})
}
When you want to call this function you prepend await , and the calling
code will stop until the promise is resolved or rejected. One caveat: the
client function must be defined as async . Here’s an example:
A quick example
This is a simple example of async/await used to run a function
asynchronously:
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000)
})
}
The above code will print the following to the browser console:
Before
After
I did something //after 3s
For example here’s how you would get a JSON resource, and parse it, using
promises:
getFirstUserData();
getFirstUserData();
function promiseToDoSomething() {
return new Promise((resolve)=>{
setTimeout(() => resolve('I did something'), 10000)
})
}
watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => {
console.log(res)
})
Will print:
Haskell, Clojure and Scala are some of the most popular purely functional
programming languages.
FP has been gaining a lot of momentum lately, so it’s the perfect time to
learn about it.
const obj = {
f(m) {
console.log(m)
}
}
obj.f('Test')
as well as to arrays:
const a = [
m => console.log(m)
]
a[0]('Test')
Declarative programming
You may have heard the term “declarative programming”.
Declarative vs Imperative
An imperative approach is when you tell the machine (in general terms),
the steps it needs to take to get a job done.
A declarative approach is when you tell the machine what you need to do,
and you let it figure out the details.
You start thinking declarative when you have enough level of abstraction
to stop reasoning about low level constructs, and think more at a higher
UI level.
Immutability
In functional programming data never changes. Data is immutable.
A variable can never be changed. To update its value, you create a new
variable.
Instead of changing an array, to add a new item you create a new array by
concatenating the old array, plus the new item.
Object.assign()
concat()
const a = [1, 2]
const b = [1, 2].concat(3)
// b = [1, 2, 3]
const c = [...a, 3]
// c = [1, 2, 3]
filter()
The same goes for removing an item from an array: instead of using pop()
and splice() , which modify the original array, use array.filter() :
Data Transformations
Since immutability is such an important concept and a foundation of
functional programming, you might ask how can data change.
Array.map()
Calling Array.map() on an array will create a new array with the result
of a function executed on every item of the original array:
const a = [1, 2, 3]
const b = a.map((v, k) => v * k)
// b = [0, 2, 6]
Array.reduce()
Calling Array.reduce() on an array allows us to transform that array
on anything else, including a scalar, a function, a boolean, an object.
You pass a function that processes the result, and a starting point:
const a = [1, 2, 3]
const sum = a.reduce((partial, v) => partial + v, 0)
// sum = 6
Recursion
Recursion is a key topic in functional programming. when a function calls
itself, it’s called a recursive function.
Composition
Composition is another key topic of Functional Programming, a good reason
to put it into the “key topics” list.
Composing in plain JS
obj.doSomething(doThis())
Introduction
What is a Progressive Web App
Progressive Web Apps alternatives
Native Mobile Apps
Hybrid Apps
Apps built with React Native
Progressive Web Apps features
Features
Benefits
Core concepts
Service Workers
The App Manifest
Example
The App Shell
Caching
Introduction
Progressive Web Apps (PWA) are the latest trend of mobile application
development using web technologies, at the time of writing (early 2018)
only applicable to Android devices.
WebKit, the tech underlying Safari and Mobile Safari, has recently
(Aug 2017) declared they started working on introducing Service
Workers into the browser. This means that soon (sooner or later) they
will land in iOS devices as well, so the Progressive Web Apps concept
could as well be applicable to iPhones and iPads, if Apple decides to
encourage this approach.
Update Feb 2018: PWAs are coming to iOS 11.3 and macOS 10.13.4, very
soon.
It’s not a groundbreaking new technology, but rather a new term that
identifies a bundle of techniques that have the goal of creating a better
experience for web-based apps.
Offline support
Loads fast
Is secure
Is capable of emitting push notifications
Has an immersive, full-screen user experience without the URL bar
Mobile platforms (Android at the time of writing, but it’s not technically
limited to that) offer an increasing support for Progressive Web Apps to
the point of asking the user to add the app to the home screen when they
detect a site a user is visiting is a PWA.
But first, a little clarification on the name. Progressive Web App can be
a confusing term, and a good definition is web apps that take advantage of
modern browsers features (like web workers and the web app manifest) to
let their mobile devices “upgrade” the app to the role of a first-class
citizen app.
Let’s focus on the pros and cons of each, and let’s see where PWAs are a
good fit.
Native mobile apps are the most obvious way to build a mobile app.
Objective-C or Swift on iOS, Java / Kotlin on Android and C# on Windows
Phone.
Each platform has its own UI and UX conventions, and the native widgets
provide the experience that the user expects. They can be deployed and
distributed through the platform App Store.
The main pain point with native apps is that cross-platform development
requires learning, mastering and keeping up to date with many different
methodologies and best practices, so if for example you have a small team
or even you’re a solo developer building an app on 3 platforms, you need
to spend a lot of time learning the technology but also the environment,
manage different libraries, and use different workflows (for example,
iCloud only works on iOS devices, there’s no Android version).
Hybrid Apps
Hybrid applications are built using Web Technologies, but deployed to the
App Store. In the middle sits a framework or some way to package the
application so it’s possible to send it for review to the traditional App
Store.
Most common platforms are Phonegap, Xamarin, Ionic Framework, and many
others, and usually what you see on the page is a WebView that essentially
loads a local website.
The key aspect of Hybrid Apps is the write once, run anywhere concept, the
different platform code is generated at build time, and you’re building
apps using JavaScript, HTML and CSS, which is amazing, and the device
capabilities (microphone, camera, network, gps…) are exposed through
JavaScript APIs.
The bad part of building hybrid apps is that unless you do a great job,
you might settle on providing a common denominator, effectively creating
an app that’s sub-optimal on all platforms because the app is ignoring the
platform-specific human-computer interaction guidelines.
React Native exposes the native controls of the mobile device through a
JavaScript API, but you’re effectively creating a native application, not
embedding a website inside a WebView.
Their motto, to distinguish this approach from hybrid apps, is learn once,
write anywhere, meaning that the approach is the same across platforms,
but you’re going to create completely separate apps in order to provide a
great experience on each platform.
Features
Progressive Web Apps have one thing that separates them completely from
the above approaches: they are not deployed to the app store..
This is a key advantage, since the app store is beneficial if you have the
reach and luck to be featured, which can make your app go viral, but
unless you’re in the 0,001% you’re not going to get much benefits from
having your little place on the App Store.
Progressive Web Apps are discoverable using Search Engines, and when a
user gets to your site which has PWAs capabilities, the browser in
combination with the device asks the user if they want to install the app
to the home screen. This is huge because regular SEO can apply to your
PWA, leading to much less reliance on paid acquisition.
Not being in the App Store means you don’t need the Apple or Google
approval to be in the users pockets, and you can release updates when you
want, without having to go through the standard approval process which is
typical of iOS apps.
The use of service workers allow the app to always have fresh content, and
download it in the background, and provide support for push notifications
to provide greater re-engagement opportunities.
Also, sharability makes for a much nicer experience for users that want to
share your app, as they just need a URL.
Benefits
So why should users and developers care about Progressive Web Apps?
1. PWA are lighter. Native Apps can weight 200MB or more, while a PWA
could be in the range of the KBs.
2. No native platform code
3. Lower the cost of acquisition (it’s much more hard to convince a user
to install an app than to visit a website to get the first-time
experience)
4. Significant less effort is needed to build and release updates
5. Much more support for deep links than regular app-store apps
Core concepts
Responsive: the UI adapts to the device screen size
App-like feel: it doesn’t feel like a website, but rather as an app as
much as possible
Offline support: it will use the device storage to provide offline
experience
Installable: the device browser prompts the user to install your app
Re-engaging: push notifications help users re-discover your app once
installed
Discoverable: search engines and SEO optimization can provide a lot
more users than the app store
Fresh: the app updates itself and the content once online
Safe: uses HTTPS
Progressive: it will work on any device, even older one, even if with
less features (e.g. just as a website, not installabla)
Linkable: easy to point to it, using URLs
Service Workers
Part of the Progressive Web App definition is that it must work offline.
Since the thing that allows the web app to work offline is the Service
Worker, this implies that Service Workers are a mandatory part of a
Progressive Web App.
Because of security reasons, only HTTPS sites can make use of Service
Workers, and this is part of the reasons why a Progressive Web App must be
served through HTTPS.
Service Workers are not available on the device the first time the user
visits the app. What happens is that the first visit the web worker is
installed, and then on subsequent visits to separate pages of the site
will call this Service Worker.
You add a link to the manifest in all your web site pages header:
Example
{
"name": "The Weather App",
"short_name": "Weather",
"description": "Progressive Web App Example",
"icons": [{
"src": "images/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "images/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "images/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "images/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}, {
"src": "images/icons/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
}],
"start_url": "/index.html?utm_source=app_manifest",
"orientation": "portrait",
"display": "standalone",
"background_color": "#3E4EB8",
"theme_color": "#2F3BA2"
}
Caching
The App Shell is cached separately from the contents, and it’s setup so
that retrieving the shell building blocks from the cache takes very little
time.
A Service Worker is programmable proxy between your web page and the
network, providing the ability to intercept and cache network requests,
effectively giving you the ability to create an offline-first experience
for your app.
It’s a special kind of web worker, a JavaScript file associated with a web
page which runs on a worker context, separate from the main thread, giving
the benefit of being non-blocking - so computations can be done without
sacrificing the UI responsiveness.
Promises
Fetch API
Cache API
And they are only available on HTTPS protocol pages, except for local
requests, which do not need a secure connection for an easier testing.
Background Processing
Service Workers run independent of the application they are associated to,
and they can receive messages when they are not active.
The main scenarios where Service Workers are very useful are:
A Service Worker only runs when needed, and it’s stopped when not used.
Offline Support
Traditionally the offline experience for web apps has been very poor.
Without a network, often web mobile apps simply won’t work, while native
mobile apps have the ability to offer either a working version, or some
kind of nice message.
This is not a nice message, but this is what web pages look like in Chrome
without a network connection:
Possibly the only nice thing about this is that you get to play a free
game by clicking the dinosaur, but it gets boring pretty quickly.
In the recent past the HTML5 AppCache already promised to allow web apps
to cache resources and work offline, but its lack of flexibility and
confusing behavior made it clear that it wasn’t good enough for the job,
failing its promises (and it’s been discontinued
(https://html.spec.whatwg.org/multipage/offline.html#offline) ).
Assets that are reused throughout the application, like images, CSS,
JavaScript files, can be installed the first time the app is opened.
This gives the base of what is called the App Shell architecture.
Using the Fetch API we can edit the response coming from the server,
determining if the server is not reachable and providing a response from
the cache instead.
Registration
Installation
Activation
Registration
Registration tells the browser where the server worker is, and it starts
the installation in the background.
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
} else {
} else {
console.log('Service Workers not supported')
}
Even if this code is called multiple times, the browser will only perform
the registration if the service worker is new, not registered previously,
or if has been updated.
Scope
navigator.serviceWorker.register('/worker.js', {
scope: '/notifications/'
})
{
scope: '/notifications'
}
NOTE: The service worker cannot “up” itself from a folder: if its
file is put under /notifications , it cannot control the / path
or any other path that is not under /notifications .
Installation
Activation
The activation stage is the third step, once the service worker has been
successfully registered and installed.
At this point, the service worker will be able to work with new page
loads.
It cannot interact with pages already loaded, which means the service
worker is only useful on the second time the user interacts with the app,
or reloads one of the pages already open.
A good use case for this event is to cleanup old caches and things
associated with the old version but unused in the new version of the
service worker.
This ensures that nothing will break on the apps / pages already working.
Refreshing the page is not enough, as the old worker is still running and
it’s not been removed.
Fetch Events
A fetch event is fired when a resource is requested on the network.
This offers us the ability to look in the cache before making network
requests.
For example the snippet below uses the Cache API to check if the request
URL was already stored in the cached responses, and return the cached
response if this is the case. Otherwise, it executes the fetch request and
returns it.
Background Sync
Background sync allows outgoing connections to be deferred until the user
has a working network connection.
This is key to ensure a user can use the app offline, and take actions on
it, and queue server-side updates for when there is a connection open,
instead of showing an endless spinning wheel trying to get a signal.
navigator.serviceWorker.ready.then((swRegistration) => {
return swRegistration.sync.register('event1')
});
This also allows an app to update data from the server as soon as there is
a working connection available.
Push Events
Service Workers enable web apps to provide native Push Notifications to
users.
Since Service Workers run even when the app is not running, they can
listen for push events coming, and either provide user notifications, or
update the state of the app.
Push events are initiated by a backend, through a browser push service,
like the one provided by Firebase.
Here is an example of how the service worker can listen for incoming push
events:
const options = {
title: 'I got a message for you!',
body: 'Here is the body of the message',
icon: '/img/icon-192x192.png',
tag: 'tag-for-this-notification',
}
event.waitUntil(
self.registration.showNotification(title, options)
)
})
Otherwise, since the service worker acts before the page is loaded, and
the console is cleared before loading the page, you won’t see any log in
the console.
FETCH API
The Fetch API, has been standardized as a modern approach to
asynchronous network requests, and uses Promises as a building block
Quite a few years after this, GMail and other rich apps made heavy use of
it, and made the approach so popular that it had to have a name: AJAX.
Working directly with the XMLHttpRequest has always been a pain and it was
almost always abstracted by some library, in particular jQuery has its own
helper functions built around it:
jQuery.ajax()
jQuery.get()
jQuery.post()
and so on.
They had a huge impact on making this more accessible in particular with
regards to making sure all worked on older browsers as well.
Fetch at the time of writing (Sep 2017) has a good support across the
major browsers, except IE.
Using Fetch
Starting to use Fetch for GET requests is very simple:
fetch('/file.json')
and you’re already using it: fetch is going to make an HTTP request to get
the file.json resource on the same domain.
As you can see, the fetch function is available in the global window
scope.
Now let’s make this a bit more useful, let’s actually see what the content
of the file is:
fetch('./file.json')
.then((response) => {
response.json().then((data) => {
console.log(data)
})
}
)
Calling fetch() returns a promise. We can then wait for the promise to
resolve by passing a handler with the then() method of the promise.
That handler receives the return value of the fetch promise, a Response
object.
Catching errors
Since fetch() returns a promise, we can use the catch method of the
promise to intercept any error occurring during the execution of the
request, and the processing done in the then callbacks:
fetch('./file.json')
.then((response) => {
//...
}
)
.catch((err) => {
console.error(err)
})
Response Object
The Response Object returned by a fetch() call contains all the
information about the request and the response of the network request.
Metadata
headers
Accessing the headers property on the response object gives you the
ability to look into the HTTP headers returned by the request:
fetch('./file.json')
.then((response) => {
console.log(response.headers.get('Content-Type'))
console.log(response.headers.get('Date'))
})
status
fetch('./file.json')
.then((response) => {
console.log(response.status)
})
statusText
fetch('./file.json')
.then((response) => {
console.log(response.statusText)
})
url
Body content
fetch('./file.json')
.then((response) => {
response.text().then(body => console.log(body))
response.json().then(body => console.log(body))
})
Request Object
The Request object represents a resource request, and it’s usually created
using the new Request() API.
Example:
Request headers
Being able to set the HTTP request header is essential, and fetch gives
us the ability to do this using the Headers object:
or more simply
To attach the headers to the request, we use the Request object, and pass
it to fetch() instead of simply passing the URL.
Instead of:
fetch('./file.json')
we do
The Headers object is not limited to setting value, but we can also query
it:
headers.has('Content-Type');
headers.get('Content-Type');
and we can delete a header that was previously set:
headers.delete('X-My-Custom-Header');
POST Requests
Fetch also allows to use any other HTTP method in your request: POST, PUT,
DELETE or OPTIONS.
Specify the method in the method property of the request, and pass
additional parameters in the header and in the request body:
const options = {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'foo=bar&test=1'
}
fetch(url, options)
.catch((err) => {
console.error('Request failed', err)
})
THE CHANNEL MESSAGING API
The Channel Messaging API allows iframes and workers to communicate
with the main document thread
How it works
port1
port2
Those properties are a MessagePort object. port1 is the port used by the
part that created the channel, and port2 is the port used by the channel
receiver (by the way, the channel is bidirectional, so the receiver can
send back messages as well).
Sending the message is done through the
otherWindow.postMessage()
A message can be a JavaScript value like strings, numbers, and some data
structures are supported, namely
File
Blob
FileList
ArrayBuffer
The main document defines an iframe and a span where we’ll print a
message that’s sent from the iframe document. As soon as the iframe
document is loaded, we send it a message on the channel we created.
<!DOCTYPE html>
<html>
<body>
<iframe src="iframe.html" width="500" height="500"></iframe>
<span></span>
</body>
<script>
const channel = new MessageChannel()
const display = document.querySelector('span')
const iframe = document.querySelector('iframe')
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage('Hey', '*', [channel.port2])
}, false)
<!DOCTYPE html>
<html>
<script>
window.addEventListener("message", (event) => {
if (event.origin !== "http://example.org:8080") {
return
}
// process
As you can see we don’t even need to initialize a channel, because the
window.onmessage handler is automatically run when the message is
received from the container page.
data : the object that’s been sent from the other window
origin : the origin URI of the window that sent the message
source : the window object that sent the message
Always verify the origin of the message sender.
What’s important to know is that Service Workers are isolated from the
main thread, and we must communicate with them using messages.
This is how a script attached to the main document will handle sending
messages to the Service Worker:
In the Service Worker code, we add an event listener for the message
event:
Introduction
Detect if the Cache API is available
Initialize a cache
Add items to the cache
cache.add()
cache.addAll()
Manually fetch and add
Retrieve an item from the cache
Get all the items in a cache
Get all the available caches
Remove an item from the cache
Delete a cache
Introduction
The Cache API is part of the Service Worker specification, and is a great
way to have more power on resources caching.
It’s not meant to cache individual chunks of data, which is the task of
the IndexedDB API.
It’s currently available in Chrome >= 40, Firefox >=39, and Opera >= 27.
Internet Explorer and Safari still do not support it, while Edge has
support for that is in preview release.
Mobile support is good on Android, supported on the Android Webview and in
Chrome for Android, while on iOS it’s only available to Opera Mobile and
Firefox Mobile users.
if ('caches' in window) {
//ok
}
Initialize a cache
Use the caches.open API, which returns a promise with a cache object
ready to be used:
caches.open('mycache').then((cache) => {
// you can start using the cache
})
cache.add()
add accepts a single URL, and when called it fetches the resource and
caches it.
caches.open('mycache').then((cache) => {
cache.add('/api/todos')
})
To allow more control on the fetch, instead of a string you can pass a
Request object, part of the Fetch API specification:
caches.open('mycache').then((cache) => {
const options = {
// the options
}
cache.add(new Request('/api/todos', options))
})
cache.addAll()
addAll accepts an array, and returns a promise when all the resources
have been cached.
caches.open('mycache').then((cache) => {
cache.addAll(['/api/todos', '/api/todos/today']).then(() => {
//all requests were cached
})
})
The Cache API offers a more granular control on this via cache.put() .
You are responsible for fetching the resource and then telling the Cache
API to store a response:
caches.open('mycache').then((cache) => {
cache.match('/api/todos').then((res) => {
//res is the Response Object
})
})
caches.keys().then((keys) => {
// keys is an array with the list of keys
})
caches.open('mycache').then((cache) => {
cache.delete('/api/todos')
})
Delete a cache
The caches.delete() method accepts a cache identifier and when
executed it wipes the cache and its cached items from the system.
caches.delete('mycache').then(() => {
// deleted successfully
})
THE PUSH API
The Push API allows a web app to receive messages pushed by a server,
even if the web app is not currently open in the browser or not running
on the device.
IE, Edge do not support it yet, and Safari has its own implementation
(https://developer.apple.com/notifications/safari-push-notifications/
(https://developer.apple.com/notifications/safari-push-notifications/) )
Since Chrome and Firefox support it, approximately 60% of the users
browsing on the desktop have access to it, so it’s quite safe to use.
This lets you deliver notifications and content updates, giving you the
ability to have a more engaged audience.
This is huge because one of the missing pillars of the mobile web,
compared to native apps, was the ability to receive notifications, along
with offline support.
How it works
Overview
When a user visits your web app, you can trigger a panel asking permission
to send updates. A Service Worker is installed, and operating in the
background listens for a Push Event.
Your server sends the notification to the client, and the Service Worker,
if given permission, receives a push event. The Service Worker reacts to
this event by triggering a notification.
Many sites implement this panel badly, showing it on the first page
load. The user is not yet convinced your content is good, and they
will deny the permission. Do it wisely.
if (!('serviceWorker' in navigator)) {
// Service Workers are not supported. Return
return
}
if (!('PushManager' in window)) {
// The Push API is not supported. Return
return
}
This code register the Service Worker located in the worker.js file
placed in the domain root:
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ',
registration.scope)
g p )
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
To know more about how Service Workers work in details, check out the
Service Workers guide.
Now that the Service worker is registered, you can request the permission.
The API to do this changed over time, and it went from accepting a
callback function as a parameter to returning a Promise, breaking the
backward and forward compatibility, and we need to do both as we don’t
know which approach is implemented by the user’s browser.
The permissionResult value is a string, that can have the value of: -
granted - default - denied
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
askPermission().then(() => {
const options = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY)
}
return registration.pushManager.subscribe(options)
}).then((pushSubscription) => {
// we got the pushSubscription object
}
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
It will be used as part of the validation that for security reasons occurs
to make sure you (and only you, not someone else) can send a push message
back to the user.
sendToServer(subscription)
What about the server? What should it do, and how should it interact with
the client?
These server-side examples uses Express.js (http://expressjs.com/
(http://expressjs.com/) ) as the base HTTP framework, but you can
write a server-side Push API handler in any language or framework
We initialize Express.js:
This utility function makes sure the request is valid, has a body and an
endpoint property, otherwise it returns an error to the client:
resolve(id)
})
})
}
We use those functions in the POST request handler below. We check if the
request is valid, then we save the request and then we return a
data.success: true response back to the client, or an error:
saveSubscriptionToDatabase(req, res.body)
.then((subscriptionId) => {
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({ data: { success: true } }))
})
.catch((err) => {
res.status(500)
res.setHeader('Content-Type', 'application/json')
res.send(JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message: 'Subscription received but failed to save it'
}
}))
})
})
app.listen(3000, () => {
console.log('App listening on port 3000')
})
Now that the server has registered the client in its list, we can send it
Push messages. Let’s see how that works by creating an example code
snippet that fetches all subscriptions and sends a Push message to all of
them at the same time.
const vapidKeys = {
publicKey: PUBLIC_KEY,
privateKey: PRIVATE_KEY
}
webpush.setVapidDetails(
'mailto:my@email.com',
vapidKeys.publicKey,
vapidKeys.privateKey
)
The meat of the code is the callback of the POST request to the
/api/push endpoint:
What the above code does is: it gets all the subscriptions from the
database, then it iterates on them, and it calls the triggerPush()
function we explained before.
It’s unlikely that you’ll set up your own Push server unless you have a
very special use case, or you just want to learn the tech or you like to
DIY. Instead, you usually want to use platforms such as OneSignal
(https://onesignal.com (https://onesignal.com) ) which transparently
handle Push events to all kind of platforms, Safari and iOS included, for
free.
It’s a normal JavaScript event listener, on the push event, which runs
inside a Service Worker:
Displaying a notification
Here we intersect a bit with the Notifications API, but for a good reason,
as one of the main use cases of the Push API is to display notifications.
Inside our push event listener in the Service Worker, we need to display
the notification to the user, and to tell the event to wait until the
browser has shown it before the function can terminate. We extend the
event lifetime until the browser has done displaying the notification
(until the promise has been resolved), otherwise the Service Worker could
be stopped in the middle of your processing:
Those messages are consistent and native, which means that the receiving
person is used to the UI and UX of them, being system-wide and not
specific to your site.
In combination with the Push API this technology can be a successful way
to increase user engagement and to enhance the capabilities of your app.
n.close()
Permissions
To show a notification to the user, you must have permission to do so.
Notification.requestPermission()
To do something when the user interacts (allows or denies), you can attach
a processing function to it:
Notification.requestPermission((permission) => {
process(permission)
}).then((permission) => {
process(permission)
})
Create a notification
The Notification object exposed by the window object in the browser
allows you to create a notification and to customize its appearance.
Here is the simplest example, that works after you asked for permissions:
Notification.requestPermission()
new Notification('Hey')
Add a body
First, you can add a body, which is usually shown as a single line:
new Notification('Hey', {
body: 'You should see this!'
})
Add an image
new Notification('Hey', {
body: 'You should see this!',
icon: '/user/themes/writesoftware/favicon.ico'
})
Close a notification
You might want to close a notification once you opened it.
n.close()
or with a timeout:
setTimeout(n.close(), 1 * 1000)
INDEXEDDB
Introduction to the Database of the Web
Introduction to IndexedDB
Create an IndexedDB Database
How to create a database
Create an Object Store
How to create an object store or add a new one
Indexes
Check if a store exists
Deleting from IndexedDB
Delete a database
Delete an object store
To delete data in an object store use a transaction
Add an item to the database
Getting items from a store
Getting a specific item from a store using get()
Getting all the items using getAll()
Iterating on all the items using a cursor via openCursor()
Iterating on a subset of the items using bounds and cursors
Introduction to IndexedDB
IndexedDB is one of the storage capabilities introduced into browsers over
the years. It’s a key/value store (a noSQL database) considered to be the
definitive solution for storing data in browsers.
In the past we also had Web SQL, a wrapper aroung SQLite, but now this is
deprecated and unsupported on some modern browsers, it’s never been a
recognized standard and so it should not be used, although 83% of users
have this technology on their devices according to Can I Use
(http://caniuse.com/#feat=sql-storage) .
While you can technically create multiple databases per site, you
generally create one single database, and inside that database you can
create multiple object stores.
strings
numbers
objects
arrays
dates
For example you might have a store that contains posts, another that
contains comments.
You can alter those stores using transactions, by performing add, edit and
delete operations, and iterating over the items they contain.
Since the advent of Promises in ES2015, and the subsequent move of APIs to
using promises, the IndexedDB API seems a bit old school.
While there’s nothing wrong in it, in all the examples that I’ll explain
I’ll use the IndexedDB Promised Library
(https://github.com/jakearchibald/idb) by Jake Archibald, which is a tiny
layer on top of the IndexedDB API to make it easier to use.
<script src="./node_modules/idb/lib/idb.js"></script>
Before using the IndexedDB API, always make sure you check for support in
the browser, even though it’s widely available, you never know which
browser the user is using:
(() => {
'use strict'
if (!('indexedDB' in window)) {
console.warn('IndexedDB not supported')
return
}
//...IndexedDB code
})()
Using idb.open() :
We use the name upgradeDB for the callback to identify this is the time
to update the database if needed.
The index gives you a way to retrieve a value later by that specific key,
and it must be unique (every item must have a different key)
A key can be set to auto increment, so you don’t need to keep track of it
on the client code. If you don’t specify a key, IndexedDB will create it
transparently for us:
but you can specify a specific field of object value to auto increment as
well:
upgradeDb.createObjectStore('notes', {
keyPath: 'id',
autoIncrement: true
})
An index is a way to retrieve data from the object store. It’s defined
along with the database creation in the idb.open() callback in this
way:
The unique option determines if the index value should be unique, and no
duplicate values are allowed to be added.
if (!upgradeDb.objectStoreNames.contains('store3')) {
upgradeDb.createObjectStore('store3')
}
Delete a database
idb.delete('mydb')
.then(() => console.log('done'))
An object store can only be deleted in the callback when opening a db, and
that callback is only called if you specify a version higher than the one
currently installed:
dbPromise.then((db) => {
const tx = db.transaction('store', 'readwrite')
const store = tx.objectStore('store')
store.delete(key)
return tx.complete
})
.then(() => {
console.log('Item deleted')
})
When using put , the value is the first argument, the key is the second.
This is because if you specify keyPath when creating the object store,
you don’t need to enter the key name on every put() request, you can just
write the value.
To add items later down the road, you need to create a transaction, that
ensures database integrity (if an operation fails, all the operations in
the transaction are rolled back and the state goes back to a known state).
For that, use a reference to the dbPromise object we got when calling
idb.open() , and run:
dbPromise.then((db) => {
const val = 'hey!'
const key = 'Hello again'
The IndexedDB API offers the add() method as well, but since
put() allows us to both add and update, it’s simpler to just use
it.
dbPromise.then((db) => {
const tx = db.transaction('store', 'readonly')
const store = tx.objectStore('store')
return store.openCursor()
})
.then(function logItems(cursor) {
if (!cursor) { return }
console.log('cursor is at: ', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(logItems)
})
.then(() => {
console.log('done!')
})
let range
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper)
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper)
} else {
range = IDBKeyRange.lowerBound(lower)
}
dbPromise.then((db) => {
const tx = db.transaction(['dogs'], 'readonly')
const store = tx.objectStore('dogs')
const index = store.index('age')
return index.openCursor(range)
})
.then(function showRange(cursor) {
if (!cursor) { return }
console.log('cursor is at:', cursor.key)
for (const field in cursor.value) {
console.log(cursor.value[field])
}
return cursor.continue().then(showRange)
})
.then(() => {
console.log('done!')
})
}
searchDogsBetweenAges(3, 10)
THE SELECTORS API
Access DOM elements using querySelector and querySelectorAll
Introduction
The Selectors API
Basic jQuery to DOM API examples
Select by id
Select by class
Select by tag name
More advanced jQuery to DOM API examples
Select multiple items
Select by HTML attribute value
Select by CSS pseudo class
Select the descendants of an element
Introduction
jQuery and other DOM libraries got a huge popularity boost in the past,
among with other features they provided, thanks to an easy way to select
elements on a page.
document.querySelector()
document.querySelectorAll()
They can be safely used, as caniuse.com tells us
(https://caniuse.com/#feat=queryselector) , and they are even fully
supported on IE9 in addition to all the other modern browsers, so
there is no reason to avoid them, unless you need to support IE8
(which has partial support) and below.
The way they work is by accepting any CSS selector, so you are no longer
limited by selecting elements by id.
document.querySelector('#test')
document.querySelector('.my-class')
document.querySelector('#test .my-class')
document.querySelector('a:hover')
Select by id
$('#test')
document.querySelector('#test')
Select by class
$('.test')
document.querySelectorAll('.test')
Select by tag name
$('div')
document.querySelectorAll('div')
$('div, span')
document.querySelectorAll('div, span')
$('[data-example="test"]')
document.querySelectorAll('[data-example="test"]')
$(':nth-child(4n)')
document.querySelectorAll(':nth-child(4n)')
$('#test li')
document.querySelectorAll('#test li')
FRONTEND DEV TOOLS
WEBPACK
Webpack is a tool that has got a lot of attention in the last few
years, and it is now seen used in almost every project. Learn about it.
Given its widely used status, you cannot really afford to not know
Webpack.
It can split the output files into multiple files, to avoid having a huge
js file to load in the first page hit.
Grunt
Broccoli
Gulp
It has a more simple syntax, better support for projects with lots of
files, and better integration with other modern tools.
Installing Webpack
Webpack is installed very easily with YARN:
or with NPM:
npm i -g webpack
$ webpack --help
webpack 3.5.6
Usage: https://webpack.js.org/api/cli/
Usage without config file: webpack <entry> [<entry>] <output>
Usage with config file: webpack
...
Webpack configuration
The Webpack configuration is usually stored in a file named
webpack.config.js in a project root folder.
let config = {
entry = './index.js',
output: {
filename: 'output.js'
}
}
module.exports = config
In the next articles you’ll see some more useful configuration options.
Running Webpack
Webpack can be run from the command line manually, but generally one
writes a script inside the package.json file, which is then run using
npm or yarn .
"scripts": {
"start": "webpack --watch"
},
npm start
or
yarn run start
Introduction to Babel
Installing Babel
An example Babel configuration
Babel presets
es2015 preset
env preset
react preset
More info on presets
Using Babel with Webpack
Introduction to Babel
Babel is an awesome tool, and it’s been around for quite some time, but
nowadays almost every JavaScript developer relies on it, and this will
continue going on, because Babel is now indispensable and has solved a big
problem for everyone.
Which problem?
The problem that every Web Developer has surely had: a feature of
JavaScript is available in the latest release of a browser, but not in the
older versions. Or maybe Chrome or Firefox implement it, but Safari iOS
and Edge do not.
So how should you deal with this problem? Should you move on and leave the
customers with older/incompatible browsers behind, or should you write
older JavaScript code to make all your users happy?
[1, 2, 3].map(function(n) {
return n + 1
})
This must happen at build time, so you must setup a workflow that handles
this for you. Webpack is a common solution.
(P.S. if all this ES thing sounds confusing to you, see more about ES
versions in the ECMAScript guide)
Installing Babel
Babel is easily installed using NPM or Yarn:
or
This will make the global babel command available in the command line:
Now inside your project install the babel-core and babel-loader packages,
by running:
or
By default Babel does not provide anything, it’s just a blank box that you
can fill with plugins to solve your specific needs.
or (Yarn)
{
"plugins": ["transform-es2015-arrow-functions"]
}
TIP: If you have never seen a dot file (a file starting with a dot)
it might be odd at first because that file might not appear in your
file manager, as it’s a hidden file.
var bob = {
_name: "Bob",
_friends: ["Sally", "Tom"],
printFriends() {
this._friends.forEach(f =>
console.log(this._name + " knows " + f));
}
};
console.log(bob.printFriends());
var bob = {
_name: "Bob",
_friends: ["Sally", "Tom"],
printFriends() {
var _this = this;
this._friends.forEach(function (f) {
return console.log(_this._name + " knows " + f);
});
}
};
console.log(bob.printFriends());
As you can see arrow functions have all been converted to JavaScript ES5
function s.
Babel presets
We just saw in the previous article how Babel can be configured to
transpile specific JavaScript features.
You can add much more plugins, but you can’t add to the configuration
features one by one, it’s not practical.
es2015 preset
This preset provides all the ES2015 features. You install it by running
or
yarn add --dev babel-preset-es2015
and by adding
{
"presets": ["es2015"]
}
env preset
The env preset is very nice: you tell it which environments you want to
support, and it does everything for you, supporting all modern JavaScript
features.
E.g. “support the last 2 versions of every browser, but for Safari let’s
support all versions since Safari 7`
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}]
]
}
or “I don’t need browsers support, just let me work with Node.js 6.10”
{
"presets": [
["env", {
"targets": {
"node": "6.10"
}
}]
]
}
react preset
The react preset is very convenient when writing React apps, by adding
preset-flow , syntax-jsx , transform-react-jsx , transform-
react-display-name .
By including it, you are all ready to go developing React apps, with JSX
transforms and Flow support.
https://babeljs.io/docs/plugins/ (https://babeljs.io/docs/plugins/)
TIP: read the Webpack guide if you’re not familiar with Webpack
or
entry: [
'babel-polyfill',
// your app scripts should be here
],
module: {
loaders: [
// Babel loader compiles ES2015 into ES5 for
// complete cross-browser support
{
loader: 'babel-loader',
test: /\.js$/,
// only include files present in the `src` subdirectory
include: [path.resolve(__dirname, "src")],
// exclude node_modules, equivalent to the above line
exclude: /node_modules/,
query: {
// Use the default ES2015 preset
// to include all ES2015 features
presets: ['es2015'],
plugins: ['transform-runtime']
}
}
]
}
Introduction to npm
Downloads
Installing all dependencies
Installing a single package
Updating packages
Versioning
Running Tasks
Introduction to npm
npm means node package manager.
In January 2017 over 350000 packages were reported being listed in the npm
registry, making it the biggest single language code repository on Earth,
and you can be sure there is a package for (almost!) everything.
Downloads
npm manages downloads of dependencies of your project.
Updating packages
npm update
npm will check all packages for a newer version that satisfies your
versioning constrains.
Versioning
In addition to plain downloads, npm also manages versioning, so you can
specify any specific version of a package, or require a version higher or
lower than what you need.
Many times you’ll find that a library is only compatible with a major
release of another library.
In all those cases, versioning helps a lot, and npm follows the Semantic
Versioning (SEMVER) standard.
Running Tasks
The package.json file supports a format for specifying command line tasks
that can be run by using
npm <task-name>
For example:
{
"scripts": {
"start-dev": "node lib/server-development",
"start": "node lib/server-production"
},
}
{
"scripts": {
"watch": "webpack --watch --progress --colors --config webpack.conf.js",
"dev": "webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
}
},
}
$ npm watch
$ npm dev
$ npm prod
YARN
Yarn is a JavaScript Package Manager, a direct competitor of npm, one
of Facebook most popular Open Source projects
Intro to Yarn
Install Yarn
Managing packages
Initialize a new project
Install the dependencies of an existing project
Install a package locally
Install a package globally
Install a package locally as a development dependency
Remove a package
Inspecing licenses
Inspecting dependencies
Upgrading packages
Intro to Yarn
Yarn is a JavaScript Package Manager, a direct competitor of npm, and it’s
one of Facebook most popular Open Source projects.
It’s compatible with npm packages, so it has the great advantage of being
a drop-in replacement for npm.
The reason you might want to use Yarn over npm are: - faster download of
packages, which are installed in parallel - support for multiple
registries - offline installation support
This is not the only feature, many other goodies are provided by Yarn,
which we’ll see in this article.
Tools eventually converge to a set of features that keeps them on the same
level to stay relevant, so we’ll likely see those features in npm in the
future - competition is nice for us users.
Install Yarn
While there is a joke around about installing Yarn with npm ( npm
install -g yarn ), it’s not recommended by the Yarn team.
but every Operating System has its own package manager of choice that will
make the process very smooth.
In the end, you’ll end up with the yarn command available in your shell:
Managing packages
Yarn writes its dependencies to a file named package.json , which sits
in the root folder of your project, and stores the dependencies files into
the node_modules folder, just like npm if you used it in the past.
yarn
or
yarn install
Inspecing licenses
When installing many dependencies, which in turn might have lots of
depencencies, you install a number of packages, of which you don’t have
any idea about the license they use.
Yarn provides a handy tool that prints the licens of any dependency you
have:
yarn licenses ls
and it can also generate a disclaimer automatically including all the
licenses of the projects you use:
Inspecting dependencies
Do you ever check the node_modules folder and wonder why a specific
package was installed? yarn why tells you:
yarn why package-name
Upgrading packages
If you want to upgrade a single package, run
yarn upgrade
But this command can sometimes lead to problems, because you’re blindly
upgrading all the dependencies without worrying about major version
changes.
yarn upgrade-interactive
JEST
Introduction to Jest
Create the first Jest test
Matchers
Mocks
Introduction to Jest
Jest is one of the many JavaScript testing library, that had a lot of
adoption because it’s developed by Facebook and is a perfect companion to
React, another very popular Facebook Open Source project.
Jest didn’t just get popular because of this - software giants regularly
ship projects that fail - but it got used by a lot of people because it’s
great.
Among its key features we can highlight: - fast, parallel tests are
distributed across workers to speed up execution - part of create-
react-app , so straightforward to start with with no configuration
needed - code coverage reports - support for React Native and TypeScript
Now, you don’t have any test here, so nothing is going to be executed:
Let’s create the first test. Open a math.js file and type a couple
functions that we’ll later test:
Now create a math.test.js file, in the same folder, and there we’ll use
Jest to test the functions defined in math.js :
Running yarn test results in Jest being run on all the test files it
finds, and returning us the end result:
Matchers
In the previous article I used toBe() as the only matcher:
All those matchers can be negated using .not. inside the statement, for
example:
Those are the most popular ones. See all the matchers you can use at
https://facebook.github.io/jest/docs/en/expect.html
(https://facebook.github.io/jest/docs/en/expect.html)
Mocks
In testing, mocking allows you to test functionality that depends on -
Database - Network requests - access to Files - any External system
so that 1. your tests run faster, giving a quick turnaround time during
development 2. your tests are independent of network conditions, the state
of the database 3. your tests do not pollute any data storage because they
do not touch the database 4. any change done in a test does not change the
state for subsequent tests, and re-running the test suite should start
from a known and reproduceable starting point 5. you don’t have to worry
about rate limiting on API calls and network requests
Even more important, if you are writing a Unit Test, you should test the
functionality of a function in isolation, not with all its baggage of
things it touches.
What is linter?
ESLint
Install ESLint globally
Install ESLint locally
Use ESLint in your favourite editor
Common ESLint configurations
Airbnb style guide
React
Use a specific version of ECMAScript
Force strict mode
More advanced rules
Disabling rules on specific lines
What is linter?
Good question! A linter is a tool that identifies issues in your code.
Running a linter against your code can tell you many things:
It will raise warnings that you, or your tools, can analyze and give you
actionable data to improve your code.
ESLint
ESLint is a linter for the JavaScript programming language, written in
Node.js.
ESLint will help you catch those errors. Which errors in particular you
ask?
ESLint is very flexible and configurable, and you can choose which rules
you want to check for, or which kind of style you want to enforce. Many of
the available rules are disabled and you can turn them on in your
.eslintrc configuration file, which can be global or specific to your
project.
Run
or
{
"extends": "airbnb",
}
React
or
{
"extends": "airbnb",
"plugins": [
"react"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
}
}
{
"parserOptions": {
"ecmaVersion": 6,
}
}
/* eslint-disable */
alert('test');
/* eslint-enable */
or on a single line:
alert('test'); // eslint-disable-line
Introduction to Prettier
Less options
Difference with ESLint
Installation
Prettier for beginners
Introduction to Prettier
Prettier is an opinionated code formatter.
Prettier logo
JavaScript
Flow, TypeScript
CSS, SCSS, Less
JSX
GraphQL
JSON
Markdown
https://prettier.io/ (https://prettier.io/)
https://github.com/prettier/prettier
(https://github.com/prettier/prettier)
https://www.npmjs.com/package/prettier
(https://www.npmjs.com/package/prettier)
Less options
I learned Go recently and one of the best things about Go is gofmt, an
official tool that automatically formats your code according to common
standards.
95% (made up stat) of the Go code around looks exactly the same, because
this tool can be easily enforced and since the style is defined on you by
the Go maintainers, you are much more likely to adapt to that standard
instead of insisting on your own style. Like tabs vs spaces, or where to
put an opening bracket.
This might sound like a limitation, but it’s actually very powerful. All
Go code looks the same.
It has very few options, and most of the decisions are already taken for
you so you can stop arguing about style and little things, and focus on
your code.
and some others, but Prettier tries to keep the number of those
customizations under control, to avoid becoming too customizable.
Installation
Prettier can run from the command line, and you can install it using Yarn
or npm.
Another great use case for Prettier is to run it on PRs for your Git
repositories, for example on GitHub.
If you use a supported editor the best thing is to use Prettier directly
from the editor, and the Prettier formatting will be run every time you
save.
Also, even if you started using JavaScript 2 weeks ago, with Prettier your
code - style wise - will look just like code written from a JavaScript
Guru writing JS since 1998.
BROWSER DEV TOOLS
The Browser DevTools are a fundamental element in the frontend
developer toolbox.
Once you figured out the differences between Internet Explored and
Netscape Navigator, and avoided the proprietary tags and technology, all
you had to use was HTML and later CSS.
JavaScript was a tech for creating dialog boxes and a little bit more, but
was definitely not as pervasive as today.
Although lots of web pages are still plain HTML + CSS, like this page,
many other websites are real applications that run in the browser.
Just providing the source of the page, like browser did once upon a time,
was not enough.
Browser had to provide much more information on how they rendered the
page, and what the page is currently doing, hence they introduced a
feature for developers: their developer tools.
Every browser is different and so their dev tools are slightly different.
At the time of writing the best developer tools in my opinion are provided
by Chrome, and this is the browser we’ll talk in the rest of the course,
although also Firefox and Edge have great tools as well.
Hovering the elements in the HTML panel highlights the element in the
page, and clicking the first icon in the toolbar allows you to click an
element in the page, and analyze it in the inspector.
You can drag and drop elements in the inspector to live change their
positioning in the page.
The CSS styles panel
On the right, the CSS styles that are applied to the currently selected
element.
In addition to editing and disabling properties, you can add a new CSS
property, with any target you want, by clicking the + icon.
Also you can trigger a state for the selected element, so you can see the
styles applied when it’s active, hovered, on focus.
At the bottom, the box model of the selected element helps you figure out
margins, paddings, border and dimensions at a quick glance:
The Console
The second most important element of the DevTools is the Console.
The Console can be seen on its own panel, or by pressing Esc in the
Elements panel, it will show up in the bottom.
The Console serves mainly two purposes: executing custom JavaScript and
error reporting.
At the bottom of the Console there is a blinking cursor. You can type any
JavaScript there, and it will be promptly executed. As an example, try
running:
alert('test')
You can write more than one line with shift-enter . Pressing enter at
the end of the script runs it.
Error reporting
Any error, warning or information that happens while rendering the page,
and subsequently executing the JavaScript, is listed here.
For example failing to load a resource from the network, with information
on why, is reported in the console.
In this case, clicking the resource URL brings you to the Network panel,
showing more info which you can use to determine the cause of the problem.
You can filter those messages by level (Error / Warning / Info) and also
filter them by content.
Those messages can be user-generated in your own JavaScript by using the
Console API:
The emulator
The Chrome DevTools embed a very useful device emulator which you can use
to visualize your page in every device size you want.
You can choose from the presets the most popular mobile devices, including
iPhones, iPads, Android devices and much more, or specify the pixel
dimensions yourself, and the screen definition (1x, 2x retina, 3x retina
HD).
In the same panel you can setup network throttling for that specific
Chrome tab, to emulate a low speed connection and see how the page loads,
and the “show media queries” option shows you how media queries modify the
CSS of the page.
The network panel
The Network Panel of the DevTools allows you to see all the connections
that the browser must process while rendering a page.
A very useful option in the toolbar is preserve log. By enabling it, you
can move to another page, and the logs will not be cleared.
Another very useful tool to track loading time is disable cache. This can
be enabled globally in the DevTools settings as well, to always disable
cache when DevTools is open.
Clicking a specific request in the list shows up the detail panel, with
HTTP Headers report:
You gain access to detailed reports and tools to interact with the
application storage:
Local storage
Session storage
IndexedDb
Web SQL
Cookies
and you can quickly wipe any information, to start with a clean slate.
Application
This tab also gives you tools to inspect and debug Progressive Web Apps.
Click manifest to get information about the web app manifest, used to
allow mobile users to add the app to their home, and simulate the “add to
homescreen” events.
Service workers let you inspect your application service workers. If you
don’t know what service workers are, in short they are a fundamental
technology that powers modern web apps, to provide features like
notification, capability to run offline and syncronize across devices.
Security tab
The Security tab gives you all the information that the browser has
relatively to the security of the connection to the website.
If there is any problem with the HTTPS connection, if the site is served
over SSL, it will provide you more information about what’s causing it.
Audits
The Audits tab will help you find and solve some issues relative to
performance and in general the quality of the experience that users have
when accessing your website.
You can perform various kinds of audits depending on the kind of website:
The audit is provided by Lighthouse
(https://developers.google.com/web/tools/lighthouse/) , an open source
automated website quality check tool. It takes a while to run, then it
provides you a very nice report with key actions to check.
REACT AND REDUX
REACT
React is a JavaScript library that aims to simplify development of
visual interfaces. Learn why it's so popular and what problems does it
solve.
Introduction to React
What is React
Why is React so popular?
Less complex than the other alternatives
Perfect timing
Backed by Facebook
Is React really that simple?
JSX
React Components
What is a React Component
Custom components
PropTypes
Which types can we use
Requiring properties
Default values for props
How props are passed
Children
Setting the default state
Accessing the state
Mutating the state
Why you should always use setState()
State is encapsulated
Unidirectional Data Flow
Moving the State Up in the Tree
Events
Event handlers
Bind this in methods
The events reference
Clipboard
Composition
Keyboard
Focus
Form
Mouse
Selection
Touch
UI
Mouse Wheel
Media
Image
Animation
Transition
React’s Declarative approach
React declarative approach
The Virtual DOM
The “real” DOM
The Virtual DOM
Why is the Virtual DOM helpful: batching
Introduction to React
What is React
Its primary goal is to make it easy to reason about an interface and its
state in any point in time, by dividing the UI into a collection of
components.
React is used to build single-page web applications, among with many other
libraries and frameworks that were available before React came into life.
At the time when React was announced, Ember.js and Angular 1.x were the
predominant choices as a framework. Both these imposed too many
conventions on the code that porting an existing app was not convienient
at all. React made a choice to be very easy to integrate into an existing
project, because that’s how they had to do it at Facebook in order to
introduce it to the existing codebase. Also, those 2 frameworks bringed
too much to the table, while React only chose to implement the View layer
instead of the full MVC stack.
Perfect timing
At the time, Angular 2.x was announced by Google, along with the backwards
incompatibility and major changes it was going to bring. Moving from
Angular 1 to 2 was like moving to a different framework, so this, along
with execution speed improvements that React promised, made it something
developers were eager to try.
Backed by Facebook
JSX
Many developers, including who is writing this article, at first sight
thought that JSX was horrible, and quickly dismissed React.
Even though they said JSX was not required, using React without JSX was
painful.
The major benefit of using JSX is that you’re only interacting with
JavaScript object, not template strings.
It looks like a strange mix of JavaScript and HTML, but in reality it’s
all JavaScript.
What looks like HTML, is actually a sugar syntax for defining components
and their positioning inside the markup.
You just need to pay attention when an attribute has a dash ( - ) which is
converted to camelCase syntax instead, and these 2 special cases:
Here’s a JSX snippet that wraps two components into a div tag:
<div>
<BlogPostsList />
<Sidebar />
</div>
A tag always needs to be closed, because this is more XML than HTML (if
you remember the XHTML days, this will be familiar, but since then the
HTML5 loose syntax won). In this case a self-closing tag is used.
React Components
Even plain HTML tags are component on their own, and they are added by
default.
The next 2 lines are equivalent, they do the same thing. One with JSX, one
without, by injecting <h1>Hello World!</h1> into an element with id
app .
ReactDOM.render(
<h1>Hello World!</h1>,
document.getElementById('app')
)
ReactDOM.render(
React.DOM.h1(null, "Hello World!"),
document.getElementById('app')
)
The built-in components are nice, but you’ll quickly outgrow them. What
React excels in is letting us compose a UI by composing custom components.
Custom components
There is a third syntax which uses the ES5 / ES2015 syntax, without
the classes:
React.createClass({
render() {
return (
<div>
<h1>Title</h1>
<p>Description</p>
</div>
)
}
})
Props is how Components get their properties. Starting from the top
component, every child component gets its props from the parent. In a
stateless component, props is all it gets passed, and they are available
by adding props as the function argument:
PropTypes
Flow and TypeScript help a lot, but React has a way to directly help with
props types, and even before running the code, our tools (editors,
linters) can detect when we are passing the wrong values:
BlogPostExcerpt.propTypes = {
title: PropTypes.string,
description: PropTypes.string
};
PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
PropTypes.oneOf(['Test1', 'Test2']),
PropTypes.instanceOf(Something)
PropTypes.node
PropTypes.any
PropTypes.arrayOf(PropTypes.string)
Requiring properties
PropTypes.arrayOf(PropTypes.string).isRequired,
PropTypes.string.isRequired,
BlogPostExcerpt.propTypes = {
title: PropTypes.string,
description: PropTypes.string
}
BlogPostExcerpt.defaultProps = {
title: '',
description: ''
}
Some tooling like ESLint have the ability to enforce defining the
defaultProps for a Component with some propTypes not explicitly required.
Children
<BlogPostExcerpt
title="A blog post"
description={desc}>
Something
</BlogPostExcerpt>
becomes:
render() {
return (
<div>
<h1>Title</h1>
<p>Description</p>
</div>
)
}
}
render() {
return (
<div>
<h1>Title</h1>
<p>Description</p>
<p>Clicked: {this.state.clicked}</p>
</div>
)
}
}
Mutating the state
this.state.clicked = true
The object can contain a subset, or a superset, of the state. Only the
properties you pass will be mutated, the ones omitted will be left in
their current state.
The reason is that using this method, React knows that the state has
changed. It will then start the series of events that will lead to the
Component being re-rendered, along with any DOM update.
State is encapsulated
A state is always owned by one Component. Any data that’s affected by this
state can only affect Components below it: its children.
Changing a state on a Component will never affect its parent, or its
siblings, or any other Component in the application: just its children.
This is the reason many times the state is moved up in the Components
tree.
Many times the closest ancestor is the best place to manage the state, but
it’s not a mandatory rule.
The state is passed down to the components that need that value via props:
render() {
return (
<div>
<Display currency={this.state.currency} />
<CurrencySwitcher currency={this.state.currency} />
</div>
)
}
}
Events
React provides an easy way to manage events. Prepare to say goodbye to
addEventListener :)
In the previous article about the State you saw this example:
If you’ve been using JavaScript for a while, this is just like plain old
JavaScript event handlers, except that this time you’re defining
everything in JavaScript, not in your HTML, and you’re passing a function,
not a string.
The actual event names are a little bit different because in React you use
camelCase for everything, so onclick becomes onClick , onsubmit
becomes onSubmit .
For reference, this is old school HTML with JavaScript events mixed in:
<button onclick="handleChangeCurrency()">
...
</button>
Event handlers
Don’t forget to bind methods. The methods of ES6 classes by default are
not bound. What this means is that this is not defined unless you define
methods as
Clipboard
onCopy
onCut
onPaste
Composition
onCompositionEnd
onCompositionStart
onCompositionUpdate
Keyboard
onKeyDown
onKeyPress
onKeyUp
Focus
onFocus
onBlur
Form
onChange
onInput
onSubmit
Mouse
onClick
onContextMenu
onDoubleClick
onDrag
onDragEnd
onDragEnter
onDragExit
onDragLeave
onDragOver
onDragStart
onDrop
onMouseDown
onMouseEnter
onMouseLeave
onMouseMove
onMouseOut
onMouseOver
onMouseUp
Selection
onSelect
Touch
onTouchCancel
onTouchEnd
onTouchMove
onTouchStart
UI
onScroll
Mouse Wheel
onWheel
Media
onAbort
onCanPlay
onCanPlayThrough
onDurationChange
onEmptied
onEncrypted
onEnded
onError
onLoadedData
onLoadedMetadata
onLoadStart
onPause
onPlay
onPlaying
onProgress
onRateChange
onSeeked
onSeeking
onStalled
onSuspend
onTimeUpdate
onVolumeChange
onWaiting
Image
onLoad
onError
Animation
onAnimationStart
onAnimationEnd
onAnimationIteration
Transition
onTransitionEnd
React’s Declarative approach
You’ll run across articles describing React as a declarative approach to
building UIs.
It’s really not a new concept, but React took building UIs a lot more
declarative than with HTML templates: you can build Web interfaces without
even touching the DOM directly, you can have an event system without
having to interact with the actual DOM Events.
For example looking up elements in the DOM using jQuery or DOM events is
an iterative approach.
React’s declarative approach abstracts that for us. We just tell React we
want a component to be rendered in a specific way, and we never have to
interact with the DOM to reference it later.
It’s kept in the browser memory, and directly linked to what you see in a
page. The DOM has an API that you can use to traverse it, access every
single node, filter them, modify them.
The API is the familiar syntax you have likely seen many times, if you
were not using the abstract API provided by jQuery and friends:
document.getElementById(id)
document.getElementsByTagName(name)
document.createElement(name)
parentNode.appendChild(node)
element.innerHTML
element.style.left
element.setAttribute()
element.getAttribute()
element.addEventListener()
window.content
window.onload
window.dump()
window.scrollTo()
React keeps a copy of the DOM representation, for what concernes the React
rendering: the Virtual DOM
Every time the DOM changes, the browser has to do two intensive
operations: repaint (visual or content changes to an element that do not
affect the layout and positioning relatively to other elements) and reflow
(recalculate the layout of a portion of the page - or the whole page
layout).
React uses a Virtual DOM to help the browser use less resources when
changes need to be done on a page.
When you call setState() on a Component, specifying a state different
than the previous one, React marks that Component as dirty. This is key:
React only updates when a Component changes the state explicitly.
The key thing is that React batches much of the changes and performs a
unique update to the real DOM, by changing all the elements that need to
be changed at the same time, so the repaint and reflow the browser must
perform to render the changes are executed just once.
JSX
Introduction to JSX
A JSX primer
Transpiling JSX
JS in JSX
HTML in JSX
You need to close all tags
camelCase is the new standard
class becomes className
The style attribute changes its semantics
Forms
CSS in React
Why is this preferred over plain CSS / SASS / LESS?
Is this the go-to solution?
Forms in JSX
value and defaultValue
A more consistent onChange
JSX auto escapes
White space in JSX
Horizontal white space is trimmed to 1
Vertical white space is eliminated
Adding comments in JSX
Spread attributes
Introduction to JSX
JSX is a technology that was introduced by React.
Although React can work completely fine without using JSX, it’s an ideal
technology to work with components, so React benefits a lot from JSX.
At first, you might think that using JSX is like mixing HTML and
JavaScript (and as you’ll see CSS).
But this is not true, because what you are really doing when using JSX
syntax is writing a declarative syntax of what a component UI should be.
And you’re describing that UI not using strings, but instead using
JavaScript, which allows you to do many nice things.
A JSX primer
Here is how you define a h1 tag containing a string:
It looks like a strange mix of JavaScript and HTML, but in reality it’s
all JavaScript.
What looks like HTML, is actually a sugar syntax for defining components
and their positioning inside the markup.
You just need to pay attention when an attribute has a dash ( - ) which is
converted to camelCase syntax instead, and these 2 special cases:
Here’s a JSX snippet that wraps two components into a div tag:
<div>
<BlogPostsList />
<Sidebar />
</div>
A tag always needs to be closed, because this is more XML than HTML (if
you remember the XHTML days, this will be familiar, but since then the
HTML5 loose syntax won). In this case a self-closing tag is used.
Notice how I wrapped the 2 components into a div . Why? Because the
render() function can only return a single node, so in case you want to
return 2 siblings, just add a parent. It can be any tag, not just div .
Transpiling JSX
A browser cannot execute JavaScript files containing JSX code. They must
be first transformed to regular JS.
Plain JS
ReactDOM.render(
React.DOM.div(
{id: "test"},
React.DOM.h1(null, "A title"),
React.DOM.p(null, "A paragraph")
),
document.getElementById('myapp')
)
JSX
ReactDOM.render(
<div id="test">
<h1>A title</h1>
<p>A paragraph</p>
</div>,
document.getElementById('myapp')
)
This very basic example is just the starting point, but you can already
see how more complicated the plain JS syntax is compared to using JSX.
At the time of writing the most popular way to perform the transpilation
is to use Babel, which is the default option when running create-react-
app , so if you use it you don’t have to worry, everything happens under
the hoods for you.
JS in JSX
JSX accepts any kind of JavaScript mixed into it.
Whenever you need to add some JS, just put it inside curly braces {} . For
example here’s how to use a constant value defined elsewhere:
HTML in JSX
JSX resembles a lot HTML, but it’s actually a XML syntax.
In the end you render HTML, so you need to know a few differences between
how you would define some things in HTML, and how you define them in JSX.
Just like in XHTML, if you have ever used it, you need to close all tags:
no more <br> but instead use the self-closing tag: <br /> (the same
goes for other tags)
Due to the fact that JSX is JavaScript, and class is a reserved word,
you can’t write
<p class="description">
Forms
Form fields definition and events are changed in JSX to provide more
consistency and utility.
CSS in React
JSX provides a cool way to define CSS.
If you have a little experience with HTML inline styles, at first glance
you’ll find yourself pushed back 10 or 15 years, to a world where inline
CSS was completely normal (nowadays it’s demonized and usually just a
“quick fix” go-to solution).
JSX style is not the same thing: first of all, instead of accepting a
string containing CSS properties, the JSX style attribute only accepts
an object. This means you define properties in an object:
var divStyle = {
color: 'white'
};
or
ReactDOM.render(<div style={{color: 'white'}}>Hello World!</div>, mountNode);
The CSS values you write in JSX is slightly different than plain CSS:
In short, they cover the basics, but it’s not the final solution.
Forms in JSX
JSX adds some changes to how HTML forms work, with the goal of making
things easier for the developer.
The value attribute always holds the current value of the field.
The defaultValue attribute holds the default value that was set when
the field was created.
This helps solve some weird behavior of regular DOM interaction when
inspecting input.value and input.getAttribute('value') returning
one the current value and one the original default value.
<textarea>Some text</textarea>
but instead
<select>
<option value="x" selected>...</option>
</select>
use
<select defaultValue="x">
<option value="x">...</option>
</select>
This means that you might run into issues when using an HTML entity in a
string expression.
<p>{"© 2017"}</p>
But it’s not, it’s printing © 2017 because the string is escaped.
To fix this you can either move the entities outside the expression:
<p>© 2017</p>
<p>{"\u00A9 2017"}</p>
If you have white space between elements in the same line, it’s all
trimmed to 1 white space.
becomes
<p>Something becomes this</p>
<p>
Something
becomes
this
</p>
becomes
<p>Somethingbecomesthis</p>
To fix this problem you need to explicitly add white space, by adding a
space expression like this:
<p>
Something
{' '}becomes
{' '}this
</p>
<p>
Something
{' becomes '}
this
</p>
<p>
{/* a comment */}
{
//another comment
}
</p>
Spread attributes
In JSX a common operation is assigning values to attributes.
<div>
<BlogPost title={data.title} date={data.date} />
</div>
<div>
<BlogPost {...data} />
</div>
A brief history
Introducing Styled Components
Installation
Your first styled component
Using props to customize components
Extending an existing Styled Component
It’s Regular CSS
Using Vendor Prefixes
Conclusion
A brief history
Once upon a time, the Web was really simple and CSS didn’t even exist. We
laid out pages using tables and frames. Good times.
Then CSS came to life, and after some time it became clear that frameworks
could greatly help especially in building grids and layouts, Bootstrap and
Foundation playing a big part of this.
Preprocessors like SASS and others helped a lot to slow down the
frameworks adoption, and to better organize the code conventions like BEM
and SMACSS grew in their usage, especially within teams.
New tools explored new ways of doing CSS-in-JS and a few succeeded with
increasing popularity:
React Style
jsxstyle
Radium
and more.
Styled Components allow you to write plain CSS in your components without
worrying about class names collisions.
Installation
Simply install styled-components using npm or yarn:
Now this component can be rendered in our container using the normal React
syntax:
render(
<Button />
)
Styled Components offer other functions you can use to create other
components, not just button , like section , h1 , input and many
others.
The syntax used, with the backtick, might be weird at first, but it’s
called Tagged Templates (https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Template_literals) , it’s plain
JavaScript and it’s a way to pass an argument to the function.
For example here’s how we pass the placeholder and type props to an
input component:
render(
<div>
<Input placeholder="@mxstbr" type="text" />
</div>
);
This will do just what you think, inserting those props as HTML
attributes.
Props instead of just being blindly passed down to the DOM can also be
used to customize a component based on the prop value. Here’s an example:
render(
<div>
<Button>A normal button</Button>
<Button>A normal button</Button>
<Button primary>The primary button</Button>
</div>
);
render(
<div>
<Button>A black button, like all buttons</Button>
<WhiteButton>A white button</WhiteButton>
</div>
);
It’s Regular CSS
In Styled Components, you can use the CSS you already know and love. It’s
just plain CSS. It is not pseudo CSS nor inline CSS with its limitations.
Conclusion
That’s it for this Styled Components introduction! These concepts will
help you get an understanding on the concept and help you get up and
running with this way of using CSS in JavaScript.
REDUX
React has its own way to manage state, as you can read on the React
Beginner’s Guide, where I introduce how you can manage State in React.
What I didn’t say in that lesson is that that approach does not scale.
Moving the state up in the tree works in simple cases, but in a complex
app you might find you moving almost all the state up, and then down using
props, so a better approach is to use an external global store.
There are a few concepts to grasp, but once you do, Redux is a very simple
approach to the problem.
Redux is very popular with React applications, but it’s in no way unique
to React: there are bindings for nearly any popular framework. That said,
I’ll make some examples using React as it is its primary use case.
Simple apps should not need it at all (and there’s nothing wrong with
simple apps)
Actions
An Action is a JavaScript object that describes a change in a minimalistic
way (just with the information needed):
{
type: 'CLICKED_SIDEBAR'
}
Action creators
function addItem(t) {
return {
type: ADD_ITEM,
title: t
}
}
You usually run action creators in combination with triggering the
dispatcher:
dispatch(addItem('Milk'))
Reducers
When an action is fired, something must happen, the state of the
application must change.
What is a reducer
A reducer is a pure function that calculates the next State Tree based on
the previous State Tree, and the action dispatched.
A pure function takes an input and returns an output without changing the
input nor anything else. Thus, a reducer returns a completely new state
tree object that substitutes the previous one.
Multiple reducers
Since the state of a complex app could be really wide, there is not a
single reducer, but many reducers for any kind of action.
A simulation of a reducer
The state
{
list: [
{ title: "First item" },
{ title: "Second item" },
],
title: 'Grocieries list'
}
A list of actions
The Store
The Store is an object that:
store.getState()
Update the state
store.dispatch(addItem('Something'))
unsubscribe()
Data Flow
Data flow in Redux is always unidirectional.
The Store takes care of passing the Action to the Reducer, generating the
next State.
The Store updates the State and alerts all the Listeners.
REDUX SAGA
Redux Saga is a library used to handle side effects in Redux
As this happens, you might need to do something that derives from this
state change.
Enter Redux Saga, a Redux middleware helping you with side effects.
You initialize Redux Saga by first importing it, then by applying a saga
as a middleware to the Redux Store:
//...
import createSagaMiddleware from 'redux-saga'
//...
Then we create a middleware and we apply it to our newly created Redux
Store:
The last step is running the saga. We import it and pass it to the run
method of the middleware:
What this code means is: every time the ADD_MESSAGE action fires, we
send a message to the WebSockets server, which responds in this case on
localhost:8989 .
A saga is some “story” that reacts to an effect that your code is causing.
That might contain one of the things we talked before, like an HTTP
request or some procedure that saves to the cache.
Once the promise is resolved the middleware resumes the saga, until the
next yield statement is found, and there it is suspended again until its
promise resolves.
Inside the saga code, you will generate effects using a few special helper
functions provided by the redux-saga package. To start with, we can
list:
takeEvery()
takeLatest()
take()
call()
put()
For example:
Basic Helpers
Helpers are abstractions on top of the low-level saga APIs.
Let’s introduce the most basic helpers you can use to run your effects:
takeEvery()
takeLatest()
take()
put()
call()
takeEvery()
In the code:
function* watchMessages() {
yield takeEvery('ADD_MESSAGE', postMessageToServer)
}
As with takeEvery() , the generator never stops and continues to run the
effect when the specified action occurs.
take()
take() is different in that it only waits a single time. When the action
it’s waiting for occurs, the promise resolves and the iterator is resumed,
so it can go on to the next instruction set.
put()
which returns a plain object that you can easily inspect in your tests
(more on testing later).
call()
When you want to call some function in a saga, you can do so by using a
yielded plain function call that returns a promise:
delay(1000)
but this does not play nice with tests. Instead, call() allows you to
wrap that function call and returns an object that can be easily
inspected:
call(delay, 1000)
returns
all()
If you write
the second fetch() call won’t be executed until the first one succeeds.
race()
race() differs from all() by not waiting for all of the helpers calls
to return. It just waits for one to return, and it’s done.
It’s a race to see which one finishes first, and then we forget about the
other participants.
It’s typically used to cancel a background task that runs forever until
something occurs:
function* someBackgroundTask() {
while(1) {
//...
}
}
yield call([
bgTask: call(someBackgroundTask),
cancel: take('CANCEL_TASK')
])
when the CANCEL_TASK action is emitted, we stop the other task that
would otherwise run forever.
GRAPHQL
GRAPHQL
GraphQL is a query language for your API, and a set of server-side
runtimes (implemented in various backend languages) for executing
queries
What is GraphQL
GraphQL Principles
GraphQL vs REST
Rest is a concept
A single endpoint
Tailored to your needs
GraphQL makes it easy to monitor for fields usage
Access nested data resources
Types
Which one is better?
GraphQL Queries
Fields and arguments
Aliases
Fragments
GraphQL Variables
Making variables required
Specifying a default value for a variable
GraphQL Directives
@include(if: Boolean)
@skip(if: Boolean)
What is GraphQL
GraphQL is the new frontier in APIs (Application Programming Interfaces).
It’s a query language for your API, and a set of server-side runtimes
(implemented in various backend languages) for executing queries.
It’s not tied to a specific technology, but you can implement it in any
language.
GraphQL was developed at Facebook, like many of the technologies that are
shaking the world lately, like React and React Native, and it was publicly
launched in 2015 - although Facebook used it internally for a few years
before.
Many big companies are adopting GraphQL beside Facebook, including GitHub,
Pinterest, Twitter, Sky, The New York Times, Shopify, Yelp and thousands
many other.
GraphQL Principles
GraphQL exposes a single endpoint.
Let’s see a first example of such a query. This query gets the name of a
person with id=1 :
or simply
{
person(id: "1") {
name
}
}
Let’s add a bit more complexity: we get the name of the person, and the
city where the person lives, by extracting it from the address object.
We don’t care about other details of the address, and the server does not
return them back to us.
or
{
person(id: "1") {
name
address {
city
}
}
}
{
"name": "Tony",
"address": {
"city": "York"
}
}
As you can see the data we get is basically the same request we sent,
filled with values.
GraphQL vs REST
Since REST is such a popular, or I can say universal, approach to building
APIs, it’s fair to assume you are familiar with it, so let’s see the
differences between GraphQL and REST.
Rest is a concept
REST is a de-facto architecture standard but it actually has no
specification and tons of unofficial definitions. GraphQL has a
specification (http://facebook.github.io/graphql/) draft, and it’s a Query
Language (http://graphql.org/learn/queries/) instead of an architecture,
with a well defined set of tools built around it (and a flourishing
ecosystem).
A single endpoint
GraphQL has only one endpoint, where you send all your queries. With a
REST approach, you create multiple endpoints and use HTTP verbs to
distinguish read actions (GET) and write actions (POST, PUT, DELETE).
GraphQL does not use HTTP verbs to determine the request type.
With REST, you generally cannot choose what the server returns back to
you, unless the server implements partial responses using sparse fieldsets
(http://jsonapi.org/format/#fetching-sparse-fieldsets) , and clients use
that feature. The API maintainer cannot enforce such filtering.
The API will usually return you much more information than what you need,
unless you control the API server as well, and you tailor your responses
for each different request.
With GraphQL you explicitly request just the information you need, you
don’t “opt out” from the full response default, but it’s mandatory to pick
the fields you want.
This helps saving resources on the server, since you most probably need
less processing, and also network savings, since the payload to transfer
is smaller.
Unless the list of friends of a person already contains the friend name,
with 100 friends you’d need to do 101 HTTP requests to the /person
endpoint, which is a huge time cost, and also a resource intensive
operation.
With GraphQL, you need only one request, which asks for the names of the
friends of a person.
Types
A REST API is based on JSON which cannot provide type control. GraphQL has
a Type System.
Which one is better?
GraphQL Queries
In this article you’ll learn how is a GraphQL query composed.
{
person(id: "1") {
name
}
}
The field person returns an Object which has another field in it, a
String.
The argument allows us to specify which person we want to reference. We
pass an id , but we could as well pass a name argument, if the API we
talk to has the option to find a person by name.
{
person(id: "1") {
name
friends(limit: 100)
}
}
Aliases
You can ask the API to return a field with a different name, for example:
{
owner: person(id: "1") {
fullname: name
}
}
will return
{
"data": {
"owner": {
"fullname": "Tony"
}
}
}
This feature, beside creating more ad-hoc naming for your client code, is
the only thing that can make the query work if you need to reference the
same endpoint 2 times in the same query:
{
owner: person(id: "1") {
fullname: name
}
first_employee: person(id: "2") {
fullname: name
}
}
Fragments
{
owner: person(id: "1") {
...personFields
}
first_employee: person(id: "2") {
...personFields
}
}
GraphQL Variables
More complex GraphQL queries need to use variables, a way to dynamically
specify a value that is used inside a query.
{
owner: person(id: "1") {
fullname: name
}
}
{
"id": "1"
}
In this snippet we have assigned the GetOwner name to our query. Think
of it as named functions, while previously you had an anonymous function.
Named queries are useful when you have lots of queries in your
application.
The query definition with the variables looks like a function definition,
and it works in an equivalent way.
GraphQL Directives
Directives let you include or exclude a field if a variable is true or
false.
{
"id": "1",
"getAddress": false
}
@include(if: Boolean)
{
"id": "1",
"getAddress": false
}
@skip(if: Boolean)
Introduction to Apollo
Apollo Client
Start a React app
Get started with Apollo Boost
Create an ApolloClient object
Apollo Links
Caching
Use ApolloProvider
The gql template tag
Perform a GraphQL request
Obtain an access token for the API
Use an Apollo Link to authenticate
Render a GraphQL query result set in a component
Apollo Server
Launchpad
The Apollo Server Hello World
Run the GraphQL Server locally
Your first Apollo Server code
Add a GraphiQL endpoint
Introduction to Apollo
In the last few years GraphQL got hugely popular as an alternative
approach to building an API over REST.
GraphQL is a great way to let the client decide which data they want to be
transmitted over the network, rather than having the server send a fixed
set of data.
Also, it allows you to specify nested resources, reducing a lot the back
and forth sometimes required when dealing with REST APIs.
Apollo is a team and community that builds on top of GraphQL, and provides
different tools that help you build your projects.
Apollo Client helps you consume a GraphQL API, with support for the most
popular frontend web technologies including React, Vue, Angular, Ember,
Meteor and more, and native development on iOS and Android.
Apollo Server is the server part of GraphQL, which interfaces with your
backend and sends responses back to the client requests.
It’s worth noting that those 3 tools are not linked together in any way,
and you can use just Apollo Client to interface with a 3rd part API, or
serve an API using Apollo Server without having a client at all, for
example.
But it’s very convenient to have all those tools together under a single
roof, a complete suite for all your GraphQL-related needs.
Apollo strives to be easy to use and easy to contribute to.
Apollo Client and Apollo Server are all community projects, built by the
community, for the community. Apollo is backed by the Meteor Development
Group (https://www.meteor.io/) , the company behind Meteor, a very popular
JavaScript framework.
Apollo Client
Apollo Client (https://www.apollographql.com/client) is the leading
JavaScript client for GraphQL. Community-driven, it’s designed to let you
build UI components that interface with GraphQL data, either in displaying
data, or in performing mutations when certain actions happen.
Most of all, Apollo Client is built to be simple, small and flexible from
the ground up.
In this post I’m going to detail the process of using Apollo Client within
a React application.
yarn start
Open src/index.js :
Apollo Boost is the easiest way to start using Apollo Client on a new
project. We’ll install that in addition to react-apollo and graphql .
or with npm:
Apollo Links
Apollo Link provides us a way to describe how we want to get the result of
a GraphQL operation, and what we want to do with the response.
In short, you create multiple Apollo Link instances that all act on a
GraphQL request one after another, providing the final result you want.
Some Links can give you the option of retrying a request if not
successful, batching and much more.
We’ll add an Apollo Link to our Apollo Client instance to use the GitHub
GraphQL endpoint URI https://api.github.com/graphql
Caching
We’re not done yet. Before having a working example we must also tell
ApolloClient which caching strategy
(https://www.apollographql.com/docs/react/basics/caching.html) to use:
InMemoryCache is the default and it’s a good one to start.
Use ApolloProvider
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
)
We’re now ready to do something with Apollo Client, and we’re going to
fetch some data from the GitHub API and render it.
any GraphQL query will be built using this template tag, like this:
We’re now ready to do something with Apollo Client, and we’re going to
fetch some data from the GitHub API and render it.
GitHub makes it easy by providing an interface from which you select any
permission you might need:
For the sake of this example tutorial you don’t need any of those
permissions, they are meant for access to private user data but we will
just query the public repositories data.
The token you get is an OAuth 2.0 Bearer token.
{"data":{"viewer":{"login":"***_YOUR_LOGIN_NAME_***"}}}
or
{
"message": "Bad credentials",
"documentation_url": "https://developer.github.com/v4"
}
So, we need to send the Authorization header along with our GraphQL
request, just like we did in the curl request above.
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})
Here is the full code for the src/index.js file with the code we have
right now:
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
}
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
)
We can now make the first GraphQL request at the bottom of this file, and
this sample query asks for the names and the owners of the 10 most popular
repositories, with more than 50k stars:
Running this code successfully returns the result of our query in the
browser console:
We let Apollo Client the burden (or joy) or fetching the data and handling
all the low-level stuff, and we can focus on showing the data, by using
the graphql component enhancer offered by react-apollo :
Express (https://expressjs.com/)
Hapi (https://hapijs.com/)
Koa (http://koajs.com/)
Restify (http://restify.com/)
For the sake of learning the basics of Apollo Server, we’re not going to
use any of the supported Node.js frameworks. Instead, we’ll be using
something that was built by the Apollo team, something really great which
will be the base of our learning: Launchpad.
Launchpad
Every project on Launchpad is called pad and has its GraphQL endpoint URL,
like:
https://1jzxrj129.lp.gql.zone/graphql
Once you build a pad, Launchpad gives you the option to download the full
code of the Node.js app that’s running it, and you just need to run npm
install and npm start to have a local copy of your Apollo GraphQL
Server.
Every time you create a new Launchpad pad, you are presented with the
Hello, World! of Apollo Server. Let’s dive into it.
const typeDefs = `
type Query {
hello: String
}
`
A resolver is an object that maps fields in the schema to resolver
functions, able to lookup data to respond to a query.
Here is a simple resolver containing the resolver function for the hello
field, which simply returns the Hello world! string:
const resolvers = {
Query: {
hello: (root, args, context) => {
return 'Hello world!'
}
}
}
Given those 2 elements, the schema definition and the resolver, we use the
makeExecutableSchema function we imported previously to get a
GraphQLSchema object, which we assign to the schema const.
This is all you need to serve a simple read-only API. Launchpad takes care
of the tiny details.
Here is the full code for the simple Hello World example:
const typeDefs = `
type Query {
hello: String
}
`
const resolvers = {
Query: {
hello: (root, args, context) => {
return 'Hello world!'
}
}
}
and as said previously the API can is publicly accessible, you just need
to login and save your pad.
$ curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ hello }" }' \
https://kqwwkp0pr7.lp.gql.zone/graphql
{
"data": {
"hello": "Hello world!"
}
}
First, run npm install and npm start on the Launchpad code you
downloaded.
server.listen(3000, () => {
console.log('GraphQL listening at http://localhost:3000/graphql')
})
With just 11 lines, this is much simpler than the server set up by
Launchpad, because we removed all the things that made that code more
flexible for their needs.
Coding is 50% deciding how much flexibility you need now, versus how
more important is to have clean, well understandable code that you
can pick up 6 months from now and easily tweak, or pass to other
developers and team members and be productive in as little time as
needed.
graphqlExpress({ schema })
server.listen(3000, () => {
console.log('GraphQL listening at http://localhost:3000/graphql')
})
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
query: ``
}))
server.listen(PORT, () => {
console.log('GraphQL listening at http://localhost:3000/graphql')
console.log('GraphiQL listening at http://localhost:3000/graphiql')
})
$ curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ hello }" }' \
http://localhost:3000/graphql
This will give you the same result as above, where you called the
Launchpad servers:
{
"data": {
"hello": "Hello world!"
}
}
GIT AND GITHUB
GIT
Git is a free and Open Source *version control system* (VCS), a
technology used to track older versions of files, providing the ability
to roll back and maintain separate different versions at the same time
What is Git
Distributed VCS
Installing Git
OSX
Windows
Linux
Initializing a repository
Adding files to a repository
Add the file to the staging area
Commit changes
Branches
Push and pull
Add a remote
Push
Pull
Conflicts
Command Line vs Graphical Interface
GitHub Desktop
Tower
GitKraken
A good Git workflow
The feature is a quick one
The feature will take more than one commit to finish
Hotfix
Develop is unstable. Master is the latest stable release
What is Git
Git is a free and Open Source version control system (VCS), a technology
used to track older versions of files, providing the ability to roll back
and maintain separate different versions at the same time.
Git is a successor of SVN and CVS, two very popular version control
systems of the past. First developed by Linus Torvalds (the creator of
Linux), today is the go-to system which you can’t avoid if you make use of
Open Source software.
Distributed VCS
Installing Git
Installing Git is quite easy on all platforms:
OSX
Using Homebrew (http://brew.sh/) , run:
Windows
Linux
or
Initializing a repository
Once Git is installed on your system, you are able to access it using the
command line by typing git .
Suppose you have a clean folder. You can initialize a Git repository by
typing
git init
What this command does? It creates a .git folder in the folder where you
ran it. If you don’t see it, it’s because it’s a hidden folder, so it
might not be shown everywhere, unless you set your tools to show hidden
folders.
to create a file. The file is now in the directory, but Git was not told
to add it to its index, as you can see what git status tells us:
Add the file to the staging area
But usually what you do once you add a file is commit it.
Commit changes
Once you have one or more changes to the staging area, you can commit them
using
and permanently stores the edit you made into a record store, which you
can inspect by typing git log :
Branches
When you commit a file to Git, you are committing it into the current
branch.
Git is very flexible: you can have an indefinite number of branches active
at the same time, and they can be developed independently until you want
to merge one of them into another.
Git by default creates a branch called master . It’s not special in any
way other than it’s the one created initially.
When creating the new branch, that branch points to the latest commit made
on the current branch. If you switch to it (using git checkout
develop ) and run git log , you’ll see the same log as the branch that
you were previously.
You work offline, do as many commits as you want, and once you’re ready
you push them to the server, so your team members, or the community if you
are pushing to GitHub, can access your latest and greatest code.
Before you can play with push and pull, however, you need to add a remote!
Add a remote
Push
Once you’re done, you can push your code to the remote, using the syntax
git push <remote> <branch> , for example:
You specify origin as the remote, because you can technically have more
than one remote. That is the name of the one we added previously, and it’s
a convention.
Pull
Conflicts
This happens when the remote contains changes subsequent to your latest
pull, which affect lines of code you worked on as well.
In the case of pull, your working copy will automatically be edited with
the conflicting changes, and you need to solve them, and make a new commit
so the codebase now includes the problematic changes that were made on the
remote.
This was key to introduce you to how Git actually works, but in the day-
to-day operations, you are most likely to use an app that exposes you
those commands via a nice UI, although many developers I know like to use
the CLI.
GitHub Desktop
https://desktop.github.com (https://desktop.github.com)
Free, at the time of writing only available for Mac and Win
Tower
https://www.git-tower.com (https://www.git-tower.com)
Paid, at the time of writing only available for Mac and Win
GitKraken
https://www.gitkraken.com (https://www.gitkraken.com)
Free / Paid depending on the needs, for Mac, Win and Linux
The commits I’ll make won’t break the code (or at least I hope so): I can
commit on develop, or do a quick feature branch, and then merge it to
develop.
Maybe it will take days of commits before the feature is finished and it
gets stable again: I do a feature branch, then merge to develop once ready
(it might take weeks).
Hotfix
The develop branch will always be in a state of flux, that’s why it should
be put on a ‘freeze’ when preparing a release. The code is tested and
every workflow is checked to verify code quality, and it’s prepared for a
merge into master.
Every time develop or another hotfix branch is merged into master, I tag
it with a version number, and if on GitHub I also create a release, so
it’s easy to move back to a previous state if something goes wrong.
GITHUB
Learn all the most important pieces of GitHub that you should know as a
developer
Introduction to GitHub
Why GitHub?
GitHub issues
Social coding
Follow
Stars
Fork
Popular = better
Pull requests
Project management
Comparing commits
Webhooks and Services
Webhooks
Services
Final words
Introduction to GitHub
GitHub is a website where millions of developers gather every day to
collaborate on open source software. It’s also the place that hosts
billions of lines of code, and also a place where users of software go to
report issues they might have.
In short, it’s a platform for software developers, and it’s built around
Git.
TIP: If you don’t know about Git yet, checkout the Git guide.
As a developer you can’t avoid using GitHub daily, either to host your
code or to make use of other people’s code. This post explains you some
key concepts of GitHub, and how to use some of its features that improve
your workflow, and how to integrate other applications into your process.
Why GitHub?
Now that you know what GitHub is, you might ask why you should use it.
Major codebases migrated over time to Git from other version control
systems, because of its convenience, and GitHub was historically well
positioned into (and put a lot of effort to “win”) the Open Source
community.
So today any time you look up some library, you will 99% of the times find
it on GitHub.
Apart from Open Source code, many developers also host private
repositories on GitHub because of the convenience of a unique platform.
GitHub issues
GitHub issues are one of the most popular bug tracker in the world.
Sometimes you’ll get a definitive answer, other times the issue will be
left open and tagged with some information that categorizes it, and the
developer could get back to it to fix a problem or improve the codebase
with your feedback.
Most developers are not paid to support their code released on GitHub, so
you can’t expect prompt replies, but other times Open Source repositories
are published by companies that either provide services around that code,
or have commercial offerings for versions with more features, or a plugin-
based architecture, in which case they might be working on the open source
software as paid developers.
Social coding
Some years ago the GitHub logo included the “social coding” tagline.
What did this mean, and is that still relevant? It certainly is.
Follow
With GitHub you can follow developers, by going on their profile and
clicking “follow”.
In both cases the activity will show up in your dashboard. You don’t
follow like in Twitter, where you see what people say, but you see what
people do.
Stars
One big feat of GitHub is the ability to star a repository. This action
will include it in your “starred repositories” list, which allows you to
find things you found interesting before, and it’s also one of the most
important rating mechanisms, as the more stars a repo has, the more
important it is, and the more it will show up in search results.
Getting into those trending lists can cause other network effects like
being featured on other sites, just because you have more visibility.
Fork
This is key to how GitHub works, as a fork is the base of a Pull Request
(PR), a change proposal. Starting from your repository, a person forks it,
makes some changes, then creates a PR to ask you to merge those changes.
Sometimes the person that forks never asks you to merge anything, just
because they liked your code and decided to add something on top of it, or
they fixed some bug they were experiencing.
Popular = better
All in all, those are all key indicators of the popularity of a project,
and generally along with the date of the latest commit and the involvement
of the author in the issues tracker, is a useful indication of whether or
not you should rely on a library or software.
Pull requests
Before I introduced what is a Pull Request (PR)
Starting from your repository, a person forks it, makes some changes, then
creates a PR to ask you to merge those changes.
Once a person submits a PR, an easy process using the GitHub interface, it
needs to be reviewed by the core maintainers of the project.
In the example i posted above, there is a PR in the repo that dates back
1.5 years. And this happens in all the projects.
Project management
Along with issues, which are the place where developers get feedback from
users, the GitHub interface offers other features aimed at helping project
management.
One of those is Projects. It’s very new in the ecosystem and very rarely
used, but it’s a kanban board that helps organizing issues and work that
needs to be done.
Comparing commits
GitHub offers many tools to work with your code.
One of the most important things you might want to do is compare one
branch to another one. Or, compare the latest commit with the version you
are currently using, to see which changes were made over time.
GitHub allows you to do this with the compare view, just add /compare
to the repo name, for example: https://github.com/facebook/react/compare
(https://github.com/facebook/react/compare)
For example here I choose to compare the latest React v15.x to the latest
v16.0.0-rc version available at the time of writing, to check what’s
changed:
The view shows you the commits made between two releases (or tags or
commits references) and the actual diff, if the number of changes is lower
than a reasonable amount.
Webhooks
Webhooks allow external services to be pinged when certain events happen
in the repository, like when code is pushed, a fork is made, a tag was
created or deleted.
When an event happens, GitHub sends a POST request to the URL we told it
to use.
We push to GitHub, GitHub tells the server we pushed, the server pulls
from GitHub.
Services
GitHub services, and the new GitHub apps, are 3rd part integrations that
improve the developer experience or provide a service to you.
For example you can setup a test runner to run the tests automatically
every time you push some new commits, using TravisCI (https://travis-
ci.org/) .
Final words
GitHub is an amazing tool and service to take advantage of, a real gem in
today’s developer toolset. This tutorial will help you start, but the real
experience of working on GitHub on open source (or closed source) projects
is something not to be missed.
A GIT CHEAT SHEET
Some useful Git commands I find handy to know
git rebase -i
Type s to apply squash to a commit with the previous one. Repeat the s
command for as many commits as you need.
git status -s
git shortlog -s -n
git rm -r --cached
Get the name of the branch where a specific commit was made
Building layouts for the web has traditionally been a complicated topic.
I won’t dig into the reasons for this complexity, which is a complex topic
on its own, but you can think yourself as a very lucky human because
nowadays you have 2 very powerful and well supported tools at your
disposal:
CSS Flexbox
CSS Grid
These 2 are the tools to build the Web layouts of the future.
Unless you need to support old browsers like IE8 and IE9, there is no
reason in 2017 to be messing with things like:
Table layouts
Floats
clearfix hacks
display: table hacks
In this guide there’s all you need to know about going from a zero
knowledge of CSS Grid to being a proficient user.
The basics
The CSS Grid layout is activated on a container element (which can be a
div or any other tag) by setting display: grid .
As with flexbox, you can define some properties on the container, and some
properties on each individual item in the grid.
These properties combined will determine the final look of the grid.
The most basic container properties are grid-template-columns and
grid-template-rows .
Those properties define the number of columns and rows in the grid, and
they also set the width of each column/row.
The following snippet defines a grid with 4 columns, each 400px wide, and
with 3 rows with a 100px height each.
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
}
.container {
display: grid;
grid-template-columns: 200px 200px;
grid-template-rows: 100px 100px 100px;
}
Automatic dimensions
Many times you might have a fixed header size, a fixed footer size, and
the main content that is flexible in height, depending on its length. In
this case you can use the auto keyword:
.container {
display: grid;
grid-template-rows: 100px auto 100px;
}
In the above examples we made regular grids by using the same values for
rows and the same values for columns.
You can specify any value for each row/column, to create a lot of
different designs:
.container {
display: grid;
grid-template-columns: 100px 200px;
grid-template-rows: 100px 50px;
}
Another example:
.container {
display: grid;
grid-template-columns: 10px 100px;
grid-template-rows: 100px 10px;
}
Example:
.container {
display: grid;
grid-template-columns: 100px 200px;
grid-template-rows: 100px 50px;
grid-column-gap: 25px;
grid-row-gap: 25px;
}
.container {
display: grid;
grid-template-columns: 100px 200px;
grid-template-rows: 100px 50px;
grid-gap: 25px;
}
Every cell item has the option to occupy more than just one box in the
row, and expand horizontally or vertically to get more space, while
respecting the grid proportions set in the container.
Those are the properties we’ll use for that: - grid-column-start -
grid-column-end - grid-row-start - grid-row-end
Example:
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
}
.item1 {
grid-column-start: 2;
grid-column-end: 4;
}
.item6 {
grid-column-start: 3;
grid-column-end: 5;
}
The numbers correspond to the vertical line that separates each column,
starting from 1:
The same principle applies to grid-row-start and grid-row-end ,
except this time instead of taking more columns, a cell takes more rows.
Shorthand syntax
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
}
.item1 {
grid-column: 2 / 4;
}
.item6 {
grid-column: 3 / 5;
}
Another approach is to set the starting column/row, and set how many it
should occupy using span :
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
}
.item1 {
grid-column: 2 / span 2;
}
.item6 {
grid-column: 3 / span 2;
}
Using fractions
Specifying the exact width of each column or row is not ideal in every
case.
The following example divides a grid into 3 columns with the same width,
1⁄
3 of the available space each.
.container {
grid-template-columns: 1fr 1fr 1fr;
}
You can also use percentages, and mix and match fractions, pixels, rem and
percentages:
.container {
grid-template-columns: 3rem 15% 1fr 2fr
}
Using repeat()
If every column has the same width you can specify the layout using this
syntax:
.container {
grid-template-columns: repeat(4, 100px);
}
Or using fractions:
.container {
grid-template-columns: repeat(4, 1fr);
}
Common use case: Have a sidebar that never collapses more than a certain
amount of pixels when you resize the window.
Here’s an example where the sidebar takes 1⁄4 of the screen and never
takes less than 200px:
.container {
grid-template-columns: minmax(200px, 3fr) 9fr;
}
You can also set just a maximum value using the auto keyword:
.container {
grid-template-columns: minmax(auto, 50%) 9fr;
}
By default elements are positioned in the grid using their order in the
HTML structure.
Here’s an example:
<div class="container">
<main>
...
</main>
<aside>
...
</aside>
<header>
...
</header>
<footer>
...
</footer>
</div>
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
grid-template-areas:
"header header header header"
"sidebar main main main"
"footer footer footer footer";
}
main {
grid-area: main;
}
aside {
grid-area: sidebar;
}
header {
grid-area: header;
}
footer {
grid-area: footer;
}
You can set an empty cell using the dot . instead of an area name in
grid-template-areas :
.container {
display: grid;
grid-template-columns: 200px 200px 200px 200px;
grid-template-rows: 300px 300px;
grid-template-areas:
". header header ."
"sidebar . main main"
". footer footer .";
}
Wrapping up
These are the basics of CSS Grid. There are many things I didn’t include
in this introduction but I wanted to make it very simple, to start using
this new layout system without making it feel overwhelming.
FLEXBOX
Flexbox, also called Flexible Box Module, is one of the two modern
layouts systems, along with CSS Grid
Introduction
Browser support
Enable Flexbox
Container properties
Rows or columns
Vertical and horizontal alignment
Horizontal alignment
Vertical alignment
A note on baseline
Wrap
Single item properties
Moving items before / after another one
Vertical alignment
Grow or shrink an item if necessary
flex-grow
flex-shrink
flex-basis
flex
Introduction
Flexbox, also called Flexible Box Module, is one of the two modern layouts
systems, along with CSS Grid.
Unless you need to support old browsers like IE8 and IE9, Flexbox is the
tool that lets you forget about using
Table layouts
Floats
clearfix hacks
display: table hacks
Let’s dive into flexbox and become a master of it in a very short time.
Browser support
The best way to find out if you can use flexbox in your designs is to
check the caniuse.com flexbox page (https://caniuse.com/#feat=flexbox) .
At the time of writing (Feb 2018), it’s supported by 97.66% of the users,
all the most important browsers implement it since years, so even older
browsers (including IE10+) are covered.
While we must wait a few years for users to catch up on CSS Grid, Flexbox
is an older technology and can be used right now.
Enable Flexbox
A flexbox layout is applied to a container, by setting display: flex;
or display: inline-flex; .
Container properties
Some flexbox properties apply to the container, which sets the general
rules for its items. They are
flex-direction
justify-content
align-items
flex-wrap
flex-flow
Rows or columns
Horizontal alignment
Wrap
order
align-self
flex-grow
flex-shrink
flex-basis
flex
Let’s see them in details.
Items are ordered based on a order they are assigned. By default every
item has order 0 and the appearance in the HTML determines the final
order.
You can override this property using order on each separate item. This
is a property you set on the item, not the container. You can make an item
appear before all the others by setting a negative value.
Vertical alignment
flex-grow
If all items are defined as 1 and one is defined as 2, the bigger element
will take the space of two “1” items.
flex-shrink
If all items are defined as 1 and one is defined as 3, the bigger element
will shrink 3x the other ones. When less space is available, it will take
3x less space.
flex-basis
If set to 0, it does not add any extra space for the item when calculating
the layout.
If you specify a pixel number value, it will use that as the length value
(width or height depends if it’s a row or a column item)
flex
This property combines the above 3 properties:
flex-grow
flex-shrink
flex-basis
Introduction
The basics of using variables
Create variables inside any element
Variables scope
Interacting with a CSS Variable value using JavaScript
Handling invalid values
Browser support
CSS Variables are case sensitive
Math in CSS Variables
Media queries with CSS Variables
Setting a fallback value for var()
Introduction
In the last few years CSS preprocessors had a lot of success. It was very
common for greenfield projects to start with Less or Sass. And it’s still
a very popular technology.
Modern CSS has a new powerful feature called CSS Custom Properties, also
commonly known as CSS Variables.
:root {
--primary-color: yellow;
}
p {
color: var(--primary-color)
}
The variable value can be any valid CSS value, for example:
:root {
--default-padding: 30px 30px 20px 20px;
--default-color: red;
--default-background: #fff;
}
:root {
--default-color: red;
}
body {
--default-color: red;
}
main {
--default-color: red;
}
p {
--default-color: red;
}
span {
--default-color: red;
}
a:hover {
--default-color: red;
}
Variables scope
Adding variables to a selector makes them available to all the children of
it.
In the example above you saw the use of :root when defining a CSS
variable:
:root {
--primary-color: yellow;
}
It’s just like targeting the html element, except that :root has
higher specificity (takes priority).
:root {
--primary-color: yellow;
}
.container {
--primary-color: blue;
}
You can also assign or overwrite a variable inside the HTML using inline
styles:
CSS Variables follow the normal CSS cascading rules, with precedence
set according to specificity
Browser support
Browser support for CSS Variables at the time of writing (Mar 2018) is
very good, according to Can I Use (https://www.caniuse.com/#feat=css-
variables) .
CSS Variables are here to stay, and you can use them today if you don’t
need to support Internet Explorer and old versions of the other browsers.
If you need to support older browsers you can use libraries like PostCSS
or Myth (http://www.myth.io/) , but you’ll lose the ability to interact
with variables via JavaScript or the Browser Developer Tools, as they are
transpiled to good old variable-less CSS (and as such, you lose most of
the power of CSS Variables).
--width: 100px;
is different than:
--Width: 100px;
:root {
--default-left-padding: calc(10px * 2);
}
body {
--width: 500px;
}
.container {
width: var(--width);
}
.container {
margin: var(--default-margin, 30px);
}
POSTCSS
Discover PostCSS, a great tool to help you write modern CSS
Introduction
Why it’s so popular
Install the PostCSS CLI
Most popular PostCSS plugins
Autoprefixer
cssnext
CSS Modules
csslint
cssnano
Other useful plugins
How is it different than Sass?
Introduction
PostCSS is a very popular tool that allows developers to write CSS pre-
processors or post-processors, called plugins. There is a huge number of
plugins that provide lots of functionalities, and sometimes the term
“PostCSS” means the tool itself, plus the plugins ecosystem.
PostCSS plugins can be run via the command line, but they are generally
invoked by task runners at build time.
The plugin-based architecture provides a common ground for all the CSS-
related operations you need.
or npm:
Once this is done, the postcss command will be available in your command
line.
This command for example runs the autoprefixer plugin on CSS files
contained in the css/ folder, and save the result in the main.css file:
Here are some of the most popular plugins, to get an overview of what’s
possible to do with PostCSS.
Autoprefixer
Example:
a {
display: flex;
}
gets compiled to
a {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
cssnext
https://github.com/MoOx/postcss-cssnext (https://github.com/MoOx/postcss-
cssnext)
This plugin is the Babel of CSS, allows you to use modern CSS features
while it takes care of transpiling them to a CSS more digestible to older
browsers:
CSS Modules
CSS Modules are not part of the CSS standard, but they are a build step
process to have scoped selectors.
csslint
Linting helps you write correct CSS and avoid errors or pitfalls. The
stylint (http://stylelint.io/) plugin allows you to lint CSS at build
time.
cssnano
On the PostCSS GitHub repo there is a full list of the available plugins
(https://github.com/postcss/postcss/blob/master/docs/plugins.md) .
The main benefit PostCSS provides over CSS preprocessors like Sass or Less
is the ability to choose your own path, and cherry-pick the features you
need, adding new capabilities at the same time. Sass or Less are “fixed”,
you get lots of features which you might or might not use, and you cannot
extend them.
The fact that you “choose your own adventure” means that you can still use
any other tool you like alongside PostCSS. You can still use Sass if this
is what you want, and use PostCSS to perform other things that Sass can’t
do, like autoprefixing or linting.
You can write your own PostCSS plugin to do anything you want.
HOW TO CENTER THINGS WITH CSS
Centering elements with CSS has always been easy for some things, hard
for others. Here is the full list of centering techniques, with modern
CSS techniques as well
In this post I explain the most common scenarios and how to solve them. If
a new solution is provided by Flexbox I ignore the old techniques because
we need to move forward, and Flexbox is supported by browsers since years,
IE10 included.
Center horizontally
Text
p {
text-align: center;
}
Blocks
The modern way to center anything that is not text is to use Flexbox:
#mysection {
display: flex;
justify-content: center;
}
any element inside #mysection will be horizontally centered.
Center horizontally
section {
margin-left: 0 auto;
width: 50%;
}
section {
margin-top: 0;
margin-bottom: 0;
margin-left: auto;
margin-right: auto;
}
Center vertically
Traditionally this has always been a difficult task. Flexbox now provides
us a great way to do this in the simplest possible way:
#mysection {
display: flex;
align-items: center;
}
Center vertically
Center both vertically and horizontally
Techniques to center vertically and horizontally can be combined to
completely center an element in the page.
#mysection {
display: flex;
align-items: center;
justify-content: center;
}
Introduction
Specific margin properties
Using margin with different values
1 value
2 values
3 values
4 values
Values accepted
Using auto to center elements
Introduction
The margin CSS property is commonly used in CSS to add space around an
element.
Remember:
margin-top
margin-right
margin-bottom
margin-left
The usage of those is very simple and cannot be confused, for example:
margin-left: 30px;
margin-right: 3em;
1 value
Using a single value applies that to all the margins: top, right, bottom,
left.
margin: 20px;
2 values
Using 2 values applies the first to bottom & top, and the second to left &
right.
3 values
Using 3 values applies the first to top, the second to left & right, the
third to bottom.
4 values
Using 4 values applies the first to top, the second to right, the third to
bottom, the fourth to left.
Values accepted
margin accepts values expressed in any kind of length unit, the most
common ones are px, em, rem, but many others exist
(https://developer.mozilla.org/en-US/docs/Web/CSS/length) .
margin: 0 auto;
As said above, using 2 values applies the first to bottom & top, and the
second to left & right.
The modern way to center elements is to use Flexbox, and its justify-
content: center; directive.
Introduction
Why Netlify
Advanced functionality for Static Sites
Note: this post is not sponsored by Netlify nor has affiliate links
Introduction
I recently switched my blog hosting from Firebase Hosting, operated by
Google, to Netlify (https://www.netlify.com) .
I did so while Firebase was having some issues that made my site
unreachable for a few hours, and while I waited for it to get up online
again, I created a replica of my site on Netlify.
I started looking for the best platform for a static site, and a few stood
out but I eventually tried Netlify, and I’m glad I did.
Netlify Logo
Why Netlify
There are a few things that made a great impression to me before trying
it.
First, the free plan is very generous for free or commercial projects,
with 100GB of free monthly bandwidth, and for a static site with just a
few images here and there, it’s a lot of space!
They include a global CDN, to make sure speed is not a concern even in
continents far away from the central location servers.
You can point your DNS nameservers to Netlify and they will handle
everything for you with a very nice interface to set up advanced needs.
I use Hugo, and locally I run a server by using its built-in tool hugo
server , which handles rebuilding all the HTML every time I make a
change, and it runs an HTTP server on port 1313 by default.
To generate the static site, I have to run hugo , and this creates a
series of files in the public/ folder.
I followed this method on Firebase: I ran hugo to create the files, then
firebase deploy , configured to push my public/ folder content to the
Google servers.
Dashboard
TIP: if you use Hugo on Netlify, make sure you set HUGO_VERSION in
netlify.toml to the latest Hugo stable release, as the default
version might be old and (at the time of writing) does not support
recent features like post bundles
If you think this is nothing new, you’re right, since this is not hard to
implement on your own server (I do so on other sites not hosted on
Netlify), but here’s something new: you can preview any GitHub (or GitLab,
or BitBucket) branch / PR on a separate URL, all while your main site is
live and running with the “stable” content.
A blog is nothing complex, maybe you want to add comments and they can be
done using services like Disqus or others.
Or maybe you want to add a form and you do so by embedding forms generated
on 3rd part applications, like Wufoo or Google Forms.
I just scratched the surface of the things you can do with Netlify without
reaching out to 3rd part services, and I hope I gave you a reason to try
it out.
TUTORIALS
MAKE A CMS-BASED WEBSITE WORK
OFFLINE
Progressively enhance a website when viewed on modern devices
This case study explains how I added the capability of working offline to
the writesoftware.org (https://writesoftware.org) website, which is based
on Grav, a great PHP-based CMS for developers (https://getgrav.org) , by
introducing a set of technologies grouped under the name of Progressive
Web Apps (in particular Service Workers and the Cache API).
When we’re finished, we’ll be able to use our site on a mobile device or
on a desktop, even if offline, like shown here below (notice the “Offline”
option in the network throttling settings)
First approach: cache-first
I first approached the task by using a cache-first approach. In short,
when we intercept a fetch request in the Service Worker, we first check if
we have it cached already. If not, we fetch it from the network. This has
the advantage of making the site blazing fast when loading pages already
cached, even when online - in particular with slow networks and lie-fi
(https://developers.google.com/web/fundamentals/performance/poor-
connectivity/#what_is_lie-fi) - but also introduces some complexity in
managing updating the cache when I ship new content.
This will not be the final solution I adopt, but it’s worth going through
it for demonstration purposes.
I add the service worker in a sw.js file in the site root. This allows
it to work on all the site subfolders, and on the site home as well. The
SW at the moment is pretty basic, it just logs any network request:
I need to register the service worker, and I do this from a script that I
include in every page:
window.addEventListener('load', () => {
if (!navigator.serviceWorker) {
return
}
navigator.serviceWorker.register('/sw.js', {
scope: '/'
}).then(() => {
//...ok
}).catch((err) => {
console.log('registration failed', err)
})
})
If service workers are available, we register the sw.js file and the
next time I refresh the page it should be working fine:
At this point I need to do some heavy lifting on the site. First of all, I
need to come up with a way to serve only the App Shell: a basic set of
HTML + CSS and JS that will be always available and shown to the users,
even when offline.
Now any other page that is clicked is intercepted by our Service Worker.
Whenever a page is loaded, we load the shell first, and then we load a
stripped-down version of the page, without the shell: just the content.
How?
We listen for the install event, which fires when the Service Worker is
installed or updated, and when this happens we initialize the cache with
the content of our shell: the basic HTML layout, plus some CSS, JS and
some external assets:
Then, if I’m requesting a local partial (just the content of a page, not
the full page) I just issue a fetch request to get it.
If it’s not a partial, we return the shell, which is already cached when
the Service Worker is first installed.
if (requestUrl.href.startsWith('https://www.googletagmanager.com') ||
requestUrl.href.startsWith('https://www.google-analytics.com') ||
requestUrl.href.startsWith('https://assets.convertkit.com')) {
// don't cache, and no cors
event.respondWith(fetch(event.request.url, { mode: 'no-cors' }))
return
}
event.respondWith(caches.match(event.request)
.then((response) => {
if (response) { return response }
if (requestUrl.origin === location.origin) {
if (requestUrl.pathname.endsWith('?partial=true')) {
return fetch(requestUrl.pathname)
} else {
return caches.match('/shell')
}
return fetch(`${event.request.url}?partial=true`)
}
return fetch(event.request.url)
})
.then(response => caches.open(cacheName).then((cache) => {
cache.put(event.request.url, response.clone())
return response
}))
.catch((error) => {
console.error(error)
}))
})
Since Service Workers are currently only supported in Chrome, Firefox and
Opera, I can safely rely on the BroadcastChannel API
(https://developers.google.com/web/updates/2016/09/broadcastchannel) for
this.
First, I connect to the ws_navigation channel and I attach a
onmessage event handler on it. Whenever I receive an event, it’s a
communication from the Service Worker with new content to show inside the
App Shell, so I just lookup the element with id content-wrapper and I
put the partial page content into it, effectively changing the page the
user is seeing.
The shell is loaded immediately, since it’s always cached and soon after,
the actual content is looked up, which might be cached as well.
window.addEventListener('load', () => {
if (!navigator.serviceWorker) { return }
const channel = new BroadcastChannel('ws_navigation')
navigator.serviceWorker.register('/sw.js', {
scope: '/'
}).then(() => {
channel.postMessage({
task: 'fetchPartial',
url: `${window.location.pathname}?partial=true`
})
}).catch((err) => {
console.log('SW registration failed', err)
})
})
The missing bit is handing a click on the page. When a link is clicked, I
intercept the event, halt it and I send a message to the Service Worker to
fetch the partial with that URL.
window.addEventListener('load', () => {
//...
}
})
Now we just miss to handle this event. On the Service Worker side, I
connect to the ws_navigation channel and listen for an event. I listen
for the fetchPartial message task name, although I could simply avoid
this condition check as this is the only event that’s being sent here
(messages in the Broadcast Channel API are not dispatched to the same page
that’s originating them - only between a page and a web worker).
If it’s not cached, I fetch it, send it back as a message to the page, and
then cache it for the next time it might be visited.
fetch(event.data.url).then((fetchResponse) => {
const fetchResponseClone = fetchResponse.clone()
fetchResponse.text().then((body) => {
channel.postMessage({ url: event.data.url, content: body })
})
caches.open(cacheName).then((cache) => {
cache.put(event.data.url, fetchResponseClone)
})
})
})
.catch((error) => {
console.error(error)
})
}
}
Now the Service Worker is installed on the site as soon as a user visits,
and subsequent page loads are handled dynamically through the Fetch API,
not requiring a full page load. After the first visit, pages are cached
and load incredibly fast, and - more importantly - then even load when
offline!
1. the URL must change when a new page is shown. The back button should
work normally, and the browser history as well
2. the page title must change to reflect the new page title
3. we need to notify the Google Analytics API that a new page has been
loaded, to avoid missing an important metric such as the page views
per visitor.
4. the code snippets are not highlighted any more when loading new
content dynamically
Fix URL, title and back button with the History API
This is working but the page title does not change in the browser UI. We
need to fetch it somehow from the page. I decided to put in the page
content partial a hidden span that keeps the page title, so we can fetch
it from the page using the DOM API, and set the document.title
property:
Google Analytics works fine out of the box, but when loading a page
dynamically, it can’t do miracles. We must use the API it provides to
inform it of a new page load. Since I’m using the Global Site Tag
( gtag.js ) tracking, I need to call:
window.addEventListener('load', () => {
if (!navigator.serviceWorker) { return }
const channel = new BroadcastChannel('ws_navigation')
navigator.serviceWorker.register('/sw.js', {
scope: '/'
}).then(() => {
channel.postMessage({
task: 'fetchPartial',
url: `${window.location.pathname}?partial=true`
})
}).catch((err) => {
console.log('SW registration failed', err)
})
and sw.js :
if (requestUrl.href.startsWith('https://www.googletagmanager.com') ||
requestUrl.href.startsWith('https://www.google-analytics.com') ||
requestUrl.href.startsWith('https://assets.convertkit.com')) {
// don't cache, and no cors
event.respondWith(fetch(event.request.url, { mode: 'no-cors' }))
return
}
event.respondWith(caches.match(event.request)
.then((response) => {
if (response) { return response }
if (requestUrl.origin === location.origin) {
if (requestUrl.pathname.endsWith('?partial=true')) {
return fetch(requestUrl.pathname)
} else {
return caches.match('/shell')
}
return fetch(`${event.request.url}?partial=true`)
}
return fetch(event.request.url)
})
fetch(event.data.url).then((fetchResponse) => {
const fetchResponseClone = fetchResponse.clone()
fetchResponse.text().then((body) => {
channel.postMessage({ url: event.data.url, content: body })
})
caches.open(cacheName).then((cache) => {
cache.put(event.data.url, fetchResponseClone)
})
})
})
.catch((error) => {
console.error(error)
})
}
}
To do this I just stripped the app shell from the install Service Worker
event and I relied on Service Workers and the Cache API to just deliver
the plain pages of the site, without managing partial updates. I also
dropped the /shell fetch hijacking when loading a full page, so on the
first page load there is no delay, but we still load partials when
navigating to other pages later.
I still use script.js and sw.js to host the code, with script.js
being the file that initializes the Service Worker, and also intercepts
click on the client-side.
Here’s script.js :
window.addEventListener('load', () => {
if (!navigator.serviceWorker) { return }
navigator.serviceWorker.register('/sw.js', {
scope: '/'
}).then(() => {
fetchPartial(window.location.pathname)
}).catch((err) => {
console.log('SW registration failed', err)
})
if (requestUrl.href.startsWith('https://www.googletagmanager.com') ||
requestUrl.href.startsWith('https://www.google-analytics.com') ||
requestUrl.href.startsWith('https://assets.convertkit.com')) {
// no cors
options = { mode: 'no-cors' }
}
}
event.respondWith(fetch(event.request, options)
.then((response) => {
if (response.status === 404) {
return fetch(PAGENOTFOUND_GIF)
}
const resClone = response.clone()
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request.url, response)
return resClone
})
})
.catch(() => caches.open(CACHE_NAME).then(cache => cache.match(event.request.url)
.then((response) => {
if (response) {
return response
}
return fetch(OFFLINE_GIF)
})
.catch(() => fetch(OFFLINE_GIF)))))
})
script.js :
window.addEventListener('load', () => {
if (!navigator.serviceWorker) { return }
navigator.serviceWorker.register('/sw.js', {
scope: '/'
}).catch((err) => {
console.log('SW registration failed', err)
})
})
sw.js :
if (requestUrl.href.startsWith('https://www.googletagmanager.com') ||
requestUrl.href.startsWith('https://www.google-analytics.com') ||
requestUrl.href.startsWith('https://assets.convertkit.com')) {
// no cors
options = { mode: 'no-cors' }
}
event.respondWith(fetch(event.request, options)
.then((response) => {
if (response.status === 404) {
return fetch(PAGENOTFOUND_GIF)
}
const resClone = response.clone()
return caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request.url, response)
return resClone
})
})
.catch(() => caches.open(CACHE_NAME).then(cache => cache.match(event.request.url)
.then((response) => {
return response || fetch(OFFLINE_GIF)
})
.catch(() => fetch(OFFLINE_GIF)))))
})
In the end for me this approach was not enough to be viable, and I ended
up implementing the version with fetch partial updates.
TUTORIAL: CREATE A SPREADSHEET
USING REACT
How to build a simple Google Sheets or Excel clone using React
Related content
First steps
Build a simple spreadsheet
Introducing formulas
Improve performance
Saving the content of the table
Wrapping up
Related content
This tutorial covers the following topics which have dedicated guides on
Write Software:
React
JSX
ES2015
You might want to check them out to get an introduction to these topics if
you’re new to them.
First steps
The code of this tutorial is available on GitHub at
https://github.com/flaviocopes/react-spreadsheet-component
(https://github.com/flaviocopes/react-spreadsheet-component)
First thing, we’re going to detail what we’re going to build. We’ll create
a Table component that will have a fixed number of rows. Each row has the
same number of columns, and into each column we’ll load a Cell component.
We’ll be able to select any cell, and type any value into it. In addition,
we’ll be able to execute formulas on those cells, effectively creating a
working spreadsheet that won’t miss anything from Excel or Google Sheets
</sarcasm> .
The tutorial first dives into the basic building blocks of the
spreadsheet, and then goes into adding more advanced functionality such
as:
create-react-app spreadsheet
cd spreadsheet
yarn start // or npm start
Let’s wipe out the bulk of this code and just replace it with a simple
render of the Table component. We pass it 2 properties: x the number of
columns and y the number of rows.
this.state = {
data: {},
}
}
updateCells = () => {
this.forceUpdate()
}
render() {
const rows = []
Table.propTypes = {
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}
The Table component manages its own state. Its render() method creates
a list of Row components, and passes to each one the part of state that
bothers them: the row data. The Row component will in turn pass this data
down to multiple Cell components, which we’ll introduce in a minute.
Row.propTypes = {
handleChangedCell: PropTypes.func.isRequired,
updateCells: PropTypes.func.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
rowData: PropTypes.shape({
string: PropTypes.string,
}).isRequired,
}
Let’s now dive into the Cell, the core (and last) component of our
spreadsheet!
/**
* Cell represents the atomic element of a table
*/
export default class Cell extends React.Component {
constructor(props) {
super(props)
this.state = {
editing: false,
value: props.value,
}
this.display = this.determineDisplay(
{ x: props.x, y: props.y },
props.value
)
this.timer = 0
this.delay = 200
this.prevent = false
}
/**
* Add listener to the `unselectAll` event used to broadcast the
* unselect all event
*/
componentDidMount() {
window.document.addEventListener('unselectAll',
this.handleUnselectAll)
}
/**
* Before updating, execute the formula on the Cell value to
* calculate the `display` value. Especially useful when a
* redraw is pushed upon this cell when editing another cell
* that this might depend upon
*/
componentWillUpdate() {
this.display = this.determineDisplay(
{ x: this.props.x, y: this.props.y }, this.state.value)
}
/**
* Remove the `unselectAll` event listener added in
* `componentDidMount()`
*/
componentWillUnmount() {
window.document.removeEventListener('unselectAll',
this.handleUnselectAll)
}
/**
* When a Cell value changes, re-determine the display value
* by calling the formula calculation
*/
onChange = (e) => {
this.setState({ value: e.target.value })
this.display = this.determineDisplay(
{ x: this.props.x, y: this.props.y }, e.target.value)
}
/**
* Handle pressing a key when the Cell is an input element
*/
onKeyPressOnInput = (e) => {
if (e.key === 'Enter') {
this.hasNewValue(e.target.value)
}
}
/**
* Handle pressing a key when the Cell is a span element,
* not yet in editing mode
*/
onKeyPressOnSpan = () => {
if (!this.state.editing) {
/**
* Handle moving away from a cell, stores the new value
*/
onBlur = (e) => {
this.hasNewValue(e.target.value)
}
/**
* Used by `componentDid(Un)Mount`, handles the `unselectAll`
* event response
*/
handleUnselectAll = () => {
if (this.state.selected || this.state.editing) {
this.setState({ selected: false, editing: false })
}
}
/**
* Called by the `onBlur` or `onKeyPressOnInput` event handlers,
* it escalates the value changed event, and restore the editing
* state to `false`.
*/
hasNewValue = (value) => {
this.props.onChangedValue(
{
x: this.props.x,
y: this.props.y,
},
value,
)
this.setState({ editing: false })
}
/**
* Emits the `unselectAll` event, used to tell all the other
* cells to unselect
*/
emitUnselectAllEvent = () => {
const unselectAllEvent = new Event('unselectAll')
window.document.dispatchEvent(unselectAllEvent)
}
/**
* Handle clicking a Cell.
*/
clicked = () => {
// Prevent click and double click to conflict
this.timer = setTimeout(() => {
if (!this.prevent) {
// Unselect all the other cells and set the current
// Cell state to `selected`
this.emitUnselectAllEvent()
this.setState({ selected: true })
}
this.prevent = false
}, this.delay)
/**
* Handle doubleclicking a Cell.
*/
doubleClicked = () => {
// Prevent click and double click to conflict
clearTimeout(this.timer)
this.prevent = true
/**
* Calculates a cell's CSS values
*/
calculateCss = () => {
const css = {
width: '80px',
padding: '4px',
margin: '0',
height: '25px',
boxSizing: 'border-box',
position: 'relative',
display: 'inline-block',
color: 'black',
border: '1px solid #cacaca',
textAlign: 'left',
verticalAlign: 'top',
fontSize: '14px',
lineHeight: '15px',
overflow: 'hidden',
fontFamily: 'Calibri, \'Segoe UI\', Thonburi,
Arial, Verdana, sans-serif',
}
return css
}
render() {
const css = this.calculateCss()
// column 0
if (this.props.x === 0) {
return (
<span style={css}>
{this.props.y}
</span>
)
}
// row 0
if (this.props.y === 0) {
const alpha = ' abcdefghijklmnopqrstuvwxyz'.split('')
return (
<span
onKeyPress={this.onKeyPressOnSpan}
style={css}
role="presentation">
{alpha[this.props.x]}
</span>
)
}
if (this.state.selected) {
css.outlineColor = 'lightblue'
css.outlineStyle = 'dotted'
}
if (this.state.editing) {
return (
<input
style={css}
type="text"
onBlur={this.onBlur}
onKeyPress={this.onKeyPressOnInput}
value={this.state.value}
onChange={this.onChange}
autoFocus
/>
)
}
return (
<span
onClick={e => this.clicked(e)}
onDoubleClick={e => this.doubleClicked(e)}
style={css}
role="presentation"
>
{this.display}
</span>
)
}
}
Cell.propTypes = {
onChangedValue: PropTypes.func.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
value: PropTypes.string.isRequired,
}
Quite a bit to discuss here! But first, you should be able to finally see
something in your browser, and this something seems already working quite
good:
It’s not much, but we can already edit the cells content.
In the constructor we set some internal state properties that we’ll need
later, and we also initialize the this.display property based upon
props.value , which is used in the render() method. Why we do this?
Because later when we’ll add the option to store th table data in local
storage, we’ll be able to initialize a cell with a value instead of an
empty value.
At the moment, props.value will always have an empty value, so all the
cells are initialized empty.
Introducing formulas
The spreadsheet at this point is nice and all, but the real power comes
from being able to execute formulas: sum values, reference other cells,
and so on.
I decided to use this pretty nice library that handles Excel formulas:
https://github.com/handsontable/formula-parser
(https://github.com/handsontable/formula-parser) so we can get full
compatibility with the most popular formulas for free, without having to
code them ourselves.
The library seems quite actively developed, and has a good test suite so
we can run the test ourselves to check if something goes wrong.
We can run yarn add hot-formula-parser and then restart our app with
yarn start .
We did the first app dissection from top to bottom, let’s now start from
the bottom.
In the Cell component, when determining the value of an item we run the
determineDisplay() method:
//...
}
Cell.propTypes = {
//...
executeFormula: PropTypes.func.isRequired,
//...
}
Row.propTypes = {
//...
executeFormula: PropTypes.func.isRequired,
//...
}
We’re simply passing it down from the component props to its children.
Nothing complicated here. The meat of the functionality is all moved up to
Table then! This is because to do anything, we must know all the state of
the table, we can’t just run a formula on a cell or on a row: any formula
might reference any other cell. So here is how we’ll edit Table to fit
formulas:
//...
import { Parser as FormulaParser } from 'hot-formula-parser'
//...
const y = cellCoord.row.index + 1
if (!this.state.data[y] || !this.state.data[y][x]) {
return done('')
}
// All fine
return done(this.state.data[y][x])
})
const colFragment = []
colFragment.push(value)
}
fragment.push(colFragment)
}
if (fragment) {
done(fragment)
}
})
}
//...
/**
* Executes the formula on the `value` usign the
* FormulaParser object
*/
executeFormula = (cell, value) => {
this.parser.cell = cell
let res = this.parser.parse(value)
if (res.error != null) {
return res // tip: returning `res.error` shows more details
}
if (res.result.toString() === '') {
return res
}
if (res.result.toString().slice(0, 1) === '=') {
// formula points to formula
res = this.executeFormula(cell, res.result.slice(1))
}
return res
}
render() {
//...
<Row
handleChangedCell={this.handleChangedCell}
executeFormula={this.executeFormula}
updateCells={this.updateCells}
key={y}
y={y}
x={this.props.x + 1}
rowData={rowData}
/>,
//...
}
}
The inner working of each event responder is a bit tricky to get, but
don’t worry about the details, focus on how it works overall.
Improve performance
The updateCells prop passed down from Table to Cell is responsible for
rerendering all the cells in the table, and it’s triggered when a Cell
changes its content.
//...
/**
* Performance lifesaver as the cell not touched by a change can
* decide to avoid a rerender
*/
shouldComponentUpdate(nextProps, nextState) {
// Has a formula value? could be affected by any change. Update
if (this.state.value !== '' &&
this.state.value.slice(0, 1) === '=') {
return true
}
return false
}
//...
What this method does is: if there is a value, and this value is a
formula, yes we need to update as our formula might depend on some other
cell value.
Then, we check if we’re editing this cell, in which case - yes, we need to
update the component.
In all other cases, no we can leave this component as-is and not rerender
it.
In short, we only update formula cells, and the cell being modified.
How do we do that?
this.tableIdentifier = `tableData-${props.id}`
We now just need to load this state when the Table component is
initialized, by adding a componentWillMount() method to Table :
componentWillMount() {
if (this.props.saveToLocalStorage &&
window &&
window.localStorage) {
const data = window.localStorage.getItem(this.tableIdentifier)
if (data) {
this.setState({ data: JSON.parse(data) })
}
}
}
Wrapping up
That’s it for this tutorial!
Don’t miss the in-depth coverage of the topics we talked about (React,
JSX, ES2015) on the Write Software website.