Polymer nested app routes are not mapping correctly - polymer

I am trying to get some basic routes right. I'm using Polymer 1.5.0 and I'm having problems using nested routes.
I'm using app-route 0.9.2
As this post suggests, Polymer uses a decentralized approach in routing. Therefore I decided to do the following:
<app-route route="{{route}}"
pattern="/:page"
data="{{data}}"
tail="{{tail}}">
</app-route>
<iron-pages selected="{{data.page}}" attr-for-selected="title" fallback-selection="404">
<pgarena-home-app title="" route="{{tail}}" ></pgarena-home-app>
<pgarena-tournament-app title="tournament" route="{{tail}}"></pgarena-tournament-app>
<pgarena-account-app title="account" route="{{tail}}"></pgarena-account-app>
<div title="404">
<h1>{{data.page}} could not be found!</h1>
</div>
</iron-pages>
Subpages:
pgarena-account-app
<iron-pages selected="{{data.action}}" attr-for-selected="title" fallback-selection="404">
<pgarena-account-index-view title=""></pgarena-account-index-view>
<pgarena-account-login-view title="login"></pgarena-account-login-view>
<pgarena-account-register-view title="register"></pgarena-account-register-view>
<div title="404">
<h1>Not found :(</h1>
</div>
</iron-pages>
pgarena-tournament-app
<!-- Chooses the new tournament page. -->
<app-route
route="{{route}}"
pattern="/:action"
data="{{data}}"
tail="{{tail}}"
>
</app-route>
<iron-pages selected="{{data.action}}" attr-for-selected="title" fallback-selection="404">
<pgarena-tournament-index-view title=""></pgarena-tournament-index-view>
<!-- The list of all the tournaments -->
<pgarena-tournament-list-view title="list"></pgarena-tournament-list-view>
<div title="404">
<h1>Not Found!</h1>
</div>
</iron-pages>
Everything seems ok, right? According to the URL What I'm doing here is taking advantage of Lazy Load of elements.
I've seen in the Polycasts examples that they use the "hidden" approach. In which they select the element. The problem is that we lose the "Lazy Loading Advantage".
What could be wrong?

OMG! I totally forgot. In Polycasts #46/47 Rob Dodson makes the strong emphasis that when using iron-selector, we should pass one-way binding which is with the square brackets [] versus the curly brackets {}
So at the end of the day it should have been:
<iron-pages selected="[[data.action]]"
Instead of:
<iron-pages selected="{{data.action}}"

Related

Polymer deep app-routing not changing url

I'm having problems with app-route (mainly because I don't understand it).
The specific issue I'm having is changing the url/route. I'm using an iron-selector on an app-drawer to change the iron-page view. The view is switching, but the url is not updating. The other issue is that in one of my views I need to switch to a detail-view (think /events -> /event/:id). I am not sure of the correct way to change a view inside a view.
Enough talk - lets look at some code
The structure of the application is as follows:
/login
/admin
/admin/view1
/admin/view1/:id
/admin/view2
/user
/user/view1
/user/view1/:id
/user/view2
my-app has an iron-pages element that holds the views for login-page, admin-portal, and user-portal. admin-portal and user-portal each have iron-pages that holds view1, view2, etc.
user-portal
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/user/:view"
data="{{routeData}}"
tail="{{subroute}}">
</app-route>
<iron-selector attr-for-selected="name" selected="{{routeData.view}}" fallback-selection="clinics">
<vaadin-item name="clinics">
<iron-icon icon="vaadin:list-select"></iron-icon>
Virtual Clinics
</vaadin-item>
<vaadin-item name="settings">
<iron-icon icon="vaadin:cog-o"></iron-icon>
Settings
</vaadin-item>
<vaadin-item name="help">
<iron-icon icon="vaadin:info-circle-o"></iron-icon>
Help
</vaadin-item>
<vaadin-item name="logout">
<iron-icon icon="vaadin:exit-o"></iron-icon>
Sign Out
</vaadin-item>
</iron-selector>
<iron-pages selected="[[routeData.view]]"
attr-for-selected="name"
selected-attribute="visible"
fallback-selection="view404">
<user-clinic-list-view name="clinics" events="{{events}}" user="{{user}}"></user-clinic-list-view>
<user-clinic-view name="event" route="{{subroute}}"></user-clinic-view>
<user-setting-view name="settings" route="{{subroute}}"></user-setting-view>
<user-help-view name="help" route="{{subroute}}"></user-help-view>
<my-view404 name="view404"></my-view404>
</iron-pages>
user-clinic-list-view
In this view I have an iron-list with a button that needs to take me to user-clinic-view but where I change the route to something like /event/:id
I have tried
I have also tried
window.history.pushState({}, null, '/user/event/:id');
window.dispatchEvent(new CustomEvent('location-changed'));
This seems like a very basic web application, and yet the routing in Polymer is so confusing and there are no examples of deep routing. Every example I've seen is only 1 layer deep (like the starter kit).
What are some best practices for routing?

