LearningWebAPIs

From HerzbubeWiki
Jump to: navigation, search

This page has notes about various Web APIs, which is to say client-side APIs that are important when you are a web programmer. Many of these APIs are desribed as part of HTML5.

JavaScript is required to make use of most of these APIs, so LearningJavaScript will also be an interesting companion page to this one.


References

  • HTML5 & CSS3 For the Real World. 2nd edition, 2015. Alexis Goldstein, Louis Lazaris, Estelle Weyl. SitePoint. ISBN 978-0-9874674-8-5. On this page I refer to this book with [HtmlForTheRealWorld].
  • Modernizr: A feature dection JavaScript library. From the website: Modernizr tells you what HTML, CSS and JavaScript features the user's browser has to offer.


Glossary

TODO


Geolocation

Introduction

Geolocation allows visitors of a website to share their current location. It works like this:

  • The browser attempts to determine the machine's current location by various means (e.g. IP address, WLAN location).
  • The browser then makes the location available to client-side JavaScript via the Geolocation API.

The Geolocation API not only provides the current location, but can also notify about changes in the location.

The following needs to be always kept in mind:

  • When your code accesses the Geolocation API, the browser will notify the user and ask for his or her consent to share the information.
  • Geolocation will not be available if the user declines to share the information (usually because of privacy concerns).
  • Geolocation is sensitive information. Keep the privacy of your users in mind even if they consent to sharing the information!
  • If geolocation is available, it may be inaccurate or even (intentionally) false
  • In some browsers (e.g. Firefox) the geolocation API only works if the website is accessed via HTTPS


Browser support

One way to check whether the browser has support for the Geolocation API is the Modernizr library (see References section). If you want to do it yourself:

if (navigator.geolocation)


These days the Geolocation API is well supported in all modern browsers. In the past it appears that some mobile platform browsers had API differences - in any case, [HtmlForTheRealWorld] suggests that if you need to bridge these differences that you use the geo-location-javascript library.


API functions and objects

TODO: Complete this section


The Geolocation API defines the following functions:

getCurrentPosition(successCallback, errorCallback, options)
watchPosition
clearPosition

The Geolocation API defines the following interfaces:

Position
Coordinates


Coordinates interface

The Geolocation API stores a location in a Coordinates object. This is the interface:

interface Coordinates
{
  // Attributes that are guaranteed to be there
  readonly attribute double latitude;
  readonly attribute double longitude;
  readonly attribute double accuracy;  // metres

  // Attributes that may not be present; if missing the value is null
  readonly attribute double? altitude;
  readonly attribute double? altitudeAccuracy;  // metres
  readonly attribute double? heading;  // degrees in relation to true north
  readonly attribute double? speed;    // meters per second
};


Position interface

The Geolocation API frequently works with Position objects. For instance, the success callback passed to getCurrentPosition will be invoked with such an object as its parameter. This is the interface:

interface Position
{
  readonly attribute Coordinates coords;
  readonly attribute DOMTimeStamp timestamp;  // when the location was determined
};


Google Maps

Documentation for Google Maps JavaScript API: https://developers.google.com/maps/documentation/javascript/.

The following snippet pulls in the necessary JavaScript code to access the Google Maps API. Check out the documentation to find out how to obtain an API key. Apparently an API key is not an absolute requirement, but my guess is that without one you will be severely restricted.

<script type=*text/javascript" src="https://maps.googleapis.com/maps/api/js?key=API_KEY" />


And this snippet loads a map and places a pin on it for the location obtained from the Geolocation API.

