Repeat light dom element - polymer

In my component I would like to repeat a list of item with template provided by the light dom of the component. For example:
<template is="dom-repeat" items="{{items}}">
<content select="#itemTemplate"></content>
</template>
However, it seems that Polymer only inserts the light dom element #itemTemplate exactly one time instead of multiple times. Is there other way to repeat a light dom element?

I have created a simple prototype, that lets you specify the number of repetitions of the light DOM template.
Because the content is in the light DOM, you can style it from the outside as you would usually do. And data binding inside the template also works, since I have implemented the _forwardParentProp, and _forwardParentPath methods from the Templatizer.
Be aware, that I have not implemented the instance specific properties, which would allow per row specific variables, such as index and item. This can, of course, be done, but would need a bit more work.
See the prototype in action: JSBin.
OK, let's go into details:
The usage of the test-element along with data-binding to both input elements is fairly straightforward:
<template is="dom-bind">
Number of repeats: <input type="text" value="{{repeats::input}}" /> <br />
Custom message: <input type="text" value="{{customMessage::input}}" />
<test-element repeats="{{repeats}}">
<template>
<h1>Title!</h1>
<p>
Custom message: <em>[[customMessage]]</em>
</p>
</template>
</test-element>
</template>
Notice the dom-bind, which is needed to create a data-binding scope.
As for the test-element, the whole source code looks like this:
<dom-module id="test-element">
<template>
<style>
:host {
display: block;
}
</style>
<content></content>
</template>
<script>
Polymer({
is: 'test-element',
behaviors: [
Polymer.Templatizer,
],
properties: {
repeats: {
type: Number,
value: 3,
notify: true,
},
},
observers: [
'_repeatsChanged(repeats)',
],
_repeatsChanged: function(repeats) {
// First time only: initialize template
if (this.template === undefined) {
this.template = Polymer.dom(this).querySelector('template');
this.templatize(this.template);
}
// Remove previously stamped children
while (Polymer.dom(this).firstChild) {
Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
}
// Stamp new ones
this.stamped = new Array(repeats);
var inst;
for (var i = 0; i < repeats; i++) {
inst = this.stamp(null);
this.stamped[i] = inst.root.querySelector('*');
Polymer.dom(this).appendChild(inst.root);
}
},
// Copied from iron-list
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance[prop] = value;
}, this);
}
},
// Copied from iron-list
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance.notifyPath(path, value, true);
}, this);
}
},
});
</script>
</dom-module>
There is only one property, repeats, which specifies the number of stamped instances. Default value is 3. To accomodate changes of said property's value, a observer has been created. This is also the place where the stamping takes place:
_repeatsChanged: function(repeats) {
// First time only: initialize template
if (this.template === undefined) {
this.template = Polymer.dom(this).querySelector('template');
this.templatize(this.template);
}
// Remove previously stamped children
while (Polymer.dom(this).firstChild) {
Polymer.dom(this).removeChild(Polymer.dom(this).firstChild);
}
// Stamp new ones
this.stamped = new Array(repeats);
var inst;
for (var i = 0; i < repeats; i++) {
inst = this.stamp(null);
this.stamped[i] = inst.root.querySelector('*');
Polymer.dom(this).appendChild(inst.root);
}
},
Firstly (and only once), the template is read from the light DOM and
the templatize method is called. This method initializes the
Templatize behavior.
Secondly, all previously stamped children are removed (so that the
elements don't just build up infinitely).
Thirdly, new children are stamped, according to the current value of
repeats. All stamped instances are saved to this.stamped, which
is needed for the data-binding from the outside to work.
Last but not least, the Templatizer behavior is implemented via two methods (and two are left unimplemented):
// Copied from iron-list
_forwardParentProp: function(prop, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance[prop] = value;
}, this);
}
},
// Copied from iron-list
_forwardParentPath: function(path, value) {
if (this.stamped) {
this.stamped.forEach(function(item) {
item._templateInstance.notifyPath(path, value, true);
}, this);
}
},
Both methods are taken from the iron-list. They iterate through the stamped children and propagate property changes and path notifications.

You can include your content in a separate element and use it.
<template is="dom-repeat" items={{items}}">
<child-element item=[[item]]></child-element>
</template>

