How to add categorical legend to Python Folium map? - javascript

I would like to add a categorical/numerical legend to a folium map like addLenged() from R does.
Examples:
Numerical legend
I also would like to add a categorical legend like this:
Categorical legend
For now I only have this code, I am stuck trying to achieve what addLegend from R does.
Function
def add_categorical_legend(folium_map, title, colors, labels):
if len(colors) != len(labels):
raise ValueError("colors and labels must have the same length.")
color_by_label = dict(zip(labels, colors))
legend_categories = ""
for label, color in color_by_label.items():
legend_categories += f"<li><span style='background:{color}'></span>{label}</li>"
legend_html = f"""
<div id='maplegend' class='maplegend'>
<div class='legend-title'>{title}</div>
<div class='legend-scale'>
<ul class='legend-labels'>
{legend_categories}
</ul>
</div>
</div>
"""
script = f"""
<script type="text/javascript">
var oneTimeExecution = (function() {{
var executed = false;
return function() {{
if (!executed) {{
var checkExist = setInterval(function() {{
if ((document.getElementsByClassName('leaflet-top leaflet-right').length) || (!executed)) {{
document.getElementsByClassName('leaflet-top leaflet-right')[0].style.display = "flex"
document.getElementsByClassName('leaflet-top leaflet-right')[0].style.flexDirection = "column"
document.getElementsByClassName('leaflet-top leaflet-right')[0].innerHTML += `{legend_html}`;
clearInterval(checkExist);
executed = true;
}}
}}, 100);
}}
}};
}})();
oneTimeExecution()
</script>
"""
css = """
<style type='text/css'>
.maplegend {
z-index:9999;
float:right;
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
border: 2px solid #bbb;
padding: 10px;
font-size:12px;
positon: relative;
}
.maplegend .legend-title {
text-align: left;
margin-bottom: 5px;
font-weight: bold;
font-size: 90%;
}
.maplegend .legend-scale ul {
margin: 0;
margin-bottom: 5px;
padding: 0;
float: left;
list-style: none;
}
.maplegend .legend-scale ul li {
font-size: 80%;
list-style: none;
margin-left: 0;
line-height: 18px;
margin-bottom: 2px;
}
.maplegend ul.legend-labels li span {
display: block;
float: left;
height: 16px;
width: 30px;
margin-right: 5px;
margin-left: 0;
border: 0px solid #ccc;
}
.maplegend .legend-source {
font-size: 80%;
color: #777;
clear: both;
}
.maplegend a {
color: #777;
}
</style>
"""
folium_map.get_root().header.add_child(folium.Element(script + css))
return folium_map
Reproducible example
import folium
m = folium.Map()
m = add_categorical_legend(m, 'My title',
colors = ['#000','#03cafc'],
labels = ['Heat', 'Cold'])
m = add_categorical_legend(m, 'My title 2',
colors = ['#F23','#777'],
labels = ['Heat 2', 'Cold 2'])
m.save("map.html")
m
Result
Problems
However the legend I have made conflicts with folium LayerControls and they stop working which for me is a very big problem because legends are for describing what I am using in LayerControls. I don't know what is the reason.
Also this only works for categorical data.
Important
I would like that legends could be added to top | bot & right | left positions like addLegend from R does. Giving absolute position is not an option.
Any help is appreciated
NOTE: I don't want to use branca color map because I would like to have a legend like a have shown you in the images

I'm not experienced with folium, but I think the reference legend provided by the official will help you solve your problem: I applied the example on this page to the Choropleth example. You're more experienced with the web code, so customize it. I checked and did not find any of the features provided by folium that can be configured for deployment.
import pandas as pd
import folium
import branca
legend_html = '''
{% macro html(this, kwargs) %}
<div style="
position: fixed;
bottom: 50px;
left: 50px;
width: 250px;
height: 80px;
z-index:9999;
font-size:14px;
">
<p><a style="color:#000000;font-size:150%;margin-left:20px;">♦</a> Heat</p>
<p><a style="color:#03cafc;font-size:150%;margin-left:20px;">♦</a> Cold</p>
</div>
<div style="
position: fixed;
bottom: 50px;
left: 50px;
width: 150px;
height: 80px;
z-index:9998;
font-size:14px;
background-color: #ffffff;
opacity: 0.7;
">
</div>
{% endmacro %}
'''
legend = branca.element.MacroElement()
legend._template = branca.element.Template(legend_html)
url = 'https://raw.githubusercontent.com/python-visualization/folium/master/examples/data'
state_geo = f'{url}/us-states.json'
state_unemployment = f'{url}/US_Unemployment_Oct2012.csv'
state_data = pd.read_csv(state_unemployment)
m = folium.Map(location=[48, -102], zoom_start=4)
folium.Choropleth(
geo_data=state_geo,
name='choropleth',
data=state_data,
columns=['State', 'Unemployment'],
key_on='feature.id',
fill_color='YlGn',
fill_opacity=0.7,
line_opacity=0.2,
legend_name='Unemployment Rate (%)'
).add_to(m)
folium.LayerControl().add_to(m)
m.get_root().add_child(legend)
m

Related