// The parameter is a Position object from the Geolocation API.
function displayOnMap(position)
{
  // The map will be placed inside this element
  var containerElement = document.getElementById("foo");

  var googleMapOptions =
  {
    // Zoom level 0 = map of Earth
    // Zoom level 14 = street level
    zoom: 14,

    // Possible map types are:
    // - ROADMAP (the default)
    // - SATELLITE
    // - HYBRID
    // - TERRAIN
    mapTypeId: google.maps.MapTypeId.ROADMAP
  };
  var googleMap = new google.maps.Map(containerElement, googleMapOptions);

  var latitude = position.coords.latitude;
  var longitude = position.coords.longitude;
  var googleMapLocation = new google.maps.LatLng(latitude, longitude);

  var googleMapPinOptions =
  {
    position: googleMapLocation,
    map: googleMap,
    title: "Hello World!"
  };
  var googleMapPin = new google.maps.Marker(googleMapPinOptions);

  googleMap.setCenter(googleMapLocation);
}


Offline Web Apps

Summary

A browser has two kinds of caches:

  • The normal browser cache. A website has little to no control over how this cache is managed, it is automatically managed by the browser.
  • The so-called "application cache". A website can tell the browser to store certain files in this cache and, until further notice, to load the files from this cache instead of fetching them from the website.

The application cache allows the user to work offline with a website, typically because no Internet connection is available for some reason.

Browsers are supposed to limit the application cache. As a general rule, assume that a single website has no more than 5 MB of cache space available.


Browser support

Modern browsers generally support Offline Web Apps. The notable exception is Internet Explorer.

Some browsers (e.g. Firefox) ask the user for permission for storing files for offline use, some browsers do not. Browsers are not generally required to ask.


How it works

All HTML files served by a website must reference a manifest file, like this:

<html manifest="cache.appcache">

The name and/or extension of the manifest file is irrelevant, the name used here is merely a convention used in the book [HtmlForTheRealWorld]. What is important that the web server must be configured to serve the manifest file with the appropriate content type. For our example we are using the .appcache extension, so for an Apache web server the following snippet can be added to the .htaccess config file:

AddType text/cache-manifest .appcache

The manifest file itself is a simple text file that defines which files should be stored in the application cache, and which files should not be stored in the application cache. The format is this:

CACHE MANIFEST
# version x.y

CACHE:
index.html
images/foo.png
images/still-image.png
js/main.js
css/main.css

NETWORK:
*

FALLBACK:
videos/ images/video-fallback.png

Notes:

  • The words "CACHE MANIFEST", "CACHE", "NETWORK" and "FALLBACK" must appear as shown above. TODO: Do they really need to be in uppercase? Are the colon characters (":") really necessary?
  • The first line of the file must be "CACHE MANIFEST"
  • Comment lines begin with a hash character ("#")
  • The CACHE section must explicitly list all files that the browser should store in the application cache. Wildcards are not allowed.
  • The NETWORK section must list all files that the browser should not store in the application cache but instead fetch from the network as usual. Wildcards are allowed in this section. In fact, it is recommended to just specify the single wildcard "*". The reason: Every URL used by the website must be accounted for in the manifest file - even URLs on foreign sites that the website links to. If a URL is not accounted for in the manifest file, that URL will fail to load regardless of whether the user is currently online or offline.
  • Normally the browser displays an error message if it is offline but attempts to access a file that is not in the application cache. The FALLBACK section can be used to tell the browser that it should display a fallback of some sort instead of an error message. It is possible to list fallbacks for specific files, or for all files below a partial path (see example above). Fallbacks are typically used for large media files that would exceed the application cache limit.


Refreshing the cache

Once a browser has established the application cache for a website, it will continue to load files from the cache even if the user is online! So how does the browser know when the files in the cache are outdated and it should refresh the cache?

The trigger is the manifest file: If the user is online and the browser loads a website from the application cache, the browser first loads the manifest file from the network. If the manifest file is still the same, the browser happily loads the rest of the website from the cache. However, if the manifest file has changed the browser knows that the application cache is outdated. It discards the entire cache, loads the website from the network and rebuilds the cache with the newly fetched files.

A useful convention therefore is to store a comment line in the manifest file that contains a version number. If the website needs updating, simply serve a manifest file with a bumped version number.

Note: If the web server hands out the manifest file with an Expire HTTP header that allows the browser to store the manifest in its normal cache, the application cache might not get updated for an undue amount of time because the browser will not reload the manifest file until the time specified in the HTTP header has gone by. Clear the browser cache if this seems to be the case.


