When inserting DOM element from JavaScript, CSS styles are not displayed - javascript

I want to display a log with a title and a content.
With the following code, li elements are inserted in the DOM with the right className but styles are not displayed.
This is inside an Angular Component, which I think may be the origin of the error. It's just a basic new app with html: <app-example></app-example>
Note: if I insert an element by hand in the html it is displayed correctly.
The only difference I notice is that li elements inserted from javascript do not have _ngcontent-cxr-c40.
Html:
<ul class="d-flex f-column log-list" id="log-list">
<li id="log-item" class="log-message">
<span class="log-message-title">TEST</span>
</li>
</ul>
This is the function to add an element to the log:
private addLogElement(title: string, message: string): void {
const newNode = document.createElement('li');
newNode.className = 'log-message';
const newNodeTitle = document.createElement('span');
newNodeTitle.className = 'log-message-title';
const headerText = document.createTextNode(title);
newNodeTitle.appendChild(headerText);
newNode.appendChild(newNodeTitle);
const parentDiv = document.getElementById('log-list');
const childDiv = document.getElementById('log-item');
parentDiv.insertBefore(newNode, childDiv);
}

You should use Renderer2 API for such DOM Manipulations.
In Component class in constructor inject it like :-
constructor(public renderer: Renderer2) {}
Then change your method to :-
private addLogElement(title: string, message: string): void {
const newNode = this.renderer.createElement('li');
this.renderer.addClass(newNode, 'log-message');
const newNodeTitle = this.renderer.createElement('span');
this.renderer.addClass(newNodeTitle, 'log-message-title');
const headerText = this.renderer.createText(title);
this.renderer.appendChild(newNodeTitle, headerText);
this.renderer.appendChild(newNode, newNodeTitle);
const parentDiv = this.renderer.selectRootElement(document.getElementById('log-list'), true);
const childDiv = this.renderer.selectRootElement(document.getElementById('log-item'), true);
this.renderer.insertBefore(parentDiv, newNode, childDiv);
}
There are other reasons why you should use Renderer2 instead of native DOM manipulation.
You can refer to those reasons here :- https://medium.com/dev-genius/dont-use-native-dom-manipulations-in-angular-6c8db13f463f

Add this in your css/scss
I have added sample styles
:host ::ng-deep .log-message{
color: red;
}
:host ::ng-deep .log-message-title{
background-color: #eeee33
}

Related

Adding a custom class to blockquote in QuillJS

I'm trying to figure out how to add a custom class when the user clicks the blockquote toolbar button. At the moment, when blockquote is clicked, the element is created as so:
<blockquote class="ql-align-justify">this is my quoted text</blockquote>
I would like to add .blockquote to the class as so:
<blockquote class="ql-align-justify blockquote">this is my quoted text</blockquote>
I'm currently looking at adding a handler, but there doesn't appear to be much documentation on how this works:
this.editor = new Quill(this.$refs.editor, this.editorOptions)
// Handlers can also be added post initialization
var toolbar = this.editor.getModule('toolbar');
toolbar.addHandler('blockquote', function(value) {
//todo: working on adding the blockquote class to blockquotes.
console.log('blockquote called');
console.log('value:');
console.log(value);
if (value) {
this.quill.format('blockquote');
}
});
I don't think handlers can do this (as far as I know)
But you can extend the BlockQuote format, by doing so, you will have full control of the node, you can add classes, or even click handler.
The format that is responsible for blockquote is:
const BlockQuote = Quill.import('formats/blockquote');
So simply you can do:
const BlockQuote = Quill.import('formats/blockquote');
class CustomBlockQuote extends BlockQuote {
static create(value) {
const node = super.create(value);
node.classList.add('test');
return node;
}
}
And you can update the tag name and the blot name like:
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
Or even simpler for only changing the class:
class CustomBlockQuote extends BlockQuote {}
CustomBlockQuote.blotName = 'custom-blockquote';
CustomBlockQuote.tagName = 'blockquote';
CustomBlockQuote.className = 'custom-class-name';
Quill.register(CustomBlockQuote, true);
I have just created a jsfiddle as an example, I hope it works for you:
https://jsfiddle.net/hassansalem/095mh3fn/4/

HTML web component does not use shadow DOM style

