Customize numbers in JavaScript with a Unity-inspired design changer

I am currently working on implementing a number input feature that allows users to adjust values by clicking and holding the mouse button while moving the cursor left and right, similar to Unity's editor number adjuster: https://youtu.be/uY9PAcNMu8s?t=907

The challenge I am facing now is making it function properly when there are multiple elements with this behavior. I have been attempting to differentiate between the elements being adjusted by utilizing unique ids and classes. Below is the source code snippet (copy and paste into an empty HTML file, then open in a browser):

<input type="number" id="Testing1" value="0"><span class="ClickHoldChangeNumber" id="MouseDragSpace1"><table><tr><td style="width:1000; height:100; border-width:1px; border: 1px solid black;"></td></tr></table></span><br><br>
<input type="number" id="Testing2" value="0"gt;<span class="ClickHoldChangeNumber" id="MouseDragSpace2"><table><tr><td style="width:1000; height:100; border-width:1px; border: 1px solid black;"></td></tr></table></span><br><br>

<span id="MousePos"></span>
<span id="DeltaDrag"></span>
<span id="IncrementDecrement"></span>
<script>
    //Initializing variables
        DragOriginX = 0;
        DragOriginY = 0;
        WhichElement = "";
        ClickHoldChangeNumberClasses = document.getElementsByClassName("ClickHoldChangeNumber");
    window.onload=function(){
        SourcePosition = 0;
        MouseState = "";
        let thing = "";
        
        /* Loop through all classes and add event listeners */
        let Index = 0;
        while (Index < ClickHoldChangeNumberClasses.length) {
            thing = ClickHoldChangeNumberClasses[Index].id;
            ClickHoldChangeNumberClasses[Index].addEventListener('mousedown', function(){PlaceDragOriginPos(ClickHoldChangeNumberClasses[Index].id)});
            Index++;
        }
        document.addEventListener('mouseup', ExitPlaceDragOriginPos);
        document.addEventListener('mousemove', logKey);
    }
    
    function logKey(e) {
        if (MouseState != "PressedDown") {
            DragOriginX = e.clientX;
            DragOriginY = e.clientY;
        }
        document.getElementById("MousePos").innerHTML = "Mouse position: (" + DragOriginX.toString(10) + ", " + DragOriginY.toString(10) + ")<br>";
        document.getElementById("DeltaDrag").innerHTML = "Delta from mouse position: (" + (e.clientX - DragOriginX).toString(10) + ", " + (e.clientY - DragOriginY).toString(10) + ")<br>";

        if (MouseState == "PressedDown") {
            SubFunctionVariable = [e.clientX, e.clientY];
            MouseMoveIncreaseDecrease();
        }
    }
    
    function MouseMoveIncreaseDecrease() {
        document.getElementById("Testing1").value = (SourcePosition + (SubFunctionVariable[0] - DragOriginX)).toString(10);
    }
    
    function PlaceDragOriginPos(ElementID) {
        MouseState = "PressedDown";
    }

    function ExitPlaceDragOriginPos() {
        SourcePosition = parseInt(document.getElementById("Testing1").value);
        MouseState = "Released";
    }
</script>

Although the page loads correctly, I encounter an error (

Cannot read property 'id' of undefined
) when trying to click-hold and adjust the values in the rectangular boxes, even though I defined them globally. The reason for them being undefined despite setting them during page load remains unclear to me.

On a related note, does anyone know the technical term used to describe this UI functionality?

Answer №1

More effective approach: bind

<input type="number" id="Testing1" value="0"><span class="ClickHoldChangeNumber" id="MouseDragSpace1"><table><tr><td style="width:1000; height:100; border-width:1px; border: 1px solid black;"></td></tr></table></span><br><br>
<input type="number" id="Testing2" value="0"><span class="ClickHoldChangeNumber" id="MouseDragSpace2"><table><tr><td style="width:1000; height:100; border-width:1px; border: 1px solid black;"></td></tr></table></span><br><br>


