Using dom-if for conditionals in arrays - polymer

In 0.5 I could use the expressions in the dom-if to select for certain things in arrays while looping through them. How could I achieve the same effect in 1.0?

It's more efficient to use the filter/observe feature of dom-repeat instead of nesting dom-if. filter specifies a method that identifies records to display from your collection, observe tells the dom-repeat what data to observe to know when to re-run the filter. E.g.
<template is="dom-repeat" items="{{records}}" filter="hasPersonLabel" observe="item.labels">
...
hasPersonLabel: function (labels) {
return (labels.indexOf("Person") >= 0);
}
Documentation is here (https://www.polymer-project.org/1.0/docs/devguide/templates.html#filtering-and-sorting-lists).

You use a function like
hasPersonLabel: function (labels) {
if (labels.indexOf("Person") === -1) {
return false
}
return true
}
And then you can use
<template is="dom-repeat" items="{{records}}">
<template is="dom-if" if="{{isPerson(item.labels)}}">

Related

Have computed binding fire when array sub property changes?

Polymer 1.*
I am trying to refine the behavior of my computed binding with a array. I had <div hidden$="[[getState(uploadState.*)]]">FOO</div> but it was firing off to often.
I refined it to uploadState.value:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadState}}">
<div hidden$="[[getState(uploadState.value)]]">FOO</div>
With:
uploadState: {
type: Array,
value: function() {
var arr = Array.apply(null, Array(5));
var newArray = arr.map(()=> {
return {
value: false,
main: false,
edited: false,
loading: false
};
});
return newArray;
},
notify: true
},
attached: function() {
setTimeout(()=> this.set(`uploadState.0.value`, true), 1000)
}
but it does not fire off at all. How can I make it fire in the computed binding when the value property changes?
Also, how can I use this.get() to get the value when it changes? I tried var uploaded = this.get(['uploadState.value', 0]) in the computed binding getState but it just shows undefined(when it used to fire with the .*)
The problem with your usage of the binding uploadState.value is it doesn't exist. You are making an array of uploadState that have a member with the property value which looks more like uploadState.*.value but you don't really want to change on all the changes of value, just the one in question so you can take advantage of the item binding of dom-repeat so that your code would come out like so:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadState}}">
<div hidden$="[[item.value]]">FOO</div>
</template>
I might suggest you change up your naming convention and use uploadStates being it's an array and all, so that you can do:
<template is="dom-repeat"
initial-count="1"
index-as="index"
items="{{uploadStates}}"
as="uploadState">
<div hidden$="[[uploadState.value]]">FOO</div>
</template>

How to access content of dom-if inside a custom element?