I have created a vanilla web component or HTML element. It just displays two links.
To encapsulate the thing, I use shadow DOM. However it does not seem to be encapsulated. In the DOM tree it's inside #shadow-root which is good.
Why does the web component use the global style instead of the style I provided in the template for my web component?
The text is red and I expected it to be green.
class MyEl extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
const template = `
<style>
a {
color: green;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = template;
}
}
window.customElements.define("my-el", MyEl);
a {
color: red
}
<my-el>
Item1
Item2
</my-el>
While this question already has an accepted answer, moving a slot's children to the shadowRoot isn't desirable for most use cases.
What you probably want to do is to use the ::slotted() selector.
Just bear in mind that styles applied to a slot's children through the ::slotted() selector only act as "default" styles and can still be overridden by using styles in light DOM.
For example, check this edited version of your snippet:
As you can see, this time my-el tries to apply both a color and a text-decoration style to anchor (<a>) children in any of it's slots.
However, in light dom, we have a a.special selector that overrides the color, so the <a class="special"> will be red, not green
class MyEl extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: "open" });
}
connectedCallback() {
const template = `
<style>
::slotted(a) {
color: green;
text-decoration: none;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = template;
}
}
window.customElements.define("my-el", MyEl);
a.special {
color: red
}
<my-el>
Item1
<a class="special" href="example.com">Item2</a>
</my-el>
The full, detailed explanation is in: ::slotted CSS selector for nested children in shadowDOM slot
TL;DR
Your links are in lightDOM and thus styled by its DOM (in your code the document DOM)
Moving the nodes from lightDOM to shadowDOM is one "solution"; but you are not using slots then.
FYI, your code can be compacted to:
class MyEl extends HTMLElement {
constructor() {
super().attachShadow({ mode: "open" })
.innerHTML = `<style>a{color:green}</style><slot></slot>`;
}
}
window.customElements.define("my-el", MyEl);
More SLOT related answers can be found with StackOverflow Search: Custom Elements SLOTs
observe this line, you have to move/copy elements to shadow for example with:
this.shadow.innerHTML = this.innerHTML + template;
I've added this to demonstrate that only inline style will be applied to shadow dom elements .. so copied links in SD are using your style :)
so red will be GLOBAL, green will be SHADOW elements
class MyEl extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.shadow = this.attachShadow({ mode: "open" });
const template = `
<style>
a {
color: green;
}
</style>
<slot></slot>`;
this.shadow.innerHTML = this.innerHTML + template;
}
}
window.customElements.define("my-el", MyEl);
a {
color: red
}
<my-el>
Item1
Item2
</my-el>

Angular Dynamic Component Issue

I make a dynamic component in one of my components and it was made and here it's in the html I place it in the (ng-template) :
<div type="text" class="form-control" contenteditable="true" name="phrase" (input)="triggerChange($event)">
<ng-container #container></ng-container>
</div>
Code of triggerChange :
triggerChange(event) {
let newText = event.target.innerText;
this.updateLineAndParentLineAndCreateComponent(newText);
}
Which made what the function says literally update the line with the new text and update the parent component with this changes and also make the on the fly component
Code for create Component :
compileTemplate(line: any) {
// console.log(line[4]);
let metadata = {
selector: `runtime-component-sample`,
template: line[4]
};
let factory = this.createComponentFactorySync(this.compiler, metadata);
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
this.componentRef = this.container.createComponent(factory);
let instance = <DynamicComponent>this.componentRef.instance;
instance.line = line;
instance.selectPhrase.subscribe(this.selectPhrase.bind(this));
}
private createComponentFactorySync(compiler: Compiler, metadata: Component, componentClass?: any): ComponentFactory<any> {
let cmpClass;
let decoratedCmp;
if (componentClass) {
cmpClass = componentClass;
decoratedCmp = Component(metadata)(cmpClass);
} else {
#Component(metadata)
class RuntimeComponent {
#Input() line: any;
#Output() selectPhrase: EventEmitter<any> = new EventEmitter<any>();
showEntities(lineIndex, entityIndex) {
this.selectPhrase.emit(entityIndex);
}
};
decoratedCmp = RuntimeComponent;
}
#NgModule({ imports: [CommonModule], declarations: [decoratedCmp] })
class RuntimeComponentModule { }
let module: ModuleWithComponentFactories<any> = compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
return module.componentFactories.find(f => f.componentType === decoratedCmp);
}
and I display a text inside theis div based on the data I calculate and it's a string with html tags like that:
Data My name is foo
I trigger the blur event of the div that is contenteditable and I see the changes and based on that I generate a new string with new spans and render it again the same div
the problem comes when I delete all the text from the contenteditable div the component removed from the dom and can't be reinstantiated again even if I try to type again in the field but it just type inside the div not the created component
how I can solve this problem and can generate the component when the user delete all text from field and try to type again ?
Here is a stackblitz for the project :
https://stackblitz.com/edit/angular-dynamic-stack?file=src%2Fapp%2Fapp.component.ts
I found the solution is by handling keystrokes in the contenteditable div especially the DEL , BackSpace Strokes so when the input is empty and the stroke is one of them you just create a new component , It still has problems that dynamic components is not appearing when have it's empty or have only a tag but that's the workaround that I came up with untill now