Adding a new Page to the Polymer Starter Kit

How can you add a new page (say <my-view4>) to the Polymer Starter Kit that covers the whole screen and has a different header than the default view?
<app-location route="{{route}}" url-space-regex="^[[rootPath]]">
</app-location>
<app-route
route="{{route}}"
pattern="[[rootPath]]:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-view1 name="view1"></my-view1>
<my-view2 name="view2"></my-view2>
<my-view3 name="view3"></my-view3>
<my-view404 name="view404"></my-view404>
</iron-pages>
The default iron pages selector is embedded into the app-header-layout element, and all new elements (or views) are displayed inside app-header layout. I would however like to add a new element that covers the whole site and "breaks out" of the iron-pages sandbox inside the app-header-layout.
At the same time the app-route element should still work so that navigating to the new route is possible via the /view4 link. Is this possible with app-route and the PSK?
Sure it is possible, that's why they give that example. I am not sure where exactly is your problem since you did not post any of your own code, this looks like what is provided by the starter kit, but you can try to follow the episode from the Polycasts series that covers the Polymer CLI, and you will see exactly what you're trying to do: https://www.youtube.com/watch?v=pj2lmXVa84U

How to use app-route in polymer 2 to have deep paths?

This is part of polymer application starter kit code. I just added a my-news-list.html to elements in src:
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<app-drawer-layout fullbleed>
<!-- Drawer content -->
<app-drawer id="drawer" slot="drawer">
<app-toolbar>Menu</app-toolbar>
<iron-selector selected="[[page]]" attr-for-selected="name" class="drawer-list" role="navigation">
<a name="news" href="/news">News</a>
<a name="view1" href="/view1">View One</a>
</iron-selector>
</app-drawer>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header slot="header" condenses reveals effects="waterfall">
<app-toolbar>
<paper-icon-button icon="my-icons:menu" drawer-toggle></paper-icon-button>
<div main-title>My App</div>
</app-toolbar>
</app-header>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-news-list name="news" route="{{subroute}}"></my-news-list>
<my-view1 name="view1"></my-view1>
<my-view404 name="view404"></my-view404>
</iron-pages>
</app-header-layout>
</app-drawer-layout>
Everything is OK, <my-news-list> and <my-view1> loads correctly when click on theme in <iron-selector>. in <my-news-list> element I got list of all news with <iron-ajax> and it works fine:
<iron-ajax auto url="localhost/api/news/news" handle-as="json" last-response="{{newsList}}"></iron-ajax>
<template is="dom-repeat" items="{{newsList}}">
[[item.title]]
</template>
I have a other element for viewing single news content, named <my-news-view> that I want to load it when click on title of each news in <my-news-list>. On click path changes to: localhost:8081:/news/2 correctly but <my-news-list> element loads again Instead of <my-news-view>.
I don't paste other elements codes(bindings, ...).
Now I want to know how config <app-route> and use subroute to have load elements in this paths:
// For News
/news //all news list. loads my-news-list element
/news/12 //news[12] view. loads my-news-view elemnt
/news/categories //all categories list view. loads my-news-categories element
/news/categories/3 //category[3] news list view. loads my-news-category element
// and jobs similar to module
/Jobs
/jobs/country/cityName
/jobs/country/cityName/featured
Thanks for your help.
You should declare you "my-news-view" component in "iron-pages" just like you do:
<iron-pages selected="[[page]]" attr-for-selected="name"
fallback-selection="view404" role="main">
<my-news-view name="news-view" route="{{subroute}}" />
</iron-pages>
So iron-pages would know what component to show when user navigate to /news-view/12 based on name attribute not component name itself.
BUT. There are some tricky parts on your solution:
Note how you pass subroute to child components to decouple it internal
routing mechanism from actual url path where it resides.
Starter kit uses lazy-loading to import components and inside that function it adds prefix 'my-' to actual route 'news-view'. So that is how result component name to load become 'my-news-view'. That may confuse.
You could read more about this polymer shop case study.
PS: personally I think that the way the polymers sample app routing works confused a lot because it looks like web-server static name resolution (based on component name, and not components attribute 'name') but it don't.
You should add the component inside the component and then use the sub route to know the id for
Try dna-router. It is now compatible with Polymer 2.0 project.
Please follow this answer - https://stackoverflow.com/a/31236817/5069120
Or visit https://github.com/Saquib764/dna-router

