I use jquery mobile sliders:
<div data-role="fieldcontain">
<input type="range" name="slider1" id="slider1" value="0" min="0" max="255" />
</div><br><br>
<div data-role="fieldcontain">
<select name="switcher1" id="switcher1" data-role="slider">
<option value="0">Off</option>
<option value="255">On</option>
</select>
</div>
It should work in the following way:
if switcher1 is touched, then slider1 value should be set either to 0 or to 255;
if slider1 value is changed, then (a) if it is = 0, then switcher1 should be set to 0, (b) else switcher1 value should be set to 255.
I've tried to do so with change event (the same is used for switcher1 change()):
var slider1_val = "0";
$('#slider1').change(function () {
sVal = $(this).val();
if (slider1_val !== sVal) {
$("#slider1").val(sVal).slider("refresh");
if (sVal == 0) {
$("#switcher1").val(0).slider("refresh");
} else {
$("#switcher1").val(255).slider("refresh");
}
slider1_val = sVal;
}
});
But looks like each call of refresh calls change event, so I am getting infinite loop.
It should work in the following way:
if switcher1 is touched, then slider1 value should be set either to 0 or to 255;
if slider1 value is changed, then (a) if it is = 0, then switcher1 should be set to 0, (b) else switcher1 value should be set to 255.
I've tried to do so with change event (the same is used for switcher1 change()):
The fact that you have two very different criteria for changing each control should tip you off that the change event handlers should be different as well. Using the same handlers leads to the infinite loop you are experiencing. The code below accounts for the strict change criteria you've provided. Note that the slider1 change handler changes switcher1 only if it needs to be changed (based on your criteria), not every time it is called. Also, note that in the slider1 change handler, switcher1_val is set before calling refresh, so that in case .slider('refresh') does call the change handler, the change handler will not do anything, because switcher1_val is already updated.
var linkSliders = function(sliderId, switcherId){
var slider = $('#'+sliderId),
switcher = $('#'+switcherId);
var min = Math.min(switcher[0].options[0].value, switcher[0].options[1].value),
max = Math.max(switcher[0].options[0].value, switcher[0].options[1].value);
var sliderVal = switcherVal = min;
// set the slider min/max to be the same as the switcher's, just in case they're different
slider.attr('max', max).attr('min', min).slider('refresh');
slider.change(function(){
var sVal = $(this).val();
if(sliderVal != sVal){
if( sVal == min && switcherVal!=min){
switcherVal=min;
switcher.val(min).slider('refresh');
}else if(sVal>min && switcherVal!=max){
switcherVal=max;
switcher.val(max).slider('refresh');
}
sliderVal = sVal;
}
});
switcher.change(function(){
var sVal = $(this).val();
if(switcherVal != sVal){
slider.val(sVal).slider('refresh');
switcherVal = sVal;
}
});
};
linkSliders('slider1','switcher1');
See the live example.
Hope this helps.
Update: As requested, the example has been modified to make it more general.
Related
First of all: Svelte is still new to me. I hope the question is not too trivial.
Within a simple component I want to use the content of a formatted input field for a calculation.
For example:
In the input field a Euro amount should be displayed formatted (1.000).
Next to it a text with the amount plus VAT should be displayed (1.190).
How I do this without formatting is clear to me. The example looks like this:
export let net;
export let vat;
$: gross = net + (net * vat / 100);
$: grossPretty = gross.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
with a simple markup like this:
<form>
<label>Net amount</label>
<input type="text" step="any" bind:value={net} placeholder="Net amount">
</form>
<div>
Gros = {grossPretty} €
</div>
In vue i used a computed property. Its getter delivers the formatted string and its setter takes the formatted string and saves the raw value.
(In data() I define net, in the computed properties i define netInput. The input field uses netInput as v-model).
It looks like this:
netInput: {
get(){
return this.net.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
},
set(s){
s = s.replace(/[\D\s._-]+/g, "");
this.net = Number(s);
}
}
How can I handle it in svelte?
You can do something somewhat similar, you create another computed variable that stores the deformatted string from the input field and is used in the calculation instead of the direct input
export let net;
export let vat;
$: net_plain = Number(net.replace(/[\D\s._-]+/g, ""));
$: gross = net_plain + (net_plain * vat / 100);
$: grossPretty = gross.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
But maybe find a better name for the variable :)
Thanks to Stephane Vanraes I found a solution.
It has not the charm of the vue approach but it's ok. First I inserted 'net_plain'. To have the input field formatted during input, I added an event listener for the keyup event.
<input type="text" step="any" bind:value={net} on:keyup={handleKeyUp} placeholder="Net amount">
The event is handled from the function handleKeyUp as follows:
function handleKeyUp(event){
if ( window.getSelection().toString() !== '' ) {
return;
}
// ignore arrow keys
let arrows = [38,40,37,39];
if ( arrows.includes( event.keyCode)) {
return;
}
let input = event.target.value.replace(/[\D\s._-]+/g, "");
input = input ? parseInt( input, 10 ) : 0;
event.target.value = ( input === 0 ) ? "" : input.toLocaleString( "de-DE" );
}
BUT: If anyone has a solution using getter and setter I would appreciate the anwer!
In the following code I create 3 boxes with the text 1 to 3, in a fourth box I'd like to show the text of the box my mouse is hovering over. So i set an onMouseEnter FProperty for each of the boxes where I change the string of the fourth box and tell it to redraw.
bool redraw = false;
str s = "0";
Figure getTextbox() {
return computeFigure(bool() {bool temp = redraw; redraw = false; return temp; },
Figure() {
return text(str() {return s; });
});
}
list[Figure] boxes = [];
for (i <- [1..4]) {
boxes += box(text(toString(i)), onMouseEnter(void () {s = toString(i); redraw = true; }));
}
Figure changer = box(getTextbox());
render(vcat(boxes + changer));
However, for some odd reason all three boxes will tell the onMouseEnter method to change the text of the fourth box into "3" (the value of the last box) instead of their individual value.
Any clue why? Thanks!
Ah yes, this is the variable capturing closure problem with for loops, also known from other languages which have this particular feature like Javascript. This is the code with the issue:
for (i <- [1..4]) {
boxes += box(text(toString(i)), onMouseEnter(void () {s = toString(i); redraw = true; }));
}
The variable i is bound by the void closure and not its value. So every time the function which is created and passed to onMouseEnter it will read the latest value of the i variable. Since the callback is called after the loop terminates, all calls to the mouse enter function will have the value 3.
To fix this and "do what you want", the following code would work I believe:
for (i <- [1..4]) {
newS = toString(i);
boxes += box(text(toString(i)), onMouseEnter(void () {s = newS; redraw = true; }));
}
This works because for every pass of the for loop a new environment is created which binds the newS variable. So you'll get a fresh newS for every loop instead of the reused i.
I'm using Angular Bootstrap UI Timepicker and if I enter 50 in the hours field, it highlights the field red, but nevertheless I get a valid date out of it that looks just like that Tue Nov 15 2016 05:00:55 GMT+0100.
Any ideas what I can do? Thanks!
For that matter you could consider to limit the hour and minute input values, here is how to customize it for Timepicker
introduce a custom template and specify the min, max and validate-value attributes for hour and minute input elements, for example:
<input type="text" placeholder="HH" validate-value min="1" max="12" ng-model="hours" ng-change="updateHours()" class="form-control text-center" ng-readonly="::readonlyInput" maxlength="2" tabindex="{{::tabindex}}" ng-disabled="noIncrementHours()" ng-blur="blur()">
implement validate-value directive to limit the number values in input element:
.directive('validateValue', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$parsers.unshift(function(viewValue) {
var min = Number(eval(attrs.min));
var max = Number(eval(attrs.max));
var value = Number(viewValue);
var valid = (!isNaN(value) && value >= min && value <= max);
if (!valid) {
var currentValue = ngModelCtrl.$modelValue.toString();
ngModelCtrl.$setViewValue(currentValue);
ngModelCtrl.$render();
return currentValue;
}
else {
return viewValue;
}
});
}
};
})
Demo
I have a pretty straight-forward range input slider. It works pretty well on all browsers except on iOS Safari.
The main problem I have is when I click on the slider track, it doesn't move the slider. It merely highlights the slider. It works fine when dragging the thumb. Any ideas on how to fix this?
<div class="slider-wrap">
<div id="subtractBtn"></div>
<input type="range" class="slider">
<div id="addBtn"></div>
</div>
For those still looking for a javascript only fix like I did. I ended up just making a "polyfill" for this; it relies on the max attribute of the slider and determines the best step to force the input range on iOS. You can thank me later :)
var diagramSlider = document.querySelector(".diagram-bar");
function iosPolyfill(e) {
var val = (e.pageX - diagramSlider.getBoundingClientRect().left) /
(diagramSlider.getBoundingClientRect().right - diagramSlider.getBoundingClientRect().left),
max = diagramSlider.getAttribute("max"),
segment = 1 / (max - 1),
segmentArr = [];
max++;
for (var i = 0; i < max; i++) {
segmentArr.push(segment * i);
}
var segCopy = segmentArr.slice(),
ind = segmentArr.sort((a, b) => Math.abs(val - a) - Math.abs(val - b))[0];
diagramSlider.value = segCopy.indexOf(ind) + 1;
}
if (!!navigator.platform.match(/iPhone|iPod|iPad/)) {
diagramSlider.addEventListener("touchend", iosPolyfill, {passive: true});
}
<input type="range" max="6" min="1" value="1" name="diagram selector" class="diagram-bar" />
Add the CSS property, -webkit-tap-highlight-color: transparent to the CSS of the element or the complete html page. This will remove the troublesome highlight effect on an element when it is tapped on a mobile device.
Enhanced version of the solution presented by Cameron Gilbert. This implementation works for sliders with a min value set to a negative number and optional a step size set.
For those not working with typescript I leave it to you to convert to plain javascript.
if (!!navigator.platform.match(/iPhone|iPad|iPod/)) {
mySlider.addEventListener(
"touchend",
(touchEvent: TouchEvent) => {
var element = touchEvent.srcElement as HTMLInputElement;
if (element.min && element.max && touchEvent.changedTouches && touchEvent.changedTouches.length > 0) {
const max = Number(element.max);
const min = Number(element.min);
let step = 1;
if (element.step) {
step = Number(element.step);
}
//Calculate the complete range of the slider.
const range = max - min;
const boundingClientRect = element.getBoundingClientRect();
const touch = touchEvent.changedTouches[0];
//Calculate the slider value
const sliderValue = (touch.pageX - boundingClientRect.left) / (boundingClientRect.right - boundingClientRect.left) * range + min;
//Find the closest valid slider value in respect of the step size
for (let i = min; i < max; i += step) {
if (i >= sliderValue) {
const previousValue = i - step;
const previousDifference = sliderValue - previousValue;
const currentDifference = i - sliderValue;
if (previousDifference > currentDifference) {
//The sliderValue is closer to the previous value than the current value.
element.value = previousValue.toString();
} else {
element.value = i.toString();
}
//Trigger the change event.
element.dispatchEvent(new Event("change"));
break;
}
}
}
},
{
passive: true
}
);
}
Safari 5.1 + should support type "range" fully - so it's weird it's not working.
did you try adding min/max/step values ? what about trying to use only the pure HTML attribute without any classes that might interfere - try pasting this code and run it on a Safari browser
<input type="range" min="0" max="3.14" step="any">
it should be working - if it does, it's probably something related to your css, if not try using one of the pure HTML examples here:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range
Try using jQuery Mobile? The workaround is obnoxious, and I'm pretty sure there's no way to use the native interface with standard HTML. It appears from other threads, you could do an elaborate CSS/JS solution.
Is there any Attributes to be able to put popup panel show in the position of the mouse cursor in richFaces 4.?
I now think of one solution is to impact on the show event in rich: component (). Show() function, set the location for the popup by javascript.
You can directly handle the showing of a richfaces modal panel through javascript as shown Here
this js function below will give you the x/y coordinate position of the mouse pointer, the return value of which you can pass as arguments to the Richfaces.showModalPanel function
function getPosition(e) {
e = e || window.event;
var cursor = {x:0, y:0};
if (e.pageX || e.pageY) {
cursor.x = e.pageX;
cursor.y = e.pageY;
}
else {
var de = document.documentElement;
var b = document.body;
cursor.x = e.clientX +
(de.scrollLeft || b.scrollLeft) - (de.clientLeft || 0);
cursor.y = e.clientY +
(de.scrollTop || b.scrollTop) - (de.clientTop || 0);
}
return cursor;
}
so you can have RichFaces.showModalPanel('panel',{top:getPosition(ev).y,left:getPosition(ev).x})
Sorry about the hideous javascript.