Introduction
This component consists in a floating window
in which several children elements can be placed.
Let's start by creating the structure of our web component. Create a file windowLab.js:
class windowLab extends HTMLElement {
//Here will be all the functionality.
}
window.customElements.define('window-lab', windowLab);
Step 2: Add the observed attributes
class windowLab extends HTMLElement {
//Step 1
static get observedAttributes() {
return ['nodrag','closable'];
}
}
window.customElements.define('window-lab', windowLab);
In this case we create two attributes: nodrag and closable. When you include them in the observedAttributes function you are saying that they will be observed in case to change at some point.
Step 4: Add a function for drag the component
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
dragElement(elmnt,shadowRoot,status) {
//Here wil be functions and codes for dragging
}
}
window.customElements.define('window-lab', windowLab);
Note that the dragElement function has three params:
- elmnt: Is the web component itself.
- shadowRoot: The shadowRoot of the component so we can reach its children elements.
- status: Allow a drag (true) or not (false).
Filling the dragElement function:
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
dragElement(elmnt,shadowRoot,status) {
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
if(status == true){
if(shadowRoot.querySelector('#mydivheader'))
shadowRoot.querySelector('#mydivheader').onmousedown = dragMouseDown;
else
elmnt.onmousedown = dragMouseDown;
} else {
if(shadowRoot.querySelector('#mydivheader'))
shadowRoot.querySelector('#mydivheader').onmousedown = '';
else
elmnt.onmousedown = '';
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
}
}
window.customElements.define('window-lab', windowLab);
Step 5: Add a function to change the component's height
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
setHeight(e){
this.shadowRoot.querySelector('#container').style.height = e;
}
}
window.customElements.define('window-lab', windowLab);
Note that the setHeight function has one params:
- e: Is the height in pixels.
Step 8: Add a attributeChangedCallback function
This will be fired when a observable attribute change. In this case nodrag and closable
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
//Step 7
attributeChangedCallback(name, oldValue, newValue) {
if(this.shadowRoot){
if (this.nodrag) {
this.dragElement(this,this.shadowRoot,false);
this.shadowRoot.querySelector('#mydivheader').style.cursor='initial';
} else {
this.dragElement(this,this.shadowRoot,true);
this.shadowRoot.querySelector('#mydivheader').style.cursor='move';
}
if (this.closable) {
this.shadowRoot.querySelector('#closeBtn').style.display='block';
} else {
this.shadowRoot.querySelector('#closeBtn').style.display='none';
}
}
}
}
window.customElements.define('window-lab', windowLab);
Step 9: Add a function connectedCallback
It fires when the component is created
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
//Step 7
//Step 8
connectedCallback () {
//Here will be the functionality.
}
}
window.customElements.define('window-lab', windowLab);
Inside connectedCallback function we will create two things:
- shadowRoot: Is the shadowDOM of the component. It contains the styles of the component and its html definition.
- Conditions to apply certain functions to the component.
Filling the connectedCallback function:
class windowLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
//Step 7
//Step 8
connectedCallback () {
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
#mydivheader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: #2196F3;
color: #fff;
}
.btn, .button {
border: none;
display: inline-block;
padding: 8px 16px;
vertical-align: middle;
overflow: hidden;
text-decoration: none;
color: inherit;
background-color: inherit;
text-align: center;
cursor: pointer;
white-space: nowrap;
font-family: "Verdana",sans-serif;
}
.btn, .button {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.display-topright {
position: absolute;
right: 0;
top: 0;
}
</style>
<div id="mydivheader">
<slot name="title">
<div>Default title</div>
</slot>
<span id='closeBtn' class="button display-topright" style='display:none;color:white;font-size:18px;float:right;padding: 7px 16px'>×</span>
</div>
<div id="container" style='background-color:white;'>
<slot name='body'>
<div></div>
</slot>
</div>`;
this.style.position = 'absolute';
this.style.boxShadow = '0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)';
//if it does not has a nodrag attribute defined
if(!this.nodrag){
this.dragElement(this,shadowRoot,true);
}else{
shadowRoot.querySelector('#mydivheader').style.cursor='initial';
}
//if it has a nodrag closable defined
if(this.closable){
shadowRoot.querySelector('#closeBtn').style.display='block';
}
//attaching a event listener to the close button.
shadowRoot.querySelector('#closeBtn').addEventListener('click', e => {
this.style.display = 'none';
});
}
}
window.customElements.define('window-lab', windowLab);
Check a working demo for this code: