12

I've got a simple number input with a min="1" and max="12" value set, this is used as an hour selector. I'd like it to cycle through the hours, so when you get to 12 and press the "up" arrow, it goes back to 1 and vice-versa as well.

Right now I have this mostly working:

var inputTimer = null;

function cycle(element) {
  if (element.attributes.max && element.attributes.min) {
    var prevVal = element.value;
    inputTimer = setTimeout(function() {
      if (prevVal === element.attributes.max.value) {
        element.value = element.attributes.min.value;
      } else if (prevVal === element.attributes.min.value) {
        element.value = element.attributes.max.value;
      }
    }, 50);
  }
}

$("input[type='number']")
  .on("mousedown", function(e) {
    //this event happens before the `input` event!
    cycle(this);
  }).on('keydown', function(e) {
    //up & down arrow keys
    //this event happens before the `input` event!
    if (e.keyCode === 38 || e.keyCode === 40) {
      cycle(this);
    }
  }).on('input', function(e) {
    //this event happens whenever the value changes
    clearTimeout(inputTimer);
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" min="1" max="12" value="12" />

Working DEMO

The issue I have is that I can't find a way to detect if the arrow spinners in the input have been clicked, or just the input as a whole has been clicked. Right now it has an issue where it changes the value when you click anywhere in the field when the value is currently at 1 or 12

Is there a way to detect if the click event occurs on the spinners/arrows within the text field?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
  • `onchange` event is working fine http://jsfiddle.net/29bea3y0/ – Ramanlfc Dec 03 '15 at 17:27
  • @Ramanlfc no it isn't. I can't cycle around back to the min/max values like in my demo above. – Chris Barr Dec 03 '15 at 17:53
  • Perhaps the answer here, on how to get mouse co-ordinates could be used in combination with what you already have: https://stackoverflow.com/questions/7790725/javascript-track-mouse-position – Martin Eyles Aug 20 '20 at 01:46
  • I have also looked at using this.style.cursor, but it always returns "", and $(this).css('cursor'), but it always returns "text", whether or not you are over the up/down arrows. – Martin Eyles Aug 20 '20 at 16:22
  • Posted an alternate answer here: https://stackoverflow.com/a/76673785/3622569 – Ben in CA Jul 12 '23 at 19:15

8 Answers8

2

You have to handle the input event, like this:

$('[type=number]').on('input',function(){
  this.value %= 12 ;
  if( this.value < 1 )
    this.value -= -12 ;
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type=number>
GetFree
  • 40,278
  • 18
  • 77
  • 104
  • This doesn't use the min and max attributes and would not trigger if min were set to 1 and max to 12. – Martin Eyles Aug 20 '20 at 01:35
  • @MartinEyles, this is just an example with hard-coded minimum and maximum. It's trivial to get those numbers from anywhere else, like the element's `min` and `max` attributes. – GetFree Sep 15 '20 at 04:22
  • @MartinEyles - here's that same answer but referencing the element's `min` & `max` values: `` – ashleedawg Nov 01 '21 at 09:32
1

I searched a lot and it seems there is no way to natively detect that. That makes this one a very important question because I think this should be added to new versions of HTML.

There are many possible workarouds. They all fail on the problem the it's impossible to know, in which direction is value going. I decided to use mouse position information to detect, whether is user increasing or decreasing a value. It works, but does not properly handle the situation, when user holds the button.

var inputTimer = null;

function cycle(event) {
  var value = this.value;
  // Value deep within bonds -> no action
  if(value>this.min && value<this.max) {
    return;  
  }
  // Check coordinate of the mouse
  var x,y;
  //This is the current screen rectangle of input
  var rect = this.getBoundingClientRect();
  var width = rect.right - rect.left;
  var height = rect.bottom-rect.top;
  //Recalculate mouse offsets to relative offsets
  x = event.clientX - rect.left;
  y = event.clientY - rect.top;
  // Now let's say that we expect the click only in the last 80%
  // of the input
  if(x/width<0.8) {
    console.log("Not click on arrows.", x, width);  
    return;
  }
  // Check "clicked button" by checking how high on input was clicked
  var percentHeight = y/height;
  // Top arrow was clicked
  if(percentHeight<0.5 && value==this.max) {
      this.value = this.min; 
      event.preventDefault();
      return false;
  }
  // Bottom arrow was clicked
  if(percentHeight>0.5 && value==this.min) {
      this.value = this.max; 
      event.preventDefault();
      return false;
  }
}
var input = document.getElementById("number");
input.addEventListener("mousedown", cycle);
<input id="number" type="number" min="1" max="12" value="12" />
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • Doesn't work in safari OS X. Stops at min and max without going back to the opposite range value. – AtheistP3ace Dec 03 '15 at 21:25
  • That's awkward. Are there any errors or does it just stop? I am able to test it in VM like you did, but I have strong hint it's safari who's doing something wrong. – Tomáš Zato Dec 03 '15 at 21:27
  • No errors in console. It just stops but lots of logs saying `Not click on arrows. 25 33` And it logs that for every click. At start, in between or end of range and when I click up and down arrow. – AtheistP3ace Dec 03 '15 at 21:29
  • Ah, that's related to arrows size. I decided to assume that size percentually since it's impossible to calculate it cross platform. If I change the percent coefficient to smaller number, it will work but also will be more likely to change value when you didn't click the arrow. – Tomáš Zato Dec 03 '15 at 21:33
  • Gotcha. Well, either way quite an original solution you have there. Very nice! – AtheistP3ace Dec 03 '15 at 21:34
  • 1
    @AtheistP3ace I hope we get official solution from W3C soon, these workarounds are quite unsatisfying. I've been thinking about it all the way home (I made this answer 4 hours ago at work) but didn't figure anything else. – Tomáš Zato Dec 03 '15 at 21:36
  • 1
    I agree. This question has had me thinking about it all day. I even was talking about it with another developer at work. This doesn't seem like a outlandish idea to expect a range to be circular. Surprised it doesn't have an attribute already to enable it. – AtheistP3ace Dec 03 '15 at 21:37
1

A method you could try is by using the Attributes of the element to track what the previous value is. This isn't, of course, actually tracking which button got hit but it's the closest I've been able to get.

JS:

$(document).ready(function() {
    function Init(){
        var test = document.getElementById('test');
        test.setAttribute('prev', 0);
    }   
    
    Init()

    $('#test').on('input', function() {
        var test = document.getElementById('test')
        var d = test.value - test.getAttribute('prev');
        console.log(d);
        test.setAttribute('prev', test.value);
    });
});

HTML:

<input type="number" id="test">

Then all you would have is logic that says if d(irection) is positive, they clicked up. If negative, they clicked down. If it's 0 then they didn't click a button.

Working Fiddle

mshaw
  • 47
  • 7
  • Stack Overflow lets you embed a snippet that's just like a working fiddle, it's preferable over a link to a fiddle – Zach Jensz Jun 23 '22 at 01:09
0

I think this is what you really want.

<input type="time" value="01:00" step="600"/>

There is currently no native way to capture the arrow input events separate from the input box events. Everything using number input seems to be kinda hacky for this purpose.

Next best option is something like http://jdewit.github.io/bootstrap-timepicker/

  • It looks like the `time` type is supported by IE, Firefox, or Safari though :( http://quirksmode.org/html5/inputs/ – Chris Barr Dec 03 '15 at 20:32
  • Typo above! That should be "**isn't supported**" ! – Chris Barr Dec 04 '15 at 13:35
  • Via jQuery you can capture the "mousewheel" event, which covers the input spinner actions. A.g.: ```$("#InputID").on('change mousewheel', function() { ... });``` will fire when something change and after any of the spinner buttons are clicked (also covering the mouse-wheel action as well, of course). Via vanilla Javascript, you can always write a function checking for ```e.type == "wheel"```. I never tried it, but it should work. Here more info of how to implement the event detection: https://stackoverflow.com/questions/25204282/mousewheel-wheel-and-dommousescroll-in-javascript – Julio Marchi Oct 17 '17 at 19:57
  • You can't use input type of time if you want to use the most significant part to represent an unlimited number (eg. 45 hours and 10 minutes as 45:10). You would still need to use two separate number inputs for this. – Martin Eyles Aug 20 '20 at 16:29
0

This doesn't work for your specific situation where you have a maximum and want it to wrap, but it might be helpful for others who want to process the field value based on changes via arrows, such as for setting .toFixed(2) to a currency value like I needed:

document.getElementById('el').setAttribute('data-last',document.getElementById('el').value);
document.getElementById('el').addEventListener('keyup', function(){
    this.setAttribute('data-last',this.value);
});
document.getElementById('el').addEventListener('click', function(){
    if(this.value>this.getAttribute('data-last')) console.log('up clicked');
    if(this.value<this.getAttribute('data-last')) console.log('down clicked');
});
dw1
  • 1,495
  • 13
  • 15
0

This is my code written in JQuery , this one can implement auto-increment ( + & - ) long-press spin buttons .

$.fn.spinInput = function (options) {
    var settings = $.extend({
        maximum: 1000,
        minimum: 0,
        value: 1,
        onChange: null
    }, options);

    return this.each(function (index, item) {
        var min = $(item).find('>*:first-child').first();
        var max = $(item).find('>*:last-child').first();
        var v_span = $(item).find('>*:nth-child(2)').find('span');
        var v_input = $(item).find('>*:nth-child(2)').find('input');
        var value = settings.value;
        $(v_input).val(value);
        $(v_span).text(value);
        async function increment() {
            value = Number.parseInt($(v_input).val());
            if ((value - 1) > settings.maximum) return;
            value++;
            $(v_input).val(value);
            $(v_span).text(value);
            if (settings.onChange) settings.onChange(value);
        }
        async function desincrement() {
            value = Number.parseInt($(v_input).val());
            if ((value - 1) < settings.minimum) return;
            value--
            $(v_input).val(value);
            $(v_span).text(value);
            if (settings.onChange) settings.onChange(value);
        }
        var pressTimer;

        function actionHandler(btn, fct, time = 100, ...args) {
            function longHandler() {
                pressTimer = window.setTimeout(function () {
                    fct(...args);
                    clearTimeout(pressTimer);
                    longHandler()
                }, time);
            }
            $(btn).mouseup(function () {
                clearTimeout(pressTimer);
            }).mousedown(function () {
                longHandler();
            });

            $(btn).click(function () {
                fct(...args);
            });
        }

        actionHandler(min, desincrement, 100);
        actionHandler(max, increment, 100)
    })
}




$('body').ready(function () {
    $('.spin-input').spinInput({ value: 1, minimum: 1 });
});
:root {
    --primary-dark-color: #F3283C;
    --primary-light-color: #FF6978;
    --success-dark-color: #32A071;
    --sucess-light-color: #06E775;
    --alert-light-color: #a42a23;
    --alert-dark-color: #7a1f1a;
    --secondary-dark-color: #666666;
    --secondary-light-color: #A6A6A6;
    --gold-dark-color: #FFA500;
    --gold-light-color: #FFBD00;
    --default-dark-color: #1E2C31;
    --default-light-color: #E5E5E5;
}

.fx-row {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
}

.fx-colum {
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
}

.fx-colum.nowrap,
.fx-row.nowrap {
    flex-wrap: nowrap;
}

.fx-row.fx-fill>*,
.fx-colum.fx-fill>* {
    flex-grow: 1;
}

.spin-input {
    border: 1px solid var(--secondary-light-color);
}

.spin-input>div:first-child {
    cursor: pointer;
    border-right: 1px solid var(--secondary-light-color);
}

.spin-input>div:first-child:active {
    transform: translate3d(1px, 0px, 1px)
}

.spin-input>div:last-child {
    flex: none;
    border-left: 1px solid var(--secondary-light-color);
    cursor: pointer;
}

.spin-input>div:last-child:active {
    transform: translate3d(1px, 0px, 1px)
}

.icon {
    font-weight: bold;
    text-align: center;
    vertical-align: middle;
    padding: 12px;
    font-size: 28px;
}

.icon.primary,
.icon.primary .ci {
    color: var(--primary-dark-color);
}

.icon.reactive:hover .ci {
    color: var(--primary-light-color);
}

.hidden {
    display: none;
}
<script src="https://releases.jquery.com/git/jquery-3.x-git.min.js"></script>
<div class="spin-input nowrap fx-row fx-fill" >
                        <div class="icon reactive">
                            <span class="ci ci-minus">-</span>
                        </div>
                        <div class="icon">
                            <span>0</span>
                            <input type="text" class="hidden" value="0">
                        </div>
                        <div class="icon reactive">
                            <span class="ci ci-plus">+</span>
                        </div>
                    </div>

There is my jQuery plugin , I hope that can help you .

AamirSohailKmAs
  • 314
  • 6
  • 14
-1

So I am not sure there is anyway to determine what is being clicked, be it field input or little arrows, but I was able to get it working like this.

Fiddle: http://jsfiddle.net/nusjua9s/4/

JS:

(function($) {

    var methods = {
        cycle: function() {
            if (this.attributes.max && this.attributes.min) {
                var val = this.value;
                var min = parseInt(this.attributes.min.value, 10);
                var max = parseInt(this.attributes.max.value, 10);
                if (val === this.attributes.max.value) {
                    this.value = min + 1;
                } else if (val === this.attributes.min.value) {
                    this.value = max - 1;
                } else if (!(val > min && val < max)) {
                    // Handle values being typed in that are out of range
                    this.value = $(this).attr('data-default');
                }
            }
        }
    };

    $.fn.circularRange = function() {
        return this.each(function() {
            if (this.attributes.max && this.attributes.min) {
                var $this = $(this);
                var defaultVal = this.value;
                var min = parseInt(this.attributes.min.value, 10);
                var max = parseInt(this.attributes.max.value, 10);
                $this.attr('min', min - 1);
                $this.attr('max', max + 1);
                $this.attr('data-default', defaultVal);
                $this.on("change", methods.cycle);
            }
        });
    };

})(jQuery);

$("input[type='number']").circularRange();

HTML:

<input type="number" min="1" max="12" value="12" />

So I am not sure why I keep thinking about this and it still doesn't solve what you are seeing with the flash of out of range numbers which I don't see. But now its not confusing to setup the html ranges at least. You can set the range you want without thinking and just initialize the type="number" fields.

AtheistP3ace
  • 9,611
  • 12
  • 43
  • 43
  • Hmmm, for me it happens so fast I don't see it. @TomášZato You mean you see it switch from 13 to 1? Or it allows you to go to 13? – AtheistP3ace Dec 03 '15 at 17:47
  • Yes, I can clearly see "0" and "13" flash briefly, and this also involves a lot of hard-coding of numbers. I'd like to be able to re-use this code in a generic way. – Chris Barr Dec 03 '15 at 17:50
  • @ChrisBarr updated answer to remove hard coded numbers. As for the flashing I don't see that. Let me think if I can find another way to do it. – AtheistP3ace Dec 03 '15 at 17:55
  • @AtheistP3ace True this doesn't hard code the numbers any more, but it's confusing to set up. You have to know ahead of time that if you want a max value of 12, you have to set it to 13. It does work, but it's really odd and the "out of range" values do briefly flash for me on Chrome in Windows. – Chris Barr Dec 03 '15 at 17:57
  • Well I attempted to remove the confusion of setting up fields but again it doesn't solve the flash you are seeing @ChrisBarr. – AtheistP3ace Dec 03 '15 at 18:55
  • Well, this becomes notable when you *hold* the mouse button. I tested this in chrome and opera with same result as in firefox - the value flashes and persists until you release the button. – Tomáš Zato Dec 03 '15 at 21:12
  • Ah I do see it. I was using safari on OS X and did not see it at all, not even on holding down the button. Opened a windows VM and used chrome and there it is. Also sticks on holding down the button. Well I suppose I will just delete this answer since I am being down voted and it seems I was no help to anyone here. – AtheistP3ace Dec 03 '15 at 21:21
  • It's better than nothing. Since the problem has no official solution, any answer has a meaning. I like how you made it into working jquery plugin, though I personally do not use jQuery for everything. – Tomáš Zato Dec 03 '15 at 21:25
  • 1
    Yea I really don't like jQuery either, I just did it because he said its confusing setting the numbers to +/-1 and I saw him using jquery in his question. I guess I will leave answer. Maybe it will help someone one day =] – AtheistP3ace Dec 03 '15 at 21:27
-3

Try with $('input[type="number"]').change(function() {}); ? No result ?