dynamic svg in html5 - javascript

I expected SVG nodes to be first class citizen in html5 but I get an unexpected behavior (under Firefox 55.0.2 and Chrome 54.0.2840.71).
In the following html file, I expect a big circle to be dynamically added to a newly created svg element. Instead :
The Inspector tells me the DOM was correctly modified
Nothing is displayed
when I copy paste the DOM (copy -> outer HTML, script deleted) in a new file, the resulting static html file is perfectly fine.
What do I miss ? Why do I have this discrepancy between the DOM and the rendered version of it ? How can I correct this ? re-draw ?
When I use NS suffixed versions of functions (ie. createElementNS and setAttributeNS) I get similar results and nothing is rendered.
Here is the culprit:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
<script type="text/javascript">
Element.prototype.grow = function (tag, attribute_map) {
var child = document.createElement(tag);
if ( attribute_map !== undefined ) {
for (let key in attribute_map) {
child.setAttribute(key, attribute_map[key]);
}
}
this.appendChild(child);
return child;
};
</script>
</head>
<body>
<div id="sandbox"><svg viewbox="0 0 200 200"></svg></div>
<script>
var g_svg = document.getElementById("sandbox").firstElementChild;
g_svg.grow('circle', {'cx':"100", 'cy':"100", 'r':"32"});
</script>
</html>
and here is the DOM-copy-pasted result I get via the inspector (script removed manually) :
<html><head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
</head>
<body>
<div id="sandbox"><svg viewBox="0 0 200 200"><circle cx="100" cy="100" r="32"></circle></svg></div>
</body></html>

Elements go in the SVG namespace, attributes usually don't.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>bug dynamic svg</title>
<script type="text/javascript">
Element.prototype.grow = function (tag, attribute_map) {
var child = document.createElementNS('http://www.w3.org/2000/svg', tag);
if ( attribute_map !== undefined ) {
for (let key in attribute_map) {
child.setAttribute(key, attribute_map[key]);
}
}
this.appendChild(child);
return child;
};
</script>
</head>
<body>
<div id="sandbox"><svg viewbox="0 0 200 200"></svg></div>
<script>
var g_svg = document.getElementById("sandbox").firstElementChild;
g_svg.grow('circle', {'cx':"100", 'cy':"100", 'r':"32"});
</script>
</html>

Related

Jsdom load javascript code but doesn't run correctly

