How to store functions in HTML5 history states

So, I use HTML5 history management to add the ability to move back and forth on a website with loaded AJAX sub-content.

Now I would like to save the javascript functions in the state object in order to call the callback when the state appears. More or less similar to the following code:

$(window).bind("popstate", function(event) { var state = event.originalEvent.state; if (!state) { return; } state.callback(state.argument); } function beforeLoad() { var resourceId = "xyz"; var func; if (case1) { func = switchPageToMode1; } else { // case 2 func = swithPageToMode2; } func(resourceId); // run the function window.history.pushState({ callback: func, resourceId: resourceId }, "newTitle", "newURL"); // and push it to history } function switchPageToMode1(resourceId) { alterPageLayoutSomeWay(); loadResource(resourceId); } function swithPageToMode2(resourceId) { alterPageLayoutSomeOtherWay(); loadResource(resourceId); } function loadResource(resourceId) { ... } 

Good. Therefore, I am trying to save a link to a javascript function. But when you click on the state (the actual call to window.history.pushState), the browser processes the complaint, namely Error: "DATA_CLONE_ERR: DOM Exception 25"

Does anyone know what I'm doing wrong? Is it even possible to store function calls inside state?

+4
source share
3 answers

No, this is not possible, and not in any case. According to the MDC, the "state object", that is, the first argument to pushState , "can be anything that can be serialized." Unfortunately, you cannot serialize a function. The WHATWG spec says basically the same thing, but in many other words, the essence of which is that functions are explicitly forbidden in the state object.

The solution is to save either a string that you can eval or a function name in a state object, for example:

 $(window).bind("popstate", function(event) { var state = event.originalEvent.state; if ( !state ) { return; } window[ state.callback ]( state.argument ); // <-- look here } function beforeLoad() { var resourceId = "xyz", func ; if ( case1 ) { func = "switchPageToMode1"; // <-- string, not function } else { // case 2 func = "swithPageToMode2"; } window[ func ]( resourceId ); // <-- same here window.history.pushState( { callback : func, argument : resourceId }, "newTitle", "newURL" ); } 

Of course, assuming switchPageToMode1 and -2 are in a global context (i.e. window ), which is not best practice. If not, they should be accessible in some way from the global context, for example. [window.]MyAppGlobal.switchPageToMode1 , in which case you call MyAppGlobal[ func ]( argument ) .

+6
source

I came up with a slightly different solution.

I added two variables to the window variable

 window.history.uniqueStateId = 0; window.history.data = {}. 

Every time I do pushstate, all I do is push a unique identifier for the first parameter

 var data = { /* non-serializable data */ }; window.history.pushState({stateId : uniqueStateId}, '', url); window.history.data[uniqueStateId] = data; 

In the popstate event, I just grab the identifier from the state object and view it from the data object.

+1
source

That's what I'm doing:

  • Each HTML page contains one or more components that can create new history entries.
  • Each component implements three methods:
    • getId() , which returns its unique DOM identifier.
    • getState() , which returns the state of the component:

       { id: getId(), state: componentSpecificState } 
    • setState(state) , which updates the state of a component using the above value.
  • When the page loads, I initialize the mapping from component identifier to component as follows:

     this.idToComponent[this.loginForm.getId()] = this.loginForm; this.idToComponent[this.signupForm.getId()] = this.signupForm; 
  • Components retain their state before creating new history entries: history.replaceState(this.getState(), title, href);

  • When the popstate event popstate fired, I call:

     var component = this.idToComponent[history.state.id]; component.setState(history.state); 

To summarize: instead of serializing function() we serialize the identifier of the component and run its setState() function. This approach survives page loading.

0
source

Source: https://habr.com/ru/post/1391982/


All Articles