Online or offline?

JavaScript code can detect whether the browser is online like this:

if (navigator.onLine)
{
  // do online stuff
}

JavaScript code can get notified when the browser goes offline or online using these events:

  • window.online
  • window.offline


Apparently there are more functions and events for dealing with the application cache. These are not listed here yet.


Web Storage

Summary

Web Storage provides between 2 and 10 MB of space for storing user data locally. The exact amount varies between browsers and whether the platform is mobile or desktop, but at the time of writing the W3C specs recommend 5 MB. Web Storage is an alternative to cookies. Web Storage is also available to websites that work offline (see previous section).

Web Storage is available in all modern browser, even Internet Explorer (since 8.0).

There are two types of web storage:

  • Session storage: Stores data for a specific browser tab. If the same website is open in different tabs, they will have separate storage. Session storage is not persistent, it gets deleted when the user closes the browser tab.
  • Local storage: Persistent storage on the user's computer. It's probably obvious, but let's make it explicit: Local storage is not shared between browsers from different vendors.

Web storage is kept separate for each domain, i.e. a website foo.com does not have access to data stored by bar.com. TODO: What about subdomains?


Storage interface

The interface for getting and setting data looks like this:

interface Storage
{
  // The number of key/value pairs available from the storage object.
  readonly attribute unsigned long length;

  // The name of the key at the specified index position.
  DOMString key(in unsigned long index);

  // Stores the specified value under the specified key. If a value
  // already exists for the key it is overwritten.
  setter creator void setItem(in DOMString key, in any value);

  // Returns the value stored under the specified key. Returns null
  // if no value exists for the key.
  // Important: If you use indexer style access and no value exists
  // for the key, the return value is undefined, not null!
  getter any getItem(in DOMString key);

  // Removes the value stored under the specified key.
  deleter void removeItem(in DOMString key);

  // Removes all values in this storage object.
  void clear();
};

There are two global objects that conform to this interface:

sessionStorage
localStorage


The data

As can be seen from the interface in the previous section, web storage data consists of key/value pairs.

The key obviously is a string.

What may not be so obvious is that the value is also stored as a string. When we pass our original value to the storage interface, no type information about that value is retained - the value is simply converted into a string and saved as such. When the value is retrieved, unfortunately we must convert it back into whatever type it originally had. A typical way to do this for integers is

var fooIntValue = parseInt(localStorage.getItem("foo"));


Also important: The user can view, clear or change web storage data, so we should always be prepared that the data is missing or, at worst, inaccurate.


Recipes

Use Modernizr to check whether the browser supports Web Storage:

if (Modernizr.localstorage)
[...]


Instead of using getItem or setItem we can either use an indexer style access, or a property style access:

sessionStorage["foo"] = "value";
var value = sessionStorage["foo"];

sessionStorage.foo = "value";
var value = sessionStorage.foo;


To test whether a value exists for a given key we must retrieve the value and test ...

  • ... for undefined if we use indexer style access or property style access
  • ... for null if we use getItem() access


To test whether there is enough space available we must check for the QUOTA_EXCEEDED_ERR exception:

try
{
  sessionStorage["foo"] = "value";
}
catch (exception)
{
  if (exception === QUOTA_EXCEEDED_ERR)
  {
    // notify user
  }
}


Canvas

Summary

The Canvas API provides functions for drawing. In addition, canvas has support for manipulating pixels in images and videos.

The Canvas API is available in all modern browser, even Internet Explorer (since 9.0).

Canvas was originally developed by Apple, a lot of stuff in the API is similar to Quartz 2D. For this reason, some of the stuff on the DrawingIniOS wiki page may also be interesting.


The HTML element

The canvas HTML element provides the target for drawing:

<canvas id="foo" width="400" height="400" style="width: 200px; height: 200px; border: dotted 2px black;">
Your browser does not support the Canvas API.
</canvas>

