How do I set properties according to custom HTML attributes in CSS? - html

I'm working on Fractions.
My checkpoint is :
frac {
display: inline-block;
position: relative;
vertical-align: middle;
letter-spacing: 0.001em;
text-align: center;
}
frac {
display: block;
padding: 0.01em;
}
frac {
border-top: thin solid black;
}
It is working but I want to two things :
I want to set default values, i.e. 0 to num and 1 to den.
I want to do use this type of code to make things simple : <frac num="0" den="1"> Note :It should not have a closing tag.The num and den attributes should have their default values if not mentioned.
Btw, I only know CSS.

There is no <frac> element in HTML.
To create your own <frac-el num="3" den="4"></frac-el>, you can create a small web component, which must adhere to custom element naming rules (name must contain a -). It also will need a closing tag.
That being said, here we go:
class FractionElement extends HTMLElement {
numerator = document.createElement('sup');
denominator = document.createElement('sub');
style = document.createElement('style');
constructor(n = 0, d = 1) {
super().attachShadow({mode:'open'});
this.num = n;
this.den = d;
this.style.textContent = `
:host { display: inline-flex; flex-direction: column; text-align: center; }
sup { border-bottom: 1px solid #666; }
`;
this.shadowRoot.append(this.numerator, this.denominator, this.style);
}
get num() { return this.numerator.textContent; }
set num(val) { this.numerator.textContent = val; }
get den() { return this.denominator.textContent; }
set den(val) { this.denominator.textContent = val; }
static get observedAttributes() {
return ['num', 'den'];
}
attributeChangedCallback(attr, oldVal, newVal) {
if (oldVal === newVal) return; // nothing to do, no change
switch (attr) {
case 'num': this.num = newVal ?? 0; break;
case 'den': this.den = newVal ?? 1; break;
}
}
}
customElements.define('frac-el', FractionElement);
<frac-el num="3" den="4"></frac-el>
<frac-el num="11" den="27691"></frac-el>
<frac-el></frac-el>
<frac-el num="3x + 7" den="y"></frac-el>

Related

Animation should start when I hover over the div

I would like to implement the following: The animation should only start when I hover the mouse over the div. After I hovered over the div, the end number should remain visible and not change to the start value.
This is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Animation</title>
<style>
.counter{
color: white;
font-size: 100px;
height: 300px;
width: 400px;
background-color: black;
display: flex;
align-items:center;
justify-content: center;
}
.animate{
position:absolute;
opacity:0;
transition:0s 180s;
}
.animate:hover {
opacity:1;
transition:0s;
}
</style>
</head>
<body>
<div id="animate" style="background-color: orange; width: 300px; height: 200px;" class="counter" data-target="500">0</div>
<script>
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
</script>
</body>
</html>
Add onmousover to id="animate"
<div id="animate" style="background-color: orange; width: 300px; height: 200px;" class="counter" data-target="500" onmouseover="animationEffect();">0</div>
Wrap the whole script in a method:
function animationEffect(){
const counters = document.querySelectorAll('.counter');
for(let n of counters) {
const updateCount = () => {
const target = + n.getAttribute('data-target');
const count = + n.innerText;
const speed = 5000; // change animation speed here
const inc = target / speed;
if(count < target) {
n.innerText = Math.ceil(count + inc);
setTimeout(updateCount, 1);
} else {
n.innerText = target;
}
}
updateCount();
}
}
Should solve the problem
EDIT:
The old answer was refering to the question before being edited. For the current case the following could be done:
const updateCount = n => {
const target = +n.getAttribute('data-target')
const count = +n.innerText
const speed = 5000 // change animation speed here
const inc = target / speed
if (count < target) {
n.innerText = Math.ceil(count + inc)
requestAnimationFrame(() => updateCount(n))
} else {
n.innerText = target
}
}
const counters = document.querySelectorAll('.counter')
for (let n of counters) {
n.addEventListener('mouseenter', () => updateCount(n), {
once: true
})
}
.counter {
color: white;
font-size: 100px;
height: 300px;
width: 400px;
background-color: black;
display: flex;
align-items: center;
justify-content: center;
}
.animate {
position: absolute;
opacity: 0;
transition: 0s 180s;
}
.animate:hover {
opacity: 1;
transition: 0s;
}
<div id="animate" style="background-color: orange; width: 300px; height: 200px" class="counter" data-target="500">
0
</div>
Old answer:
You would need to add a mouseenter event to the parent element. Note that the {once: true} option will make the event only fire once.
const parent = document.getElementById('parent')
parent.addEventListener('mouseenter', mouseEnterHandler, {once: true})
Then define the mouseEnterHandler callback as follows:
function mouseEnterHandler() {
for (let n of counters) {
n.style.display = 'block'
updateCount(n)
}
/* If you only have one counter then just get it by its Id:
const div = document.getElementById('hover-content')
div.style.display = 'block'
updateCount(div)
*/
}
n.style.display = 'block' will make the counter visible so no need for the css rule #parent:hover #hover-content { display:block; }.
Here is a working example:
const updateCount = n => {
const target = +n.getAttribute('data-target')
const count = +n.innerText
const speed = 5000 // change animation speed here
const inc = target / speed
if (count < target) {
n.innerText = Math.ceil(count + inc)
requestAnimationFrame(() => updateCount(n))
} else {
n.innerText = target
}
}
const counters = document.querySelectorAll('.counter')
const parent = document.getElementById('parent')
parent.addEventListener('mouseenter', mouseEnterHandler, {
once: true
})
function mouseEnterHandler() {
for (let n of counters) {
n.style.display = 'block'
updateCount(n)
}
}
.counter {
color: white;
font-size: 100px;
height: 140px;
width: 400px;
background-color: black;
display: flex;
align-items: center;
justify-content: center;
}
#hover-content {
display: none;
}
<div id="parent">
Some content
<div hidden id="hover-content" class="counter" data-target="232">0</div>
</div>