Best way to consume javascript data blocks with react or web components?

I will be working with legacy software built with Microsoft Behaviors, and Data Islands, both no longer supported since IE 10. The middle tier transforms xml data into "data-blocks" (JavaScript can use the content of a element as a data block if the src attribute is omitted..). Unfortunately, there's a gazillion of these data blocks so a full-rewrite of the middle-tier or establishing a serverless architecture to interface with the backend is out of the question. A clean solution is eluding me, can anyone think of a way to reuse these existing data-blocks with a modern framework such as react, angular, vue, stencil (custom elements via web components)?
You could use the DOMParser to parse the content of your custom <script> Data Block into a XML document.
Since it is standard Javascript, you can use it in the framework of your choice.
Below is an example with a vanilla Custom Element that I called <xml-renderer>:
class XMLRenderer extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
.innerHTML = `<style>
div { background-color: lightblue ;
display: inline-block ;
padding : 10px ;}
</style>
<div>XML Content
<ul></ul>
</div>`
}
connectedCallback() {
//get the data island id
var _block_id = this.dataset.block
var _ul = this.shadowRoot.querySelector('ul')
var _xml
parseXML()
render()
//internal methods
//parse
function parseXML() {
var text = document.getElementById(_block_id)
var parser = new DOMParser()
_xml = parser.parseFromString(text.textContent, 'application/xml')
}
//render
function render() {
_xml.querySelectorAll('data').forEach(d => {
let li = document.createElement('li')
li.textContent = d.textContent
_ul.appendChild(li)
})
}
}
}
customElements.define("xml-renderer", XMLRenderer)
<script type="application/xml" id="data-block1">
<base><data>Data 1</data><data>Data 2</data></base>
</script>
<xml-renderer data-block="data-block1"></xml-renderer>

Quill JS adding inline or 'formatBlock' styles

I'm using QuillJS for an editor, and in this editor I'd like to create some custom text styles. You have the default, bold etc. which already exist, however i'd like to extend upon these. For example, there's blockquote which'll create a block quote, however I want an inline quote. For this i'd ideally wrap it with say a span and class to apply the desired style, however I can't figure out how this is to be achieved with Quills API. Sure I can create a custom block, but that applies to the whole section of text rather then just the selected text. So i've tried using .formatText with my custom block, but not had any luck although if I change 'quote' to 'bold' it does... Any help / suggestions would be greatly appreciated!
let Block = Quill.import('blots/block');
class quote extends Block { }
quote.blotName = 'quote';
quote.className = 'quote';
quote.tagName = 'span';
Quill.register({ 'formats/quote': quote });
//Handler to change inline
var quoteHandler = function(){
var range = quill.getSelection();
console.log(range);
quill.formatText(range.index, range.length, 'quote', true);
}
/* Quill */
var quill = new Quill('.editor_space', {
theme: 'snow',
placeholder: 'Compose an epic...',
modules: {
toolbar:{
container: '.main_toolbar',
handlers: {
'linebreak': linebreakHandler,
'inlineQuote': quoteHandler,
}
}
}
});
To answer my own question, I should have been extending Inline for it to obviously be inline. No need for a handler function.
let Inline = Quill.import('blots/inline');
class quote extends Inline {
static create(value) {
let node = super.create(value);
return node;
}
}
quote.blotName = 'quote';
quote.className = 'quote';
quote.tagName = 'div';
Quill.register(quote);

Categories