Nomenclature Predictably Misunderstood
    Wondering what’s next for npm?Check out our public roadmap! »

    detached-navigation
    TypeScript icon, indicating that this package has built-in type declarations

    0.0.3 • Public • Published

    detached-navigation

    npm Build Status codecov codebeat badge Dependency Status devDependency Status JavaScript Style Guide

    Provides mocked location and history objects detached from the browser window. They can help if you need to navigate in your browser application (change the location and history), but you cannot afford changing the window location.

    Features:

    • Standard window, location, history and document interfaces, no new API to learn.
    • Zero deviations from the native behavior, except for location.reload.
    • Applicable in both web browser and Node.js.
    • Detaching (mocking) functions for window and Backbone provided.
    • All-in-one bundle and separate bundles for the three API functions.
    • Module integration with ES6, CJS and IIFE formats.
    • Small bundle sizes: all-in-one 11.3 kB, 5.1 kB, 1.6 kB, the Browser alone: 7.7 kB, 3.4 kB, 1.3 kB (normal, minified, gzipped).
    • TypeScript declarations included.
    • No dependencies.

    Usage scenarios:

    • Tests of SPA. If you verify navigation by tests running on the same page, you cannot cause the location changes to leave the testing page.
    • Embedded components. If you embed a component, which performs page navigations, in other page, you cannot let the embedded location changes disturb the parent window location.

    The Browser object can serve as an abstraction that you supply to your application, or component for navigation purposes to swap the functionality of the global window object. (Not the rest of the window functionality, of course, which would need other abstractions.)

    You can continue using the standard and familiar API to navigate in both native or detached (mocked) modes.

    Synopsis

    import { createDetachedBrowser, detachWindowHistory } from 'detached-navigation'
    // Create a browser object with the navigation interface of the window.
    const browser = createDetachedBrowser()
    // Let changes to location and history occur only in the browser object.
    const reattachWindowHistory = detachWindowHistory(window, browser)
    // Start listening to the detached location changes.
    window.addEventListener('popstate', () => console.log(window.location.href))
    // Push a location change to the detached history.
    window.pushState({}, 'Test', 'http://localhost/test')
    // Go back detached to the previous location, triggering the navigating event.
    window.history.back()
    // Return the navigation functionality to their original objects.
    reattachWindowHistory()
    import { createDetachedBrowser, detachBackboneHistory } from 'detached-navigation'
    // Let a component navigate with Backbone.history detached from window.
    detachBackboneHistory(Backbone.history, createDetachedBrowser())
    // Start listening to the detached route navigation.
    Backbone.history.on('route', () => console.log(Backbone.history.fragment))
    // Navigate detached to a specific route, triggering the routing event.
    Backbone.history.navigate('test', { trigger: true })

    Installation

    Make sure that you have installed Node.js. Use your favourite package manager (NPM, Yarn or PNPM) to add the detached-navigation module to your project. Add -D on the command line if you want to add the script to a bundle.

    npm i detached-navigation
    yarn add detached-navigation
    pnpm i detached-navigation
    

    Node.js and Bundling

    If you write a ES6 module for a bundler, import the exported functions your preferred way:

    import {
      createDetachedBrowser, detachWindowHistory, detachBackboneHistory
    } from 'detached-navigation'
     
    import createDetachedBrowser from 'detached-navigation/dist/detached-browser'
    import detachWindowHistory from 'detached-navigation/dist/detach-window'
    import detachBackboneHistory from 'detached-navigation/dist/detach-backbone'

    If you write a CJS module, either for Node.js or for a bundler, require the exported functions your preferred way:

    const {
      createDetachedBrowser, detachWindowHistory, detachBackboneHistory 
    = require('detached-navigation')
     
    const createDetachedBrowser = require('detached-navigation/dist/detached-browser.cjs')
    const detachWindowHistory = require('detached-navigation/dist/detach-window.cjs')
    const detachBackboneHistory = require('detached-navigation/dist/detach-backbone.cjs')

    Plain Web Page

    If you write a plain HTML page, load the script with your chosen global exports from CDN or from the local filesystem:

    <script src=https://unpkg.com/detached-navigation@0.0.2/dist/index.iife.min.js></script>
    <script>
      const {
        createDetachedBrowser, detachWindowHistory, detachBackboneHistory 
      } = detachedNavigation
    </script> 
     
    <script src=node_modules/detached-navigation/dist/detached-browser.iife.min.js></script>
    <script src=node_modules/detached-navigation@/dist/detach-window.iife.min.js></script>
    <script src=node_modules/detached-navigation@/dist/detach-backbone.iife.min.js></script>
    script name prefix export and its global name in window
    index object detachedNavigation with the tree keys below
    detached-browser function createDetachedBrowser
    detach-window function detachWindowHistory
    detach-backbone function detachBackboneHistory

    Alternatively, you can import the script from CDN or from the local filesystem to an ES6 module on a plain HTML page:

    <script type=module>
    import {
      createDetachedBrowser, detachWindowHistory, detachBackboneHistory
    } from 'https://unpkg.com/detached-navigation@0.0.2/dist/index.min.mjs'
     
    import createDetachedBrowser from
      './node_modules/detached-navigation/dist/detached-browser.min.mjs'
    import detachWindowHistory from
      './node_modules/detached-navigation/dist/detach-window.min.mjs'
    import detachBackboneHistory from
      './node_modules/detached-navigation/dist/detach-backbone.min.mjs'
    </script> 

    API

    The Browser object provided by this package implements a subset of the interface of the global window object, which takes part in page navigation. No other functionality. It covers the following scenarios:

    A Browser instance and the global window are meant to be replaceable without the application or a component noticing a difference. In regards of the navigation functionality, of course. There is one exception - the method browser.location.reload. It does not reload the page; it just dispatches the popstate event on the Browser instance.

    This package exports functions to create a Browser instance and to detach the navigation functionality from the original window or Backbone objects. Detaching methods return a function that can be used to re-attach the original location and history functionality. If you write an application from the scratch, you should perform the navigation using a Browser instance, which you initialize either to window or to a Browser instance depending on the target scenario.

    createDetachedBrowser

    Creates a new Browser instance. It can be used for a (mocked) navigation detached from the web page, or it can detach the native location ahd history in window or Backbone.

    function createDetachedBrowser(
      window?: Window, // the object to read the initial state, title and url from
      state?: object,  // the current history state, history.state by default
      title?: string,  // the current document title, document.title by default
      url?: string     // the current location, location.href by default
    ): Browser         // the object with the navigation interface like window

    If the window object is not supplied, the global window will be tried. If other parameters are missing, their values will be filled from the current window state. If no parameters are provided and no global window is available, the initial location will be set to { state: null, title: 'Untitled', url: 'detached://default' }.

    detachWindowHistory

    Detaches the native navigation from the window object and replaces it by the navigation using the Browser instance. Returns a function to undo this operation.

    function detachWindowHistory(
      window: Window,  // the window object to detach the location and history from
      browser: Browser // the object with the navigation interface like window
    ): () => {}        // function for re-attaching the original location and history

    detachBackboneHistory

    Detaches the native routing from the Backbone.history object and replaces it by the navigation using the Browser instance. Returns a function to undo this operation.

    function detachBackboneHistory(
      history: Bacbone.History, // the Backbone.history object to detach from window
      browser: Browser          // the object with the navigation interface like window
    ): () => {}                 // function for re-attaching the original history

    Browser

    The object returned by createDetachedBrowser offers a subset of the interface of window, Location, History and Document, which is needed for the navigation.

    class Browser {
      addEventListener(eventName: string, listener: Listener): void
      removeEventListener(eventName: string, listener: Listener): void
      dispatchEvent(event: Event): boolean
      location: Location
      history: History
      document: Document
      onpopstate: EventListener
      onhashchange: EventListener
    }

    See "Working with the History API" at MDN for more information.

    Location

    The object returned by browser.location from Browser with the interface of window.location. It has the same behaviour as its native original, but works detached from the original on the web page. Only the location.reload method behaves differently. It does not reload the page; it just dispatches the popstate event.

    class Location {
      assign(url: string): void
      reload(): void
      replace(url: string): void
      toString(): string
      hash: string
      host: string
      hostname: string
      href: string
      origin: string
      pathname: string
      port: string
      protocol: string
      search: string
    }

    See Location at MDN for more information.

    History

    The object returned by browser.history from Browser with the interface of window.history. It has the same behaviour as its native original, but works detached from the original on the web page.

    class History {
      back(): void
      forward(): void
      go(offset: number): void
      pushState(state: object, title: string, url: string): void
      replaceState(state: object, title: string, url: string): void
      length: number
      state: object
    }

    See History at MDN for more information.

    Document

    The object returned by browser.document from Browser with the interface of window.document. But only the part needed for navigation. Otherwise it has the same behaviour as its native original, but works detached from the original on the web page.

    class Document {
      title: string
    }

    See Document.title at MDN for more information.

    Build

    Make sure that you have installed Node.js 10 or newer, PNPM 5 or newer and Make 3.80 or newer. Clone the repository and run the build including the module installation, lint and test phases:

    git clone https://github.com/prantlf/detached-navigation.git
    cd detached-navigation
    make prepare lint test
    

    Maintenance

    Distribution and test files will be generated in dist and test directories. During the development, ensure proper coding standard and verify the changes:

    make fix test
    

    When you check the test coverage after running the target test, you will need to rebuild the output files, because the esm plugin caches them and would not allow nyc to supply the instrumented ones:

    make clean coverage
    

    Before releasing a new version, a clean re-built and re-test including code coverage should be performed:

    make new
    

    Upgrade

    Development module dependencies declared in package.json should be regularly upgraded to the most recent versions and this package rebuilt to check and adapt to their changes:

    make upgrade
    

    License

    Copyright (c) 2020 Ferdinand Prantl

    Licensed under the MIT license.

    Install

    npm i detached-navigation

    DownloadsWeekly Downloads

    53

    Version

    0.0.3

    License

    MIT

    Unpacked Size

    247 kB

    Total Files

    49

    Last publish

    Collaborators

    • avatar