Notes:

  • An ID is useful for getting the element in JavaScript
  • The width and height attributes determine the size of the canvas' coordinate system in pixel. If not specified the default size is 300 x 150.
  • The width and height CSS properties specify the size of the box that displays the image. The canvas image is scaled up or down to match the size of the box specified by CSS.


Pixels or points?

Whenever the canvas API talks about pixels, it is important to understand that these are logical pixels. On high resolution displays, one logical pixel corresponds to two or more physical pixels.

A logical pixel in the canvas API somewhat corresponds to points in Quartz 2D.

TODO: Improve this section. Check out this article and make tests.


The rendering context

You don't draw directly onto the canvas, instead you first obtain a drawing (or rendering) context object, then use that object's function to perform the drawing.

// The canvas object has the interface HTMLCanvasElement
var canvas = document.getElementById("canvas");

// The 2D drawing context object has the interface CanvasRenderingContext2D.
// To draw in 3D, pass the string "webgl" to obtain a drawing context object
// that conforms to the interface WebGLRenderingContext. This only works in
// browsers that support WebGL. The technology used for implementing WebGL is
// OpenGL ES 2.0.
var context = canvas.getContext("2d");


Checking for support

To check whether the browser supports the Canvas API:

var canvas = document.getElementById('canvas');
if (canvas.getContext)
{
  // yes, we have support
}


The coordinate system

The origin of the coordinate system is in the upper left corner. The coordinate system looks like this:

  0
0 +--------> x
  |
  |
  |
  v
  y

Important: The path drawing functions of the canvas API work with floating point numbers because paths are abstract vectors. The bitmap image, however, that is used for rendering the canvas supports, of course, only integer numbers because the bitmap is a rasterization of the abstract vectors. If the rasterization process encounters fractional values in the vector data it will generally perform something that is variously called interpolation, sub-pixeling, or anti-aliasing. See this StackOverflow answer for a nice graphical depiction of the problem. If you need to draw a sharp crisp line, then you must take extra care to prevent interpolation by placing the line exactly on a bitmap pixel - this StackOverflow post shows the technique, called "straddling pixels".


Shapes and paths

The canvas API supports only one pre-defined shape: The rectangle.

All other shapes must be created by combining 1-n paths. A path is a list of points connected by lines. The lines do not necessarily have to be straight lines, they can also be arcs.


Stroke

A path in itself is not visible. One method to make a path visible is to configure a so-called stroke. In conventional painting this would be the brush that is saturated with paint and that we then use to draw lines.

There are three types of strokes: Solid colors, gradients and patterns. TODO: Find out how gradients and patterns work.

var context = [...]

// The brush is a solid color
context.strokeStyle = "blue";
context.strokeStyle = "#00F";
context.strokeStyle = rgba(0, 0, 255, 0.5);

// The brush is a gradient (linear or radial)
var gradient = ...
context.strokeStyle = gradient;

// The brush is a pattern (a repetitive image)
var pattern = ...
context.strokeStyle = pattern;


Once a stroke style has been defined the path can be drawn using that stroke:

// Stroking a rectangle
context.strokeRect(10, 10, 50, 50);

// Stroking a path
context.beginPath();
context.arc(...);
context.stroke();

Optionally we can change the stroke's width:

// By default the stroke's width is 1 pixel
context.lineWidth = 3;


Fill

As mentioned in the previous section, a path in itself is not visible. A second method to make a path visible is to configure a so-called fill. Fills work very similar to strokes, the only difference is that instead of using the brush to draw lines we use the brush to, well, fill an area on the canvas. For this to work, the path must define a shape of some sort that has an enclosed area.

There are three types of fills: Solid colors, gradients and patterns.

var context = [...]

// The brush is a solid color
context.fillStyle = "blue";
context.fillStyle = "#00F";
context.fillStyle = rgba(0, 0, 255, 0.5);

// The brush is a gradient (linear or radial)
var gradient = ...
context.fillStyle = gradient;

// The brush is a pattern (a repetitive image)
var pattern = ...
context.fillStyle = pattern;


Once a stroke style has been defined the path can be drawn using that stroke:

