Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shouldn't there be a way to change query, path and hash at the same time? #6

Open
zboralski opened this issue Dec 19, 2015 · 10 comments

Comments

@zboralski
Copy link
Contributor

If I use an observe such as o(query, path) ... and click on a link, it is triggered twice. Once with the old query string and once with the new query string.

@zboralski
Copy link
Contributor Author

@rictic ?

@zboralski
Copy link
Contributor Author

Maybe export _dontUpdateUrl so we can set it to true while we set path, query and hash?

@rictic
Copy link
Collaborator

rictic commented Jan 6, 2016

Sorry for the delay, I was out for the holidays!

This is just the nature of Polymer's synchronous data binding and observing. What are you doing in your observer, and what kind of problems arise from it being called multiple times?

@robdodson
Copy link

I think I'd also like the ability to pass in, and receive the entire URL. I'm hooking iron-page-url up to router.js and am doing stuff like this:

// Tell iron-page-url to update the URL in response to a router update
this.router.updateURL = (url) => {
  var updated = new URL(url, window.location.origin);
  // Currently iron-page-url doesn't really have a way to pass
  // the entire URL around. You have to break it into pieces :\
  this.$.url.path = updated.pathname;
  this.$.url.query = updated.search;
  this.$.url.hash = updated.hash;
};
observers: [
  '_urlChanged(path, query, hash)'
],

_urlChanged: function(path, query, hash) {
  this.debounce('_urlChanged', function() {
    var url = path;
    url = query ? url + '?' + query : url;
    url = hash ? url + '#' + hash : url;
    console.log('urlChanged', url);

    // Tell the router to execute a handler based on URL change
    if (this.router) {
      this.router.handleURL(url)
      .catch(function(err) {
        if (err.name == 'UnrecognizedURLError') {
          console.log('Can\'t find', err.message);
        }
      });
    }
  });
},

It works but it's just kinda wonky

@rictic
Copy link
Collaborator

rictic commented Jan 15, 2016

I can see the convenience argument here, but then we would end up with multiple sources of truth, causing issues like:

var ironUrl = document.querySelector('iron-page-url');
ironUrl.addEventListener('url-changed', function() {
  assert(ironUrl.url === ironUrl.path); // fails!
});
ironUrl.url = '/new';

Maybe we could get around that by making url a readable-writable custom property. Or maybe this isn't a big enough issue to worry about?

\cc @sjmiles

@zboralski
Copy link
Contributor Author

@rictic I wouldn't worry so much as the assertion can still fail if the url changes again before the event handler has a chance to get executed.

@akc42
Copy link

akc42 commented Jul 7, 2016

I have a situation in which I am using distributed app-route elements to progressively pass down elements of the route to lower levels in the tree.

So a url path of something like /reports/bydate/missd/20160101/20100630/staff/4

is progressivly matched by app-route patterns of /:page, /:page, /:section/:startdate/:enddate, another /:page and finally /:id . The second /:page match puts me in an element my-reports-bydate which is a number couple of paper-input fields populated by a translated version of /:startdate and /:enddate, a custom element which can put up a drop down list based on which page is selected by iron pages selector and the selection item from within the list. This my-reports-bydate element also has a complex menu which can select a series of buttons dependant on the value of /:section. It also has a close button which has a tap event associated with it which sets the path in an iron-location to /reports. In this case the second /:page route is not matched, the route (at that level) in not active, and I use an observer on the routeData and active to select a menu in a set of iron-pages inside my my-reports element..

The reason I gave all that explanation is that when a button other than close is clicked in my-reports-bydate element I have Javascript code which creates an 'a' element with an href of /api/csv/xxx?params and a download attribute with a filename of xxx.csv which I then simulate a click on. This causes the server to download a .csv file. Currently though, the address bar in the browser gives no indication of which button is pressed. I do a similar thing with a server request to create and open in a new window PDF files.

What I would like to do is give these requests routed urls, which do appear in the address bar, and use elements which respond to those routed urls and a set of query parameters to pass them down to the server. I am thinking (since it isn't just my reports section that can generate these requests) is to have urls like /pdf/xxx or /csv/xxx and for elements in my first level route selection with queryParams that match the parameters I am passing. Because these originate deep down inside multiple nested elements, the only way I see then is to write them into an iron-location at that level much like I write a path into the iron-location with the close button.

However, as this issue points out, I can't see how I can successfully update the path and the query string simultaneously.. The effect of observers on a property and the notify flag seems to be that its effects will propagate throughout the dom tree when I either write the query parameters or the path first. I could write query parameter first, since I currently don't use query parameters elsewhere, but ...
.
.. My multiple levels of app-route elements partially comes from the fact that app-route does not allow optional parameters unless I use multiple app-route elements chained from tail to route and use their "active" flags to determine what has matched. One advantage of using a query string is that the parameters can be named and optional and could reduce the need to chain. I am thinking of changing my routing structure to use query parameters more in this case.

@rictic
Copy link
Collaborator

rictic commented Jul 12, 2016

A workaround for your issue @akc42 would be to call:

window.history.pushState({}, null, '/api/csv/xxx?params');
window.dispatchEvent(new CustomEvent('location-changed'));

That's a cheap and easy way to update the URL and notify any iron-location elements of the change. Creating iron-location elements may be overkill for your use case.

That said, this is still a desirable API for other use cases.

e111077 pushed a commit that referenced this issue Aug 2, 2016
@nik3daz
Copy link

nik3daz commented Feb 6, 2019

We're having an issue where dwellTime = 0 means that we push 2 history states when the path changes, and then the query changes, even though we intend for it to be one atomic operation. Our workaround is this:

// Disable state pushing.
ironLocation.dwellTime = Infinity;
ironLocation.hash = hash;
ironLocation.query = query;

// Re-enable state pushing.
ironLocation.dwellTime = 0;
ironLocation.path = path;

You'll still get multiple location-changed events, but this lets you completely manage your history stack.

@e111077
Copy link
Member

e111077 commented Feb 6, 2019

Wasn't this solved in polymer 2?

Does ironLoc.setProperties({query: val1, path: val2}) work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants