Run a function once all children element are _actually_ updated - polymer

In lit-html we have the firstUpdated() method to run one-time initialisations once an element is rendered.
What if you need to run a function only once all children within that template are updated?
And what if your template includes native form elements and custom ones?
Right now I am doing a terrible:
firstUpdated () {
super.firstUpdated()
setTimeout(() => this.onceChildrenAreUpdated(), 100)
}
Surely there is a better way?
I realise it's tricky, because for lit-element "rendered" means that the DOM is done; it doesn't mean that all elements inside have done whichever initialisation then want to do.
But still...

You can wait for all the children to be updated:
async firstUpdated () {
const children = this.shadowRoot.querySelectorAll('*'));
await Promise.all(Array.from(children).map((c) => c.updateComplete));
this.onceChildrenAreUpdated();
}
Replace querySelectorAll('*') with something more specific if possible.

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!

Angular 4 Execute Function when html fully loaded

I have a problem with asynchronous HTTP calls in Angular 4 using typescript/components... I create an array of objects, and in the HTML I have checkboxes next to the objects. Now I want certain objects to be checked, by executing a function in angular. However when I do
(document.getElementById(id) as HTMLInputElement).checked = true;
In my component.ts.
It can't find the element however when I do the same code in a function that executes when you push a button it works. So the problem is that the HTML is not fully loaded when I execute the function. How can I make sure the HTML is fully loaded?
Yeah You shouldn't be manipulating the DOM.
Tag your HTML element in the html using hash.
<input ... #inputname />
Retrieved in the ts controller component.
#ViewChild('inputname') theinput;
Check after view init. ngAfterViewInit if it is checked
ngAfterViewInit() {
...
(this.form as HTMLInputElement).checked
...
}
Consider this as the last option since I wouldn't recommend direct DOM manipulation in Angular. But if you are still facing the issue, use can use my solution as a work around.
In constructor ,
let interval = setInterval(() => {
let flag = self.checkFunction();
if (flag)
clearInterval(interval);
}, 100)
Now create the function
checkFunction() {
if(document.getElementById(id)){
(document.getElementById(id) as HTMLInputElement).checked = true;
return true;
}
return false;
}

React upgrade: "this" visibility in getDefaultProps

I am upgrading some older react component I inherited (v0.10.0) to work with the latest version of react (v0.14.8).
The following scenario stopped working:
// within a react component
onClick: function() {
// DO SOMETHING
}
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: this.onClick
}
}
This is easily resolved moving the code into an anonymous function, like the following:
getDefaultProps: function () {
return {
someProp: 'prop',
onClick: function() {
//DO SOMETHING
}
}
}
My question is: why has the visibility of 'this' changed at that level and what's the best way to refactor this code? And what if I had-to/wanted-to use 'this' at that level?
Any help appreciated, as a disclaimer I am a react super-beginner!
The result of getDefaultProps() is shared across all instances of a component. That means that the result can't rely on any particular instance of the component. The reason it changed is likely because of the performance benefit from caching, although I can't say for sure.
As for refactoring the code, I'm not sure there's a silver-bullet here. From my perspective what you currently have seems like an anti-pattern. Props are meant to be passed in by consumers that have no knowledge of the inner workings of the component, so it seems odd that a default value for a prop would depend on the inner workings. Without knowing exactly what you're doing, I would say your best bet is to just use null as the default value for the prop, then check the value at runtime when you do have access to the this context.
handleSomeAction() {
if (!this.props.onClick) {
// DO SOMETHING
}
}

Cannot select elements inside "auto-binding" template

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');