Custom CSS with Checkbox and NgFor - javascript

I am trying to get a custom check box 'clickable' when being iterated on with an *ngFor. I have the custom CSS, but nothing is working on the click event.
I am guessing that it is because of the for property on the label, but I don't know how to fix it.
Stackblitz: https://stackblitz.com/edit/angular-4znmwv?embed=1&file=src/app/app.component.css&view=editor
HTML:
<div *ngFor="let item of data">
<input type="checkbox" [checked]= "item.selected" (change)="setChange(item, $event)">
<label htmlFor="{{item.name}}">{{item.name}}</label>
</div>
CSS:
input[type="checkbox"] {
position: absolute;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
}
input[type="checkbox"] + label {
display: block;
position: relative;
padding: 0 1.5rem;
}
input[type="checkbox"] + label::before {
content: '';
position: relative;
display: inline-block;
margin-right: 10px;
width: 20px;
height: 20px;
color: #2a3037;
background-color: #fff;
border-color: #1c8d3e;
}
input[type="checkbox"]:checked + label::before {
color: #fff;
background-color: #138630;
border-color: #000000;
}
input[type="checkbox"]:checked + label::after {
content: '';
position: absolute;
top: 3px;
left: 27px;
border-left: 2px solid black;
border-bottom: 2px solid black;
height: 6px;
width: 13px;
transform: rotate(-45deg);
}
input[type="checkbox"]:focus + label::before {
outline: #5d9dd5 solid 1px;
box-shadow: 0 0px 8px #5e9ed6;
}
input[type="checkbox"]:disabled + label {
color: #575757;
}
input[type="checkbox"]:disabled + label::before {
background: #ddb862;
}

try like this , I have just set the for attribute for the label base of the checkbox id that have been set by item name
<div *ngFor="let item of data">
<input type="checkbox" [id]="item.name">
<label [for]="item.name">{{item.name}}</label>
</div>
demo 🚀

<div *ngFor="let item of data;let i = index">
<input type="checkbox" [checked]= "item.selected" id="{{item.name}}" (change)="setChange(item, $event)">
<label for="{{item.name}}">{{item.name}}</label>
</div>
Try this.
Set the id as the same as the for of the label it will work properly and for is only the attribute name for label and not htmlFor.
Here i have added index for reference if you want the id to be different from the itemname which is dynamically gives index Use like this
<div *ngFor="let item of data;let i = index">
<input type="checkbox" [checked]= "item.selected" id="{{'checkbox'+i}}" (change)="setChange(item, $event)">
<label for="{{'checkbox' + i}}">{{item.name}}</label>
</div>
This way your id would be something like this
checkbox1
checkbox2
...

Related

How to check a specific checkbox in a pure CSS Accordion via URL?

