Trying to apply this script to a specific div - javascript

Here's a transition loader running while downloading an external mp3, waiting to be ready to stream with <audio></audio> tags inside a <div class="player"></div>.
How can I apply this script to the specific <div class="player"></div> only and not to the whole page? Thanks.
let e= {
backgroundColor: "#fff", filterBrightness:2, strokeWidth:10, timeOnScreen:100
},
t=document.querySelector("*"),
r=document.createElement("style"),
i=document.createElement("<div>"),
s="http://www.w3.org/2000/svg",
n=document.createElementNS(s, "svg"),
o=document.createElementNS(s, "circle");
document.head.appendChild(r),
r.innerHTML="#keyframes swell{to{transform:rotate(360deg)}}",
i.setAttribute("style", "background-color:"+e.backgroundColor+";color:"+e.backgroundColor+";display:flex;align-items:center;justify-content:center;position:fixed;top:0;height:100vh;width:100vw;z-index:2147483647"),
document.body.setAttribute("style", "overflow:hidden!important;"),
document.body.prepend(i),
n.setAttribute("style", "height:50px;filter:brightness("+e.filterBrightness+");animation:.3s swell infinite linear"),
n.setAttribute("viewBox", "0 0 100 100"),
i.prepend(n),
o.setAttribute("cx", "50"),
o.setAttribute("cy", "50"),
o.setAttribute("r", "35"),
o.setAttribute("fill", "none"),
o.setAttribute("stroke", "currentColor"),
o.setAttribute("stroke-dasharray", "165 57"),
o.setAttribute("stroke-width", e.strokeWidth),
n.prepend(o),
t.style.pointerEvents="none",
t.style.userSelect="none",
t.style.cursor="wait",
window.onload=()=> {
setTimeout(()=> {
t.style.pointerEvents="", t.style.userSelect="", t.style.cursor="", i.remove(), document.body.setAttribute("style", "")
}
, e.timeOnScreen)
}

Convert "myplayer" into a web component.
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
class MyPlayer extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// Element functionality written in here
// ADD YOUR CODE HERE
}
}
customElements.define("myplayer", MyPlayer);
This creates an HTML element that has code specific to its self.

Related

How to make an svg interactive to gather comments/annotations on depicted elements

I create directed graphs like the following from wikidata with the help of networkx and nxv. The result is an svg file which might be embedded in some html page.
Now I want that every node and every edge is "clickable", such that a user can add their comments to specific elements of the graph. I think this could be done with a modal dialog popping up. This dialog should know from which element it was triggered and it should send the content of the textarea to some url via a post request.
What would be the best way to achieve this?
Wrapped in a W3C standard Web Component (supported in all Modern Browsers) you can make it generic for any src="filename.svg"
Simple example: How to get SVG document data to be inserted into the DOM?
More complex example:
<graphviz-svg-annotator src="https://graphviz.org/Gallery/directed/fsm.svg">
</graphviz-svg-annotator>
The SVG is loaded with an async fetch
Nodes and Edges are clickable in this SO Snippet
add your own, better modal, window and saving to database
Try the SVGs from: https://graphviz.org/Gallery/directed/Genetic_Programming.html
<graphviz-svg-annotator src="fsm.svg"></graphviz-svg-annotator>
<graphviz-svg-annotator src="Linux_kernel_diagram.svg"></graphviz-svg-annotator>
<style>
svg .annotate { cursor:pointer }
</style>
<script>
customElements.define('graphviz-svg-annotator', class extends HTMLElement {
constructor() {
let loadSVG = async ( src , container = this.shadowRoot ) => {
container.innerHTML = `<style>:host { display:inline-block }
::slotted(svg) { width:100%;height:200px }
</style>
<slot name="svgonly">Loading ${src}</slot>`;
this.innerHTML = await(await fetch(src)).text(); // load full XML in lightDOM
let svg = this.querySelector("svg");
svg.slot = "svgonly"; // show only SVG part in shadowDOM slot
svg.querySelectorAll('g[id*="node"],g[id*="edge"]').forEach(g => {
let label = g.querySelector("text")?.innerHTML || "No label";
let shapes = g.querySelectorAll("*:not(title):not(text)");
let fill = (color = "none") => shapes.forEach(x => x.style.fill = color);
let prompt = "Please annotate: ID: " + g.id + " label: " + label;
g.classList.add("annotate");
g.onmouseenter = evt => fill("lightgreen");
g.onmouseleave = evt => fill();
g.onclick = evt => g.setAttribute("annotation", window.prompt(prompt));
})
}
super().attachShadow({ mode: 'open' });
loadSVG("//graphviz.org/Gallery/directed/"+this.getAttribute("src"));
}});
</script>
Detailed:
this.innerHTML = ... injects the full XML in the component ligthDOM
(because the element has shadowDOM, the lightDOM is not visible in the Browser)
But you only want the SVG part (graphviz XML has too much data)... and you don't want a screen flash; that is why I put the XML .. invisible.. in lightDOM
A shadowDOM <slot> is used to only reflect the <svg>
with this method the <svg> can still be styled from global CSS (see cursor:pointer)
With multiple SVGs on screen <g> ID values could conflict.
The complete SVG can be moved to shadowDOM with:
let svg = container.appendChild( this.querySelector("svg") );
But then you can't style the SVG with global CSS any more, because global CSS can't style shadowDOM
As far as I know, nxv generates a g element with class "node" for each node, all nested inside a graph g. So basically you could loop over all gs elements inside the main group and attach a click event listener on each one. (actually, depending of the desired behavior, you might want to attach the event listener to the shape inside the g, as done below. For the inside of the shape to be clickable, it has to be filled)
On click, it would update a form, to do several things: update its style to show it as a modal (when submitted, the form should go back to hiding), and update an hidden input with the text content of the clicked g.
Basically it would be something like that:
<svg>Your nxv output goes here</svg>
<form style="display: none;">
<input type="hidden" id="node_title">
<textarea></textarea>
<input type="submit" value="Send!">
</form>
<script>
const graph = document.querySelector("svg g");
const form = document.querySelector("form");
[...graph.querySelectorAll("g")].map(g => { //loop over each g element inside graph
if (g.getAttribute("class") == "node") { //filter for nodes
let target = "polygon";
if (g.querySelector("polygon") === null) {
target = "ellipse";
}
g.querySelector(target).addEventListener("click",() => {
const node_title = g.querySelector("text").innerHTML;
form.querySelector("#node_title").setAttribute("value", node_title);
form.setAttribute("style","display: block;");
});
}
});
const submitForm = async (e) => { //function for handling form submission
const endpoint = "path to your POST endpoint";
const body = {
source_node: form.querySelector("#node_title").value,
textarea: form.querySelector("textarea").value
}
e.preventDefault(); //prevent the default form submission behavior
let response = await fetch(endpoint, { method: "POST", body: JSON.stringify(body) });
// you might wanna do something with the server response
// if everything went ok, let's hide this form again & reset it
form.querySelector("#node_title").value = "";
form.querySelector("textarea").value = "";
form.setAttribute("style","display: none;");
}
form.addEventListener("submit",submitForm);
</script>

