Open corey-dawson opened 8 months ago
There are a number of features that people have asked for for number inputs. The current implementation doesn't give us much flexibility since we're just using an HTML <input type="number">
, but a JS-based implementation would allow us to do a lot more, in terms of formatting and validation.
Temporary fix to get currency-like formatted inputs. Will have to turn it back into a number when using it in the server
from pathlib import Path
from shiny import App, render, ui, reactive
# from shiny.types import ImgData
import os
import re
js = """
function formatCurrency(inputId) {
const inputElement = document.getElementById(inputId);
inputElement.addEventListener('keyup', function(e) {
let value = e.target.value;
// Remove all non-numeric characters
value = value.replace(/[^0-9.]/g, '');
// Format the number with commas
if (value) {
value = parseInt(value).toLocaleString('en-US', { minimumFractionDigits: 0 });
value = "$" + value;
} else {
value = "";
}
// Update the input value
inputElement.value = value;
});
}
// Call the function for each input element
formatCurrency('dollars1');
formatCurrency('dollars2');
"""
app_ui = ui.page_fluid(
ui.div(
ui.tags.label("Dollars1 input", style="width: 100%; margin-bottom: 0.5rem"),
ui.tags.input(type="text", id="dollars1", name="dollars", value="$20,000,000", style="padding: 0.375rem 0.75rem; font-size: 0.9375rem; font-weight: 400"),
stye="width: 300px; margin-bottom: 1rem"
),
ui.div(
ui.tags.label("Dollars1 input", style="width: 100%; margin-bottom: 0.5rem"),
ui.tags.input(type="text", id="dollars2", name="dollars", value="$100,000", style="padding: 0.375rem 0.75rem; font-size: 0.9375rem; font-weight: 400"),
stye="width: 300px; margin-bottom: 1rem"
),
ui.input_numeric("tst", "formatsteal", value=5),
ui.input_action_button("btn", "Update costs"),
ui.tags.script(js)
)
def server(input, output, session):
@reactive.Effect
@reactive.event(input.btn)
def prntVals():
print(f"input 1: {input.dollars1()}")
print(f"input 2: {input.dollars2()}")
print(f"input 3: {input.tst()}")
# make a number
rgx = r"[\$,]"
in_str = input.dollars1()
in_str = re.sub(rgx, "", in_str)
in_int = int(in_str)
print(f"input 1 as int: {in_int}")
app = App(app_ui, server)
@corey-dawson this is a great work around, thank you! just what I needed. How might I change it so that that the currency symbol can be varied? I have a use case where depending on another input, the currency symbol required is available on the server side say as a reactive value, i'd like to have the currency symbol be updated in the js function. not sure how to pass this is in dynamically, suspect I could get around it by having different inputs for each symbol (only need three currently) and then conditionally showing the correct one and coalescing the results of all three together afterwards, which would avoid js function changes but some duplication. Any suggestion for a useful approach?
@gdsutton, can update the JavaScript a bit. Function should input all arguments you want to be considered in the formatting. here is a simple example that updates on inputted number OR inputted currency symbol. Another note is you should be able to open the developer tools -> console in your browser and play around with the javascript. For example, could test getting the inputs from your app
Browser Console Testing
//in the browser terminal
curSel
//try printing
console.log("my current selection is: " + curSel)
Python Sample
from shiny import App, Inputs, Outputs, Session, reactive, ui
import shinyswatch
js = """
function formatCurrency(curSel, dollarAmt) {
let fmtValue = dollarAmt
//format
//add all formatting here. simplified for example
fmtValue = fmtValue.replace(/[^0-9.]/g, '');
fmtValue = curSel + fmtValue;
console.log(fmtValue);
//set value
dolAmt.value = fmtValue;
}
const curSel = document.getElementById("curSel");
const dolAmt = document.getElementById("dollars");
//Listener for one input
curSel.addEventListener("change", function() {
formatCurrency(curSel.value, dolAmt.value);
});
//listener for second input
dolAmt.addEventListener("change", function() {
formatCurrency(curSel.value, dolAmt.value);
});
//need to run the function on startup
formatCurrency(curSel.value, dolAmt.value);
"""
app_ui = ui.page_fluid(
shinyswatch.theme.darkly(),
ui.h1("MyApp"),
ui.input_select("curSel", "Currency Selection", choices=["$", "€"]),
ui.tags.input(type="text", id="dollars", name="Dollars", value=5),
ui.tags.script(js)
)
def server(input: Inputs, output: Outputs, session: Session):
print("do stuff")
app = App(app_ui, server)
There should be optional args to add a comma separator or dollar formatting to numeric inputs. Currently, only an number with no formatting is presented to the user. Would be much more user friendly if large numbers could have thousand separator and dollars could have the dollar symbol + comma separator.
additional args could be "separator": True/False "currency_symbol": "$" # appends to the front of the input