How do I change the route from inside a view in Polymer?

In a simple routing configuration in Polymer, like that obtained from the starter-kit:
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<app-drawer-layout fullbleed>
<!-- Drawer content -->
<app-drawer>
<app-toolbar>Menu</app-toolbar>
<iron-selector selected="[[page]]" attr-for-selected="name" class="drawer-list" role="navigation">
<a name="view1" href="/view1">View One</a>
<a name="view2" href="/view2">View Two</a>
<a name="view3" href="/view3">View Three</a>
</iron-selector>
</app-drawer>
<!-- Main content -->
<app-header-layout has-scrolling-region>
<app-header condenses reveals effects="waterfall">
<app-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div main-title>My App</div>
</app-toolbar>
</app-header>
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-view1 name="view1"></my-view1>
<my-view2 name="view2"></my-view2>
<my-view3 name="view3"></my-view3>
<my-view404 name="view404"></my-view404>
</iron-pages>
</app-header-layout>
</app-drawer-layout>
and a view with a button which if pressed should change the route, like this:
<dom-module id="my-view1">
<template>
<paper-button raised on-tap="changeRoute">Change route</paper-button>
</template>
<script>
Polymer({
is: 'my-view1',
changeRoute() {
// How to change route here?
}
});
</script>
</dom-module>
how can I change the route programmatically, in the event handler above? I tried changing window.location without success (I'm inside Electron, not sure if it's part of the problem).
From the Polymer docs, about changing routes:
Updating the route. The route object is read-write, so you can use
two-way data binding or this.set to update the route. Both the route
and routeData objects can be manipulated this way. For example:
this.set('route.path', '/search/');
Or:
this.set('routeData.user', 'mary');
but this is correct (and works) if done inside the main component (the one that defines the routes), while it doesn't work if called inside a view. Another way to put this question maybe is: how can I access the main router object from an inner component?
There's a few ways to do this, I may have gone overboard but I started to get into it so here goes...
Based on the answer you gave it looks like you just want to "go back". If that's the case you could just use
history.back();
Or if you want to move to a new path then a more robust, including more SEO friendly approach (I believe) is to use something like:
<a href="/newpath" tabindex="-1">
<paper-button>Change Route</paper-button>
</a>
Or the answer by #Carlos works too, but the app-route "philosophy" seems to be more about allowing a decentralized routing approach & so passing an event up to let another component handle the route feels kind of like it's going against the grain. Although this is not necessarily the case since a component can be quite simple, maybe just a button, icon, etc, so you would probably want routing handled by a parent element. In this case though it looks like your component is a full view so I'd lean more towards it being a case where it might handle the routing itself.
I'd also say something similar to the above for #tony19's answer as well.
I think the biggest drawback of the approach in your own answer is that you're digging in the guts of another element so you've tightly coupled to it. That one is definitely not recommended.
Oh & one more way to change the route:
window.history.pushState({}, null, '/new_path');
window.dispatchEvent(new CustomEvent('location-changed'));
This is given here - https://github.com/PolymerElements/app-route#integrating-with-other-routing-code But this is really more for working with other routing code or special situations & would be pretty hacky in your case.
Uhm. Turned out it was not so difficult:
document.querySelector("main-window").set('route.path', "/");
where main-window is the container component that defines the routes (first source file).
Does anyone have a better/more general approach to this problem?
I had the same issue and solved it like by adding the routing-elements to the views as well:
<app-location route="{{route}}"></app-location>
<app-route route="{{route}}" pattern="/app/:view" data="{{routeData}}" tail="{{subRoute}}"></app-route>
<app-route route="{{subRoute}}" pattern="/:id" data="{{idData}}" active="{{onDetailPage}}"></app-route>
then you can change the route from within the element like this:
this.set("route.path", "/app/foo");
Your 'view1' could fire a custom event that would trigger a route change in your app.
This would follow the observer pattern recommended in: Thinking in Polymer (The Polymer Summit 2015)
Note: the answer to this question may also help you.
You can easily do so with the path property of the app-location's element:
changeRoute() {
var $router = this.shadowRoot.querySelector("app-location");
$router.path = "/view1";
}
Here the element's documentation app-location:
Setting the path property will fire up the path-changed event... This will begin the events' cascade to the iron-pages element and switch the view.

