Polymer app-route redirect via function issue - polymer

just when I thought I figured out how app-route is working, I run into an issue that let me doubt that my understanding of this element is correct.
From what I understood, it's the responsibility of app-location to keep the browser URL and the value of route in sync. If one of them changes app-location takes care that the other one is changing as well.
And because the route attribute of app-route, is in sync with it's data attribute, changes of the data attribute by the paper-tabs in the code below causes a change in the route attribute of app-route causes the app-location to update the browser URL.
However since I didn't use the fallback-selection attribute in paper-tabs the surfing to http://localhost will set the path to '/' and therefore not showing the home-page.
So I thought I could redirect the URL with the code in the ready function. But unfortunately the route.path indeed changes but the URL doesn't.
Why is that? What do I have to do to redirect the route manually via a function?
Or in other words: Why does a change of routeData.subpage via the paper-tabs element causes a redirect, and a change of routeData.subpage from the function not?
<dom-module id="polymer-app">
<template>
<style>
:host {
display: block;
}
</style>
<app-location route="{{route}}"></app-location>
<app-route id="ar"
route="{{route}}"
pattern="/:subpage"
data="{{routeData}}"
tail="{{routeTail}}"
active="{{routeActive}}">
</app-route>
<header>
<paper-tabs attr-for-selected="name" selected="{{routeData.subpage}}">
<paper-tab name="home">Home</paper-tab>
<paper-tab name="settings">Settings</paper-tab>
</paper-tabs>
</header>
<section id="main">
<iron-pages attr-for-selected="name" selected="[[routeData.subpage]]">
<home-page name="home"></home-page>
<settings-page name="settings"></settings-pagina>
</iron-pages>
</scection>
</template>
<script>
Polymer({
is: 'polymer-app',
properties: {
route: Object,
routeData: Object,
routeTail: Object,
routeActive: Boolean,
},
ready: function() {
if (this.route.path == "/") {
this.set('routeData.subpage', 'home');
console.log(this.routeData);
console.log(this.route);
}
},
});
</script>
</dom-module>

Add a page property with an observer that will either assign it the current value (based on the URL) or a default value if the URL path is empty
static get properties() {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged',
},
}
static get observers() {
return [
'_routePageChanged(routeData.page)',
];
}
_routePageChanged(page) {
// If no page was found in the route data, page will be an empty string.
// Default to 'home' in that case.
this.routeData.subpage = page || 'home';
}

Related

Reading and displaying json data using Polymer

I am new to polymer and I am trying to read JSON data in a custom-element and display it in other element.
This is my JSON data:
jsonData.json
[
{
"name":"Ladies+Chrome+T-Shirt",
"title":"Ladies Chrome T-Shirt"
},
{
"name":"Ladies+Google+New+York+T-Shirt",
"title":"Ladies Google New York T-Shirt"
}
]
This is my shop-app.html file where I try to read data from JSON file (I am not sure if this is correct or not as I am not able to test it):
<dom-module id="shop-category-data">
<script>
(function(){
class ShopCategoryData extends Polymer.Element {
static get is() { return 'shop-category-data'; }
static get properties() { return {
data: {
type: Object,
computed: '_computeData()',
notify: true
}
}}
_computeData() {
this._getResource( {
url: 'data/jsonData.json',
onLoad(e){
this.set('data.items', JSON.parse(e.target.responseText));
}
})
}
_getResource(rq) {
let xhr = new XMLHttpRequest();
xhr.addEventListener('load', rq.onLoad.bind(this));
xhr.open('GET', rq.url);xhr.send();
}
}
customElements.define(ShopCategoryData.is, ShopCategoryData);
})();
</script>
</dom-module>
This is the element where I want to display the data I read from the JSON file:
<dom-module id="shop-app">
<template>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}">
</app-route>
<shop-category-data data="{{data}}"></shop-category-data>
<template>
<div> Employee list: </div>
<template is="dom-repeat" items="{{data}}">
<div>First name: <span>{{item.name}}</span></div>
<div>Last name: <span>{{item.title}}</span></div>
</template>
</template>
</template>
<script>
class ShopApp extends Polymer.Element {
static get is() { return 'shop-app'; }
}
customElements.define(ShopApp.is, ShopApp);
</script>
</dom-module>
The line <shop-category-data data="{{data}}"></shop-category-data> should give me the data, which I then try to display using dom-repeat. But nothing is being displayed. So, I think there is some mistake in my reading the JSON data.
Edit:
The JSON is read correctly, it is just not getting reflected back in my:
<shop-category-data data="{{data}}"></shop-category-data>
Computed properties is not returning a value. If you want to define data as a computed property you must return a value from the computed property function _computeData(). But in your case you are using asynchronous XMLHttpRequest. So, if you return a value after calling this._getResource... you need to make it synchronous (which no one recommends).
Plnkr for synchronous method: http://plnkr.co/edit/jdSRMR?p=preview
Another way is calling the method inside ready(). This is asynchronous.
Plnkr: http://plnkr.co/edit/pj4dgl?p=preview
It's not getting reflected back because the json is assigned to data.items, rather than to data itself.
this.set('data', JSON.parse(e.target.responseText));
It's recommended to use <iron-ajax>, and scrap <shop-category-data>. e.g. replace the following line
<shop-category-data data="{{data}}"></shop-category-data>
with
<iron-ajax auto url="data/jsonData.json" handle-as="json"
last-response="{{data}}"></iron-ajax>

