Issue
I'm new to python/flask, and have made a simple app which collects user input for 5 variables, and display an output that's dependant on the combination of those six variables. I've made a simple webpage with the following code to collect the user's input via clickable buttons:
function setValue(inputId, value, button) {
document.getElementById(inputId + '_input').value = value;
}
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
.button {
align-self: center;
background-color: #fff;
background-image: none;
background-position: 0 90%;
background-repeat: repeat no-repeat;
background-size: 4px 3px;
border-radius: 15px 225px 255px 15px 15px 255px 225px 15px;
border-style: solid;
border-width: 2px;
box-shadow: rgba(0, 0, 0, .2) 15px 28px 25px -18px;
box-sizing: border-box;
color: #41403e;
cursor: pointer;
display: inline-block;
font-family: Neucha, sans-serif;
font-size: 1rem;
line-height: 23px;
outline: none;
padding: .75rem;
text-decoration: none;
transition: all 235ms ease-in-out;
border-bottom-left-radius: 15px 255px;
border-bottom-right-radius: 225px 15px;
border-top-left-radius: 255px 15px;
border-top-right-radius: 15px 225px;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.button:hover {
box-shadow: rgba(0, 0, 0, .3) 2px 8px 8px -5px;
transform: translate3d(0, 2px, 0);
}
.button:focus {
box-shadow: rgba(0, 0, 0, .3) 2px 8px 4px -6px;
}
<h1>Rhesus Genotype Analyzer</h1>
<form method="POST" action="/">
<label for="D">D:</label>
<button type="button" class="button" onclick="setValue('D', 'pos', this)">Pos</button>
<button type="button" class="button" onclick="setValue('D', 'neg', this)">Neg</button>
<label for="C">C:</label>
<button type="button" class="button" onclick="setValue('C', 'pos', this)">Pos</button>
<button type="button" class="button" onclick="setValue('C', 'neg', this)">Neg</button>
<br><br>
<label for="E">E:</label>
<button type="button" class="button" onclick="setValue('E', 'pos', this)">Pos</button>
<button type="button" class="button" onclick="setValue('E', 'neg', this)">Neg</button>
<br><br>
<label for="c">c:</label>
<button type="button" class="button" onclick="setValue('c', 'pos', this)">Pos</button>
<button type="button" class="button" onclick="setValue('c', 'neg', this)">Neg</button>
<br><br>
<label for="e">e:</label>
<button type="button" class="button" onclick="setValue('e', 'pos', this)">Pos</button>
<button type="button" class="button" onclick="setValue('e', 'neg', this)">Neg</button>
<br><br>
<input type="hidden" id="D_input" name="D">
<input type="hidden" id="C_input" name="C">
<input type="hidden" id="E_input" name="E">
<input type="hidden" id="c_input" name="c">
<input type="hidden" id="e_input" name="e">
<input type="submit" value="Submit">
The issue I have is that only one of the 10 buttons displays the 'focus' state. Is it possible to treat each row of buttons independently? I.e., allow the 'D' pos or neg button to be 'focused', and allow the 'E' pos or neg to be 'focused' too, so that the user can see what they've selected?
Thanks a lot for any help!
Solution
One approach is as follows, though this answer is reliant on the :has()
CSS pseudo-class function, though this is – now – well supported in modern browsers, this approach may mean you'll need to use JavaScript depending on the browsers employed by your users.
That said, the following meets your needs (I think) and uses only HTML and CSS to do so; explanatory comments are in the code:
/*
a simple reset, in which I ensure that all elements are sized
in the same way, following the border-box algorithm which
includes assigned padding, margins, and border-width within
the assigned size of the element. I'm also setting the font
of the document to remove browser defaults:
*/
*,
::before,
::after {
box-sizing: border-box;
font-family: system-ui;
font-size: 1rem;
font-weight: 400;
margin: 0;
padding: 0;
}
/* forcing the <html> element to occupy the full size of the
viewport's block-axis (the axis upon which 'blocks' are
positioned according to the language choice of the user): */
html {
block-size: 100%;
}
/* defining some custom properties to help lay out the contents,
and its spacing: */
body {
--space-s: calc(var(--space) * 0.5);
--space: 0.5rem;
--space-l: calc(var(--space) * 2);
/* 1vb is 1% of the element's size on the block-axis, therefore
this rule sizes the body to take up the full size of the
viewport: */
min-block-size: 100vb;
/* setting the padding of the element, using the CSS var()
function along with the defined CSS custom property: */
padding: var(--space-l);
}
main {
/* as above, sizing the block-axis of the element to take 100%
of the available space: */
min-block-size: 100%;
border: 1px solid currentColor;
padding: var(--space-l);
}
h1 {
/* styling the font of the <h1> after forcing all
fonts (earlier) to be only 1rem, here we double
that size to emphasise the heading: */
font-size: 2rem;
/* setting a margin on the block-end (the side of
the element that would be nearest the next block-
level sibling element) to be in proportion to
the font-size of the current element: */
margin-block-end: 0.5em;
}
h2::after {
content: ':';
}
form {
/* using grid layout: */
display: grid;
/* setting the gap between adjacent elements within
the grid: */
gap: var(--space-l);
}
/* styling any <button> element within any <form> that
has any :invalid form-elements (<input>, <textarea>...): */
form:has(:invalid) button {
/* highlighting that clicking the button is disallowed: */
cursor: not-allowed;
/* reducing the opacity to imply that the element is
disabled: */
opacity: 0.5;
/* it would have been possible to use:
pointer-events: none;
but unfortunately that prevents the :hover pseudo-class
from matching, which means the cursor wouldn't be
changed. This is the choice I made, you may feel
that preventing user-action on the <button> is worth
that limitation. */
}
fieldset {
--indicator: hsl(0deg 60% 40% / 0.8);
align-items: center;
display: flex;
flex-flow: row wrap;
gap: var(--space-l);
isolation: isolate;
padding-block: var(--space);
padding-inline: var(--space-l);
position: relative;
}
fieldset::after {
/* I freely admit I went a bit overboard...
here we're using a linear-gradient as a background image,
the gradient running 90degrees (to the right, 0degrees is
vertical), we have transparent from 0 running to 70% of the
background-size, after which we use the colour assigned to
the custom CSS property --indicator or, if that property isn't
defined, the default colour of transparent.
The background is positioned at 100% (x-axis) and 50% (y-axis),
and sized 100% in both horizontal and vertical axes (respectively),
and we set no-repeat so that the image doesn't repeat: */
background: linear-gradient(90deg, transparent 0 70%, var(--indicator, transparent)) 100% 50% / 100% 100% no-repeat;
/* to occlude the error gradient we use the clip-path, which allows
us to limit the visibility of the element according to the
shape we define. We're using the polygon() function here,
which takes a list of comma-separated positions (for x and y
respectively), to define a number of points; within the space
formed by those points the content is visible. Outside, the
content is hidden ('clipped'): */
clip-path:
polygon(
100% 0,
100% 0,
100% 100%,
100% 100%
);
/* content is a mandatory property to have the pseudo-element
be rendered, an empty string is valid 'content' for this: */
content: '';
/* using inset to specify the position of the pseudo-element in
reference to its containing block, here it's positioned 0
distance from each edge (top, right, bottom, left respectively)
*/
inset: 0;
position: absolute;
/* we're transitioning the clip-path property, over 500ms and
with a linear easing function: */
transition: clip-path 500ms linear;
/* to position the pseudo-element behind all content of its
containing block ancestor, appearing in front of only
that element's background: */
z-index: -1;
}
/* styling the ::after pseudo-element of any <fieldset> that contains
a :user-invalid form-element: */
fieldset:has(:user-invalid)::after {
clip-path: polygon( 0 0, 100% 0, 100% 100%, 0 100%);
}
label {
/* using a non-'static' (the default) value for the
position property, in order that this element becomes
the containing block for its descendants: */
position: relative;
}
/* the element that contains the text associated with each
<input> element: */
.labelText {
/* various CSS custom properties: */
--base-h: 0deg;
--base-s: 20%;
--base-l: 30%;
--base-z-pos: 0px;
--border-color: hsl(var(--base-h) var(--base-s) var(--base-l) / 1);
--offset-x: 0px;
--offset-y: 6px;
--spread: 0.3rem;
/* to ensure that the element is sized to have equal size on both
its block, and inline, axes: */
aspect-ratio: 1;
/* a subtle background, I left it in but it's not all that visible: */
background: repeating-linear-gradient(135deg, snow 0 0.5rem, azure 0.5rem 0.75rem);
border: 2px solid var(--border-color);
/* defining the block-size of the element, which will influence the
inline-size via the aspect-ratio property: */
block-size: 3.5rem;
display: grid;
/* using drop-shadow in place of box-shadow, as it's a little more accurate
(if you remove the background of this element, the visible content of the
element generates an accurate shadow, whereas box-shadow gives a shadow to
the outer shape of the element, effectively just a rectangular polygon): */
filter: drop-shadow(var(--offset-x) var(--offset-y) var(--spread) var(--border-color));
/* centering the content within the element: */
place-content: center;
/* using perspective and translateZ() to move the element somewhat
faithfully in 3d 'space': */
transform: perspective(500px) translateZ(var(--base-z-pos));
/* required to enable any sort of accuracy within the 3d space: */
transform-style: preserve-3d;
/* defining a transition to apply to every (animatable) property: */
transition: all 300ms ease-in-out;
/* defining the precise properties to transition: */
transition-property: transform, filter;
}
/* updating CSS properties for:
any .labelText element when hovered: */
.labelText:hover,
/* any .labelText element that is immediately preceded
by an <input> which is in any of the states listed
within the :is() pseudo-class function: */
input:is(:active, :focus, :checked) + .labelText {
--base-h: 120deg;
--base-z-pos: -50px;
}
/* as above, but to specify different properties that
I didn't want available to the .labelText:hover: */
input:is(:active, :focus, :checked) + .labelText {
--base-s: 40%;
--base-l: 60%;
--base-z-pos: -80px;
}
/* we're hiding the <input>, since we're "replacing" it
with the <span>: */
input {
/* scaling it very, very, very small: */
scale: 0.01;
/* using absolute position to remove the element from
the document flow: */
position: absolute;
}
.error {
background-color: snow;
border: 2px solid currentColor;
/* using a large border-radius to have the browser
style the borders into a 'pill' shape, with
rounded ends: */
border-radius: 5rem;
/* using inset to position the element with reference
to the containing block, here the 'auto' values
are equivalent to not setting a property and allowing
the browser to calculate it, but all values must be
provided so we have to specify a value; -150% positions
the pseudo-element's right side 150% outside of the
containing block. Negative numbers move away from the
center of the containing block, positive numbers
move towards the center (on the relevant axis): */
inset: auto -150% auto auto;
padding-block: var(--space-s);
padding-inline: var(--space);
position: absolute;
transition: inset 500ms linear;
}
/* :user-invalid matches form-elements that have been given,
or retained invalid values due to the action/inaction of
the user; the pseudo-class only matches elements after
the user attempts to submit the form; with this selector
we're seleing any .error message within a <fieldset> that
itself contains an <input> which is invalid following the
user's attempt to submit the form: */
fieldset:has(input:user-invalid) .error {
inset: auto 20% auto auto;
}
<main>
<h1>Rhesus Genotype Analyzer</h1>
<form method="POST" action="/">
<fieldset>
<!-- I opted to use an <h2> element to label the grouped elements,
as a <legend> wouldn't be styled as you seem to wish: -->
<h2>D</h2>
<!-- using <label> elements to associate the text, in the nested <span>
with the <input>, no "for" attribute is required as the relationship
is implicit due to the <input> being nested within the <label> -->
<label>
<!-- the <span> follows the <input> in order to use
the validity, and user-interactivity, pseudo-
classes of the <input> to style the adjacent
<span>: -->
<input type="radio" value="pos" name="D" required="">
<span class="labelText">pos</span>
</label>
<label>
<input type="radio" value="neg" name="D">
<span class="labelText">neg</span>
</label>
<!-- using a <span> to contain an error message in the event that the
user tries to submit an incomplete form: -->
<span class="error">This field is mandatory, please select one of the options.</span>
</fieldset>
<fieldset>
<h2>C</h2>
<label>
<input type="radio" value="pos" name="C" required="">
<span class="labelText">pos</span>
</label>
<label>
<input type="radio" value="neg" name="C">
<span class="labelText">neg</span>
</label>
<span class="error">This field is mandatory, please select one of the options.</span>
</fieldset>
<fieldset>
<h2>E</h2>
<label>
<input type="radio" value="pos" name="E" required="">
<span class="labelText">pos</span>
</label>
<label>
<input type="radio" value="neg" name="E">
<span class="labelText">neg</span>
</label>
<span class="error">This field is mandatory, please select one of the options.</span>
</fieldset>
<fieldset>
<h2>c</h2>
<label>
<input type="radio" value="pos" name="c" required="">
<span class="labelText">pos</span>
</label>
<label>
<input type="radio" value="neg" name="c">
<span class="labelText">neg</span>
</label>
<span class="error">This field is mandatory, please select one of the options.</span>
</fieldset>
<fieldset>
<h2>e</h2>
<label>
<input type="radio" value="pos" name="e" required="">
<span class="labelText">pos</span>
</label>
<label>
<input type="radio" value="neg" name="e">
<span class="labelText">neg</span>
</label>
<span class="error">This field is mandatory, please select one of the options.</span>
</fieldset>
<!-- rather than use an <input> to submit the form, I opted to use
a <button>; the default action of a <button> within a <form> is
to submit the form, which is why there's no explicit 'submit'
attribute: -->
<button>Submit</button>
</form>
</main>
References:
:active
.align-items
.aspect-ratio
.background
.background-color
.block-size
.border
.border-radius
.box-sizing
.clip-path
.content
.- CSS custom properties.
- CSS logical properties.
cursor
.display
.filter
.flex-flow
.:focus
.font-family
.font-size
.font-weight
.gap
.:has()
, compatibility.:hover
.inset
.isolation
.margin
.margin-block-end
.min-block-size
.opacity
.padding
.padding-block
.padding-inline
.place-content
.pointer-events
.position
.scale
.transform
.transform-style
.transition
.transition-property
.:user-invalid
.z-index
.
Answered By - David Thomas
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.