Manually Querying Elements In Nested Shadow Dom? API has no recursion? Wasn't like this in PSK1

This is in the context of Chrome where Shadow dom is baked in...
In Polymer Starter Kit 1, there is a great example of being able to globally query a element - app.$.paperDrawerPanel.closeDrawer();. Here is another great example - var middleContainer = Polymer.dom(document).querySelector('#mainToolbar .middle-container');
I assumed it traversed shadow doms recursively. I'm assuming that the polymer elements, such as paperDrawerPanel, were in a shadow dom at some point even though I didn't have to use Polymer.dom().
But in the starter kit template for Polymer Starter kit 2, I am having to manually query each nested shadow dom.
For example I have:
my-app.html
<dom-module id="my-app">
<template>
<app-header-layout fullbleed has-scrolling-region>
<!-- Drawer content -->
<app-header fixed shadow>
<app-toolbar>
<paper-icon-button icon="menu" on-tap="_drawerToggle"></paper-icon-button>
<div main-title class="app-name">Dolphin</div>
</app-toolbar>
</app-header>
<!-- Main content -->
<iron-pages
selected="[[page]]"
attr-for-selected="name"
fallback-selection="view404"
role="main">
<my-view1 name="view1"></my-view1>
and
my-view.html:
<dom-module id="my-view1">
<template>
<app-drawer id="drawer">
<div class='left-bar-container'>
<app-toolbar class="toolbar-drawer">Menu</app-toolbar>
<iron-selector selected="[[page]]" attr-for-selected="name" class="drawer-list" role="navigation">
<a name="view1" href="/view1">Home</a>
<a name="view2" href="/view2">Users</a>
<a name="view3" href="/view3">Contact</a>
</iron-selector>
</div>
</app-drawer>
If I want to query <app-drawer id="drawer"> globally I have to do this crazy routine:
level1 = Polymer.dom(document.querySelector('my-app').root)
level2 = Polymer.dom(level1).querySelector('my-view1').root
Polymer.dom(level2).querySelector('#drawer')
What changed between PSK1 and PSK2 in Polymer where I have to manually query the nested Shadow Doms? It seems the query API used to have recursion. And is there a better to do this if I need to query #drawer globally?
After asking on the Polymer channel, Shady dom pierces the dom tree where the elements reside in the light dom. PSk1 uses shady dom. PSK2 defaults to Shadow dom if available. With that, I have to manually query each nested level.