Buttons not wrapping and overflowing container in Safari 10 (fine in Edge and Chrome)

Supposed to look like the below (does in Edge & Chrome):
But looks like this in Safari 10:
I've read a few SO questions that have not help resolve the issue. Most recently this one:
Flexbox not working on button or fieldset elements
The answers are not resolving my issue.
How do I get a new button to wrap to a new line instead of overflowing the container?
Here is the Sass and ReactJS component I have. Also, I am using Bootstrap 4.
class BasicQueryCuts extends Component {
constructor(props) {
super(props);
this.state = {
cuts: {}
}
this.clearSelection = this.clearSelection.bind(this);
this.selectAll = this.selectAll.bind(this);
}
clearSelection() {
this.setState({
cuts: {}
})
this.props.clearCuts();
}
// pieces together the cuts that are selected
onCutSelect(cut, str) {
// want to make sure it stays a number
// so that it matches the server data
// tends to convert to string if not specific
cut = Number(cut);
this.setState(
({cuts: prevCuts}) => (
this.state.cuts[cut] && str !== 'all'
?
{cuts: {...prevCuts, [cut]: undefined }}
:
{cuts: {...prevCuts, [cut]: cut }}
),
() => this.props.basicQueryResults(this.state.cuts)
)
}
selectAll() {
const { country } = this.props;
let { query_data } = this.props;
if (query_data) {
if (query_data[0].Country && country) {
var data = _.filter(query_data, {'Country': country});
} else {
var data = query_data;
}
}
_.map(
data, c => {
this.onCutSelect(c.SortOrder, 'all');
}
)
}
// These buttons will allow selecting everything, or clearing the selection
renderAllNothingButtons() {
let { query_data } = this.props;
// generate the list of cuts
if (query_data) {
return (
<Row>
<Col>
<Button color='primary' key='all' className='cuts-btn' onClick={this.selectAll}>
Select All
</Button>
</Col>
<Col>
<Button color='danger' key='cancel' className='cuts-btn' onClick={this.clearSelection}>
Clear All
</Button>
</Col>
</Row>
)
}
}
// renders the cut multi-list, by first ordering what comes from
// the server and then depending on the survey
// setting up the option and value keys
renderCutsButtons() {
const { country } = this.props;
let { query_data } = this.props;
if (query_data) {
if (query_data[0].Country && country) {
var data = _.filter(query_data, {'Country': country});
} else {
var data = query_data;
}
}
// generate the list of cuts
return (
<Row>
{_.map(data, c => {
var cut = c.RptCutCat + ': ' + c.RptCut
return (
<Col key={c.SortOrder}>
<Button
className={this.state.cuts[c.SortOrder] ? 'cuts-btn-active' : 'cuts-btn'}
key={c.SortOrder}
value={c.SortOrder}
onClick={event => this.onCutSelect(event.target.value, 'single')}
>
<span>{cut}</span>
</Button>
</Col>
)}
)}
</Row>
)
}
render() {
const { query_data } = this.props;
return (
<div className='results-bq-cuts'>
{this.renderCutsButtons()}
{query_data
?
<hr />
:
null
}
{this.renderAllNothingButtons()}
{query_data
?
<hr />
:
null
}
</div>
)
}
}
.results-modal {
#media all and (max-width: 1250px) {
max-width: 95%;
}
max-width: 1200px;
.modal-content {
.modal-body {
// padding: 0;
margin-left: 13px;
margin-right: 13px;
.results-bq-cuts {
.col {
padding:2px;
}
.cuts-btn {
font-size: 11px;
padding: 3px;
width: 100%;
box-shadow: none;
}
.cuts-btn-active {
font-size: 11px;
padding: 3px;
width: 100%;
background-color: $croner-blue;
box-shadow: none;
}
h5 {
text-align: left;
}
hr {
margin-top: 5px;
margin-bottom: 5px;
}
}
}
}
}
Compiled HTML here:
Turns out it was an issue with Safari 10.0.0. Upgraded the VM I have macOS running in which upgraded Safari and now the issue is gone. Saw some responses that seemed to indicate the issue was addressed in Safari 10.1.

