Redistribute repeated slot elements in nested shadow DOM - html

Background: this question relates to development of an extension for recent versions of Chrome. It relies on javascript features such as HTML imports and custom elements that are not available on all browsers but that's OK for this case.
I'm trying to implement a HTML custom element simplified as follows:
<custom-el>
<span slot="head">Great</span>
<span slot="item">Item one</span>
<span slot="item">Item two</span>
<span slot="foot">done</span>
</custom-el>
I register the <custom-el>. Each time the element is created, my code's custom element class attaches a shadow root and appends to the shadow root content from the following template:
<template id="main">
<h1><slot name="head"></slot></h1>
<ul>
<slot name="item"></slot>
</ul>
<i><slot name="foot"></slot></i>
</template>
I would like to redistribute each <span> with attribute slot="item" to a secondary template responsible for rendering an individual item:
<template id="sub">
<li><slot name="item"></slot></li>
</template>
The number of slots with attribute name="item" is not fixed. It is generated from a database and changes regularly.
I understand that a slot can be redistributed by attaching a shadowRoot to the slot's parent element and setting the slot's slot attribute, e.g. <slot name="item" slot="newItem">. But I don't think this will work in my case since the sub template needs to wrap each item instance, not the list of items.
I could attach shadow roots and sub templates to each item in the main document. This would work but my preference is that the main template import and apply any nested shadowRoots and templates. This way, the primary document need only import the file containing the main template. The implementation of the component's details is encapsulated in the main template html file.
I could also use the slotchange event and the HTMLSlotElement.assignedNodes method to cobble together a scripting solution. But I'd rather not go that way.
Is there another approach? My actual use case involves a more complex HTML structure. Or maybe my architecture or understanding of web components is flawed?

The simplest approach, if possible is to add items with the <li> tag in the light DOM.
<span slot=item><li>Item 1</li></span>
<span slot=item><li>Item 2</li></span>
This way you don't need to use Javascript to generate the <li> tags.

Related

Angular 8 - Is it possible to perform data binding with a template loaded from an external source?