How to prevent flickering with web components?

Consider a simple web component
class TimeAgo extends HTMLElement {
constructor() {
super();
this.innerHTML = '2 hours ago'
}
}
customElements.define('time-ago', TimeAgo);
Being used like this
This post was created <time-ago datetime="2020-09-26T11:28:41.9731640+01:00">September 26th</time-ago>
When the page renders, the browser will first write "September 26th" and right after that switch to "2 hours ago".
Is there a way to prevent this from happening? I'd much prefer to display "2 hours" on first paint.
The JS file is loaded through a CDN, moving the script tag up and down the response didn't change anything.
But is your script executed before that part of the DOM is parsed?
Code below displays 1A in Chromium and 1 in FireFox,
because (don't pin me on terminology) in Chromium the 1 is injected into the DOM before content A is parsed.
So if you don't want a FOUC leave <time-ago> content empty.. or maybe blur it with CSS
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = () => {
console.log("onload");
};
customElements.define(
"my-element",
class extends HTMLElement {
constructor() {
super();
console.log("constructor");
}
connectedCallback() {
console.log("connectedCallback");
this.innerHTML = "1";
}
}
);
</script>
</head>
<body>
<my-element>A</my-element>
<script>
console.log("end DOM");
</script>
</body>
</html>
One of the possible solution I can think of is to enable shadow-dom
class TimeAgo extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.innerHTML = '2 hours ago'
}
}
customElements.define('time-ago', TimeAgo);
By defining your web component to be shadow dom, you can change the content even before it's attached to the DOM.

How to say 'if ALL Instances of a CSS Class are Removed' in an if/else inside of a foreach Method

