Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 51 additions & 24 deletions R/update-input.R
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
updateTextInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL, placeholder = NULL) {
validate_session_object(session)

message <- dropNulls(list(label=label, value=value, placeholder=placeholder))
message <- dropNulls(list(
label=processDeps(label, session),
value=value,
placeholder=placeholder
))
session$sendInputMessage(inputId, message)
}

Expand Down Expand Up @@ -111,7 +115,10 @@ updateTextAreaInput <- updateTextInput
updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, value = NULL) {
validate_session_object(session)

message <- dropNulls(list(label=label, value=value))
message <- dropNulls(list(
label=processDeps(label, session),
value=value
))
session$sendInputMessage(inputId, message)
}

Expand Down Expand Up @@ -175,13 +182,17 @@ updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, la
validate_session_object(session)

if (!is.null(icon)) icon <- as.character(validateIcon(icon))
message <- dropNulls(list(label=label, icon=icon, disabled=disabled))
message <- dropNulls(list(
label=processDeps(label, session),
icon=icon,
disabled=disabled
))
session$sendInputMessage(inputId, message)
}
#' @rdname updateActionButton
#' @export
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
updateActionButton(session, inputId=inputId, label=label, icon=icon)
updateActionButton(session, inputId=inputId, label=processDeps(label, session), icon=icon)
}


Expand Down Expand Up @@ -225,7 +236,12 @@ updateDateInput <- function(session = getDefaultReactiveDomain(), inputId, label
min <- dateYMD(min, "min")
max <- dateYMD(max, "max")

message <- dropNulls(list(label=label, value=value, min=min, max=max))
message <- dropNulls(list(
label=processDeps(label, session),
value=value,
min=min,
max=max
))
session$sendInputMessage(inputId, message)
}

Expand Down Expand Up @@ -275,10 +291,10 @@ updateDateRangeInput <- function(session = getDefaultReactiveDomain(), inputId,
max <- dateYMD(max, "max")

message <- dropNulls(list(
label = label,
value = dropNulls(list(start = start, end = end)),
min = min,
max = max
label=processDeps(label, session),
value=dropNulls(list(start = start, end = end)),
min=min,
max=max
))

session$sendInputMessage(inputId, message)
Expand Down Expand Up @@ -379,8 +395,11 @@ updateNumericInput <- function(session = getDefaultReactiveDomain(), inputId, la
validate_session_object(session)

message <- dropNulls(list(
label = label, value = formatNoSci(value),
min = formatNoSci(min), max = formatNoSci(max), step = formatNoSci(step)
label=processDeps(label, session),
value=formatNoSci(value),
min=formatNoSci(min),
max=formatNoSci(max),
step=formatNoSci(step)
))
session$sendInputMessage(inputId, message)
}
Expand Down Expand Up @@ -460,14 +479,14 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
}

message <- dropNulls(list(
label = label,
value = formatNoSci(value),
min = formatNoSci(min),
max = formatNoSci(max),
step = formatNoSci(step),
`data-type` = dataType,
`time-format` = timeFormat,
timezone = timezone
label=processDeps(label, session),
value=formatNoSci(value),
min=formatNoSci(min),
max=formatNoSci(max),
step=formatNoSci(step),
`data-type`=dataType,
`time-format`=timeFormat,
timezone=timezone
))
session$sendInputMessage(inputId, message)
}
Expand All @@ -491,7 +510,11 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
))
}

message <- dropNulls(list(label = label, options = options, value = selected))
message <- dropNulls(list(
label=processDeps(label, session),
options=options,
value=selected
))

session$sendInputMessage(inputId, message)
}
Expand Down Expand Up @@ -644,7 +667,11 @@ updateSelectInput <- function(session = getDefaultReactiveDomain(), inputId, lab
choices <- if (!is.null(choices)) choicesWithNames(choices)
if (!is.null(selected)) selected <- as.character(selected)
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
message <- dropNulls(list(label = label, options = options, value = selected))
message <- dropNulls(list(
label=processDeps(label, session),
options=options,
value=selected
))
session$sendInputMessage(inputId, message)
}

