Can I use the polymer dom-repeat template without an array?
For example I want to render some code 20 times.
<template is="dom-repeat" items="[[itemCounter]]">
<div>Whatever</div>
</template>
This would render <div>Whatever</div> 20 times, but to achieve this I have to create an array "itemCounter" in the components properties with length 20 with the sole purpose of looping over it.
I was wondering if something like this is possible, so I don't need to create the Array.
<template is="dom-repeat" times="20">
<div>Whatever</div>
</template>
Nope, you can't do this with the normal dom-repeat but I wrote a component which does exactly the trick: https://github.com/MeTaNoV/dom-repeat-n
There is also a discussion about this feature on the Polymer github repository that you can find here: https://github.com/Polymer/polymer/issues/3313
You can do a messy hack like this
Properties:
totalNo: {
type: Number,
value: 20
},
_arrayContainer: {
type: Array ,
value: [],
computed: '_numberToArray(totalNo)'
}
Method:
_numberToArray: function(totalNo) {
var array = [], i;
for (i = 0; i < totalNo; ++i) {
array.push(i);
};
return array;
},
HTML:
<template is="dom-repeat" items="[[_arrayContainer]]">
<div>Whatever</div>
</template>
But I'm not sure you'd every really want to.
Related
Im new to Polymer. Im using two different Polymer components that communicates with each other. I have to do it twice (2 x 2 components).
The following code uses just one pair of different components and it works:
<dom-bind id="dombind">
<template is="dom-bind">
<polymer-componentA id="polymercomponentA_1"
attribute="[[x]]"
attribute="[[x]]"
attribute="{{x}}">
</polymer-componentA>
<polymer-componentB id="polymer-componentB_1"
attribute="{{x}}">
</polymer-componentB>
</template>
</dom-bind>
But when I add other pair of components, it starts working bad. I think they use the same component (instead of being independent):
<dom-bind id="dombind">
<template is="dom-bind">
<polymer-componentA id="polymercomponentA_1"
attribute="[[x]]"
attribute="[[x]]"
attribute="{{x}}">
</polymer-componentA>
<polymer-componentB id="polymer-componentB_1"
attribute="{{x}}">
</polymer-componentB>
</template>
</dom-bind>
<dom-bind id="dombind">
<template is="dom-bind">
<polymer-componentA id="polymercomponentA_2"
attribute="[[x]]"
attribute="[[x]]"
attribute="{{x}}">
</polymer-componentA>
<polymer-componentB id="polymer-componentB_2"
attribute="{{x}}">
</polymer-componentB>
</template>
</dom-bind>
I know this is not done well, but I dont find good examples about this.
Whats the right way to use two polmyer components?
Thanks in advance!
If you provide a function, Polymer calls the function once per element
instance.
When initializing a property to an object or array value, use a
function to ensure that each element gets its own copy of the value,
rather than having an object or array shared across all instances of
the element.
Source: https://www.polymer-project.org/2.0/docs/devguide/properties#configure-values
Example
static get properties() {
return {
data: {
type: Object,
value: function() { return {}; }
}
}
}
I am writing a simple widget that will create an output based on fetched data (taken from an AJAX request).
This version of the my-element is the non-configurable, standard one:
http://jsbin.com/rivala/edit?html,output#H:L56
Thing is, I want the user to be able to decide what the output will look like. Since Polymer doesn't allow us to extend existing elements, I went the other way around: I create a behaviour (err... excuse me, a behavior, it's so hard not to type that "u" every time) that does most of the work. Here is my result:
http://jsbin.com/yuxecu/edit?html,output
So, in order to create create an element, all the user needs to do is:
<dom-module id="my-element">
<template>
<!-- THE FOLLOWING PART IS THE ONLY THING THE USER WILL CHANGE -->
<paper-dropdown-menu label="Your favourite category">
<paper-menu class="dropdown-content">
<template is="dom-repeat" items="{{_data}}">
<paper-item>{{item.name}}</paper-item>
</template>
</paper-dropdown-menu>
</template>
<script>
Polymer({
is: "my-element",
behaviors: [ MyBehaviour],
})
</script>
</dom-module>
And then use it:
I would have much much preferred something a little easier. For example, it would have been much nicer to allow something like this:
<my-element url="http://output.jsbin.com/zonona/3.js">
<template id="bindme">
<!-- THE FOLLOWING PART IS THE ONLY THING THE USER WILL CHANGE -->
<paper-dropdown-menu label="Your favourite category">
<paper-menu class="dropdown-content">
<template is="dom-repeat" items="{{_data}}">
<paper-item>{{item.name}}</paper-item>
</template>
</paper-dropdown-menu>
</template>
</my-element>
But I tried and tried and then tried some more, and it doesn't seem to be possible unless you really want to get your hands dirty.
Once extending non-native elements is possible, I assume I can just create an element declaratively that extends my-element and defines a new template. Till then...
Questions:
Does my code seem to be following at least roughly Polymer's best practices?
Is there a much easier way to do this, that I didn't think of?
Any more comments?
Thank you as ever...
I don't know what I am doing is quite the same thing, but you might be able to draw inspiration from it. I have created a generic dialog box that will provide the results from a database query in it, with the headings data driven and the row size and content also data driven. I actually create this element dynamically in a "manager" element.
Something like this is how the manager retrieves the data and creates the dialog (I call it a report-grid)...
newGrid: function(name, useId, useDates, parent) {
var self = this;
var body;
// jshint unused: false
var dataPromise = new Promise(function(accept, reject) {
var sendOptions = {
url: '/api/queries',
method: 'POST',
handleAs: 'json',
headers: {'content-type': 'application/json'}
};
body = {};
body.name = name;
if (useId) {
body.id = parent.id;
}
if (useDates) {
body.startdate = parent.startdate;
body.enddate = parent.enddate;
}
sendOptions.body = body;
var request = document.createElement('iron-request');
request.send(sendOptions).then(function() {
accept(request.response);
});
});
// jshint unused: true
var x;
var y;
var grid = document.createElement('pas-report-grid');
Polymer.dom(self).appendChild(grid);
if (this.grids.length === 0) {
x = 0;
y = 0;
} else {
x = this.grids[this.grids.length - 1].x + this.deltaX;
y = this.grids[this.grids.length - 1].y + this.deltaY;
}
this.grids.push(grid);
grid.open(dataPromise,body,x,y);
And then the element itself has a load of stuff (not shown) to provide drag and resize handles, but the core of the grid is the following templated stuff
<div class="layout horizontal">
<template is="dom-repeat" items="[[heading]]">
<span class="flex">[[item]]</span>
</template>
</div>
<iron-list id="grid" class="flex" items="[[data]]" as="row">
<template>
<div class="layout horizontal row" tabindex$="[[tabIndex]]" index="[[index]]">
<template is="dom-repeat" items="[[row]]" as="field">
<div class="flex field">[[field]]</div>
</template>
</div>
</template>
</iron-list>
The open function of the grid does this with the data
open: function(dataPromise, params, x, y) {
var self = this;
this.x = x;
this.y = y;
dataPromise.then(function(data) {
self.title = data.name;
self.heading = data.heading;
self.data = data.data;
self.$.griddialog.open();
});
this.params = params;
So what is happening here is the manager is making an iron request (also created dynamically) for a generic query that might or might not need an id and start and end dates, the server responds with a json object which contains a heading array, with a list of heading names, and a data array which is the rows, each row also being an array with the values from the query. I pass that info to the grid element as a promise - so it can get started, attach and so on, and then when the data arrives its loaded into a heading div and an iron list.
The grid element knows nothing about the actual query, how many fields each row will have, or indeed how many rows.
I have a custom element that utilizes iron-list to display an array of objects. Each item is generated via a template as follows:
<iron-list id="projectList" items="[[projects]]" indexAs="_id" as="projLI" class="layout flex">
<template>
<div>
<paper-material id="itemShadow" animated elevation="1">
<div class="item layout horizontal" onmouseover="hoverOver(this)" onmouseout="hoverOut(this)">
<!-- I use a paper-menu-button to display a list of available actions here -->
<!-- list item object content here such as: [[projLI.desc]] etc. -->
</div>
</paper-material>
</div>
</template>
</iron-list>
What is the best polymer-friendly approach to detect both a tap event on the iron-list item itself (ideally knowing which item was actually tapped via projLI._id), yet also be able to handle the internal paper-menu-button tap events in a different way?
I've eye-balled polymer 1.0's new event listeners (https://www.polymer-project.org/1.0/docs/devguide/events.html), as a possible approach, attempting to listen for different element tap events (as shown in example 1 on that page), but I'm not sure if that will work here. I've also considered possibly using iron-selector somehow around iron-list? Is that doable? I'm not sure that will work either, given that iron-selector would only have one child (i.e. the iron-list element and not it's templated children).
I feel like I'm missing a really easy way to accomplish this. Can someone please show me the light?
Follow the model outlined on lines 154 and 184 of this demo.
https://github.com/PolymerElements/iron-list/blob/master/demo/collapse.html
my-element.html
<iron-list items="[[items]]">
<template>
<my-list-item on-tap="_toggleMe"></my-list-item>
</template>
</iron-list>
...
_toggleMe: function(e) {
console.log(e.model.index);
}
The key is to place the event and listener method (toggleMe() in this case) inside the <template> of the iron-list. This allows the iron-list to register the array index.
I do this by encoding an array index in a list element id, then pulling the id out of a list item event target. Here is an example Polymer element that does this.
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/iron-list/iron-list.html">
<dom-module id="list-example">
<style>
:host {
display: block;
}
#list-example {
height: 100px;
}
</style>
<template>
<paper-material animated elevation="1">
<iron-list id="list-example" items="[[data]]">
<template>
<div id="{{index2id(item.index)}}" on-mouseover="onMouseOverItem">{{item.name}}</div>
</template>
</iron-list>
</paper-material>
</template>
</dom-module>
<script>
(function () {
Polymer({
is: 'list-example',
ready: function() {
for(var i = 0; i < this.data.length; i++) {
this.data[i].index = i;
}
},
index2id: function(index) {
return "_" + index;
},
id2index: function(id) {
return Number(id.substr(1));
},
onMouseOverItem: function(e) {
console.log('on-mouseover list item:', this.data[this.id2index(e.target.getAttribute('id'))]);
},
properties: {
data: {
type: Array,
value: [{name: 'A'}, {name: 'B'}, {name: 'C'},
{name: 'D'}, {name: 'E'}, {name: 'F'},
{name: 'G'}, {name: 'H'}, {name: 'I'}]
}
}
});
})();
</script>
I was having a similar issue and solved my problem using <array-selector> as follows:
<iron-list items="{{myarray}}" as="ref">
<template>
<div>
<paper-checkbox on-tap="toggleSelection"></paper-checkbox>
<span>{{ref.name}}</span>
</div>
</template>
</iron-list>
<array-selector id="arrsel" items="{{myarray}}"
selected="{{selectedName}}" toggle></array-selector>
and myarray is an array of objects:
var myarray = [{name: "Alice"}, {name: "Ben"}, ...]
and the function toggleSelection is defined as follows:
toggleSelection: function(e) {
console.log ("Selected index is " + e.model.index);
console.log ("Selected name is " + e.model.ref);
this.$.arrsel.select (e.model.ref);
console.log ("Current selection: ", this.selectedName);
}
The field name ref after e.model.__ is the value of the as attribute of iron-list.
WARNING: The variable e.model is not officially documented on the Polymer 1.0 iron-list doc (https://elements.polymer-project.org/elements/iron-list), however I discovered it during my debugging session. I am assuming that e.model is a public property (the coding style of Polymer uses underscore prefix for private property such as: _scroll_Position) and it is not a candidate for deprecation.
I just solved my issue of here https://groups.google.com/forum/#!topic/polymer-dev/r9IsUKVnLVM. Reading this documentation https://www.polymer-project.org/1.0/docs/devguide/events.html.
I hope it helps you!
Wrap your iron-list with an iron-selector - this allows you get to get the row selected / tapped.
(NB: you might need to remove your custom indexAs="_id" attribute to get the right row index)
<iron-selector attr-for-selected="index" on-tap="_itemSelected">
<iron-list id="projectList" items="[[projects]]" as="projLI" class="fit">
<template>
<div class="layout horizontal center" id="{{index}}">
<!-- your row content here -->
</div>
</template>
</iron-list>
</iron-selector>
Polymer method for row item selected:
_itemSelected: function (e) {
console.log(e.target.id); // selected iron-list row index
}
I have a core-ajax element like so:
<core-ajax id="ds" auto url="address/to/data.json" response="{{data}}"></core-ajax>
Then a list element like so:
<my-items alarms="{{data}}"></my-items>
Which iterates through the data and displays it in another template:
<template repeat="{{item in items}}">
<my-item alarm="{{item}}"></my-item>
</template>
My question is, from within "my-item", how can I access the original {{data}}? For instance, if I wanted to get a count of the total items in a javascript function?
You need to pass that information into <my-item>.
If <my-item> also published a data property, you could pass it like this:
<template repeat="{{alarm in alarms}}">
<my-item alarm="{{alarm}}" data="{{alarms}}"></my-item>
</template>
or just the stats you need:
<my-item alarm="{{alarm}}" length="{{alarms.length}}"></my-item>
Polymer('my-item', {
dataChanged: function (e) {
console.log(this.data);
}
});
that should give you the data you need
This seems a trivial thing but I'm unable to find it:
What if I want to reverse the order of my items in a repeat, without actually touching the order of the array, like in:
<template repeat="{{layer in layers}}">
<div>{{layer.name}}</div>
</template>
where layers is an array of objects.
I've tried applying a filter and then working with a copy of the array, like in:
<template repeat="{{layer in layers | reverse}}">
<div>{{layer.name}}</div>
</template>
...
reverse: function(arr){
return _(arr).reverse();
}
but that results in some observers failing since they're looking at the copy instead of the original objects. I don't want to apply a sort to my original array since other parts of the code depend on that order.
Anyone knows of an option where just the order of display in the DOM is affected?
I think you need to do something like this
<template repeat="{{layer in temp_array}}">
<div>{{layer.name}}</div>
</template>
<script>
Polymer('el-name',{
ready: function(){
this.temp_array =[];
this.temp_array = layers.reverse();
}
}
);
</script>
if your layers is empty when ready called, use change listener
<script>
Polymer('el-name',{
ready: function(){
this.temp_array =[];
},
layersChanged: function(oldValue, newValue){
if(newValue.length != 0)
this.temp_array = newValue.reverse();
}
}
);
</script>
Hope it help for you
If it is possible to put the repeated elements in a vertical/horizontal layout, then reverse might do the trick (see layout documentation):
<div vertical layout reverse?="{{ isReversed }}">
<template repeat="{{ layer in layers }}">
<div>{{ layer.name }}</div>
</template>
</div>
I would like to offer a safier and more clear way to revert an array for repeat binding:
<polymer-element name="my-element" attributes="layers layersReversed">
<template>
<template repeat="{{layer in layers}}">
<div>{{layer.name}}</div>
</template>
</template>
<script>
Polymer({
layersReversedChanged: function() {
var layers = this.layersReversed.slice();
layers.reverse();
this.layers = layers;
}
});
</script>
</polymer-element>
<my-element layers="{{layers}}"><!-- direct order --></my-element>
<my-element layersReversed="{{layers}}"><!-- reverse order --></my-element>
Direct or reverse order is defined by used attribute: layers or layersReversed.
There are no value changing in corresponding -Changed event by itself (which may cause falling to endless loop).
The .reverse() method changes the original array, so it should be applied on its copy.
There is another funny and extravagant way to do the same via an intermediate web-component:
<polymer-element name="reverse-order" attributes="in out">
<template></template>
<script>
Polymer({
inChanged: function() {
var out = this.in.slice();
out.reverse();
this.out = out;
}
});
</script>
</polymer-element>
It can be used to bind some elements with different order. I.e., array is populated by .push() method, while preferred array presentation is in reverse order:
<my-element layers="{{layersReversed}}"></my-element>
<reverse-order in="{{layers}}" out="{{layersReversed}}"></reverse-order>
<core-localstorage name="layers" value="{{layers}}"></core-localstorage>