<span id="MousePos"></span>
<span id="DeltaDrag"></span>
<span id="IncrementDecrement"></span>
<span id="SourcePosition"></span>
<script>
    //Initializing variables
        DragOriginX = 0
        DragOriginY = 0
        WhichElement = ""
        ClickHoldChangeNumberClasses = document.getElementsByClassName("ClickHoldChangeNumber")
    window.onload=function(){
        SourcePosition = 0
        MouseState = ""
        let thingy = ""
        //ClickHoldChangeNumberClasses = document.getElementsByClassName("ClickHoldChangeNumber")
        //addEventListener is specific to each element, so we loop to add for each class.
            let IndexVal = 0
            while (IndexVal < ClickHoldChangeNumberClasses.length) {
                thingy = ClickHoldChangeNumberClasses[IndexVal].id
                ClickHoldChangeNumberClasses[IndexVal].addEventListener('mousedown', setDragOrigin.bind(event, ClickHoldChangeNumberClasses[IndexVal].id))
                IndexVal++
            }
        document.addEventListener('mouseup', doneWithDragOrigin);
        document.addEventListener('mousemove', captureKey);
        
    }
    function captureKey(e) {
        if (MouseState != "PressedDown") {
            DragOriginX = e.clientX
            DragOriginY = e.clientY
        }
        document.getElementById("MousePos").innerHTML = "Mouse position: (" + DragOriginX.toString(10) + ", " + DragOriginY.toString(10) + ")<br>"
        document.getElementById("DeltaDrag").innerHTML = "Delta from mouse position: (" + (e.clientX - DragOriginX).toString(10) + ", " + (e.clientY - DragOriginY).toString(10) + ")<br>"
        if (MouseState == "PressedDown") {
            SubFunctionVariable = [e.clientX, e.clientY]
            moveIncreaseDecrease()
        }
    }
    function moveIncreaseDecrease() {
        if (WhichElement == "MouseDragSpace1") {
            document.getElementById("Testing1").value = (SourcePosition + (SubFunctionVariable[0] - DragOriginX)).toString(10)
        } else {
            document.getElementById("Testing2").value = (SourcePosition + (SubFunctionVariable[0] - DragOriginX)).toString(10)
        }
    }
    function setDragOrigin(ElementID) {
        WhichElement = ElementID
        MouseState = "PressedDown"
        //SourcePosition = parseInt(document.getElementById(WhichElement).value)
        if (WhichElement == "MouseDragSpace1") {
            SourcePosition = parseInt(document.getElementById("Testing1").value)
        } else {
            SourcePosition = parseInt(document.getElementById("Testing2").value)
        }
    }
    function doneWithDragOrigin() {
        //SourcePosition = parseInt(document.getElementById("Testing1").value)
        
        MouseState = "Released"
    }
</script>

This is still a work in progress. The intention is to simplify the process of adding more number inputs without requiring code modifications using this JavaScript system.

Answer №2

A recent update with improved revisions has finally resolved the selection issue that was disrupting the system. Previously, the problem caused the system to ignore the first mouseup event, leaving it in a constant "drag" state even after the user released the left mouse button. Updates now allow for the addition of more HTML tags with this behavior without requiring any edits to the JS code:

<h1 style="">Enhanced number adjuster inspired by Unity</h1>

<p>Simply click and hold the left mouse button over &ldquo;Value 1/2/3&rdquo; then move your mouse left or right to adjust values in the input boxes</p>

<span class="ClickHoldChangeNumber" id="NumbInput_Testing1">Value 1:</span> <input type="number" id="Testing1" value="0"><br>
<span class="ClickHoldChangeNumber" id="NumbInput_Testing2">Value 2:</span> <input type="number" id="Testing2" value="0"><br>
<span class="ClickHoldChangeNumber" id="NumbInput_Testing3">Value 3:</span> <input type="number" id="Testing3" value="0"><br>

<span id="MousePos"></span>
<span id="DeltaDrag"></span>
<span id="IncrementDecrement"></span>
<span id="SourcePosition"></span>
<script>
//Initialize variables
        MouseDragOriginX = 0
        MouseDragOriginY = 0
        WhichElement = ""
        ClickHoldChangeNumberClasses = document.getElementsByClassName("ClickHoldChangeNumber")
//Attach listeners
    window.onload=function(){
        SourcePosition = 0
        MouseState = ""
        let thing = ""
            let Index = 0
            while (Index < ClickHoldChangeNumberClasses.length) {
                thing = ClickHoldChangeNumberClasses[Index].id
                ClickHoldChangeNumberClasses[Index].addEventListener('mousedown', PlaceDragOriginPos.bind(event, ClickHoldChangeNumberClasses[Index].id))
                Index++
            }
        document.addEventListener('mouseup', ExitPlaceDragOriginPos);
        document.addEventListener('mousemove', logKey);
        
    }
    function logKey(e) {
        if (MouseState != "PressedDown") {
            MouseDragOriginX = e.clientX
            MouseDragOriginY = e.clientY
        }
        document.getElementById("MousePos").innerHTML = "Mouse position: (" + MouseDragOriginX.toString(10) + ", " + MouseDragOriginY.toString(10) + ")<br>"
        document.getElementById("DeltaDrag").innerHTML = "Delta from mouse position: (" + (e.clientX - MouseDragOriginX).toString(10) + ", " + (e.clientY - MouseDragOriginY).toString(10) + ")<br>"
        if (MouseState == "PressedDown") {
            SubFunctionVariable = [e.clientX, e.clientY]
            MouseMoveIncreaseDecrease()
        }
    }