href to another element does not work in Polymer

I created a project using the Polymer Starter Kit, with its app-router.
My my-app.html has the selectors for the views like so..
<iron-pages role="main" selected="[[page]]" attr-for-selected="name">
<my-view1 name="view1"></my-view1>
<my-view2 name="view2"></my-view2>
<my-view3 name="view3"></my-view3>
<my-view4 name="view4"></my-view4>
<my-view5 name="view5"></my-view5>
<my-view6 name="view6"></my-view6>
</iron-pages>
I have a component my-view1.html, within which I have div with href tag
<div>God 1 Jump to example </div>
elsewhere in the document I have
<p id="gotogod">
God is great
</p>
While I dont get any errors, it doesnt jump to the paragraph when I click the link. When I hover over the link, I do get
http://localhost:8080/view1#gotogod
But the page doesnt jump to that spot.
I have tried various combinations for the href without luck.
Please help.
HTML Element:
<a on-click="scrollIntoView" section-id="gotogod">
Javascript Function:
scrollIntoView(e) {
let sectionId = e.target.getAttribute('section-id');
this.$[sectionId].scrollIntoView();
}
Ok, you need to be alert about the app-route, when you're going to change the route, you should change it by the app-route.
I do it (kind of) this way:
<app-location route="{{ route }}"></app-location>
<app-route route="{{ route }}"
pattern="/:page"
data="{{ routeData }}"
tail="{{ subroute }}"></app-route>
<iron-selector attr-for-selected="route"
selected="[[ page ]]"
role="navigation">
<a route="editor" href="/editor">Editor</a>
<a route="analyze" href="/analyze">Analyze</a>
<a route="community" href="/community">Community</a>
</iron-selector>
<iron-pages role="main"
attr-for-selected="route"
selected="[[ page ]]">
<my-editor route="editor"></my-editor>
<my-analyze route="analyze"></my-analyze>
<my-community route="community"></my-community>
</iron-pages>
<script>
Polymer({
is:'my-element',
properties: {
page: {
type: String,
notify: true,
reflectToAttribute: true,
observer: "_pageChanged"
}
},
observers: [
"_routePageChanged(routeData.page)"
],
listeners: {
"requestChangeRoute": "_changeRoute"
},
attached: function(e) {
// Lazyload the views as soon as the AppShell has been Painted
this.importHref(
this.resolveUrl("my-editor.html"), null, null, true);
this.importHref(
this.resolveUrl("my-analyze"), null, null, true);
this.importHref(
this.resolveUrl("my-community"), null, null, true);
// If the application is reloaded, redirect to /analyze
if(this.page != "analyze"){
this.set("route.path", "/analyze");
}
},
_changeRoute: function(e) {
this.set("route.path", e.detail.requestRoute);
},
_routePageChanged: function(page) {
this.page = page || "analyze";
}
})
</script>
On the main page, where the route is defined, you can use the href with route to navigate, but when you're inside some of your page elements, where there isn't the app-route defined, you should ask for you main page to change the route by the app-route using the function _changeRoute without href, this call is made like this:
this.fire("requestChangeRoute", {
route: "/editor"
});
This way you have sure that the app-route is taking care of the navigation and that every page is going to work, and you can even set validations or parameters to be passed by this function.
It seems that this answer is what you look for :
Polymer scroll to a specific div
You can do :
<a href="#gotogod" class="link" on-click="_goToGoScroll">
And in the code of the Polymer element :
_goToGoScrool() {
this.$.gotogod.scrollIntoView();
}