Expand Down Expand Up @@ -764,9 +791,9 @@ updateSelectizeInput <- function(session = getDefaultReactiveDomain(), inputId,
attr(choices, 'selected_value') <- value

message <- dropNulls(list(
label = label,
value = value,
url = session$registerDataObj(inputId, choices, selectizeJSON)
label=label,
value=value,
url=session$registerDataObj(inputId, choices, selectizeJSON)
))
session$sendInputMessage(inputId, message)
}
Expand Down
6 changes: 3 additions & 3 deletions srcts/src/bindings/input/checkboxgroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ class CheckboxGroupInputBinding extends InputBinding {
options: options,
};
}
receiveMessage(
async receiveMessage(
el: CheckboxGroupHTMLElement,
data: CheckboxGroupReceiveMessageData
): void {
): Promise<void> {
const $el = $(el);

// This will replace all the options
Expand All @@ -132,7 +132,7 @@ class CheckboxGroupInputBinding extends InputBinding {
this.setValue(el, data.value);
}

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

$(el).trigger("change");
}
Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,13 @@ class DateInputBinding extends DateInputBindingBase {
startview: startview,
};
}
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void {
async receiveMessage(
el: HTMLElement,
data: DateReceiveMessageData
): Promise<void> {
const $input = $(el).find("input");

updateLabel(data.label, this._getLabelNode(el));
await updateLabel(data.label, this._getLabelNode(el));

if (hasDefinedProperty(data, "min")) this._setMin($input[0], data.min);

Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/daterange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,16 @@ class DateRangeInputBinding extends DateInputBindingBase {
startview: startview,
};
}
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void {
async receiveMessage(
el: HTMLElement,
data: DateRangeReceiveMessageData
): Promise<void> {
const $el = $(el);
const $inputs = $el.find("input");
const $startinput = $inputs.eq(0);
const $endinput = $inputs.eq(1);

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

if (hasDefinedProperty(data, "min")) {
this._setMin($startinput[0], data.min);
Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ class NumberInputBinding extends TextInputBindingBase {
return "shiny.number";
el;
}
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void {
async receiveMessage(
el: NumberHTMLElement,
data: NumberReceiveMessageData
): Promise<void> {
// Setting values to `""` will remove the attribute value from the DOM element.
// The attr key will still remain, but there is not value... ex: `<input id="foo" type="number" min max/>`
if (hasDefinedProperty(data, "value")) el.value = data.value ?? "";
if (hasDefinedProperty(data, "min")) el.min = data.min ?? "";
if (hasDefinedProperty(data, "max")) el.max = data.max ?? "";
if (hasDefinedProperty(data, "step")) el.step = data.step ?? "";

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

$(el).trigger("change");
}
Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ class RadioInputBinding extends InputBinding {
options: options,
};
}
receiveMessage(el: RadioHTMLElement, data: RadioReceiveMessageData): void {
async receiveMessage(
el: RadioHTMLElement,
data: RadioReceiveMessageData
): Promise<void> {
const $el = $(el);
// This will replace all the options

Expand All @@ -122,7 +125,7 @@ class RadioInputBinding extends InputBinding {
this.setValue(el, data.value);
}

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

$(el).trigger("change");
}
Expand Down
6 changes: 3 additions & 3 deletions srcts/src/bindings/input/selectInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ class SelectInputBinding extends InputBinding {
options: options,
};
}
receiveMessage(
async receiveMessage(
el: SelectHTMLElement,
data: SelectInputReceiveMessageData
): void {
): Promise<void> {
const $el = $(el);

// This will replace all the options
Expand Down Expand Up @@ -199,7 +199,7 @@ class SelectInputBinding extends InputBinding {
this.setValue(el, data.value);
}

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

$(el).trigger("change");
}
Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ class SliderInputBinding extends TextInputBindingBase {
unsubscribe(el: HTMLElement): void {
$(el).off(".sliderInputBinding");
}
receiveMessage(el: HTMLElement, data: SliderReceiveMessageData): void {
async receiveMessage(
el: HTMLElement,
data: SliderReceiveMessageData
): Promise<void> {
const $el = $(el);
const slider = $el.data("ionRangeSlider");
const msg: {
Expand Down Expand Up @@ -226,7 +229,7 @@ class SliderInputBinding extends TextInputBindingBase {
}
}

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

// (maybe) update data elements
const domElements: Array<"data-type" | "time-format" | "timezone"> = [
Expand Down
7 changes: 5 additions & 2 deletions srcts/src/bindings/input/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,13 @@ class TextInputBinding extends TextInputBindingBase {
placeholder: el.placeholder,
};
}
receiveMessage(el: TextHTMLElement, data: TextReceiveMessageData): void {
async receiveMessage(
el: TextHTMLElement,
data: TextReceiveMessageData
): Promise<void> {
if (hasDefinedProperty(data, "value")) this.setValue(el, data.value);

updateLabel(data.label, getLabelNode(el));
await updateLabel(data.label, getLabelNode(el));

if (hasDefinedProperty(data, "placeholder"))
el.placeholder = data.placeholder;
Expand Down
22 changes: 16 additions & 6 deletions srcts/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import $ from "jquery";
import { windowDevicePixelRatio } from "../window/pixelRatio";
import type { MapValuesUnion, MapWithResult } from "./extraTypes";
import type { HtmlDep } from "../shiny/render";
import { hasOwnProperty, hasDefinedProperty } from "./object";
import { renderContent } from "../shiny/render";

function escapeHTML(str: string): string {
/* eslint-disable @typescript-eslint/naming-convention */
Expand Down Expand Up @@ -336,23 +338,31 @@ const compareVersion = function (
else throw `Unknown operator: ${op}`;
};

function updateLabel(
labelTxt: string | undefined,
async function updateLabel(
labelContent: string | { html: string; deps: HtmlDep[] } | undefined,
labelNode: JQuery<HTMLElement>
): void {
): Promise<void> {
// Only update if label was specified in the update method
if (typeof labelTxt === "undefined") return;
if (typeof labelContent === "undefined") return;
if (labelNode.length !== 1) {
throw new Error("labelNode must be of length 1");
}

if (typeof labelContent === "string") {
labelContent = {
html: labelContent,
deps: [],
};
}

// Should the label be empty?
const emptyLabel = Array.isArray(labelTxt) && labelTxt.length === 0;
const emptyLabel =
Array.isArray(labelContent.html) && labelContent.html.length === 0;
Copy link
Collaborator

@cpsievert cpsievert Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check might also now need to check for empty string? I noticed that this PR will break this "clear label" feature:

library(shiny)

ui <- fluidPage(
  checkboxInput("id", label = "foo")
)

server <- function(input, output, session) {
  updateCheckboxInput(inputId = "id", label = character(0))
}

shinyApp(ui, server)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch. Do we still need Array.isArray check? I'm not sure what's for to be honest; could we/can we pass a vector of length greater than 1 to label?

Asking because I wonder if it can't be simplified to just labelContent.html.length == 0 since "".length == 0 is true

Copy link
Collaborator

@cpsievert cpsievert Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I believe we can. Done in 6b97b63

It does make me realize that, with this PR updateFooInput(label = "") will now display:none the label container, which feels more like a feature than a bug


if (emptyLabel) {
labelNode.addClass("shiny-label-null");
} else {
labelNode.text(labelTxt);
await renderContent(labelNode, labelContent);
labelNode.removeClass("shiny-label-null");
}
}
Expand Down
2 changes: 1 addition & 1 deletion srcts/types/src/bindings/input/checkboxgroup.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ declare class CheckboxGroupInputBinding extends InputBinding {
value: ReturnType<CheckboxGroupInputBinding["getValue"]>;
options: ValueLabelObject[];
};
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): void;
receiveMessage(el: CheckboxGroupHTMLElement, data: CheckboxGroupReceiveMessageData): Promise<void>;
subscribe(el: CheckboxGroupHTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: CheckboxGroupHTMLElement): void;
}
Expand Down
2 changes: 1 addition & 1 deletion srcts/types/src/bindings/input/date.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ declare class DateInputBinding extends DateInputBindingBase {
format: string;
startview: DatepickerViewModes;
};
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): void;
receiveMessage(el: HTMLElement, data: DateReceiveMessageData): Promise<void>;
}
export { DateInputBinding, DateInputBindingBase };
export type { DateReceiveMessageData };
2 changes: 1 addition & 1 deletion srcts/types/src/bindings/input/daterange.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ declare class DateRangeInputBinding extends DateInputBindingBase {
language: string;
startview: string;
};
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): void;
receiveMessage(el: HTMLElement, data: DateRangeReceiveMessageData): Promise<void>;
initialize(el: HTMLElement): void;
subscribe(el: HTMLElement, callback: (x: boolean) => void): void;
unsubscribe(el: HTMLElement): void;
Expand Down
2 changes: 1 addition & 1 deletion srcts/types/src/bindings/input/number.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ declare class NumberInputBinding extends TextInputBindingBase {
getValue(el: NumberHTMLElement): string[] | number | string | null | undefined;
setValue(el: NumberHTMLElement, value: number): void;
getType(el: NumberHTMLElement): string;
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): void;
receiveMessage(el: NumberHTMLElement, data: NumberReceiveMessageData): Promise<void>;
getState(el: NumberHTMLElement): {
label: string;
value: ReturnType<NumberInputBinding["getValue"]>;
Expand Down
Loading