Related

How can I get data from another Vue file?

As the code show below, A.vue file has element data return some number values
<template></template>
<script>
export default {
data(){
return{
element: [
{
number:'11'
}
{
number:'22'
}
]
}
}
}
</script>
Now I want to get element.length from A.vue to B.vue. Is there a way to do that? I saw a solution with button click but i dont want to use button to pass data.
B.vue file
<template>
<div>I want to get element.length here</div>
</template>
You can simply achieve it by passing prop (which contains the length of the element array) from A.vue component to B.vue component. Here is the live demo :
Vue.component('bcomponent', {
// declare the props
props: ['length'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<div>Element length: {{ length }}</div>',
});
var app = new Vue({
el: '#app',
data: {
element: [{
number: '11'
}, {
number: '22'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Component A -->
<div id="app">
<BComponent :length="element.length">
</BComponent>
</div>
If it's possible, just pass the data as a prop from B to A, this way you can implement any logic on the data.
If it's not, you should use vuex for data storage, so any component can access it.

How to change parent element property from child element?

How can I let a child element change the value of a property in the parent element with the ability to observe that change in the parent element
<link rel="import" href="paper-tree-node.html">
<dom-module id="paper-tree">
<template>
<div>
<paper-tree-node id="root" data="[[data]]" actions="[[actions]]" on-click='_handlePaperCheck' chapterIds={{chapterIds}}></paper-tree-node>
</div>
</template>
</dom-module>
<script>
Polymer({
is: 'paper-tree',
properties: {
chapterIds: {
type: Array,
value: [],
notify: true,
observer: "_chapterChanged"
}
},
_handlePaperCheck: function (e) {
let element = e.target.parentElement
if (element.checked) {
this.push('chapterIds', parseInt(element.id.substr(2)))
// console.info(this.chapterIds);
} else {
var index = this.chapterIds.indexOf(element.id);
this.splice('chapterIds', index, 1)
// console.info(this.chapterIds);
}
},
_chapterChanged: function () {
console.log(this.chapterIds)
// this.$.root.chapterIds = this.chapterIds
}
})
noting that paper-tree-node is a child element hosts a paper-check inside it's template, the purpose of this is to harvest the clicked paper-tree-node id attr and push it to the chapterIds property.
Problem is that _chapterChanged wont fire when i click on any checkbox
I am attaching a sample project since this cannot be posted on somthing like jsbin, here is a gdrive zip folder for the project https://drive.google.com/file/d/1yCeXkZu8Yp-8GUgadGHIfeP5w5uyI12J/view?usp=sharing
You're using the right thinking, but not the entire way.
notify: true, should be stated in your child element paper-tree-node under the property chapterIds, and not under your paper-tree element. I made this mistake too when I began with Polymer.
Also, whenever Polymer sees camelCase variables, it assumes the variable contains dashes:
<paper-tree-node id="root" data="[[data]]" actions="[[actions]]" on-click='_handlePaperCheck' chapterIds={{chapterIds}}></paper-tree-node>
... should be ...
<paper-tree-node id="root" data="[[data]]" actions="[[actions]]" on-click='_handlePaperCheck' chapter-ids={{chapterIds}}></paper-tree-node>
... where I switched the property chapterIds to chapter-ids. I rarely use camelCase variables when creating a new element because this mistake is so easy to make.
You can do this with an event or with data binding.

How to distinguish between structural changes to array made in parent element vs property changes to an item of array made in child element observer?

This appears to be similar to a question I asked earlier: In Polymer 2.0 how to observe edits to properties of an object bound to an element inside dom-repeat?
But it's not. The code example is similar, but the question is focusing on a different aspect of that code.
Here is the example code: https://plnkr.co/edit/iTZqM4GwpASEqQgtRGEk
The list element:
<link rel="import" href="./editor-element.html">
<dom-module id="list-element">
<template>
<dom-repeat items="{{list}}" as="item">
<template>
<div class="item">
<editor-element description="{{item.description}}">
</editor-element>
</div>
</template>
</dom-repeat>
</template>
<script>
class ListElement extends Polymer.Element {
static get is() {
return "list-element";
}
static get properties() {
return {
list: {
type: Array,
value:function() {
return []
}
}
}
}
ready() {
super.ready();
this.push("list", {
description:"one"
})
this.push("list", {
description:"two"
})
setTimeout(function() {
this.set("list.0.description", "one edited");
}.bind(this), 500)
setTimeout(function() {
this.unshift("list", {
description:"three"
})
}.bind(this), 1000)
}
}
customElements.define(ListElement.is, ListElement);
</script>
</dom-module>
And the editor element:
<dom-module id="editor-element">
<template>
<div>Editor for [[description]]</div>
</template>
<script>
class EditorElement extends Polymer.Element {
static get is() {
return "editor-element";
}
static get properties() {
return {
description:String
}
}
static get observers() {
return [
"observe(description)"
]
}
observe(description) {
console.log("Observed change for TODO item: "+description);
}
}
customElements.define(EditorElement.is, EditorElement);
</script>
</dom-module>
In the list (parent) element, two todo items are pushed onto the array, the first one is modified, and then a third item is unshifted into the array.
The observer in the editor (child) element is triggered twice for the pushes, and once for the edit. So far so good. It's then triggered three times for the unshift.
I understand the logic behind this, the unshift scrambles the mapping between data and elements, which are re-used to render the newly assigned data.
My question is this:
How do I write an observer that can distinguish between observations triggered by a restructuring of the array, vs those triggered by an edit to a property of an item in that array?
Unshift is enough to illustrate this, but the same thing would happen if the dom-repeat has a sort or filter applied.
Just to clarify, I'm neither here nor there on the fact that the addition of one item to the array in the parent element results in an observable change in every dom-repeated child element. What is important however is that I can distinguish that observable change from the mutation of a property in one specific child element. In the approach shown in the sample code, I cannot, so it might mean I have taken the wrong approach to data binding here.

Two way data binding with Polymer.Templatizer

I am trying to get two way data-binding between a host element and a template in Polymer using templatizer. For example if I am trying to keep two input boxes in-sync:
<html>
<body>
<my-element>
<template >
<input type="text" value="{{test::change}}" />
<div>The value of 'test' is: <span>{{test}}</span></div>
</template>
</my-element>
<dom-module id="my-element">
<template>
<input type="text" value="{{test::change}}" />
value:
<p>{{test}}</p>
<div id="items"></div>
<content id="template"></content>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element',
test: {
type: String,
value: "a"
},
behaviors: [ Polymer.Templatizer ],
_forwardParentProp: function(prop, value) {debugger},
_forwardParentPath: function(path, value) {debugger},
_forwardInstanceProp: function(inst, prop, value) {debugger},
_forwardInstancePath: function(inst, path, value) {debugger},
ready: function() {
this._instanceProps = {
test: true
};
var templates = Polymer.dom(this.$.template).getDistributedNodes();
template = templates[1];
this.templatize(template);
var itemNode = this.stamp({ test: this.test});
Polymer.dom(this.$.items).appendChild(itemNode.root);
}
});
</script>
</body>
</html>
In the above code I hit the debugger in the _forwardInstanceProp but not any of the others. Why is this? Inside _forwardInstanceProp I can access my-element and manually update the test property. Is there a better way to do this? I also could add an observer on my-element to the test property and then propagate any changes in my-element to the template. Is there a better way to do that? I am just trying to understand what all four of these methods do and when/why they should be used.
It beats my why I can never get neither _forwardParentPath nor _forwardParentProp to run. However, I know when the other two run :)
_forwardInstanceProp runs for direct properties of model passed to stamp and _instanceProps is initialized:
this._instanceProps = {
text: true
};
var clone = this.stamp({
text: this.text
});
_forwardInstancePath on the other hand runs when you pass nested objects to stamp:
var clone = this.stamp({
nested: {
text: this.text
}
});
See this bin for an example: http://jsbin.com/kipato/2/edit?html,js,console,output
In the stamped template there are two inputs bound to two variables which trigger instanceProp and instancePath. Unfortunately I've been unable to fix the error thrown when the latter happens.

Remove child element's attribute from Polymer js

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 :)