From knowing Hash and Html5 History to simply implementing routing

Hash

The hash attribute is a readable and writable string, which is the anchor part of the URL (starting with the #), and hash in the page has a variety of functional meanings:

Anchor point

url: http://www.example.com/index.html#jump
dom: <a name="jump"></a> perhaps <div id="jump" >

After reading hash, the browser automatically scrolls to the visual area where the corresponding element is located.

Not attached to the request

It means that no matter how it changes, it will not affect the request URL, that is, it is only for browsers.

Browser: http://www.example.com/index.html#jump
 Server: http://www.example.com/index.html

Note: In some cases, you will have # symbols on the URL, but you did not intend to use them as hash, such as callback address or reference. At this time, the browser will only be treated as hash, so it needs to be transcoded first.

// Untranscoded
 Browser: http://www.example.com/index.html? Test= 123
 Server: http://www.example.com/index.html?=

// Transcoding
 Browser: http://www.example.com/index.html?test=%23123
 Server: http://www.example.com/index.html?test=%23123

Change access history without triggering page refresh

As you all know, although it won't jump or refresh, you can find that it will also be added to the access history by clicking on the browser forward and backward. (Low version IE is not considered.)

shortcoming

  • Search engine is unfriendly
  • Difficult to track user behavior

thinking

When the fragment identifier of the URL changes, the hashchange event (the part of the URL following the # symbol, including the # symbol) is triggered, and then some routing jump processing operations are performed according to the hash value. Specific parameters can be accessed to the location view.

http://www.example.com/index.html#jump

The most basic way to implement routing is to listen for events and judge the interface according to location.hash

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Document</title>
  </head>
  <body>
    <ul>
      <li>
        <a href="#/a">a</a>
      </li>
      <li>
        <a href="#/b">b</a>
      </li>
      <li>
        <a href="#/c">c</a>
      </li>
    </ul>
    <div id="view"></div>

    <script>
      var view = null;
      // Page loading does not trigger hashchange. Here, a hashchange event is triggered actively, which is faster than onLoad, so you need to operate here.
      window.addEventListener('DOMContentLoaded', function () {
        view = document.querySelector('#view');
        viewChange();
      });
      // Monitoring routing changes
      window.addEventListener('hashchange', viewChange);

      // Render view
      function viewChange() {
        switch (location.hash) {
          case '#/b':
            view.innerHTML = 'b';
            break;
          case '#/c':
            view.innerHTML = 'c';
            break;
          default:
            view.innerHTML = 'a';
            break;
        }
      }
    </script>
  </body>
</html>

Specific code can be viewed hash_demo.html

History

DOM window object provides access to the browser's session history through history object. It exposes many useful methods and attributes that allow you to jump forward and backward in user browsing history

Jump forward and backward

window.history.back();
window.history.forward();

Jump to a point specified in history

You can use go() to load a specific page in the session history and mark it by its relative position to the current page (the relative position of the current page is 0).

window.history.go();

Add entries in history

Change the current URL address without loading the page immediately, add an entry to the history unless you refresh the page, etc.

history.pushState(state, title , URL);
  • State object

    State is a JavaScript object, and the state attribute of the popstate event contains a copy of the state object of the history entry.

    State objects can be anything that can be serialized. The reason is that Firefox saves state objects on the user's disk for use when the user restarts the browser. We specify a size limit of 640k for state objects after serialization. If you pass a serialized state object greater than 640k to the pushState() method, it throws an exception. If you need more space, use session Storage and local Storage.

  • Title

    Firefox currently ignores this parameter, but may use it in the future. Passing an empty string here should be a safe precaution against future changes in this method. Alternatively, you can pass a short title for the jump state.

  • URL

    New historical URL records. The new URL does not have to be an absolute path. If the new URL is a relative path, it will be treated as relative to the current URL. The new URL must be homologous to the current URL, otherwise pushState() throws an exception. This parameter is optional and defaults to the current URL.

Note: pushState() will never trigger hashchange events, even if the new URL is only hashed different from the old one.

Change the current entry in the history

Change the current URL address without loading the page immediately, and change the current entry of the history unless you refresh the page, etc.

history.replaceState(state, title , URL);

popstate event

Whenever the history entry of the activity changes, the popstate event is passed to the window object. If the history entry of the current activity is created by pushState or changed by replaceState, the state attribute of the popstate event contains a copy of the current history state object.

Get the current state

When the page is loaded, there may be a non-null state object. This is possible, for example, if the page (through the pushState() or replaceState() method) sets the state object and the user restarts the browser. When the page is reloaded, the page receives an onload event, but no popstate event. However, if you read the history.state property, you will get the state object as popstate is triggered.

You can read the state object of the current history item without waiting for the popstate event

thinking

The default jump operation is forbidden by monitoring click events, and a set of jump logic is implemented manually by using history, which renders the interface according to location.pathname.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="ie=edge"/>
    <title>Document</title>
  </head>
  <body>
    <ul>
      <li>
        <a href="/a">a</a>
      </li>
      <li>
        <a href="/b">b</a>
      </li>
      <li>
        <a href="/c">c</a>
      </li>
    </ul>
    <div id="view"></div>

    <script>
      var view = null;
      // Page loading does not trigger hashchange. Here, a hashchange event is triggered actively, which is faster than onLoad, so you need to operate here.
      window.addEventListener('DOMContentLoaded', function () {
        view = document.querySelector('#view');
        document
          .querySelectorAll('a[href]')
          .forEach(e => e.addEventListener('click', function (_e) {
            _e.preventDefault();
            history.pushState(null, '', e.getAttribute('href'));
            viewChange();
          }));

        viewChange();
      });
      // Monitoring routing changes
      window.addEventListener('popstate', viewChange);

      // Render view
      function viewChange() {
        switch (location.pathname) {
          case '/b':
            view.innerHTML = 'b';
            break;
          case '/c':
            view.innerHTML = 'c';
            break;
          default:
            view.innerHTML = 'a';
            break;
        }
      }
    </script>
  </body>
</html>

Specific code can be viewed html5_demo.html
Note that this method does not support local operation and can only operate online or start the server to see the effect.

html5_demo.html:26 Uncaught DOMException: Failed to execute 'pushState' on 'History': A history state object with URL 'file:///C:/b' cannot be created in a document with origin 'null' and URL 'file:///C:/work/project/router_demo/src/html5_demo.html'.
    at HTMLAnchorElement.<anonymous> (file:///C:/work/project/router_demo/src/html5_demo.html:26:15)
(anonymous) @ html5_demo.html:26

Simple Encapsulated Routing Library

API

Basic routing methods:

  • router.push(url, onComplete)
  • router.replace(url, onComplete)
  • router.go(n)
  • router.back()
  • router.stop()
<!DOCTYPE html>
<html>
  <head>
    <title>router</title>
  </head>

  <body>
    <ul>
      <li onclick="router.push('/a', ()=>console.log('push a'))">push a</li>
      <li onclick="router.push('/b', ()=>console.log('push b'))">push b</li>
      <li onclick="router.replace('/c', ()=>console.log('replace c'))">replace c</li>
      <li onclick="router.go(1)">go</li>
      <li onclick="router.back(-1)">back</li>
      <li onclick="router.stop()">stop</li>
    </ul>
    <div id="view"></div>
  </body>
</html>

Initialization

import Router from '../router'

window.router = new Router('view', {
  routes: [
    {
      path: '/a',
      component: '<p>a</p>'
    },
    {
      path: '/b',
      component: '<p>b</p>'
    },
    {
      path: '/c',
      component: '<p>c</p>'
    },
    { path: '*', redirect: '/index' }
  ]
}, 'hash')// Or'html 5'

router class

import HashHstory from "./HashHistory";
import Html5History from "./Html5History";

export default class Router {
  constructor(wrapper, options, mode = 'hash') {
    this._wrapper = document.querySelector(`#${wrapper}`)
    if (!this._wrapper) {
      throw new Error(`You need to provide a container element insert`)
    }
    // Whether HTML5 History mode is supported or not
    this._supportsReplaceState = window.history && typeof window.history.replaceState === 'function'
    // Matching Path
    this._cache = {}
    // Default routing
    this._defaultRouter = options.routes[0].path
    this.route(options.routes)
    // Enabling mode
    this._history = (mode !== 'hash' && this._supportsReplaceState) ? new Html5History(this, options) : new HashHstory(this, options)
  }

  // Adding routing
  route(routes) {
    routes.forEach(item => this._cache[item.path] = item.component)
  }

  // Native Browser Forward
  go(n = 1) {
    window.history.go(n)
  }

  // Back-off of native browsers
  back(n = -1) {
    window.history.go(n)
  }

  // increase
  push(url, onComplete) {
    this._history.push(url, onComplete)
  }

  // replace
  replace(url, onComplete) {
    this._history.replace(url, onComplete)
  }

  // Remove events
  stop() {
    this._history.stop()
  }
}

Hash Class

export default class HashHistory {
  constructor(router, options) {
    this.router = router
    this.onComplete = null
    // Monitoring events
    window.addEventListener('load', this.onChange)
    window.addEventListener('hashchange', this.onChange)
  }

  onChange = () => {
    // Matching failure redirection
    if (!location.hash || !this.router._cache[location.hash.slice(1)]) {
      window.location.hash = this.router._defaultRouter
    } else {
      // Render view
      this.router._wrapper.innerHTML = this.router._cache[location.hash.slice(1)]
      this.onComplete && this.onComplete() && (this.onComplete = null)
    }
  }

  push(url, onComplete) {
    window.location.hash = `${url}`
    onComplete && (this.onComplete = onComplete)
  }

  replace(url, onComplete) {
    // graceful degradation
    if (this.router._supportsReplaceState) {
      window.location.hash = `${url}`
      window.history.replaceState(null, null, `${window.location.origin}#${url}`)
    } else {
      // You need to see if the current URL already has a hash value.
      const href = location.href
      const index = href.indexOf('#')
      url = index > 0
        ? `${href.slice(0, index)}#${url}`
        : `${href}#${url}`
      // Domain name unchanged will not refresh the page
      window.location.replace(url)
    }

    onComplete && (this.onComplete = onComplete)
  }

  // Remove events
  stop() {
    window.removeEventListener('load', this.onChange)
    window.removeEventListener('hashchange', this.onChange)
  }
}

HTML5 Class

export default class Html5Hstory {
  constructor(router, options) {
    this.addEvent()
    this.router = router
    this.onComplete = null
    // Monitoring events
    window.addEventListener('popstate', this.onChange)
    window.addEventListener('load', this.onChange)
    window.addEventListener('replaceState', this.onChange);
    window.addEventListener('pushState', this.onChange);
  }

  // pushState/replaceState does not trigger popstate events, so we need to customize
  addEvent() {
    const listenWrapper = function (type) {
      const _func = history[type];
      return function () {
        const func = _func.apply(this, arguments);
        const e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return func;
      };
    };
    history.pushState = listenWrapper('pushState');
    history.replaceState = listenWrapper('replaceState');
  }

  onChange() {
    // Matching failure redirection
    if (location.pathname === '/' || !this.router._cache[location.pathname]) {
      window.history.pushState(null, '', `${window.location.origin}${this.router._defaultRouter}`);
    } else {
      // Render view
      this.router._wrapper.innerHTML = this.router._cache[location.pathname]
      this.onComplete && this.onComplete() && (this.onComplete = null)
    }
  }

  push(url, onComplete) {
    window.history.pushState(null, '', `${window.location.origin}${url}`);
    onComplete && (this.onComplete = onComplete)
  }

  replace(url, onComplete) {
    window.history.replaceState(null, null, `${window.location.origin}${url}`)
    onComplete && (this.onComplete = onComplete)
  }

  // Remove events
  stop() {
    window.removeEventListener('load', this.onChange)
    window.removeEventListener('popstate', this.onChange)
    window.removeEventListener('replaceState', this.onChange)
    window.removeEventListener('pushState', this.onChange)
  }
}

Complete code can be copied router_demo

Tags: Javascript Attribute IE Session Firefox

Posted on Tue, 06 Aug 2019 18:11:09 -0700 by keiran420