HTML 5

When I wrote this (7/14/2011), IE10 preview 2 didn’t install on W2008Rx, Chrome didn’t support output etc, so I ended up using Opera.

 

Sources & Resources

·         http://www.w3schools.com/html5

·         Introducing HTML 5 (E-book on Safari)

·         http://diveintohtml5.org/

Page structure changes:

·         <!doctype html>
Is all that is required to engage standards mode

·         <meta charset="utf-8" />
Always set character encoding straight after doctype to prevent this attack (http://code.google.com/p/doctype/wiki/ArticleUtf7)

·         <html>, <head>, & <body> are all optional (and are assumed if not specifically stated – i.e. they still appear in the DOM), but IE8- have issues if <body> is missing.

·         Screen readers like it when you declare the language of the page (<html lang=en>) as it affects pronunciation.

·         HTML5 validator: http://html5.validator.nu

Promoted divs:

Common div-with-class-names (e.g. ‘<div classname=footer>’) have been promoted to their own tags (e.g. ‘<footer>’). This promotion gives semantic meaning to what were before arbitrary divs. The nobody-has-implement-it-yet document outlining functionality is just one thing that can use these semantic meanings.

These tags are CSS customizable in the usual way. In HTML5 the article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav & section elements render as display:block. The default for an unknown element is render it display:inline, so HTML4 will show these tags differently than HTML5 even though they are ‘only’ semantic. This can be worked around using (for example) stylesheets (but not on IE8-).

See http://www.w3schools.com/html5/html5_reference.asp for a full list of ‘promoted div’ elements.

·         <header>
These are normally nested within the section/article/etc they are for (i.e. they are not a separate preceding element).

·         <hgroup>
Like a header, but only the highest level is included in any generated document outline. Typically used for tag lines that follow headers, e.g.

<article>
     <header>
       <hgroup>
           <h1>Dave’s World<h1>
           <h2>Where every day is Sunday</h2>
       </hgroup>
     </header>
     …

·         <mark>
Highlight an area of interest to the user (such as with a yellow marker, found search term, etc)

·         <small>
This no longer means ‘reduce font size’, it now means ‘small print’

·         <wbr>
Indicates a good place to introduce a line break if the browser needs one.

New values for the <link> rel attribute

·         alternative (when combined with type=application/atom+xml)
Link to RSS feed

·         start, prev, next, last (actually introduced in HTML4)
Navigation between pages in a series

·         up (actually introduced in HTML4)
Move back up the breadcrumb trail.

·         shortcut
Official way to support favicon.ico. Was in HTML4, but HTML5 ads sizes attribute to indicate what sizes the icon is available in (in the space delimited format “widthxheight width2xheight2”)

·         nofollow
Indicates search engine crawlers should not follow the link. Intended to discourage spammers from putting spurious links in  comments in weblogs in order to improve their page rankings.

·         noreferrer
Indicates that browsers should not set the referrer header. No yet implemented in most (any?) browser.

·         prefetch
Indicates the browser should prefetch the target of the link as the user is very likely to go there next. For example, the top hit in a search result, or the next page of a book.

·         Bunch of less interesting ones: author, external, sidebar, license, pingback, tag, search

Other:

Other changes allow for machine readable values:

·         <time datetime="2011-01-31T20:00Z">8pm last night<time>

·         <time datetime="2011-01-31T20:00Z" pubdate>I updated this text 8pm last night<time>

·         [Not actually HTML5, but worth a mention as the sematic thing overlaps…]
<… role=x aria-level-"2"…>
WAI-ARIA (W3C Web Accessibility Initiative – Accessible Rich Internet Applications). Allows existing tags to indicate (for example) they are headers (role="heading") when normal mark-up is insufficient. For example, a slider control consisting of completely custom HTML could include role="navigation" in its outer div.

·         <ruby>
Places text (where supported) over other text. Intended to clarify pronunciation in Asian languages where the text could be pronounced in multiple ways.

Changes to existing tags:

·         <small>
This no longer means ‘reduce font size’, it now means ‘small print’.

·         <ol start=3>
This was depreciated in v4 and is now back. Controls the number at which the list starts.

·         <cite>

·         <em> adds emphasis to subtly change or add to the meaning of the text, whereas <strong> indicates that the sentence itself is important, and <b> is used to highlight key words.

New Tags

·         <keyGen name=x>
Generates a public/private key, saves the private key locally and includes the public key in the postback.

·         <progress>, <meter>
‘Percentage bars’. Meter is for when the min/max is known, progress is for when it is not.

·         <menu>, <command>

·         <video src=uri [autoplay] [controls] [poster=placeholder image while content downloads] [height=] [width=] [preload=auto|none|metadata] [loop=true|false]/>
Note: HTML inside the <video></video> won’t be displayed by a browser that understands the <video> element, meaning it’s a safe place to put fallback text for older browsers.
HTML 5 spec originally all browsers to support Ogg Vorbis for audio and Ogg Theora for video, but this was cut and the field is fragmented. To help with this, multiple <source src=… type=… codecs=…> elements can be included inside the <video> tag, allowing the browser to pick one it supports. The <source> tag has an min-device-width attribute that can be used to prevent HD video going to low-res mobile phones if a better choice is available.
The <video> tag is easily controlled from Javascript: load(), play(), pause() etc

·         <audio [as video, minus height, width & poster]>

New and updated attributes

·         contentEditable
Now part of the HTML standard. Not all elements are editable. Attribute is inherited by child tags. ‘document.designMode = on’ turns editing for the whole doc on.

·         draggable

·         hidden

·         spellcheck
In the browsers this worked on, it put a wavy red underline under misspelt words.

·         data-*
The practice of ‘customAttribute=x’ to store additional data against a tag was technically a breach of HTML4. HTML5 allows the practice, but requires that the attribute name be prefixed with ‘data-’. These attributes can be accessed normally, or via the element.dataset property (in which case the prefix needs to be dropped. For example data-myValue would get accessed as myEleement.dataset.myValue)

Forms

·         Many elements (like <button>) no longer have to be encased in a <form> tag. They still have to be associated with a form though (using the new form attribute).

·         New input types beyond ‘type=text’ provide automatic validation. Unfortunately there is no standard about how validation errors should be displayed.

·         New types (email etc) did not work for me in IE9 and act like type=text

o   Email, url, date (includes vendor specific date picker), time, datetime, datetime-local, month, week,  number (a spinner, with min, max, and step attributes), range (slide control), search, tel (telephone number), color (color picker), search.

·         <datalist id=x><option .../><option …/></datalist>
<input … list=x />

Drop-down populated from inner list of <option>s from which the user can select, but with the ability for the user to enter other text if they desire. Note how the list is separate and reusable from the actual control.

·         <input name="x" type="number"> + <input name="y" type="number"> = <output name="XPlusY" onforminput="value=x.valueAsNumber + y.valueAsNumber"></output>
Called (and the output value updated) when any form input changes.

New form input attributes:

·         required attribute
Prevents posting. Chrome highlights the box and displays the field title.

·         autofocus attribute
Sets the focus to the control when the page has loaded, unless the user has already started typing / selected a control. Basically fixes the problem where setting focus in script when the page loads can steal focus from impatient users.

·         placeholder attribute
Places help text in the field for the user until they start typing (e.g. ‘Enter name here’)

·         pattern attribute
Regular expression validation. Leading ^ and trailing $ do not need to be specified.

·         autocomplete attribute
Default is on, so the only use of this is to set it to false to tell the browser it’s not OK to remember this field. The autocomplete attribution can also be specified at the <form> level.

·         min, max, step
As they sound (used with spinner, range control, etc)

·         novalidate

o   Skips this item when submitting a validated form.

·         Image and Submit types only:

o   formnovalidate
submits the page without validation

o   formenctype, formmethod, formaction, formtarget
overrides the corresponding setting on the <form> element.

·         image type only:

o   height & width

·         email and file types only:

o   multiple attribute
Allows multiple values to be entered / selected

New methods and properties:

·         element.checkValidity()
Returns true or false depending on whether the element’s value is valid or not. This method is not universal, but is supported by more than just input elements

·         navigator.onLine
Returns whether the browser is online. Does not work properly in many browsers (returning whether the user has selected ‘work offline’ rather than checking actual connectivity). Using a cache manifest file to switch between a ‘IsOnline()’ method that returns false when offline and true when online works around this issue.

New events:

·         (form) oninputchange
Fires when any form field changes. Equivalent to hooking the onchange event of every field on the form.

Body

New Events:

·         onafterprint, onbeforeprint

·         onbeforeload, onunload

·         onerror, onoffline, ononline

·         onhaschange

·         onpagehide, onpageshow

·         onmessage

·         onundo, onredo

·         onstorage,

·         onpopstate

DOM/CSS Changes

·         CSS selectors

o   querySelector
Returns the first element it matches. E.g. document.querySelector(‘article’)

o   querySelectorAll
Return all the elements it matches. E.g. document.querySelectorAll(‘article’)

Canvas

·         var context = document.querySelector(‘canvas’).getContext(‘2d’)
context.fillRect(10,20,50,50)

·         SVG: Vector, can interact (e.g. line gets click event)
Canvas: Raster. Individual elements are non interactive

·         drawImage(…)
Can take input from a hidden image/video/whatnot and copy/translate/expand it elsewhere.

·         toDataUrl(…)
Saves canvas image. Spec only requires PNG support.

·         (X,Y) is across & down.

·         Setting the width of height of the canvas will reset its contents and properties. This is true even if the new value matches the old.

·         Pixel manipulation is via getImageData(x,y,w,h) and putImageData(imageData, x, y [,…]).
getImageData(…) returns an object containing:

o   .width, .height,

o   .data
1-dimensional array where groups of 4 bytes hold a single RGBA value.

putImageData(…) using the same object structure to write pixels. createImageData(…) can be used to create the object structure;

    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext("2d");
    for (y = 0; y < canvas.height; y++)
        for (x = 0; x < canvas.width; x++) {
            var pixel = context.createImageData(1,1);
            pixel.data[0] = x % 256;
            pixel.data[1] = y % 256;
            pixel.data[2] = 128;
            pixel.data[3] = 255;
            context.putImageData(pixel, x, y);
        }


Mandelbrot:

<!DOCTYPE html>
<canvas id="myCanvas" width=700 height=440>
</canvas>
</html>
<script>
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext("2d");
    var pixel = context.createImageData(1, 1);
    pixel.data[3] = 255;
 
 
    var minX = -2;
    var maxX = 1;
    var minY = -1.2
    var maxY = -minY;
 
    var imageWidth = canvas.width;
    var imageHeight = canvas.height;
 
    var xScale = imageWidth / Math.abs(maxX - minX);
    var yScale = imageHeight / Math.abs(maxY - minY);
 
    var maxPass = 250;
    var colorSize = 255 / maxPass / 3;
 
    for (y = 0; y < imageHeight; y++)
        for (x = 0; x < imageWidth; x++) {
 
            var scaledX = x / xScale;
            var scaledY = y / yScale;
            var pass = calc(minX + scaledX, minY + scaledY);
            pixel.data[0] = 0;
            pixel.data[1] = 0;
            pixel.data[2] = 0;
            if (pass < maxPass) {
                pixel.data[Math.floor(3 * pass / maxPass)] = (pass % colorSize) * (255 / colorSize);
            }
            
            context.putImageData(pixel, x, y);
        }
 
        function calc(x, y) 
        {
            var orgX = x;
            var orgY = y;
            var pass = 0;
            while ((x * x + y * y) < 4 && pass < maxPass)
            {
                var xtemp  = x*x - y*y + orgX;
                y = 2*x*y + orgY;
                x = xtemp;
                pass++;
            }
 
            return pass;
        }
 
</script>

 

Data Storage

·         Cookies still work

o   document.cookie = “name1=value1; name2=value2”

o   cookies without an expiry date (session cookies) are still deleted when the browser is closed

·         Web Storage (??is this HTML5 or just a common standard on modern browsers??)

o   5MB limit, no limit on number of items stored

o   Two stores: sessionStorage and localStorage. API is same for both.

o   sessionStorage is limited to the declaring window and deleted when that window closes.

o   localStorage storage is limited to all windows for that domain and items remain until specifically deleted.

o   Accessing Web Storage throws on Safari is cookies are disabled

o   Stored values are auto converted to strings (with all the issues that implies for objects, casts, and culture formatting issues). Usual work around for objects is to use JSON (JSON.stringify(object) / JSON.parse(string))

o   API is

§  [session|local]Storage.setItem(key, value);

§  .getItem(key);
returns null if key is not present.

§  .removeItem(key);

§  .clear();

§  .key(int index)

§  .length
Number of key-value pairs in storage

o   Due to expando support, you can access the keys directly (e.g. sessionStorage.MyKey = 12)

·         Web SQL Databases (Chrome, Opera, Safari) / Indexed Database API

o   Too influx to be worth looking at today (7/15/2011)

·         Offline storage (applicationCache)

o   Raises an onUpdateReady event if the .manifest file changes

o    

 

Caching Manifests (and working offline)

Behavior is defined by a manifest file reference in the <html … manifest=”my.manifest”> tag. The file containing the manifest reference is automatically included in the manifest.

Example file:

CACHE MANIFEST

CACHE:

page1.html
site.css
myLib.js

FALLBACK:
myServerCode.js  myOfflineEquivalentCode.js

/ everythingElse.html

NETWORK:

*

 

(CACHE: is a namespace. Other namespaces can be used. If no namespaces are defined, CACHE: is simply assumed)

·         Cache manifests must have the .manifest file extension and the text/cache-manifest mime type.

·         Comment lines can be added and start with the # character.

·         Files in the cache section will be available offline.

·         Files in the fallback section will be substituted when offline.

·         An entry for ‘/’ in the fallback section indicates the file to be substituted for all other file requests not specifically listed in the fallback section.

·         Files/paths in the network section indicate that these should always be fetched from the server and never cached. A value of * indicates anything not explicitly cached, and is the default is NETWORK: is omitted from the manifest file.

·         Files cached due to a mention in a .manifest file tend to be rather sticky (i.e. the client sees no reason to check for a new version). The usual workaround is to include a comment in the .manifest file that can be easily changed (such as a version number or timestamp) whenever one of the cached file is updated.

Drag & Drop

Basically same as IE, with the addition of:

·         draggable attribute
(Which didn’t work on any of the browsers I tried it on)

·         [event.dataTransfer.]setDragImage method
Allows you to set the icon show when dragging and dropping.

·         [Aside] According to ‘Introducing HTML 5’, no browser has implemented keyboard support for drag & drop.

Geolocation

·         Not part of the HTML5 spec, but has general (and good) adoption amongst HTML5 browsers

·         API:

o   [navigator.geolocation.]getCurrentPosition(fnHandle(position) [, fnHandle(error) [,options]])
Gets the user’s position (if they agree to give it). Some browsers block while asking for permission, some do not (FireFox, Opera).
The position object holds: latitude, longitude, accuracy (in meters), altitude, altitudeAccuracy, heading & speed, but currently only the first three are usually populated.
Note: In Opera I found this to be nested within a coords object. i.e. navigator.geolocation.getCurrentPosition(function test(position) {alert('I know where you are within ' + position.coords.accuracy + 'm');});

Options are: enableHighAccuracy (false by default as high could cause mobile phone to use GPS), timeout (in ms), maximumage (in ms)

o   watchCurrentPosition
Polls for the user’s position (if they agree to give it)

o   clearWatch

Web Workers

Web Workers are as close to a multi-threading model as browsers are expected to come. Again, not part of the HTML5 spec.

o   Support check: typeof Worker != undefined

o   WebWorkers are sandboxed (no  DOM access). It does have access to XMLHttpRequest though.

o   Outside API:

o   new Worker(‘jsFile.js’);

o   .onmessage = function(event);

o   .onerror = function(errorEvent);

o   .postmesssage(string);

o   .terminate()
Ends the ‘thread’ from outside

o   Inside API:

o   .onmessage = function(event);

o   .postmessage(string);

o   close()
Ends the ‘thread’ from inside

o   onerror()

o   setTimeout, setInterval [i.e. window like timer functionality]

o   XMLHttpRequest, Web sockets, other Web Workers

o   importScripts(‘script1.js’, ‘script2.js’, …);

o   ‘this’ refers to the worker instance (rather than a window)

Sample:
  var myWorker = new Worker("myWorkerFile.js");
  myWorker.onmessage = 
function(event) { alert("Worker says: " + event.data); }
  myWorker.postMessage(
"Get to work you lazy implementation!");

     myWorkerFile.js
  onmessage = function(event) {postMessage('You want me to: ' + event.data)};

Note: casing is important but non-consistent (onmessage, but postMessage)

SharedWorkers

SharedWorkers are WebWorkers that can be accessed by more than one document. Web MDI applications like OWA might find this useful. SharedWorkers need to specify a port per connector.

The same postMessage / onMessage API can be used to talk across IFrames:

Page1.html
<!DOCTYPE html>Host page
<button onclick="document.getElementById('myIFrame').contentWindow.postMessage('hello world', '*');" title="Hi">Click me</button>
<IFRAME id=myIFrame src="Page2.htm" />
 
Page2.html
<!DOCTYPE html>
Page 2<br /><span id="spanMessages"/>
<script>window.onmessage = function (event) { spanMessages.innerHTML += "Origin: " + event.origin + ", Data: " + event.data + "<br/>"; }</script>

Mabelbrot using WebWorkers

Mbot.htm

<!DOCTYPE html>
<title>HTML5 Mandelbot with Worker Roles</title>
<table><tr><td style="text-align:right; vertical-align:top">
<label for="horizontalChunks">Horizontal Chunks: </label><input type="number" id="horizontalChunks" min=1 max=8 value=3 /> <br />
<label for="verticalChunks">Vertical Chunks: </label><input type="number" id="verticalChunks" min=1 max=8 value=3 /><br />
<button onclick="go()" style="width:200px">Go</button>
<p>Status:</p>
<p id="status"></p>
</td><td>
<canvas id="myCanvas" width=1280 height=800>
</canvas>
</td></tr></table>
<script>
    var canvas = document.getElementById('myCanvas');
    var context = canvas.getContext("2d");
 
    var maxPass = 250;
    var workerArray;
 
    function go() {
        // View area of (unzoomed) Mandelbrot set
        var minX = -2;
        var maxX = 1;
        var minY = -1.2
        var maxY = -minY;
        
        document.getElementById("status").innerHTML = "";
        writeLine("Start");
        workerArray = new Array();
        var xChunkCount = Number(horizontalChunks.value);
        var yChunkCount = Number(verticalChunks.value);
        var imageWidth = canvas.width;
        var imageHeight = canvas.height;
        var chunkWidth = Math.floor(imageWidth / xChunkCount);
        var chunkHeight = Math.floor(imageHeight / yChunkCount);
        var xScale = imageWidth / Math.abs(maxX - minX);
        var yScale = imageHeight / Math.abs(maxY - minY);
 
        var workerId = 0;
        for (var xChunk = 0; xChunk < xChunkCount; xChunk++) {
            for (var yChunk = 0; yChunk < yChunkCount; yChunk++) {
                // Capture & display QUOTA_EXCEEDED_ERR
                try {
                    workerArray[workerId] = new Worker("MandlebotWorkerFile.js");
                } catch (err) {
                    for (workerIndex in workerArray) { try {workerArray[workerIndex].terminate();} catch (terminationError) {} }
                    alert(err);
                    return;
                }
                workerArray[workerId].onerror = errorHandler;
                workerArray[workerId].onmessage = workComplete;
 
                var args = {
                    workerId:workerId,
                    xStart:xChunk * chunkWidth, yStart:yChunk * chunkHeight, 
                    width:chunkWidth, height:chunkHeight,
                    minX: minX, minY: minY, xScale: xScale, yScale: yScale, maxPass: maxPass
                };
 
                workerArray[workerId].postMessage(JSON.stringify(args));
 
                writeLine("Started worker " + workerId);
                workerId++;
            }
        }
    }
 
    function errorHandler(err) {
        writeLine('');
        writeLine(err.message); 
        writeLine('');
    }
 
    function workComplete(event) {
        var args = JSON.parse(event.data);
        var pixelCount = args.passCounts.length;
        writeLine("Worker " + args.workerId + " returning: (" + args.x + "," + args.y + ") -> (+" + args.width + ", +" + args.height + ")");
        var pixels = context.createImageData(args.width, args.height);
        var colorSize = maxPass / 3;
        var offset = 0;
         for (var index = 0; index < pixelCount; index++) {
            var pass = args.passCounts[index];
            if (pass < maxPass) {
                pixels.data[offset + Math.floor(3 * pass / maxPass)] = (pass % colorSize) * (255 / colorSize);
            }
            pixels.data[offset + 3] = 255;
            offset += 4;
        }
        context.putImageData(pixels, args.x, args.y);
        workerArray[args.workerId] = null;
    }
 
    function writeLine(text) {
        document.getElementById("status").innerHTML += "<span>" + text + "</span><br/>";
    }
 
</script>

 

MandlebotWorkerFile.js

onmessage = function (event) {
    var args = JSON.parse(event.data);
    var passCounts = CalcArea(args.xStart, args.yStart, args.width, args.height, args.minX, args.minY, args.xScale, args.yScale, args.maxPass);
    postMessage(JSON.stringify({ workerId: args.workerId, passCounts: passCounts, x: args.xStart, y: args.yStart, width: args.width, height: args.height }));
    close();
}
 
function CalcArea(xStart, yStart, width, height, minX, minY, xScale, yScale, maxPass) {
    var results = new Array();
    for (var y = 0; y < height; y++) {
        for (var x = 0; x < width; x++) {
 
            var scaledX = (x + xStart) / xScale;
            var scaledY = (y + yStart) / yScale;
            results[y * width + x] = calc(minX + scaledX, minY + scaledY, maxPass);
        }
    }
    return results;
}
 
function calc(x, y, maxPass) {
    var orgX = x;
    var orgY = y;
    var pass = 0;
    while ((x * x + y * y) < 4 && pass < maxPass) {
        var xtemp = x * x - y * y + orgX;
        y = 2 * x * y + orgY;
        x = xtemp;
        pass++;
    }
 
    return pass;
}

 

Shortest Path

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Shortest Route</title>

    <style>

        .label {display:inline-blockwidth:60px}

        button {width200px}

        table {border-collapsecollapseborder1px solid black}

        td {border1px solid blackpadding:5px}

    </style>

</head>

<body>

<span class="label">From:</span><select id="fromCity" style="background-color:Green" onchange="setCityColors();drawData();"></select>

<button id="cmdGenerateData" disabled onclick="generateData();setCityColors();drawData();">Generate new layout</button><br />

<span class="label">To:</span><select id="toCity" style="background-color:Yellow" onchange="setCityColors();drawData();"></select>

<button id="cmdCalculateRoute" disabled onclick="calculateRoute();">CalculateRoute</button>

<p>

<span id="results"></span>

</p>

<canvas id="map" width=1028px height=768px ></canvas>

<br />

<p id="log" style="display:inline-block;"/>

<table id="flightTable" style="display:inline-block; vertical-align:top;" />

 

</body>

<script type="text/javascript">

    function get(id) { return document.getElementById(id); };

 

    var logIndent = 0;

    function log(text) {

        var element = get("log");

        for (var i = 0; i < logIndent; i++) {

            element.innerHTML += "&nbsp;&nbsp;&nbsp;&nbsp;";

        }

        element.innerHTML += text + "<br/>";

    }

 

    function City(name) {

        var radius = 25 + Math.floor(25 * Math.random());

        return {

            name: name,

            x: radius + Math.floor((mapWidth - 2 * radius) * Math.random()),

            y: radius + Math.floor((mapHeight - 2 * radius) * Math.random()),

            radius: radius,

            fillStyle: "blue",

            distance: -1,

            flight: null,

            getTotalDistance:

                function () {

                    if (this.flight == nullreturn this.distance;

                    return this.distance + this.flight.fromCity.getTotalDistance();

                },

            draw: function () {

                var borderWidth = 2;

                context.beginPath();

                context.arc(this.x, this.y, this.radius - Math.round(borderWidth / 2 + 0.5), 0, Math.PI * 2, false);

                context.fillStyle = this.fillStyle;

                context.fill();

            

                context.lineWidth = borderWidth;

                context.strokeStyle= "black";

                context.stroke();

 

                context.font = "20pt Calibri";

                context.fillStyle = "#ff8080";

                context.textAlign = "center";

                context.textBaseline = "middle";

                // context.fillText(this.x + "," + this.y + ", " + this.radius, this.x, this.y);

                context.fillText(this.name, this.x, this.y);

            }

        };

    }

 

    function Flight(fromCity, toCity)

    {

        var distance = Math.ceil(Math.sqrt(

                Math.pow(fromCity.x - toCity.x, 2) +

                Math.pow(fromCity.y - toCity.y, 2)));

 

        return { fromCity: fromCity, toCity: toCity, distance: distance,

            draw: function (bold) {

                // Draw flight path

                context.strokeStyle = "black";

                context.beginPath();

                context.lineWidth = bold ? 5 : 1;

                context.moveTo(this.fromCity.x, this.fromCity.y);

                context.lineTo(this.toCity.x, this.toCity.y);

                context.stroke();

 

                // Add distance label near midpoint of flight path

                var midpointX = (this.fromCity.x + this.toCity.x) / 2;

                var midpointY = (this.fromCity.y + this.toCity.y) / 2

                context.fillStyle = "#cccccc";

                context.lineWidth = 1;

                context.fillRect(midpointX - 10, midpointY - 10, 40, 12);

                context.font = "10pt Calibri";

                context.fillStyle = "blue";

                context.fillText(this.distance, midpointX, midpointY);

             }

        };

    }

 

    function forEachFlightFromCity(city, flights, action) {

        for (i in flights) {

            if (flights[i].fromCity == city) action(flights[i]);

        }

    }

 

    function generateData() {

        var cityNames = new Array("Bristol""Cardiff""Building 18""Tipperary""Neevada""Edingburgh""Reading""Yate""Bedlam""Redmond""Bellview""Canterbury");

        cities = new Array();

        for (i = 0; i < cityNames.length; i++) {

            var overlap;

            do {

                overlap = false;

                cities[i] = new City(cityNames[i]);

                for (j = 0; j < i; j++) {

                    if (Math.abs(cities[i].x - cities[j].x) < (cities[i].radius + cities[j].radius) &&

                        Math.abs(cities[i].y - cities[j].y) < (cities[i].radius + cities[j].radius))

                    {

                        overlap = true;

                        break;

                    }

                }

            } while (overlap);

        }

        flights = new Array();

        for (i = 0; i < cities.length * 2; i++) {

            var fromCity, toCity;

            do {

                fromCity = cities[Math.floor(cities.length * Math.random())];

                toCity = cities[Math.floor(cities.length * Math.random())];

            } while (fromCity == toCity);

 

            // Make all flights bi-directional

            flights[i*2] = new Flight(fromCity, toCity);

            flights[i*2 + 1] = new Flight(toCity, fromCity);

        }

 

        // Don't display flight data on IE as we can't set table.innerHTML in IE

        if (window.navigator.userAgent.indexOf('MSIE') > 0) return;

 

        // Display flight data

        var table = get("flightTable");

        table.innerHTML = "<tr><td>From</td><td>To</td><td>Distance</td></tr>";

        var displayArray = flights.concat(new Array());

        displayArray.sort(function (a, b) {

            var aVal = a.fromCity.name + "|" + a.toCity.name;

            var bVal = b.fromCity.name + "|" + b.toCity.name;

            if (aVal < bVal) return -1;

            if (aVal > bVal) return 1;

            return 0;

            });

        for (var i = 0; i < displayArray.length; i++)

        {

            table.innerHTML += 

                "<tr>" +

                    "<td>" + displayArray[i].fromCity.name + "</td>" +

                    "<td>" + displayArray[i].toCity.name + "</td>" +

                    "<td>" + displayArray[i].distance + "</td>" + 

                    "<tr>";

        }

    }

 

    function drawData()

    {

        context.clearRect(0, 0, mapWidth, mapHeight);

        for (var i = 0; i < flights.length; i += 2) flights[i].draw(false);

        for (i in cities) cities[i].draw();

    }

 

    function setCityColors()

    {

        for(i in cities) {

            cities[i].fillStyle="blue";

        }

        cities[get("fromCity").value].fillStyle="green";

        cities[get("toCity").value].fillStyle="yellow";

    }

 

    function calcPerCity(city) {

        log("calcPerCity(" + city.name + ") : start");

        logIndent++;

        forEachFlightFromCity(city, flights,

            function (flight) {

                var fromCity = flight.fromCity;

                var targetCity = flight.toCity;

                log("fromCity.getTotalDistance()=" + fromCity.name + " " + fromCity.getTotalDistance());

                var distanceThroughCityToTargetCity = fromCity.getTotalDistance() + flight.distance;

                var previousDistance = targetCity.distance;

 

                log("flight " + fromCity.name + " to " + targetCity.name +

                    ": targetCity.getTotalDistance()=" + targetCity.getTotalDistance() +

                    ", distanceThroughCityToTargetCity=" + distanceThroughCityToTargetCity);

 

                if (targetCity.distance == -1 || targetCity.getTotalDistance() > distanceThroughCityToTargetCity) {

                    targetCity.flight = flight;

                    targetCity.distance = flight.distance;

                    if (previousDistance == -1) {

                        calcPerCity(targetCity);

                    }

                }

            }

        );

        logIndent--;

        log("calcPerCity(" + city.name + ") : end");

    }

 

    function calculateRoute() {

        // Reset data

        get("results").innerHTML = "";

        get("log").innerHTML = "";

        for (i in cities) {

            cities[i].distance = -1;

            cities[i].flight = null;

        }

        var fromCity = cities[get("fromCity").value];

        var toCity = cities[get("toCity").value];

        fromCity.distance = 0;

 

        // Calculate the route

        calcPerCity(fromCity);

 

        // Display the results

        if (toCity.distance == -1) {

            get("results").innerHTML = "No solution for " + fromCity.name + " -> " + toCity.name;

        }

        else {

            flight = toCity.flight;

            while (flight != null) {

                get("results").innerHTML = "--" + flight.distance + " miles --&gt; " + flight.toCity.name + get("results").innerHTML;

                flight.draw(true);

                flight = flight.fromCity.flight;

            }

            get("results").innerHTML = "Route:<br/>" + fromCity.name + get("results").innerHTML;

        }

    }

    

    var cities, flights;

    var context = get("map").getContext('2d');

    var mapWidth = context.canvas.width;

    var mapHeight = context.canvas.height;

    generateData();

    for (i in cities) {

        var e = document.createElement("option");

        e.setAttribute("value", i);

        e.innerText = cities[i].name;

        get("fromCity").appendChild(e);

 

        // IE doesn't like 'get("toCity").innerHTML = get("fromCity").innerHTML;', so duplicate the above code

        get("toCity").appendChild(e.cloneNode(true));

    }

    get("toCity").selectedIndex = cities.length - 1;

    setCityColors();

    drawData();

    get("cmdGenerateData").removeAttribute("disabled");

    get("cmdCalculateRoute").removeAttribute("disabled");

 

</script>

</html>

 

Microdata

·         Declared in container via itemscope marker attribute and itemtype namespace attribute (e.g. <section itemscope itemtype="http://brankin.com/person">)

·         Data is then declare in inner tags: <span itemprop="name">Dave</span>

·         Note: there is nothing special about the use of section or span in the above. They could be any tags.

·         Other related attributes: itemtype, itemid, itemref

·         Google defines a bunch of these its crawel understands

 

Tips & Tricks

·         !!feature detection
!undefined = true, so !!undefined = false. This logic is useful in determining is a features is often a case of
function isGeolocationSupported(){ return !!navigator.geolocation; }
?? why not use { return ‘geolocation’ in navigator; } instead? ??

·          

------------------------

[Note to self: title != label for]

input[type=range]::before {content: attr(min);}

input[type=range]::after {content: attr(max);}

Modernizr (www.modernizr.com)

Content-Type = mime-type

Win2k -> Win7

Control Panel -> Add / Remove Windows -> Role -> File Services -> Add Role Services -> Tick Windows Search Service. Next through the wizard (do not select any drives)

Restart Outlook

Control Panel -> Add / Remove Windows -> Features -> Add Features -> Desktop Experience

Services -> Set Themes to autostart

Reboot

Table Storage Entities derive from TableServiceEntity now, and contexts from TableServiceContext.

DataServiceQuery is the one that doesn’t process continuation tokens automatically, AsTableServiceQuery (extension method in StorageClient namespace) is the one that does process continuation tokens automatically,

this.Response.TrySkipIisCustomErrors

 

HostingEnvironment.MapPath() (static equ of Server.MapPath())