Accessible input elements without label [duplicate]

Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?
I've been looking for a lightweight, dependency free dual slider for some time (it seemed crazy to import jQuery just for this) and there don't seem to be many out there. I ended up modifying #Wildhoney's code a bit and really like it.
function getVals(){
// Get slider values
var parent = this.parentNode;
var slides = parent.getElementsByTagName("input");
var slide1 = parseFloat( slides[0].value );
var slide2 = parseFloat( slides[1].value );
// Neither slider will clip the other, so make sure we determine which is larger
if( slide1 > slide2 ){ var tmp = slide2; slide2 = slide1; slide1 = tmp; }
var displayElement = parent.getElementsByClassName("rangeValues")[0];
displayElement.innerHTML = slide1 + " - " + slide2;
}
window.onload = function(){
// Initialize Sliders
var sliderSections = document.getElementsByClassName("range-slider");
for( var x = 0; x < sliderSections.length; x++ ){
var sliders = sliderSections[x].getElementsByTagName("input");
for( var y = 0; y < sliders.length; y++ ){
if( sliders[y].type ==="range" ){
sliders[y].oninput = getVals;
// Manually trigger event first time to display values
sliders[y].oninput();
}
}
}
}
section.range-slider {
position: relative;
width: 200px;
height: 35px;
text-align: center;
}
section.range-slider input {
pointer-events: none;
position: absolute;
overflow: hidden;
left: 0;
top: 15px;
width: 200px;
outline: none;
height: 18px;
margin: 0;
padding: 0;
}
section.range-slider input::-webkit-slider-thumb {
pointer-events: all;
position: relative;
z-index: 1;
outline: 0;
}
section.range-slider input::-moz-range-thumb {
pointer-events: all;
position: relative;
z-index: 10;
-moz-appearance: none;
width: 9px;
}
section.range-slider input::-moz-range-track {
position: relative;
z-index: -1;
background-color: rgba(0, 0, 0, 1);
border: 0;
}
section.range-slider input:last-of-type::-moz-range-track {
-moz-appearance: none;
background: none transparent;
border: 0;
}
section.range-slider input[type=range]::-moz-focus-outer {
border: 0;
}
<!-- This block can be reused as many times as needed -->
<section class="range-slider">
<span class="rangeValues"></span>
<input value="5" min="0" max="15" step="0.5" type="range">
<input value="10" min="0" max="15" step="0.5" type="range">
</section>
No, the HTML5 range input only accepts one input. I would recommend you to use something like the jQuery UI range slider for that task.
Coming late, but noUiSlider avoids having a jQuery-ui dependency, which the accepted answer does not. Its only "caveat" is IE support is for IE9 and newer, if legacy IE is a deal breaker for you.
It's also free, open source and can be used in commercial projects without restrictions.
Installation: Download noUiSlider, extract the CSS and JS file somewhere in your site file system, and then link to the CSS from head and to JS from body:
<!-- In <head> -->
<link href="nouislider.min.css" rel="stylesheet">
<!-- In <body> -->
<script src="nouislider.min.js"></script>
Example usage: Creates a slider which goes from 0 to 100, and starts set to 20-80.
HTML:
<div id="slider">
</div>
JS:
var slider = document.getElementById('slider');
noUiSlider.create(slider, {
start: [20, 80],
connect: true,
range: {
'min': 0,
'max': 100
}
});
Sure you can simply use two sliders overlaying each other and add a bit of javascript (actually not more than 5 lines) that the selectors are not exceeding the min/max values (like in #Garys) solution.
Attached you'll find a short snippet adapted from a current project including some CSS3 styling to show what you can do (webkit only). I also added some labels to display the selected values.
It uses JQuery but a vanillajs version is no magic though.
#Update: The code below was just a proof of concept. Due to many requests I've added a possible solution for Mozilla Firefox (without changing the original code). You may want to refractor the code below before using it.
(function() {
function addSeparator(nStr) {
nStr += '';
var x = nStr.split('.');
var x1 = x[0];
var x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + '.' + '$2');
}
return x1 + x2;
}
function rangeInputChangeEventHandler(e){
var rangeGroup = $(this).attr('name'),
minBtn = $(this).parent().children('.min'),
maxBtn = $(this).parent().children('.max'),
range_min = $(this).parent().children('.range_min'),
range_max = $(this).parent().children('.range_max'),
minVal = parseInt($(minBtn).val()),
maxVal = parseInt($(maxBtn).val()),
origin = $(this).context.className;
if(origin === 'min' && minVal > maxVal-5){
$(minBtn).val(maxVal-5);
}
var minVal = parseInt($(minBtn).val());
$(range_min).html(addSeparator(minVal*1000) + ' €');
if(origin === 'max' && maxVal-5 < minVal){
$(maxBtn).val(5+ minVal);
}
var maxVal = parseInt($(maxBtn).val());
$(range_max).html(addSeparator(maxVal*1000) + ' €');
}
$('input[type="range"]').on( 'input', rangeInputChangeEventHandler);
})();
body{
font-family: sans-serif;
font-size:14px;
}
input[type='range'] {
width: 210px;
height: 30px;
overflow: hidden;
cursor: pointer;
outline: none;
}
input[type='range'],
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
background: none;
}
input[type='range']::-webkit-slider-runnable-track {
width: 200px;
height: 1px;
background: #003D7C;
}
input[type='range']:nth-child(2)::-webkit-slider-runnable-track{
background: none;
}
input[type='range']::-webkit-slider-thumb {
position: relative;
height: 15px;
width: 15px;
margin-top: -7px;
background: #fff;
border: 1px solid #003D7C;
border-radius: 25px;
z-index: 1;
}
input[type='range']:nth-child(1)::-webkit-slider-thumb{
z-index: 2;
}
.rangeslider{
position: relative;
height: 60px;
width: 210px;
display: inline-block;
margin-top: -5px;
margin-left: 20px;
}
.rangeslider input{
position: absolute;
}
.rangeslider{
position: absolute;
}
.rangeslider span{
position: absolute;
margin-top: 30px;
left: 0;
}
.rangeslider .right{
position: relative;
float: right;
margin-right: -5px;
}
/* Proof of concept for Firefox */
#-moz-document url-prefix() {
.rangeslider::before{
content:'';
width:100%;
height:2px;
background: #003D7C;
display:block;
position: relative;
top:16px;
}
input[type='range']:nth-child(1){
position:absolute;
top:35px !important;
overflow:visible !important;
height:0;
}
input[type='range']:nth-child(2){
position:absolute;
top:35px !important;
overflow:visible !important;
height:0;
}
input[type='range']::-moz-range-thumb {
position: relative;
height: 15px;
width: 15px;
margin-top: -7px;
background: #fff;
border: 1px solid #003D7C;
border-radius: 25px;
z-index: 1;
}
input[type='range']:nth-child(1)::-moz-range-thumb {
transform: translateY(-20px);
}
input[type='range']:nth-child(2)::-moz-range-thumb {
transform: translateY(-20px);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="rangeslider">
<input class="min" name="range_1" type="range" min="1" max="100" value="10" />
<input class="max" name="range_1" type="range" min="1" max="100" value="90" />
<span class="range_min light left">10.000 €</span>
<span class="range_max light right">90.000 €</span>
</div>
Actually I used my script in html directly. But in javascript when you add oninput event listener for this event it gives the data automatically.You just need to assign the value as per your requirement.
[slider] {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 5px;
border-radius: 14px;
background-color: #d02128;
}
[slider] > div > [thumb] {
position: absolute;
top: -7px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
background-color: #FFF;
border-radius: 50%;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
opacity: 0;
position: absolute;
margin-left: -11px;
top: -39px;
z-index:3;
background-color: #d02128;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
}
[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color: #d02128;
}
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
<div slider id="slider-distance">
<div>
<div inverse-left style="width:70%;"></div>
<div inverse-right style="width:70%;"></div>
<div range style="left:0%;right:0%;"></div>
<span thumb style="left:0%;"></span>
<span thumb style="left:100%;"></span>
<div sign style="left:0%;">
<span id="value">0</span>
</div>
<div sign style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" oninput="
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;" />
<input type="range" value="100" max="100" min="0" step="1" oninput="
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;" />
</div>
The question was: "Is it possible to make a HTML5 slider with two input values, for example to select a price range? If so, how can it be done?"
In 2020 it is possible to create a fully accessible, native, non-jquery HTML5 slider with two thumbs for price ranges. If found this posted after I already created this solution and I thought that it would be nice to share my implementation here.
This implementation has been tested on mobile Chrome and Firefox (Android) and Chrome and Firefox (Linux). I am not sure about other platforms, but it should be quite good. I would love to get your feedback and improve this solution.
This solution allows multiple instances on one page and it consists of just two inputs (each) with descriptive labels for screen readers. You can set the thumb size in the amount of grid labels. Also, you can use touch, keyboard and mouse to interact with the slider. The value is updated during adjustment, due to the 'on input' event listener.
My first approach was to overlay the sliders and clip them. However, that resulted in complex code with a lot of browser dependencies. Then I recreated the solution with two sliders that were 'inline'. This is the solution you will find below.
var thumbsize = 14;
function draw(slider,splitvalue) {
/* set function vars */
var min = slider.querySelector('.min');
var max = slider.querySelector('.max');
var lower = slider.querySelector('.lower');
var upper = slider.querySelector('.upper');
var legend = slider.querySelector('.legend');
var thumbsize = parseInt(slider.getAttribute('data-thumbsize'));
var rangewidth = parseInt(slider.getAttribute('data-rangewidth'));
var rangemin = parseInt(slider.getAttribute('data-rangemin'));
var rangemax = parseInt(slider.getAttribute('data-rangemax'));
/* set min and max attributes */
min.setAttribute('max',splitvalue);
max.setAttribute('min',splitvalue);
/* set css */
min.style.width = parseInt(thumbsize + ((splitvalue - rangemin)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
max.style.width = parseInt(thumbsize + ((rangemax - splitvalue)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
min.style.left = '0px';
max.style.left = parseInt(min.style.width)+'px';
min.style.top = lower.offsetHeight+'px';
max.style.top = lower.offsetHeight+'px';
legend.style.marginTop = min.offsetHeight+'px';
slider.style.height = (lower.offsetHeight + min.offsetHeight + legend.offsetHeight)+'px';
/* correct for 1 off at the end */
if(max.value>(rangemax - 1)) max.setAttribute('data-value',rangemax);
/* write value and labels */
max.value = max.getAttribute('data-value');
min.value = min.getAttribute('data-value');
lower.innerHTML = min.getAttribute('data-value');
upper.innerHTML = max.getAttribute('data-value');
}
function init(slider) {
/* set function vars */
var min = slider.querySelector('.min');
var max = slider.querySelector('.max');
var rangemin = parseInt(min.getAttribute('min'));
var rangemax = parseInt(max.getAttribute('max'));
var avgvalue = (rangemin + rangemax)/2;
var legendnum = slider.getAttribute('data-legendnum');
/* set data-values */
min.setAttribute('data-value',rangemin);
max.setAttribute('data-value',rangemax);
/* set data vars */
slider.setAttribute('data-rangemin',rangemin);
slider.setAttribute('data-rangemax',rangemax);
slider.setAttribute('data-thumbsize',thumbsize);
slider.setAttribute('data-rangewidth',slider.offsetWidth);
/* write labels */
var lower = document.createElement('span');
var upper = document.createElement('span');
lower.classList.add('lower','value');
upper.classList.add('upper','value');
lower.appendChild(document.createTextNode(rangemin));
upper.appendChild(document.createTextNode(rangemax));
slider.insertBefore(lower,min.previousElementSibling);
slider.insertBefore(upper,min.previousElementSibling);
/* write legend */
var legend = document.createElement('div');
legend.classList.add('legend');
var legendvalues = [];
for (var i = 0; i < legendnum; i++) {
legendvalues[i] = document.createElement('div');
var val = Math.round(rangemin+(i/(legendnum-1))*(rangemax - rangemin));
legendvalues[i].appendChild(document.createTextNode(val));
legend.appendChild(legendvalues[i]);
}
slider.appendChild(legend);
/* draw */
draw(slider,avgvalue);
/* events */
min.addEventListener("input", function() {update(min);});
max.addEventListener("input", function() {update(max);});
}
function update(el){
/* set function vars */
var slider = el.parentElement;
var min = slider.querySelector('#min');
var max = slider.querySelector('#max');
var minvalue = Math.floor(min.value);
var maxvalue = Math.floor(max.value);
/* set inactive values before draw */
min.setAttribute('data-value',minvalue);
max.setAttribute('data-value',maxvalue);
var avgvalue = (minvalue + maxvalue)/2;
/* draw */
draw(slider,avgvalue);
}
var sliders = document.querySelectorAll('.min-max-slider');
sliders.forEach( function(slider) {
init(slider);
});
* {padding: 0; margin: 0;}
body {padding: 40px;}
.min-max-slider {position: relative; width: 200px; text-align: center; margin-bottom: 50px;}
.min-max-slider > label {display: none;}
span.value {height: 1.7em; font-weight: bold; display: inline-block;}
span.value.lower::before {content: "€"; display: inline-block;}
span.value.upper::before {content: "- €"; display: inline-block; margin-left: 0.4em;}
.min-max-slider > .legend {display: flex; justify-content: space-between;}
.min-max-slider > .legend > * {font-size: small; opacity: 0.25;}
.min-max-slider > input {cursor: pointer; position: absolute;}
/* webkit specific styling */
.min-max-slider > input {
-webkit-appearance: none;
outline: none!important;
background: transparent;
background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, silver 30%, silver 60%, transparent 60%, transparent 100%);
}
.min-max-slider > input::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 14px; /* Set a specific slider handle width */
height: 14px; /* Slider handle height */
background: #eee; /* Green background */
cursor: pointer; /* Cursor on hover */
border: 1px solid gray;
border-radius: 100%;
}
.min-max-slider > input::-webkit-slider-runnable-track {cursor: pointer;}
<div class="min-max-slider" data-legendnum="2">
<label for="min">Minimum price</label>
<input id="min" class="min" name="min" type="range" step="1" min="0" max="3000" />
<label for="max">Maximum price</label>
<input id="max" class="max" name="max" type="range" step="1" min="0" max="3000" />
</div>
Note that you should keep the step size to 1 to prevent the values to change due to redraws/redraw bugs.
View online at: https://codepen.io/joosts/pen/rNLdxvK
2022 - Accessible solution - 30 second solution to implement
This solution builds off of this answer by #JoostS. Accessibility is something none of the answers have focused on and that is a problem, so I built off of the above answer by making it more accessible & extensible since it had some flaws.
Usage is very simple:
Use the CDN or host the script locally: https://cdn.jsdelivr.net/gh/maxshuty/accessible-web-components/dist/simpleRange.min.js
Add this element to your template or HTML: <range-selector min-range="0" max-range="1000" />
Hook into it by listening for the range-changed event (or whatever event-name-to-emit-on-change you pass in)
That's it. View the full demo here. You can easily customize it by simply applying attributes like inputs-for-labels to use inputs instead of labels, slider-color to adjust the color, and so much more!
Here is a fiddle:
window.addEventListener('range-changed', (e) => {console.log(`Range changed for: ${e.detail.sliderId}. Min/Max range values are available in this object too`)})
<script src="https://cdn.jsdelivr.net/gh/maxshuty/accessible-web-components#latest/dist/simpleRange.min.js"></script>
<div>
<range-selector
id="rangeSelector1"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
number-of-legend-items-to-show="6"
/>
</div>
<div>
<range-selector
id="rangeSelector1"
min-label="Minimum"
max-label="Maximum"
min-range="1"
max-range="500"
number-of-legend-items-to-show="3"
inputs-for-labels
/>
</div>
<div>
<range-selector
id="rangeSelector2"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
number-of-legend-items-to-show="3"
slider-color="#6b5b95"
/>
</div>
<div>
<range-selector
id="rangeSelector3"
min-label="Minimum"
max-label="Maximum"
min-range="1000"
max-range="2022"
hide-label
hide-legend
/>
</div>
I decided to address the issues of the linked answer like the labels using display: none (bad for a11y), no visual focus on the slider, etc., and improve the code by cleaning up event listeners and making it much more dynamic and extensible.
I created this tiny library with many options to customize colors, event names, easily hook into it, make the accessible labels i18n capable and much more. Here it is in a fiddle if you want to play around.
You can easily customize the number of legend items it shows, hide or show the labels and legend, and customize the colors of everything, including the focus color like this.
Example using several of the props:
<range-selector
min-label="i18n Minimum Range"
max-label="i18n Maximum Range"
min-range="5"
max-range="555"
number-of-legend-items-to-show="6"
event-name-to-emit-on-change="my-custom-range-changed-event"
slider-color="orange"
circle-color="#f7cac9"
circle-border-color="#083535"
circle-focus-border-color="#3ec400"
/>
Then in your script:
window.addEventListener('my-custom-range-changed-event', (e) => { const data = e.detail; });
Finally if you see that this is missing something that you need I made it very easy to customize this library.
Simply copy this file and at the top you can see cssHelpers and constants objects that contain most of the variables you would likely want to further customize.
Since I built this with a Native Web Component I have taken advantage of disconnectedCallback and other hooks to clean up event listeners and set things up.
Here is a reusable double range slider implementation, base on tutorial Double Range Slider by Coding Artist
near native UI, Chrome/Firefox/Safari compatible
API EventTarget based, with change/input events, minGap/maxGap properties
let $ = (s, c = document) => c.querySelector(s);
let $$ = (s, c = document) => Array.prototype.slice.call(c.querySelectorAll(s));
class DoubleRangeSlider extends EventTarget {
#minGap = 0;
#maxGap = Number.MAX_SAFE_INTEGER;
#inputs;
style = {
trackColor: '#dadae5',
rangeColor: '#3264fe',
};
constructor(container){
super();
let inputs = $$('input[type="range"]', container);
if(inputs.length !== 2){
throw new RangeError('2 range inputs expected');
}
let [input1, input2] = inputs;
if(input1.min >= input1.max || input2.min >= input2.max){
throw new RangeError('range min should be less than max');
}
if(input1.max > input2.max || input1.min > input2.min){
throw new RangeError('input1\'s max/min should not be greater than input2\'s max/min');
}
this.#inputs = inputs;
let sliderTrack = $('.slider-track', container);
let lastValue1 = input1.value;
input1.addEventListener('input', (e) => {
let value1 = +input1.value;
let value2 = +input2.value;
let minGap = this.#minGap;
let maxGap = this.#maxGap;
let gap = value2 - value1;
let newValue1 = value1;
if(gap < minGap){
newValue1 = value2 - minGap;
}else if(gap > maxGap){
newValue1 = value2 - maxGap;
}
input1.value = newValue1;
if(input1.value !== lastValue1){
lastValue1 = input1.value;
passEvent(e);
fillColor();
}
});
let lastValue2 = input2.value;
input2.addEventListener('input', (e) => {
let value1 = +input1.value;
let value2 = +input2.value;
let minGap = this.#minGap;
let maxGap = this.#maxGap;
let gap = value2 - value1;
let newValue2 = value2;
if(gap < minGap){
newValue2 = value1 + minGap;
}else if(gap > maxGap){
newValue2 = value1 + maxGap;
}
input2.value = newValue2;
if(input2.value !== lastValue2){
lastValue2 = input2.value;
passEvent(e);
fillColor();
}
});
let passEvent = (e) => {
this.dispatchEvent(new e.constructor(e.type, e));
};
input1.addEventListener('change', passEvent);
input2.addEventListener('change', passEvent);
let fillColor = () => {
let overallMax = +input2.max;
let overallMin = +input1.min;
let overallRange = overallMax - overallMin;
let left1 = ((input1.value - overallMin) / overallRange * 100) + '%';
let left2 = ((input2.value - overallMin) / overallRange * 100) + '%';
let {trackColor, rangeColor} = this.style;
sliderTrack.style.background = `linear-gradient(to right, ${trackColor} ${left1}, ${rangeColor} ${left1}, ${rangeColor} ${left2}, ${trackColor} ${left2})`;
};
let init = () => {
let overallMax = +input2.max;
let overallMin = +input1.min;
let overallRange = overallMax - overallMin;
let range1 = input1.max - overallMin;
let range2 = overallMax - input2.min;
input1.style.left = '0px';
input1.style.width = (range1 / overallRange * 100) + '%';
input2.style.right = '0px';
input2.style.width = (range2 / overallRange * 100) + '%';
fillColor();
};
init();
}
get minGap(){
return this.#minGap;
}
set minGap(v){
this.#minGap = v;
}
get maxGap(){
return this.#maxGap;
}
set maxGap(v){
this.#maxGap = v;
}
get values(){
return this.#inputs.map((el) => el.value);
}
set values(values){
if(values.length !== 2 || !values.every(isFinite))
throw new RangeError();
let [input1, input2] = this.#inputs;
let [value1, value2] = values;
if(value1 > input1.max || value1 < input1.min)
throw new RangeError('invalid value for input1');
if(value2 > input2.max || value2 < input2.min)
throw new RangeError('invalid value for input2');
input1.value = value1;
input2.value = value2;
}
get inputs(){
return this.#inputs;
}
get overallMin(){
return this.#inputs[0].min;
}
get overallMax(){
return this.#inputs[1].max;
}
}
function main(){
let container = $('.slider-container');
let slider = new DoubleRangeSlider(container);
slider.minGap = 30;
slider.maxGap = 70;
let inputs = $$('input[name="a"]');
let outputs = $$('output[name="a"]');
outputs[0].value = inputs[0].value;
outputs[1].value = inputs[1].value;
slider.addEventListener('input', (e) => {
let values = slider.values;
outputs[0].value = values[0];
outputs[1].value = values[1];
});
slider.addEventListener('change', (e) => {
let values = slider.values;
console.log('change', values);
outputs[0].value = values[0];
outputs[1].value = values[1];
});
}
document.addEventListener('DOMContentLoaded', main);
.slider-container {
display: inline-block;
position: relative;
width: 360px;
height: 28px;
}
.slider-track {
width: 100%;
height: 5px;
position: absolute;
margin: auto;
top: 0;
bottom: 0;
border-radius: 5px;
}
.slider-container>input[type="range"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: absolute;
margin: auto;
top: 0;
bottom: 0;
width: 100%;
outline: none;
background-color: transparent;
pointer-events: none;
}
.slider-container>input[type="range"]::-webkit-slider-runnable-track {
-webkit-appearance: none;
height: 5px;
}
.slider-container>input[type="range"]::-moz-range-track {
-moz-appearance: none;
height: 5px;
}
.slider-container>input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -9px;
height: 1.7em;
width: 1.7em;
background-color: #3264fe;
cursor: pointer;
pointer-events: auto;
border-radius: 50%;
}
.slider-container>input[type="range"]::-moz-range-thumb {
-moz-appearance: none;
height: 1.7em;
width: 1.7em;
cursor: pointer;
border: none;
border-radius: 50%;
background-color: #3264fe;
pointer-events: auto;
}
.slider-container>input[type="range"]:active::-webkit-slider-thumb {
background-color: #ffffff;
border: 3px solid #3264fe;
}
<h3>Double Range Slider, Reusable Edition</h3>
<div class="slider-container">
<div class="slider-track"></div>
<input type="range" name="a" min="-130" max="-30" step="1" value="-100" autocomplete="off" />
<input type="range" name="a" min="-60" max="0" step="2" value="-30" autocomplete="off" />
</div>
<div>
<output name="a"></output> ~ <output name="a"></output>
</div>
<pre>
Changes:
1. allow different min/max/step for two inputs
2. new property 'maxGap'
3. added events 'input'/'change'
4. dropped IE/OldEdge support
</pre>
For those working with Vue, there is now Veeno available, based on noUiSlider. But it does not seem to be maintained anymore. :-(
This code covers following points
Dual slider using HTML, CSS, JS
I have modified this slider using embedded ruby so we can save previously applied values using params in rails.
<% left_width = params[:min].nil? ? 0 : ((params[:min].to_f/100000) * 100).to_i %>
<% left_value = params[:min].nil? ? '0' : params[:min] %>
<% right_width = params[:max].nil? ? 100 : ((params[:max].to_f/100000) * 100).to_i %>
<% right_value = params[:max].nil? ? '100000' : params[:max] %>
<div class="range-slider-outer">
<div slider id="slider-distance">
<div class="slider-inner">
<div inverse-left style="width:<%= left_width %>%;"></div>
<div inverse-right style="width:<%= 100 - right_width %>%;"></div>
<div range style="left:<%= left_width %>%;right:<%= 100 - right_width %>%;"></div>
<span thumb style="left:<%= left_width %>%;"></span>
<span thumb style="left:<%= right_width %>%;"></span>
<div sign style="">
Rs.<span id="value"><%= left_value.to_i %></span> to
</div>
<div sign style="">
Rs.<span id="value"><%= right_value.to_i %></span>
</div>
</div>
<input type="range" name="min" value=<%= left_value %> max="100000" min="0" step="100" oninput="
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;" />
<input type="range" name="max" value=<%= right_value %> max="100000" min="0" step="100" oninput="
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;" />
</div>
<div class="range-label">
<div>0</div>
<div>100000</div>
</div>
</div>
[slider] {
/*width: 300px;*/
position: relative;
height: 5px;
/*margin: 20px auto;*/
/* height: 100%; */
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 14px;
top: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 14px;
border-radius: 3px;
background-color: #CCC;
/*margin: 0 7px;*/
margin: 0 -7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 14px;
border-radius: 3px;
background-color: #CCC;
/*margin: 0 7px;*/
margin: 0 -7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 14px;
border-radius: 14px;
background-color:#8950fc;
}
[slider] > div > [thumb] {
position: absolute;
top: -3px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
/* box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); */
background-color: #FFF;
/*border-radius: 50%;*/
border-radius:2px;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
/* opacity: 0;
position: absolute;
margin-left: -11px;
top: -39px;
z-index:3;
background-color:#1a243a;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;*/
color: #A5B2CB;
border-radius: 28px;
justify-content: center;
text-align: center;
display: inline-block;
margin-top: 12px;
font-size: 14px;
font-weight: bold;
}
.slider-inner{
text-align:center;
}
/*[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color:#1a243a;
}*/
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
.range-label{
display: flex;
justify-content: space-between;
margin-top: 28px;
padding: 0px 5px;
}
.range-slider-outer{
width:calc(100% - 20px);
margin:auto;
margin-bottom: 10px;
margin-top: 10px;
}

How can a span id that div classes are applied to be looped?

I'm new to coding, and I'm trying to learn the basics. I wanted to practice what I learned by making flashcards (nothing complicated like saving it, importing it, or exporting it). So far, I made a table that the user can edit. I know how to gather data from the table, but I don't know how to make a CSS flashcard appear every time the user adds a card to the table. I am aware that the code will not work since I put the CSS in JavaScript since this code is just meant to show what I am trying to do. Also, if I am taking a completely wrong approach, please let me know. Thank you! Please excuse the poor variable naming, I was just testing some things.
<script>
function getFlashcardValue() {
for (var repeat = 0; repeat < 200; repeat++) {
var Table = document.getElementById('flashcardsTable');
var column1 = 0;
var column2 = 1;
var numberOfFlashcards = 2;
for (var row = 0; row < numberOfFlashcards; row++) {
var Cells = Table.rows.item(1).cells;
var Question1 = Cells.item(column1).innerHTML;
var Cells1 = Table.rows.item(1).cells;
var Answer1 = Cells.item(column2).innerHTML;
document.getElementById("myFlashcardQuestion" + row).innerHTML = Question1;
document.getElementById("myFlashcardAnswer" + row).innerHTML = Answer1;
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
<span id="myFlashcardQuestion1"></span>
</div>
<div class="flip-card-back">
<span id="myFlashcardAnswer1"></span>
</div>
</div>
</div>
}
}
}
</script>
<p style = "font-size: 25px">Hover over the flashcard to flip it!</p>
<style>
.flip-card {
background-color: transparent;
width: 350px;
height: 175px;
margin: auto;
padding: 5px 5px;
perspective: 1000px;
}
.flip-card-inner {
position: relative;
background-color: lightblue;
width: 100%;
height: 100%;
text-align: center;
transition: transform 0.6s;
transform-style: preserve-3d;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.flip-card-front {
background-color: lightblue;
width: 350px;
height: 175px;
color: black;
font-size: 35px;
text-alignment: center;
}
.flip-card-back {
background-color: red;
color: white;
font-size: 35px;
text-alignment: center;
transform: rotateY(180deg);
}
</style>
So first of all you can create a code snippet in stackoverflows editor (see below), or use jsfiddle and post a shared-link.
It depends on which action the user has to do after he enters the data.
If it is, for example, a button click, then it is possible to call a function that shows the user's input in the flashcard. Now if you want that for every single Q&A you have to create Elements in the for loop and edit them there. Here a little example.
var allCards = document.getElementById("allCards");
for (var i = 1; i <= 5; i++) { //i used 5, you should use length of data
var question = document.createElement("div");
question.textContent = "Question " + i;
question.classList.add("flip-card");
allCards.appendChild(question);
}
.flip-card {
background-color: lightblue;
width: 350px;
height: 175px;
margin: 10px auto;
padding: 5px 5px;
font-size: 35px;
text-align: center;
}
<div id="allCards"></div>
Edit:
As promised, here is an example of how you can set up the flip cards.
https://jsfiddle.net/ybu59hfp/1/
Your concern should now be resolved. If you have any further questions, feel free to write to me in the chat or read a little about JavaScript on the Internet.

Electron js - Divs are unclickable until the screen is maximized- after that the situation is normal

I have some code working to make 2 div tags visible only when another div is clicked (an attempt to make a custom title bar)The 2 divs are invisible initially.
const {remote} = require('electron');
var win = remote.getCurrentWindow();
win.setSize(810,610);
function max(){
var win = remote.getCurrentWindow();
if(win.isMaximized()){
win.unmaximize();
document.getElementById('file').style.right = "744px";
document.getElementById('about_us').style.right = "664px";
}else{
win.maximize();
document.getElementById('file').style.right = "1314px";
document.getElementById('about_us').style.right = "1234px";
}
}
hidden = true;
function get(el){
return document.getElementById(el);
}
function open_file_menu(){
var fileMenu = [get('file_menu'),get('file_menu2')];
if (hidden == false){
for (i = 0;i<fileMenu.length;i++){
fileMenu[i].style.visibility = 'hidden';
hidden = true;
}
}
else{
for (i = 0;i<fileMenu.length;i++){
fileMenu[i].style.visibility = 'visible';
hidden = false;
}
}
}
body,html{
width: 47.5pc;
height: 100pc;
overflow: hidden;
-webkit-app-region:drag;
z-index: 0;
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: monospace;
z-index: 1;
}
#container{
width: 100pc;
height: 100pc;
display: block;
position: absolute;
right: 40px;
top: 0;
}
#container nav{
display: grid;
width: 100pc;
height: 2.2pc;
background-color: #333333;
z-index: 1;
}
span{
position: absolute;
top:0;
transform: translateX(100px);
color:#dacbcb;
font-size: 1.5em;
font-weight:bold;
background-color: #222222;
padding-left: 25px;
padding-right: 25px;
padding-top: 7px;
padding-bottom: 6px;
transition: ease-in-out 1ms;
-webkit-app-region:no-drag;
}
#buttons{
position: absolute;
right: 120px;
}
#maximize{
transform: translateX(-124px);
}
#maximize span:hover,#minimize:hover span,.menu1:hover,.menu2:hover{
background-color: #333333;
}
#file{
position: absolute;
right:743px;
}
#about_us{
position: absolute;
right:663px;
#run{
transform: translateY(100px);
}
.menu1{
font-size: 1.1em;
padding-left: 25px;
padding-right: 25px;
padding-top: 10px;
padding-bottom: 8px;
}
.menu2{
font-size: 1.1em;
padding-left: 25px;
padding-right: 25px;
padding-top: 10px;
padding-bottom: 8px;
}
#file_menu{
visibility: hidden;
background-color: #222222;
transform: translateY(34px);
font-size: 1.1em;
}
#file_menu2{
visibility: hidden;
background-color: #222222;
transform: translateY(63px);
font-size: 1.1em;
padding-left: 25px;
padding-right: 17px;
padding-top: 7px;
padding-bottom: 6px;
}
.fm{
z-index: 2;
}
#file_menu:hover,#file_menu2:hover{
background-color: #333333;
}
<!DOCTYPE html>
<html>
<head>
<script src="init.js"></script>
<script src="text_editor.js"></script>
<meta charset="UTF-8">
<title>Extorc App</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div onclick="uff()">
<span id = "file_menu" class = "fm">new</span>
</div>
<div>
<span id = "file_menu2" class = "fm">open</span>
</div>
<div id="container">
<nav>
<div id="buttons">
<div id="file" onclick="open_file_menu()">
<span class = "menu1">file</span>
</div>
<div id="about_us">
<span class = "menu2">about..us</span>
</div>
<div id="maximize" onclick="max()">
<span>+</span>
</div>
</div>
</nav>
</div>
</body>
</html>
The code works ... the menu opens up with 2 divs displaying but the divs are un clickable or probably totally passive (i have set a :hover background color in my css)Though , this problem ends if i open the 2 divs and then maximize the window..then they become interactable and then after minimizing also they work which means the divs are un-interactable initially but then after one maximize , they work...any fix for that....should i not use visibility here?
Edit Not only by fullscreen but also if i resize the window , the buttons start working , and starting the window as fullscreen : true, doesnt make it work
You cannot put all the body as draggable: -webkit-app-region:drag; on body,html
You will have to define a specific area where it defined as draggable.
My recommendation define a specific area / SVG that means drag and don't put clickables on it.
E.g take a look at known electron products (VSCODE, Slack) you have specific regions from which you can drag from.
Hope I have helped.

how to run javascript function 'live' without refresh?

im trying to make a average grade calculator, now thats is going fine but now i want to calculate the average immediately when a number gets inputted in one of the fields. I've been trying this with "on(), live(), onkeyup()" but can't get it to work.
The result of the average now displays beneath the inputfields 'onclick' on the button. I want the average displayed there but then as soon as you input numbers in one of the fields it should show there as it now does after the onclick.
What i've tryed with the 'on(), live(), onkeyup()' is to connect them to the input fields and connect them to the calculator() function.
Is there a easy way to do this or a other certain way?
greetings.
function calculator() {
var weight = 0;
var mark = 0;
var weights = document.querySelectorAll('[id^=weight-]');
var grades = document.querySelectorAll('[id^=mark-]');
var trs = document.getElementsByTagName('tr');
var tBody = document.getElementsByTagName('tbody')[0];
var totalWeight = 0;
var totalGrade = 0;
for (var i = 0; i < weights.length; i++) {
totalWeight += +weights[i].value;
}
for (var i = 0; i < grades.length; i++) {
totalGrade += +grades[i].value;
}
var finalGrade=totalGrade/totalWeight;
var display = document.getElementById('output-div');
var newTr = document.createElement('TR');
newTr.innerHTML = `<td><input id="weight-${trs.length + 1}" type="text" size=2 value=""></td><td><input id="mark-${trs.length + 1}" type="text" size=2 value=""></td>`;
tBody.appendChild(newTr);
display.innerHTML='Je gemiddelde is: ' +finalGrade.toFixed(2);
}
html {
background-color: ;
}
header {
background-color: ;
}
h2 {
text-align: center;
}
h3 {
text-align: center;
}
body {
font-family: 'Roboto', sans-serif;
}
table {
margin: auto;
}
tr {
background-color: ;
}
td {
background-color: ;
}
#table-title {
font-size: 20px;
font-style: italic;
text-align: center;
position: relative;
}
input {
text-align: center;
}
[id^="mark"] {
width: 100px;
}
[id^="weight"] {
width: 100px;
}
#calc-btn-div {
position: relative;
width: 150px;
margin: auto;
}
#calc-btn {
position: relative;
padding: 5px;
margin-top: 20px;
}
#calc-btn:hover {
border-color: black;
box-shadow: 8px 8px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
}
/* #add-input-div {
height: 50px;
text-align: center;
width: 300px;
margin: auto;
margin-top: 20px;
}
#add-input-btn {
position: relative;
padding: 5px;
margin-top: 20px;
}
#add-input-btn:hover {
border-color: black;
box-shadow: 8px 8px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
} */
#output-div {
background-color: ;
height: 50px;
text-align: center;
width: 300px;
margin: auto;
margin-top: 20px;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 15% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
}
/* The Close Button */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
</head>
<header>
<h2>Gemiddelde cijfer</h2>
<h3>Voer hieronder je cijfers in</h3>
</header>
<body>
<table id="table">
<tr id="table-title">
<td>Weging</td>
<td>Cijfer</td>
</tr>
<tr>
<td><input id="weight-1" type="text" size=2 value=""></td>
<td><input id="mark-1" type="text" size=2 value=""></td>
</tr>
</table>
<div id="calc-btn-div">
<input id="calc-btn" type="button" value="Berekenen je gemiddelde" onclick="calculator()">
</div>
<!-- <div id="add-input-div">
<input id="add-input-btn" type="button" value="Voeg cijfer toe" onclick="addInput()">
</div> -->
<div id="output-div"></div>
</body>
</html>
Using vanilla JavaScript I would attach an eventListener to the inputfields like this
document.getElementById('weight-1').addEventListener('change',function(){
calculator();
});
document.getElementById('mark-1').addEventListener('change',function(){
calculator();
});
These addEventListener functions adds listeners to the "input" field's predefined 'change'-events, and fires the calculator(); function from your code.
Seeing that you are using some sort of dynamic generation of the inputfields, you could add the listeners to your inputfields using the same querySelector that you use to target them during calculation. It would mean replacing getElementById('weight-1') in my example above with querySelectorAll('[id^=weight-]') for the weight-fields.
Also, doing work with values, IO, and calculation between html and JavaScript, I would suggest using a library like jQuery. jQuery simplifies these processes a lot.
This is the documentation for the jQuery alternative onClick function:
https://api.jquery.com/change/
I think you could do that with an angularJS module. Take a look at this tutorial : https://www.w3schools.com/angular/angular_modules.asp
maybe it will help.

