LitElement appending custom elements using button - javascript

I have a custom LitElement and inside it, I need to have a button that will dynamically append new custom elements (preferably using TemplateResult objects generated by html function) inside a container:
import { LitElement, html, render } from "lit";
import { customElement } from "lit/decorators.js";
#customElement('example-custom-component')
class CustomComponent extends LitElement {
render() {
return html`
<div id="custom-el-container">
</div>
<button #click=${this.appendNewCustomEl}>click me!</button>
`;
}
appendNewCustomEl() {
const templateToAppend = html`
<another-custom-component>
some other things added here
</another-custom-component>
`;
render(templateToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
}
As you can see above I tried to achieve it by using the render function, but instead of appending it at the end of the container, I'm simply overwriting the content of the div. What am I doing wrong? What's the best approach to achieve those results? Please help.
EDIT:
New example for my question from comments about click events:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
<button #click=${this.functionFromCustomComponent}>click me!</button>
</another-custom-component>
`)
}

If you really want to do it with lit-html and your container's content is purely what you are dynamically rendering on each button click, (i.e. not server side rendered content) or you are using lit-html v2 then you could have a list and track what you have rendered. something like:
items=[];
appendNewCustomEl() {
this.items.push(null);
const templatesToAppend = this.items.map(() => html`
<another-custom-component>
some other things added here
</another-custom-component>
`);
render(templatesToAppend, this.shadowRoot?.querySelector('#custom-el-container'));
}
in general what lit-html is good at and tries to achieve is an optimal re-render of markup when only parts are changed. not necessary a template engine alternative to handlebars, mustache and co.
In your example, you don't need it and could do it simply without lit-html:
appendNewCustomEl() {
this.shadowRoot!.querySelector('#custom-el-container').insertAdjacentHTML("beforeend", `
<another-custom-component>
some other things added here
</another-custom-component>
`)
}

I've slightly modified your example code to be more idiomatic Lit. Instead of using a querySelector and insertAdjacentHTML, it is preferred to render the array of templates declaratively. This has the added benefit that your functionFromCustomComponent event bindings "just work".
I've written the full example in a Lit playground.
The list of templates are stored in a reactive property, this.templates that schedules an efficient update whenever the list is updated.
Then the this.appendNewCustomEl method can push new templates to the list, and an automatic efficient render happens automatically.
this.templates is rendered into the #custom-el-container div using an expression:
<div id="custom-el-container">
${this.templates}
</div>
See Lit expressions documentation for other examples.
In my linked example, the list of templates turn blue when clicked to demonstrate the event working.
Not necessary but related, Lit.dev has a tutorial all about Working with lists that goes much more in depth if you're curious.

Related

Prioritise element hierarchy in Quill / Parchment

When using React Quill I am trying to keep the elements in a specific order so that we can can handle clicks on the annoations accordingly (along with things like hover effects etc)
Currently, we highlight text in strings. This is currently how Quill will format the HTML.
[abc]this could [def][abc]be one[/abc][/def] of the strings[/abc]
The problem with this format is that hover/click's on [def] cause issues because it's a child of [abc], the [abc] will trigger the click/hover listeners.
I would like quill to instead format as HTML like this:
[abc]this could [/abc][def][abc]be one[/abc][/def][abc] of the strings[/abc]
As you can see, [def] is now an equal level whilst still possessing [abc] as a child.
I've tried using quill to register extensions of block and inline components as shown here: But unfortunately, neither have provided the solution that I would like. Maybe i'm trying to achieve this the wrong way. I also have a different SuggestionBlock for each type of highlight (Same content inside) so the content removed.
class SuggestionBlock extends Block {
static create(suggestion) {
let node = super.create()
// Add the suggestion attributes.
node.setAttribute("data-id", suggestion.id)
node.classList.add("highlight")
node.classList.add(suggestion.name)
return node
}
static formats(domNode) {
const id = domNode.getAttribute("data-id")
return id
}
}
Any ideas on how I could implement this would be incredible! Thank you.

Instantiate Svelte Component in HTML5 Custom Tag (without Shadow DOM)

I'm building a Svelte component library to be consumed using JavaScript only. At a later stage also by other Svelte applications as an additional option.
I want to avoid Svelte's custom element feature, since there are limitations with true Web Components and its Shadow DOM.
Currently the component is instantiated like this:
<div id="my-component"></div>
<script>
document.addEventListener("DOMContentLoaded", function (event) {
new MySvelteComponent({
target: document.getElementById("my-component"),
props: {
firstProp: true,
secondProp: "some value"
}
});
});
</script>
Now I would like to provide a more elegant way by defining a HTLM5 custom tag like:
<my-component firstProp="true" secondProp="some value">
What is a good way to implement this?
found it: github/svelte-tag npm/svelte-tag
see regarding issue on github near to the bottom is the answer from chrisward.
It makes a custom element but without shadow DOM. Found it today and workes for me.

Angular template not rendering

Right now I am creating a library (my-custom-library) and a project in which we'll use that library (called my-Project)
The requirement is, that within my-project I have to use my-custom-library, extended with templates, like this (my-project's app.component.html):
<my-custom-library>
<ng-template #myTemplate>
<div>Some static content for now</div>
</ng-template>
</my-custom-library>
The reason for this is, that in my-custom-library they want to have template-able components, where the template is given from the outside (in this case from my-project).
Within my-custom-library I'm supposed to access the given template(s) and pass them to the corresponding components. This I'm trying to achieve (my-custom-project's app.component.ts)
#ContentChild("myTemplate") myTemplateRef?: TemplateRef<any>;
(my-custom-project's app.component.html)
<ng-container [ngTemplateOutlet]="myTemplateRef"></ng-container>
My problem is, that the contentChild is always empty, the template never renders. The structure itself I think is working, since when I'm moving this same structure within just one project and use it there everything works fine, the contentChild gets its value and "my template" is rendered.
One more information, I don't know if its useful but my-custom-library is created like this (my-custom-library's app.module.ts):
export class AppModule {
constructor(private injector: Injector) {
const customElement = createCustomElement(AppComponent, { injector: this.injector });
customElements.define('my-custom-library', customElement);
}
}
What could cause this issue? Is it even possible to achieve this?
I had the same issue, Apparently ngTemplateOutlet does not work with angular elements. but you can try content projection without ngTemplateOutlet and it works fine.
e.g
<my-custom-library>
<div placeholder1></div>
</my-custom-library>
and you can define placeholder1 within your angular element (my-custom-library)
e.g
<div>
/*your angular my-custom-library code here */
/* the content you want to inject from outside of your angular elements*/
<ng-content select="[placeholder1]"></ng-content>
</div>
Note: you can also do nesting of your angular elements as well using this, but with this approach you have to make sure that ng-content is not affect by any ngif condition, because your angular element can project the content from outside but it can not regenerate projection based on your conditions. those conditions should be added from where you are projecting content.

Recreating HTML/CSS Behavior with Styled-Components

I have a fairly annoying problem using Styled-Components in a React app. The class names of the HTML elements are very important, as is the dynamism of styled-components (via props passing). So it's important that both are used.
tl;dr I want to use a classname in CSS (and not HTML) without having to create an empty component or something.
I have a class with a dynamic classname, let's stay "style-123", where "123" is dynamically changed via state and is important for parity with the current setup. (IMO, the dynamic nature of the classname is preventing a solution.) The "style-123" classname is used in other HTML elements, but it is not a component itself so should not show up as an HTML element.
I have a component ("ComponentA") that has a classname of "component-a". This component's HTML should looking like the following:
<div class="component-a">
HELLO
</div>
and its Styles (in Dev Tools) should look like this:
.style-123 .component-a {
margin-left: -2%;
}
where we now see the class ("style-123") added. This should be the final result.
The general structure of the HTML is along the lines of:
<div class="name-whatever style-123 another-classname">
<div>
<div class="component-a">
</div>
</div>
</div>
Problem
I do not seem to be able to isolate "component-a" as an HTML classname for "ComponentA" while applying CSS as .style-123 .component-a via styled-components. In order for the HTML to have an isolated classname, the component (JSX) would need to be:
<ComponentA className={'component-a'}/>
and the styled component:
const ComponentA = styled.div`
&.component-a {
color: red;
}
`
This is the only way I know how to link-up a component in JSX with its styled-component counterpart.
I cannot do:
<ComponentA className={'component-a'}/>
and the styled component:
const ComponentA = styled.div`
&.style-123 .component-a {
color: red;
}
`
as this won't apply the styling since there's a classname-mismatch.
I've tried many combinations of SASS, all the ampersands and arrows and pluses and etc., over the last few hours. I hope this is feasible via styled-components as I need to declare classnames and have classnames dynamic as well as the CSS attributes and values, all passed via props. If there is another solution that doesn't use styled-components but maintains this dynamism, I am open to that.
Thanks for the time.

Adding custom elements to aurelia view

Is it possible to load custom elements dynamically?
Let's say my viewModel looks like this:
export class MyViewModel {
attached() {
$(".content").prepend("<card></card>");
$(".content").prepend("<otherCard></otherCard>");
}
}
Why is aurelia not rendering my "cards". Is there any way i can achive similar behaviour?
Adding those cards directly in HTML works great, but i need a more modular approach.
Thanks
Check out the <compose view-model="card" /> element in the docs. It should give you what you're looking for.

Categories