Polymer 2.x - Shadow DOM - <slot> - javascript

I'm trying to use the slot API in this example:
<tabs-s>
<tab-c>
<tab-c>
</tabs>
where tabs-s is the component that wraps other components.Inside it I'm using the tag to render its dom but if I want the assigned nodes I also get the whitespaces (text nodes).
Is there a manner to avoid getting the text nodes when calling assignedNodes() method? This was not happening in Polymer 1.x
Thanks

Let say you want to create a featured section to present new items
the section needs to have some basic information and change colors.
The element will take the title, count and class from his parent
<featured-section class="blue">
<span slot="count">3</span>
<h1 slot="title">The title of the element go here</h1>
</featured-section>
Inside the element featured-section
<dom-module id="featured-section">
<template>
<section>
<div class="vertical-section-container">
<div class="circle-container">
<div class="circle">
<slot name="count"></slot>
</div>
</div>
<slot name="title"></slot>
<feature-box></feature-box>
<feature-grid></feature-grid>
</div>
</section>
</template>
But who is in charge of the class detail? The element itself featured-section
<custom-style>
<style include="shared-styles">
:host {
display: block;
background-color: var(--my-section-color);
}
:host(.blue) {
--my-section-color: #03A9F4;
}
:host(.green) {
--my-section-color: #8BC34A;
}
:host(.pink) {
--my-section-color: #FF6EB6;
}
::slotted(h1) {
color: #fff;
padding-bottom: 1.5em;
line-height: 48px;
}
</style>
</custom-style>

Related

Making text appear on hover changes div height

