Polymer Advanced DataBinding - polymer

I plan to display three columns in my application, each with roughly 1k of custom items (picture, header, text). It will probably work with something like
<template>
<template bind="{{myData as m}}">
<template repeat if="{{m.1}}">
<custom-item icon="{{m.icon}}" label="{{m.title}}"></custom-item>
</template>
<template repeat if="{{m.2}}">
<custom-item icon="{{m.icon}}" label="{{m.title}}"></custom-item>
</template>
...
</template>
</template>
but I want to avoid loading 3k items from database to client, they will probably stay within top 200. Is there a way to do some sort of dynamic loading in polymer?

i would recommend core-list for this. https://www.polymer-project.org/docs/elements/core-elements.html#core-list i recently used it in a album list. (large album covers and several buttons and text) it greatly improved performance from using only a repeating template. to load more results you could use a onscroll callback.
keep in mind all this is assuming you are working in a auto-binding template.
<template id="app" is="auto-binding">
// all html code in here
</template>
and waited for the template to be bound
document.querySelector("#app").addEventListener('template-bound', function () {
//all this js code will be in here
});
im my app i used core-header-panel to get scroll event it would work the same with core-scroll-header-panel. i gave the header panel a id. i just called it headerPanel.
<core-header-panel id="headerPanel">
then i set the scrollTarget for core-list to use the scroller of the headerPanel. make a variable in js
this.scrollTarget = document.querySelector('#headerPanel').scroller;
then i link that to the core-list
<core-list data="{{data}}" scrollTarget="{{scrollTarget}}">
<template>
<custom-item icon="{{model.icon}}" label="{{model.title}}"></custom-item>
</tempalte>
</core-list>
that has core-list set up. as for not loading 3000 results i would use a callback on the scroller we set up a min ago. then calculate a % of the page that has been scrolled and call a function if scrolled more then say 80%
this.scrollTarget.onscroll = function () {
var precent = (this.scrollTarget.scrollTop / (this.scrollTarget.scrollHeight - this.scrollTarget.offsetHeight)) * 100;
if (precent > 80) {
// call some function to .push() more to this.data
}
};
edit: added bit about waiting for template to be bound.
hope this helps.

There is also a polymer element for infinite scrolling :
core-scroll-thresold, you can combine it with the core-list

Related

How to access elements within dom-repeat in Polymer 3.0?

I have the following piece of code:
<dom-repeat id="template" items="{{chats}}">
<template>
<template is="dom-if" if="[[item.opened]]">
<section id$="msg-[[index]]"></section>
</template>
</template>
</dom-repeat>
How do can I access section with id=msg-3 and set scrollTop to 999 upon being rendered?
I know that in polymer 1 the best practice was to bind to on attached and wait on a dom-repeat event with a debouncer this no longer works in polymer 3, however.
If you know the index number you can access the element with ;
let el = this.shadowRoot.querySelector('#msg-'+<index>);
Remember, if item not rendered due to your filter with dom-if, you can not acces this element.
EDIT
In order to show on top of the selected item;
let screenPosition = el.getBoundingClientRect();
window.scrollTo(screenPosition);
Demo

dynamically edit template of iron-list

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>

Call Method of Child Web Component

I'm really having difficulty trying to figure out how to call a function of a nested Polymer web component.
Here's the markup:
<rise-playlist>
<rise-playlist-item duration="5">
<rise-distribution distribution='[{"id":"VGZUDDWYAZHY"}]'></rise-distribution>
</rise-playlist-item>
</rise-playlist>
The rise-distribution component has a canPlay function that I would like to call from inside of rise-playlist.
The dom-module definition of rise-playlist looks like this:
<dom-module id="rise-playlist">
<template>
<content id="items" select="rise-playlist-item"></content>
</template>
</dom-module>
I can successfully access the rise-distribution element like this:
var distribution = Polymer.dom(this.$.items[0]).querySelector("rise-distribution");
However, when I try to call distribution.canPlay(), it says that distribution.canPlay is not a function.
I've defined the dom-module of rise-playlist-item like this:
<dom-module id="rise-playlist-item">
<content id="dist" select="rise-distribution"></content>
</dom-module>
Not sure if I need that <content> tag, although neither works.
Any ideas?
Thx.
I know that there have been a while but I am sure this problems still occurs as it is being viewed number of times.
Probably there is a problem with your component definition. Let me explain.
This is the way you put your child component inside DOM:
<your-child-component></your-child-component>
And and this should be the definition of your component:
Polymer({
is: 'your-child-component',
apiMethod: function() {
//some stuff
}
});
If you by mistake or due copy-paste error mistype the is: 'your-child-component' part, so it will not reflect the <your-child-component> you will get confused becouse your:
this.$$('your-child-component').apiMethod();
will tell you that there is no method you are willing to call.
Polymer correctly identified and selected from DOM <your-child-component> but if you have different is property (like for example is: your_child_component>) it will not attach its API to dom element you selected.
I hope that it will help if anyone ever will encounter this problem.

Is it possible to inject HTML into a polymer component via an attribute?

I'm using one of the core polymer components that basically has:
<polymer-element attributes="label">
<div>{{label}}</div>
as part of the source. I'd like to inject some HTML into this so that it ultimately renders as:
<div>Item <small>Description</small></div>
Is there any way to do this without copying the entire component (which is basically impossible considering the dependency chain)?
Polymer doesn't allow setting HTML inside {{}} expressions because it's a known XSS outlet. However, there are ways around it (1, 2).
I'm not sure there's a great way around this issue but I found something that works. You want to extend the element but also need to modify its shadow dom because of the .innerHTML limitation. Taking paper-button as an example, it has an internal {{label}}. You could extend the element, drill into its shadow dom, and set .innerHTML of the container where {{label}} is set. React to label changing (labelChanged) and call this.super():
<polymer-element name="x-el" extends="paper-button">
<template>
<shadow></shadow>
</template>
<script>
Polymer('x-el', {
labelChanged: function() {
// When label changes, find where it's set in paper-button
// and set the container's .innerHTML.
this.$.content.querySelector('span').innerHTML = this.label;
// call paper-button's labelChanged().
this.super();
}
});
</script>
</polymer-element>
Demo: http://jsbin.com/ripufoqu/1/edit
Problem is that it's brittle and requires you to know the internals of the element you're extending.

Attaching a method to a dynamically created element within Polymer

Is it possible to attach dynamically created elements to methods within the element?
For illustration purposes, the method 'alertMe' is not called, and is probably not being attached correctly. Changing the callback from ready to let's say created would not allow me to use the selector "this.$"
<polymer-element name="decorated-button">
<template>
<div id="container"></div>
</template>
<script>
Polymer('decorated-button', {
ready: function(){
var button = document.createElement('button');
button.setAttribute('on-click','{{alertMe}}');
this.$.container.appendChild(button);
},
alertMe: function(){
alert("From alert");
}
});
</script>
</polymer-element>
I'm looking to be able to create n elements dynamically, and to be able to call a method internally to take action and decide which element n initiated the action. The way I'm approaching it feels like I'm missing an underlying concept...open to any suggestions. Thanks in advance.