|
/* By Bo Ericsson, https://www.linkedin.com/in/boeric00/ */ |
|
|
|
const controlsContainer = document.querySelector('.controls-container'); |
|
const contentContainer = document.querySelector('.content-container'); |
|
let rootElemId = ''; |
|
let mainRootElemId; |
|
let combinator; |
|
let combinatorOperation = ' '; |
|
|
|
function refreshSelection() { |
|
// Clear 'selected' class from all elems |
|
const allElems = document.querySelectorAll(`#${mainRootElemId} *`); |
|
allElems.forEach((elem) => { |
|
elem.classList.remove('selected'); |
|
}); |
|
|
|
// Determine selector (if no root element is selected, set class 'none', which matches |
|
// nothing) |
|
const selectorStr = rootElemId !== '' |
|
? `#${rootElemId} ${combinatorOperation} div` |
|
: '.none'; |
|
|
|
const rootElemIdStr = rootElemId !== '' |
|
? rootElemId |
|
: 'unselected'; |
|
|
|
const selectedElems = document.querySelectorAll(selectorStr); |
|
selectedElems.forEach((elem) => { |
|
elem.classList.add('selected'); |
|
}); |
|
|
|
// Update info panel |
|
const infoStr = `Root: ${rootElemIdStr}, Combinator: ${combinator}, Selector: '${selectorStr}', Selected elements: ${selectedElems.length}`; |
|
document.querySelector('.info').innerHTML = infoStr; |
|
} |
|
|
|
const combinators = { |
|
title: 'Combinators', |
|
selected: '', |
|
items: [ |
|
{ name: 'Descendant combinator (A B)', operation: ' ', selected: true }, |
|
{ name: 'Child combinator (A > B)', operation: '>', selected: false }, |
|
{ name: 'General sibling combinator (A ~ B)', operation: '~', selected: false }, |
|
{ name: 'Adjacent sibling combinator (A + B)', operation: '+', selected: false }, |
|
], |
|
}; |
|
|
|
// Combinator click handler |
|
function onCombinatorClick() { |
|
const elems = document.querySelectorAll('.selector'); |
|
// Clear 'chosen' class from all combinators |
|
elems.forEach((d) => { |
|
d.classList.remove('chosen'); |
|
}); |
|
const combinatorName = this.innerHTML; |
|
// Test if already selected, then unselect |
|
if (combinators.selected === combinatorName) { |
|
// Clear combinator |
|
// TODO: handle un-select of combinator |
|
// combinators.selected = ''; |
|
} else { |
|
// Add 'chosen' class to clicked combinator |
|
this.classList.add('chosen'); |
|
combinators.selected = combinatorName; |
|
|
|
// Update the current combinator-related variables |
|
combinator = combinatorName; |
|
combinatorOperation = combinators.items.find((d) => d.name === combinatorName).operation; |
|
|
|
// Refresh the selection |
|
refreshSelection(); |
|
} |
|
} |
|
|
|
// Create combinators |
|
const combinatorTitle = document.createElement('p'); |
|
combinatorTitle.className = 'header'; |
|
combinatorTitle.innerHTML = combinators.title; |
|
controlsContainer.appendChild(combinatorTitle); |
|
|
|
combinators.items.forEach((d) => { |
|
const { name, operation, selected } = d; |
|
const item = document.createElement('p'); |
|
item.className = 'selector'; |
|
item.innerHTML = name; |
|
item.onclick = onCombinatorClick; |
|
controlsContainer.appendChild(item); |
|
if (selected) { |
|
combinator = name; |
|
combinatorOperation = operation; |
|
item.classList.add('chosen'); |
|
} |
|
}); |
|
|
|
// DOM structure |
|
const domStructure = { |
|
type: 'div', |
|
dir: 'column', |
|
children: [ |
|
{ |
|
type: 'div', |
|
dir: 'row', |
|
children: [ |
|
{ type: 'div' }, |
|
{ type: 'span' }, |
|
{ type: 'div' }, |
|
{ type: 'div' }, |
|
], |
|
}, |
|
{ |
|
type: 'div', |
|
dir: 'row', |
|
children: [ |
|
{ type: 'div' }, |
|
{ type: 'div' }, |
|
{ type: 'div' }, |
|
{ type: 'span' }, |
|
{ type: 'span' }, |
|
{ type: 'div' }, |
|
{ type: 'span' }, |
|
], |
|
}, |
|
{ |
|
type: 'div', |
|
dir: 'row', |
|
children: [ |
|
{ type: 'div' }, |
|
{ |
|
type: 'div', |
|
dir: 'row', |
|
children: [ |
|
{ type: 'div' }, |
|
{ type: 'span' }, |
|
], |
|
}, |
|
], |
|
}, |
|
{ |
|
type: 'div', |
|
dir: 'row', |
|
children: [ |
|
{ type: 'span' }, |
|
{ type: 'div' }, |
|
{ type: 'div' }, |
|
{ type: 'span' }, |
|
{ type: 'div' }, |
|
], |
|
}, |
|
], |
|
}; |
|
|
|
function onElemClick(evt) { |
|
const elems = document.querySelectorAll('*'); |
|
elems.forEach((elem) => { |
|
elem.classList.remove('root'); |
|
}); |
|
|
|
if (rootElemId === this.id) { |
|
rootElemId = ''; |
|
} else { |
|
this.classList.add('root'); |
|
rootElemId = this.id; |
|
} |
|
|
|
// Refresh the selection |
|
refreshSelection(); |
|
|
|
// Don't propage the event |
|
evt.stopPropagation(); |
|
} |
|
|
|
// Build DOM |
|
function walkDomTree(node, parentLevel, title) { |
|
const level = parentLevel + 1; |
|
const { type, dir = '', children = [] } = node; |
|
|
|
// Create a new element |
|
const elem = document.createElement(type); |
|
elem.className = dir; |
|
elem.innerHTML = `${type}${title}`; |
|
elem.id = `${type}${title}`; |
|
elem.onclick = onElemClick; |
|
|
|
// Iterate over the children |
|
children.forEach((child, idx) => { |
|
const childTitle = `${title}-${idx}`; |
|
const childElem = walkDomTree(child, level, childTitle); |
|
elem.appendChild(childElem); |
|
}); |
|
|
|
// Return the element |
|
return elem; |
|
} |
|
|
|
// Instantiate the DOM tree recursively |
|
const root = walkDomTree(domStructure, -1, '-0'); |
|
|
|
// Append the root to the content container |
|
contentContainer.appendChild(root); |
|
mainRootElemId = root.id; |
|
|
|
// Initial refresh |
|
refreshSelection(); |