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>
Related
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.
My goal is to append an element to existing dom-if dynamically. Problem is that after appending I can see appended element in the DOM three but it never reacts on condition and stays always hidden.
<template>
<template id="domif" is="dom-if" if="[[condition]]" restamp></template>
</template>
ready() {
var el = document.createElement("input");
Polymer.dom(this.$.domif).appendChild(el);
Polymer.dom.flush();
}
Exploring DOM with hardcoded dom-if and input shows that <input /> element is actually not a child of dom-if but lives next to it..
<template>
<template is="dom-if" if="[[condition]]" restamp>
<input />
</template>
</template>
That gave me a clue that I probably should append my element next to dom-if... But now the biggest question is how to say to dom-if that appended element should be rendered if condition is satisfied. Any ideas?
How about adding a span in your dom-if and appending it to that span?
Update after some comments : We need to use this.async for the item to be found. Using the ready-event only works when the condition is true initially. So you could append the element in a conditionChanged-observer - this is a working example :
<dom-module id='my-element1'>
<template>
<template is="dom-if" if="[[condition]]" restamp>
<span id="appendHere"></span>
</template>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element1',
properties: {
condition: {
type: Boolean,
observer: "_conditionChanged"
}
},
_conditionChanged: function(newVal) {
if (newVal) {
this.async(function() {
var el = document.createElement("input");
Polymer.dom(this.$$("#appendHere")).appendChild(el);
Polymer.dom.flush();
});
}
}
});
</script>
Try it here : http://plnkr.co/edit/1IIeM3gSjHIIZ5xpZKa1?p=preview .
A side-effect of using dom-if in this case is that after setting the condition to false, the element disappears completely and gets added on the next condition-change again. So every change before setting the condition to false gets lost. You could work around it by putting the added element somewhere hidden when the condition changes and getting it back later, but I don't think this is a good idea, if the following is an alternative :
The Polymer-team recommends using dom-if only if there is no other way, like hiding the element. So, if it is possible you also could do something like this (condition has to be true to hide the element) :
<dom-module id='my-element1'>
<template>
<span id="appendHere" hidden$="[[condition]]"></span>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element1',
properties: {
condition: Boolean
},
ready: function() {
var el = document.createElement("input");
Polymer.dom(this.$.appendHere).appendChild(el);
Polymer.dom.flush();
}
});
</script>
Try it here :
http://plnkr.co/edit/mCtwqmqtCPaLOUveOqWS?p=preview
The template element itself will not be added to the DOM, this is the reason you can't access it using querySelector or getElementXxx
I have a question regarding dom-repeat. I have two different elements like:
<host-element>
<item-element></item-element>
<item-element></item-element>
</host-element>
each item-element has an array, in which items can be added at runtime. When the item-element
is attached is fires an event, so that the host-element knows about the item-element within its content and adds each
item-element to an array of item-elements to a property. To access the item-element
item arrays you could bind to the property of the host-element like:
<host-element items="{{itemElements}}">
<item-element></item-element>
<item-element></item-element>
</host-element>
to print the content of the itemElements iterate over it with dom-repeat
<template is="dom-repeat" items="{{itemElements}}">
<ul>
<template is="dom-repeat" items="{{item.values}}" as="value">
<li>[[value]]</li>
</template>
<ul>
</template>
so far everything works as expected. When the item-element change the dom-repeat
should redraw itself, but it is not happening. The documentation states you could uses dom-repeat.observe or dom-repeat.render
to update the dom-repeat element. Using dom-repeat.render manually works and could be
run automatically, but is not ideal. Therefor I am trying to find a solution with dom-repeat.observe with no luck so far.
<template is="dom-repeat" items="{{itemElements}}" observe="values.splice">
<ul>
<template is="dom-repeat" items="{{item.values}}" as="value" observe="????">
<li>[[value]]</li>
</template>
<ul>
</template>
I have pushed my source to github at source and a live demo
Thanks for your help.
Sandro
I have found a hack to get it to work. I need to alter the array holding all items.
The function _itemElementChanged is called every time a item-element is changed.
_itemElementChanged: function(){
// the check is needed if this function is run multiple times in the same tick it would erase the whole array
if (this.items.length > 0){
var itemsTmp = this.items;
this.items = [];
this.async(function () {
this.self.items = this.items;
}.bind({items: itemsTmp, self: this}));
}
}
The check for of this.items.length > 0 is need incase _itemElementChanged is called twice
before the async function runs. In that case this.items would end up empty.
This is by fare not a satisfying solution, but its the only working on I found so far.. I have updated the source to include the solution.
I've a custom element which, among other things, has a core-input and a paper button in it.
When the element is created, the input is disabled, and I want to enable it when I tap the button.
I've tried several ways and can't access the input's attribute.
<paper-input-decorator label="Nombre de usuario" floatingLabel>
<input id="usernameinput" value="{{UserName}}" is="core-input" disabled />
</paper-input-decorator>
<paper-button raised id="edprobutton" on-tap="{{edbutTapped}}">EDITAR</paper-button>
What should I write in
edbutTapped: function () {
},
EDIT
So, I've learned that the problem was that my username input element was inside a repeat template, and that's bad for what I was trying to do. Now I'm trying to bind a single json object to my element, with no luck so far.
What I have right now:
In my Index page:
<profile-page id="profpage" isProfile="true" entity="{{profEntity}}"></profile-page>
<script>
$(document).ready(function () {
var maintemplate = document.querySelector('#fulltemplate');
$.getJSON('api/userProfile.json', function (data) {
var jsonString = JSON.stringify(data);
alert(jsonString);
maintemplate.profEntity = jsonString;
});
}
</script>
In my element's page:
<polymer-element name="profile-page" attributes="isprofile entity">
<template>
<style>
[...]
</style>
<div flex vertical layout>
<core-label class="namepro">{{entity.Name}}</core-label>
<core-label class="subpro">{{entity.CompanyPosition}}</core-label>
<core-label class="subpro">{{entity.OrgUnitName}}</core-label>
</div>
</template>
</polymer-element>
And my JSON looks like this:
{"Name": "Sara Alvarez","CompanyPosition": "Desarrollo","OrgUnitName": "N-Adviser"}
I'm asuming I need to "update" my element somehow after changing its entity attribute?
Try the following
<script>
Polymer({
edbutTapped: function () {
this.$.usernameinput.disabled = false;
}
});
</script>
The this.$ allows you to access controls defined in an elements and the usernameinput is the id you assigned to the input.
This can go below the closing tag of the element you are defining.
'disabled' is conditional-attribute.
So this will be the correct use of it:
<input id="usernameinput" value="{{UserName}}" is="core-input" disabled?="{{isDisabled}}" />
In the prototype:
//first disable the field, can be done in ready callback:
ready: function () {
this.isDisabled = 'true';
}
//set idDisabled to 'false' i.e. enable the input
edbutTapped: function () {
this.isDisabled = 'false';
},
OK this is going to be a long answer (hence why I am not entering this as an edit of my original answer). I've just done something which is functionally the same.
The first thing is this code;
$.getJSON('api/userProfile.json', function (data) {
var jsonString = JSON.stringify(data);
alert(jsonString);
maintemplate.profEntity = jsonString;
});
Polymer has a control called core-ajax - this as it's name suggests makes an ajax call. The other really nice thing is that it can be made to execute when the URL changes. This is the code from the project I've got.
<core-ajax id="ajax"
auto=true
method="POST"
url="/RoutingMapHandler.php?Command=retrieve&Id=all"
response="{{response}}"
handleas="json"
on-core-error="{{handleError}}"
on-core-response="{{handleResponse}}">
</core-ajax>
The auto is the bit which tells it to fire when the URL changes. The description of auto from the polymer documentation is as follows;
With auto set to true, the element performs a request whenever its
url, params or body properties are changed.
you don't need the on-core-response but the on-core-error might be more useful. For my code response contains the JSON returned.
So for your code - it would be something like this
<core-ajax id="ajax"
auto=true
method="POST"
url="/api/userProfile.json"
response="{{jsonString}}"
handleas="json"
on-core-error="{{handleError}}" >
</core-ajax>
Now we have the data coming into your project we need to handle this. This is done by making use of Polymer's data-binding.
Lets detour to the element you are creating. Cannot see anything wrong with the following line.
<polymer-element name="profile-page" attributes="isprofile entity">
We have an element called 'profile-page' with two properties 'isprofile' and 'entity'.
Only because my Javascript leaves a bit to be desired I would pass each property as a seperate entity making that line
<polymer-element name="profile-page" attributes="isprofile name companyposition OrgUnitName">
Then at the bottom of your element define a script tag
<script>
Polymer({
name: "",
companyposition: "",
OrgUnitName: ""
});
</script>
Now back to the calling (profile-page). The following code (from my project) has the following;
<template repeat="{{m in response.data}}">
<map-list-element mapname="{{m.mapName}}" recordid="{{m.Id}}" on-show-settings="{{showSettings}}">
</map-list-element>
</template>
Here we repeat the following each element. In your case you only have one entry and it is stored in jsonString so your template is something like this
<template repeat="{{u in jsonString}}">
<profile-page name="{{u.name}} companyposition="{{u.companyposition}}" OrgUnitName="{{u.OrgUnitName}}">
</profile-page>
</template>
Now we get to the issue you have. Return to your profie-page element. Nothing wrong with the line
on-tap="{{edbutTapped}}"
This calls a function called edbutTapped. Taking the code I gave you earlier
<script>
Polymer({
edbutTapped: function () {
this.$.usernameinput.disabled = false;
}
});
</script>
The only thing to change here is add the following code
created: function() {
this.$.usernameinput.disabled = true;
},
This is inserted after the Polymer({ line. I cannot see in your revised code where the usernameinput is defined but I am assuming you have not posted it and it is defined in the element.
And you should be working, but remember to keep your case consistent and to be honest I've not been - certain parts of Polymer are case sensitive - that catches me out all the time :)
I am calling a polymer element within another element. The inner polymer element has a published attribute to which I am binding JSON from the parent polymer. However it is not getting reflected.
<polymer-element name="parent-test" attributes="testData">
<template>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
</template>
<script>
Polymer('parent-test', {
testData: [],
ready: function () {
debugger;
this.testData = [1, 2, 3, 4]
}
});
</script>
</polymer-element>
<polymer-element name="child-test" attributes="testDataChild">
<template>
<!--{{testDataChild}}-->
<template repeat="{{test in testDataChild}}">
{{test}}
</template>
</template>
<script>
Polymer('child-test', {
testDataChild: [],
ready: function () {
debugger;
}
});
</script>
</polymer-element>
I am not sure what could be the problem here.
Edit:
Seems like I am not having the actual parentContent at the time of generating the child-polymer-element.
If I assign hardcoded values in ready function for this.parentContent, it doesnt work as well.
If I assign hardcoded values in create function for this parent.Content, it works.
So, I am not not sure if this is something related to generating the child polymer element before the values getting binded to parent.
Thanks,
Sam
I modified your plunk example and get it working without your workaround :
Plunk
<polymer-element name="child-test" attributes="testdatachild">
<template>
<br><br>
In Child el.:
<br>
<template repeat="{{test in testdatachild}}">
{{test}}
<br>
</template>
</template>
<script>
Polymer('child-test', {
ready: function () {
}
});
</script>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
<br>
</template>
<script>
Polymer('parent-test', {
created: function () {
this.testData = [1, 2, 3, 4];
}
});
</script>
The main problem seems to be the order of the code
I guess it works better to first declare the child, then the parent, as the child is used in the parent...
Also, as specified in the polymer documentation :
polymer
Important: For properties that are objects or arrays, you should always initialize the properties in the created callback. If you set the default value directly on the prototype (or on the publish object), you may run into unexpected “shared state” across different instances of the same element.
Here is modified example of you code that works : Plunk
Why your example is not working, I don't have all answers buy you are right for one:
<!-- This won't work cause:
"Attributes on child-test were data bound prior to Polymer upgrading the element.
This may result in incorrect binding types." -->
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>