callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

callback! function refactoring - #39

Merged
waralex merged 3 commits into
devfrom
callbacks_refactoring
Jun 8, 2020
Merged

callback! function refactoring#39
waralex merged 3 commits into
devfrom
callbacks_refactoring

Conversation

@waralex

@waralexwaralex commented May 29, 2020

Copy link
Copy Markdown
Contributor

Refactoring of callbacks

  • string macro callid removed
  • separate classes Input, Output and State added
  • signature of callback! changed to
functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
  • the order of arguments in the callback handler is changed to inputs..., states...
  • CallbackId renamed to CallbackDeps and removed from exports (i.e. from public API)

Now the only way to set the callback is like:

callback!(app, Output("display-all-of-the-values", "value"),
[Input("x","value"), Input("y","value"), Input("x-plus-y","value"), Input("x-plus-y-div-2","value")],
[State("x","value"), State("y","value")]
) do args...returnjoin(string.(args), "\n")
end

The ability to pass a dictionary(named tuple in the case of Julia) as id will be done in a separate PR associated with pattern-matching. In this PR, I do not add new functionality, only bring the existing one to its normal appearance

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation enhancement New feature or request tests labels May 29, 2020
@waralex
waralex requested a review from alexcjohnsonMay 29, 2020 08:33
@alexcjohnson

Copy link
Copy Markdown
Contributor

Very nice - what you've implemented here is very similar to the current Python version, which will really help.

There's also a PR open right now on the Python side to allow all the outputs, inputs, and states to be passed without nesting them in a list, with the constraint that outputs come first, then inputs, then states: plotly/dash#1180 (it's named "Single Input" as it started out just allowing a single input to be unnested, but was then generalized, and we do intend to accept it once it's finished).

I suppose we could always add this as an additional method (which is effectively what we're doing in Python with that PR) but I wonder if - since we're free of the backward-compatibility constraint here - we shouldn't just make the unnested form the only way to do it?

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order - I was hoping Julia would allow defining a function with a signature with multiple varargs like

f(app::dashApp, outputs::Output..., inputs::Input..., states::State...)

but apparently not 🤷

The only behavior I can think of that this would prevent is returning a single output as a one-item list. That's a pretty weird use case, but theoretically I can imagine wanting it for creating callbacks programmatically if sometimes there's one output and sometimes more, but your function always returns a list.

So I guess unless you can think of another simple way to disambiguate a single output in a list or not, we should keep the implementation you have here and add the varargs form as an alternative, just like we're doing in Python. Which means it need not happen in this PR, but it could if you like.

@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson

I was hoping Julia would allow defining a function with a signature with multiple varargs like

Unfortunately the function can only have one varargs argument

The only behavior I can think of that this would prevent is returning a single output as a one-item list.

To be honest, I didn't really understand the problem, could you explain it in more detail and give an example?

In principle, there is nothing complicated about making a function accept an arbitrary number of both individual elements and arrays of elements.

callback!(app,
Output("f","f"),
[Output("ff","fff"), ...],
Output("gg","gg"),
Input("gg","ggg")
.....
) ....

@waralex

Copy link
Copy Markdown
ContributorAuthor

Unfortunately I guess that would require a manual type check to find the first Input and State and throw if they're out of order

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream. But it would allow such things to be possible:

callback!(app, Output("my-div", "children"),
State("my-input", "value"), #inportant valuemake_inputs()...#long generated list of inputs
) do important_state, args...return args[important_state]
end

@alexcjohnson

Copy link
Copy Markdown
Contributor

If the only syntax we provide is varargs, then a single-output callback would look like:

callback!(app,
Output("out1", "children"),
Input("in1", "value")
) do input
return val
end

And a multi-output callback would look like:

callback!(app,
Output("out1", "children"),
Output("out2", "children"),
Input("in1", "value")
) do input
return [val1, val2]
end

But what if you want your single output to be nested in a list/vector? With the way this PR looks today, it would be:

callback!(app,
[Output("out1", "children")],
Input("in1", "value")
) do input
return [val]
end

but there would be no way to specify this in the varargs form. Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have. It's a weird case (and there's probably even less use for it after we have pattern-matching callbacks) but the only way I see to allow it is if Output(...) and [Output(...)] are treated differently.

@alexcjohnson

Copy link
Copy Markdown
Contributor

With this approach, it would make sense to ensure instead that arguments are passed to the callback in the same order as they are defined in the function. But this is different from the behavior in Python, so it's probably just a dream.

Yes, in principle that could be supported, but as I said in plotly/dash#1180 (comment):

I think it's still important to have the items in order: outputs, inputs, state, rather than mixing them up. It would be super confusing to have outputs mixed in with the others, so definitely we need those to be first. Less confusing to have inputs and state mixed up, and I can see the rationale of grouping related items, but the distinction between inputs and state is important enough to the logic of the callback that I think it's worth keeping them separate.

Let's enforce the ordering outputs..., inputs..., states... for now, we can always loosen the constraint later but it would be a breaking change to take a loose constraint and make it stricter.

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

Normally you don't want this, but maybe you would if you're programmatically generating the callback function and you don't know ahead of time how many outputs it will have

I understood. Thank you, this is an interesting case, I didn't think about it. In fact, Dash now stores output always as an array. and in what form it is given to the frontend is determined based on array size:

functionoutput_string(deps::CallbackDeps)
iflength(deps.output) ==1returndependency_string(deps.output[1])
endreturn".."*join(dependency_string.(deps.output), "...") *".."end

So I should learn more about how this works if an array of a single element is returned from the callback. Apparently the problem is deeper and I need to fix this part too.
What do you think about allowing to pass both single elements and arrays to varargs version?

@waralex

waralex commented May 29, 2020

Copy link
Copy Markdown
ContributorAuthor

I tend to think that it is most convenient and understandable to still have 2 versions. One as now and the second with varagrs, in which only individual elements can be passed to varargs. The ability to mix single elements and arrays in varargs will confuse the user.

I.e. 2 overloads:

functioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
output::Union{Vector{Output}, Output},
input::Union{Vector{Input}, Input},
state::Union{Vector{State}, State}= State[]
)
endfunctioncallback!(func::Union{Function, ClientsideFunction, String},
app::DashApp,
deps::Dependency...
)
end

@waralexwaralex mentioned this pull request Jun 4, 2020
@waralex

Copy link
Copy Markdown
ContributorAuthor

@alexcjohnson in 7c36096 I added a flat version of callback! and fixed the work with a single element array output

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks great! Very nicely done, and excellent tests. 💃

@waralex
waralex merged commit 308cee3 into devJun 8, 2020
@etpinard
etpinard deleted the callbacks_refactoring branch June 13, 2023 14:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationenhancementNew feature or requesttests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename id argument of callback! to params to match Dash convention Assert and validate Dash component functionality

2 participants

@waralex@alexcjohnson