Cannot select elements inside "auto-binding" template - polymer

I have created some custom elements, now I'm writing tests for them.
I wanted to use "auto-binding" because I have plenty of attributes that needs to be bound among my elements.
Unfortunately I cannot query any element inside the template.
Here is some code.
<template id="root" is="auto-binding">
<dalilak-data-service id="dds" regions="{{regions}}"></dalilak-data-service>
<dalilak-regions-handler id="drh" regions="{{regions}}" flattendedRegions="{{flattendRegions}}" descendantsRegionNames="{{descendantsRegionNames}}" regionsByNameId="{{regionsByNameId}}"></dalilak-regions-handler>
</template>
In the test script I have tried the following
drh = document.querySelector('#drh');
suite('dalilak-regions-handler', function() {
test('handler initialized', function() {
assert.ok(drh);
});
});
Also tried this:
drh = document.querySelector('* /deep/ #drh'); // or '#root /deep/ #drh'
suite('dalilak-regions-handler', function() {
test('handler initialized', function() {
assert.ok(drh);
});
});
But none of them worked.
Note without the template I can query my custom elements.

auto-binding templates stamp asynchronously, I expect your problem is that you need to wait for the template to stamp before querying for elements.
The template fires a template-bound event when this happens, so you can use code like this:
addEventListener('template-bound', function() {
drh = document.querySelector('#drh');
...
});
Of course, this means your testing infrastructure will need to understand how to handle asynchrony, which can be a concern.

Where possible, it is best to avoid the /deep/ selector. That is a nuclear option and can return unexpected results because it pierces all shadow DOMs. It also won't work for your auto-binding template because its contents are inside a #document-fragment, not a #shadow-root. Instead, try querying the #document-fragment itself. This preferable because you are limiting your query to the scope of your template, which is much more precise.
var template = document.querySelector('#root');
var drh = template.content.querySelector('#drh');

Related

Conditionally adding tags options parameter to select2

I have multiple elements on a page that are triggering a load of select2 to the element. I'm trying to conditionally check if the element has a certain class, and if so add the tag option; otherwise do not. I thought something like this would work, but it's not:
$('.element_to_add_select_two_on').select2({
tags:function(element) {
return (element.className === 'classname_i_am_targeting');
},
});
What am I missing here? I'm subjecting myself to the following buffoonery to get this to target and load:
$('.element_to_add_select_two_on').each((index,element) => {
let showTags = false;
if ($(element).attr('class').split(' ').includes('classname_i_am_targeting')) {
showTags = true;
}
$(element).select2({
tags:showTags,
});
});
There are a few problems with your first attempt. First, you are defining tags as a function when what you want is the result of the function, since tags needs to be defined as a boolean true or false. The other is that inside your .select2() call, you do not have access to the calling element $('.element_to_add_select_two_on') in the way that you think. It isn't an event that you are listening on, it's a function call that wants an object passed with its configuration.
You conveyed that your second method works, but it can be simplified with the jQuery hasClass() function:
$('.element_to_add_select_two_on').each((index, element) => {
$(element).select2({
tags: $(element).hasClass('classname_i_am_targeting'),
});
});
There is a much simpler way to do all of this, however, and it is much more flexible and already built into select2 via the way of data-* attributes (note, you need jQuery > 1.x). You can simply add data-tags="true" to any of your select elements with which you want tags enabled. These will override any configuration options used when initializing select2 as well as any defaults:
<select data-tags="true">
...
</select>

Data Bindings across template tags

I'm wondering, is there a possibility to have databindings "out of" a template? Say I have a <template/>-Tag somewhere which I put into the slot of a different component - that component stamps it to its context. Then I want to bind data from the root element to the <template/>-Tag. Also, event bindings (on-x-changed) don't work, because you can't assign a function which is defined in the hosting component. Any ideas?
Example:
... host
{{boundData}}
<binding-component>
<template>
{{boundData}}
</template>
</binding-component>
I don't see changes when I observe boundData in the hosting component. Is there a way to get around this? Or is firing a custom event my only chance?
If you are looking for binding a property outside of polymer something like from index.html you may bind value with element. an example ; index.html
<dom-bind>
<template>
<binding-component bound-data="{{boundData}}"></binding-component>
</template>
</dom-bind>
<script>
// set a value a string, Number or Object etc.
// Optionally wrap this code into a listener ie;
// window.addEventListener('load', e=> { ...below code ... })
var boundData= document.querySelector('dom-bind');
boundData = {} //
</script>
Now in your binding-component element has a property as boundData
hope its helps or provide more code to understand better.
I've made it work the way dom-if does it, too. Like in dom-if (reference), I'm creating a Templatize-instance which then uses forwardHostProp to handle the "inside"-properties
this.__ctor = Templatize.templatize(template, this, {
mutableData: true,
forwardHostProp(prop, value) {
// handling item updates, item being the only property
// from within the binding component
// everything else is automatically bound by templatize
this.set(prop, value);
this.update(this.item);
},
});
this.__instance = new this.__ctor();
this.root.appendChild(this.__instance.root);
This all happens in connectedCallback.
Because the Templatize-instance is passed this, it's bound to the current context as well.
Good luck!

