inlining / lambda function in pure css - javascript

I would like to be able to write <div class="inlined">content</div> or so, and have it transformed into some other html, using the content, and as defined by inlined. I would like to do this in pure CSS ie., no javascript.
For instance, that would be a function inlined:
<div data-descr="content"/> -> <div class="container"><div class="topright">content</div></div>
With some implementation "like":
div[data-descr] {
content: <div class="container"><div class="topright">attr(data-descr)</div></div>;
}
If no pure CSS can be used, what would be a less/sass/js solution?

Since your data attribute includes HTML, you can't use the CSS content property for this because your HTML will be treated as a string - it won't get parsed.
You should also avoid adding HTML to data attributes anyway.
You can do it with JavaScript, though, via replaceWith.
Try creating an object for that markup you want to add. You can then reference that to get the HTML
// helper function to handle new element creation
const createNewElement = content => {
const container = document.createElement("div");
container.classList.add("container");
const inner = document.createElement("div");
inner.classList.add("top-right");
inner.innerHTML = dataObject[content];
container.append(inner);
return container;
}
// create an object to get the HTML for a specific describtion attribute
const dataObject = {
content: `<div class="red">red text</div>`,
content2: `<div class="green">green text</div>`,
content3: `<div class="blue">blue text</div>`,
}
// get the elements you want to change
const targetElements = document.querySelectorAll("[data-descr]");
// loop through them and swap them
targetElements.forEach(element => {
const content = element.dataset.descr;
const newElement = createNewElement(content)
element.replaceWith(newElement)
})
.red {
color: red;
}
.green {
color: green;
}
.blue {
color: blue
}
<div data-descr="content"></div>
<div data-descr="content2"></div>
<div data-descr="content3"></div>

As stated by #ErikMartino in this answer: https://stackoverflow.com/a/5865996/8106583
content doesn't support HTML, only text. You should probably use
javascript, jQuery or something like that.
If content did support HTML you could end up in an infinite loop where
content is added inside content.

Related

Combine :host() with :has() - not possible?

I have a web component with a shadow DOM and a default slot.
I need to apply certain styling based on the presence or absence of specific a light DOM descendant. Please note that I don't need a specific workaround for this specific styling, it's just an example and in the real world the example is alot more complex.
I also cannot work with regular DOM CSS like x-y:has(div) since I need to apply styles to an element in the shadow DOM based on the presence of the div in the light DOM.
Please note that the code snippet only works in browsers that support constructable stylesheets (e.g. Safari won't).
const styleStr = `
:host {
display: block;
border: 3px dotted red;
}
:host(:has(div)) {
border-color: green;
}
`;
let css;
try {
css = new CSSStyleSheet;
css.replaceSync(styleStr);
} catch(e) { console.error(e) }
customElements.define('x-y', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).adoptedStyleSheets.push(css);
this.shadowRoot.append(document.createElement('slot'))
}
})
<x-y>no div - should have red border</x-y>
<x-y>
<div>div, should have green border</div>
</x-y>
I was trying to find if maybe :host() is not accepting :has(), but was unable to find anything on it, neither in the spec, nor on MDN or caniuse.
Does anyone have definitive knowledge/reference about this, and can point me to some documentation?
You want to style slotted content based on an element inside the slot
Since <slot> are reflected, (deep dive: ::slotted CSS selector for nested children in shadowDOM slot)
you need to style a <slot> in its container element.
If you want that logic to be done from inside the Component,
you could do it from the slotchange Event, which checks if a slotted element contains that DIV
Then creates a <style> element in the container element
Disclaimer: Provided code is a Proof of Concept, not production ready
<my-component>
Hello Web Component
</my-component>
<!-- <my-component> will add a STYLE element here -->
<my-component>
<!-- <my-component> will assign a unique ID to the DIV -->
<div>Web Component with a DIV in the slot</div>
</my-component>
<script>
customElements.define("my-component", class extends HTMLElement {
constructor() {
super().attachShadow({mode: "open"}).innerHTML = `<slot/>`;
let slot = this.shadowRoot.querySelector("slot");
slot.addEventListener("slotchange", (evt) => {
[...slot.assignedNodes()].forEach(el => {
if (el.nodeName == "DIV") {
el.id = "unique" + new Date() / 1;
// inject a <style> before! <my-component>
this.before( Object.assign( document.createElement("STYLE"), {
innerHTML : `#${el.id} { background:lightgreen } `
}));
}
});
});
}
})
</script>
PS. Don't dynamically add any content inside <my-component>, because that slotchange will fire again...

