Polymer 1.0: more-route selectedParams params not set - polymer

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.

Related

JS method not defined

I want to use a java-script method in a polymer Template. I am using Vaadin with Polymer Elements. In my Project I have a Vaadin-Grid of Objects that can be of different type. I want to render these types with different Templates.
This problem can be solved with a dom-if template, as described by ollitietavainen in this answer
This works perfectly, but there is a problem. When using more than two different Types of Objects in the Grid, one would need to use the same amount of booleans to set that up. Suppose we have a fictional shop that displays PC-Parts, and each type of PC-Part needs to be rendered with its own template, then we would need something like the fallowing. This is quite cumbersome.
private boolean isMemory(AbstractPcPart pcPart) {
return pcPart.getClass().equals(Memory.class);
}
private boolean isGraphicsCard(AbstractPcPart pcPart) {
return pcPart.getClass().equals(GraphicsCard.class);
}
private boolean isCPU(AbstractPcPart pcPart) {
return pcPart.getClass().equals(CPU.class);
}
// … is-checker for all other types of pcParts.
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("isMemory", this::isMemory)
.withProperty("isGraphicsCard", this::isGraphicsCard)
.withProperty("isCPU", this::isCPU)
// add all other properties
);
}
The corresponding Templates would look something like this.
<template is='dom-if' if='[[item.isMemory]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[item.isGraphicsCard]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[item.isCPU]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The question now is, if there is any other way, that would not be needing all these Properties.
Luckily there is, as Kuba Šimonovský explained in an answer to another question.
Using this method we could rewrite the code from above to something like the fallowing.
private String type(AbstractPcPart pcPart) {
return pcPart.getClass().getSimpleName();
}
private void initColumn() {
addColumn(Objects.requireNonNull(CardFactory.getTemplate())
.withProperty("partCard", CardFactory::create)
.withProperty("type", this::type));
}
This time we use a java-script method to conditionally select the corresponding template.
<template is='dom-if' if='[[_isEqualTo(item.type, "Memory")]]'>"
<memory-card part-card='[[item.partCard]]'>
</memory-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "GraphicsCard")]]'>"
<graphics-card part-card='[[item.partCard]]'>
</graphics-card-card>"
</template>
<template is='dom-if' if='[[_isEqualTo(item.type, "CPU")]]'>"
<cpu-card part-card='[[item.partCard]]'>
</cpu-card>"
</template>
<!-- one additional template for every type of part -->
The Polymer Template is a bit more complicated now, but on the java side, the code is much shorter, and possibly easier to maintain. There is probably still some overhead, as every template gets added to the dom. But in addition to that only the content from the templates that we want to see gets added to the dom.
I don’t think there is a better way to do this though.
So using this method, we need a java-script method called _isEqualTo. This method is not a standard method so we need to implement it ourselves. The implementation for this method is straightforward.
function _isEqualTo(one, other) {
return one == other;
}
But the answer from Kuba does not specify where to implement this method. I have tried to put the method in different places with no luck. The js console in my browser always complains that it can not find the method.
Digging a little bit deeper I found this Link. So maybe what i want to have is a global variable.
window._isEqualTo = function(one, other) {
return one == other;
}
But even with this change the same warning persists. What’s weird is that the function is visible in the interactive console in the developer tools. Setting a breakpoint in the java-script file that i have added the function; and calling the function in the console reveals that it is really the correct function that get’s called, leading me to beleave that the function gets initialized too late in the lifecycle of the application. Although I am not sure at all.
And because the function is not found, the grid in the view will be empty. It still shows the rows, but they don’t show content.
I really hope someone can help me out.
Here is a Git-Repository to reproduce my problem. The concerning views are the PartsDomIfView and the PartsDomIfElegantView.
Instead of using the deprecated TemplateRenderer, you could create a LitRenderer (v22+) and create a custom lit component that can be used there as your column's content. In there you could create complex logic based templates as a separate component, that can be better maintained.

lazyRegister: max possible with nested elements or iron pages?

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>

How to changeURL via data of app-route element

Is it possible to change the URL via changing the app-route data object in polymer? Like described here: https://www.polymer-project.org/1.0/toolbox/routing#change-routes
In this example they are using this.set('routeData.user', 'mary'); to change the URL.
In our case its not working and we cant find the problem in our code.
We nearly removed all our code and just using this app-router configuration:
<app-route route="{{route}}"
pattern="/:view"
data="{{routeData}}">
</app-route>
In our attached lifecycle event we are using this:
attached: function(){
var self = this;
setTimeout(function(){
self.set('routeData.view', 'GNAAA');
});
}
Expected URL in address bar is
http://localhost:8888/polymer/index.html#/GNAAA
But we only got
http://localhost:8888/polymer/index.html#/
What are we missing here. Why we cant set the URL via the data object as mentioned in the docs? Maybe its a bug? But we cant find something on the GutHub Buglist of app-route.
UPDATE: We are also using iron-location to get query params from URL. If we remove iron-location all works as expected. So we currently created an issue on github.
Try adding <app-location route="{{route}}"></app-location> before your <app-route> statement.
Or, if you prefer to use iron-location, use iron-route-converter:
<iron-location path="{{path}}" query="{{query}}"></iron-location>
<iron-query-params
params-string="{{query}}"
params-object="{{queryParams}}">
</iron-query-params>
<app-route-converter
path="{{path}}"
query-params="{{queryParams}}"
route="{{route}}">
</app-route-converter>
Also, don't forget to import all used components with <link rel="import"> tags or your components will not work, without telling you about it.

How to access Polymer functions from JS