How to offset dataObject from referenceObject using popper.js?

How do I offset the boat from the anchor by a few pixels?
You can find a code pen where I have unsuccessfully tried to set an offset here
https://codepen.io/anon/pen/wXraLK?editors=1111
HTML
<script src="https://unpkg.com/popper.js/dist/umd/popper.min.js"></script>
<div class="anchor">Anchor</div>
<div class="boat">Boat</div>
CSS
.boat {
display: inline-block;
background-color: yellow;
}
.anchor {
display: inline-block;
background-color: gray;
}
JavaScript
var anchor = document.getElementsByClassName("anchor")[0];
var boat = document.getElementsByClassName("boat")[0];
var offsetTopModifier = function (data) {
data.offsets.popper.top += 50;
return data;
}
var popper = new Popper(
anchor,
boat,
{
placement: 'bottom-end',
modifiers: [offsetTopModifier]
}
);
This was the source that inspired my attempt:
https://github.com/FezVrasta/popper.js/issues/107
One work around was to set margins on the boat.

Truncate opposite end of string inside html table

Is there a css / html way of truncating the from the start of a string? Showing the end characters instead?
For example:
string = "A0000000982091011328885"
truncated (show start) = "A000000098..."
truncated (show end) = "...1011328885"
I've tried changing the text direction but apart from that I'm out of ideas. I am completely capable of doing this in Javascript however it'd be nice not to.
I'm also doing this within a table td, so if there is some weird table specific <element> that'd be satisfactory.
Here is a "reverse ellipsis" pen made by Roman Komarov which does exactly what you want using just pure CSS. It just requires a specific HTML markup in order to work.
<div class="box ellipsis reverse-ellipsis">
<div class="ellipsis__content">Here is some long content that doesn't fit.</div>
</div>
It also uses pseudo-elements as the ellipsis and positioned them at the start of the text.
.reverse-ellipsis::after {
content: "…";
float: left;
width: 1em;
padding: 0 1px 0 1em;
margin: -1.35em -1em;
background: #FFF;
}
var rows = document.getElementById('container').childNodes;
for (var i=0, row; row = rows[i]; i++) {
trimLeft(row);
}
function trimLeft(row){
var trimContents = function(row, node){
while (row.scrollWidth > row.offsetWidth) {
var childNode = node.firstChild;
if (!childNode)
return true;
if (childNode.nodeType == document.TEXT_NODE){
trimText(row, node, childNode);
}
else {
var empty = trimContents(row, childNode);
if (empty){
node.removeChild(childNode);
}
}
}
}
var trimText = function(row, node, textNode){
var value = '...' + textNode.nodeValue;
do {
value = '...' + value.substr(4);
textNode.nodeValue = value;
if (value == '...'){
node.removeChild(textNode);
return;
}
}
while (row.scrollWidth > row.offsetWidth);
}
trimContents(row, row);
}
#container {
width: 200px;
border: 1px solid blue;
}
#container div {
width: 100%;
overflow: hidden;
white-space: nowrap;
}
<div id="container" >
<div>A00000009sfsgsdfggdsf1011328885</div>
</div>