jQuery UI effect "shake" makes div disappear

I have this div ( that contains the content of my website. I would like to make it shake when something doesn't authenticate properly using the jQuery effect.("shake"). However, I can't get it to shake at all. Instead, the div disappears for a period of time and then reappears. This happens regardless of me switching any of the parameters. The only effect that switching the parameter has is changing the duration of the disappearance.
I haven't been able to troubleshoot much (this is literally my first use of jQuery and the only jQuery I anticipate this project utilizing).
The reason the google apps script tag was added (I added it back) is because this project is using Google Apps Script. I'm using it to pull data from a Google Spreadsheet (which avoids paying for and maintaining a database).
The offending jQuery (is currently in the HTML file):
$(document).click(function(){
$("#container").effect( "shake", {times:4}, 1000 );
});
And here's ALL of the code:
function getTournamentInfo(){
google.script.run.withSuccessHandler(setTournamentInfo).grabTournamentInfo()
}
function setTournamentInfo(systemData){
// insert tournament header
var tournamentBanner = document.createElement("h1");
var tournamentBannerText = document.createTextNode(systemData[0]);
tournamentBanner.appendChild(tournamentBannerText);
document.getElementById("container").insertBefore(tournamentBanner,document.getElementById("maindata"));
//create a space
document.getElementById("container").insertBefore(document.createElement("br"),document.getElementById("maindata"));
//insert chamber header
var chamberBanner = document.createElement("h2");
var chamberBannerText = document.createTextNode(systemData[1]);
chamberBanner.appendChild(chamberBannerText);
document.getElementById("container").insertBefore(chamberBanner,document.getElementById("maindata"));
//insert session header
var sessionNumber = document.getElementById("sessionNameNumber");
var sessionNumberText = document.createTextNode(systemData[2]);
sessionNumber.appendChild(sessionNumberText);
}
var ids = [];
var names = [];
var school = [];
function getData(){
google.script.run.withSuccessHandler(setIdData).grabDebaters();
}
function setIdData(systemData){
for (var i=0; i<systemData[0].length-1; i++)
{
ids.push(systemData[0][i]);
names.push(systemData[1][i]);
school.push(systemData[2][i]);
console.log(names[3]);
}
makeRows();
}
function makeClassroom()
{
}
function makeRows() {
for (var i=0; i<ids.length-1; i++)
{
generateDebaters();
}
}
var nameSchoolCounter = 0; // this is used to ensure that all the names are iterated through
function generateDebaters() {
var mainTable = document.getElementById("maindata");
var debaterRow = mainTable.insertRow(-1);
debaterRow.setAttribute("onmouseover","darkenRow(this)");
debaterRow.setAttribute("onmouseout","lightenRow(this)");
var nameCell = debaterRow.insertCell(0);
nameCell.innerHTML = names[nameSchoolCounter];
nameCell.setAttribute("id","debater");
var schoolCell = debaterRow.insertCell(1);
schoolCell.innerHTML = school[nameSchoolCounter];
schoolCell.setAttribute("id","debater");
nameSchoolCounter++;
var speech1Cell = debaterRow.insertCell(2);
speech1Cell.innerHTML = '<input name="Speech 1" placeholder="Enter 1-6">'
var speech2Cell = debaterRow.insertCell(3);
speech2Cell.innerHTML = '<input name="Speech 2" placeholder="Enter 1-6">'
var speech3Cell = debaterRow.insertCell(4);
speech3Cell.innerHTML = '<input name="Speech 3" placeholder="Enter 1-6">'
var ethosCell = debaterRow.insertCell(5);
ethosCell.innerHTML = '<input name="Ethos" placeholder="Enter 1-3">'
var nomCell = debaterRow.insertCell(6);
nomCell.innerHTML = '<input type="checkbox" id="nomBox" name="nom"/>'
}
function darkenRow(row) {
row.style.backgroundColor = "rgba(0,0,0,.6)";
}
function lightenRow(row) {
row.style.backgroundColor = "rgba(0,0,0,0)";
}
function submitProceduresGraphics(){ // this does all the graphical procedures for submitting
// this prevents the divs width and height from disappearing (it's set as display: table in the stylesheet)
var container = document.getElementById("container");
var containerStyle = getComputedStyle(container);
container.style.width = containerStyle.width;
container.style.height = containerStyle.height;
while (container.firstChild) {
container.removeChild(container.firstChild);
}
return submitProcedures();
}
function submitProcedures(){
//incomplete method
return students;
}
//below is what actually runs
getData();
getTournamentInfo();
h1{
font-family: 'Lato', sans-serif;
font-weight: 700;
font-size: 36px;
color: white;
margin-bottom: 0px;
}
h2{
font-family: 'Lato', sans-serif;
font-weight: 700;
font-size: 20px;
color: white;
margin-top: 0px;
}
#maindata{
border-collapse: collapse;
border: 0px;
width: 70%;
margin-top: 40px;
margin-left: auto;
margin-right: auto;
text-align: center;
font-family: 'Lato', sans-serif;
font-weight: 700;
font-size: 18px;
color: white;
white-space: nowrap;
}
#maindata td{
margin-left:0px;
margin-right: 0px;
padding: 4px;
border: 0px;
border-image-width: 0px;
}
#maindata td#debater{
font-family: 'Lato', sans-serif;
font-weight: 400;
font-size: 14px;
}
body{
background: url(https://d3591ee267da5305673fdd35d46a7c93a6509bd1.googledrive.com/host/0B3UFP8Xs5x7WUldKaFZJTjhkbWM);
}
#container {
background-color: rgba(0,0,0,.5);
padding-left: 40px;
padding-right: 40px;
display: table;
margin: auto;
border-radius: 6px;
position: relative;
top: 100%;
transform: translateY(20%);
}
input {
background-color: none;
}
#nomBox {
margin-top: 2.5px;
margin-bottom: 2.5px;
width: 18px;
height:18px;
}
#sessionName {
float: right;
margin-top: 26.2px;
text-align: center;
font-family: 'Lato', sans-serif;
color: white;
font-weight: 700;
font-size: 18px
}
#sessionNameNumber{
color: white;
font-family: 'Lato', sans-serif;
margin-top: 0px;
font-size:66px;
font-weight: 700;
margin-bottom: 4px;
}
/* below here is the CSS for the submit button */
.button {
border: 0 none;
border-radius: 2px 2px 2px 2px;
color: #FFFFFF;
cursor: pointer;
font-family: Lato,sans-serif;
font-size: 12px;
font-weight: bold;
line-height: 20px;
margin-left: auto;
margin-right: auto;
margin-bottom: 40px;;
margin-top: 40px;
padding: 7px 10px;
text-transform: none;
transition: all 0.3s ease 0s;
-moz-transition: all 0.3s ease 0s;
-webkit-transition: all 0.3s ease 0s;
width: 10%;
/* auto */
text-align: center;
/* DELETE WHEN WIDTH AUTO */
}
.button.green {
background: none repeat scroll 0 0 #46b98a;
color: #FFFFFF;
}
.button.green:hover {
background: none repeat scroll 0 0 #444444;
color: #FFFFFF;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<link href='http://fonts.googleapis.com/css?family=Lato:400,700' rel='stylesheet' type='text/css'> <!-- This has a font called Lato because TNR was hurting my eyes. Btw Google Fonts is awesome !-->
<title>autoTab</title>
</head>
<body>
<div id="container">
<div id=sessionName>SESSION<br><p id="sessionNameNumber"></p></div>
<!-- Right now, this is brining up an unattractive page when submitted. We should get rid of that: https://mashe.hawksey.info/2014/07/google-sheets-as-a-database-insert-with-apps-script-using-postget-methods-with-ajax-example/ !-->
<table id="maindata">
<tbody>
<tr>
<td>
Debater Name
</td>
<td>
School
</td>
<td>
<!-- Noice-->
Speech 1
</td>
<td>
Speech 2
</td>
<td>
Speech 3
</td>
<td>
Ethos
</td>
<td>
Nom?
</td>
</tr>
</tbody>
</table>
<div class='button green center' onclick="submitProceduresGraphics()">Submit Scores</div>
<p>
</body>
</html>
<!-- Store data passed to template here, so it is available to the
imported JavaScript. -->
<script>
$( document ).click(function() {
$( "#hello" ).effect( "bounce", "slow" );
});
</script>
<?!= HtmlService.createHtmlOutputFromFile('JavaScript').getContent(); ?>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
top: 100% applied to #container is causing your problems. Removing the style doesn't seem to have any effects, so I removed it.
Explanation: When jQuery UI creates the shake effect, it first wraps your element in a div.ui-effects-wrapper, which is a relatively positioned element and copies any position styling (such as top) from your element. It then places your element, stripped of its original position styling, inside of this div, and animates a shake by adjusting its left CSS property. In your case, you had top: 100% applied to your container. When jQuery placed your element inside of its effect-wrapper, the wrapper had both position: relative and top: 100%, which placed your element, the container, off the bottom edge of the screen, out of view. Play around with setting your container styling to something like top: 20px and see the effect this has.
Hope I helped!

Categories

Resources