Stamping Template Async? Race condition issue

Polymer 1.7
Is stamping a template a async operation? It's not I/O so I don't know why it would be. However, there is a race condition here where this.$$('#uploadedImage1') is undefined. It works if I use <div hidden="[[foo]]"> instead of template is="dom-if", so this is how I know it is a race condition.
<template is="dom-if" if="[[uploadedImage1]]">
<div id="uploadedImage1" class="row-image horizontal layout">
</div>
</template>
...
ready: function(e) {
function readURL(e) {
var el = e.target.id
var uploadedID = e.target.dataset.uploaded;
var file = Polymer.dom(e).localTarget.files[0];
var reader = new FileReader();
reader.onloadend = ()=> {
this[uploadedID] = true;
//RACE CONDITION HERE
this.$$('#uploadedImage1').style.backgroundImage = `url(${reader.result})`;
}
reader.readAsDataURL(file);
}
this.$['images-container'].addEventListener('change', readURL.bind(this), false)
}
Short answer: yes, template stamping is async. Here, you could use an attribute binding to style to apply the background image directly, so you don't need to manipulate the stamped DOM.
If you really need to get at the stamped DOM, you can either a) wait for a dom-change event, or b) force a synchronous render by calling render. (The latter is usually an anti-pattern--you don't want to force something to happen synchronously if you don't have to.)
https://www.polymer-project.org/1.0/docs/devguide/templates#synchronous-renders
https://www.polymer-project.org/1.0/docs/devguide/templates#dom-change
Note the docs as written may imply that only dom-repeat is rendered async, but render and dom-change are available on both elements.
Hope that helps.

Adding properties for a Polymer element to observe based on content or a better way to handle forms

I need to create a form using the Polymer Paper-Input elements, and I need a way to know when all required content has been filled out.
I looked for a built in element, but didn't see one. So I wanted to create a polymer form element that would wrap all of the input tags. The resulting element would have an Invalid attribute which lets you know if any of the input tags are invalid.
The use of the tag would look like this:
<test-form id="testform">
<paper-input label="test" required error="This field is required"></paper-input>
</test-form>
Invalid: {{ $.testform.invalid }}
However, it appears that by the time in the elements lifecycle that I can loop over all the elements inside of the content tag, that anything added to the observe object is ignored.
Here is the code I was working on below:
<polymer-element name="test-form" attributes="invalid">
<template>
<content id="content">
</content>
</template>
<script>
Polymer('test-form', {
domReady: function () {
this.observe = {};
for (var i = 0; i < this.children.length; i++) {
this.observe["this.children[" + i + "].invalid"] = "valChanged";
}
},
invalid: false,
valChanged: function (oldValue, newValue) {
// TODO: If newValue is true set invalid to true
// If newValue is false, loop over all elements to see if all are now valid and invalid can be set to false.
alert("VALUE CHANGED" + oldValue + newValue);
}
});
</script>
Is there a better way to handle this or does anyone know how to make changes to what polymer is observing at this point in the lifecycle?
As far as checking the form's validity, you could simply check each form element's invalid property:
validate: function() {
var invalid = false;
Array.prototype.forEach.call(this.children, function(child) {
if (child.invalid === true) {
invalid = true;
}
});
this.invalid = invalid;
}
Then you could add an input event listener and run this method each time a form element's input changes.
Here's a working jsbin.
If I understand your question, your high level goal is form validation?
As has been detailed in polycasts and other places, I have used iron-form which has some very powerful validate() functionality, including what you mention above and much more.
It does sometimes require some odd usages of hidden <input> fields to get all of the work done, but this is easy to learn in the polycasts, such as polycast 55 and 56
If you stumbled upon this question in 2017, you would definitely now want to use more primitive tech, after you've seen what this has to offer.

Accessing an attribute on a child that's set with a data binding

I have a little bit of markup that looks like this:
<polymer-element name="blog-post" noscript>
<template>
<mark-down>
<textarea value="{{post}}"></textarea>
</mark-down>
<polymer-localstorage name="my-blog-editor" value="{{post}}">
</polymer-localstorage>
</template>
</polymer-element>
I'd like the mark-down tag to be able to see the value of the textarea but I'm having a hard time knowing when to query for it.
Inside of mark-down my code looks something like this:
attached: function() {
this.textarea = this.$.textareaContent.getDistributedNodes()[0]; // this grabs the textarea element
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
// pass in the target node, as well as the observer options
observer.observe(this.textarea, { attributes: true });
}
Unfortunately the mutation observer never fires. I've tried checking for the value of textarea directly in attached and domReady but it's always null. The only success I've had is to use a setTimeout to check for the value asynchronously.
textarea is specifically difficult to use in this manner, because you cannot observe it's content changing with MutationObservers, only with events.
Worse still, there is no signal at all for setting textarea.value (which is what your binding will do).
If you want to use textarea like this, suggest you expose a property on the mark-down element for binding a value, and listen to events from the textarea to monitor user edits.
<mark-down value="{{post}}">
<textarea></textarea>
</mark-down>