In a custom element I want to access a span and append a child to it but all usual accessors give undefined:
<template>
<template is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
ready() {
var a = this.$.myspan; //<------- is undefined
var b = this.$$.myspan; //<------- is undefined
var c = document.getElementById("myspan"); //<------- is undefined
var d = this.$$("#myspan"); //<------- is undefined
}
How to access a span in this case?
UPDATE: here is plunk
The reason this didn't work inside the lifecycle callback without setTimeout or this.async is that right after attaching your element the dom-if template has not yet rendered. Upon attaching your element, Polymer calls the attached callback. However, when the value gets set on the the dom-if, an observer runs and debounces its own _render function. The debounce waits an amount of time to catch any other calls to it, and then it executes the ._render function and attaches the element to the DOM. In other words, when the attached callback runs, normally the dom-if template hasn't rendered yet.
The reason for this debounce is performance. If several changes were made within a very short span of time, this debounce prevents the template from rendering several times when the result we would care about is the end result.
Fortunately, dom-if provides a .render() method which allows you to make it render synchronously. All you need to do is add an id to your dom-if, switch to an attached callback and call like this:
<template>
<template id="someDomIf" is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
attached() {
this.$.someDomIf.render();
var c = document.getElementById("myspan"); //<------- should be defined
var d = this.$$("#myspan"); //<------- should be defined
}
Triggering a synchronous render on the dom-if shouldn't be a huge performance problem, since luckily your element should only be getting attached once.
Edit: As it turns it, this even works in a ready callback:
<template>
<template id="someDomIf" is="dom-if" if="[[condition]]" restamp>
<span id="myspan"></span>
</template>
</template>
ready() {
this.$.someDomIf.render();
var c = document.getElementById("myspan"); //<------- should be defined
var d = this.$$("#myspan"); //<------- should be defined
}
See this fork of your plunker:
http://plnkr.co/edit/u3richtnt4COpEfx1CSN?p=preview
Try to do it asynchronously in the attached method as follows, this method works:
attached: function(){
this.async(function(){
var d = this.$$("#myspan");
console.log(d);
},someTimeIfThereAreManyItemsToLoad);
}
The responses above only work if your condition is true initially. Please see my answer to your initial question that lead to this one :
https://stackoverflow.com/a/34137955/3085985
Not sure if you should mix in the .render-stuff from Dogs, but I still think the observer would be the right place for it as it otherwise does not work if condition is false initially.
As in Polymer documentation:
Note: Nodes created dynamically using data binding (including those in dom-repeat and dom-if templates) are not added to the this.$ hash. The hash includes only statically created local DOM nodes (that is, the nodes defined in the element’s outermost template).
You will need to use this.$$('#yourElementId");

Is it possible to set Polymer expressions dynamically?

I want to set filters dynamically. Is it possible?
dynamicFilter is a variable with name of the Polymer expression.
<template is="auto-binding">
<span>{{value | dynamicFilter}}</span>
</template>
AFAIK, there is no handy way to assign Filter to element in runtime. But there is a simple workaround you might find useful:
We are to define the staticFilter function, which would be a proxy (wrapper) to calls to dynamicFilters. Assuming dynamic filters to be instances of PolymerExpression, this might be put together as follows:
<polymer-element name="my-element" attributes="dynamicFilter">
<template>
<span>{{value | staticFilter(dynamicFilter)}}</span>
</template>
<script>
PolymerExpressions.prototype.uppercase = function(input) {
return input.toUpperCase();
},
PolymerExpressions.prototype.lowercase = function(input) {
return input.toLowerCase();
},
Polymer({
value: '¡Hola!',
dynamicFilter: null,
staticFilter: function(v, df) {
return df ? PolymerExpressions.prototype[df](v) : v;
}
});
</script>
</polymer-element>
<my-element></my-element>
<my-element dynamicFilter='uppercase'></my-element>
<my-element dynamicFilter='lowercase'></my-element>
Now you are free to set the dynamicFilter attribute of my-element even in runtime.
The reason is that filters are compiled and bound during element initialization; for security reasons there is no eval behind and therefore you cannot simply pass the arbitrary dynamic value there. On the other hand, filters are ready to receive parameters and that fact actually does the trick. BTW, you might even pass the function instance there whether you are not satisfied with PolymerExpressions for this purpose.
Your use case is not really clear, but you could use this.injectBoundHTML as a workaround.
When you need to change the filter dynamically, just reinject the content of the span

polymer template repeat field names

How can I dynamically get all the fields of this rows array objects using a polymer repeat template?
rows = [{
"field1":"test1",
"field2":"test2",
"field3":"test3",
"field4":"test4"
}]
<template repeat="{{data in rows}}">
{{data.field1}}
{{data.field2}}
{{data.field3}}
{{data...}}
</template>
This plunker shows two examples:
How to refresh a repeat using a filter parameter. Less overhead and
simple to implement. It's recommended to use event actions over
observers where possible,
How to observe an Object as an Array (based from this issue comment
Polymer/polymer-expressions#11 (comment))
<!-- parameters passed to filters are observed, so changing refresh updates the repeat -->
<template repeat="{{key in objectData | toKeys(refresh)}}">
{{ objectData[key] }}
</template>
Polymer({
refresh: 0, // update this value to refresh the repeat
toKeys: function(input) {
if (!input) return;
return Object.keys(input);
}
});
Dodgy request...
I tried and my best luck was something like this:
<polymer-element name="x-for-in">
<template>
<template repeat="{{field in keys}}">
{{obj[field]}}
</template>
</template>
<script>
Polymer({
obj : null,
keys : [],
created : function () {
this.obj = {
field1 : 1,
field2 : 2,
field3 : 3
};
this.keys = Object.keys(this.obj);
}
})
</script>
</polymer-element>
OUTPUT : 1,2,3
BTW: I'm not 100% sure what you meant in description, because your code says rows - is array of objects, while you're saying rows is an object....
Anyway so far so good, example above should give an idea.
BTW2: {{data is rows}} where rows is an object - will not work, because it's essentially a javascript for in. polymer restricts such things as I understand

Polymer custom element with template-as-content

In the code below, the content "Foo" of template#bar are always empty when I try to access it programatically or when inspecting the DOM in Chrome. Can someone explain why?
In general, how does one provide a template defined in an outer element to an inner element so the inner element can access the content and conditionally clone or import that content?
I am using polymer 0.4.2.
<polymer-element name="x-inner" noscript>
<!--
How can I access the content "Foo" of <template>Foo</template>,
So that I can import/clone it here?
Using <content> moves the template from x-outer to x-inner,
but the template's .content property is empty, instead of 'Foo' as I expected.
-->
<content></content>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<!--
How can I pass a template to a custom element?
I don't want the contents of this template to be rendered
here in x-outer, but instead conditionally rendered by x-inner
-->
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
<x-outer></x-outer>
This topic is potentially complicated, below is something to get you started.
(This is the third update to this answer, confirming the bit above about 'complicated' =P).
Polymer includes the TemplateBinding.js library.
The TemplateBinding.jslibrary imbues <template> with numerous features, including data-binding to models, conditional stamping, and replication/iteration via arrays. It also adds a feature whereby cloned nested templates do not replicate their own contents, preventing a possible explosion of useless nodes when iterating. Instead, TemplateBinding.js creates references in cloned-nested-templates to original content-ful templates. The upshot is that if you are using TemplateBinding.js you should use template.createInstance() API for best results.
Now, when using raw templates without TemplateBinding.js, you can stamp a template simply using var nodes = document.importNode(template.content, true). Of course, in this case you do not get the nested template replication optimization (which may or may not matter).
Note:
I removed the <content> node from the <x-inner>
template because it serves no purpose. The code below plucks the
template directly out of light-dom, and stamps the instance into the
shadow-root.
Declare x-inner before x-outer because the latter depends on the former.
Example code:
<x-outer></x-outer>
<polymer-element name="x-inner">
<template>
</template>
<script>
Polymer({
domReady: function() {
this.renderTemplate();
},
renderTemplate: function() {
// note: this only works if `template` is a true child of `this`
// (as opposed to projected)
var template = this.querySelector('template');
// use createInstance from TemplateBinding.js library API
this.shadowRoot.appendChild(template.createInstance());
/*
// this would work for raw templates, but Polymer includes TemplateBinding.js
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// if you don't know whether TemplateBinding.js exists or not,
// you could do something like this (see stampTemplate definition below)
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// this way uses the binding feature of TemplateBinding.js
template.setAttribute('bind', '');
template.model = { /* some data */ };
*/
}
});
// use best available API
function stampTemplate(template) {
if (template.createInstance) {
return template.createInstance();
} else {
return document.importNode(template.content, true);
}
}
</script>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
http://jsbin.com/nemaha/14/edit