I am just trying out Polymer 1.0. I find that app-route/iron-pages is not working. Navigating between routes does not appear to show the correct view. Not sure which part went wrong:
In the main file:
<app-drawer-layout>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:view"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<app-drawer>
<main-drawer></main-drawer>
</app-drawer>
<app-header-layout>
<app-header>
<paper-toolbar>
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
<div class="title">
Expenses App
</div>
</paper-toolbar>
<iron-pages selected="[[view]]">
<expenses-dashboard name="dashboard" route="{{subroute}}"></expenses-dashboard>
<expenses-settings name="settings" route="{{subroute}}"></expenses-settings>
</iron-pages>
</app-header>
</app-header-layout>
</app-drawer-layout>
In side both expenses-dashboard and expenses-settings is just placeholder content like:
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="expenses-dashboard">
<template>
<h1>Dashboard</h1>
</template>
<script>
Polymer({
is: 'expenses-dashboard'
});
</script>
</dom-module>
For <iron-pages selected="[[view]]">, should I be using routeData.view or view? I tried both didnt seem to change anything.
The code on GitHub
There are a few issues in your code...
Since your <iron-pages>.selected property is bound to an undefined property ("view"), <iron-pages> does not change its page. In your <app-route> data binding, the route parts are parsed into routeData, where the :view slug would be accessed with routeData.view, which is what you should be binding to <iron-pages>.selected:
<iron-pages selected="[[routeData.view]]">
The default selector for <iron-pages> is the page index (i.e., the child index of its contents), so selected would normally have to be an integer between 0 and N - 1 inclusively, where N is the number of child pages. But you could change that. It looks like you want the route to specify the page, which would need to match the name of a page under <iron-pages>. To use "name" as the selected attribute, you'd have to configure <iron-pages>.attrForSelected property:
<iron-pages selected="foo" attr-for-selected="name">
<div name="foo"></div>
<div name="bar"></div>
</iron-pages>
It might also be a good idea to specify a fallback selection, since the user could accidentally navigate to a URL that doesn't correspond to an existing page (e.g., https://mypage.com/#/non-existent-page).
<iron-pages selected="[[routeData.view]]" attr-for-selected="name" fallback-selection="foo">
<div name="foo"></div>
<div name="bar"></div>
</iron-pages>
In <main-drawer>, you may want to define menuTap() with ES5 syntax (instead of ES6) for maximum browser compatibility.
Polymer({
// menuTap(e) { ... } // <-- ES6
menuTap: function(e) { ... }
});
Your menuTap() function sets the window.location to a raw path, which noticeably refreshes the page. You could avoid the page refresh by using hash paths, where the intended sub-path of the URL is prefixed with a # (e.g., https://mypage.com/#/settings).
For hash paths, configure <app-location> to ignore the hash prefix by setting the useHashAsPath property:
<app-location use-hash-as-path route="{{route}}">
If you prefer to avoid hash paths, you could follow Polymer CLI's app-drawer-template, which uses anchor tags inside an <iron-selector> to set the location, which <app-location> detects and updates its route accordingly. Or you could pass the route in from <expenses-app> and then use this.set('route.path', "dashboard") inside of menuTap().
With the changes above, the following would occur when the user navigates to https://mypage.com/#/dashboard.
<app-location> would set the route property to /dashboard.
<app-route> would parse the route and set routeData.view to dashboard.
<iron-pages> sees routeData.view as dashboard, which matches the specified attribute on a child, which in turn causes only that page to be displayed.
For reference, the guide on Encapsulated Routing with Elements is quite useful.
Related
At first, i know, there are many questions about iron-list. But mostly about editing items and not whole template inside iron-list..
My code is really extremely complicated and posting it is pointless. I am working on data-tables which are using iron-list. I have element called diamond-listing and inside this diamond-listing i have iron-list.
You can image this like: Parent element define <template> with some content inside it, and child element (diamond-listing) will render this template as a table
Of course diamond-listing is used multiple times in my application and always with different template. For example: page users have columns with userID, userName etc.. and on page stations there are columns stationID, address etc.. with different number of columns. Every pagea has it's own <template> which i am trying to propagate to diamond-listing. For example:
<diamond-listing as="user" id="permissionsTable" type="pagination" pagination-items-per-page="6" header-data="{{headerData}}" address="/user/" loading="{{loading}}">
<div id="test" slot="content">
<template>
<div class="diamond-row" on-tap="_openUrl" info$="/user/[[user.id]]">
<diamond-item text="{{user.username}}"></diamond-item>
<diamond-item text="{{user.partner.name}}"></diamond-item>
</div>
</template>
</div>
</diamond-listing>
What i managed to do is to make it work in shadow dom using <slot> and simply rewrite <template> inside <iron-list>, but here we are.. For example using Firefox, which doesn't support webcomponents, there isn't <template> as a child of <iron-list> (because there is no shadow-dom) so there is no way how to update <template> and render iron-list.
What i tried:
1) Find template inside iron-list and use removeChild and appendChild functions.
var test = this.querySelector("#test template");
this.$$("#diamondList").removeChild(this.$$("#diamondList template"));
this.$$("#diamondList").appendChild(test);
Without success.
2) Define in HTML empty iron-list without any template inside it. And then in javascript add template dynamically. Without success. ( iron-list is crying it requires template)
3) Create dynamically iron-list using document.createElement
var test = this.querySelector("#test template");
var list = document.createElement("iron-list");
list.appendChild(test);
list.as = this.as;
list.items = [{"username":"test","partner":{"name":"Test partner","id":1}}];
list.id = "diamondList";
result: same as 2) ...
Is there a way, how to update template which is used to render all items in iron-list?
Or create iron-list with defined template inside JS ?
Or somehow do it with dom-repeat ? I won't have more than 10 items in listing, since it's fully pagination listing. ( this is propably simplest solution, but i don't know how to render <template> for every iteration
Here is one general answer, don't know if it will work for your case:
In Polymer, recommended way of manipulating the DOM is by manipulating the data, not by removeChild or appendChild.
For example,
if you have list of users as: var users_array = [....];
create the iron-list as:
<iron-list date="users_array">
<template>
...
<template>
</iron-list>
adding and removing elements in users_array will affect the iron-list
immediately.
Use a dom-if or use hidden inside the iron-list.
<iron-list items="[[items]]">
<template>
<template is="dom-if" if="[[item.isType1]]">
<!-- item1 -->
</template>
<template is="dom-if" if="[[item.isType2]]">
<!-- item2 -->
</template>
</template>
</iron-list>
Polymer 1.0
Is it possible to lazyRegister: max with:
1) nesting elements in parentmy-app element?
2) nesting elements in iron-pages?
I have a console.log statement in element single-listing that fires when attached is ran... which does it right away when the app loads. So, lazyRegister is not working for me.
<script>
// Setup Polymer options
window.Polymer = {
dom: 'shadow',
lazyRegister: 'max'
};
...
<my-app></my-app>
my-app.html:
<!-- Main content -->
<iron-pages attr-for-selected="data-route" selected="{{route}}">
<user-login data-route="user-login"></user-login>
<my-view1 data-route="my-view1" form-loading="{{isLoading}}"
listings="[[listings]]" tabindex="-1"></my-view1>
<single-listing data-route="single-listing"></single-listing>
<my-view3 data-route="my-view3"></my-view3>
</iron-pages>
single-listing.html:
attached: function() {
this.async(()=> {
console.log('foo') })
}
Is it possible to lazyRegister: max with nesting elements [...]?
Yes, this is possible, as can be seen in this Plunker.
Plunker note: There's a delay in Plunker before registering x-el, so wait a few seconds after "Hello world" appears before checking the console, which should display x-el attached:
lazyRegister is not working for me
I think you might be misunderstanding the purpose of that flag, and when registration occurs. The flag defers element registration until the element is created. You can create an element declaratively (i.e., by writing "<lazy-element>" in HTML) or imperatively (i.e., document.create('lazy-element'); in JavaScript).
In your example, you've declared the element, which effectively creates it, so the registration occurs when the host of the element is created. In the following example snippet, <lazy-element> is created when <my-app> is created, which is when <my-app>'s definition is imported.
<!-- my-app.html -->
<my-app>
<iron-pages>
<lazy-element></lazy-element>
</iron-pages>
</my-app>
If you want to defer element creation until the page is selected, you could lazy-load the element's import using one of the following methods (and remove lazyRegister flag, as it would be redundant):
this.importHref('lazy-element.html') (See how Polymer Starter Kit does it)
<iron-lazy-pages>
<lazy-imports>
Given this simplified component:
<dom-module id="poly-component">
<template>
<paper-button raised onclick="dialog.open()">Button</paper-button>
<paper-dialog id="dialog"><h1>Paper Dialog Here!</h1></paper-dialog>
</template>
<script>
Polymer({
is: 'poly-component'
})
</script>
which does nothing more than open the dialog on clicking the button.
This module works when it used once on a page.
But when it is inserted twice
[...]
<dom-module id="polyTest-app">
<template>
<h2>Hello [[prop1]]</h2>
<poly-component></poly-component>
<poly-component></poly-component>
</template>
[...]
it doesn't work anymore.
A click on the button leads to a:
(index):1 Uncaught TypeError: dialog.open is not a function
Am I missing something?
The code for this example can be found here: Example Code on GitHub
Your code cannot work because you're not binding the event handler correctly.
A built-in handler like onclick tries to execute the bit of code in global scope where dialog doesn't exist. Hence the error.
Here's how you can rewrite your code
<dom-module id="poly-component">
<template>
<paper-button raised on-click="_dialogOpen">Button</paper-button>
<paper-dialog id="dialog"><h1>Paper Dialog Here!</h1></paper-dialog>
</template>
<script>
Polymer({
is: 'poly-component',
_dialogOpen: function() {
this.$.dialog.open();
}
})
</script>
</dom-module>
First, notice how onclick changes to on-click - the Polymer event handling notation. PS. tap event is advised.
Second, you can only access other elements from code. Not directly in bindings. Hence the _dialogOpen function.
UPDATE
Ok, I know what's happening. When there is only one element with a given id, the browsers let's you use it simply by that id in global scope.
When you use Shady DOM, which I assume you do, two instances of your poly-component element render two <dialog id="dialog">. At this point window.dialog is not available anymore.
Again, with Polymer it's safer to use the this.$ notation aka Automatic node finding to reference elements in local DOM.
Displaying the menu only to members
I've got this bit of code
<app-drawer-layout fullbleed>
<!-- Drawer content -->
<template is="dom-if" if="{{signedIn}}">
<app-drawer>
....
This displays the menu only when users are logged in to the application. It works fine but is there anyway to remove the error it causes in the console.
error:
polymer-mini.html:2046 Uncaught TypeError: Cannot read property 'getWidth' of undefined
The layout logic in <app-drawer-layout> requires an <app-drawer> to determine the appropriate container margins. I don't see an option to disable that logic.
A workaround for the error you're seeing is to create an empty <app-drawer> by moving the dom-if inside the <app-drawer>:
<app-drawer>
<template is="dom-if" if="{{signedIn}}">
...
</template>
</app-drawer>
This unfortunately would create a blank drawer before the user signs in, but maybe this is acceptable for your app. codepen
I am wondering if selectedParams is supported for Polymer 1.0.
I am following the doc of more-route
https://github.com/PolymerLabs/more-routing
in the README file.
<link rel="import" href="../bower_components/more-routing/more-routing.html">
<more-routing-config driver="hash"></more-routing-config>
<more-route name="login" path="/">
</more-route>
<more-route name="inbox" path="/inbox">
<more-route name="viewemail" path="/:threadId">
</more-route>
</more-route>
<more-route-selector selectedParams="{{params}}" on-click="bodyClick">
<iron-pages selected="{{route}}" class="fullbleed fit">
<section route="login" >
</section>
<section route="viewemail" class="layout vertical fit">
Test params : <span>{{params}}</span> End
Test params : <span>{{params.threadId}}</span> End
<span>{{route}}</span>
</section>
</iron-pages>
</more-route-selector>
The problem is that params is not set, so I cannot reference the threadId param in the path.
The routing it working which means for /inbox/testid is routed to section with route="viewemail", but params is not set.
selectedParams should be written as selected-params. From 1.0 docs:
Attribute names with dashes are converted to camelCase property names by capitalizing the character following each dash, then removing the dashes. For example, the attribute first-name maps to firstName.
So your more-route-selector should be declared as follows:
<more-route-selector selected-params="{{params}}" on-click="bodyClick">
<more-route name="viewemail" path="/:threadId">
above in "routes.html" binds name to the threadId you want a ref to ....
When you use those in template, get a context...
<more-route context name="viewemail" params="{{params}}"></more-route>
... so you can make use of those properties within the Polymer class...
ready: function() {
if (typeof this.params.viewemail != 'undefined'){
this._mvar = this.params.viewemail;
this.$.get_divID = _mvar;
if(MoreRouting.getRoute('viewemail').active){...}
}
},
As you already pointed out in this Github issue, the behavior is a bug of the Polymer 1.0 migration of more-routing (call it missing feature, as there is simply no instruction that would set the selectedParams).
As long as no fix exists, you have two options to circumvent the problem manually without modifying the project source code:
1. Workaround: Listen on on-more-route-change on the more-route-selector element.
The current routing params can be accessed as event.detail.newRoute.params from the event handler (caution: that object is not serializable due to non-enumerable JS getters). Manually hand over these params to your elements.
2. Good solution: Use context-aware routes.
selectedParams are not set on the more-route-selector, but well on routes. Include a context route to your pages and bind {{params}} on them.
Therefore, you may create a named route in your router file as:
<more-route path='/path/:param' name='path-route'/>
Reference this route by name from within your page element (or state the path directly):
<more-route context name='path-route' params='{{params}}'/>
Place the page element in more-router-selector as before. Parameters are bound to {{params}}.
The pattern is explained on the project page and in use in the demo app.