Activating default route if no other carbon-route active - polymer

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";
}
}
});

Related

Polymer app-route redirect via function issue

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';
}

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).

How can i bind a dynamic function within a polymer component?

As far as my Polymer knowledge goes I can
bind a function using the "on-*" syntax to a webcomponent method
bind a function available in the global window namespace using vanilla html js binding (using onClick="...")
But I want to bind a function (provided as property of datamodel objects) to the webcomponent template.
One sidenote : Moving the datamodel objects to the global javascript namespace (i.e. window.*) is not an option.
The example below does'nt work but reflects exactly my use case :
...
Polymer('x-foo', {
items : [
...,
{
label : "ExampleCommand",
action : function() {
// do something
}
}
...
]
})
...
<template>
<template repeat="{{item in items}}">
<paper-button onClick="{{item.action}}">
{{item.label}});
</paper-button>
</template>
</template>
...
one more question if someone has an idea how to solve the question above) : how can i provide additional arguments to function ?
Any help is appreciated :-)
I had to ask the team about this because it's kinda confusing. Declarative event "bindings" are not the same thing as a Polymer expression. Unfortunately, both event bindings and Polymer expressions use the {{ }} syntax, which implies they work the same. They don't. The scope of event bindings is the element itself, whereas as an expression is scoped to the model for the template instance.
In Polymer 0.8, I believe the syntax has changed, so event bindings no longer use {{ }}. Hopefully that will clear it up a bit.
To achieve the effect you want, you can define a method on the element, which looks at the event target, grabs its model, and calls the function you've defined.
<polymer-element name="x-foo">
<template>
<template repeat="{{items}}">
<button on-click="{{doAction}}">{{label}}</button>
</template>
</template>
<script>
Polymer({
items: [
{
label: 'ExampleCommand',
action: function() {
alert('hello world');
}
},
{
label: 'AnotherCommand',
action: function() {
alert('another command');
}
}
],
doAction: function(e) {
e.target.templateInstance.model.action();
}
});
</script>
</polymer-element>
Here's the example running on jsbin