I'm having trouble removing the CSS 'active_bg' class of the wheel's core circle when it's removed from all segments.
The full code is on Github and Codepen.
Codepen: https://codepen.io/Rburrage/pen/xmqJoO
Github: https://github.com/RBurrage/wheel
In my click event, I tried saying that if the class exists on a segment, add it to the core circle, too -- ELSE -- remove it from the core circle. My code is within a forEach method that loops through all groups in the SVG.
The part in question is in the last event listener below (the 'click' event).
var secondGroups = document.querySelectorAll('.sols-and-mods');
secondGroups.forEach(function (secondGroup) {
let solution = secondGroup.childNodes[1];
let module = secondGroup.childNodes[3];
let core = document.querySelector('.core_background');
secondGroup.addEventListener('mouseover', () => {
solution.classList.add('hovered_bg');
module.classList.add('hovered_bg');
})
secondGroup.addEventListener('mouseout', () => {
solution.classList.remove('hovered_bg');
module.classList.remove('hovered_bg');
})
secondGroup.addEventListener('click', () => {
solution.classList.toggle('active_bg');
module.classList.toggle('active_bg');
if (solution.classList.contains('active_bg')) {
core.classList.add('active_bg');
solution.classList.remove('hovered_bg');
module.classList.remove('hovered_bg');
}else{
core.classList.remove('active_bg');
}
})
})
When the user clicks on a segment of the wheel, the CSS 'active_bg' class gets added to both the clicked segment and the wheel's core circle.
I want to remove the 'active_bg' class from the wheel's core circle but only when it is removed from ALL segments.
Currently, as soon as I remove the class from any ONE segment, it gets removed from the core circle.
Can someone please tell me what I'm doing wrong?
Thank you!
Explained
Okay, so to keep this as short and simply as possible, I changed your logic only ever so slightly, I've just included a check to see if there's at least one option selected, if not, then the core circle has the default class, otherwise, it will continue to have the class name of active_bg.
Here's the JSFiddle that I've made.
If there's any further issues with this solution, don't hesitate to ask.
Edit
I just thought I'd go ahead an include the JavaScript that I was playing around with.
window.onload = function() {
TweenMax.staggerFrom('.solution', .5, {
opacity: 0,
delay: 0.25
}, 0.1);
TweenMax.staggerFrom('.module', .5, {
opacity: 0,
delay: 0.5
}, 0.1);
}
var secondGroups = document.querySelectorAll('.sols-and-mods');
secondGroups.forEach(function(secondGroup) {
let solution = secondGroup.childNodes[1];
let module = secondGroup.childNodes[3];
let core = document.querySelector('.core_background');
secondGroup.addEventListener('mouseover', () => {
solution.classList.add('hovered_bg');
module.classList.add('hovered_bg');
})
secondGroup.addEventListener('mouseout', () => {
solution.classList.remove('hovered_bg');
module.classList.remove('hovered_bg');
})
secondGroup.addEventListener('click', () => {
solution.classList.toggle('active_bg');
module.classList.toggle('active_bg');
if (solution.classList.contains('active_bg')) {
core.classList.add('active_bg');
solution.classList.remove('hovered_bg');
module.classList.remove('hovered_bg');
}
// Added this line.
if (document.querySelector(".sols-and-mods .active_bg") == null) {
core.classList.remove('active_bg');
}
})
})

DOM Manipulations in Angular 5

For example I have:
<div class="btn-wrapper-bt1">
<button>AAA</button>
</div>
This button is on the 3rd party element that exists in node_modules/somebt
I would like to do some simple class change within Angular environment.
Is there a simple way to change it in ngOnInit? Or I need to fork the source and change it within the source?
Thanks in advance.
In the html, add a #ref reference to the element containing your 3rd party component
yourComponent.html
<div #ref >
<your-3rd-party-component></your-3rd-party-component>
</div>
Then, in your component, retrieve the children of the containing element
yourComponent.ts
import { Component,Renderer2, ViewChild,ElementRef } from '#angular/core';
export class YourParentComponent {
#ViewChild('ref') containerEltRef: ElementRef;
constructor(private renderer: Renderer2)
{
}
ngAfterViewInit()
{
// retrieves element by class
let elt = this.containerEltRef.nativeElement.querySelector('.btn-wrapper-bt1');
this.renderer.addClass(elt, 'newClass'); //Adds new class to element
}
}
Here is a stacklblitz demo
Note: If you just want to change the 3rd party component's appearance, you could just override the class in your own component
yourComponent.scss
:host ::ng-deep .btn-wrapper-bt1
{
color: red;
}
Add a reference :
<div #myRef class="btn-wrapper-bt1">
<button>AAA</button>
</div>
And in your TS :
#ViewChild('myRef') myElement: ElementRef;
myFunc(){
// do whatever you want with it AFTER you third party module finished its job (that's your call)
//this.myElement.nativeElement.querySelector()
//this.myElement.nativeElement.classList.remove('toto')
}

Using the Wiris editor within a Web Component

I have created a Web Component which hosts Wiris. However when the component is rendered the Wiris editor is (very) badly formed:
You can see the issue live here.
The code is as follows:
class WirisComponent extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
var shadow = this.attachShadow( { mode: 'open' } );
// Create a div to host the Wiris editor
var div = document.createElement('div');
div.id = 'editorContainer';
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
// Insert the Wiris instance into the div
editor.insertInto(div);
// Append it to the shadow route
shadow.appendChild(div);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
and the HTML mark-up is:
<wiris-component></wiris-component>
Note that I've tried this in Chrome which does have full support for web components.
Any idea what the problem is? Is the problem related to the styling issue found in this issue?
Don't use a Shadow DOM: the styles imported with your library are not working with it.
class WirisComponent extends HTMLElement {
connectedCallback() {
var wirisDefaultConfig = {
'language': 'en'
};
var editor = com.wiris.jsEditor.JsEditor.newInstance(wirisDefaultConfig);
editor.insertInto(this);
}
}
// Define the new element
customElements.define('wiris-component', WirisComponent);
<script src="https://www.wiris.net/demo/editor/editor"></script>
<wiris-component></wiris-component>

Categories