How to stop jQuery sliders (bars) when range of points is emptied - javascript

I have a problem with jQuery-UI sliders ( http://jqueryui.com/demos/slider/#default ).
I've created a couple of them on my website, and there is DIV with "points" (I entered there 1500). Each slider after increasing takes points from the DIV, so - when I increase one slider from 0 to 100, there is only 1400 points in DIV left. When I increase another slider, there will be less points in the DIV. If I decrease one of the sliders, these points will be added to the DIV - this works great.
But... I need to disable increasing value of the sliders after points in DIV gains 0 (there can't be values less than 0), decreasing must be still available (so .slider({ disabled: true }) is not an option). I've tried almost everything, and nothing works...
JS code:
$("#addMapPage").ready(function() {
$("#addMap").click(function(){ checkMap() });
$(".vChanger").slider({ step: 10,
max: 1500,
slide: function(event, ui) { checkValues() },
stop: function(event, ui) { checkValues() }
});
checkValues();
});
function getValues() {
var data = new Array();
data['water'] = $("#water").slider("value");
data['steppe'] = $("#steppe").slider("value");
data['swamp'] = $("#swamp").slider("value");
data['desert'] = $("#desert").slider("value");
data['forest'] = $("#forest").slider("value");
data['mountain'] = $("#mountain").slider("value");
data['hill'] = $("#hill").slider("value");
return data;
}
function checkValues() {
var slidersValues = getValues(); //getting slider values
var sum = 0;
var pula = parseInt($("#pula").text()); //points to use (now can be less than 0)
if (pula < 0){
$(".vChanger").slider({ range: pula });
}
for (value in slidersValues) {
sum += slidersValues[value];
$("#"+value+"Info").text(slidersValues[value]);
}
var pula = 1500-sum;
$("#pula").text(pula);
}
HTML:
<div id="addMapPage">
<ul>
<li><span>Woda:</span><div class="vChanger" id="water"></div><span id="waterInfo"><span/></li>
<li><span>Step:</span><div class="vChanger" id="steppe"></div><span id="steppeInfo"><span/></li>
<li><span>Bagno:</span><div class="vChanger" id="swamp"></div><span id="swampInfo"><span/></li>
<li><span>Pustynia:</span><div class="vChanger" id="desert"></div><span id="desertInfo"><span/></li>
<li><span>Las:</span><div class="vChanger" id="forest"></div><span id="forestInfo"><span/></li>
<li><span>Góry:</span><div class="vChanger" id="mountain"></div><span id="mountainInfo"><span/></li>
<li><span>Wzgórza:</span><div class="vChanger" id="hill"></div><span id="hillInfo"><span/></li>
<li><span>Nazwa mapy:</span><input type="text" id="mapName"></input></li>
</ul>
<p id="mapInfo"><span id="pula"></span>Points to use</p>
<input type="button" id="addMap" value="create map"></INPUT>
</div>
Here is example (3 screenshots):
Any ideas, how to limit points (don't let them to be less than 0)?

You'll find that adjusting the max values of your sliders is probably not a very desirable solution. It will change the scale of the increments on your slider and not be very representative of what portion of the whole the current slider value represents. Instead, intercept and prevent the slide event if the current sum of all other sliders plus the value for this event exceed the max. For instance:
var maxSum = 1500;
var $sliders = $(".slider").slider({
value: 0,
min: 0,
max: 1500,
step: 10,
slide: function(event, ui) {
var sum = 0;
// Don't add the value for this slider;
// We need to add the new value represented by the slide event
$(".slider").not(this).each(function() {
sum += $(this).slider("value");
});
// Add the slide event value
sum += ui.value;
if (sum > maxSum) event.preventDefault();
else $(this).next().html("Value: " + ui.value);
}
});
The only thing this is prone to is not pushing the values all the way to the max if the user is sliding very rapidly by large increments. See a working demo here.

You're already storing the available points in "pula".
Whenever a user adjusts a slider, all the remaining sliders need to be changed so that the user can't allocate more than the remaining points. You do this by setting the .slider() MAX option to the current value of the slider + the remaining available points.
for (value in slidersValues) {
var currentValue = slidersValues[value];
var newMax;
//If max + available is > 1500, we set it to 1500,
//otherwise we add the available points to the current values
if ( currentValue + pula >= 1500) {
newMax = 1500;
} else {
newMax = currentValue + pula;
}
//jQuery allows you to reset the 'max' value for a slider
$('#'+value).slider( "option" , max , newMax );
}

Related

how to transition an input slider smoothly when a discrete jump of the value takes place

I have a basic html input slider. This slider is controlled by some other UI element. This means that it can jump discreetly in value.
For example, if the current value is 100, it can be set to 500 by the other UI element.
In such a scenario, is it possible to get the slider to smoothly transition from 100 to 500? Perhaps by steps of 10 or even 1?
EDITED:
I am using react. I tried to use setInterval to update the useState that holds the value for the slider, but it doesn't work
Here's a very basic implementation of a linear interpolation with an interval. You can change _interpSpeed and the interval timer to whatever you like to make the animation faster or slower. _interpSpeed is the step size and the interval timer is the step frequency (currently set to 60fps).
const _interpSpeed = 5;
var interval = setInterval(()=>{
let range = document.querySelector("input[type=range]");
//If we don't have a value to interp to, just return
if(range.newValue === undefined)
return;
//Convert the string value to an integer
let value = parseInt(range.value);
//Determine whether we need to add or subtract
let delta = range.newValue - value;
let sign = delta / Math.abs(delta);
//Add the designated interp amount (linear)
let v = value + sign * _interpSpeed;
//Prevent exceeding the minimum or maximum of the range
if(v > parseInt(range.max)) v = parseInt(range.max);
else if(v < parseInt(range.min)) v = parseInt(range.min);
//Prevent going past the desired value
else if(v > range.newValue && sign > 0) v = range.newValue;
else if(v < range.newValue && sign < 0) v = range.newValue;
//Update the value
range.value = v;
//If we've reached the desired value, delete the desired value
//so the interval will return at the top of the function.
if(range.value == range.newValue)
delete range.newValue;
}, 1000/60);
document.querySelector("input[type=number]").addEventListener("keyup", function(){
let v = parseInt(this.value);
if(Number.isNaN(v))
v = 0;
let range = document.querySelector("input[type=range]");
range.newValue = v;
});
input[type="range"]
{
width: 100%;
}
Pick a number between 0 and 500
<input type="number" value="250" min="0" max="500"/>
<br><br>
<input type="range" value="250" min="0" max="500"/>

Is there a minimum possible size change in CSS?

I wrote some JavaScript code to animate CSS properties of elements. I pass the following arguments to the function: amount, interval, and duration; amount being the change in the property (for example 200 could mean add 200 pixels to the element's width), interval being the time between two consecutive changes, and duration being the total duration of the animation.
The code works fine unless I pass the arguments in a way that the change in each interval becomes very small (like a tiny fraction of a pixel).
I know the code is working fine theoretically, as I get the change in console.
Any ideas about the problem?
Cheers.
UPDATE: the code:
function handleTimer (amount, interval, duration, execute, element) {
let i = 0;
let current = 0;
let stepsCount = countSteps(interval, duration);
let stepLength = calcStepLength(stepsCount, amount);
let count = setTimeout(function addOneMore () {
if ( i < stepsCount -1 ){
i++;
current += stepLength;
execute(stepLength, element);
if (current < amount) {
count = setTimeout(addOneMore, interval)
}
} else {
current = amount;
execute(amount - (stepsCount -1) * stepLength, element);
}
}, interval)
}
function countSteps (interval, duration) {
let remainder = duration % interval;
let stepsCount;
if (remainder) {
stepsCount = Math.floor(duration / interval) + 1;
} else {
stepsCount = duration / interval;
}
return stepsCount;
}
function calcStepLength(stepsCount, amount) {
return amount / stepsCount;
}
function resizeWidth (amount, element) {
let widthSTR = $(element).css('width');
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
$(element).css('width', `${width + amount}px`);
}
So this:
handleTimer(218, 5, 200, resizeWidth, '.box');
works fine, but this:
handleTimer(218, 5, 2000, resizeWidth, '.box');
doesn't.
UPDATE 2:
I know browsers are super accurate with pixels, like when you use percentages. Of course the value will be rounded before rendering since displays cant display half pixels, but the value is still calculated accurately.
I don't know at what decimal the rounding occurs.
This happens because parseInt is rounding your number up.
Pay attention to this line:
let width = parseInt( widthSTR.substr( 0 , widthSTR.length - 2 ) );
if width is a decimal number, like 22.5px, it will be rounded up to 22.
If amount is less than 1, it won't reach 23 and when you round up the number again, you'll get 22 again and it becomes a loop.
You have two solutions:
Use another variable to save the width value, avoiding to writing and reading it from CSS:
let initialWidth = $(element).css('width');
let savedWidth = widthSTR.substr(0, initialWidth, initialWidth.length - 2 ) );
function resizeWidth (amount, element) {
savedWidth += amount;
$(element).css('width', `${savedWidth}px`);
}
Just use parseFloat in place of parseInt to don't round your number up:
let width = parseFloat( widthSTR.substr( 0 , widthSTR.length - 2 ) );

select a value from array based on var

Basically. I am creating a preloader page for my site. I want the icon in the middle of the page to change everytime the visitor comes to my page. I need to select a variable or a string within the array.
My code:
$(document).ready(function() {
//randomly pick a number an pick an icon to show on main page
//Math.floor(Math.random() * 6) + 1 [from SOF]
var min = 1,
max = 8;
var number = Math.floor(Math.random() * (max - min + 1) + min);
var icons = ['preload/img/audio.svg', 'preload/img/bars.svg', 'preload/img/grid.svg', 'preload/img/oval.svg', 'preload/img/puff.svg', 'preload/img/rings.svg', 'preload/img/tail-spin.svg', 'preload/img/three-dots.svg'];
alert(number);
});
I have tried alert(icons.get(numbers)); but never worked. I have been searching for a while and cannot figure it out.
You need to retrieve the icon using the index.
Also, for the random, it returns a float between 0 and 1, so as an array is 0 based, and you have 8 items, you need to:-
$(document).ready(function() {
var number = Math.round(Math.random() * 7);
var icons = ['preload/img/audio.svg', 'preload/img/bars.svg', 'preload/img/grid.svg', 'preload/img/oval.svg', 'preload/img/puff.svg', 'preload/img/rings.svg', 'preload/img/tail-spin.svg', 'preload/img/three-dots.svg'];
alert(icons[number]);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
var icons = ['preload/img/audio.svg', 'preload/img/bars.svg', 'preload/img/grid.svg', 'preload/img/oval.svg', 'preload/img/puff.svg', 'preload/img/rings.svg', 'preload/img/tail-spin.svg', 'preload/img/three-dots.svg'];
var imageNum = Math.floor(Math.random()*icons.length);
document.getElementById("myIcon").src = icons[imageNum];
Where "myIcon" is the id of the image you want to change.

I can't push items into jasvacript array quick enough

I have a jQuery UI slider that when a user triggers a slide function, I need to push the current slide value into an array. The problem is, when a user slides the handle too quickly, the length of the array and the current index value fall out of sync. As if the array cant update as quick as the user can slide...
Is there a solution for this?
Thanks.
$( "#slider" ).slider({
max: gCoordsArray.length,
min: 0,
step: 1,
slide: function(event, ui){
var direction = '';
// test whether we are sliding left or right ( increasing / decreasing increment )
if (ui.value > count) direction = 'right';
if (ui.value < count) direction = 'left';
count = ui.value;
if (direction == 'right') {
test.push(count);
console.log(test.length); // Should match count
console.log(count); // Should match length
}
if (direction === 'left') {
test.pop();
console.log(test.length); // Should match count
console.log(count); // Should match length
}
}, 1)
});
test.length and count=ui.value won't be the same except in the edge-case that the user only moves one step at a time.
The slide event returns the current mouse position, if the user skips straight to the end, ui.value will be gCoordsArray.length while test.length == 0
One solution might be to record in a "global" var (outside the event handler) the previous position and compare that, eg:
var prevPosition = 0;
$( "#slider" ).slider({
max: gCoordsArray.length,
min: 0,
step: 1,
slide: function(event, ui) {
var thisPosition = ui.value;
if (thisPosition > prevPosition) direction = 'right';
if (thisPosition < prevPosition) direction = 'left';
if (direction == 'right')
{
while (prevPosition != thisPosition)
{
prevPosition++;
test.push(prevPosition);
}
}
... equivalent for left
}, 1)
});

Javascript and Variable assistance

(Rephrasing question from earlier) So here is the assignment:
First, you will have to calculate a cost for the weight of the parcel. The user will enter the total weight of their parcel into the text field. The schedule is as follows…
0 – 150 lbs $20.00 per pound
| 151 – 300 lbs $15.00 per pound
| 301 – 400 lbs $10.00 per pound
Do not allow the user to enter a weight that is < 0 or > 400. If they do, output an error message in red to div#results and ‘return’ out of the function.
Next, the user will choose a discount amount (for whatever reason, does not matter). You will need to apply whatever discount amount is chosen. (50% off, 20% off, none).
This is what I have done so far. Variable aren't declared yet, just wrote them in.
function calcTotal() {
var msg;
var weight = parseInt( document.getElementById("weight").value );
var discount;
var total;
if( weight >= 0 && weight <= 150 ) {
total = weight * 20
}
else if( weight >150 && weight <= 300 ) {
total = weight * 15
}
else if( weight >300 && weight <= 400 ) {
total = weight * 10
}
if( document.getElementById("50%").selected == true ) {
total = total * 0.50;
}
if( document.getElementById("25%").selected == true ) {
total = total * 0.25;
}
if( document.getElementById("none").selected == true ) {
total = total;
}
Is this somewhat correct so far?
Can't seem to figure out how to apply the discount based on what the user selects. The discounts are 3 radio buttons. Do i need to apply an id to each radio button?
First of all you need to use && (AND) instead of || (OR) because you want both conditions to be met not just one. First IF statement will process value -1000 as TRUE (as well as any other value because your interval is from 0 to infinity plus from minus infinity to 150) because it satisfies the second part of the first condition.
Second, the formula is correct but you have to convert percents into 0-1 interval. 100% = 1, 0% = 0 and x% = x/100. Then it should work without any problems.
Last thing to do is that you need to pass the values into your function:
function calcTotal(weight, discount) {
// do the calculation with passed values, you do not need to declare them here anymore
}
Or you need to set values right inside of that function, e.g.:
function calcTotal() {
var discount = $("#inputField").val(); // using jQuery, where inputField is element
// from which the value is taken e.g. < input >
...
}
To display the final output, add this to your function:
$("body").append("<div id='output'>" + output + "</div>"); // using jQuery, watch for single/double quotes
and style it with css to be in the center:
#output {
position: absolute;
width: 200px;
height: 200px;
left: 50%;
margin-left: -100px;
top: 200px;
}
I made a fiddle that you should be able to get what is going on pretty quickly.
http://jsfiddle.net/a58rR/3/
I used a touch of jQuery just to get the bindings on the UI elements.
I hope this is not for a class and you copy this wholesale! ;)
Basically, I put all your Tier pricing into one object.
Tiers = [{
nPrice: 20,
nWeightMin: 1,
nWeightMax: 150
}, {
nPrice: 15,
nWeightMin: 151,
nWeightMax: 300
}, {
nPrice: 10,
nWeightMin: 301,
nWeightMax: 400
}];
Then, your function will calculate based on the entered weight and discount selected, determine the Tier, validate the weight, update the UI with a message if out of range, calculate the final price and apply any discount, and update the UI with the total price:
function calculatePrice() {
console.log('Begin Calc');
var _nW = document.getElementById('nParcelWeight').value * 1;
var _nD = document.getElementById('aDiscounts').value * 1;
var _nP = 0;
var nTotalPrice = 0;
var _TotalPrice = document.getElementById('nPrice');
var _nMaxWt = Tiers[Tiers.length - 1].nWeightMax;
// Using the last Tier keeps the max weight dynamic no matter how many tiers you add as long as they are in order
console.log('Max Weight: ' + _nMaxWt);
console.log('Weight: ' + _nW);
console.log('Discount: ' + _nD);
if (isNaN(_nW) || _nW < 1 || _nW > _nMaxWt) {
// Throw/Display an out of range error here
console.log('Yep, out of range');
document.getElementById('uiFeedback').innerHTML = 'The number is out of range.';
} else {
// reset if valid
document.getElementById('uiFeedback').innerHTML = '';
}
// Find Tier
for (var i = 0; i < Tiers.length; i++) {
console.log('we are in loop:' + i);
if (_nW >= Tiers[i].nWeightMin && _nW <= Tiers[i].nWeightMax) {
_nP = Tiers[i].nPrice;
break;
}
}
console.log('Tier: ' + i);
console.log('Price: ' + _nP);
// Calculate Discount
if (_nD != 1) _nD = 1 - _nD; // (20%==.20, but that would be .80 of the Price, etc)
// Calc Price
nTotalPrice = (_nP * _nW * _nD);
_TotalPrice.value = nTotalPrice;
}
The html will look something like this:
<div id='uiFeedback'></div>Parcel Weight:
<input id='nParcelWeight' value='0'>Discount:
<select id='aDiscounts'>
<option value='1'>none</option>
<option value='.2'>20%</option>
<option value='.5'>50%</option>
</select>
<hr>Price:
<input id='nPrice'>
And your CSS at a minimum might just color your messaging:
#uiFeedback {
color: red;
font-weight: bold;
}
Here are the bindings, which you could do with inline onChanges or raw js attach events:
$(function () {
$('#nParcelWeight,#aDiscounts ').on('change', function () {
calculatePrice();
});
})

Categories