// Filling a rectangle
context.fillRect(10, 10, 50, 50);

// Filling a path
context.beginPath();
context.arc(...);
context.fill();  // specifying the fill rule is optional; specify either "nonzero" or "evenodd"


Gradient

var context = [...]

var gradient1 = context.createLinearGradient(
  xStartPoint,
  yStartPoint,
  xEndPoint,
  yEndPoint);
// Color stop offset is a value between 0 (representing the start point of the gradient)
// and 1 (representing the end point of the gradient)
gradient1.addColorStop(offset, "blue");

var gradient1 = context.createRadialGradient(
  xCenterPoint1,
  yCenterPoint1,
  radius1,
  xCenterPoint2,
  yCenterPoint2,
  radius2);
// Color stop offset is a value between 0  and 1
// TODO: 0 is somehow related to circle 1, 1 is somehow related
// to circle 2, but what do 0 and 1 represent, exactly?
gradient2.addColorStop(offset, "blue");


Patterns

var image = new Image();
image.src = "/path/to/image.png";
img.onload = function()
{
  var context = [...]

  // Valid repeat values are the same as those in the CSS repeat* properties
  var repeatValue = "repeat";
  var pattern = context.createPattern(image, repeat);

  context.fillStyle = pattern;
  context.fillRect(10, 10, 50, 50);
}
var gradient = context.createLinearGradient


Drawing a path

Drawing straight lines:

var context = [...]

// Begins a new path
context.beginPath();

// Moves the pen to the start of a new sub-path without drawing
context.moveTo(xStartPoint, yStartPoint);

// Draws a straight line from the starting point to the next point
context.lineTo(xPoint1, yPoint1);
// Same
context.lineTo(xPoint2, yPoint2);

// This is necessary only if the path has not been closed yet.
// It draws a straight line from the last point in the path to the first path in the path.
// In this example we create a triangle.
context.closePath();


Drawing an arc (circle, ellipse, sector, etc.):

var context = [...]

// Begins a new path
context.beginPath();

// angles are measured clockwise from the positive x-axis; they are expressed in radians. 
// anticlockwise is optional; by default it's false, i.e. the arc is drawn clockwise.
context.arc(
  xCenter,
  yCenter,
  radius,
  startAngle,
  endAngle,
  [anticlockwise]);

// As in the previous example, this is necessary only if the path has not been closed yet.
context.closePath();


Radians

  • The radian unit is Pi. In JavaScript the constant Math.PI can be used.
  • 1 Pi radians = 180°
  • 2 Pi radians = 360°

Here's a useful JavaScript function that converts degrees into radians. The documentation syntax is doxygen. The snippet comes directly from my Little Go iOS project.

// -----------------------------------------------------------------------------
/// @brief Converts @a degrees into the corresponding radians value.
///
/// Radians are often used by Canvas operations, such as drawing arcs
/// or performing CTM rotations.
///
/// Radians describe angles around a circle as a measurement of pi. A full
/// revolution around a circle is 2 times pi.
// -----------------------------------------------------------------------------
function degrees2radians(degrees)
{
  return degrees * Math.PI / 180;
}


Drawing text

The LearningCSS wiki page has details about fonts.

var context = [...]

// The text background
context.fillStyle = "white";

// Values "start" and "end" are better because they take right-to-left locales into account.
// Also possible are "left", "right" and "center".
context.textAlign = "start";

// The same rules apply here as for the CSS "font" property
context.font = "font-style font-weight font-variant font-size/line-height font-family";

// The "textBaseline" property is another property, but it's not covered here

// Finally, draw the text
context.fillText("Scarborough Fair");


Drawing an image

var context = [...]
var image = [...]    // an "img" element

context.drawImage(
  image,
  xTopLeftCornerSubrectangle,
  yTopLeftCornerSubrectangle,
  widthSubrectangle,
  heightSubrectangle,
  xTopLeftCornerInCanvas,
  yTopLeftCornerInCanvas,
  // Used for scaling
  widthInCanvas,
  heightInCanvas);