I have a page that allows users to customise a design before downloading it. I have a list of designs that when clicked on display a different HTML layout. These HTML layouts are stored in a database as a string of HTML. To display these, i use:
<div [innerHTML]="myDesignHTML | safeHtml"></div>
As super basic example of one of the HTML strings is the following:
<div id="wrapper">
<div id="content">
<div id="title">titleText</div>
</div>
<div id="footer">
<p>footerText</p>
</div>
</div>
As I can't perform data binding on the actual HTML template that's inserted, I find the IDs of the elements and replace the 'placeholder' text with real user data. The issue I'm having is that my page also needs the ability to change colours of elements on the page. I've partially achieved this by doing the following for example:
document.getElementById("content").style.backgroundColor = color;
However, this doesn't always correctly update the DOM and feels a bit sketchy. Is there anything within Angular that allows the same functionality as [ngStyle] but within dynamic HTML templates that are inserted through [innerHTML]? When the user wants to change the colour of the background, or the text, it would be great to have a variable in the component.ts that get's updated and for the HTML template to react like [style.border-top-color]="mainCOlor" or something of the sort?
It seems that you can use the Renderer2 (see https://angular.io/api/core/Renderer2) in Angular. You would want a template reference do your div, and then you would use the nativeElement property to pull the current template. This is a better way to interact directly with the HTML inside of a div.

Is it always valid to fill an HTML template slot with an element not permitted in the slot's parent?

In the simplest example of <template> and <slot> on MDN, they use this template:
<template id="my-paragraph">
<p><slot name="my-text">My default text</slot></p>
</template>
And later fill the slot like this:
<my-paragraph>
<ul slot="my-text">
<li>Let's have some different text!</li>
<li>In a list!</li>
</ul>
</my-paragraph>
This got my attention, because normally, <ul> is not allowed inside <p>. I went looking for clarification as to whether this was valid or advisable, and haven't found anything addressing it.
Is it valid and/or advisable to do this sort of thing? Are there any instances of non-permitted children that are also not permitted when they're only children indirectly via Shadow DOM?
My current best guess is that at least this example is completely fine. The validity issue with <ul> inside <p> is strictly to do with parsing HTML tags, not document structure; when you do
<p><ul></ul></p>
directly, the parser implicitly closes the paragraph when you start the list, and the validator's complaint is that the </p> doesn't actually have a <p> to go with. So the only actual question at play is whether these two constructions are separately fine:
<p><slot></slot></p>
<my-paragraph><ul></ul></my-paragraph>
…and of course they are. But I'd be interested to hear if there are any other element combinations in this category that do cause problems, or if there's spec language I've missed that addresses this situation.

What does it mean to set data-target attribute of a div to the id of that div?

I'm reading some code and there is a piece of html that reads:
<div id="uniqueId1234" data-target=".uniqueId1234">
...
</div>
and then earlier on in the same html file there is a span element that seems to use this div as a class:
<span class="uniqueId1234">
...
</span>
Can someone explain how this works? I thought that a class was something created in a css file. Sorry if this is a dumb question.
This is likely part of some piece of Javascript code or a library that listens for some type of change or event on your element with the data-target attribute.
When this event is triggered, it can then use the value of that attribute as a selector for performing some other logic as seen in this basic jQuery-based example below:
// When an element containing your data-target attribute is clicked
$('[data-target]').click(function(){
// Find the appropriate target (i.e. ".uniqueId1234")
var target = $(this).data('target');
// Then use it as a selector for some type of operation
$(target).toggle();
});
Classes are very common within CSS to style multiple elements, but they can also commonly be used as a mechanism in Javascript as well, which is likely the case in your scenario here.
What does it mean to set data-target attribute of a div to the id of that div?
Nothing standard. data-* attributes are designed to hold custom data for custom code (typically client side JS) to process.
I thought that a class was something created in a css file.
Classes are an HTML feature used to put elements into arbitrary groups. They are commonly used when writing CSS, but also client side JS and other code.

Should Angular Elements Be Treated As Blocks or Wrappers

When using element directives I have seen Angular element directives used in two ways:
1. As block level components
The element is styled as display:block, is the component and its children are the component's children, so the element itself is the component.
Use of directive:
<example class="Example"></example>
The directive's html:
<header class="Example-header"></header>
<img class="Example-image">
<footer class="Example-footer"></footer>
2. As an inline wrapper of the component
The element is left as display:inline and effectively acts as an invisible wrapper for the component itself:
Use of directive:
<example></example>
The directive's html:
<div class="Example">
<header class="Example-header"></header>
<img class="Example-image">
<footer class="Example-footer"></footer>
</div>
Obviously each have advantages and disadvantages for example extra markup, loss of layout context due to inline element etc, but is one the correct way? or is this another of Angular's vagaries?
I'm surprised no one responded, but the idea behind creating custom directives is the ability to create reusable pieces of code that fulfill a specific bit of functionality on your site.
This functionality, however, doesn't care about the styles that you are going to use. You can of course conditionally change classes and styles from within Angular, but the core component when dealing with the framework is the data.
With that being said, there is no "correct way" as you bolded in your question. Develop the directive to fit your needs and style of your site.
First this is probably opinion based but i'd really like to share my point of view about this.
If you really follow angular way of doing directives none of theses are a correct way.
Directives are intended to add a behavior to an HTML element.
The less the directive add HTML the best it is as it allow you to have a better control on this element.
Lets take an exemple.
We have a data grid (let say ui-grid, it doesn't really matter)
<ui-grid ...>
...
</ui-grid>
I had a the idea to add some button to add or remove element in the grid.
I first came up with this
<ui-grid ...>
...
</ui-grid>
<button ng-click="addItem()">Add</button>
<button ng-click="removeItem()">Remove</button>
I'm really happy with this and that's ok, but finally i need to use theses buttons in some other views. I'll have to duplicate the HTML, duplicate the JS and adapt it to the collection.
The common mistake
That's obviously not a good idea.
Instead i will do a directive. Lets say "button-list" : it produce the same html and behavior.
<ui-grid ...>
...
</ui-grid>
<button-list></button-list>
That's pretty cool.
Now i have some need. On one view i need the add button to be blue, on an other one i don't need to have a remove button, and on a last one i want the button text to be "X" and "+" (That's was some request by a client in a true story).
I could make a big object with a list of option and etc... but this is really painful and you need to touch the directive each time you need to add a custom different little behavior.
The good way to go
Now lets just think again about what i wanted to do.
I want the button to interact with the grid... and that's pretty much all. This is how we should go building a custom directive.
We could then produce this directive this way :
<div grid-button-container collection="mycollection">
<ui-grid ...>
...
</ui-grid>
<button grid-add-item>Add</button>
<button grid-remove-item>Remove</button>
</div>
So what do we have here ? We have three different directives.
grid-button-container : Its responsibility is to hold the list for the sub-directives.
grid-add-item : It add a function on click that add an element to the collection of grid-button-container.
grid-remove-item : It add a function on click that remove an element to the collection of grid-button-container.
Both grid-add-item and grid-remove-item will be requiring the grid-button-container to be used.
I cannot describe all the implementation of this (it would take too long) but i think this is how directives should be used. Such as almost no angular build-in directives (ng-*) add HTML and just add a behavior i think all the directives should be build in this way.
Pro :
You have a full control about your HTML
Directives are tiny and trivial
This is really re-usable
Cons :
Can be harder and longer to implement.
To make a final point, the two way you're asking about are just different. No one is better than the other it will just depend on your own HTML organisation and it will depend on the directive use.
Hope it helped.

Is there a way to safely hide HTML tags in AngularJS?

I'm recently starting to explore AngularJS, and of course, i know it is ran at the client side, and since SPA (Single Page Applications) are becoming more and more common, i have a question regarding how to safely hide HTML elements.
Let me give a simple example:
Employee
<div ng-show="canSeeSalary">
{{salary}}
</div>
Now, of course, at runtime the div tag related to the salary won't be displayed, however by seeing the HTML source code, or using a developer tool like the one we have in Chrome, it would be possible to see the tag and probably its value.
I know tags like these should be filtered at the the server-side, of course, but since it has come to the client side, the div will be there.
My question is exactly, if there is any way i could hide these divs from the HTML source code, without needing to mix AngularJS with JSTL, for example.
Thanks in advance.
Try ng-if directive:
<div ng-if="canSeeSalary">
{{salary}}
</div>
Corresponding div element will be removed from the DOM. From the official documentation:
The ngIf directive removes or recreates a portion of the DOM tree
based on an {expression}. If the expression assigned to ngIf evaluates
to a false value then the element is removed from the DOM, otherwise a
clone of the element is reinserted into the DOM.
Use
Employee
<div ng-if="canSeeSalary">
{{salary}}
</div>
ng-if completely removes and recreates the element in the DOM rather than changing its visibility via the display css property
I would recommend using ngCloak rather than ngIf.
The ngCloak directive is used to prevent the Angular html template from being briefly displayed by the browser in its raw (uncompiled) form while your application is loading. Use this directive to avoid the undesirable flicker effect caused by the html template display.
example:
<div ng-cloak> {{::test}} </div>
ngCloak # Official Angular Docs