Sorry if this comes out a bit garbled, I'm not sure how to ask this question.
What I am trying to do is keep the DOM synced with a localStorage value, and am updating the localStorage value with an interact.js mouse event.
Currently, I am able to properly set the localStorage value, but am having problems updating the DOM.
My current build is within the Polymer framework, so I am having trouble selecting shadow DOM content.
The DOM tree looks like
PARENT-ELEMENT
# SHADOW ROOT
EL
EL
DIV
CUSTOM ELEMENT
EL
EL
Here are some ways I have failed to solve the problem. The Custom Element is in pure JS, since I am not sure how to properly wrap interact.js function in Polymer:
I tried directly accessing the PARENT-ELEMENT's shadow DOM from the Custom Element in pure JS.
var shadowDOMNode = document.querySelector('PARENT-ELEMENT');
var dom_object_1 = shadowDOMNode.querySelector('#dom_object_1');
dom_object_1.innerHTML = localStorage.dom_object_1;
I tried selecting a helper updateDOM() function from the PARENT Polymer element and running it from the Custom Element's setter directly.
if (event.dy > 0) {
this.$$('PARENT-ELEMENT').updateDOM();
}
Maybe I am taking the wrong approach entirely, but I haven't been able to find analogues for interact.js in using native Polymer functions.
I hope this question was clear enough...
If we ignore the interact.js part of the problem and focus on Polymer, you could probably solve this without coupling the two.
To bind to a localStorage value with Polymer, use the <iron-localstorage> element. In the following example, the localStorage value named flavor_1_amount is loaded and stored into a property named _flavor1Amount. If the value doesn't exist in localStorage or is empty, the <iron-localstorage> element fires an event (iron-localstorage-load-empty), which allows you to bind to a callback (e.g., to initialize it).
<iron-localstorage name="flavor_1_amount"
value="{{_flavor1Amount}}"
use-raw
on-iron-localstorage-load-empty="_initFlavor1Amount">
</iron-localstorage>
In the same element, you could provide an input for the user to update the localStorage value.
<paper-input label="Flavor Amount (mL)" value="{{_flavor1Amount}}"></paper-input>
And you can use <iron-localstorage>.reload() to keep your data binding in sync, assuming it could be changed externally.
See this codepen for a full demo. Check your localStorage from Chrome DevTools:
Generally speaking you should use this.set() or any of the array mutation methods if it's an array in order for the ShadowDOM to be notified properly.
Since you want to perform this update from outside the element itself, imperatively, I'd suggest this:
Expose a couple of methods from your element that you can use to add/remove/change property values from outside your element.
These methods would internally use the proper channels to make the changes.
An example (you can call addItem() to add items from outside your element):
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<dom-module id="x-example">
<template>
<template is="dom-repeat" items="[[data]]">
<div>{{item.name}}</div>
</template>
</template>
<script>
HTMLImports.whenReady(function() {
"use strict";
Polymer({
is: "x-example",
properties: {
data: {
type: Array,
value: [
{name: "One"},
{name: "Two"},
{name: "Three"}
]
}
},
// Exposed publicly, grab the element and use this method
// to add your item
addItem: function(item) {
this.push("data", item);
}
});
});
</script>
</dom-module>
<x-example id="x-example-elem"></x-example>
<script>
setTimeout(function() {
// simply 'grab' the element and use the
// `addItem()` method you exposed publicly
// to add items to it.
document.querySelector("#x-example-elem").addItem({name: "Four"});
}, 2500);
</script>
Important: That being said, this is not the "Polymeric" way of doing stuff as this programming-style is imperative, in constrast with Polymer's style which is more declarative. The most Polymeric solution is to wrap your interact.js functionality in an element itself and use data-binding between your 2 elements to perform the changes.

Confused about the difference between Polymer attribute reflection and two way databinding

I am trying to make a polymer element that authenticates users against an SMF forum. I want the element to expose a "user" property which will initially be an empty object {} but if/when the element has made an ajax request and is able to authenticate the user, this object will contain details about the user (and an authentication token) for use in the rest of the application. The rest of the application is in the content section of the element, and will only be displayed when authetication has happened.
So the application is structured like this
<smf-auth login="login/url", fail="fail/url", splash="initial/splash/img" user>
rest of application which will need access to user
</smf-auth>
I have published user with reflective properties, and set its initial value to {}. However when I run unit tests, I run javascript to get the element (as a javascript variable) and look at el.user and it has the value "" (ie blank string). I tried altering the code to initialise user as something more complex, but I still get a blank string.
Here is the element definition (with my more complex user)
<polymer-element name="smf-auth" attributes="login fail splash">
<template>
<core-ajax id="req" url="{{login}}" handleAs="json" on-core-response="{{handleResponse}}"></core-ajax>
<img id="splash" src="{{splash}}"/>
<template if="{{autheticated}}">
<content></content>
</template>
</template>
<script>
Polymer('smf-auth',{
publish:{
user:{
value:{token:{},uid:0,name:''},
reflect:true
}
},
created:function(){
this.authenticated = false;
},
attached:function(){
this.$.req.go();
},
handleResponse:function(response){
//TODO decide what the response is
}
});
</script>
</polymer-element>
So how should I declare and use the "user" property so that the content of the element (more polymer elements) has access to it
You need to specify a data binding to a user property when you instantiate the smf-auth element. Then you can access this property inside the element's body:
<smf-auth user="{{user}}" ...>
Hello {{user.name}}!
</smf-auth>
This assumes that your smf-auth element is itself inside a Polymer element. Otherwise you need an auto-binding template element.
If your element hierarchy is deep and you need the user object in an element at the bottom, it can be a problem to pass the user property down the hierarchy. In this case another option might be to use core-signals and send an event when the user has logged in. Inner elements can then listen for this event and fetch the user object from the event details.