binding textContent and overriding dom with svelte

I have a div with contenteditable=true and bind:textContent={value} so it behaves pretty much like a textarea.
The only issue I have with it is that I want to override the content of the div by processing the value, but seems like it is not possible.
To test I wrote this
<div contenteditable="true" bind:textContent={value}>testVal</div>
where value is an exported property of the component.
I kind of expected value to be set to testVal, but instead the div contains the value property.
I sort of understand why this is happening and that what I am doing is sort of an edge case, but is it at all possible to change this behaviour to kind of get a one way binding to value?
and I have tried my "normal" way of creating a one way binding (with some hacks to demonstrate issues):
<div contenteditable="true" on:input={e => value = e.target.textContent}>
{#each (value || "").split("") as part}
{part}
{/each}
</div>
this looks fine, but whenever I change type in the div my input gets multiplied, i.e. if I type e the div gets updated with ee. If I add another e I get eeee
I think the way to go is to use your "normal" way of creating a one way binding. Otherwise, using multiple ways of binding on the same element will conflict.
I used a combination of on:input like you described and, inside of the div, {#html html}
The following example formats each other word in bold as you type (there's some glitch when starting with an empty field):
<script>
import {tick} from "svelte";
let html = '<p>Write some text!</p>';
// for the implementation of the two functions below, see
// https://stackoverflow.com/a/13950376/4262276
let saveSelection = (containerEl) => { /**/ };
let restoreSelection = (containerEl, savedSel) => { /**/ };
let editor;
function handleInput(e){
const savedSelection = saveSelection(editor);
html = e.target.textContent
.split(" ")
.map((t, i) => i % 2 === 0
? `<span style="font-weight:bold">${t}</span>`
: t
)
.join(" ");
tick().then(() => {
restoreSelection(editor, savedSelection);
})
}
</script>
<div
bind:this={editor}
contenteditable="true"
on:input={handleInput}
>{#html html}</div>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>

How to get the inlines styles as JS object set via style attribute?

I would like to get the inlines styles set via style attribute as JS object. For e.g in following html I would like to fetch the #box div styles attribute value as {width: "300px" , color: "red" } (js object)
HTML
<div id="box" style="width: 300px; color: red">Box</div>
If I directly access the style object of the dom object, the object returned is CSSStyleDeclaration, which is not what I want:
var para = document.getElementById('box')
console.dir(box.style)
Also using window.getComputedStyle() is not an option because the target object is a clone of a dom element which has not been mounted yet.
Thanks
You can use getAttribute to get the string attribute, then split by ;s to turn it into the desired format:
const styleProps = Object.fromEntries(
box.getAttribute('style')
.split(/; +/)
.map(str => str.split(/: */))
);
console.log(styleProps);
<div id="box" style="width: 300px; color: red">Box</div>
Here's a different approach that will only give you valid values that the browser understands (instead of simply parsing a string.) This solution iterates over the style object of an element to build the object:
const para = document.getElementById('box')
const styleObj = Array.from(para.style).reduce((style, name) => {
style[name] = para.style[name]
return style
}, {})
The problem with parsing a string and splitting is that any invalid values will also be used. So the example below will give you back { width: 'foobar' } which is clearly invalid.
<div id="box" style="width: foobar">Box</div>

Q: Add Javascript(By Link To HTML File) To Custom Element With DOM

I have a problem with adding javascript to handle event for a custom element . I defined a custom element in a javascript file called menu.js, by adding this element to DOM directly, like the code below:
customElements.define("custom-menu", class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<header class="header">
<div class="header__menu">
<div class="header__menu_bar"></div>
<div class="header__menu_bar"></div>
<div class="header__menu_bar"></div>
</div>
</header>
<div class="modal2">
// some code HTML
</div>
<div class="modal__details">
// some code HTML
</div>`;
}
});
const header__menu = document.querySelector(".header__menu");
header__menu.addEventListener("click", function () {
document.getElementsByClassName("modal2")[0].style.width = "100%";
$("body").addClass("stop-scrolling");
$("body").removeClass("enable-scrolling");
if (document.body.offsetWidth <= "640") {
document.getElementsByClassName("modal-content")[0].style.width = "100%";
} else {
document.getElementsByClassName("modal-content")[0].style.width = "290px";
}
document.getElementsByClassName("modal-content")[0].style.right = "0";
});
// some code Javascript handle class "modal2" and "modal__details"
I use connectedCallback() function to call everytime custom element is inserted into the DOM. If I add Javascipt code directly like the code above, I have succeeded to add click event to the div with class "header__menu" and handle both the modals. Now I want to put the Javascipt code after customElements.defined(...); to another Javascipt file and link this file to file HTML using this element to do the same task but it doesn't work as when I add directly. Can someone tell me the reason?
Thank you!
You have to make sure the js code get called after the custom element is created.
For example:
$(function () {
const header__menu = document.querySelector(".header__menu");
header__menu.addEventListener("click", function () {
document.getElementsByClassName("modal2")[0].style.width = "100%";
$("body").addClass("stop-scrolling");
$("body").removeClass("enable-scrolling");
if (document.body.offsetWidth <= "640") {
document.getElementsByClassName("modal-content")[0].style.width = "100%";
} else {
document.getElementsByClassName("modal-content")[0].style.width = "290px";
}
document.getElementsByClassName("modal-content")[0].style.right = "0";
});
});
When I style the custom element, I put css code to another file then link to HTML file like I usually work with HTML tag, class or id. This work well, and when I inspect by F12, I can see all html codes of custom element and styles of them.
For example, this is style of the div with class name modal__details:
.modal__details {
position: fixed;
top: 0;
right: 0;
z-index: 2;
width: 0;
height: 100%;
transition: 0.4s;
background-color: black;
}
I don't need to specify anything. It works like any other div. It means my custom element is in HTML DOM, but I just can handle it by put Javascript directly like above, it doesn't work in other JS file. I have also tried the way you instruct, but it has failed. So it's my wondering about that.

Function Return HTML

I have function who return html
renderSuggestion(suggestion) {
const query = this.query;
if (suggestion.name === "hotels") {
const image = suggestion.item;
return this.$createElement('div', image.title);
} else {
let str = suggestion.item.name;
let substr = query;
return this.$createElement('div', str.replace(substr, `<b>${substr}</b>`));
}
},
But<b> element not render in browser as html element. Its display like string...
How I display this <b> element?
Tnx
That is because when you provide a string as the second argument of createElement, VueJS actually inserts the string as a text node (hence your HTML tags will appear as-is). What you want is actually to use a data object as the second argument, which give you finer control over the properties of the created element:
this.$createElement('div', {
domProps: {
innerHHTML: str.replace(substr, `<b>${substr}</b>`)
}
});
Of course, when you are using innerHTML, use it with caution and never insert user-provided HTML, to avoid XSS attacks.
You can also create a component and use v-html to render the output.
Declare props for your inputs:
export default {
props: {
suggestion: Object,
query: String
}
};
And use a template that uses your logic in the template part
<template>
<div class="hello">
<div v-if="suggestion.name === 'hotels'">{{suggestion.item.title}}</div>
<div v-else>
<div v-html="suggestion.item.name.replace(this.query, `<b>${this.query}</b>`)"/>
</div>
</div>
</template>
This allows for greater flexibility when using more complex layouts.
A working example here
Provide more detail(possibly a picture) of how it's not showing. Consider using a custom CSS class to see the div and what's happening to it.
bold {
border-style: black;
font-weight: bold;
}
then just use the "bold" class instead of "b".

Categories

Resources