Polymer deactivating pages when their not in view

I'm using the polymer application drawer template from the polymer cli.
I'm having some trouble with:
When you load a new page, the html element is imported; then it's code executes
When I move to another page the code for the previous page is still running.
Is there a way to destroy and create the page/element or suspend and enable?
Whats the best practice for dealing with this problem?
Have the pages implement a create and destroy method and invoke it when changing page?
Ie
oldPageElement.destroy();
newPageElement.create();
Polymer({
is: 'my-random-page',
behaviors: [MyBehaviors.CommonPageBehavior],
/**
* #override
*/
create: function() {..}
/**
* #override
*/
destroy: function() {..}
})
You actually don't need to implement anything complicated, but just use a mere dom-if.
Working prototype: http://jsbin.com/gezihatera/edit?html,console,output
As you can see, the "View One" uses a custom page element, which is always restamped when re-selected. Other pages are ordinary div elements, since this is only a minimal prototype. But this also shows that you can selectively choose which pages get restamped and which do not (if you don't always need this).
The essence is the following: as per dom-if documentation, if you set the restamp attribute to true, then the dom-if will always create and destroy your pages upon selecting/deselecting them. You can see this in the console, where I print out sample-page ready on every ready element. I also create a helper function _equals to help with comparing whether the specified page is really selected.
To sum up, let me paste the code for the app:
<dom-module id="sample-app">
<template>
<style>
:host {
display: block;
}
</style>
<iron-selector selected="{{page}}" attr-for-selected="name">
<a name="view1" href="#">View One</a>
<a name="view2" href="#">View Two</a>
<a name="view3" href="#">View Three</a>
</iron-selector>
<iron-pages role="main" selected="[[page]]" attr-for-selected="name">
<template is="dom-if" if="[[_equals(page, 'view1')]]" restamp="true">
<sample-page name="view1">view1</sample-page>
</template>
<div name="view2">view2</div>
<div name="view3">view3</div>
</iron-pages>
</template>
<script>
Polymer({
is: 'sample-app',
_equals: function(a, b) {
return a == b;
},
});
</script>
</dom-module>
And the code for the sample page:
<dom-module id="sample-page">
<template>
<style>
:host {
display: block;
}
</style>
<content></content>
</template>
<script>
Polymer({
is: 'sample-page',
ready: function() {
console.log('sample-page ready');
},
});
</script>
</dom-module>
Hope this satisfies your question.
Note: you should not put the name attribute on the dom-if itself, but rather onto its content (the same way I did).
Thought I would post my solution after implementing #alesc's dom-if to get the element to be deactivated.
// after a successful importHref, _pageLoaded is called.
_pageLoaded: function(pageName) {
var name = 'my-' + pageName;
this.async(function() {
// async to wait for element restamping, if done
var pages = this.$.pages;
var page = pages.querySelector(name);
page.load()
.then(page.isAuthorized.bind(this))
.catch(this._catchPageIsAuthorizedError.bind(this))
.then(this._shouldSetPage.bind(this, pageName));
}.bind(this));
}

How to properly bind iron-selector to iron-pages?

I am trying to do the same basic task as this other question, iron-selector selected={{}} binding with iron-pages selected={{}}, but I believe my case must be different, since I cannot replicate the solution.
I want to have a menu of buttons with iron-selector, and when clicked, the content with iron-pagesshould change.
So I have the actual page about.htm that has 1 webcomponent for the button-menu, called about-buttons.htm and then 1 webcomponent for each page, that should load according to which button is pushed by the user; about-who.htm, about-manifesto and about-team.
My question is:
How can I with this structure of my web components bind my buttons with my pages - and / or why is my current method wrong?
There's a lot of different ways to do a simple data binding like this. My method here is definitely not simple, and it does not work (by clicking buttons, the pages does not change).
So my about.htm looks like this (and this is the page people will visit):
<about-buttons selected="{{who}}">
</about-buttons>
<about-iron-pages attr-for-selected="name" selected="{{who}}" fallback-selection="who">
<about-us name="who">
</about-us>
<about-manifesto name="manifesto">
</about-manifesto>
<about-team name="team">
</about-team>
</about-iron-pages>
My about-buttons.htm looks like this:
<iron-selector
attr-for-selected="name"
selected="{{buttonSelected}}"
fallback-selection="who"
class="f-column f-column_3 f-column_mobile_2">
<button class="f-button-group__button" name="manifesto">Manifesto</button>
<button class="f-button-group__button" name="who">Who we are</button>
<button class="f-button-group__button" name="team">Team</button>
</iron-selector>
With this script:
<script>
Polymer({
is: 'about-buttons',
properties: {
buttonSelected: {
type: String,
notify: true,
value: 'who'
}
}
});
</script>
And here's my version of iron-pages:
<dom-module id="about-iron-pages">
<template>
<style>
:host {
display: block;
}
:host > ::content > :not(.iron-selected) {
display: none !important;
}
</style>
<content>
</content>
</template>
<script>
Polymer({
is: 'about-iron-pages',
behaviors: [
Polymer.IronResizableBehavior,
Polymer.IronSelectableBehavior
],
properties: {
activateEvent: {
type: String,
value: null,
}
},
observers: [
'_selectedPageChanged(selected)'
],
_selectedPageChanged: function(selected, old) {
this.async(this.notifyResize);
}
});
</script>
</dom-module>
As already pointed out, your attribute in <about-buttons selected="..."> does not match the actual property name. For <about-buttons>.buttonSelected, your attribute should be button-selected, and that's the only change to your code needed to get the selector working (plunker):
<about-buttons button-selected="{{who}}" ...>
Perhaps there's more context to your need for <about-buttons> and <about-iron-pages>, but otherwise, if you're only trying to implement a tabbed view, you could just use Polymer's components.
Specifically, <about-buttons> could be replaced by <paper-tabs> and <about-iron-pages> by <iron-pages> (plunker).

Activating default route if no other carbon-route active

Since Polymer 1.4 carbon-route is available, which can be used for navigation:
<carbon-location
route="{{route}}"
use-hash-as-path>
</carbon-location>
<carbon-route
route="{{route}}"
pattern="/tabs/:tabName"
data="{{data}}">
</carbon-route>
However, if none of the routes match when the page initially loads, then the URL is not updated when the route changes.
Is there a way to select a default route when no other route matches?
For example, in excess-route, this could be done via:
<excess-route
route="/(.*)"
redirect-to="/default"
activation-modifiers="x">
</excess-route>
It looks like there's an open issue for this, is there a good work-around?
https://github.com/PolymerElements/carbon-route/issues/68
Try using the new fallback-selection attribute on iron-selectables like iron-pages or paper-tabs: https://github.com/PolymerElements/iron-selector/blob/master/iron-selectable.html#L121
At time of writing this property is very new and isn't yet up on the elements guide. You may also need to do a bower update locally as well to get the new code.
Answering own question, the following can be used as a work-around:
...
<carbon-location
route="{{route}}"
use-hash-as-path>
</carbon-location>
<carbon-route
route="{{route}}"
pattern="/tabs/:tabName"
data="{{data}}">
</carbon-route>
<paper-tabs
selected="{{data.tabName}}"
attr-for-selected="key">
<paper-tab key="foo">Foo</paper-tab>
<paper-tab key="bar">Bar</paper-tab>
</paper-tabs>
...
Polymer({
...
properties: {
data: {
type: Object,
observer: "_onData"
}
},
_onData: function (newValue, oldValue) {
if (! newValue.tabName) {
window.location = ("" + window.location).replace(/\#.*$/, "") + "#/tabs/foo";
}
}
});