Polymer: passing attributes from container component to an item component - dom

I have 2 components:
x-container and x-item.
they have a hierarchy similar to < table > and < tr > or to < tr > and < td >...
Hence x-item components are only effective when they're inside an x-container component:
<x-container>
<x-item></x-item>
</x-container>
I want to pass an attribute value from x-container to x-item:
<x-container att="value">
<x-item></x-item>
</x-container>
In this case I need value to be visible to x-item - is it possible?
Thanks!

It works out of the box if you know the structure of x-container beforehand (JSBin):
<polymer-element name="x-container" attributes="value">
<template>
<x-item value="{{value}}"></x-item>
</template>
<script>
Polymer('x-container', {
value : null,
ready: function() {
}
});
</script>
</polymer-element>
<polymer-element name="x-item" attributes="value">
<template>
<div>x-item value:{{value}}</div>
</template>
<script>
Polymer('x-item', {
});
</script>
</polymer-element>
<x-container value="test"></x-container>
If you want to dynamically create the relationship of x-container and x-item refer to these SO threads:
Data-binding between nested polymer elements
Using template defined in light dom inside a Polymer element
What is the best way to implement declarative collection rendering with Polymer?

Related

Is it possible in vue3 to access the root DOM element in a child component slot? I am trying to use a 3rd party library (sortablejs) in vue3

In vue2 I could use this.$el
export default {
render() {
return this.$slots.default[0]
},
mounted() {
Sortable.create(this.$el, {});
})
}
If, in vue3 I try to use this.$slots.default()[0] I can't see how to target the element.
If I use a template ref, I can get the div, but not the contained slot.
The closest question / answer I have found is here Vue 3 Composition API - How to get the component element ($el) on which component is mounted
but this also seems to give the div, but not the slot $el.
This was extremely powerful in vue2 because sortable could be passed a ul, or a div, or another constructed sortable vue component in a slot, and work without the element having to be defined in the child component and I can't work out how to replicate this in vue3.
I originally came across this in a screen cast by Adam Wathan: "Building a Sortable Component with Vue.js", but this was vue2.
I've come up with the following (perhaps there are better out there)
Use template ref:
<template>
<div ref="root">
<slot></slot>
</div>
</template>
Then in the script:
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// the DOM element will be assigned to the ref after initial render
// console.log(root.value.children[0]) // this is your $el
let el = root.value.children[0]
Sortable.create(el, {})
})
return {
root
}
}
}

How can I make StencilJS component to render without component tag itself with TSX?

While I understand this is probably a terrible practice, I need to build StencilJS component such that inside render(), I don't want to render component tag itself due to already existing style guide and it expect DOM to be constructed in certain way. Here is what I'm trying to achieve - component code (from HTML or within another component):
<tab-header-list>
<tab-header label="tab 1"></tab-header>
<tab-header label="tab 2"></tab-header>
</tab-header-list>
when rendered, I want generated DOM to be something like:
<tab-header-list>
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
</tab-header-list>
so inside tab-header-list render() function, I'm doing
return (
<ul>
<slot/>
</ul>
);
and I can do this inside tab-header render() function
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
to get what I want but how can I do this with TSX? (for simplicity sake, above code is really simple but what I really need to build is lot more complicated li tag with events etc so I would like to use TSX)
Tried to store DOM to variable but I'm not sure how I can assign it as this.el (outerHTML seem to be only way I can come up with, but I feel there must be better way)
#Element() el: HTMLElement;
#Prop() label: string;
render() {
var tabheaderDOM = (<li>{this.label}</li>);
// how can I assign above DOM to this.el somehow?
//this.el.outerHTML = ?
}
I appreciate any help I can get - thanks in advance for your time!
Unfortunately, you can't use custom elements without tags, but there is a workaround for it:
You can use Host element as reference to the result tag.
render () {
return (
<Host>....</Host>
)
}
Then in your stylesheet you can set the display property for it:
:host {
display: contents;
}
display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself
Beware: it doesn't work in IE, opera mini... https://caniuse.com/#feat=css-display-contents
UPD:
If you are not using the shadowDOM then you need to replace :host by the tag name like:
tab-header {
display: contents;
}
Functional components might be able to help you achieve this. They are merely syntactic sugar for a function that returns a TSX element, so they are completely different to normal Stencil components. The main difference is that they don't compile to web components, and therefore only work within TSX. But they also don't result in an extra DOM node because they simply return the template that the function returns.
Let's take your example:
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
you could write it as a functional component:
import { FunctionalComponent } from '#stencil/core';
interface ListItemProps {
label: string;
}
export const ListItem: FunctionalComponent<ListItemProps> = ({ label }) => (
<li>{label}</li>
);
and then you can use it like
import { ListItem } from './ListItem';
#Component({ tag: 'my-comp' })
export class MyComp {
render() {
return (
<ul>
<ListItem label="tab 1" />
<ListItem label="tab 2" />
</ul>
);
}
}
Which will render as
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
Instead of a label prop you could also write your functional component to accept the label as a child instead:
export const ListItem: FunctionalComponent = (_, children) => (
<li>{children}</li>
);
and use it like
<ListItem>tab 1</ListItem>
BTW Host is actually a functional component. To find out more about functional components (and there limitations), see https://stenciljs.com/docs/functional-components.