I use a pure CSS Accordion to present my content. The Accordion works with normal checkboxes. Now I want to implement, that by sending a simple link, a single checkbox entry will be checked and with the help of an anchor the browser should jump to that entry and show the specific content to the reader.
The whole thing should be done preferably without a scripting or programming language, but after a lot of research I think that at least JavaScript will be required (it must run on the client side, so no PHP or similar).
I have searched and tested a lot but unfortunately I have not found any suitable solution that would work.
```
<!DOCTYPE html>
<html>
<head>
<title>My example Website</title>
</head>
<body>
<style>
body {
font-size: 21px;
font-family: Tahoma, Geneva, sans-serif;
max-width: 550px;
margin: 0 auto;
background-color: black;
}
input {
display: none;
}
label {
display: block;
padding: 8px 22px;
margin: 0 0 1px 0;
cursor: pointer;
background: #181818;
border: 1px solid white;
border-radius: 5px;
color: #FFF;
position: relative;
}
label:hover {
background: white;
border: 1px solid white;
color:black;
}
label::after {
content: '+';
font-size: 22px;
font-weight: bold;
position: absolute;
right: 10px;
top: 2px;
}
input:checked + label::after {
content: '-';
right: 14px;
top: 3px;
}
.content {
background: #DBEECD;
background: -webkit-linear-gradient(bottom right, #DBEECD, #EBD1CD);
background: -moz-linear-gradient(bottom right, #DBEECD, #EBD1CD);
background: linear-gradient(to top left, #DBEECD, #EBD1CD);
padding: 10px 25px 10px 25px;
border: 1px solid #A7A7A7;
margin: 0 0 1px 0;
border-radius: 1px;
}
input + label + .content {
display: none;
}
input:checked + label + .content {
display: block;
}
</style>
<input type="checkbox" id="title1" name="contentbox" />
<label for="title1">Content 1</label>
<div class="content">
My Content 1
</div>
</div>
<input type="checkbox" id="title2" name="contentbox" />
<label for="title2">Content 2</label>
<div class="content">
My Content 2
</div>
</div>
<input type="checkbox" id="title3" name="contentbox" />
<label for="title3">Content 3</label>
<div class="content">
My Content 3
</div>
</body>
</html>
```
You're correct that JavaScript is required. I have provided a solution, but I haven't tested it, because it's not possible to test in the snippet. It should select the relevant checkbox when a hash tag is detected in the URL that corresponds with a checkbox ID.
So you would use some time https://www.website.com/#title1
// Check if URL of browwser window has hash tag
if (location.hash) {
// Get URL hash tag
const hash = window.location.hash;
// Select checkbox with ID of hashtag
const checkbox = document.querySelector(hash);
// Check if checkbox exists
if(checkbox) {
// Set selected checkbox as checked
checkbox.checked = true;
}
}
body {
font-size: 21px;
font-family: Tahoma, Geneva, sans-serif;
max-width: 550px;
margin: 0 auto;
background-color: black;
}
input {
display: none;
}
label {
display: block;
padding: 8px 22px;
margin: 0 0 1px 0;
cursor: pointer;
background: #181818;
border: 1px solid white;
border-radius: 5px;
color: #FFF;
position: relative;
}
label:hover {
background: white;
border: 1px solid white;
color:black;
}
label::after {
content: '+';
font-size: 22px;
font-weight: bold;
position: absolute;
right: 10px;
top: 2px;
}
input:checked + label::after {
content: '-';
right: 14px;
top: 3px;
}
.content {
background: #DBEECD;
background: -webkit-linear-gradient(bottom right, #DBEECD, #EBD1CD);
background: -moz-linear-gradient(bottom right, #DBEECD, #EBD1CD);
background: linear-gradient(to top left, #DBEECD, #EBD1CD);
padding: 10px 25px 10px 25px;
border: 1px solid #A7A7A7;
margin: 0 0 1px 0;
border-radius: 1px;
}
input + label + .content {
display: none;
}
input:checked + label + .content {
display: block;
}
<input type="checkbox" id="title1" name="contentbox" />
<label for="title1">Content 1</label>
<div class="content">
My Content 1
</div>
<input type="checkbox" id="title2" name="contentbox" />
<label for="title2">Content 2</label>
<div class="content">
My Content 2
</div>
<input type="checkbox" id="title3" name="contentbox" />
<label for="title3">Content 3</label>
<div class="content">
My Content 3
</div>

How to change onClick button in knockoutjs

I have a html with some css:
<label class="label-checkbox">
<input type="checkbox" data-bind="click: clickedMultipleServicesButton, checked: checkedMultipleServicesButton, css: {checked: true}">
<span style="font-size:14px !important">Test Button</span>
</label>
<style>
label.label-checkbox {
cursor: pointer;
}
label.label-checkbox input {
position: absolute;
top: 0;
left: 0;
visibility: hidden;
pointer-events: none;
}
label.label-checkbox span {
padding: 8px 11px;
border: 1px solid #ccc;
display: inline-block;
color: #ffffff;
border-radius: 6px;
margin: 7px;
background: #253965;
user-select: none;
}
label.label-checkbox input:checked + span {
box-shadow: inset 1px 2px 5px #777;
transform: translateY(1px);
background: #ffd800;
}
</style>
It works when I remove data-bindings. On click it colors the blue button a yellow one and opposite.
But however, when I add data-binding: click it doesnt work anymore.
I assume that I need to dynamically attach css classes on click event?
Desired behavior:
I want to have data-bind="click: someFunction" but when I click, to have css behavior, and to have function that will catch when is the button checked, and when it is not.
Yes I'm not sure how to manipulate pseudo-classes in the knockout but you can dynamically add and remove CSS class:
<label class="label-checkbox" >
<input type="checkbox" data-bind="click: clickedMultipleServicesButton, css: checkedMultipleServicesButton() && 'checked'"/>
<span style="font-size:14px !important">Test Button</span>
</label>
<style>
label.label-checkbox {
cursor: pointer;
}
label.label-checkbox input {
position: absolute;
top: 0;
left: 0;
visibility: hidden;
pointer-events: none;
}
label.label-checkbox span {
padding: 8px 11px;
border: 1px solid #ccc;
display: inline-block;
color: #ffffff;
border-radius: 6px;
margin: 7px;
background: #253965;
user-select: none;
}
label.label-checkbox input.checked + span {
box-shadow: inset 1px 2px 5px #777;
transform: translateY(1px);
background: #ffd800;
}
</style>
note that I turned your input:checked into input.checked.
Code in the view model:
self.checkedMultipleServicesButton = ko.observable(false);
self.clickedMultipleServicesButton = function (e) {
self.checkedMultipleServicesButton(!self.checkedMultipleServicesButton());
}

Active Toggle switch to change pricing

I built a page that has three payment plans. There is also a Toggle switch, to change between Monthly and Annually. I have the price set to ex. $19.99 for monthly and when I click the toggle button to switch to annually I want the price to change to $199.99. Here is the code I have so far. All HTML and CSS so now my JS isn't working for it. What's wrong?
function myFunction() {
var x = document.getElementsByClassName("switch");
if (x.innerHTML === "$19.99") {
x.innerHTML = "$199.99";
} else {
x.innerHTML = "$19.99";
}
}
input.cmn-toggle-round:checked+label:after {
margin-left: 32px;
}
.switch {
position: relative;
left: 47.5%;
}
.cmn-toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.cmn-toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.cmn-toggle-round+label {
padding: 2px;
width: 50px;
height: 20px;
background-color: #dddddd;
border-radius: 60px;
}
input.cmn-toggle-round+label:before,
input.cmn-toggle-round+label:after {
display: block;
position: absolute;
top: 1px;
left: 1px;
bottom: 1px;
content: "";
}
input.cmn-toggle-round+label:before {
right: 1px;
background-color: #f1f1f1;
border-radius: 60px;
transition: background 0.4s;
}
input.cmn-toggle-round+label:after {
width: 20px;
background-color: #fff;
border-radius: 100%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
transition: margin 0.4s;
}
input.cmn-toggle-round:checked+label:before {
background-image: Linear-Gradient( to left, hsl(236, 72%, 79%), hsl(237, 63%, 64%)
);
}
<div class="switch" onclick="myFunction()">
<input id="cmn-toggle-1" class="cmn-toggle cmn-toggle-round" type="checkbox" />
<label for="cmn-toggle-1"></label>
</div>
There are several problem with your code. First you compare against the innerHTML property which isn't initially set this can not work until you have a default value.
The next is that you want to set the wrapper containers innerHTML which will remove the button.
In the solution below I introduced an paragraph with an initialValue of 199.95 and then toggle on it.
Also I replaced the incline scripting and used instead an eventListener.
let inp = document.getElementById("cmn-toggle-1");
inp.addEventListener("click", myFunction);
function myFunction() {
var x = document.getElementById("par");
if(x.innerHTML === "$199.99"){
x.innerHTML = "$19.99";
}else{
x.innerHTML = "$199.99";
}
}
input.cmn-toggle-round:checked+label:after {
margin-left: 32px;
}
.switch {
position: relative;
left: 47.5%;
}
.cmn-toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.cmn-toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.cmn-toggle-round+label {
padding: 2px;
width: 50px;
height: 20px;
background-color: #dddddd;
border-radius: 60px;
}
input.cmn-toggle-round+label:before,
input.cmn-toggle-round+label:after {
display: block;
position: absolute;
top: 1px;
left: 1px;
bottom: 1px;
content: "";
}
input.cmn-toggle-round+label:before {
right: 1px;
background-color: #f1f1f1;
border-radius: 60px;
transition: background 0.4s;
}
input.cmn-toggle-round+label:after {
width: 20px;
background-color: #fff;
border-radius: 100%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
transition: margin 0.4s;
}
input.cmn-toggle-round:checked+label:before {
background-image: Linear-Gradient( to left, hsl(236, 72%, 79%), hsl(237, 63%, 64%));
}
<div class="switch">
<input id="cmn-toggle-1" class="cmn-toggle cmn-toggle-round" type="checkbox" />
<label for="cmn-toggle-1"></label>
<p id="par">$199.99</p>
</div>
Having just read the comment about using the code to manipulate prices for several elements you need a different approach as using an ID will no longer work - ID attributes must be unique so unless you get tricky with numerically incremented indices and the like ( messy ) you can inspect the target attribute of any click event ( or other type ) and use parent/child/sibling selectors to identify and manipulate nodes of interest.
The HTML below was slightly modified by adding dataset attributes and ignoring any ID attributes present. The CSS was hastily fudged to facilitate the display.... but I think it shows how you might do it.
An event listener is assigned to each parent DIV (.switch) and the currentTarget property of the event is used to find the label which actually receives the click. Using the combination of parentNode and querySelector we can identify the nodes we need to make the logic work
document.querySelectorAll('div.switch').forEach( div=>{
div.addEventListener('click',e=>{
if( e.target === e.target.parentNode.lastElementChild ) {
let span=e.target.parentNode.querySelector('span');
document.documentElement.style.setProperty('--currency','$')
span.dataset.price=e.currentTarget.dataset.state == 1 ? e.currentTarget.dataset.annual : e.currentTarget.dataset.month;
span.dataset.period=e.currentTarget.dataset.state == 1 ? 'Monthly:' : 'Annually:';
span.dataset.currency=document.documentElement.style.getPropertyValue('--currency');
e.currentTarget.dataset.state = 1 - Number( e.currentTarget.dataset.state );
}
})
})
input.cmn-toggle-round:checked + label:after {
margin-left: 32px;
}
.switch {
--left:47.5%;
position: relative;
left: var(--left);
margin:1rem 0 1.5rem 0;
width:calc(80% - var(--left));
}
.cmn-toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.cmn-toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.cmn-toggle-round+label {
padding: 2px;
width: 50px;
height: 20px;
background-color: #dddddd;
border-radius: 60px;
}
input.cmn-toggle-round+label:before,
input.cmn-toggle-round+label:after {
display: block;
position: absolute;
top: 1px;
left: 1px;
bottom: 1px;
content: "";
}
input.cmn-toggle-round+label:before {
right: 1px;
background-color: #f1f1f1;
border-radius: 60px;
transition: background 0.4s;
}
input.cmn-toggle-round + label:after {
width: 20px;
background-color: #fff;
border-radius: 100%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
transition: margin 0.4s;
}
input.cmn-toggle-round:checked + label:before {
background-image: Linear-Gradient( to left, hsl(236, 72%, 79%), hsl(237, 63%, 64%) );
}
span:after{
display:inline-block;
float:right;
clear:none;
margin:0;
content:attr(data-period)' 'attr(data-currency)attr(data-price)!important;
}
<div class='switch' data-state=0 data-month='199.99' data-annual='19.99'>
<span></span>
<input id='cmn-toggle-1' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-1'></label>
</div>
<div class='switch' data-state=0 data-month='259.99' data-annual='23.99'>
<span></span>
<input id='cmn-toggle-2' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-2'></label>
</div>
<div class='switch' data-state=0 data-month='150.00' data-annual='75.00'>
<span></span>
<input id='cmn-toggle-3' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-3'></label>
</div>
<div class='switch' data-state=0 data-month='652.50' data-annual='480.50'>
<span></span>
<input id='cmn-toggle-4' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-4'></label>
</div>
<div class='switch' data-state=0 data-month='40.00' data-annual='19.99'>
<span></span>
<input id='cmn-toggle-5' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-5'></label>
</div>
<div class='switch' data-state=0 data-month='300.00' data-annual='14.99'>
<span></span>
<input id='cmn-toggle-6' class='cmn-toggle cmn-toggle-round' type='checkbox' />
<label for='cmn-toggle-6'></label>
</div>

Masking labels for Checkboxes - The correct way?

I am aware that we can create customised checkboxes using <label for="#checkbox_id"> using the for attribute and referencing the input's id.
I need to know which would be the best way to hide the background checkbox tag i.e, <input type="checkbox" class="masked-input" id="checkbox_id"/>
I'm currently using .masked-input{display: none;} .No issues for me so far.
But I have seen in many webpages, they don't use the display:none property.
Instead they use
.checkbox{
height: 0;
width: 0;
border: 0;
overflow: hidden;
visibility: hidden;
}
Why wouldn't they just use display:none; and keep this simple? Or am I missing something or doing it the wrong way?
User Accessibility Issue:
Its a very straight forward question. You should use
.checkbox{
height: 0;
width: 0;
border: 0;
overflow: hidden;
visibility: hidden;
}
or
.checkbox{
opacity: 0;
}
because if you use display: none; it will be hard to access by the user. For instance if the user is trying to access the checkbox by pressing the tab key, the checkbox with the display none property will get skipped from the focus.
In our example below, the second checkbox property is set to display: none; and other two checkboxes are set with opacity: 0; you can see the focus by pressing tab key and how the second checkbox is getting skipped.
.styled-checkbox {
position: absolute;
opacity: 0;
}
#styled-checkbox-2{
display: none;
}
.styled-checkbox + label {
position: relative;
cursor: pointer;
padding: 0;
}
.styled-checkbox + label:before {
content: '';
margin-right: 10px;
display: inline-block;
vertical-align: text-top;
width: 20px;
height: 20px;
background: white;
}
.styled-checkbox:hover + label:before {
background: #f35429;
}
.styled-checkbox:focus + label:before {
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.12);
}
.styled-checkbox:checked + label:before {
background: #f35429;
}
.styled-checkbox:disabled + label {
color: #b8b8b8;
cursor: auto;
}
.styled-checkbox:disabled + label:before {
box-shadow: none;
background: #ddd;
}
.styled-checkbox:checked + label:after {
content: '';
position: absolute;
left: 5px;
top: 9px;
background: white;
width: 2px;
height: 2px;
box-shadow: 2px 0 0 white, 4px 0 0 white, 4px -2px 0 white, 4px -4px 0 white, 4px -6px 0 white, 4px -8px 0 white;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
html {
background: lightgray;
}
body {
font-family: 'Source Sans Pro', sans-serif;
}
.unstyled {
margin: 0;
padding: 0;
list-style-type: none;
}
li {
margin: 20px 0;
}
.centered {
width: 300px;
margin: auto;
}
.title {
text-align: center;
color: #4571ec;
}
<h1 class="title">Pure CSS Custom Checkboxes</h1>
<ul class="unstyled centered">
<li>
<input class="styled-checkbox" id="styled-checkbox-1" type="checkbox" value="value1">
<label for="styled-checkbox-1">Checkbox</label>
</li>
<li>
<input class="styled-checkbox" id="styled-checkbox-2" type="checkbox" value="value2">
<label for="styled-checkbox-2">CSS Only</label>
</li>
<li>
<input class="styled-checkbox" id="styled-checkbox-3" type="checkbox" value="value4">
<label for="styled-checkbox-3">Fourth option</label>
</li>
</ul>
The code you use is often used when there is a custom styled checkbox. They hide the original checkbox and display a custom one instead. In order to still keep the functionality they only hide the checkbox.
If you use display: none the complete checkbox will be removed from the DOM, making it impossible to click.
As https://kyusuf.com/post/completely-css-custom-checkbox-radio-buttons-and-select-boxes states:
Note that we are hiding the input with z-index: -1; and opacity: 0; -
using display: none; or visibility: hidden; would stop the inputs
functioning correctly. Onto the .control__indicator - this is what we
will style to look like checkboxes/radio buttons.

Custom Radio button selection bug in angular 6 (inside of ngFor)

Selecting radio input inside one item in ngFor triggers other radio inputs checked . Created a demo .
Html
<div class='section' *ngFor="let item of radioData">
<div class="radio-selection">
<input type="radio" attr.name="radio-{{item.Id}}" value="true" attr.id="radio-first-{{item.Id}}" [(ngModel)]='item.IsSelected'>
<label attr.for="radio-first-{{item.Id}}">Radio-first {{item.Id}}--value={{item.IsSelected}}</label>
</div>
<div class="radio-selection">
<input type="radio" attr.name="radio-{{item.Id}}" value="false" attr.id="radio-second-{{item.Id}}" [(ngModel)]='item.IsSelected'>
<label attr.for="radio-second-{{item.Id}}">Radio-second {{item.Id}}--value={{item.IsSelected}}</label>
</div>
</div>
css
.section {
border:1px solid #000;
margin-bottom:1rem;
}
.radio-selection {
input[type="radio"] {
display: none;
}
input[type="radio"] + label {
position: relative;
display: inline-block;
vertical-align: middle;
margin: 5px;
cursor: pointer;
&:hover {
&:before {
border-color: green;
}
}
}
input[type="radio"] + label:before {
content: "";
background: #fff;
border: 1px solid #333;
display: inline-block;
vertical-align: middle;
width: 15px;
height: 15px;
margin-right: 10px;
text-align: center;
padding: 2px;
border-radius:50%;
}
input[type="radio"]:checked + label:before {
background:red;
box-shadow: inset 0px 0px 0px 2.5px #fff;
}
}
Thanks
Use name instead of attr.name to set the name of the input elements (see this stackblitz):
<input type="radio" name="radio-{{item.Id}}" ...>
or use the property binding syntax (see this stackblitz):
<input type="radio" [name]="'radio-'+item.Id" ...>

Categories