//Handling mouse key press
    function PlaceDragOriginPos(ElementID) {
        WhichElement = ElementID
        MouseState = "PressedDown"
            SourcePosition = parseInt(document.getElementById(WhichElement.substring((WhichElement.search(/_/) + 1), WhichElement.search(/$/))).value)
            if (window.getSelection) {
                if (window.getSelection().empty) {  
                    window.getSelection().empty();
                } else if (window.getSelection().removeAllRanges) {  
                    window.getSelection().removeAllRanges();
                }
            } else if (document.selection) {  
                document.selection.empty();
            }
    }
//Adjusting numbers in the input box while dragging
    function MouseMoveIncreaseDecrease() {
        document.getElementById(WhichElement.substring((WhichElement.search(/_/) + 1), WhichElement.search(/$/))).value = (SourcePosition + (SubFunctionVariable[0] - MouseDragOriginX)).toString(10)
    }
    function ExitPlaceDragOriginPos() {
        MouseState = "Released"
    }
</script>

Answer №3

It appears that the Index is set to 2 after the window.onload function executes the while loop. The event handler is then attached to the element being sought, allowing the use of 'this'.

ClickHoldChangeNumberClasses[Index].addEventListener('mousedown', function(){
    PlaceDragOriginPos(this.id);
});

UPDATE: I have made a new suggestion by replacing input ids with a class (ChangeNumber) and attaching a single event handler to document.body for event delegation. This way, you can identify which element was clicked by examining the event.target and take action only on input.ChangeNumber elements.

The Setting SourcePosition, DragOriginX, and DragOriginY has been relocated to the mousedown event, and logKey now checks if the left mouse button is down, eliminating the need for MouseState, MouseMoveIncreaseDecrease, and ExitPlaceDragOriginPos.

This solution allows for seamless addition of more 'ChangeNumber' elements without requiring any changes to the script.

<h1 style="">Unity-styled number adjuster</h1>
<p>Press and hold down the left mouse button over &ldquo;Value 1/2/3&rdquo; and move your mouse left or right to adjust the value in the input boxes</p>
<span>Value 1:</span> <input type="number" class="ChangeNumber" value="0"><br>
<span>Value 2:</span> <input type="number" class="ChangeNumber" value="0"><br>
<span>Value 3:</span> <input type="number" class="ChangeNumber" value="0"><br>
<span id="MousePos"></span>
<span id="DeltaDrag"></span>
<span id="IncrementDecrement"></span>
<span id="SourcePosition"></span>
<script>
    DragOriginX = 0;
    DragOriginY = 0;
    SourcePosition = 0;
    WhichElement = null;
    window.onload=function(){
        // Delegate a single event handler to handle all ChangeNumber
        document.body.addEventListener('mousedown', function(e){
            WhichElement = e.target.closest('.ChangeNumber');
            if (WhichElement) { // Clicked on a input.ChangeNumber?
                SourcePosition = parseInt(WhichElement.value);
                DragOriginX = e.clientX;
                DragOriginY = e.clientY;
            }
        });
        document.body.addEventListener('mousemove', logKey);        
    }
    function logKey(e) {
        document.getElementById("MousePos").innerHTML = "Mouse position: (" + DragOriginX.toString(10) + ", " + DragOriginY.toString(10) + ")<br>"
        document.getElementById("DeltaDrag").innerHTML = "Delta from mouse position: (" + (e.clientX - DragOriginX).toString(10) + ", " + (e.clientY - DragOriginY).toString(10) + ")<br>"
        if (e.buttons == 1 && WhichElement) { // 1: Left Mouse Button
            WhichElement.value = (SourcePosition + (e.clientX - DragOriginX)).toString(10)
        }
    }
</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to automatically add a div to the window as the user scrolls and then hide the div when they scroll back to the

Seeking assistance with creating a function that will add a 'sticky' class to the menu when the user scrolls down to the middle, and then append a div when the 'sticky' class is present. Currently facing issues where the div keeps appen ...

Setting the default time zone to UTC for Material UI date and time picker

Looking to implement a dialog in React with Material UI for users to input start and end date/time. The goal is to have the default start set to the beginning of the current day and the default end set to the end of the current day (UTC). However, I'm ...

Error with jQuery variables

I'm encountering an issue with my code. The input box I have should display the value of a button when clicked. What I want is for the currently displayed value in the input box to be saved in a variable when a button is pressed, and then update to s ...

What are the steps for implementing videojs vr in a Vue.js project?

