Polymer: register a behaviour at runtime - polymer

I need to setup the behaviour of a polymer web-compontent at runtime. I tried to change the "behaviours" array by pushing the new behaviour, but it didn't work. Is there a proper way to do it?
I'm trying to create a table web-component with a pager at bottom. It should be extensible allowing the loading of data from a javascript array, a restful service or a custom source. Thus, I decided to create a behaviour for each one of these source and change it when the source changes. Is it a correct way to design it?
Here as example the source code of the behaviour to load data from an array. It has the following function:
itemsLoad: function(page, itemsPerPage, callback) {...
which is called from the web-component to load data of a specific page. My idea is that each behaviour based on the type of data source (e.g. CSV, JSON, etc.) will implement this method in a different way. Then, the behaviour will be registered at run-time, because is at run-time that the developers knows which is the source to use.

I don't think you will be able to change behaviours at run-time, because they are mixed into the element prototype.
What you can do is create a separate element for each of your cases (csv, json, etc) and create nodes dynamically as required. You could than place that element inside your grid
<table-component>
<json-data-source></json-data-source>
</table-component>
The <table-component> would look for a child element which implements itemsLoad to get the data.
EDIT
To work with child nodes you would use Polymer's DOM API. For example you could listen to added child nodes and select one that implements the itemsLoad method.
Polymer({
attached: function() {
Polymer.dom(this).observeNodes(function(info) {
var newNodes = info.addedNodes;
for(var i=0; i<newNodes.length; i++) {
var dataSource = newNodes[i];
if(dataSource.itemsLoad && typeof dataSource.itemsLoad === 'function') {
this.loadItems(dataSource);
break;
}
}
});
}
loadItems: function(dataSource) {
dataSource.itemsLoad().then(...);
}
});
You could replace Polymer.dom(this).observeNodes with simply iteration over Polymer.dom(this).children. Whichever works best for you.

Related

How should I access generated children of a custom HTML component in an idiomatic React way?

I am attempting to create a search bar using a custom HTML component for predictive text input. The way this component is built, it generates several plain HTML children that I need to act on to get full features. Specifically, I need to execute a blur action on one of the generated elements when the user presses escape or enter.
I got it to work using a ref on the custom component and calling getElementsByClassName on the ref, but using getElementsByClassName does not seem like the best solution. It pierces through the virtual and has odd side effects when testing.
This is a snippet of the component being rendered:
<predictive-input id='header-search-bar-input' type='search'
value={this.state.keywords}
ref={(ref: any) => this.predictiveInput = ref}
onKeyDown={(e: React.KeyboardEvent<any>) => this.handleKeyDown(e)}>
</predictive-input>
and the keyDown handler:
private handleKeyDown(e: React.KeyboardEvent<any>) {
// must access the underlying input element of the kat-predictive-input
let input: HTMLElement = this.predictiveInput.getElementsByClassName('header-row-text value')[0] as HTMLElement;
if (e.key === 'Escape') {
// blur the predictive input when the user presses escape
input.blur();
} else if (e.key === 'Enter') {
// commit the search when user presses enter
input.blur();
// handles action of making actual search, using search bar contents
this.commitSearch();
}
}
The element renders two children, one for the bar itself and one for the predictive dropdown. The classes of the underlying in the first are 'header-row-text' and 'value', so the element is correctly selected, but I am worried that this is violating proper React style.
I am using React 16.2, so only callback refs are available. I would rather avoid upgrading, but if a 16.3+ solution is compelling enough, I could consider it.
If you don't have any control over the input then this is the best approach in my opinion.
It's not ideal, but as you're stuck with a 3rd party component you can only choose from the methods that are available to you. In this case, your only real options are to find the element based on its class, or its position in the hierarchy. Both might change if the package is updated, but if I had to choose which would be more stable, I'd go for className.

Automatically change the type of the elements in an array

I wrote a class for my project like this using typescript and react.
class myImage extends Image {
oriHeight: number;
}
After I uploaded two images I have an array named 'results' which is full of objects with type myImage.
[myImage, myImage]
When I click it in browser, I could see the data of oriHeight of each element.
Then I try to use results.map() method to traverse all the elements in that array.
results.map((result: myImage) => {
console.log(result);
var tmp = result.oriHeight;
console.log(tmp);
})
However, the output of result is no longer an object but an img tag (because the type of Image is a HTMLElement) which makes the data of result unreadable. So the output of every tmp is undefined.
I am confused about that. Why the myImage object will become an img tag when I want to traverse it? I hope someone could help me with that. Really appreciate it.
I bet your data is actually fine. When you console log an html element, the chrome console displays it as an html tag instead of the javascript object.
Update: It's generally a bad practice to add your own properties to DOM elements because they're harder to debug and you risk them being overwritten by future browser properties. Instead, you could create a javascript object that contains both the image and your custom property. Here's an example interface definition:
interface MyImage {
imageEl: HTMLImageElement;
oriHeight: number;
}

How to check the (initial) render state (not the update state) of a component in shadow DOM

Following the question I would like to ask about the appropriate way to check the initial render status of a component (not the update status) in shadow DOM. Is there any similar to document.readyState or a promise?
I have also tried to do:
getItems() {
this.updateComplete
.then(() => {
this.nodesLists = this.shadowRoot.querySelectorAll(".name");
})
.then(...)
}
which also failed.
Tia
await this.updateComplete (or this.updateComplete.then(...)) is the correct way to wait until the element has no pending render work before e.g. querying the state of the element's rendering, so your code should generally work as long as the element is connected to the document before running getItems.
Example: https://jsbin.com/jiquhez/edit?html,console,output
Note however, that if you await updateComplete before the element is connected and the element has no properties set that would trigger a render, then updateComplete currently resolves before the first render. This may be considered an unintended bug, filed at lit-element/#594.
Note you may also want to look into using the firstUpdated lifecycle method, depending on your use case. This is a method you can implement on your class to perform one-time work following the first update/render cycle for the element (useful for e.g. selecting static nodes that won't change based on rendering).
Example: https://jsbin.com/limikas/edit?html,console,output

Polymer - cloneNode including __data

I am using the library dragula for doing some drag & drop stuff.
Dragula internally uses cloneNode(true) to create a copy of the dragged element that will be appended to the body to show the preview image while dragging.
Unfortunately, if dragging a polymer element, the bound data get's not cloned. By consequence the contents of the dragged element (e.g. <div>[[someString]]</div>) are empty.
Is there a solution for this?
I actually do not need the data to be bound for my element, it is just a "read-only" element that displays some data that does not change after being initialized. Is there maybe a way to somehow "resolve" the strings to the html without being bound anymore?
Thank you already!
Found a solution myself. You have to override the cloneNode method inside the polymer class:
cloneNode(deep) {
let cloned = super.cloneNode(deep);
for (let prop in MyClass.properties) {
cloned[prop] = this[prop];
}
return cloned;
}

Templatizer - How to render multiple times same template, Polymer 2.x

In Polymer 1.x I was used to write a templatize code like this:
renderTemplate(query, properties) {
let element = this.shadowRoot.querySelector(query);
this.templatize(element);
var instance = this.stamp(properties);
return instance;
}
which worked well. But in Polymer 2.x there is a new error message A <template> can only be templatized once. Well it doesn't make sense, because I have 1 template which I want to redistribute multiple times with different properties.
I am giving here an example of how is my code
I have #template1 and #template2
I want to render #template1 then #template2 then #template1.
In steps how I render templates:
1) templatize #template1
2) stamp properties
3) templatize #template2
4) stamp properties
5 a) templatize #template1 => ERROR
5 b) skip templatize and stamp properties => #template2 is rendered....
How am i able to make this possible? calling stamp() after rendering #template2 will result in another #template2 render. I want #template1, but I can't templatize #template1 because it has been already templatized. And stamp is always "binded" to last templatized element.
Am I doing something wrong? I do really hate Polymer because of it's bad documentation and hard to google something usefull
I found a workaround which is propably not the best solution but it works. I tried to search in source code for some solutions but there wasn't anything usefull except the one property called __templatizeOwner. This property is set to all templatized elements. Removing this property from an element is the way.
renderTemplate(query, properties) {
let element = this.shadowRoot.querySelector(query);
if(element.__templatizeOwner) {
element.__templatizeOwner = null;
}
this.templatize(element);
var instance = this.stamp(properties);
return instance;
}
I am not sure what side effects this might have (more memory usage or something) but this is the only way I was able to find out.