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 activate a click event on a particular child element within a parent element?

When attempting to click on a specific descendant of an element, I located the ancestor using ng-class since it lacked an id. yes = document.querySelectorAll('[ng-controller = "inventoryController"]') Having found the desired ancestor, ...

Tips for modifying JSON response using a function

When I call the function buildFileTree, I store its response in a constant variable called data. const data = this.buildFileTree(dataObject, 0); The value of dataObject is: const dataObject = JSON.parse(TREE_DATA); And the content of TREE_DATA is: cons ...

The Lightgallery plugin is creating three duplicates of the slides

I am currently facing an issue with a gallery that loads images from an API and displays them using the lightgallery plugin. Upon implementing the lightbox in the correct location (view question here), I discovered that the plugin is generating three slid ...

When using Next.js revalidate, an error may occur stating: "There are keys that require relocation: revalidate

I have been attempting to utilize the revalidate function in my code by following the example provided by Vercel. However, I keep encountering an error. Here is the snippet of code that I am currently using: export async function getServerSideProps() { c ...

Is there a way to transfer the value from one directive to another directive template and access it in a different directive's scope?

I attempted to pass the directive attribute value to a template ID, which can then be used in another directive. Below is my index.html code: <my-value name="jhon"></my-value> Here is the JavaScript code: .directive('myValue',func ...

Another project cannot import the library that was constructed

I am in the process of creating a library that acts as a wrapper for a soap API. The layout of the library is structured like this: |-node_modules | |-src | |-soapWrapperLibrary.ts | |-soapLibraryClient.ts | |-types | |-soapResponseType.d.ts The libra ...

Saving an item using localStorage

I've been struggling to figure out how to make localStorage save the clicks variable even after refreshing the browser. Initially, I attempted using JSON.stringify and JSON.parse but later discovered that using parseInt could be a more suitable optio ...

Enhance your figures with a unique Javascript magnifying tool that works seamlessly across all browsers

After searching the web for magnifying glasses, I found that most only work for one picture. So, I took matters into my own hands and created a magnifying glass that can magnify all pictures within a specific div. It functions perfectly on Chrome browser b ...

Receiving data from multiple sockets in Node.js with Socket.io

I recently started working with Node.js to develop an online game that acts as a server-side application. This application serves the .html and .js files to the client while managing the game's logic. I'm utilizing Socket.io for communication bet ...

Problem with Onsen UI navigation: It is not possible to provide a "ons-page" element to "ons-navigator" when attempting to navigate back to the initial page

Hi, I am having trouble with navigation using Onsen UI. Here is the structure of my app: start.html: This is the first page that appears and it contains a navigator. Clicking on the start button will open page1.html page1.html: Performs an action that op ...

Place a gap at a specific spot within the boundary line

Here is a CSS code snippet that displays a horizontal line: .horizontalLineBottom { border-bottom:solid #6E6A6B; border-width:1px; } Is it possible to add space at a specific position on this line? For example, _________________________________ ...

Guide to implementing bidirectional data binding for a particular element within a dynamic array with an automatically determined index

Imagine having a JavaScript dynamic array retrieved from a database: customers = [{'id':1, 'name':'John'},{'id':2, 'name':'Tim}, ...] Accompanied by input fields: <input type='text' na ...

Tips for including a new class in an HTML element

My goal is to add a specific class to an HTML tag, if that tag exists. This is the code I have attempted: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>index</title> <style> ...

The React loader fails to function properly when used with nested routes

I'm currently working on my App.js file where I have defined all the routes for my application. I wanted to implement React-Router data loader functionality. import React from 'react' import { Routes, Route, Navigate, RouterProvider, createB ...

Exploring the implementation of query parameters in Nest.js

I am currently a freshman in the world of Nest.js. Below is an excerpt from my code: @Get('findByFilter/:params') async findByFilter(@Query() query): Promise<Article[]> { } I have utilized Postman to test this specific router. ht ...

What is the best way to fetch values from individual buttons using PHP?

<form action="posts-ayarlar.php" method="POST" id="demo-form2" data-parsley-validate class="form-horizontal form-label-left"> <table class="table table-striped table-bordered" ...

Having trouble with the installation of nodemon globally on macOS Mojave?

When using the Visual Studio Code terminal, I ran the following command: npm install -g nodemon The output in the terminal showed: npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules npm ERR! code EACCES npm ERR! syscall access n ...

Having trouble with JavaScript function returning false?

I am trying to call a JavaScript function on an ASP.NET button client click event and want to prevent postback. The function is working, but it is not preventing the page from posting back. Here is my JavaScript code: function User2Check() { v ...

Exploring jQuery functionalities for dynamic selector input

I have encountered a scenario where I must use dynamic ID's in the format of functionalDescription_IDNUMBER across my page. It is necessary to target specific areas based on the unique IDNUMBER associated with the clicked object. However, I am looking ...

The Foundation 6 Zurb Template is not compatible for offline use

After successfully installing Foundation 6 Zurb Template via the cli, I encountered no issues. I then added the missing babel install and everything worked fine online. However, BrowserSync does not seem to work offline. Upon initiating watch, I receive a ...