After installing the videojs vr with npm install --save videojs-vr, I attempted to use it in my project: https://i.stack.imgur.com/lSOqF.png However, I encountered this error message: https://i.stack.imgur.com/QSe1g.png Any assistance would be greatly ...

Access a designated tab using cbpFWTabs

I am currently using the cbpFWTabs plugin from Tympanus for my tabs (http://tympanus.net/Development/TabStylesInspiration/). However, I am struggling to open a specific tab upon page load. I have attempted to create a show method within the page script, bu ...

Using Backbone: Leveraging a custom extended model when fetching a collection

Currently, I am working on developing a custom Wordpress theme using technologies like Javascript, Requirejs, and Backbonejs. As part of this process, in the index route, I have set up a new postsCollection app.postsCollection = new Posts.Collection(); to ...

I am looking to access a public method from a different component in Angular 2

Trying to access the headerExpand property from app.component is causing an error message in the console: metadata_resolver.js:559 Uncaught Error: Invalid providers for "Page1" - only instances of Provider and Type are allowed, got: [?undefined?] page1 ...

Tips for automatically refreshing a Next.js application following an update in an external library

I have a monorepo containing two applications: The first is a Next.js web app The second is a UI library using Tailwind CSS and Microbundle Currently, the only way I can get the web app to recognize changes made in the UI library is by following these st ...

Unable to direct to the main page in index.js of the next.js application

I have been working on a next.js application and encountered an issue with a component I created called <ButtonGroup>. I added a button with the code <Button href="index">Home</Button> to allow users to navigate back to the home ...

What could be causing the NoScript tag to malfunction across different web browsers?

I've incorporated the NoScript tag into my JSP pages in the head section. To avoid conflicts with tiles, I made sure not to include multiple NoScript tags. However, I am experiencing issues in Internet Explorer where it doesn't seem to be working ...

Tips for minimizing padding in X-Range graphs

Is there a way to reduce the overall height of my X-Range chart by adjusting the padding on the Y axis around each series? I have experimented with different properties, such as adding groupPadding: 0, pointPadding: 0, and other parameters using this jsfi ...

I have created a Joomla Template that incorporates my own CSS through JavaScript. Is there a method to include a distinct version tag to my custom CSS file, such as custom.css?20180101?

My usual method involves adding a unique tag to the css path in the html section, but my template is incorporating custom CSS through Javascript: if (is_file(T3_TEMPLATE_PATH . '/css/custom.css')) { $this->addStyleSheet(T3_TEMPLATE_URL . &apo ...

Using JQuery to dynamically set dropdown option values from a JSON object

I have an HTML code snippet: $.ajax({ type: "POST", url: "hanca/hanca_crud.php", dataType: 'json', data: { id_hanca: id_hanca, type: "detail_hanca" }, //detail_hanca success: function(data) { var teks = ""; $.each( ...

Having trouble with Ajax and facebox integration issues?

My website utilizes ajax jquery and facebox for interactive features. You can check out a demo here. The div with the ID "#content" contains links to other pages that open successfully using facebox. However, when I reload the content of this div using aj ...

Designate the forward slash as the URL path

Can Express handle requests like this? app.get('/.test/abc', function(req, res) { res.send( 'abc test' ) }) So if I visit localhost:3000/.test/abc, it should display abc test. I tried it but it seems to not be working. Do I need t ...

Chrome successfully processes Ajax request, but Firefox encounters failure

I've encountered a peculiar issue with a JavaScript function that is responsible for making an Ajax call to a PHP page for database transactions and data processing. Take a look at the function below: function processQuizResults() { console.log(" ...

What is the best way to integrate JavaScript and Python for seamless collaboration?

I'm looking to create a bidirectional communication model between JavaScript and Python. The idea is for JavaScript to handle data processing, send it to Python for further processing, and then receive the results back from Python. However, I'm u ...

Develop a table with dynamic features in Angular according to the number of rows selected from a dropdown menu

I am currently exploring AngularJS 1.6 and tackling the challenge of dynamically populating a table with rows based on the number selected in a dropdown list, ranging from 1 to 12. Here's the code I have up until now: <body ng-controller="myContr ...

What is the best way to retrieve the state value in react once it has been changed?

How can I ensure that the react state 'country' is immediately accessible after setting it in the code below? Currently, I am only able to access the previous state value in the 'country' variable. Is there a method such as a callback o ...

Implement a new aggregate function for tooltips in the Kendo chart

I am currently utilizing a kendo chart with a date x-axis. Each point on the graph corresponds to different dates, but the x-axis displays only a monthly view. To showcase the last data point for each month, I have implemented a custom aggregate function a ...