What is wrong with this code? It displays the says: hello bar from the out.js console.log but does not run the rest of the script doesn't add the link inside <div id="link"></div>
If I put the script directly in the code it works, but not in a .js file
teste2.js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="link"></div>
<p>Hello world</p>
<script src="http://localhost/2022/jsdom/out.js"></script>
</body>
</html>`, { resources: "usable", runScripts: "dangerously"});
const document = dom.window.document;
console.log(document.getElementsByTagName('html')[0].innerHTML);
out.js
let T = document.getElementById('link');
T.innerHTML = 'LINK';
console.log('bar says: hello');
JSDOM loads sub-resources asynchronously, so your Node.js code is accessing the DOM before the <script> code has executed.
This is also why the log of document.getElementsByTagName('html')[0].innerHTML appears before the log of 'bar says: hello'.
You can handle this by explicitly waiting for the load event:
const document = dom.window.document;
document.addEventListener('load', () => {
console.log(document.getElementsByTagName('html')[0].innerHTML);
});
You'll need more complex logic if the script itself does anything asynchronously. Firing a custom event that you can listen for is a good approach.

JS: Display certain div if browser is IE

I have this react project that doesn't work in Internet Explorer, and we don't intend it to work in IE.
So when rendering the index.html there is the usual root div that renders react.
I want not to render that root div when browser is IE but a different div, with a message warning that the app doesn't work in IE
I can know if the browser is IE like so:
const isIE = !!window.MSInputMethodContext && !!document.documentMode
So I'm trying to change the html div output depending of isIE and I'm not quite sure how.
Logic:
if isIE - true render <div id="root"></div>
if !isIE - false render <div>Browser not supported</div>
Tried something like this:
<html>
<head>
<meta charset="utf-8" />
<% _.map(css, (item) => { %><link href="<%= item %>" rel="stylesheet"><% }) %>
</head>
<body>
<script>
const isIE = !!window.MSInputMethodContext && !!document.documentMode;
if (isIE) { document.getElementById('root').innerHTML += '<p>IE!!</p>' } // or something similar
</script>
<div id="root"></div>
<script>window.__ENVIRONMENT__ = Object.freeze(<%= JSON.stringify(environment) %>)
</script>
<% _.map(js, (item) => { %><script src="<%= item %>"></script><% }) %>
</body>
</html>
Also tried a logic returning the html as a string like so:
<script>
if (isIE) { return '<div>Browser not supported</div>'; }
else { return '<div id="root"></div> }
</script>
And this
const IEdiv = '<div>This is IE</div>';
const rootDiv = '<div id="root"></div>';
if (isIE) { IEdiv.append('body'); }
else { rootDiv.append('body'); }
None of this works
React shouldn't work out of the box with any version of IE as far as I can tell. It requires (or at least used to require) react-app-polyfill to get anywhere.
You're also going to have problems with const as it's only supported by IE11. If the only version of IE you're concerned with is IE11 you should be fine for this.
UPDATED ANSWER
I created a sample myself to see what I could do. I first experimented with loading the <div> into the page with JS, and hit a nasty IE error in being unable to add elements to the document.
So I changed my approach back to yours - editing the existing <div>. The problem isn't your code, the problem is that your script is being ran before the <div> is present in the DOM (to be accessed). Put the <div> above the script (or move the JS to an external file and load it in) and it should work as you intend it to.
Here's my sample for reference:
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="root"></div>
<script>
var isIE = !!window.MSInputMethodContext && !!document.documentMode;
console.warn(isIE);
if (isIE) {
var root = document.getElementById('root');
root.innerHTML += 'IE!!';
}
else {
var root = document.getElementById('root');
root.innerHTML += 'hello hello hello';
// you may also need to defer React being loaded in
}
</script>
</body>
</html>

How do I connect an audio worklet to a web page?

I'm trying to connect a web page to an audio worklet (following this demos but I got stuck.
Can somebody help?
Here's the code I've got so far.
Those files are all in the same folder, but they don't log anything.
INDEX.HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
INDEX.JS
const demoCode = async () => {
const audioContext = new AudioContext()
await audioContext.audioWorklet.addModule('test-processor.js')
const testNode = new AudioWorkletNode(audioContext, 'test-processor')
testNode.connect(audioContext.destination)
}
TEST-PROCESSOR.JS
class TestProcessor extends AudioWorkletProcessor {
constructor () {
super()
console.log(currentFrame)
console.log(currentTime)
}
process (inputs, outputs, parameters) {
return true
}
}
console.log(sampleRate)
const usefulVariable = 42
console.log(usefulVariable)
registerProcessor('test-processor', TestProcessor)
It looks like you're not invoking your demoCode() function anywhere. If you want to be compliant with the autoplay policy in todays browsers that needs to happen in response to a user gesture.
First you need to add a button to your HTML.
<button id="start" type="button">start</button>
Then you can attach an event listener for that button within your index.js file.
document.getElementById('start').addEventListener('click', demoCode);

Passing onclick event into template literals without global function

I've been working on adding onclick event in template literals with plain javascript (without jquery only javascript). As you can see the result html knows that onclick event on each div has function which will alert as I click but when I click, it didn't respond. It seems like suppose to work but somehow it is not working.
I got lots of help from Stackoverflow but most of the anwser was using global function. but I don't personally want to use global function since sometimes it cause some trouble.
so how can I actually pass the function into onclick event by using template literals?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="ul"></div>
</body>
<script src="/index.js"></script>
</html>
index.js
function test() {
const list = [
{ number: 1, check: () => alert("1") },
{ number: 2, check: () => alert("2") },
{ number: 3, check: () => alert("3") },
];
const $list = list.reduce((acc, item) =>
acc + `<div onclick='${item.check}'>${item.number}</div>`,""
);
const $ul = document.querySelector("#ul");
$ul.innerHTML = $list;
}
test();
result
Instead of building the DOM via HTML strings, create actual DOM elements.
const $ul = document.querySelector("#ul");
for (const item of list) {
const element = document.createElement('div');
element.textContent = item.number;
element.onclick = item.check;
$ul.appendChild(element);
}

Forcing AngularJS directive to link outside digest cycle

This refers to an Angular 1 application.
If the DOM is modified outside the context of my angular application, I know I can use angular.element(document.body).scope().$apply() to force the whole app to re-render, including the newly injected content.
However my directives never seem to link.
So in the example below, the markup <message></message> should render Hello World, but when it is injected manually, then digest applied, the link method never appears to run.
https://jsbin.com/wecevogubu/edit?html,js,console,output
javascript
var app = angular.module('app', [])
app.directive('message', function() {
return {
template: 'Hello, World!',
link: function() {
console.log('message link')
}
}
})
document.getElementById('button').addEventListener('click', function() {
document.getElementById('content').innerHTML = '<message>default content</message>'
var scope = window.angular.element(document.body).scope()
scope.$apply()
})
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
</head>
<body ng-app="app">
inside app:
<message></message>
outside app:
<button id="button">Print another message</button>
<div id="content"></div>
</body>
</html>
According to the docs, you can do this with angular.injector
angular.injector allows you to inject and compile some markup after the application has been bootstrapped
So the code for your example could be:
document.getElementById('button').addEventListener('click', function() {
var $directive = $('<message>default message</message>');
$('#content').append($directive);
angular.element(document.body).injector().invoke(function($compile) {
var scope = angular.element($directive).scope();
$compile($directive)(scope);
});
})
Hope this is what you are looking for!

Categories

Resources