How to select an element inside a dom-module in polymer 1.0?

I have the following dom-module that I am trying to create interactions for.
<dom-module is="bw-image-upload">
<template>
<vaadin-upload id="uploader"
target="{{ API_URL}}/images/upload"
method="POST"
max-files="1"
max-file-size="200000"
accept="image/*"
upload-success="uploadResponseHandler"
file-reject="errorHandler"
>
</vaadin-upload>
</template>
<script>
Polymer({
is: 'bw-image-upload',
properties: {
image: String,
notify: true
}
});
var uploader = document.querySelector('#uploader');
uploader.addEventListener('upload-before', function(event) {
console.log(event);
});
</script>
</dom-module>
I want to select the vaadin-upload element by it's ID but it returns a null and I am confused on why it is returning null.
How do I select an element like this in Polymer?
If the element has an id and is statically added to the template, you can use
var uploader = this.$.uploader;
to get a reference to an element with the id uploader.
If the element is inside <template is="dom-if">, <template is="dom-repeate"> or otherwise dynamically created this is not supported.
In such cases you can use
var uploader = this.$$('#uploader');
this.$$(...) provides full CSS selector support and returns the first matching element, while this.$... only supports IDs.

ReactJS dealing with server-side loaded DOM

I'm just about to experiment with reactjs I'm new to it. Is reactjs capabile to add a component to existing DOM.
So I have already server-side created DOM and on the flow reactjs should add a component which should render inside body but keep what already is there.
<script type="text/jsx">
var VisualEditor = React.createClass({
render: function() {
return (
<div>Hello, world!</div>
);
}
})
var initVisualEditor = <VisualEditor params={true} />
React.render(initVisualEditor , document.body);
</script>
this one is removing everything inside body and is returning
<div>Hello, world!</div>
React won't add a new div.
The render function is where the component will be rendered and replaces the current code already there.
I recommend you make a div with an id in the template and then render to that in React.render.
Template:
<body><div id="app"></div></body>
React:
React.render(
<Component />,
document.getElementById('app')
)
It's also bad practice to render to the body.

Callback for when a child dom element is inserted or has its class changed?

In Ember.js, I have a view that has
{{#if obj.property}}
<div {{bindAttr class="prop"}}>content</div>
{{/if}}
How can I get called back for when this element is inserted into the view, and for when the class is attached onto the element? I want to do this because the CSS class is an animation class, and I'd like to hook onto the onAnimationEnd event of the element so that I get notified when the animation ends.
How about changing the div to be a custom view subclass that implements didInsertElement? e.g.
{{#if obj.property}}
{{view App.MyView}}
{{/if}}
and
App.MyView = Ember.View.extend({
classNameBindings: "prop",
didInsertElement: function() {
// use this.$() to get a jQuery handle for the element and do what you'd like
}
})
In addition to Luke's answer, I found out another way to achieve this, which may be preferable since creating a view is required for Luke's approach.
By exploiting the fact that DOM events bubble up, I can setup an event handler for animationEnd on a parent DOM element that contains whatever may be inserted. E.g.
<div id="container">
{{#if obj.property}}
<div {{bindAttr class="prop"}}>content</div>
{{/if}}
</div>
// view.js
didInsertElment: function() {
this.$('#container').bind('webkitAnimationEnd', function(e) {
// e.target is the element whose animation ended.
}
}