Notes:

  • Instead of an img element it is possible to draw any "canvas image source" to be drawn. This includes, for instance, an image added by CSS or a video (which will draw the current frame).
  • The 4 subrectangle parameters are optional and can be omitted if you want to draw the entire image
  • The 2 width/height parameters for the canvas are optional and can be omitted if you don't want to scale the image
  • See the MDN docs for full details.


Manipulating pixel data

This snippet shows how to obtain the raw pixel data from a rectangle on the canvas:

var context = [...]

var imageData = context.getImageData(
  xTopLeftCorner,
  yTopLeftCorner,
  width,
  height);

// A one-dimensional array.
// For every pixel there are 4 values in the array: The RGBA values.
// Values are integers between 0 and 255. For alpha, 0 means fully transparent.
// The order in which pixels appear in the array is "line by line".
var pixelData = imageData.data;

The pixel data array can now be manipulated in whatever way we need. When we are finished we need to put the data back onto the canvas:

context.putImageData(
  // We draw the image data object, NOT the pixel data array
  imageData,
  // Destination location on the canvas
  xTopLeftCornerInCanvas,
  yTopLeftCornerInCanvas,
  // Source rectangle
  xTopLeftCornerSubrectangle,
  yTopLeftCornerSubrectangle,
  widthSubrectangle,
  heightSubrectangle);


Save canvas content as image

The following code generates a PNG image from the canvas content and opens it in a new browser tab. The user can then select the browser's "Save as" function to download the image.

var canvas = [...]

var mimeType = "image/png";
window.open(canvas.toDataURL(mimeType);

See the MDN docs for more details (e.g. other image formats).


Recipes

A simple way to clear the canvas is to reset its width or height:

canvas.width = canvas.width

If you want to draw a crisp sharp line of 1 pixel width, then the path must "straddle" the pixel by adding (or subtracting) a half-pixel to the the coordinates:

context.moveTo(10.5, 10.5);
context.lineTo(10.5, 30.5);


SVG (Scalable Vector Graphics)

See LearningSVG.


Drag and Drop

Summary

Besides its obvious purpose, the Drag and Drop API can also be combined with the File API to drag files from the computer into a web page.

Drag and Drop is available in all modern desktop browser, even Internet Explorer (partially since 7.0, almost fully since 10.0). Drag and Drop is generally not available on mobile devices, particularly on Android and iOS. This is by design because those devices generally use touch not mouse.


How it works

The Drag and Drop API is event-driven. There are generally three steps to make Drag and Drop work:

  • Set the draggable attribute to true on the HTML element that should be draggable
  • Add an event listener for the dragstart event on the draggable HTML element
  • Add event listeners for the dragover and drop events on any elements that should accept dropped items. Note that we must add event listeners for both events, because the default behaviour prohibits drag & drop.


dragstart event

function dragstartEventHandler(event)
{
  // The DataTranser object lets us store data that describes the
  // drag & drop operation.
  var dataTransferObject = event.dataTransfer;

  // In this example we remember the ID of the element that we
  // are dragging
  var mimeType = "text/plain";
  var idOfDraggedElement = [...]
  dataTransferObject.setData(mimeType, idOfDraggedElement);
}


dragover event

function dragoverEventHandler(event)
{
  // Allow the drag & drop operation to take place
  event.preventDefault();
}


drop event

function dropEventHandler(event)
{
  // Allow the drag & drop operation to take place
  event.preventDefault();

  // Retrieve the DataTransfer object that we set up when
  // the drag & drop operation started
  var dragstartEvent = event.originalEvent;
  var dataTransferObject = dragstartEvent.dataTransfer;

  // Extract the data
  var mimeType = "text/plain";
  var idOfDraggedElement = dataTransferObject.getData(mimeType);

  // Do something with the element that was dragged. In this
  // contrived example we simply remove it from the page.
  var draggedElement = document.getElementById(idOfDraggedElement);
  draggedElement.parentNode.removeChild(draggedElement);
}