{"id":26021209,"url":"https://github.com/teclone/forensic-js","last_synced_at":"2026-04-20T11:05:14.314Z","repository":{"id":57240112,"uuid":"142606390","full_name":"teclone/forensic-js","owner":"teclone","description":"A collection of tested JavaScript modules that performs different kinds of web development tasks","archived":false,"fork":false,"pushed_at":"2018-12-28T09:56:42.000Z","size":402,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-20T16:49:59.713Z","etag":null,"topics":["event-management","fetch-api","queue-processor","xhr-framework","xml-parser","xpath-api"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teclone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-07-27T17:37:15.000Z","updated_at":"2023-09-08T17:43:13.000Z","dependencies_parsed_at":"2022-08-29T22:21:33.021Z","dependency_job_id":null,"html_url":"https://github.com/teclone/forensic-js","commit_stats":null,"previous_names":["harrison-ifeanyichukwu/forensic-js"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teclone%2Fforensic-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teclone%2Fforensic-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teclone%2Fforensic-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teclone%2Fforensic-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teclone","download_url":"https://codeload.github.com/teclone/forensic-js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242179280,"owners_count":20084940,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event-management","fetch-api","queue-processor","xhr-framework","xml-parser","xpath-api"],"created_at":"2025-03-06T08:50:29.995Z","updated_at":"2025-12-06T11:01:32.100Z","avatar_url":"https://github.com/teclone.png","language":"JavaScript","readme":"# ForensicJS - Module-based JS Library\n\n[![Build Status](https://travis-ci.org/harrison-ifeanyichukwu/forensic-js.svg?branch=master)](https://travis-ci.org/harrison-ifeanyichukwu/forensic-js)\n[![Coverage Status](https://coveralls.io/repos/github/harrison-ifeanyichukwu/forensic-js/badge.svg?branch=master)](https://coveralls.io/github/harrison-ifeanyichukwu/forensic-js?branch=master)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n[![npm version](https://badge.fury.io/js/forensic-js.svg)](https://badge.fury.io/js/forensic-js)\n![npm](https://img.shields.io/npm/dt/forensic-js.svg)\n\nForensicJS is a collection of tested JavaScript modules that performs different kinds of web development tasks. It unifies all its API across target environments (browsers, node.js) and provides pollyfills wherever possible.\n\nIt is developed to be cross platform and can be used in both `browser` and `node.js` environments without additional setup needs.\n\n## Contributing to ForensicJS\n\nTo actively contribute, inspect or take a walk through on the development process of this library, you should have the latest [Git](https://git-scm.com/), [Node.js](https://nodejs.org) and [NPM](https://www.npmjs.com/) installed and follow the steps below:\n\n**Clone this repository to your machine:**\n\n```shell\ngit clone git://github.com/harrison-ifeanyichukwu/forensic-js.git \u0026\u0026 cd forensic-js\n```\n\n**Install development dependencies:**\n\n```shell\nnpm install\n```\n\n**Test comand:**\n\n```shell\nnpm test\n```\n\n**View test in browser:**\n\n```shell\nnpm start #start up the test server that ships with the project\n```\n\nNavigate to `http://localhost:4000/test` to view test.\n\n## Customizing Your Build\n\nYou can customize your own build by tweaking the [forensic-js](https://github.com/harrison-ifeanyichukwu/forensic-js) `.buildrc.json` config file located inside the project root directory as well as the `exports.js` file (located inside the src directory).\n\n```shell\nnpm run build\n```\n\nBy default, separate builds are created in both `CommonJS` \u0026 `IIFE` formats for [node.js](https://nodejs.org/en/) \u0026 browsers respectively. All browser builds are located inside the `dist` directory while [node.js](https://nodejs.org/en/) builds are located inside the `lib` folder with separate entries for each module.\n\n## Getting Started\n\n**NPM Install:**\n\n```shell\nnpm install --save forensic-js\n```\n\n**Node.js usage samples**:\n\n```javascript\n//import everything\nimport * as FJS from 'forensic-js';\n\n//import some specific modules\nimport {Util, Queue, Xhr} from 'forensic-js';\n\n//import a single module\nimport XML from './node_modules/forensic-js/lib/modules/XML.js';\n```\n\n**Browser usage samples**:\n\n```html\n\u003c!-- include forensic-js. exposes FJS on the window --\u003e\n\u003cscript type=\"text/javascript\" src=\"node_modules/forensic-js/dist/exports.js\"\u003e\u003c/script\u003e\n\n\u003c!-- include Xhr module. exposes Xhr on the window --\u003e\n\u003cscript type=\"text/javascript\" src=\"node_modules/forensic-js/dist/modules/Xhr.js\"\u003e\u003c/script\u003e\n\n\u003c!-- include XPath module. exposes XPath on the window --\u003e\n\u003cscript type=\"text/javascript\" src=\"node_modules/forensic-js/dist/modules/XPath.js\"\u003e\u003c/script\u003e\n```\n\n\u003e**Note that minified builds are available too.**\n\n## Current Modules\n\n1. [Globals module](#globals-module)\n2. [Utility module](#utility-module)\n3. [Queue module](#queue-module)\n4. [Event module](#event-module)\n5. [Xhr module](#xhr-module)\n6. [XML module](#xml-module)\n7. [XPath module](#xpath-module)\n\n## Globals Module\n\nThe `Globals` module provides unified global variables that all other modules use which include the host (`window`) and root (`document`) objects. The purpose is to make all other library modules independent of runtime environment, and to make component testing easy.\n\nIt provides the `install` methods, which are used to set the window \u0026 document objects (which can come from an `iframe`, `node-jsdom`, `topmost window`, etc.) on a global level respectively.\n\nAll modules provide the `install` method, when used separately.\n\n**Using `FJS` in Node.js**:\n\n```javascript\nimport * as FJS from 'forensic-js';\nimport {JSDOM} from 'jsdom';\n\nlet dom = new JSDOM('');\nFJS.install(dom.window, dom.window.document);\n\n//using with a single module\nimport XML from './node_modules/forensic-js/lib/XML.js';\nXML.install(dom.window, dom.window.document);\n```\n\nThe Globals module checks for the presence of a `window` and `document` objects, and installs them if both are found. Hence there is no need to call the `install(host, root)` method when running in a browser.\n\n**Automatically Detect Objects**:\n\n```javascript\nimport {JSDOM} from 'jsdom';\n\nlet dom = new JSDOM('');\n\n//set global window and document\nglobal.window = dom.window;\nglobal.document = window.document;\n\n//will automatically detect and install\nimport * as FJS from 'forensic-js';\n\n//will still automatically detect and install\nimport XML from './node_modules/forensic-js/lib/XML.js';\n```\n\n## Utility Module\n\nThe `Util` module provides some bunch of utility methods that are handy to most other modules. Many of these methods tests variable types, perform string operations, run scoped processes and lots more.\n\n**Test Variables Types**:\n\n```javascript\nUtil.isCallable(variable);\nUtil.isArray(variable);\nUtil.isObject(variable);\nUtil.isPlainObject(variable);\n...\n```\n\n**Generate a Callback function**:\n\n```javascript\nlet callback = function(name, age) {\n    console.log(`My name is ${name}. I am ${this.age} years old`);\n},\nscope = {age: 21};\n\nUtil.generateCallback(callback, scope, 'Harrison')();\n//will log My name is Harrison. I am 21 years old.\n```\n\n**Run code Safely, Supress any Runtime Errors**:\n\n```javascript\nlet callback = function(arg1) {\n    console.log(arg1);\n    throw new Error('this error will be suppressed');\n},\nscope = {age: 21};\n\nUtil.runSafe(callback, scope, 33);\n//logs 33\n\n/**\n* run the callback after some given number of milliseconds have passed.\n* it returns a promise in such cases\n*/\nUtil.runSafe(callback, scope, 30, 5000).then(() =\u003e {console.log('done')});\n```\n\n**Encode query components**:\n\n```javascript\nconsole.log(Util.encodeComponent('name', 'Uchenna'));\n//name=Uchenna\n\nconsole.log(Util.encodeComponents({\n    username: 'elvis',\n    password: 'random'\n}));\n\n//username=elvis\u0026password=random;\n```\n\n**Generate random text**:\n\n```javascript\n//generates a random string that is 8 character length\nconsole.log(util.getRandomText(8));\n```\n\n**Apply Camel Casing**:\n\n```javascript\nconsole.log(Util.camelCase('user-name'));\n//logs userName\n\nconsole.log(Util.camelCase('user;name', ';')); //pass in the delimeter as second option\n//logs userName\n```\n\nThe [Util](#utility-module) module contains lots of other methods, such as `nodeContains(parent, child)` method, `loadInlineCSS(cssCode)`, `mergeObjects(...objects)` methods.\n\n## Queue Module\n\nThis module offers great possibilities for managing related items, letting you sort, search, delete, manipulate items using different criteria as suits your application. It is built to be extensible and offers array-like operations on the queued items.\n\n```javascript\n//module signature\nnew Queue(\n    items?: any[]|any,\n    sortable?: boolean,\n    caseSensitive?: boolean,\n    defaultFnSort?: ((item1: any, item2: any, caseSensitive: boolean): number),\n    defaultFnSearch?: ((key: any, compareWith: any, caseSensitive: boolean) =\u003e number)\n);\n```\n\n**Usage example:**\n\nA simple usage example would be to process a list of employees data as loaded from a server. The resulting response is assumed to be in [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) format.\n\n**The employees data structure**:\n\n```json\n[\n    {\n        \"id\": 1,\n        \"name\": \"Aaron Munachi\",\n        \"salary\": 150\n    },\n    .....more records\n]\n```\n\n**Load and create an employees Queue**:\nIt uses the [Xhr module](#xhr-module) to fetch the resource.\n\n```javascript\nXhr.get('/employees').then((response) =\u003e {\n    //default sort function. sorts using the id criteria\n    let defaultFnSort = function(emp1, emp2) {\n        return Queue.fnSort(emp1.id, emp2.id);\n        //always reuse the Queue class' static fnSort method.\n    },\n\n    //default search method. searches using the id criteria.\n    defaultFnSearch = function(empId, emp, caseSensitive) {\n        return Queue.fnSearch(empId, emp.id, caseSensitive);\n        //always reuse the Queue class' static funSearch method.\n    };\n\n    //create the employees Queue\n    let employees = new Queue(response.json(), true, true, defaultFnSort, defaultFnSearch);\n});\n```\n\n**Iterating through items**:\n\n```javascript\n// List all employees in ascending order using the id criteria\n/**\n * outputs a row of data to our table\n*/\nfunction writeRow(target: HTMLTableSectionElement, ...cellValues: any[]) {\n    let row = target.insertRow();\n    cellValues.forEach(cellValue =\u003e {\n        row.insertCell().appendChild(document.createTextNode(cellValue));\n    });\n};\n\n/**\n * clear all row\n*/\nfunction clearRows(target: HTMLTableSectionElement) {\n    let len = tBody.rows.length;\n    while (--len \u003e= 0) {\n        tBody.deleteRow(len);\n    }\n}\n\nlet table = document.getElementById('employees-table'),\n    tHead = table.tHead,\n    tBody = table.tBodies[0];\n\n//output header.\nwriteRow(tHead, '#id', 'name', 'salary');\n\n//output rows\nemployees.forEach((emp) =\u003e {\n    writeRow(tBody, emp.id, emp.name, employer)\n});\n```\n\n**Note that** we did not have to sort the result set as it is already sorted when it was created.\n\n**Deleting Items**:\n\n```javascript\n//delete the first employee whose id equals 1\nemployees.deleteItem(1);\n\n//delete employee at index 3. index starts from 0.\nemployees.deleteIndex(3);\n```\n\n**Run Utility Operations**:\n\n```javascript\n//Filter out employees whose salary exceeds $200\nlet topEarners = employees.filter(emp =\u003e emp.salary \u003e 200);\n\n//check if all employees earn at least $50\nconsole.log(employees.every(emp =\u003e emp.salary \u003e= 50));\n\n//check if some employees earn below $50\nconsole.log(employees.some(emp =\u003e emp.salary \u003c 50));\n\n//calculate all employees salary added together\nconsole.log(employees.reduce((result, current) =\u003e result + current.salary, 0);\n```\n\n**Extend list of criteria afforded by the instance**:\n\n```javascript\n//add sort \u0026 search by name criteria\nemployees.addSortFunction('by-name', (emp1, emp2) =\u003e {\n    return Queue.fnSort(emp1.name, emp2.name);\n})\n    .addSearchFunction('by-name', (empName, emp, caseSensititve) =\u003e {\n        return Queue.fnSearch(empName, emp.name, caseSensititve);\n    });\n\n//add sort \u0026 search by salary criteria, but in descending order\nemployees.addSortFunction('by-salary', (emp1, emp2) =\u003e {\n    return Queue.fnSort(emp2.salary, emp1.salary);\n})\n    .addSearchFunction('by-salary', (empSalary, emp, caseSensititve) =\u003e {\n        return Queue.fnSearch(emp.salary, empSalary, caseSensititve);\n    });\n```\n\n**Sort using a specific criteria**:\n\n```javascript\n//list employers using their salary as criteria\nclearRows(tBody);\n\nemployees.sort('by-salary');\nfor (const emp of employees)\n    writeRow(tBody, emp.id, emp.name, emp.salary);\n```\n\n**Perform search \u0026 delete operations**:\n\nThe `indexOf(key: any, criteria?: string): number` searches for the given `key` using the criteria or default criteria if not specified. It returns the index position of the first item match or `-1` if no result is found.\n\n```javascript\nconsole.log(employees.indexOf(302, 'by-salary'));\n\n//return true if there is an item matching the queue\nincludes(key: any, criteria?: string): boolean\n\n//find and return the first item that matches the key.\n//returns null if not found. optionally delete the item if found\nfind(key: any, criteria?: string, deleteIfFound?: boolean): item|null\n\n//delete first employee whose name equals 'Aaron Munachi'\nemployees.deleteItem('Aaron Munachi', 'by-name');\n```\n\n**Write A Custom Queue class that extends the `Queue` module**\n\n```javascript\nimport {Queue, Util} from 'forensic-js';\n\nclass Employees extends Queue {\n    constructor(items, sortable, caseSensitive, fnSort, fnSearch) {\n        super(items, sortable, caseSensitive, fnSort, fnSearch);\n    }\n\n    /**\n     * define a quard method, that screens out invalid employee objects during insertion\n     * operations.\n     *\n     *@override\n    */\n    screen(item) {\n        return super.screen(item) \u0026\u0026 Util.isPlainObject(item) \u0026\u0026 Util.isNumber(item.id)\n        \u0026\u0026 this.includes(item.id) === false;\n    }\n}\n```\n\n## Event Module\n\nThe `Event` module abstracts away `dom` and `custom` event handling logic from the browser and stops the browser from propagating events down and up the document tree. It does this by registering just two event listeners on the `host` \u0026 `root` object, and calls `event.stopPropagation()` at those top levels.\n\nIt then handles the event propagations internally, taking care of `capturing phase` listeners, `passive` listeners, `runOnce` listeners, `target phase only` listeners, listeners' `priority` run order and lots more.\n\nThe rationale behind this module is to:\n\n1. Provide unified interface for cross platform event handling, providing pollyfill for unimplemented Event interfaces such as [Pointer Events](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent), [Input Events](https://www.w3.org/TR/uievents/#events-inputevents), etc.\n\n2. Remove the hazzle associated with binding listeners for vendor prefixed events such as [Animation](https://www.w3.org/TR/css-animations-1/#events) and [Transition](https://www.w3.org/TR/css-transitions-1/#transition-events) events. You simply specify the event type such as `transitionend` without worrying about the accurate vendor prefixed type.\n\n3. Remove the hazzle associated with some browsers dispatching certain events on `window` while others dispatch same on the `document`. You choose where to register the handler, `document` or  `window`. It takes care of it.\n\n4. Offer ability to unbind even annonymous functions throught `Event.unbindAll(type, target, config)` method.\n\n5. Prevent the browser from dispatching incessant event and from performing `dom tree walk` while propagating events.\n\n6. Offer ability to alter the order of execution of event listeners through the `config.priority`, `config.runFirst`, and `config.runLast` flags.\n\n7. Offer a centralized system where events are handled, throttled (resize and scroll events), rejected or debounced.\n\n\u003e **NB:** When binding events using vanilla JavaScript or other Event libraries, you should disable this module from stopping the browser from propagating events. To do this, set the `.silenceEvents` property to false. i.e `Event.silenceEvents = false`.\n\n**Listening for DOM Ready Event**:\n\nTo run a callback once the document is ready, use the `.ready(callback, scope?, ...parameters?)` method. You can register multiple callback functions by calling this method multiple times. It is chainable.\n\n```javascript\nEvent.ready((e) =\u003e {\n    console.log('document is ready');\n})\n    .ready((e) =\u003e {\n        console.log('second call');\n    });\n```\n\nYou can as well pass in a `scope` object as a second parameter, as well as extra comma separated list of parameters to pass to `callback` function during execution.\n\n**Binding Event Listeners**:\n\nTo bind event listeners, there is the `.bind()` \u0026\u0026 `.on()` methods that binds an `event` listener callback for a given set of event `type(s)` on a given event `target`.\n\n```javascript\n//method signature\nE.bind(\n    type: string|string[],\n    callback: ((event: Event, ...args?: any[]) =\u003e void),\n    target: EventTarget,\n    config?: EventConfigOptions|null,\n    scope?: {}|null,\n    ...parameters?: any[]\n);\n\nlet testDiv = document.getElementById('my-div');\n\n//click event listener\nEvent.bind('click', (e) =\u003e {\n    console.log(e.type);\n}, testDiv)\n\n    .on('click', (e) =\u003e {\n        console.log(e.type);\n    }, testDiv)\n\n    .dispatch('click', testDiv); //trigger the click event.\n```\n\n**Binding RunOnce Event Listeners**:\n\nTo bind event listener callbacks that will only get executed once, set the boolean `config.runOnce` parameter to `true` or use the `once()` bind method\n\n```javascript\nlet testImage = new Image();\n\nEvent.bind('load', (e) =\u003e {\n    console.log('image is loaded');\n}, testImage, {runOnce: true})\n\n    .once('load', (e) =\u003e {\n        console.log('image is loaded');\n    }, testImage);\n\ntestImage.src = 'image.png';\n```\n\n**Binding Listeners on the Capturing Phase**:\n\nTo bind event listener callback that will get executed in the capturing phase, set the boolean `config.capture` parameter to `true`. The default value is `false`.\n\n```javascript\nEvent.once('click', (e) =\u003e {\n    console.log(e.phase); //logs 1\n}, document.body, {capture: true})\n\n    .dispatch('click', document.body.firstElementChild);\n```\n\n\u003e **NB**. Event listeners bound on the capturing phase are not executed for events that originated from the target. Events that originated from the target will be on the `target phase` by the time it reaches the `target` element.\n\n**Binding Listener on the target Phase Only**:\n\nTo bind event listener callbacks that will get executed for only events that originated from the target, set the boolean `config.acceptBubbledEvents` parameter to `false`. The default value is `true`.\n\n```javascript\nlet testDiv = document.createElement('div');\ndocument.body.appendChild(testDiv);\n\nlet callCount = 0,\n\n    callback = function() {\n        callCount += 1;\n    };\n\n_Event.bind('click', callback, document.body, {\n    acceptBubbledEvents: false\n})\n\n    .dispatch('click', testDiv) // this event should not trigger the callback\n\n    .dispatch('click', document.body) // this event should trigger the callback\n\n    .unbind('click', callback, document.body);\n\nconsole.log(callCount); //logs 1\n```\n\nOnly the second event `dispatch` will trigger the `listener`. The first event `dispatch` will not despite bubbling up to the testDiv parent element (`document.body`).\n\n**Binding Passive Event Listeners**:\n\nPassive event listeners are event listeners that is not supposed to call `preventDefault()`, thereby telling the browser before hand to keep updating the `UI` and not wait for the listener to finish executing.\n\nTo use this feature, set the boolean `config.passive` parameter to `true`. The default value is `false`. Passive event listeners are great for events like `touch events`, and `wheel events`. To learn more about passive events, and web scroll performance check this [google developers article](https://blog.chromium.org/2016/05/new-apis-to-help-developers-improve.html).\n\n```javascript\nEvent.on('touchstart', (e) =\u003e {\n    console.log(e.passive); //logs true.\n    e.preventDefault(); // not acceptable. throws error\n}, document.body, {\n    passive: true\n});\n```\n\n**Alter the Execution Order of Event Listeners**:\n\nThe `Event` module makes it possible and easy to alter the execution order of event listeners, thanks to the power afforded to it by the [Queue](#queue-module) module. By using this feature, you can achieve a great effect as you dictate how `event` listeners are to be executed.\n\n1. **Alter Through The `config.priority` Option**:\n\n    By specifying a `priority` value for a listener callback during the bind process, we can alter its execution in the midst of other bound listeners, like below:\n\n    ```javascript\n    let signatures = [],\n    testImage = new Image(300, 500);\n\n    let firstListener = function() {\n            signatures.push('first');\n        },\n\n        secondListener = function() {\n            signatures.push('second');\n        },\n\n        thirdListener = function() {\n            signatures.push('third');\n        };\n\n    Event.bind('load', firstListener, testImage, {\n        priority: 9 // gets executed last after callback with default priority of 5\n    })\n\n        .on('load', secondListener, testImage) //default priority of 5 is assigned.\n\n        .on('load', thirdListener, testImage, {\n            priority: 1 //gets executed first.\n        })\n\n        .dispatch('load', testImage)\n\n        .unbindAll('load', testImage); //unbind  all the load event listeners\n\n    console.log(signatures.join('\u003e\u003e')); //logs third\u003e\u003esecond\u003e\u003efirst\n    ```\n\n    \u003e **NB:** Priority values are rated higher in descending order. priority value of `3` is rated higher than priority value of `10` for instance.\n\n2. **Alter Through The `config.runFirst` Option**:\n\n    By setting the `runFirst` option to `true`, on a listener callback during the bind process, You are asking that the listener should be executed first in the midst of other listeners along the chain of the event target.\n\n    Note that, when there are two or more listeners with the `runFirst` flag set to `true`, their `priority` values will be used.\n\n    ```javascript\n    let signatures = [],\n        testInput = document.createElement('input');\n    testInput.type = 'text';\n\n    let firstListener = function() {\n            signatures.push('first');\n        },\n\n        secondListener = function() {\n            signatures.push('second');\n        },\n\n        thirdListener = function() {\n            signatures.push('third');\n        };\n\n    Event.on('keydown', firstListener, testInput) // priority defaults to 5. gets executed last\n\n        .on('keydown', secondListener, testInput, {\n            priority: 3 //gets executed immediately after the thirdListener.\n        })\n\n        .on('keydown', thirdListener, testInput, {\n            priority: 10,\n            runFirst: true //gets executed first due to the runFirst config despite having\n            //the least priority\n        })\n\n        .dispatch('keydown', testInput)\n\n        .offAll('keydown', testInput); //unbind all the keydown event listeners\n\n    console.log(signatures.join('\u003e\u003e')); //logs third\u003e\u003esecond\u003e\u003efirst\n    ```\n\n3. **Alter Through The `config.runLast` Option**\n\n    By setting the `runLast` option to `true`, on a listener callback during the bind process, You are asking that the listener should be executed last in the midst of other listeners along the chain of the event target.\n\n    Note that, when there are two or more listeners with the `runLast` flag set to `true`, their `priority` values will be used.\n\n    ```javascript\n    let signatures = [],\n        testInput = document.createElement('input');\n    testInput.type = 'text';\n\n    let firstListener = function() {\n            signatures.push('first');\n        },\n\n        secondListener = function() {\n            signatures.push('second');\n        },\n\n        thirdListener = function() {\n            signatures.push('third');\n        };\n\n    Event.bind('focus', firstListener, testInput, {\n        priority: 1,\n        runLast: true // gets executed just before the secondListener, because of the\n        //priority difference though both are configured to run last.\n    })\n\n        .bind('focus', secondListener, testInput, {\n            runLast: true //gets executed last. it priority is defaulted to 5\n        })\n\n        .bind('focus', thirdListener, testInput, {\n            priority: 10 //gets executed first. despite having the least priority.\n            //This is because other listeners have been configured to run last.\n        })\n\n        .dispatch('focus', testInput)\n\n        .unbindAll('focus', testInput); //unbind all the focus event listeners\n\n    console.log(signatures.join('\u003e\u003e')); //logs third\u003e\u003efirst\u003e\u003esecond\n    ```\n\n**Specifying Execution Scope and Extra Parameters**:\n\nYou can specify an execution scope object as a fifth parameter. If specified, the `this` object inside the listener will refer to the object rather than the event `target`.\n\n```javascript\nlet isAccurateScope = false,\n    scope = {id: 'testing scope'};\n\nlet callback = function() {\n    if (this === scope)\n        isAccurateScope = true;\n};\n\nEvent.bind('copy', callback, document, {\n    runOnce: true\n}, scope)\n\n    .dispatch('copy', document);\n\nconsole.log(isAccurateScope); // logs true\n```\n\nYou can also pass in additional **comma separated** list of parameters that will be sent to the listener callback.\n\n```javascript\nlet list = [];\n\nlet callback = function(e, param1, param2, param3) {\n    list.push(param1, param2, param3);\n};\n\n_Event.once('pagehide', callback, window, null, null, 1, 2, 3)\n/**            ^^^       ^^^^^^   ^^^^^^  ^^^^^ ^^^^^ ^^^^^^^\n *              ||          ||       ||     ||     ||     ||\n               type      listener  target config scope parameters\n**/\n    .dispatch('pagehide', document); //trigger on document, it will bubble to window\n\nconsole.log(list.join('\u003e\u003e')); //logs 1\u003e\u003e2\u003e\u003e3\n```\n\n## Xhr Module\n\nThis is an `XMLHttpRequest` [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) based implementation of [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)  with inbuilt request prioritization and polling manager. It exposes similar features as offered by [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) API.\n\nUnlike in [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) that rejects only on network error, the **Xhr** module rejects all responses whose status code does not fall withing the `200` mark and is not a [304, Not Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) status code.\n\n**Making a simple fetch call**:\n\n```javascript\nXhr.fetch(url).then(response =\u003e {\n    console.log(response.ok); // true\n})\n    .catch(response =\u003e {\n        let message = '';\n        switch (response.statusCode) {\n            case 404:\n                message = 'Resource not found';\n                break;\n            //more test cases follows\n        }\n    });\n```\n\n**Restful methods**:\n\nBy default, the fetch method uses `GET` method for requests, unless a second options object is passed in. You can make use of restful methods that are already made available.\n\n```javascript\ntype requestOptions = {\n    method?: string,\n    responseType?: 'json'|'document'|'blob'|'arraybuffer',\n    data?: {}|FormData,\n    cache?: 'no-cache'|'force-cache'|'only-if-cached'|'default'|'reload',\n    priority?: number,\n    withCredentials?: boolean,\n    timeout?: number|null,\n    contentType: string,\n    progress?: ((loaded: number, total: number) =\u003e void),\n    headers?: {}\n};\n\n//universal\nXhr.fetch(url: string, options?: requestOptions);\n\n//make GET request\nXhr.get(url: string, options?: requestOptions);\n\n//make POST request\nXhr.post(url: string, options?: requestOptions);\n\n//make PUT request\nXhr.put(url: string, options?: requestOptions);\n\n//make HEAD request\nXhr.head(url: string, options?: requestOptions);\n\n//make DELETE request\nXhr.delete(url: string, options?: requestOptions);\n\n//make OPTIONS request\nXhr.options(url: string, options?: requestOptions);\n```\n\n**Set Custom Global Request headers**:\n\nTo set custom global headers that is sent along with every request, use `Xhr.addHeader(name, value)` or `Xhr.addHeaders(headers)` methods. This is handy for setting global headers such as `X-CSRF-TOKEN`, `X-Requested-With`.\n\n```javascript\n//the calls are chainable\nXhr.addHeader('X-Requested-With', 'XMLHttpRequest').addHeaders({\n    'X-CSRF-TOKEN': 'token'\n});\n```\n\nTo remove already set global headers, use `Xhr.removeHeader(headerName)` and `Xhr.removeHeaders(...headerNames)` methods.\n\n```javascript\nXhr.removeHeaders('X-Requested-With', 'X-CSRF-TOKEN');\n```\n\n**Specify Request data through `options.data`**:\n\nYou can specify request data to send by passing in an `options.data` parameter. The parameter can either be a plain object containing data `key:value` pairs or a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.\n\nFor [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) \u0026 [PUT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) operations, the data is transmitted through the request body. For other methods, the data is transmitted through the url as encoded `query` parameters (only plain objects are accepted in this case).\n\n```javascript\n//use form data\nlet data = new FormData(), //we could just pass in the form to the constructor.\n    elements = document.forms['frm-signup'];\n\ndata.append('username', elements.username.value);\ndata.append('email', elements.email.value);\ndata.append('password', elements.password.value);\n\nlet options = {\n    data: data,\n    responseType: 'json'\n};\n\nXhr.post('user/create', options).then(response =\u003e {\n    if (response.json().status === 'success')\n        console.log('account created');\n    else\n        console.log(response.json().errors);\n});\n```\n\n**Access Response headers. `Response#getHeader(name)` \u0026 `Response#getHeaders(camelize?`**:\n\nTo access response headers, there are `getHeader(name)` \u0026 `getHeaders(camelize?)` methods on the passed in [Response](#response) instance.\n\nThe former returns the value for a given header while the latter returns all the response headers in one go, as an object of header `name:value` pairs.\n\nThe `camelize` optional boolean parameter specifies if the header names should be turned into camel cases, allowing one to use the `.` notation when accessing headers. The default value is `true`.\n\n```javascript\n//get resource meta headers\nXhr.head('rss-feed.xml').then(response =\u003e {\n    response.getHeader('Last-modified');\n\n        //OR\n\n    let headers = response.getHeaders();\n    console.log(headers.contentType, headers.contentLength, headers.etag);\n\n        //OR\n\n    //get headers without camelizing the names.\n    //all headers are in lowercase\n    headers = response.getHeaders(false);\n    console.log(headers['content-type'], headers['content-length'], headers['etag']);\n});\n```\n\n**Alter Request execution order through `options.priority`**:\n\nFor a large application that has lots of requests to poll, one may decide to prioritize request polling based on their importance. For such scenerio, the `options.priority` method becomes handy. Note that the lower the assigned priority value, the more prioritized the request will be.\n\n```javascript\n/**\n* here, we want to specify the order of request execution whenever there are\n* requests for chat messages, news feeds, notifications, birthdays, likes,\n* comments, etc\n*/\nXhr.get('/feeds', {priority: 10});\nXhr.get('/messages', {priority: 4}); //more prioritized than feeds\nXhr.get('/post/id/comments', {priority: 5}); //less important than messages but more\n//important than feeds\n...\n```\n\n\u003eNote that it has a way of balancing stuffs by promoting a request's `priority` level one step forward after the `request` has stayed in the queue for some determined number of milliseconds (the `promoteAfter` options which defaults `3000ms`). To change this value, use `Xhr.promoteAfter = msValue`.\n\n**Alter how Response is Parsed through `options.responseType`**:\n\nThe `options.responseType` parameter specifies how the resulting `response` should be parsed regardless of the response `content-type` header. Accepted values includes `json`, `document`, `blob`, and `arraybuffer`.\n\n```javascript\n//fetch an html document\nXhr.get('/about.html', {responseType: 'document'}).then(response =\u003e {\n    console.log(response.document.title);\n});\n```\n\n\u003eNote that by default, the module will parse response as [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) if the response `content-type` header matches either of `application/json` or `text/json`.\n\n**Watch for Request Progress through `options.progress`**:\n\nTo watch for progress events, simply pass in an options.progress callback parameter. The callback should accept two parameters, `loaded` \u0026 `total` data to load.\n\nUsage could be to update a progress bar as below:\n\n*HTML*:\n\n```html\n\u003cdiv class=\"progress-bar\" id=\"progress-bar\"\u003e\n    \u003cdiv class=\"bar\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\n*SCSS*:\n\n```scss\n.progress-bar {\n    position: relative;\n    background: grey;\n    height: 30px;\n\n    \u003e .bar {\n        display: absolute;\n        background-color: blue;\n        left: 0;\n        width: 0;\n        height: 100%;\n        transition: width 0.15s ease;\n    }\n}\n```\n\n*JavaScript*:\n\n```javascript\nlet progressBar = document.getElementById('progress-bar');\nXhr.post(url, {\n    data: dataToPost,\n    progress: (loaded, total) =\u003e {\n        //update progress bar.\n        let percent = (loaded / total) * 100;\n        progressBar.style.width = `${percent}%`;\n    }\n});\n```\n\n## XML Module\n\nThe **XML Module** unifies **Internet Explorer** `ActiveXObject` XML implementation and `DOMImplementation.createDocument()`  patterns.\n\nIt provides support for IE specific xml methods and properties , including `loadXML` method, `parseError`, `xml`, and `text` properties.\n\nIt is promise-based as both the `load(url)` \u0026 `loadXML(xmlString)` methods return promises.\n\n**Usage sample:**\n\n```javascript\nnew XML().load(url).then(xmlDoc =\u003e {\n    console.log(xmlDoc.xml); //logs the serialized xml document\n    console.log(xmlDoc.text); //logs the text content of the xml document\n\n    //get some nodes\n    xmlDoc.getElementsByTagName('element-tag');\n    ......\n})\n    .catch(xmlDoc =\u003e {\n        console.log(xmlDoc.parseError.reason);\n    });\n```\n\n**Creating XML**:\n\n```javascript\n//signature\nnew XML(qualifiedName?: string, namespaces?: {}, documentType?: DocumentType);\n\nlet namespaces = {\n    default: 'http://www.w3.org/ns',\n    svg: 'http://www.w3.org/2000/svg',\n    html: 'http://www.w3.org/1999/xhtml'\n};\n\nlet xml = new XML('root', namespaces);\n\nconsole.log(xml.xml);\n//\u003croot xmlns=\"http://www.w3.org/ns\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:html=\"http://www.w3.org/1999/xhtml\" /\u003e\n```\n\n## XPath Module\n\nThe `XPath` module provides interface for running xpath selection on a given valid `xml` node with support for namespacing. It  unifies IE ActiveXObject `selectNode`, `selectNodes`, \u0026 domimplementations `document.evaluate` methods.\n\n**Usage example**:\n\n```javascript\nimport {XML, XPath} from 'forensic-js';\n\nlet xmlString = `\n    \u003c?xml version=\"1.0\" encoding=\"utf-8\" ?\u003e\n    \u003cstudents xmlns=\"http://student.org/ns\"\u003e\n        \u003cstudent\u003e\n            \u003cname\u003eHarrison Ifeanyichukwu\u003c/name\u003e\n            \u003cclass\u003eJSS3\u003c/class\u003e\n            \u003crating\u003e0.7\u003c/rating\u003e\n        \u003c/student\u003e\n        \u003cstudent\u003e\n            \u003cname\u003eHelen Brown\u003c/name\u003e\n            \u003cclass\u003eJSS3\u003c/class\u003e\n            \u003crating\u003e0.9\u003c/rating\u003e\n        \u003c/student\u003e\n    \u003c/students\u003e\n`;\n\nlet namespaces = {\n    st: 'http://student.org/ns'\n};\n\nlet xmlDoc = new XML().loadXML(xmlString);\n\n//run selection from the document context\nXPath.selectNode('st:students/st:student/st:name', xmlDoc, namespaces);\n\n//run selection from the documentElement context\nXPath.selectNodes('st:students/st:student/st:name', xmlDoc.documentElement, namespaces);\n```\n\n## About Project Maintainers\n\nThis project is maintained by [harrison ifeanyichukwu](mailto:harrisonifeanyichukwu@gmail.com), a young, passionate full stack web developer, an [MDN](https://developer.mozilla.org/en-US/profiles/harrison-ifeanyichukwu) documentator, maintainer of w3c [xml-serializer](https://www.npmjs.com/package/@harrison-ifeanyichukwu/xml-serializer) project, node.js [R-Server](https://github.com/harrison-ifeanyichukwu/r-server), [Rollup-all](https://www.npmjs.com/package/rollup-all) one-time [Rollup.js](https://rollupjs.org/guide/en) build tool and other amazing projects.\n\nHe is available for hire, ready to work on amazing `PHP`,  `Node.js`,  `React`,  `Angular`,  `HTML5`,  `CSS`. Looks forward to hearing from you soon!!!","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteclone%2Fforensic-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteclone%2Fforensic-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteclone%2Fforensic-js/lists"}