HTML / CSS: Nested <options> in a <select> field?

Is it possible to create nested option fields in a form drop down, much like you would create nested ul lists?
Since the change is just aesthetical, is it possible to do this with css?
You can use <optgroup> to create a single level of nesting...
<select>
<optgroup label="Options 1">
<option>Option 1.1</option>
<option>Option 1.2</option>
</optgroup>
<optgroup label="Options 2">
<option>Option 2.1</option>
<option>Option 2.2</option>
</optgroup>
</select>
Note that the group labels are not selectable options. In that case, I would recommend using the text-indent solution that is mentioned in the top answer to the question that home linked to in his comment.
Nested Accordeon select
I made this approach since I couldn´t find what I was searching. A nested accordeon select. Its CSS is very simple and can be improved. The only thing you need is an object with keys and values you want to add into the select. Keys would be subgroups, and key values (arrays and single elements) would be selectable items.
Once you have your array, only thing you need to do is to call
initAccordeon(obj);
with your data object as an argument, and the nested accordeon will appear:
const obj = {
Cars: {
SwedishCars: [
"Volvo",
"Saab"
],
GermanCars: [
"Mercedes",
{
Audi: [
"Audi A3",
"Audi A4",
"Audi A5"
]
}
]
},
Food: {
Fruits: [
"Orange",
"Apple",
"Banana"
],
SaltyFoods: [
"Pretzels",
"Burger",
"Noodles"
],
Drinks: "Water"
}
};
initAccordeon(obj); // <--------------------------- Call initialization
function accordeonAddEvents() {
Array.from(document.getElementsByClassName("accordeon-header")).forEach(function(header) {
if (header.getAttribute("listener") !== "true") {
header.addEventListener("click", function() {
this.parentNode.getElementsByClassName("accordeon-body")[0].classList.toggle("hide");
});
header.setAttribute("listener", "true");
}
});
Array.from(document.getElementsByClassName("button-group")).forEach(function(but) {
if (but.getAttribute("listener") !== "true") {
but.addEventListener("click", function() {
if (this.getAttribute("depth") === "-1") {
let header = this;
while ((header = header.parentElement) && header.className !== "accordeon");
header.getElementsByClassName("accordeon-header")[0].innerHTML = this.innerHTML;
return;
}
const groups = Array.from(this.parentNode.getElementsByClassName("accordeon-group"));
groups.forEach(g => {
if (g.getAttribute("uuid") === this.getAttribute("uuid") &&
g.getAttribute("depth") === this.getAttribute("depth")) {
g.classList.toggle("hide");
}
});
});
but.setAttribute("listener", "true");
}
});
}
function initAccordeon(data) {
accordeons = Array.from(document.getElementsByClassName("accordeon-body"));
accordeons.forEach(acc => {
acc.innerHTML = "";
const route = (subObj, keyIndex = 0, parent = acc, depth = 0) => {
const keys = Object.keys(subObj);
if (typeof subObj === 'object' && !Array.isArray(subObj) && keys.length > 0) {
while (keyIndex < keys.length) {
var but = document.createElement("button");
but.className = "button-group";
but.setAttribute("uuid", keyIndex);
but.setAttribute("depth", depth);
but.innerHTML = keys[keyIndex];
var group = document.createElement("div");
group.className = "accordeon-group hide";
group.setAttribute("uuid", keyIndex);
group.setAttribute("depth", depth);
route(subObj[keys[keyIndex]], 0, group, depth + 1);
keyIndex++;
parent.append(but);
parent.append(group);
}
} else {
if (!Array.isArray(subObj)) subObj = [subObj];
subObj.forEach((e, i) => {
if (typeof e === 'object') {
route(e, 0, parent, depth);
} else {
var but = document.createElement("button");
but.className = "button-group";
but.setAttribute("uuid", i);
but.setAttribute("depth", "-1");
but.innerHTML = e;
parent.append(but);
}
});
}
};
route(data);
});
accordeonAddEvents();
}
.accordeon {
width: 460px;
height: auto;
min-height: 340px;
font-size: 20px;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
display: block;
position: relative;
z-index: 10;
}
.accordeon-header {
display: inline-block;
width: 450px;
border: solid 0.1vw black;
border-radius: 0.2vw;
background-color: white;
padding-left: 10px;
color: black;
}
.accordeon-header:hover {
opacity: 0.7;
}
.accordeon-body {
display: block;
position: absolute;
}
.button-group {
display: block;
cursor: pointer;
width: 460px;
text-align: left;
font-size: 20px;
font-weight: bold;
}
.accordeon-group {
padding-left: 20px;
}
.accordeon-group .button-group {
width: 100%;
}
.button-group[depth="-1"] {
color: green;
}
.hide {
display: none;
}
<div class="accordeon">
<span class="accordeon-header">Select something</span>
<div class="accordeon-body hide">
</div>
</div>
You cannot nest multiple <option>s. If you want to group <option> elements, use <optgroup>.
No, not really. There is an optgroup tag which are unselectable headers you can add between sections, but nesting is not possible for <select> elements.
Look into using the optgroup tag. As for styling support, there is some, but you are at the mercy of the browser as to how far you can take it as it is a form element.
http://www.456bereastreet.com/lab/styling-form-controls-revisited/select-single-optgroup/
If you need extensive restyling, consider building your own UI widget using perhaps a nested UL structure and giving it the interaction via JavaScript.
It's possible to nest options and even make them selectables. But you'll need to use JavaScript. In this example, the code is written in TypeScript (Angular v6), but you could do the same with any other modern Javascript framework, pure Javascript or jQuery.
Imagine A, B and C are your options:
let options = [
'A',
'B',
'C'
];
You want to display them like: A->B->C (A is B's parent & B is C's parent).
And you want the user to be able to select A and C, but no B. Let's create a simple interface that will make that easier:
interface CoolOption {
content: string,
selectable: boolean,
depth: number
};
Now, your options will look like:
let selectedOption: string = null;
let options: CoolOption[] = new Array<CoolOption>();
let A: CoolOption = {
content: 'A',
selectable: true,
depth: 0
};
let B: CoolOption = {
content: 'B',
selectable: false,
depth: 1
};
let C: CoolOption = {
content: 'A',
selectable: true,
depth: 2
};
And your select template will look like:
<mat-select>
<mat-option *ngFor="let option of options" (change)="setSelectedOption($event)">
<span [style.padding-left.px]="option.depth * 5">
{{
option.content
}}
</span>
</mat-option>
</mat-select>
Simple explanation: when the user selects an option, setSelectedOption function (we'll define it next) will be called.
Also, the CSS padding-left property will be affected by the 'depth' property we set before.
A element will have a padding-left of 0 * 5 = 0px
B element will have a padding-left of 1 * 5 = 5px
C element will have a padding-left of 2 * 5 = 10px
This way we'll 'emulate' the nest effect.
At last, our setSelectedOption function:
setSelectedOption(option: CoolOption) {
if (option.selectable) {
this.selectedOption = option.content;
}
}
Basically if it's selectable, the value of selectedOption will change. Otherwise it will remain intact.
Hope this helps to somebody, and sorry for not having enough time to replicate the examples in pure JavaScript.