Search code examples
javascriptbrowser-historypolymer

Polymer core-menu double event


I'm using Polymer for my web-app and I'm currently having some issues with page linking. It's the first site I'm making using all ajax/javascript, thus I haven't used the history functions of javascript a lot yet.

Anyhow, I have a main menu in the left sidebar. When one of those is pressed, it should change the url of the browser and also put it in the history of the browser. To do so I have the following code:

Polymer('my-app', {
    mainMenu: function(){
        this.$.mainPages.selected = this.$.mainMenu.selected;
        console.log("Pusing state " + this.$.mainMenu.selected);
        history.pushState(null , "title", this.$.mainMenu.selected);
    }
});

Now the problem is that it gets called twice, so if you click once the pushState is called twice. Needless to say this is not good.

I have made a sample code here: click me.

In this sample code you can see after pressing a couple of menu-items, first of all the event gets fired two times. I've also noticed when pressing the back-button, updating the page seems to also refire the pushState.

So in short, my first concern is that the event of pressing an element from the core-menu, the mainMenu function is called twice. Second concern is that I am repushing states when going back, which I presume should be prevented as well.

Updated code:

Polymer('my-app', {
    ready: function(){
        var link = document.URL.split("/");
        this.$.mainMenu.selected = link[link.length-1];
        console.log("ready with link: "+link[link.length-1]);
        window.onpopstate = function(){
            var link = document.URL.split("/");
            console.log("Calling onpopstate. New link is: "+link[link.length-1]);
            this.$.mainMenu.selected = link[link.length-1];
            this.$.mainPages.selected = this.$.mainMenu.selected;
        }
    },
    nav: function(){
        this.$.drawerPanel.togglePanel();
    },
    back: function(){
        window.history.back();
    },
    mainMenu: function(){
        console.log("Pusing state " + this.$.mainMenu.selected);
        this.$.mainPages.selected = this.$.mainMenu.selected;
        history.pushState(null , "title", this.$.mainMenu.selected);
    }
}

Solution

  • You are much better off using data-binding. The idea here is to make your element model-driven, an model-view-presenter (MVP) pattern. The model is made up of properties in your element, the view is described by the template, and the presenter is in your script. Data-binding allows us to loosely couple the view from the logic.

    Let's decide the current page will be controlled by a property called page.

    Then we can set up our UI to be driven by the page property:

      <core-menu selected="{{page}}" valueattr="id">
         ...
      </core-menu>
      <core-animated-pages class="main" selected="{{page}}" valueattr="id" transitions="slide-from-right"> 
    

    This is good because we have decoupled the UI from the code. The code never talks directly to the core-animated-pages or the core-menu (notice I removed the ids). We don't listen to any events. This way we can change the UI at will without having to modify the script.

    Now, we want the page to be initially selected by the URL, so we'll initialize it that way, or default to 'home'. We also want to synchronize page to back events, so we'll listen to `onpopstate'.

       ready: function() {
          // scrape the initial page off the window location
          this.page = location.hash.slice(1) || 'home';
          // listen for 'back' events
          addEventListener('popstate', this.popstate.bind(this));
       },
       popstate: function(event) {
         // comes here whether we went 'back' from code or UI
         this.poppedPage = this.page = event.state;
       }
    

    We keep track of poppedPage so we can differentiate back and forward. We only want to push a new state when we go forward.

    Now we need the page to be reflected in the history, so we need to push state when the page changes. As above, the one caveat is that we need to only push the state if we are going forward.

        pageChanged: function() {
          // if the selected page changes, push a state (unless we are going backward)
          if (this.poppedPage !== this.page) {
            history.pushState(this.page, "Title", '#' + this.page);
          }
        }
    

    Here it is all put together:

    http://jsbin.com/luwitudu/9/edit