Keyboard events
Example (
keyup
user interface event: see also ☛ and ☛) Shopping.ts.zipclass Shopping { … static Handle(event: KeyboardEvent) { if (event.defaultPrevented) window.console.log("'event.preventDefault()' already done..."); // By construction, propagation goes on... /** * Test */ // window.console.log(`${event.key}` + ' <-event.key - event.code-> ' + `${event.code}`); // window.console.log('match: ' + event.key.match(Shopping._Query_content)); if (Shopping._Query !== null) Shopping._Query.innerHTML += `${event.key}`; // Caution: raw display... /** * End of test */ if (event.code === 'Backspace') Shopping._Buffer.pop(); if (event.code === 'Escape' || event.code === 'Enter') { Shopping._Buffer.length = 0; if (window.confirm("Erase screen?")) Shopping._Query.innerHTML = ""; } if (Shopping._Query_content.test(event.key) && event.key.length === 1) { // Test again regular expression... let last = Shopping._Buffer.pop(); if (event.code === 'Space') { if (last !== undefined) { if (!last.includes(" ")) // 'includes' tests equality as well... Shopping._Buffer.push(last); // Re-inject 'last' at last position Shopping._Buffer.push(event.key); } } else { // Something else that 'Space'... if (last === undefined) Shopping._Buffer.push(event.key); else { Shopping._Buffer.push(last); // Re-inject 'last' at last position Shopping._Buffer.push(event.key); if (!last.includes(" ")) { // 'includes' tests equality as well... window.console.log("Attempt to search: " + Shopping._Buffer.join('')); Shopping._Search(Shopping._Buffer.join('')); } } } } // if (Shopping._Query !== null) // Shopping._Query.innerHTML = Shopping._Buffer.join(''); // Nice display... } … } window.addEventListener('keyup', Shopping.Handle);
Touch events
User Interface -UI- events may depend upon browser/device capabilities like “touch”. Event subscription can then be guided by these capabilities.
/** https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events, compatibility: 'caniuse.com/#feat=pointer' */ // Chrome: 'chrome://flags' and Firefox: dom.w3c_pointer_events.enabled to 'true' in 'about:config' <- 'false' in old Firefox versions! if (window.PointerEvent) { // Chrome v.76, Firefox v.68, but it does not work with Safari 12.1 (v.13 required) window.document.getElementById('NoLanguage').onpointerdown = NoLanguagePlayground.prototype.pointerdown.bind(this); window.document.getElementById('NoLanguage').onpointermove = NoLanguagePlayground.prototype.pointermove.bind(this); window.document.getElementById('NoLanguage').onpointerover = NoLanguagePlayground.prototype.pointerover.bind(this); window.document.getElementById('NoLanguage').onpointerup = NoLanguagePlayground.prototype.pointerup.bind(this); } else { // Note: 'window.navigator.maxTouchPoints === undefined' with Firefox on an ordinary desktop computer // We must then test that the 'TouchEvent' API is enabled *AND* the device is really touch-based if ('ontouchstart' in window && window.navigator.maxTouchPoints > 0) { window.document.getElementById('NoLanguage').addEventListener('touchstart', NoLanguagePlayground.prototype.pointerdown.bind(this), false); /* Etc. */ } else { // Safari v12.1: window.document.getElementById('NoLanguage').addEventListener('mousedown', NoLanguagePlayground.prototype.pointerdown.bind(this), false); /* Etc. */ } }
Rule(s)
- There is no concurrent programming support in JavaScript as multithreading in Java. Anyway, JavaScript is intrinsically “concurrent” by the possibility of dispatching event occurrences to (user-defined) handling functions that operate, from an external viewpoint, “in parallel”.
Example (mouse events)
var My_controller = function (…) { // 'My_controller' class (JavaScript 5 style) … this._my_canvas = window.document.getElementById("my_canvas"); … this._my_canvas.addEventListener('mousedown', My_controller.prototype.pointerdown.bind(this), false); this._my_canvas.addEventListener('mousemove', My_controller.prototype.pointermove.bind(this), false); this._my_canvas.addEventListener('mouseover', My_controller.prototype.pointerover.bind(this), false); this._my_canvas.addEventListener('mouseup', My_controller.prototype.pointerup.bind(this), false); … }; … My_controller.prototype.pointerdown = function (mouse_event) { mouse_event.preventDefault(); // Default behaviors are canceled mouse_event.stopPropagation(); // The event occurrence is processed once and for all if (mouse_event.button === 0) { // Mouse left button pressed… … } if (mouse_event.button === 2) { // Mouse right button pressed… … } … };
Drag&drop events
Example (drag&drop management based on utility -
static
- function inBPMiNer
class)public static readonly File_reader = new FileReader(); public static readonly Drag_and_drop = (): void => { // FYI: 'drag', 'dragend' and 'dragstart' are *NEVER* fired when dragged entities come from the OS (compared to draggable entities in the browser having "draggable = true") // Corollary: 'setDragImage' won't work here... // Mouse events are inhibited when drag&drop: http://blog.teamtreehouse.com/implementing-native-drag-and-drop window.document.body.addEventListener('dragenter', (event: DragEvent) => { event.preventDefault(); event.stopImmediatePropagation(); }, false); // 'preventDefault' in 'dragover' is MANDATORY to later allow 'drop': window.document.body.addEventListener('dragover', (event: DragEvent) => { event.preventDefault(); event.stopImmediatePropagation(); }, false); window.document.body.addEventListener('drop', (event: DragEvent) => { event.preventDefault(); event.stopImmediatePropagation(); // Caution: 'event.dataTransfer.types.length === 2' with Firefox while 'event.dataTransfer.types.length === 1' with Chrome // 'event.dataTransfer.types.length === 7' with Safari! if ('dataTransfer' in event) { // 'event.dataTransfer.items' might be 'undefined' for Safari! let i = 0; while (event.dataTransfer!.items[i].kind !== 'file') i++; // Dropped file name (OK with Chrome, Firefox and Safari): // window.alert(event.dataTransfer!.items[i].getAsFile()!.name); BPMiNer.Current_test_case(14, event.dataTransfer!.items[i].getAsFile()!.name.substring(0, event.dataTransfer!.items[i].getAsFile()!.name.indexOf("."))); // Load XML: BPMiNer.File_reader.readAsText(event.dataTransfer!.items[i].getAsFile()!); // UTF-8 else throw new Error("'Drag_and_drop' >> ''dataTransfer' in event', untrue."); }, false); };
Event model Rule(s)
- The foundation of JavaScript is its event model. Beyond the basic
Event
type, a lot of event (sub-)types are available in JavaScript. Occurrences of events (mouse event occurrences for instance) are (automatically) dispatched by JavaScript for asynchronous processing: events are queued and delivered to handling functions at appropriate moments.- The JavaScript event model offers the
CustomEvent
interface to let the possibility of sending complex data attached to instances of homemade event types.Example (user-defined events) Asynchronous_programming.Three.js.ts.zip
class Asynchronous_programming { private readonly _image: HTMLImageElement = new Image(); … constructor(private readonly _image_URL: string, private readonly _count_down = 20, private readonly _steps = 4) { … window.document.addEventListener("Image_is_ready", this._create_3D_object, false); window.document.addEventListener("3D_object_is_ready", () => { // 'custom_event' is ignored this._interval_id = window.setInterval(this._time_out, this._count_down / this._steps * 1000); }, false); window.document.addEventListener("3D_object_is_ready", (custom_event: Event) => { if ((custom_event as CustomEvent).detail.name !== this._image_URL) throw ("Abnormal situation..."); this._animation_id = window.requestAnimationFrame(this._animate_3D_object); }, false); this._image.onload = () => window.document.dispatchEvent(new Event("Image_is_ready")); this._image.src = this._image_URL; … } private _create_3D_object: () => void = () => { // Test: window.console.assert(this._image && this._image instanceof Image && this._image.complete, 'Contract violation: \'this._image\''); // Versus defensive programming: if (!(this._image && this._image instanceof Image && this._image.complete)) throw ('Contract violation: \'this._image\''); // Construction of 3D object based on Three.js… window.document.dispatchEvent(new CustomEvent("3D_object_is_ready", {'detail': {name: this._image_URL}})); } … }
Timers are the way of performing periodic and/or repetitive tasks: setInterval
(cyclic timer) andsetTimeout
(one-shot timer) are ready-to-use facilities for time managementExample Asynchronous_programming.Three.js.ts.zip
class Asynchronous_programming { … private _time_left; // In sec... private _animation_id: number = 0; // '0' means that animation hasn't yet started... private _interval_id!: number; … constructor(private readonly _image_URL: string, private readonly _count_down = 20, private readonly _steps = 4) { … window.document.addEventListener("3D_object_is_ready", () => { // 'custom_event' is ignored this._interval_id = window.setInterval(this._time_out, this._count_down / this._steps * 1000); }, false); … } private _time_out = () => { if ((this._time_left -= this._count_down / this._steps) > 0) // Go on... else { // Stop animation: window.cancelAnimationFrame(this._animation_id); window.clearInterval(this._interval_id); … } } }
Animation is the ability to register a function, which is called each time the browser repaints frames by means of the window.requestAnimationFrame
facilityRule(s)
- The
_animate_3D_object
function in the code below is called each time the browser repaints frames. The recommended (changeable) rate is 60 refreshed frames per sec. (one may note that the original Cinéma used 24 images per sec.).Example Asynchronous_programming.Three.js.ts.zip
class Asynchronous_programming { … private _time_left; // In sec... private _animation_id: number = 0; // '0' means that animation hasn't yet started... private _interval_id!: number; … constructor(private readonly _image_URL: string, private readonly _count_down = 20, private readonly _steps = 4) { … window.document.addEventListener("3D_object_is_ready", (custom_event: Event) => { if ((custom_event as CustomEvent).detail.name !== this._image_URL) throw ("Abnormal situation..."); this._animation_id = window.requestAnimationFrame(this._animate_3D_object); }, false); … } private _animate_3D_object = (timestamp?: number) => { if (timestamp !== undefined) { /* first call: 'timestamp === undefined' */ // One may handle the elapsed time from the first call to '_animate_3D_object' } // Three.js stuff here... this._animation_id = window.requestAnimationFrame(this._animate_3D_object); } private _time_out = () => { if ((this._time_left -= this._count_down / this._steps) > 0) // Go on... else { // Stop animation: window.cancelAnimationFrame(this._animation_id); window.clearInterval(this._interval_id); … } } }
Rule(s)
- TypeScript supports asynchronous programming through the
Promise<T>
generic type (i.e.,Deferred Object
andPromise
in jQuery) and its associatedasync
/await
“syntactical sugars”.Example (
resolve
class method) Promises.ts.zipconst Firefox = Promise.resolve(window.navigator.userAgent.includes("Firefox")); Firefox.then(result => { if (result) window.alert(window.navigator.userAgent); }); const Chrome = Promise.resolve(window.navigator.userAgent.includes("Chrome")); Chrome.then(result => { if (result) window.alert(window.navigator.userAgent); });
Example (
race
class method) Promises.ts.zipconst image = new Image(); const is_image_loaded = new Promise(send => { image.onload = () => { window.console.assert(image.complete); send(image.complete); }; }); image.src = "./img/Night_sky.jpg"; // Image load… const is_image_loaded_in_less_than_100ms = new Promise((yes, no) => { window.setTimeout(() => no(new Error('more than 100ms')), 100); // Change to '1000' afterwards... }); const who_wins = Promise.race([is_image_loaded, is_image_loaded_in_less_than_100ms]); who_wins.then(image_complete => window.alert("Image complete: " + image_complete)); who_wins.catch(error => window.alert(error));
Rule(s)
- The
fetch
API (☛) is a simplified way of getting resources (images, data from Web services…) in an asynchronous way.Example (
all
class method) Promises.ts.zip// 'fetch' API support at 'http://caniuse.com/#feat=fetch' Promise.all([Promise.resolve(window.fetch !== undefined), (window as any).DOM_ready, window.fetch("./img/Night_sky.jpg").then((response: Response) => { return response.blob(); })]).then((parameters: Array<any>) => { // 'parameters[1]' guarantees that DOM is ready: const image = window.document.getElementById("night_sky") as HTMLImageElement; if (parameters[0] === true) // 'fetch' API is supported by the browser // 'parameters[2]' -> Image is available (i.e., 'complete') and transformed into blob: image.src = URL.createObjectURL(parameters[2] as Blob); // 'image' is loaded from "./img/Night_sky.jpg" });
Rule(s)
- While
async
functions (including lambdas) returnPromise<T>
objects,await
functions returnT
objects. In other words,async
allows the returned result of a function to be accessed only when available (non blocking execution) by means of a promise.await
only makes sense inside anasync
function. Simply speaking, anyawait
-marked function call, pauses theasync
function (blocking execution) as doesthen
for a promise.Example LiveBPMN.com.Node.js.ts.zip
/* Node.js program */ // https://developer.chrome.com/docs/puppeteer/ // Download problem: 'sudo npm i puppeteer --unsafe-perm=true --allow-root' // Check whether 'puppeteer.js' has been installed: 'npm list -depth=0' import * as puppeteer from "puppeteer"; const scrape = async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); await page.goto("https://LiveBPMN.com"); await page.screenshot({path: "./LiveBPMN.png"}); await browser.close(); return "https://LiveBPMN.com"; }; scrape().then((value) => { console.log(JSON.stringify(value)); // Success! });
Example Carrefour_Items.Java.zip (App. has to be launched from Java server side)
private readonly _item_data: Promise<Open_Food_Facts_item>; private constructor(private readonly _gtin: string) { // '_gtin' *field* automatic inclusion // => this._gtin = _gtin; -> no need! this._item_data = new Promise((ready: Ready, problem) => { // 'problem' aims at being called when 'Promise' object has difficulty in achieving its job... this._get_Open_Food_Facts_item(ready, problem); }); }; public async get_Open_Food_Facts_item() { // So returned type is inferred from 'async'... return await this._item_data; // Wait for '_get_item_data' (i.e., 'ready(offi);') } private _get_Open_Food_Facts_item(ready: Ready, problem, time_out?: number): void { // Call may omit 'time_out' optional parameter …
Catching
window.document
ready as a promiselet DOM_ready = null; Object.defineProperty(window, "DOM_ready", { value: new Promise(launched_function_when_DOM_ready => { DOM_ready = launched_function_when_DOM_ready; }), enumerable: false, configurable: false, writable: false }); window.document.onreadystatechange = DOM_ready; // In other places, one may check whether the DOM is ready: (window as any).DOM_ready.then(() => { window.document.body.offsetWidth; // Access to 'body' content... window.document.body.offsetHeight; // Access to 'body' content... // Etc. });
Catching
window
loaded as a promiselet Window_loaded: ((event: Event) => void) | undefined = undefined; Object.defineProperty(window, "Window_loaded", { value: new Promise(launched_function_when_Window_loaded => { Window_loaded = launched_function_when_Window_loaded; }), enumerable: false, configurable: false, writable: false }); window.addEventListener('load', Window_loaded!); // In other places, one may check whether both the DOM is ready and the browser window (images, sounds, videos...) is loaded: Promise.all([(window as any).DOM_ready, (window as any).Window_loaded]).then(value => { /* 'value' is an array of results provided by 'window.DOM_ready' and 'window.Window_loaded' */ window.alert("Everything is now ready for the Web!"); });
for await...of
// Generator function, which delivers values based on async. constraints: async function* primes(): AsyncGenerator<number> { // 'Promise' simulates the fact that some time is required to get prime number: yield await new Promise(serve => { setTimeout(() => { serve(2); }, 10); // Each 10 ms... }); yield await new Promise(serve => { setTimeout(() => { serve(3); }, 10); // Each 10 ms... }); yield await new Promise(serve => { setTimeout(() => { serve(5); }, 10); // Each 10 ms... }); yield await new Promise(serve => { setTimeout(() => { serve(7); }, 10); // Each 10 ms... }); yield await new Promise(serve => { setTimeout(() => { serve(11); }, 10); // Each 10 ms... }); } (async () => { for await (const prime of primes()) // 'for' awaits prime values... console.info(prime); })(); // Lambda is immediately called for test only...