I am building a site and one of the components requires different text to be displayed in the same spot when you hover over different blocks. I am using jQuery to accomplish this and changing the html, however I am noticing since the text is different sizes it pushes down the div to allocate more room for the text.
Is it possible to keep the text transparent or something so the colour and html is changed at the same time, to give the illusion it is popping in?
Please see code below:
$(".stats-text-1").hover(
function() {
$(".stats-text").html(
"Our client’s monetary milestones are driven by our social tactics and digital marketing."
);
},
function() {
$(".stats-text").html(" ");
}
);
$(".stats-text-2").hover(
function() {
$(".stats-text").html(
"Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses."
);
},
function() {
$(".stats-text").html(" ");
}
);
$(".stats-text-3").hover(
function() {
$(".stats-text").html(
"Our clients currently see a minimum average of 5.4 times return on ad spend."
);
},
function() {
$(".stats-text").html(" ");
}
);
.stats-1 {
font-size: 12vw;
font-weight: bold;
color: rgba(255, 255, 255, 0.83);
}
.stats-2 {
font-size: 2vw;
font-weight: bold;
color: #f2f2f2;
}
.stats-3 {
font-size: 2vw;
color: rgba(255, 255, 255, 0.6);
}
.stats-text {
padding-top: 1rem;
font-size: 2vw;
text-align: left;
color: #fff6f4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="row">
<div class="col-1"> </div>
<div class="col-11 card-2-title">BEEN THERE, DONE THAT.</div>
<div class="card-2-title-mobile">BEEN THERE, DONE THAT.</div>
</div>
</div>
<div class="row stats-border">
<div class="col-1"> </div>
<div class="col-3 stats stats-text-1">
<h1 class="stats-1" style="text-align: center">11</h1>
<h2 class="stats-2" style="text-align: center">Million</h2>
<h3 class="stats-3" style="text-align: center">
Revenue Generated
</h3>
</div>
<div class="col-spec24"> </div>
<div class="col-3 stats stats-text-2">
<h1 class="stats-1" style="text-align: center">9</h1>
<h2 class="stats-2" style="text-align: center">Years</h2>
<h3 class="stats-3" style="text-align: center">
In The Making
</h3>
</div>
<div class="col-spec24"> </div>
<div class="col-3 stats stats-text-3">
<h1 class="stats-1" style="text-align: center">6</h1>
<h2 class="stats-2" style="text-align: center">Times</h2>
<h3 class="stats-3" style="text-align: center">
Return On Ad Spend
</h3>
</div>
<div class="col-1"> </div>
</div>
<div class="card-2-desktop" style="padding-bottom: 15vw">
<div class="row">
<div class="col-1"> </div>
<div class="col-7 stats-text" id="statsText"></div>
<div class="col-4"> </div>
</div>
</div>
The way I've adressed the problem is based on using CSS Grid to create the initial size of the element in which you're showing your messages, and also adding the messages to the HTML, rather than replacing text.
While you could – of course – establish the sizes of the elements by positioning the content off-screen to inform the necessary sizing, and then animate to those dimensions before showing the message on-screen, that's more work than feels necessary.
My suggested approach is below, with explanatory comments in the code itself:
// using the '.stats' selector to obtain a jQuery Object containing
// all of the elements with that class-name in the document,
// we then use the attr() method to set the custom data-index attribute
// for later use:
$('.stats').attr('data-index', function(i) {
return i + 1;
// rather than the hover() method we use the on() method instead to handle
// both 'mouseenter' and 'mouseleave' events, and we pass the Event Object,
// as 'evt', into the anonymous function:
}).on('mouseenter mouseleave', function(evt) {
// here we use jQuery's data() method to retrieve the value of the
// data-index custom-attribute:
let index = $(this).data('index');
// here we retrieve the .message element which has the same
// data-index attribute and attribute-value, which is also within
// a .marketing element:
$(`.marketing .message[data-index="${index}"]`)
// we then use the toggleClass() method to add, or remove,
// the 'visible' class to the relevant .message element
// depending on whether the assessment returns true or false;
// if the evt.type is exactly-equal to 'mouseenter' the
// assessment returns Boolean true, and the class is added;
// otherwise Boolean false is returned and the class is
// removed:
.toggleClass('visible', evt.type === 'mouseenter');
});
$('input').on('input', function(){
$('main').css('--textSize',`${$(this).val()}rem`)
}).change();
/* a basic CSS reset to ensure that all elements
are sized in similar ways: */
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
font-weight: normal;
line-height: 1.5;
margin: 0;
padding: 0;
}
/* defining CSS Grid as the layout of
the <main> element: */
main {
display: grid;
/* defining three equal-width columns, each
of one fractional-unit of the available
space: */
grid-template-columns: repeat(3, 1fr);
margin: 0.5em auto;
width: 90vw;
}
/* I removed the inline <style> attribute from the
various elements, since it made the HTML noisier
than I'd like (adjust to taste of course): */
.stats > :is(h1, h2, h3) {
text-align: center;
}
/* I assumed that the messages should be full-width,
so here I defined the .marketing element should
start in the first track and end in the last: */
.marketing {
grid-column: 1 / -1;
}
/* again, using CSS Grid for the element that holds the
marketing messages: */
.marketing > div {
display: grid;
/* defining a single named area in which the marketing
claims should appear: */
grid-template-areas: "claims";
}
.marketing > div > .message {
/* here we position all of the .message elements into
the same grid area; which allows the largest grid-item
to define the size of that grid area: */
grid-area: claims;
/* effectively hiding the elements, and centring the text: */
opacity: 0;
pointer-events: none;
text-align: center;
user-select: none;
z-index: -1;
}
/* this is the 'background' element against which the .message
will be displayed, this can be easily adjusted or the
.message elements themselves can have their own background: */
.marketing > div > .mask {
background: linear-gradient(135deg, lime, #ffaf);
grid-area: claims;
}
/* when the 'visible' class-name is added to the .message elements
this CSS promotes their visibility, by raising their opacity to
1 (fully visible), raising their z-index above the background
and re-enabling pointer events and user-selection: */
.marketing > div > .message.visible {
opacity: 1;
pointer-events: auto;
user-select: auto;
z-index: 2;
}
.message:nth-child(2) {
font-size: var(--textSize, inherit);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- this is absolutely irrelevant to the demo, but does demonstrate how the
grid size automatically adjusts to the size of the largest element -->
<label>Adjust text-size of the second <code>message</code> element to:
<input type="number"
min="0.5"
max="20"
step="0.5"
value="1" /></label>
<!-- using the <main> element as a wrapping block for the posted content; -->
<main>
<!-- I removed the 'stats-text-n' class-name, since that would seem
to be more use as an id (given its role in uniquely identifying
a specific element, and also because that makes your code
inherently non-reusable; whereas each element has a 'stats' class-
name which allows us to generalise the JavaScript -->
<div class="col-3 stats">
<h1 class="stats-1">11</h1>
<h2 class="stats-2">Million</h2>
<h3 class="stats-3">Revenue Generated</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">9</h1>
<h2 class="stats-2">Years</h2>
<h3 class="stats-3">In The Making</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">6</h1>
<h2 class="stats-2">Times</h2>
<h3 class="stats-3">Return On Ad Spend</h3>
</div>
<!-- here I added the 'marketing' class-name, since the 'card-2-desktop' seems
as though it may be a product of a framework -->
<div class="card-2-desktop marketing">
<div class="row">
<!-- these messages were taken from your jQuery code, and placed inside of
the '.row' element, along with a custom data-* attribute which indicates
which of the '.stats' elements it refers to: -->
<div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
<div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
<div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
<!-- an element to act as the background of the other elements, this is
entirely optional and largely irrelevant -->
<div class="mask"></div>
</div>
</div>
</main>
JS Fiddle demo.
Of course, anything that can be accomplished in jQuery can also be achieved in native JavaScript; again, explanatory notes are in the comments of the code below:
// we use Array.from() to convert the NodeList returned by
// document.querySelectorAll() into an Array, in order to
// use Array methods later:
const messages = Array.from(
// here we retrieve all .message elements within a .marketing
// element:
document.querySelectorAll('.marketing .message')
),
// defining the toggle function, using Arrow syntax, and passing
// the Event Object ('evt') into the function:
toggle = (evt) => {
// we use 'currentTarget' property of the Event Object to find
// the element to which the event-handler was bound, as opposed
// to the 'target' property which simply returns the element
// upon which the event was initially fired; from that element
// we retrieve the data-index attribute-value:
let index = evt.currentTarget.dataset.index,
// here we filter the Array of .message elements to find the
// element(s) matching the the supplied filter, using an
// Arrow function to pass the current Array-element into
// the function body:
message = messages.filter(
// here we're looking to retain elements whose data-index
// attribute-value matches that of the .stats element
// upon which the event-handler was triggered:
(msg) => msg.dataset.index === index
);
// Array.prototype.filter() returns an Array, so here we use
// Array.prototype.forEach() to iterate through that Array:
message.forEach(
// here we toggle the 'visible' class-name on the retained
// .message elements, if the Event-type (evt.type) is exactly
// equal to 'mouseenter' the assessment returns Boolean true,
// and the class-name is added; otherwise Boolean false is
// returned and the class-name is removed (this generates no
// error if the class-name addition or removal would match
// the existing state):
(msg) => msg.classList.toggle('visible', evt.type === 'mouseenter')
);
};
// here we retrieve all elements matching the supplied CSS selector,
// and use NodeList.prototype.forEach() to iterate over that NoseList:
document.querySelectorAll('.stats').forEach(
// here we pass in a reference to the current Node of the NodeList
// (stat) and the index of that Node in the NodeList (i):
(stat, i) => {
// here we set the data-index attribute to be equal to the index
// plus 1 (to match the 1-based index in the HTML attributes I
// added):
stat.dataset.index = i + 1;
// and then bind the toggle() function - note the deliberate
// omission of the parentheses in the below code - as the
// event-handler for both the 'mouseenter' and 'mouseeout'
// events:
stat.addEventListener('mouseenter', toggle);
stat.addEventListener('mouseleave', toggle);
});
// again, largely irrelevant to the demo but demonstrates how the font-size
// determines the grid-area size to avoid size jumps between 'empty' and
// 'populated':
document.querySelector('input').addEventListener('input', (evt) =>
document.querySelectorAll('.marketing')
.forEach(
(el) => el.style.setProperty(
'--textSize',
`${evt.currentTarget.value}rem`)
)
);
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
font-weight: normal;
line-height: 1.5;
margin: 0;
padding: 0;
}
main {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0.5em auto;
width: 90vw;
}
.stats > :is(h1, h2, h3) {
text-align: center;
}
.marketing {
grid-column: 1 / -1;
}
.marketing > div {
display: grid;
grid-template-areas: "claims";
}
.marketing > div > .message {
grid-area: claims;
opacity: 0;
pointer-events: none;
text-align: center;
user-select: none;
z-index: -1;
}
.marketing > div > .mask {
background: linear-gradient(135deg, lime, #ffaf);
grid-area: claims;
}
.marketing > div > .message.visible {
opacity: 1;
pointer-events: auto;
user-select: auto;
z-index: 2;
}
.message:nth-child(2) {
font-size: var(--textSize, inherit);
}
<label>Adjust text-size of the second <code>message</code> element to:
<input type="number" min="0.5" max="20" step="0.5" value="1" /></label>
<main>
<div class="col-3 stats">
<h1 class="stats-1">11</h1>
<h2 class="stats-2">Million</h2>
<h3 class="stats-3">Revenue Generated</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">9</h1>
<h2 class="stats-2">Years</h2>
<h3 class="stats-3">In The Making</h3>
</div>
<div class="col-3 stats">
<h1 class="stats-1">6</h1>
<h2 class="stats-2">Times</h2>
<h3 class="stats-3">Return On Ad Spend</h3>
</div>
<div class="card-2-desktop marketing">
<div class="row">
<div class="message" data-index="1">Our client’s monetary milestones are driven by our social tactics and digital marketing.</div>
<div class="message" data-index="2">Our experience is from more than just a couple of wins - it’s from learning through years of wins and losses.</div>
<div class="message" data-index="3">Our clients currently see a minimum average of 5.4 times return on ad spend.</div>
<div class="mask"></div>
</div>
</div>
</main>
JS Fiddle demo.
References:
CSS:
Attribute-selectors.
:is().
JavaScript:
Arrow functions.
Array.from().
Array.prototype.filter().
document.querySelectorAll().
Element.classList API.
EventTarget.addEventListener().
HTMLElement.dataset.
NodeList.prototype.forEach().
Template literals.
jQuery:
attr().
change().
data().
on().
toggleClass().
val().

Insert wrapper around div without an ID or class

I'm trying to insert a wrapper around two divs, one with a dynamically generated ID. The div with the random ID is dynamically generated. No matter what I try, the wrapper is getting inserted after the target div though.
Before wrapper
<div id="search">Search</div>
<div id="234234">Unknown</div>
<div id="list">List</div>
After wrapper
<div id="search">Search</div>
<div id="wrapper">
<div id="234234">Unknown</div>
<div id="list">List</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="search">Search</div>
<div id="wrapper">
<div id="234234">Unknown</div>
<div id="list">List</div>
</div>
EDIT
I have decided to use CSS to reposition the elements so that I no longer need the wrapper.
Simply wrap the children!
$("#wrapper").children().wrapAll("<div class='wrapper'/>")
#wrapper { padding: 20px; }
.wrapper { background-color: gold; outline: 2px solid red; }
<div id="wrapper">
<div id="123456">has whatever ID</div>
<div id="list">has id</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Or, to target elements that actually have any ID attribute use the "[id]" selector:
$("#wrapper").children("[id]").wrapAll("<div class='wrapper'/>")
#wrapper { padding: 20px; }
.wrapper { background-color: gold; outline: 2px solid red; }
<div id="wrapper">
<div id="list123456">has id</div>
<div id="list">has id</div>
<div>Foo bar</div>
<div id="list234">has id</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
Before Edit:
Use the :not([id]) or / and [id=""] selectors if you want to target "no ID attribute" / or / "empty ID attribute" respectively:
$("#wrapper").children("div:not([id]), div[id='']").wrap("<div class='wrapper'/>")
#wrapper { padding: 20px; }
.wrapper { background-color: gold; outline: 2px solid red; }
<div id="wrapper">
<div id="">has no id</div>
<div>has no attribute id</div>
<div id="list">has id</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
If you do not know the element id, you can still use a class attribute to access it. Give the element with the random id (and any other elements you want to move) a class attribute so you can access via JavaScript and manipulate the DOM. You can do it simply with no jQuery, like this:
// Create the `div.wrapper` element
const wrapperEl = document.createElement('div');
wrapperEl.classList.add('wrapper');
// Create a reference to each element you want to make a child of `div.wrapper`
const childEls = document.querySelectorAll('.wrapped');
// Move the elements from `body` to `div.wrapper` parent
wrapperEl.replaceChildren(...childEls);
// Append the `div.wrapper` to the body element
const bodyEl = document.querySelector('body')
bodyEl.appendChild(wrapperEl);
console.log(wrapperEl.outerHTML);
<div id="search">Search</div>
<div id="234234" class="wrapped">Unknown</div>
<div id="list" class="wrapped">List</div>
I've logged the wrapperEl HTML in the console, but you can also inspect the HTML in your dev tools "Elements" tab to see the wrapper.
Reference: https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/replaceChildren#transferring_nodes_between_parents

How to target a component in svelte with css?

How would I do something like this:
<style>
Nested {
color: blue;
}
</style>
<Nested />
i.e. How do I apply a style to a component from its parent?
You need to pass props to the parent component with export let, then tie those props to class or style in the child component.
You can either put a style tag on the element in the child you want to style dynamically and use a variable you export for the parent to determine the value of a style directly, then assign the color on the tag like this:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested color="green"/>
<!-- in Nested.svelte -->
<script>
export let color;
</script>
<p style="color: {color}">
Yes this will work
</p>
Upside here is flexibility if you only have one or two styles to adjust, downside is that you won't be able to adjust multiple CSS properties from a single prop.
or
You can still use the :global selector but just add a specific ref to the element being styled in the child like so:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested ref="green"/>
<style>
:global([ref=green]) {
background: green;
color: white;
padding: 5px;
border-radius: .5rem;
}
</style>
<!-- in Nested.svelte -->
<script>
export let ref;
</script>
<p {ref}>
Yes this will work also
</p>
This ensures global only affects the exact ref element inside the child it's intended for and not any other classes or native elements. You can see it in action at this REPL link
The only way I can think of is with an additional div element.
App.svelte
<script>
import Nested from './Nested.svelte'
</script>
<style>
div :global(.style-in-parent) {
color: green;
}
</style>
<div>
<Nested />
</div>
Nested.svelte
<div class="style-in-parent">
Colored based on parent style
</div>
Multiple Nested elements
You could even allow the class name to be dynamic and allow for different colors if you use multiple Nested components. Here's a link to a working example.
You could use inline styles and $$props...
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested style="background: green; color: white; padding: 10px; text-align: center; font-weight: bold" />
<!-- in Nested.svelte -->
<script>
let stylish=$$props.style
</script>
<div style={stylish}>
Hello World
</div>
REPL
using :global(*) is the simplest solution.
No need to specify a class in the child if you want to style all immediate children for example
In the parent component:
<style>
div > :global(*) {
color: blue;
}
<style>
<div>
<Nested />
<div>
Nested will be blue.
I take a look and found nothing relevant (maybe here), so here is an alternative by adding <div> around your custom component.
<style>
.Nested {
color: blue;
}
</style>
<div class="Nested">
<Nested />
</div>
Maybe you will found something but this one works.
The way I do it is like this:
<style lang="stylus">
section
// section styles
:global(img)
// image styles
</style>
This generates css selectors like section.svelte-15ht3eh img that only affects the children img tag of the section tag.
No classes or tricks involved there.

Find element that has no direct children with specific class

In a page of HTML elements, I am trying to find a parent element with class .dmc that does not contain a direct child element with a specific class .dynamic, using JQuery.
I have tried:
$('.dmc:not(:has(.dynamic))')
However this checks all descendants, and I only want to check the first level of descendants.
I think there is a very simple answer, but I can't quite come up with it. Any help appreciated!
Since :has is already jQuery-specific, you could use an unrooted >, which jQuery's Sizzle selector engine seems to support:
$(".dmc:not(:has(> .dynamic))").addClass("green");
// --------------^
$(".dmc:not(:has(> .dynamic))").addClass("green");
.green {
color: green;
font-weight: bold;
}
<div class="dmc">1</div>
<div class="dmc">2
<div class="dynamic">dynamic</div>
</div>
<div class="dmc">3</div>
<div class="dmc">4
<div class="dynamic">dynamic</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
But I'd be slightly worried about it, and would probably go for filter instead:
$(".dmc").filter(function() {
return $(this).children(".dynamic").length == 0;
}).addClass("green");
$(".dmc").filter(function() {
return $(this).children(".dynamic").length == 0;
}).addClass("green");
.green {
color: green;
font-weight: bold;
}
<div class="dmc">1</div>
<div class="dmc">2
<div class="dynamic">dynamic</div>
</div>
<div class="dmc">3</div>
<div class="dmc">4
<div class="dynamic">dynamic</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

CSS maintain original styles on elements added by a JS library

I have a web application with a lot of <div> used for layout. Now I need to add some tables drown by a library. The issue is that the library creates a lot of <div> with their own style and depending on the position they collide with styles already in place for <div> in that position.
This is the (very) simplified html structure where [myTableDataSource] identify the element with the table.
<div id="id1">
<div>
<div myTableDataSource="xxx"></div>
</div>
<div id="id2">
<div myTableDataSource="yyy"></div>
</div>
<div id="id3">
<div>
<div>
<div myTableDataSource="zzz"></div>
</div>
</div>
</div>
</div>
My idea is to avoid applying style on <div> that are descendant of [myTableDataSource]but... How can I do? Is there a selector to get all <div> element not descendand of a [myTableDataSource] attribute?
Please consider that I have a style for all <div> descendant of #id1, of #id2, #id3 [...] and I can't change this, but only modify selector to avoid conflicts.
What about using attributes and the .not() selector?
Something like this is a start:
#id1 div:not([myTableDataSource="xxx"]) {
background: orange;
padding: 30px;
width: 200px;
height: 200px;
}
div[myTableDataSource="xxx"] {
width: 100px;
height: 100px;
background: grey;
}
To ignore descendants (not tested):
#id1 div:not([myTableDataSource="xxx"]):not(div[myTableDataSource="xxx"] div){ ... }

Categories