I've been thinking a lot more about web components lately, and this especially got a kick up after seeing a great presentation on the topic by Nolan Erck. Yesterday I was curious if web components could access, and manipulate, content between the opening and closing tag. So for example, consider this code:
<place-kitten>
Here is text about a cat.
</place-kitten>
What I'd like to do is read the text inside (Here is text about a cat.
) and manipulate it. I'm not talking about the slot feature, which looks like a good feature, but a simpler approach where I can just grab the text between the paired tags.
I was able to figure it out, and I'm going to share my findings below, but please remember that I'm new at this and maybe what I'm proposing is a bad idea. To be 100% clear, my third and final example is a very, very bad idea and you should not copy it. Seriously.
Attempt One
For my first example, I checked to see if I could read and edit the textContent property. I created a quick web component named inner-text
, and tried this code:
class InnerTest extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: 'closed'
});
const wrapper = document.createElement('div');
wrapper.innerText = this.textContent.split('').reverse().join('');
shadow.appendChild(wrapper);
}
}
customElements.define('inner-test', InnerTest);
Basically - read textContent
, reverse it by using the convert to an array trick, and return it to the div I'm adding in the shadow DOM. I tested like so:
<inner-test>
Raymond
Camden
</inner-test>
And voila, it worked:
See the Pen InnerText by Raymond Camden (@cfjedimaster) on CodePen.
Attempt Two
Seeing how simple it was, I then decided to build something real, and maybe a tiny bit practical. How about a Markdown Renderer?
import { marked } from 'https://jspm.dev/marked';
class MarkdownRender extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: 'closed'
});
const wrapper = document.createElement('div');
wrapper.innerHTML = marked.parse(this.textContent);
shadow.appendChild(wrapper);
}
}
customElements.define('markdown-render', MarkdownRender);
In the code above, I load in the marked JavaScript Markdown library which lets me simply use the parse
command on text to convert it to HTML. Using input like so:
<markdown-render>
# Hello World
* ray
* camden
* was
* here
</markdown-render>
I get valid HTML out. Here's it in action:
See the Pen Markdown by Raymond Camden (@cfjedimaster) on CodePen.
Ok, stop reading now, honest.
Attempt Three
Still reading? Why, I warned you! So, about five years ago, I blogged a completely dumb experiment I did on OpenWhisk, serverless BASIC support: Serverless Basic. I've got a fond memories of BASIC, specifically AppleSoft Basic, as it was my first programming language. Wouldn't it be cool, and completely useless, to build a web component that allowed for this?
<applesoft-basic>
10 x = 10
20 y = 2
40 print "X+Y="
50 print x+y
</applesoft-basic>
Or even:
<applesoft-basic>
10 x = 0
20 for i = 1 to 10
30 x = x + i
40 next
50 print x
</applesoft-basic>
Yes, of course it's cool! I used the same library I used in the past, jsbasic, and built it into the following web component:
class ApplesoftBasic extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({
mode: 'open'
});
const wrapper = document.createElement('div');
shadow.appendChild(wrapper);
}
connectedCallback() {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src='./applesoftbasic.js';
document.head.appendChild(script);
let interval = setInterval(() => {
if(window.basic) {
clearInterval(interval);
let result = this.compile(this.textContent);
let mydiv = this.shadowRoot.querySelector('div');
mydiv.innerHTML = result;
}
}, 250);
}
compile(input) {
let result = '';
let program = basic.compile(input);
program.init({
tty: {
getCursorPosition: function() { return { x: 0, y: 0 }; },
setCursorPosition: function() { },
getScreenSize: function() { return { width: 80, height: 24 }; },
writeChar: function(ch) {
//console.log('writeChar called with: '+ch);
result += ch;
},
writeString: function(string) {
//console.log('writeString called with: '+string);
result += string+'\n';
},
readChar: function(callback) {
//callback(host.console.getc());
callback('');
},
readLine: function(callback, prompt) {
//host.console.puts(prompt);
//callback(host.console.gets().replace(/[\r\n]*/, ''));
callback('');
}
}
});
let driver = function() {
var state;
do {
try {
state = program.step(driver);
} catch(e) {
console.log('ERROR!',e);
return {
error:e
}
}
// may throw basic.RuntimeError
} while (state === basic.STATE_RUNNING);
}
driver(); // step until done or blocked
return result;
}
}
customElements.define('applesoft-basic', ApplesoftBasic);
This one's a bit more complex. First, I load in the library via a script tag added to the DOM, and then use an interval to see when the global object, basic
, exists. Updating the content was a bit more complex. Since my BASIC parsing was finished after the component was loaded and rendered, I grab a div via querySelector
and update it that way.
In case it isn't obvious, this whole thing is a really bad idea. But it works. To be clear, it works if you stick to non-interactive BASIC, but heck, even the infamous goto
and gosub
work. You can play with it here:
Unfortunately I can't show this on CodePen as it needs access to the BASIC library in a CORS friendly manner, but you can test the running app here: https://cfjedimaster.github.io/webdemos/webcomponents/basic.html
All of the code shown here, and my other web components, may be be found here: https://github.com/cfjedimaster/webdemos/tree/master/webcomponents
Photo by Ilja Tulit on Unsplash